From e5be45b540b31047b5e7a36cf6374df9b2d7679d Mon Sep 17 00:00:00 2001 From: Flosch Date: Fri, 2 Sep 2016 11:22:39 +0200 Subject: [PATCH] [In-Context] Create a new PayPal Express In-Context gateway extending PayPal Express --- src/ExpressInContextGateway.php | 24 ++ src/Message/ExpressAuthorizeResponse.php | 17 +- .../ExpressInContextAuthorizeRequest.php | 14 + .../ExpressInContextAuthorizeResponse.php | 20 + src/Message/ExpressInContextOrderRequest.php | 17 + tests/ExpressInContextGatewayTest.php | 78 ++++ tests/Message/ExpressAuthorizeRequestTest.php | 1 + .../Message/ExpressAuthorizeResponseTest.php | 1 + .../ExpressInContextAuthorizeRequestTest.php | 374 ++++++++++++++++++ .../ExpressInContextAuthorizeResponseTest.php | 45 +++ 10 files changed, 584 insertions(+), 7 deletions(-) create mode 100644 src/ExpressInContextGateway.php create mode 100644 src/Message/ExpressInContextAuthorizeRequest.php create mode 100644 src/Message/ExpressInContextAuthorizeResponse.php create mode 100644 src/Message/ExpressInContextOrderRequest.php create mode 100644 tests/ExpressInContextGatewayTest.php create mode 100644 tests/Message/ExpressInContextAuthorizeRequestTest.php create mode 100644 tests/Message/ExpressInContextAuthorizeResponseTest.php diff --git a/src/ExpressInContextGateway.php b/src/ExpressInContextGateway.php new file mode 100644 index 0000000..a2e9ac9 --- /dev/null +++ b/src/ExpressInContextGateway.php @@ -0,0 +1,24 @@ +createRequest('\Omnipay\PayPal\Message\ExpressInContextAuthorizeRequest', $parameters); + } + + public function order(array $parameters = array()) + { + return $this->createRequest('\Omnipay\PayPal\Message\ExpressInContextOrderRequest', $parameters); + } +} diff --git a/src/Message/ExpressAuthorizeResponse.php b/src/Message/ExpressAuthorizeResponse.php index a5e185b..2574beb 100644 --- a/src/Message/ExpressAuthorizeResponse.php +++ b/src/Message/ExpressAuthorizeResponse.php @@ -24,13 +24,7 @@ public function isRedirect() public function getRedirectUrl() { - $query = array( - 'cmd' => '_express-checkout', - 'useraction' => 'commit', - 'token' => $this->getTransactionReference(), - ); - - return $this->getCheckoutEndpoint().'?'.http_build_query($query, '', '&'); + return $this->getCheckoutEndpoint().'?'.http_build_query($this->getRedirectQueryParameters(), '', '&'); } public function getTransactionReference() @@ -48,6 +42,15 @@ public function getRedirectData() return null; } + protected function getRedirectQueryParameters() + { + return array( + 'cmd' => '_express-checkout', + 'useraction' => 'commit', + 'token' => $this->getTransactionReference(), + ); + } + protected function getCheckoutEndpoint() { return $this->getRequest()->getTestMode() ? $this->testCheckoutEndpoint : $this->liveCheckoutEndpoint; diff --git a/src/Message/ExpressInContextAuthorizeRequest.php b/src/Message/ExpressInContextAuthorizeRequest.php new file mode 100644 index 0000000..e07962e --- /dev/null +++ b/src/Message/ExpressInContextAuthorizeRequest.php @@ -0,0 +1,14 @@ +response = new ExpressInContextAuthorizeResponse($this, $data); + } +} diff --git a/src/Message/ExpressInContextAuthorizeResponse.php b/src/Message/ExpressInContextAuthorizeResponse.php new file mode 100644 index 0000000..421be9f --- /dev/null +++ b/src/Message/ExpressInContextAuthorizeResponse.php @@ -0,0 +1,20 @@ + 'commit', + 'token' => $this->getTransactionReference(), + ); + } +} diff --git a/src/Message/ExpressInContextOrderRequest.php b/src/Message/ExpressInContextOrderRequest.php new file mode 100644 index 0000000..4b7c3b0 --- /dev/null +++ b/src/Message/ExpressInContextOrderRequest.php @@ -0,0 +1,17 @@ +gateway = new ExpressInContextGateway($this->getHttpClient(), $this->getHttpRequest()); + + $this->options = array( + 'amount' => '10.00', + 'returnUrl' => 'https://www.example.com/return', + 'cancelUrl' => 'https://www.example.com/cancel', + ); + $this->voidOptions = array( + 'transactionReference' => 'ASDFASDFASDF', + ); + } + + public function testAuthorizeSuccess() + { + $this->setMockHttpResponse('ExpressPurchaseSuccess.txt'); + + $response = $this->gateway->authorize($this->options)->send(); + + $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); + $this->assertFalse($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); + } + + public function testPurchaseSuccess() + { + $this->setMockHttpResponse('ExpressPurchaseSuccess.txt'); + + $response = $this->gateway->purchase($this->options)->send(); + + $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); + $this->assertFalse($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); + } + + public function testOrderSuccess() + { + $this->setMockHttpResponse('ExpressOrderSuccess.txt'); + + $response = $this->gateway->order($this->options)->send(); + + $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); + $this->assertFalse($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); + } +} diff --git a/tests/Message/ExpressAuthorizeRequestTest.php b/tests/Message/ExpressAuthorizeRequestTest.php index a9cc483..b110e4d 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\Message\ExpressAuthorizeRequest; use Omnipay\PayPal\Support\InstantUpdateApi\ShippingOption; use Omnipay\Tests\TestCase; diff --git a/tests/Message/ExpressAuthorizeResponseTest.php b/tests/Message/ExpressAuthorizeResponseTest.php index 588bdd1..e80db7a 100644 --- a/tests/Message/ExpressAuthorizeResponseTest.php +++ b/tests/Message/ExpressAuthorizeResponseTest.php @@ -3,6 +3,7 @@ namespace Omnipay\PayPal\Message; use Omnipay\Tests\TestCase; +use Omnipay\PayPal\Message\ExpressAuthorizeResponse; class ExpressAuthorizeResponseTest extends TestCase { diff --git a/tests/Message/ExpressInContextAuthorizeRequestTest.php b/tests/Message/ExpressInContextAuthorizeRequestTest.php new file mode 100644 index 0000000..8471fb0 --- /dev/null +++ b/tests/Message/ExpressInContextAuthorizeRequestTest.php @@ -0,0 +1,374 @@ +request = new ExpressInContextAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->initialize( + array( + 'amount' => '10.00', + 'returnUrl' => 'https://www.example.com/return', + 'cancelUrl' => 'https://www.example.com/cancel', + ) + ); + } + + public function testGetDataWithoutCard() + { + $this->request->initialize(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', + 'noShipping' => 0, + 'localeCode' => 'EN', + 'allowNote' => 0, + 'addressOverride' => 0, + 'brandName' => 'Dunder Mifflin Paper Company, Inc.', + 'customerServiceNumber' => '1-801-FLOWERS', + )); + + $data = $this->request->getData(); + + $this->assertSame('10.00', $data['PAYMENTREQUEST_0_AMT']); + $this->assertSame('AUD', $data['PAYMENTREQUEST_0_CURRENCYCODE']); + $this->assertSame('111', $data['PAYMENTREQUEST_0_INVNUM']); + $this->assertSame('Order Description', $data['PAYMENTREQUEST_0_DESC']); + $this->assertSame('https://www.example.com/return', $data['RETURNURL']); + $this->assertSame('https://www.example.com/cancel', $data['CANCELURL']); + $this->assertSame('demo@example.com', $data['SUBJECT']); + $this->assertSame('https://www.example.com/header.jpg', $data['HDRIMG']); + $this->assertSame(0, $data['NOSHIPPING']); + $this->assertSame(0, $data['ALLOWNOTE']); + $this->assertSame('EN', $data['LOCALECODE']); + $this->assertSame(0, $data['ADDROVERRIDE']); + $this->assertSame('Dunder Mifflin Paper Company, Inc.', $data['BRANDNAME']); + $this->assertSame('1-801-FLOWERS', $data['CUSTOMERSERVICENUMBER']); + } + + public function testGetDataWithCard() + { + $this->request->initialize(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', + 'noShipping' => 2, + 'allowNote' => 1, + 'addressOverride' => 1, + 'brandName' => 'Dunder Mifflin Paper Company, Inc.', + 'maxAmount' => 123.45, + 'logoImageUrl' => 'https://www.example.com/logo.jpg', + 'borderColor' => 'CCCCCC', + 'localeCode' => 'EN', + 'customerServiceNumber' => '1-801-FLOWERS', + 'sellerPaypalAccountId' => 'billing@example.com', + )); + + $card = new CreditCard(array( + 'name' => 'John Doe', + 'address1' => '123 NW Blvd', + 'address2' => 'Lynx Lane', + 'city' => 'Topeka', + 'state' => 'KS', + 'country' => 'USA', + 'postcode' => '66605', + 'phone' => '555-555-5555', + 'email' => 'test@email.com', + )); + $this->request->setCard($card); + + $expected = array( + 'METHOD' => 'SetExpressCheckout', + 'VERSION' => ExpressInContextAuthorizeRequest::API_VERSION, + 'USER' => null, + 'PWD' => null, + 'SIGNATURE' => null, + 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Authorization', + 'SOLUTIONTYPE' => null, + 'LANDINGPAGE' => null, + 'NOSHIPPING' => 2, + 'ALLOWNOTE' => 1, + 'ADDROVERRIDE' => 1, + 'PAYMENTREQUEST_0_AMT' => '10.00', + 'PAYMENTREQUEST_0_CURRENCYCODE' => 'AUD', + 'PAYMENTREQUEST_0_INVNUM' => '111', + 'PAYMENTREQUEST_0_DESC' => 'Order Description', + 'RETURNURL' => 'https://www.example.com/return', + 'CANCELURL' => 'https://www.example.com/cancel', + 'SUBJECT' => 'demo@example.com', + 'HDRIMG' => 'https://www.example.com/header.jpg', + 'PAYMENTREQUEST_0_SHIPTONAME' => 'John Doe', + 'PAYMENTREQUEST_0_SHIPTOSTREET' => '123 NW Blvd', + 'PAYMENTREQUEST_0_SHIPTOSTREET2' => 'Lynx Lane', + 'PAYMENTREQUEST_0_SHIPTOCITY' => 'Topeka', + 'PAYMENTREQUEST_0_SHIPTOSTATE' => 'KS', + 'PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE' => 'USA', + 'PAYMENTREQUEST_0_SHIPTOZIP' => '66605', + 'PAYMENTREQUEST_0_SHIPTOPHONENUM' => '555-555-5555', + 'EMAIL' => 'test@email.com', + 'BRANDNAME' => 'Dunder Mifflin Paper Company, Inc.', + 'MAXAMT' => 123.45, + 'PAYMENTREQUEST_0_TAXAMT' => null, + 'PAYMENTREQUEST_0_SHIPPINGAMT' => null, + 'PAYMENTREQUEST_0_HANDLINGAMT' => null, + 'PAYMENTREQUEST_0_SHIPDISCAMT' => null, + 'PAYMENTREQUEST_0_INSURANCEAMT' => null, + 'LOGOIMG' => 'https://www.example.com/logo.jpg', + 'CARTBORDERCOLOR' => 'CCCCCC', + 'LOCALECODE' => 'EN', + 'CUSTOMERSERVICENUMBER' => '1-801-FLOWERS', + 'PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID' => 'billing@example.com', + ); + + $this->assertEquals($expected, $this->request->getData()); + } + + public function testGetDataWithItems() + { + $this->request->setItems(array( + array('name' => 'Floppy Disk', 'description' => 'MS-DOS', 'quantity' => 2, 'price' => 10, 'code' => '123456'), + array('name' => 'CD-ROM', 'description' => 'Windows 95', 'quantity' => 1, 'price' => 40), + )); + + $data = $this->request->getData(); + $this->assertSame('Floppy Disk', $data['L_PAYMENTREQUEST_0_NAME0']); + $this->assertSame('MS-DOS', $data['L_PAYMENTREQUEST_0_DESC0']); + $this->assertSame(2, $data['L_PAYMENTREQUEST_0_QTY0']); + $this->assertSame('10.00', $data['L_PAYMENTREQUEST_0_AMT0']); + $this->assertSame('123456', $data['L_PAYMENTREQUEST_0_NUMBER0']); + + $this->assertSame('CD-ROM', $data['L_PAYMENTREQUEST_0_NAME1']); + $this->assertSame('Windows 95', $data['L_PAYMENTREQUEST_0_DESC1']); + $this->assertSame(1, $data['L_PAYMENTREQUEST_0_QTY1']); + $this->assertSame('40.00', $data['L_PAYMENTREQUEST_0_AMT1']); + + $this->assertSame('60.00', $data['PAYMENTREQUEST_0_ITEMAMT']); + } + + public function testGetDataWithExtraOrderDetails() + { + $this->request->initialize(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', + 'noShipping' => 0, + 'allowNote' => 0, + 'addressOverride' => 0, + 'brandName' => 'Dunder Mifflin Paper Company, Inc.', + 'taxAmount' => '2.00', + 'shippingAmount' => '5.00', + 'handlingAmount' => '1.00', + 'shippingDiscount' => '-1.00', + 'insuranceAmount' => '3.00', + )); + + $data = $this->request->getData(); + $this->assertSame('2.00', $data['PAYMENTREQUEST_0_TAXAMT']); + $this->assertSame('5.00', $data['PAYMENTREQUEST_0_SHIPPINGAMT']); + $this->assertSame('1.00', $data['PAYMENTREQUEST_0_HANDLINGAMT']); + $this->assertSame('-1.00', $data['PAYMENTREQUEST_0_SHIPDISCAMT']); + $this->assertSame('3.00', $data['PAYMENTREQUEST_0_INSURANCEAMT']); + } + + public function testHeaderImageUrl() + { + $this->assertSame($this->request, $this->request->setHeaderImageUrl('https://www.example.com/header.jpg')); + $this->assertSame('https://www.example.com/header.jpg', $this->request->getHeaderImageUrl()); + + $data = $this->request->getData(); + $this->assertEquals('https://www.example.com/header.jpg', $data['HDRIMG']); + } + + public function testMaxAmount() + { + $this->request->setMaxAmount(321.54); + + $this->assertSame(321.54, $this->request->getMaxAmount()); + + $data = $this->request->getData(); + + $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(ExpressInContextAuthorizeRequest::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(); + } +} \ No newline at end of file diff --git a/tests/Message/ExpressInContextAuthorizeResponseTest.php b/tests/Message/ExpressInContextAuthorizeResponseTest.php new file mode 100644 index 0000000..d5ea04a --- /dev/null +++ b/tests/Message/ExpressInContextAuthorizeResponseTest.php @@ -0,0 +1,45 @@ +getMockRequest(), 'example=value&foo=bar'); + + $this->assertEquals(array('example' => 'value', 'foo' => 'bar'), $response->getData()); + } + + public function testExpressPurchaseSuccess() + { + $httpResponse = $this->getMockHttpResponse('ExpressPurchaseSuccess.txt'); + $request = $this->getMockRequest(); + $request->shouldReceive('getTestMode')->once()->andReturn(true); + $response = new ExpressInContextAuthorizeResponse($request, $httpResponse->getBody()); + + $this->assertFalse($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertSame('EC-42721413K79637829', $response->getTransactionReference()); + $this->assertNull($response->getMessage()); + $this->assertNull($response->getRedirectData()); + $this->assertSame('https://www.sandbox.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); + $this->assertSame('GET', $response->getRedirectMethod()); + } + + public function testExpressPurchaseFailure() + { + $httpResponse = $this->getMockHttpResponse('ExpressPurchaseFailure.txt'); + $response = new ExpressInContextAuthorizeResponse($this->getMockRequest(), $httpResponse->getBody()); + + $this->assertFalse($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertNull($response->getTransactionReference()); + $this->assertNull($response->getTransactionReference()); + $this->assertSame('This transaction cannot be processed. The amount to be charged is zero.', $response->getMessage()); + } +} \ No newline at end of file