diff --git a/src/Message/ExpressAuthorizeRequest.php b/src/Message/ExpressAuthorizeRequest.php index e05f3bf..fd08b88 100644 --- a/src/Message/ExpressAuthorizeRequest.php +++ b/src/Message/ExpressAuthorizeRequest.php @@ -2,14 +2,86 @@ namespace Omnipay\PayPal\Message; +use Omnipay\Common\Exception\InvalidRequestException; +use Omnipay\PayPal\Support\InstantUpdateApi\ShippingOption; + /** * PayPal Express Authorize Request */ class ExpressAuthorizeRequest extends AbstractRequest { + + const DEFAULT_CALLBACK_TIMEOUT = 5; + + public function setCallback($callback) + { + return $this->setParameter('callback', $callback); + } + + public function getCallback() + { + return $this->getParameter('callback'); + } + + public function setCallbackTimeout($callbackTimeout) + { + return $this->setParameter('callbackTimeout', $callbackTimeout); + } + + public function getCallbackTimeout() + { + return $this->getParameter('callbackTimeout'); + } + + /** + * @param ShippingOption[] $data + */ + public function setShippingOptions($data) + { + $this->setParameter('shippingOptions', $data); + } + + /** + * @return ShippingOption[] + */ + public function getShippingOptions() + { + return $this->getParameter('shippingOptions'); + } + + protected function validateCallback() + { + $callback = $this->getCallback(); + + if (!empty($callback)) { + $shippingOptions = $this->getShippingOptions(); + + if (empty($shippingOptions)) { + throw new InvalidRequestException( + 'When setting a callback for the Instant Update API you must set shipping options' + ); + } else { + $hasDefault = false; + foreach ($shippingOptions as $shippingOption) { + if ($shippingOption->isDefault()) { + $hasDefault = true; + continue; + } + } + + if (!$hasDefault) { + throw new InvalidRequestException( + 'One of the supplied shipping options must be set as default' + ); + } + } + } + } + public function getData() { $this->validate('amount', 'returnUrl', 'cancelUrl'); + $this->validateCallback(); $data = $this->getBaseData(); $data['METHOD'] = 'SetExpressCheckout'; @@ -34,6 +106,31 @@ public function getData() $data['LOCALECODE'] = $this->getLocaleCode(); $data['CUSTOMERSERVICENUMBER'] = $this->getCustomerServiceNumber(); + $callback = $this->getCallback(); + + if (!empty($callback)) { + $data['CALLBACK'] = $callback; + // callback timeout MUST be included and > 0 + $timeout = $this->getCallbackTimeout(); + + $data['CALLBACKTIMEOUT'] = $timeout > 0 ? $timeout : self::DEFAULT_CALLBACK_TIMEOUT; + + // if you're using a callback you MUST set shipping option(s) + $shippingOptions = $this->getShippingOptions(); + + if (!empty($shippingOptions)) { + foreach ($shippingOptions as $index => $shipping) { + $data['L_SHIPPINGOPTIONNAME' . $index] = $shipping->getName(); + $data['L_SHIPPINGOPTIONAMOUNT' . $index] = number_format($shipping->getAmount(), 2); + $data['L_SHIPPINGOPTIONISDEFAULT' . $index] = $shipping->isDefault() ? '1' : '0'; + + if ($shipping->hasLabel()) { + $data['L_SHIPPINGOPTIONLABEL' . $index] = $shipping->getLabel(); + } + } + } + } + $data['MAXAMT'] = $this->getMaxAmount(); $data['PAYMENTREQUEST_0_TAXAMT'] = $this->getTaxAmount(); $data['PAYMENTREQUEST_0_SHIPPINGAMT'] = $this->getShippingAmount(); diff --git a/src/Support/InstantUpdateApi/ShippingOption.php b/src/Support/InstantUpdateApi/ShippingOption.php new file mode 100644 index 0000000..aba549d --- /dev/null +++ b/src/Support/InstantUpdateApi/ShippingOption.php @@ -0,0 +1,72 @@ +name = $name; + $this->amount = $amount; + $this->isDefault = $isDefault; + $this->label = $label; + } + + /** + * @return bool + */ + public function hasLabel() + { + return !is_null($this->label); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return float + */ + public function getAmount() + { + return $this->amount; + } + + /** + * @return boolean + */ + public function isDefault() + { + return $this->isDefault; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } +} diff --git a/tests/Message/ExpressAuthorizeRequestTest.php b/tests/Message/ExpressAuthorizeRequestTest.php index 16848af..b7186de 100644 --- a/tests/Message/ExpressAuthorizeRequestTest.php +++ b/tests/Message/ExpressAuthorizeRequestTest.php @@ -3,6 +3,7 @@ namespace Omnipay\PayPal\Message; use Omnipay\Common\CreditCard; +use Omnipay\PayPal\Support\InstantUpdateApi\ShippingOption; use Omnipay\Tests\TestCase; class ExpressAuthorizeRequestTest extends TestCase @@ -215,4 +216,158 @@ public function testMaxAmount() $this->assertSame(321.54, $data['MAXAMT']); } + + public function testDataWithCallback() + { + $baseData = array( + 'amount' => '10.00', + 'currency' => 'AUD', + 'transactionId' => '111', + 'description' => 'Order Description', + 'returnUrl' => 'https://www.example.com/return', + 'cancelUrl' => 'https://www.example.com/cancel', + 'subject' => 'demo@example.com', + 'headerImageUrl' => 'https://www.example.com/header.jpg', + 'allowNote' => 0, + 'addressOverride' => 0, + 'brandName' => 'Dunder Mifflin Paper Company, Incy.', + ); + + $shippingOptions = array( + new ShippingOption('First Class', 1.20, true, '1-2 days'), + new ShippingOption('Second Class', 0.70, false, '3-5 days'), + new ShippingOption('International', 3.50), + ); + + // with a default callback timeout + $this->request->initialize(array_merge($baseData, array( + 'callback' => 'https://www.example.com/calculate-shipping', + 'shippingOptions' => $shippingOptions, + ))); + + $data = $this->request->getData(); + $this->assertSame('https://www.example.com/calculate-shipping', $data['CALLBACK']); + $this->assertSame(ExpressAuthorizeRequest::DEFAULT_CALLBACK_TIMEOUT, $data['CALLBACKTIMEOUT']); + + $this->assertSame('First Class', $data['L_SHIPPINGOPTIONNAME0']); + $this->assertSame('1.20', $data['L_SHIPPINGOPTIONAMOUNT0']); + $this->assertSame('1', $data['L_SHIPPINGOPTIONISDEFAULT0']); + $this->assertSame('1-2 days', $data['L_SHIPPINGOPTIONLABEL0']); + + $this->assertSame('Second Class', $data['L_SHIPPINGOPTIONNAME1']); + $this->assertSame('0.70', $data['L_SHIPPINGOPTIONAMOUNT1']); + $this->assertSame('0', $data['L_SHIPPINGOPTIONISDEFAULT1']); + $this->assertSame('3-5 days', $data['L_SHIPPINGOPTIONLABEL1']); + + $this->assertSame('International', $data['L_SHIPPINGOPTIONNAME2']); + $this->assertSame('3.50', $data['L_SHIPPINGOPTIONAMOUNT2']); + $this->assertSame('0', $data['L_SHIPPINGOPTIONISDEFAULT2']); + + // with a defined callback timeout + $this->request->initialize(array_merge($baseData, array( + 'callback' => 'https://www.example.com/calculate-shipping', + 'callbackTimeout' => 10, + 'shippingOptions' => $shippingOptions, + ))); + + $data = $this->request->getData(); + $this->assertSame('https://www.example.com/calculate-shipping', $data['CALLBACK']); + $this->assertSame(10, $data['CALLBACKTIMEOUT']); + } + + public function testDataWithCallbackAndNoDefaultShippingOption() + { + $baseData = array( + 'amount' => '10.00', + 'currency' => 'AUD', + 'transactionId' => '111', + 'description' => 'Order Description', + 'returnUrl' => 'https://www.example.com/return', + 'cancelUrl' => 'https://www.example.com/cancel', + 'subject' => 'demo@example.com', + 'headerImageUrl' => 'https://www.example.com/header.jpg', + 'allowNote' => 0, + 'addressOverride' => 0, + 'brandName' => 'Dunder Mifflin Paper Company, Incy.', + ); + + $shippingOptions = array( + new ShippingOption('First Class', 1.20, false, '1-2 days'), + new ShippingOption('Second Class', 0.70, false, '3-5 days'), + new ShippingOption('International', 3.50), + ); + + // with a default callback timeout + $this->request->initialize(array_merge($baseData, array( + 'callback' => 'https://www.example.com/calculate-shipping', + 'shippingOptions' => $shippingOptions, + ))); + + $this->setExpectedException( + '\Omnipay\Common\Exception\InvalidRequestException', + 'One of the supplied shipping options must be set as default' + ); + + $this->request->getData(); + } + + public function testNoAmount() + { + $baseData = array(// nothing here - should cause a certain exception + ); + + $this->request->initialize($baseData); + + $this->setExpectedException( + '\Omnipay\Common\Exception\InvalidRequestException', + 'The amount parameter is required' + ); + + $this->request->getData(); + } + + public function testAmountButNoReturnUrl() + { + $baseData = array( + 'amount' => 10.00, + ); + + $this->request->initialize($baseData); + + $this->setExpectedException( + '\Omnipay\Common\Exception\InvalidRequestException', + 'The returnUrl parameter is required' + ); + + $this->request->getData(); + } + + public function testBadCallbackConfiguration() + { + $baseData = array( + 'amount' => '10.00', + 'currency' => 'AUD', + 'transactionId' => '111', + 'description' => 'Order Description', + 'returnUrl' => 'https://www.example.com/return', + 'cancelUrl' => 'https://www.example.com/cancel', + 'subject' => 'demo@example.com', + 'headerImageUrl' => 'https://www.example.com/header.jpg', + 'allowNote' => 0, + 'addressOverride' => 0, + 'brandName' => 'Dunder Mifflin Paper Company, Incy.', + ); + + $this->request->initialize(array_merge($baseData, array( + 'callback' => 'https://www.example.com/calculate-shipping', + ))); + + // from the docblock on this exception - + // Thrown when a request is invalid or missing required fields. + // callback has been set but no shipping options so expect one of these: + $this->setExpectedException('\Omnipay\Common\Exception\InvalidRequestException'); + + $this->request->getData(); + } + }