├── .gitignore ├── tests ├── Mock │ ├── ExpressVoidFailure.txt │ ├── ExpressVoidSuccess.txt │ ├── ExpressOrderSuccess.txt │ ├── ExpressPurchaseSuccess.txt │ ├── ProPurchaseSuccess.txt │ ├── ExpressCompletePurchaseFailure.txt │ ├── RestGenericSubscriptionSuccess.txt │ ├── ExpressOrderFailure.txt │ ├── ExpressPurchaseFailure.txt │ ├── ProPurchaseFailure.txt │ ├── ExpressFetchCheckoutFailure.txt │ ├── RestTokenFailure.txt │ ├── ExpressCompletePurchaseFailureRedirect.txt │ ├── RestExecuteSubscriptionSuccess.txt │ ├── RestCompletePurchaseFailure.txt │ ├── RestTokenSuccess.txt │ ├── ExpressFetchCheckoutSuccess.txt │ ├── RestPurchaseFailure.txt │ ├── ExpressCompletePurchaseSuccess.txt │ ├── RestRefundSuccess.txt │ ├── RestCreateCardSuccess.txt │ ├── RestCaptureSuccess.txt │ ├── RestPurchaseWithoutCardSuccess.txt │ ├── RestPurchaseSuccess.txt │ ├── RestAuthorizationSuccess.txt │ ├── RestFetchPurchaseSuccess.txt │ └── RestCompletePurchaseSuccess.txt ├── Message │ ├── RestFetchPurchaseRequestTest.php │ ├── RestListWebhooksRequestTest.php │ ├── RestCompleteSubscriptionRequestTest.php │ ├── ExpressVoidRequestTest.php │ ├── RestCompletePurchaseRequestTest.php │ ├── RestCancelSubscriptionRequestTest.php │ ├── RestUpdatePlanRequestTest.php │ ├── RestSuspendSubscriptionRequestTest.php │ ├── RestFetchTransactionRequestTest.php │ ├── RestReactivateSubscriptionRequestTest.php │ ├── RestDeleteCardRequestTest.php │ ├── RestListPlanRequestTest.php │ ├── RestAuthorizeResponseTest.php │ ├── RestSearchTransactionRequestTest.php │ ├── FetchTransactionRequestTest.php │ ├── RestCreateSubscriptionRequestTest.php │ ├── ResponseTest.php │ ├── RestVerifyWebhookSignatureResponseTest.php │ ├── CaptureRequestTest.php │ ├── RestCreateWebhookRequestTest.php │ ├── ExpressTransactionSearchResponseTest.php │ ├── RestCreateCardRequestTest.php │ ├── RestCreatePlanRequestTest.php │ ├── RefundRequestTest.php │ ├── ExpressAuthorizeResponseTest.php │ ├── ExpressInContextAuthorizeResponseTest.php │ ├── RestVerifyWebhookSignatureRequestTest.php │ ├── ExpressFetchCheckoutRequestTest.php │ ├── ProPurchaseRequestTest.php │ ├── ProAuthorizeRequestTest.php │ ├── ExpressTransactionSearchRequestTest.php │ ├── RestResponseTest.php │ ├── RestPurchaseRequestTest.php │ ├── ExpressCompletePurchaseRequestTest.php │ ├── ExpressCompleteAuthorizeRequestTest.php │ └── RestAuthorizeRequestTest.php ├── ProGatewayTest.php └── ExpressInContextGatewayTest.php ├── src ├── Message │ ├── ProPurchaseRequest.php │ ├── ExpressOrderRequest.php │ ├── ExpressInContextAuthorizeRequest.php │ ├── ExpressCompleteOrderRequest.php │ ├── ExpressInContextOrderRequest.php │ ├── ExpressVoidRequest.php │ ├── FetchTransactionRequest.php │ ├── ExpressCompletePurchaseRequest.php │ ├── ExpressInContextAuthorizeResponse.php │ ├── CaptureRequest.php │ ├── ExpressFetchCheckoutRequest.php │ ├── RefundRequest.php │ ├── RestVoidRequest.php │ ├── RestListWebhooksRequest.php │ ├── RestVerifyWebhookSignatureResponse.php │ ├── ExpressTransactionSearchResponse.php │ ├── RestRefundCaptureRequest.php │ ├── Response.php │ ├── ExpressCompletePurchaseResponse.php │ ├── RestCreateWebhookRequest.php │ ├── RestFetchPurchaseRequest.php │ ├── ExpressAuthorizeResponse.php │ ├── RestTokenRequest.php │ ├── ProAuthorizeRequest.php │ ├── ExpressCompleteAuthorizeRequest.php │ ├── RestFetchTransactionRequest.php │ ├── RestDeleteCardRequest.php │ ├── RestResponse.php │ ├── RestCaptureRequest.php │ ├── RestCompletePurchaseRequest.php │ ├── RestRefundRequest.php │ ├── RestAuthorizeResponse.php │ ├── RestSuspendSubscriptionRequest.php │ ├── RestCancelSubscriptionRequest.php │ ├── RestReactivateSubscriptionRequest.php │ ├── RestUpdatePlanRequest.php │ ├── RestCreateCardRequest.php │ ├── RestCompleteSubscriptionRequest.php │ ├── RestVerifyWebhookSignatureRequest.php │ ├── RestSearchTransactionRequest.php │ ├── RestListPlanRequest.php │ └── AbstractRestRequest.php ├── PayPalItem.php ├── ExpressInContextGateway.php ├── PayPalItemBag.php ├── Support │ └── InstantUpdateApi │ │ ├── ShippingOption.php │ │ └── BillingAgreement.php ├── ProGateway.php └── ExpressGateway.php ├── grumphp.yml ├── CONTRIBUTING.md ├── phpunit.xml.dist ├── phpunit.xml.dist~ ├── LICENSE ├── .github └── workflows │ └── run-tests.yml ├── composer.json ├── README.md └── makedoc.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | composer.phar 4 | phpunit.xml 5 | .idea/ 6 | 7 | .directory 8 | dirlist.app 9 | dirlist.vendor 10 | dirlist.cache 11 | documents/ 12 | -------------------------------------------------------------------------------- /tests/Mock/ExpressVoidFailure.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 19:19:21 GMT 3 | Server: Apache 4 | Content-Length: 136 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | AUTHORIZATIONID=ASDFASDFASDF&TIMESTAMP=2013%2d02%2d15T19%3a19%3a21Z&CORRELATIONID=37b8b9915987c&ACK=Failure&VERSION=85%2e0&BUILD=5060305 -------------------------------------------------------------------------------- /tests/Mock/ExpressVoidSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 19:19:21 GMT 3 | Server: Apache 4 | Content-Length: 136 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | AUTHORIZATIONID=ASDFASDFASDF&TIMESTAMP=2013%2d02%2d15T19%3a19%3a21Z&CORRELATIONID=37b8b9915987c&ACK=Success&VERSION=85%2e0&BUILD=5060305 -------------------------------------------------------------------------------- /tests/Mock/ExpressOrderSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 19:19:21 GMT 3 | Server: Apache 4 | Content-Length: 136 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TOKEN=EC%2d42721413K79637829&TIMESTAMP=2013%2d02%2d15T19%3a19%3a21Z&CORRELATIONID=37b8b9915987c&ACK=Success&VERSION=85%2e0&BUILD=5060305 -------------------------------------------------------------------------------- /tests/Mock/ExpressPurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 19:19:21 GMT 3 | Server: Apache 4 | Content-Length: 136 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TOKEN=EC%2d42721413K79637829&TIMESTAMP=2013%2d02%2d15T19%3a19%3a21Z&CORRELATIONID=37b8b9915987c&ACK=Success&VERSION=85%2e0&BUILD=5060305 -------------------------------------------------------------------------------- /tests/Mock/ProPurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 18:38:31 GMT 3 | Server: Apache 4 | Content-Length: 190 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TIMESTAMP=2013%2d02%2d15T18%3a38%3a36Z&CORRELATIONID=541737dc565cb&ACK=Success&VERSION=85%2e0&BUILD=5060305&AMT=10%2e00&CURRENCYCODE=USD&AVSCODE=X&CVV2MATCH=M&TRANSACTIONID=96U93778BD657313D -------------------------------------------------------------------------------- /src/Message/ProPurchaseRequest.php: -------------------------------------------------------------------------------- 1 | response = new ExpressInContextAuthorizeResponse($this, $data); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Mock/RestGenericSubscriptionSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 204 OK 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava3.slc.paypal.com;threadId=3205 4 | Paypal-Debug-Id: 217a9ddefd384 5 | SERVER_INFO: identitysecuretokenserv:v1.oauth2.token&CalThreadId=91&TopLevelTxnStartTime=146fbfe679a&Host=slcsbidensectoken502.slc.paypal.com&pid=29059 6 | CORRELATION-ID: 217a9ddefd384 7 | Date: Thu, 03 Jul 2014 11:31:32 GMT 8 | 9 | {} 10 | -------------------------------------------------------------------------------- /src/Message/ExpressCompleteOrderRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 13 | $data = $this->getBaseData(); 14 | $data['METHOD'] = 'DoVoid'; 15 | $data['AUTHORIZATIONID'] = $this->getTransactionReference(); 16 | return $data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Mock/ProPurchaseFailure.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Fri, 15 Feb 2013 18:49:26 GMT 3 | Server: Apache 4 | Content-Length: 340 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TIMESTAMP=2013%2d02%2d15T18%3a49%3a27Z&CORRELATIONID=ec641b50c33b0&ACK=Failure&VERSION=85%2e0&BUILD=5060305&L_ERRORCODE0=10562&L_SHORTMESSAGE0=Invalid%20Data&L_LONGMESSAGE0=This%20transaction%20cannot%20be%20processed%2e%20Please%20enter%20a%20valid%20credit%20card%20expiration%20year%2e&L_SEVERITYCODE0=Error&AMT=150%2e04&CURRENCYCODE=USD -------------------------------------------------------------------------------- /src/Message/FetchTransactionRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 13 | 14 | $data = $this->getBaseData(); 15 | $data['METHOD'] = 'GetTransactionDetails'; 16 | $data['TRANSACTIONID'] = $this->getTransactionReference(); 17 | 18 | return $data; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Mock/ExpressFetchCheckoutFailure.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: 05 Apr 2007 23:44:11 GMT 3 | Server: Apache 4 | Connection: close 5 | Content-Type: text/plain; charset=utf-8 6 | 7 | TIMESTAMP=2007%2d04%2d05T23%3a44%3a11Z&CORRELATIONID=6b174e9bac3b3&ACK=Failure&VERSION=85.0&BUILD=11235635&L_ERRORCODE0=10414&L_SHORTMESSAGE0=Transaction%20refused%20because%20of%20an%20invalid%20argument.%20See%20additional%20error%20messages%20for%20details.&L_LONGMESSAGE0=The%20amount%20exceeds%20the%20maximum%20amount%20for%20a%20single%20transaction.&L_SEVERITYCODE0=Error -------------------------------------------------------------------------------- /tests/Mock/RestTokenFailure.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 401 Unauthorized 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=3683 4 | Paypal-Debug-Id: f115dd7f08d14 5 | SERVER_INFO: identitysecuretokenserv:v1.oauth2.token&CalThreadId=126527&TopLevelTxnStartTime=146fc214a29&Host=slcsbidensectoken502.slc.paypal.com&pid=29059 6 | CORRELATION-ID: f115dd7f08d14 7 | Date: Thu, 03 Jul 2014 12:09:38 GMT 8 | Content-Type: application/json 9 | Content-Length: 93 10 | 11 | {"error":"invalid_client","error_description":"Client secret does not match for this client"} -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | * Fork the project. 4 | * Make your feature addition or bug fix. 5 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 6 | * Commit just the modifications, do not mess with the composer.json or CHANGELOG.md files. 7 | * Ensure your code is nicely formatted in the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 8 | style and that all tests pass. 9 | * Send the pull request. 10 | * Check that the Travis CI build passed. If not, rinse and repeat. 11 | -------------------------------------------------------------------------------- /src/PayPalItem.php: -------------------------------------------------------------------------------- 1 | getParameter('code'); 23 | } 24 | 25 | /** 26 | * Set the item code 27 | */ 28 | public function setCode($value) 29 | { 30 | return $this->setParameter('code', $value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Mock/ExpressCompletePurchaseFailureRedirect.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Wed, 20 Feb 2013 13:55:50 GMT 3 | Server: Apache 4 | Content-Length: 214 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TOKEN=ASDFASDFASDF&SUCCESSPAGEREDIRECTREQUESTED=false&TIMESTAMP=2016%2d09%2d12T14%3a06%3a12Z&CORRELATIONID=31e509fc8f6a8&ACK=Failure&VERSION=119%2e0&BUILD=25037053&L_ERRORCODE0=10486&L_SHORTMESSAGE0=This%20transaction%20couldn%27t%20be%20completed%2e&L_LONGMESSAGE0=This%20transaction%20couldn%27t%20be%20completed%2e%20Please%20redirect%20your%20customer%20to%20PayPal%2e&L_SEVERITYCODE0=Error -------------------------------------------------------------------------------- /src/Message/ExpressCompletePurchaseRequest.php: -------------------------------------------------------------------------------- 1 | response = new ExpressCompletePurchaseResponse($this, $data); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Message/ExpressInContextAuthorizeResponse.php: -------------------------------------------------------------------------------- 1 | 'commit', 17 | 'token' => $this->getTransactionReference(), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Message/CaptureRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference', 'amount'); 13 | 14 | $data = $this->getBaseData(); 15 | $data['METHOD'] = 'DoCapture'; 16 | $data['AMT'] = $this->getAmount(); 17 | $data['CURRENCYCODE'] = $this->getCurrency(); 18 | $data['AUTHORIZATIONID'] = $this->getTransactionReference(); 19 | $data['COMPLETETYPE'] = 'Complete'; 20 | 21 | return $data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ExpressInContextGateway.php: -------------------------------------------------------------------------------- 1 | createRequest('\Omnipay\PayPal\Message\ExpressInContextAuthorizeRequest', $parameters); 18 | } 19 | 20 | public function order(array $parameters = array()) 21 | { 22 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressInContextOrderRequest', $parameters); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Mock/RestExecuteSubscriptionSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=76018 4 | Paypal-Debug-Id: 965491cb1a1e5 5 | SERVER_INFO: paymentsplatformserv:v1.payments.sale&CalThreadId=129&TopLevelTxnStartTime=14701c36ef9&Host=slcsbjm3.slc.paypal.com&pid=15797 6 | CORRELATION-ID: 965491cb1a1e5 7 | Content-Language: * 8 | Date: Fri, 04 Jul 2014 14:24:52 GMT 9 | Content-Type: application/json 10 | 11 | { 12 | "id": "I-0LN988D3JACS", 13 | "links": [ 14 | { 15 | "href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS", 16 | "rel": "self", 17 | "method": "GET" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/Message/ExpressFetchCheckoutRequest.php: -------------------------------------------------------------------------------- 1 | validate(); 13 | 14 | $data = $this->getBaseData(); 15 | $data['METHOD'] = 'GetExpressCheckoutDetails'; 16 | 17 | // token can either be specified directly, or inferred from the GET parameters 18 | if ($this->getToken()) { 19 | $data['TOKEN'] = $this->getToken(); 20 | } else { 21 | $data['TOKEN'] = $this->httpRequest->query->get('token'); 22 | } 23 | 24 | return $data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Message/RefundRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 13 | 14 | $data = $this->getBaseData(); 15 | $data['METHOD'] = 'RefundTransaction'; 16 | $data['TRANSACTIONID'] = $this->getTransactionReference(); 17 | $data['REFUNDTYPE'] = 'Full'; 18 | if ($this->getAmount() > 0) { 19 | $data['REFUNDTYPE'] = 'Partial'; 20 | $data['AMT'] = $this->getAmount(); 21 | $data['CURRENCYCODE'] = $this->getCurrency(); 22 | } 23 | 24 | return $data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Mock/RestCompletePurchaseFailure.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 400 Bad Request 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbplatformapiserv3002.slc.paypal.com;threadId=533 4 | Paypal-Debug-Id: 65f24674659dc 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=166&TopLevelTxnStartTime=14b8c851ff1&Host=slcsbpaymentsplatformserv3001.slc.paypal.com&pid=12653 6 | Content-Language: * 7 | Date: Sun, 15 Feb 2015 09:15:09 GMT 8 | Connection: close, close 9 | Content-Type: application/json 10 | Content-Length: 235 11 | 12 | {"name":"PAYMENT_STATE_INVALID","message":"This request is invalid due to the current state of the payment","information_link":"https://developer.paypal.com/webapps/developer/docs/api/#PAYMENT_STATE_INVALID","debug_id":"65f24674659dc"} -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/PayPalItemBag.php: -------------------------------------------------------------------------------- 1 | items[] = $item; 29 | } else { 30 | $this->items[] = new PayPalItem($item); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Message/RestFetchPurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 15 | $request = $this->getHttpRequest(); 16 | $this->request = new RestFetchPurchaseRequest($client, $request); 17 | } 18 | 19 | public function testEndpoint() 20 | { 21 | $this->request->setTransactionReference('ABC-123'); 22 | $this->assertStringEndsWith('/payments/payment/ABC-123', $this->request->getEndpoint()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Mock/RestTokenSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava3.slc.paypal.com;threadId=3205 4 | Paypal-Debug-Id: 217a9ddefd384 5 | SERVER_INFO: identitysecuretokenserv:v1.oauth2.token&CalThreadId=91&TopLevelTxnStartTime=146fbfe679a&Host=slcsbidensectoken502.slc.paypal.com&pid=29059 6 | CORRELATION-ID: 217a9ddefd384 7 | Date: Thu, 03 Jul 2014 11:31:32 GMT 8 | Content-Type: application/json 9 | Content-Length: 328 10 | 11 | {"scope":"openid https://uri.paypal.com/services/invoicing https://api.paypal.com/v1/payments/.* https://api.paypal.com/v1/vault/credit-card/.* https://api.paypal.com/v1/vault/credit-card","access_token":"A015GQlKQ6uCRzLHSGRliANi59BHw6egNVKEWRnxvTwvLr0","token_type":"Bearer","app_id":"APP-80W284485P519543T","expires_in":28800} -------------------------------------------------------------------------------- /src/Message/RestVoidRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 22 | } 23 | 24 | public function getEndpoint() 25 | { 26 | return parent::getEndpoint() . '/payments/authorization/' . $this->getTransactionReference() . '/void'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Message/RestListWebhooksRequest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 17 | $request = $this->getHttpRequest(); 18 | $this->request = new RestListWebhooksRequest($client, $request); 19 | } 20 | 21 | public function testEndpoint() 22 | { 23 | $this->assertStringEndsWith('/notifications/webhooks', $this->request->getEndpoint()); 24 | } 25 | 26 | public function testGetData() 27 | { 28 | $this->assertEmpty($this->request->getData()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Message/RestVerifyWebhookSignatureResponse.php: -------------------------------------------------------------------------------- 1 | getVerificationStatus(); 20 | } 21 | 22 | /** 23 | * The status of the signature verification. Value is `SUCCESS` or `FAILURE`. 24 | * 25 | * @return string 26 | */ 27 | public function getVerificationStatus() 28 | { 29 | return $this->data['verification_status']; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Message/RestCompleteSubscriptionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestCompleteSubscriptionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'transactionReference' => 'ABC-123', 21 | )); 22 | } 23 | 24 | public function testGetData() 25 | { 26 | $data = $this->request->getData(); 27 | $this->assertEquals(array(), $data); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Message/ExpressVoidRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new ExpressVoidRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | $this->request->initialize( 21 | array( 22 | 'transactionReference' => 'ASDFASDFASDF', 23 | ) 24 | ); 25 | } 26 | 27 | public function testGetData() 28 | { 29 | $data = $this->request->getData(); 30 | 31 | $this->assertSame('ASDFASDFASDF', $data['AUTHORIZATIONID']); 32 | $this->assertSame('DoVoid', $data['METHOD']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /phpunit.xml.dist~: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/Message/RestCompletePurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 22 | 23 | $request = $this->getHttpRequest(); 24 | $this->request = new RestCompletePurchaseRequest($client, $request); 25 | $this->request->initialize(array()); 26 | } 27 | 28 | public function testGetData() 29 | { 30 | $this->request->setTransactionReference('abc123'); 31 | $this->request->setPayerId('Payer12345'); 32 | 33 | $data = $this->request->getData(); 34 | 35 | $this->assertSame('Payer12345', $data['payer_id']); 36 | } 37 | } -------------------------------------------------------------------------------- /tests/Message/RestCancelSubscriptionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestCancelSubscriptionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'transactionReference' => 'ABC-123', 21 | 'description' => 'Cancel this subscription', 22 | )); 23 | } 24 | 25 | public function testGetData() 26 | { 27 | $data = $this->request->getData(); 28 | $this->assertEquals('Cancel this subscription', $data['note']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Message/RestUpdatePlanRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestUpdatePlanRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'transactionReference' => 'ABC-123', 21 | 'state' => 'ACTIVE', 22 | )); 23 | } 24 | 25 | public function testGetData() 26 | { 27 | $data = $this->request->getData(); 28 | $this->assertEquals('/', $data[0]['path']); 29 | $this->assertEquals('ACTIVE', $data[0]['value']['state']); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Message/RestSuspendSubscriptionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestSuspendSubscriptionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'transactionReference' => 'ABC-123', 21 | 'description' => 'Suspend this subscription', 22 | )); 23 | } 24 | 25 | public function testGetData() 26 | { 27 | $data = $this->request->getData(); 28 | $this->assertEquals('Suspend this subscription', $data['note']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Message/ExpressTransactionSearchResponse.php: -------------------------------------------------------------------------------- 1 | data as $key => $value) { 19 | if ($this->isSuccessful() 20 | && preg_match('/(L_)?(?[A-Za-z]+)(?[0-9]+)/', $key, $matches) 21 | ) { 22 | $payments[$matches['n']][$matches['key']] = $value; 23 | unset($this->data[$key]); 24 | } 25 | } 26 | 27 | $this->data['payments'] = $payments; 28 | } 29 | 30 | public function getPayments() 31 | { 32 | return $this->data['payments']; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Message/RestFetchTransactionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 15 | $request = $this->getHttpRequest(); 16 | $this->request = new RestFetchTransactionRequest($client, $request); 17 | } 18 | 19 | public function testGetData() 20 | { 21 | $this->request->setTransactionReference('ABC-123'); 22 | $data = $this->request->getData(); 23 | $this->assertEquals(array(), $data); 24 | } 25 | 26 | public function testEndpoint() 27 | { 28 | $this->request->setTransactionReference('ABC-123'); 29 | $this->assertStringEndsWith('/payments/sale/ABC-123', $this->request->getEndpoint()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Message/RestReactivateSubscriptionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestReactivateSubscriptionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'transactionReference' => 'ABC-123', 21 | 'description' => 'Reactivate this subscription', 22 | )); 23 | } 24 | 25 | public function testGetData() 26 | { 27 | $data = $this->request->getData(); 28 | $this->assertEquals('Reactivate this subscription', $data['note']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Message/RestDeleteCardRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new RestDeleteCardRequest($this->getHttpClient(), $this->getHttpRequest()); 21 | $this->request->initialize(array('cardReference' => 'CARD-TEST123')); 22 | } 23 | 24 | public function testGetData() 25 | { 26 | $data = $this->request->getData(); 27 | $this->assertTrue(is_array($data)); 28 | $this->assertEmpty($data); 29 | } 30 | 31 | public function testEndpoint() 32 | { 33 | $this->assertStringEndsWith('/vault/credit-cards/CARD-TEST123', $this->request->getEndpoint()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Message/RestListPlanRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 15 | $request = $this->getHttpRequest(); 16 | $this->request = new RestListPlanRequest($client, $request); 17 | } 18 | 19 | public function testGetData() 20 | { 21 | $data = $this->request->getData(); 22 | $this->assertArrayHasKey('page',$data); 23 | $this->assertArrayHasKey('status',$data); 24 | $this->assertArrayHasKey('page_size',$data); 25 | $this->assertArrayHasKey('total_required',$data); 26 | } 27 | 28 | public function testEndpoint() 29 | { 30 | $this->assertStringEndsWith('/payments/billing-plans', $this->request->getEndpoint()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/Mock/ExpressCompletePurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Wed, 20 Feb 2013 13:54:27 GMT 3 | Server: Apache 4 | Content-Length: 869 5 | Connection: close 6 | Content-Type: text/plain; charset=utf-8 7 | 8 | TOKEN=EC%2d96V667110D1727015&SUCCESSPAGEREDIRECTREQUESTED=true&TIMESTAMP=2013%2d02%2d20T13%3a54%3a28Z&CORRELATIONID=f78b888897f8a&ACK=Success&VERSION=85%2e0&BUILD=5060305&INSURANCEOPTIONSELECTED=false&SHIPPINGOPTIONISDEFAULT=false&PAYMENTINFO_0_TRANSACTIONID=8RM57414KW761861W&PAYMENTINFO_0_RECEIPTID=0368%2d2088%2d8643%2d7560&PAYMENTINFO_0_TRANSACTIONTYPE=expresscheckout&PAYMENTINFO_0_PAYMENTTYPE=instant&PAYMENTINFO_0_ORDERTIME=2013%2d02%2d20T13%3a54%3a03Z&PAYMENTINFO_0_AMT=10%2e00&PAYMENTINFO_0_FEEAMT=0%2e59&PAYMENTINFO_0_TAXAMT=0%2e00&PAYMENTINFO_0_CURRENCYCODE=USD&PAYMENTINFO_0_PAYMENTSTATUS=Completed&PAYMENTINFO_0_PENDINGREASON=None&PAYMENTINFO_0_REASONCODE=None&PAYMENTINFO_0_PROTECTIONELIGIBILITY=Ineligible&PAYMENTINFO_0_PROTECTIONELIGIBILITYTYPE=None&PAYMENTINFO_0_SECUREMERCHANTACCOUNTID=VZTRGMSKHHAEW&PAYMENTINFO_0_ERRORCODE=0&PAYMENTINFO_0_ACK=Success -------------------------------------------------------------------------------- /src/Message/RestRefundCaptureRequest.php: -------------------------------------------------------------------------------- 1 | validate('transactionReference'); 22 | 23 | return array( 24 | 'amount' => array( 25 | 'currency' => $this->getCurrency(), 26 | 'total' => $this->getAmount(), 27 | ), 28 | 'description' => $this->getDescription(), 29 | ); 30 | } 31 | 32 | public function getEndpoint() 33 | { 34 | return parent::getEndpoint() . '/payments/capture/' . $this->getTransactionReference() . '/refund'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Mock/RestRefundSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=76018 4 | Paypal-Debug-Id: 965491cb1a1e5 5 | SERVER_INFO: paymentsplatformserv:v1.payments.sale&CalThreadId=129&TopLevelTxnStartTime=14701c36ef9&Host=slcsbjm3.slc.paypal.com&pid=15797 6 | CORRELATION-ID: 965491cb1a1e5 7 | Content-Language: * 8 | Date: Fri, 04 Jul 2014 14:24:52 GMT 9 | Content-Type: application/json 10 | Content-Length: 592 11 | 12 | {"id":"7G199247NK652674M","create_time":"2014-07-04T14:24:52Z","update_time":"2014-07-04T14:24:52Z","state":"completed","amount":{"total":"2.34","currency":"USD"},"sale_id":"44E89981F8714392Y","parent_payment":"PAY-6RT04683U7444573DKO2WI6A","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/refund/7G199247NK652674M","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RT04683U7444573DKO2WI6A","rel":"parent_payment","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/44E89981F8714392Y","rel":"sale","method":"GET"}]} -------------------------------------------------------------------------------- /tests/Message/RestAuthorizeResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockHttpResponse('RestPurchaseWithoutCardSuccess.txt'); 14 | $data = json_decode($httpResponse->getBody()->getContents(), true); 15 | 16 | $response = new RestAuthorizeResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 17 | 18 | $this->assertTrue($response->isSuccessful()); 19 | $this->assertSame('PAY-3TJ47806DA028052TKTQGVYI', $response->getTransactionReference()); 20 | $this->assertNull($response->getMessage()); 21 | 22 | $this->assertNull($response->getRedirectData()); 23 | $this->assertSame('GET', $response->getRedirectMethod()); 24 | $this->assertSame('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-5KV58254GL528393N', $response->getRedirectUrl()); 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Adrian Macneil 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tests/Mock/RestCreateCardSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava3.slc.paypal.com;threadId=39734 4 | Paypal-Debug-Id: 17a8320884bba 5 | Content-Language: * 6 | CORRELATION-ID: 17a8320884bba 7 | Date: Fri, 04 Jul 2014 14:50:33 GMT 8 | SERVER_INFO: vaultplatformserv:v1.vault.credit-card&CalThreadId=76352&TopLevelTxnStartTime=14701dafae7&Host=slcsbvaultplatformserv501.slc.paypal.com&pid=19516 9 | Content-Type: application/json 10 | Content-Length: 690 11 | 12 | {"id":"CARD-70E78145XN686604FKO3L6OQ","state":"ok","payer_id":"user12345","type":"visa","number":"xxxxxxxxxxxx0331","expire_month":"11","expire_year":"2018","first_name":"Betsy","last_name":"Buyer","valid_until":"2017-07-03T00:00:00Z","create_time":"2014-07-04T14:50:34Z","update_time":"2014-07-04T14:50:34Z","links":[{"href":"https://api.sandbox.paypal.com/v1/vault/credit-card/CARD-70E78145XN686604FKO3L6OQ","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/vault/credit-card/CARD-70E78145XN686604FKO3L6OQ","rel":"delete","method":"DELETE"},{"href":"https://api.sandbox.paypal.com/v1/vault/credit-card/CARD-70E78145XN686604FKO3L6OQ","rel":"patch","method":"PATCH"}]} -------------------------------------------------------------------------------- /tests/Mock/RestCaptureSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=127 4 | Paypal-Debug-Id: 030d6a098c7c5 5 | SERVER_INFO: paymentsplatformserv:v1.payments.authorization&CalThreadId=15946&TopLevelTxnStartTime=14701ba0b41&Host=slcsbjm3.slc.paypal.com&pid=15797 6 | CORRELATION-ID: 030d6a098c7c5 7 | Content-Language: * 8 | Date: Fri, 04 Jul 2014 14:14:42 GMT 9 | Content-Type: application/json 10 | Content-Length: 724 11 | 12 | {"id":"9EP22596VU4085001","create_time":"2014-07-04T14:14:35Z","update_time":"2014-07-04T14:14:42Z","amount":{"total":"7.47","currency":"USD"},"is_final_capture":false,"state":"completed","parent_payment":"PAY-66G66446716792522KO3LK4Y","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/capture/9EP22596VU4085001","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/capture/9EP22596VU4085001/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/authorization/58N7596879166930B","rel":"authorization","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-66G66446716792522KO3LK4Y","rel":"parent_payment","method":"GET"}]} -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - "*" 10 | schedule: 11 | - cron: '0 0 * * *' 12 | 13 | jobs: 14 | php-tests: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 15 17 | env: 18 | COMPOSER_NO_INTERACTION: 1 19 | 20 | strategy: 21 | matrix: 22 | php: [8.1, 8.0, 7.4, 7.3, 7.2] 23 | dependency-version: [prefer-lowest, prefer-stable] 24 | 25 | name: P${{ matrix.php }} - ${{ matrix.dependency-version }} 26 | 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v2 30 | 31 | - name: Setup PHP 32 | uses: shivammathur/setup-php@v2 33 | with: 34 | php-version: ${{ matrix.php }} 35 | coverage: none 36 | tools: composer:v2 37 | 38 | - name: Install dependencies 39 | run: | 40 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress 41 | 42 | - name: Execute Unit Tests 43 | run: composer test 44 | 45 | - name: Check Code Style 46 | run: composer check-style 47 | if: ${{ matrix.dependency-version == 'prefer-stable' }} 48 | -------------------------------------------------------------------------------- /tests/Message/RestSearchTransactionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestSearchTransactionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'agreementId' => 'ABC-123', 21 | 'startDate' => '2015-09-01', 22 | 'endDate' => '2015-09-30', 23 | )); 24 | } 25 | 26 | public function testGetData() 27 | { 28 | $data = $this->request->getData(); 29 | $this->assertEquals('2015-09-01', $data['start_date']); 30 | $this->assertEquals('2015-09-30', $data['end_date']); 31 | } 32 | 33 | public function testEndpoint() 34 | { 35 | $this->assertStringEndsWith('/payments/billing-agreements/ABC-123/transactions', $this->request->getEndpoint()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Mock/RestPurchaseWithoutCardSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbplatformapiserv3001.slc.paypal.com;threadId=383 4 | Paypal-Debug-Id: b4dc1c6623f68 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=166&TopLevelTxnStartTime=14b8ca1806f&Host=slcsbpaymentsplatformserv3001.slc.paypal.com&pid=12653 6 | Content-Language: * 7 | Date: Sun, 15 Feb 2015 09:46:09 GMT 8 | Content-Type: application/json 9 | Content-Length: 894 10 | 11 | {"id":"PAY-3TJ47806DA028052TKTQGVYI","create_time":"2015-02-15T09:46:09Z","update_time":"2015-02-15T09:46:09Z","state":"created","intent":"sale","payer":{"payment_method":"paypal","payer_info":{"shipping_address":{}}},"transactions":[{"amount":{"total":"199.00","currency":"USD","details":{"subtotal":"199.00"}},"description":"Product 1","item_list":{"items":[{"name":"Item 1","price":"199.00","currency":"USD","quantity":"1","description":"Item Description"}]},"related_resources":[]}],"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-3TJ47806DA028052TKTQGVYI","rel":"self","method":"GET"},{"href":"https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-5KV58254GL528393N","rel":"approval_url","method":"REDIRECT"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-3TJ47806DA028052TKTQGVYI/execute","rel":"execute","method":"POST"}]} -------------------------------------------------------------------------------- /src/Message/Response.php: -------------------------------------------------------------------------------- 1 | request = $request; 16 | parse_str($data, $this->data); 17 | } 18 | 19 | public function isPending() 20 | { 21 | return isset($this->data['PAYMENTINFO_0_PAYMENTSTATUS']) 22 | && $this->data['PAYMENTINFO_0_PAYMENTSTATUS'] == 'Pending'; 23 | } 24 | 25 | public function isSuccessful() 26 | { 27 | return isset($this->data['ACK']) && in_array($this->data['ACK'], array('Success', 'SuccessWithWarning')); 28 | } 29 | 30 | public function getTransactionReference() 31 | { 32 | foreach (array('REFUNDTRANSACTIONID', 33 | 'TRANSACTIONID', 34 | 'PAYMENTINFO_0_TRANSACTIONID', 35 | 'AUTHORIZATIONID') as $key) { 36 | if (isset($this->data[$key])) { 37 | return $this->data[$key]; 38 | } 39 | } 40 | } 41 | 42 | public function getMessage() 43 | { 44 | return isset($this->data['L_LONGMESSAGE0']) ? $this->data['L_LONGMESSAGE0'] : null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omnipay/paypal", 3 | "type": "library", 4 | "description": "PayPal gateway for Omnipay payment processing library", 5 | "keywords": [ 6 | "gateway", 7 | "merchant", 8 | "omnipay", 9 | "pay", 10 | "payment", 11 | "paypal", 12 | "purchase" 13 | ], 14 | "homepage": "https://github.com/thephpleague/omnipay-paypal", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Adrian Macneil", 19 | "email": "adrian@adrianmacneil.com" 20 | }, 21 | { 22 | "name": "Omnipay Contributors", 23 | "homepage": "https://github.com/thephpleague/omnipay-paypal/contributors" 24 | } 25 | ], 26 | "autoload": { 27 | "psr-4": { "Omnipay\\PayPal\\" : "src/" } 28 | }, 29 | "require": { 30 | "php": "^7.2|^8.0", 31 | "omnipay/common": "^3" 32 | }, 33 | "require-dev": { 34 | "omnipay/tests": "^4.1.2", 35 | "squizlabs/php_codesniffer": "^3" 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "3.0.x-dev" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "phpunit", 44 | "check-style": "phpcs -p --standard=PSR2 src/", 45 | "fix-style": "phpcbf -p --standard=PSR2 src/" 46 | }, 47 | "prefer-stable": true 48 | } 49 | -------------------------------------------------------------------------------- /tests/Message/FetchTransactionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | 19 | $request = $this->getHttpRequest(); 20 | 21 | $this->request = new FetchTransactionRequest($client, $request); 22 | } 23 | 24 | public function testGetData() 25 | { 26 | $this->request->setTransactionReference('ABC-123'); 27 | $this->request->setUsername('testuser'); 28 | $this->request->setPassword('testpass'); 29 | $this->request->setSignature('SIG'); 30 | $this->request->setSubject('SUB'); 31 | 32 | $expected = array(); 33 | $expected['METHOD'] = 'GetTransactionDetails'; 34 | $expected['TRANSACTIONID'] = 'ABC-123'; 35 | $expected['USER'] = 'testuser'; 36 | $expected['PWD'] = 'testpass'; 37 | $expected['SIGNATURE'] = 'SIG'; 38 | $expected['SUBJECT'] = 'SUB'; 39 | $expected['VERSION'] = RefundRequest::API_VERSION; 40 | 41 | $this->assertEquals($expected, $this->request->getData()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Message/RestCreateSubscriptionRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestCreateSubscriptionRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'name' => 'Test Subscription', 21 | 'description' => 'Test Billing Subscription', 22 | 'startDate' => new \DateTime('now', new \DateTimeZone('UTC')), 23 | 'planId' => 'ABC-123', 24 | 'payerDetails' => array( 25 | 'payment_method' => 'paypal', 26 | ), 27 | )); 28 | } 29 | 30 | public function testGetData() 31 | { 32 | $data = $this->request->getData(); 33 | $this->assertEquals('Test Subscription', $data['name']); 34 | $this->assertEquals('Test Billing Subscription', $data['description']); 35 | $this->assertEquals('ABC-123', $data['plan']['id']); 36 | $this->assertEquals('paypal', $data['payer']['payment_method']); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Message/ExpressCompletePurchaseResponse.php: -------------------------------------------------------------------------------- 1 | data['ACK']) && in_array($this->data['ACK'], array('Success', 'SuccessWithWarning')); 18 | return !$this->isRedirect() && $success; 19 | } 20 | 21 | /** 22 | * The complete purchase response can be in error where it wants to have your customer return to paypal. 23 | * 24 | * @return bool 25 | */ 26 | public function isRedirect() 27 | { 28 | return isset($this->data['L_ERRORCODE0']) && in_array($this->data['L_ERRORCODE0'], array('10486')); 29 | } 30 | 31 | /** 32 | * The transaction reference obtained from the purchase() call can't be used to refund a purchase. 33 | * 34 | * @return string 35 | */ 36 | public function getTransactionReference() 37 | { 38 | if ($this->isSuccessful() && isset($this->data['PAYMENTINFO_0_TRANSACTIONID'])) { 39 | return $this->data['PAYMENTINFO_0_TRANSACTIONID']; 40 | } 41 | 42 | return parent::getTransactionReference(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Message/ResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockRequest(), 'example=value&foo=bar'); 13 | $this->assertEquals(array('example' => 'value', 'foo' => 'bar'), $response->getData()); 14 | } 15 | 16 | public function testProPurchaseSuccess() 17 | { 18 | $httpResponse = $this->getMockHttpResponse('ProPurchaseSuccess.txt'); 19 | $response = new Response($this->getMockRequest(), $httpResponse->getBody()); 20 | 21 | $this->assertFalse($response->isPending()); 22 | $this->assertTrue($response->isSuccessful()); 23 | $this->assertSame('96U93778BD657313D', $response->getTransactionReference()); 24 | $this->assertNull($response->getMessage()); 25 | } 26 | 27 | public function testProPurchaseFailure() 28 | { 29 | $httpResponse = $this->getMockHttpResponse('ProPurchaseFailure.txt'); 30 | $response = new Response($this->getMockRequest(), $httpResponse->getBody()); 31 | 32 | $this->assertFalse($response->isPending()); 33 | $this->assertFalse($response->isSuccessful()); 34 | $this->assertNull($response->getTransactionReference()); 35 | $this->assertSame('This transaction cannot be processed. Please enter a valid credit card expiration year.', $response->getMessage()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Message/RestCreateWebhookRequest.php: -------------------------------------------------------------------------------- 1 | \array_map( 14 | function ($value) { 15 | return ['name' => $value]; 16 | }, 17 | $this->getEventTypes() 18 | ), 19 | 'url' => $this->getUrl(), 20 | ]; 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function getEventTypes() 27 | { 28 | return $this->getParameter('event_types') ?: []; 29 | } 30 | 31 | /** 32 | * @inheritDoc 33 | */ 34 | public function getEndpoint() 35 | { 36 | return parent::getEndpoint().'/notifications/webhooks'; 37 | } 38 | 39 | /** 40 | * @return string|null 41 | */ 42 | public function getUrl() 43 | { 44 | return $this->getParameter('webhook_url'); 45 | } 46 | 47 | /** 48 | * @param array $eventTypes 49 | * 50 | * @return $this 51 | */ 52 | public function setEventTypes(array $eventTypes) 53 | { 54 | return $this->setParameter('event_types', $eventTypes); 55 | } 56 | 57 | /** 58 | * @param string $url 59 | * 60 | * @return $this 61 | */ 62 | public function setUrl($url) 63 | { 64 | return $this->setParameter('webhook_url', $url); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Message/RestVerifyWebhookSignatureResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockRequest(), 13 | ['verification_status' => 'SUCCESS'] 14 | ); 15 | 16 | $this->assertSame('SUCCESS', $response->getVerificationStatus()); 17 | } 18 | 19 | public function testIsSuccessfulWillReturnFalseIfParentCheckIsSuccesfullButVerificationFailed() 20 | { 21 | $response = new RestVerifyWebhookSignatureResponse( 22 | $this->getMockRequest(), 23 | ['verification_status' => 'FAILED'] 24 | ); 25 | 26 | $this->assertFalse($response->isSuccessful()); 27 | } 28 | 29 | public function testIsSuccessfulWillReturnFalseIfParentCheckIsUnsuccesfull() 30 | { 31 | $response = new RestVerifyWebhookSignatureResponse( 32 | $this->getMockRequest(), 33 | ['verification_status' => 'foobar'], 34 | 400 35 | ); 36 | 37 | $this->assertFalse($response->isSuccessful()); 38 | } 39 | 40 | public function testIsSuccessfulWillReturnTrueIfEverythingIsOk() 41 | { 42 | $response = new RestVerifyWebhookSignatureResponse( 43 | $this->getMockRequest(), 44 | ['verification_status' => 'SUCCESS'] 45 | ); 46 | 47 | $this->assertTrue($response->isSuccessful()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Support/InstantUpdateApi/ShippingOption.php: -------------------------------------------------------------------------------- 1 | name = $name; 28 | $this->amount = $amount; 29 | $this->isDefault = $isDefault; 30 | $this->label = $label; 31 | } 32 | 33 | /** 34 | * @return bool 35 | */ 36 | public function hasLabel() 37 | { 38 | return !is_null($this->label); 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getName() 45 | { 46 | return $this->name; 47 | } 48 | 49 | /** 50 | * @return float 51 | */ 52 | public function getAmount() 53 | { 54 | return $this->amount; 55 | } 56 | 57 | /** 58 | * @return boolean 59 | */ 60 | public function isDefault() 61 | { 62 | return $this->isDefault; 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function getLabel() 69 | { 70 | return $this->label; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Message/CaptureRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | $request = $this->getHttpRequest(); 19 | $this->request = new CaptureRequest($client, $request); 20 | } 21 | 22 | public function testGetData() 23 | { 24 | $this->request->setTransactionReference('ABC-123'); 25 | $this->request->setAmount('1.23'); 26 | $this->request->setCurrency('USD'); 27 | $this->request->setUsername('testuser'); 28 | $this->request->setPassword('testpass'); 29 | $this->request->setSignature('SIG'); 30 | $this->request->setSubject('SUB'); 31 | $this->request->setButtonSource('BNCode_PP'); 32 | 33 | $expected = array(); 34 | $expected['METHOD'] = 'DoCapture'; 35 | $expected['AUTHORIZATIONID'] = 'ABC-123'; 36 | $expected['AMT'] = '1.23'; 37 | $expected['CURRENCYCODE'] = 'USD'; 38 | $expected['COMPLETETYPE'] = 'Complete'; 39 | $expected['USER'] = 'testuser'; 40 | $expected['PWD'] = 'testpass'; 41 | $expected['SIGNATURE'] = 'SIG'; 42 | $expected['SUBJECT'] = 'SUB'; 43 | $expected['BUTTONSOURCE'] = 'BNCode_PP'; 44 | $expected['VERSION'] = CaptureRequest::API_VERSION; 45 | 46 | $this->assertEquals($expected, $this->request->getData()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Message/RestFetchPurchaseRequest.php: -------------------------------------------------------------------------------- 1 | 19 | * // Fetch the transaction so that details can be found for refund, etc. 20 | * $transaction = $gateway->fetchPurchase(); 21 | * $transaction->setTransactionReference($sale_id); 22 | * $response = $transaction->send(); 23 | * $data = $response->getData(); 24 | * echo "Gateway fetchTransaction response data == " . print_r($data, true) . "\n"; 25 | * 26 | * 27 | * @see RestPurchaseRequest 28 | * @link https://developer.paypal.com/docs/api/#look-up-a-payment-resource 29 | */ 30 | class RestFetchPurchaseRequest extends AbstractRestRequest 31 | { 32 | public function getData() 33 | { 34 | $this->validate('transactionReference'); 35 | return array(); 36 | } 37 | 38 | /** 39 | * Get HTTP Method. 40 | * 41 | * The HTTP method for fetchTransaction requests must be GET. 42 | * Using POST results in an error 500 from PayPal. 43 | * 44 | * @return string 45 | */ 46 | protected function getHttpMethod() 47 | { 48 | return 'GET'; 49 | } 50 | 51 | public function getEndpoint() 52 | { 53 | return parent::getEndpoint() . '/payments/payment/' . $this->getTransactionReference(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Mock/RestPurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=1534 4 | Paypal-Debug-Id: 98cbd3ab19dfe 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=129&TopLevelTxnStartTime=146fc9074ec&Host=slcsbjm3.slc.paypal.com&pid=15797 6 | CORRELATION-ID: 98cbd3ab19dfe 7 | Content-Language: * 8 | Date: Thu, 03 Jul 2014 14:11:10 GMT 9 | Content-Type: application/json 10 | Content-Length: 1243 11 | 12 | {"id":"PAY-6RT04683U7444573DKO2WI6A","create_time":"2014-07-03T14:11:04Z","update_time":"2014-07-03T14:11:10Z","state":"approved","intent":"sale","payer":{"payment_method":"credit_card","funding_instruments":[{"credit_card":{"type":"mastercard","number":"xxxxxxxxxxxx5559","expire_month":"12","expire_year":"2018","first_name":"Betsy","last_name":"Buyer"}}]},"transactions":[{"amount":{"total":"7.47","currency":"USD","details":{"subtotal":"7.47"}},"description":"This is the payment transaction description.","related_resources":[{"sale":{"id":"44E89981F8714392Y","create_time":"2014-07-03T14:11:04Z","update_time":"2014-07-03T14:11:10Z","state":"completed","amount":{"total":"7.47","currency":"USD"},"parent_payment":"PAY-6RT04683U7444573DKO2WI6A","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/sale/44E89981F8714392Y","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/44E89981F8714392Y/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RT04683U7444573DKO2WI6A","rel":"parent_payment","method":"GET"}]}}]}],"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RT04683U7444573DKO2WI6A","rel":"self","method":"GET"}]} 13 | -------------------------------------------------------------------------------- /src/Message/ExpressAuthorizeResponse.php: -------------------------------------------------------------------------------- 1 | data['ACK']) && in_array($this->data['ACK'], array('Success', 'SuccessWithWarning')); 23 | } 24 | 25 | public function getRedirectUrl() 26 | { 27 | return $this->getCheckoutEndpoint().'?'.http_build_query($this->getRedirectQueryParameters(), '', '&'); 28 | } 29 | 30 | public function getTransactionReference() 31 | { 32 | return isset($this->data['TOKEN']) ? $this->data['TOKEN'] : null; 33 | } 34 | 35 | public function getRedirectMethod() 36 | { 37 | return 'GET'; 38 | } 39 | 40 | public function getRedirectData() 41 | { 42 | return null; 43 | } 44 | 45 | protected function getRedirectQueryParameters() 46 | { 47 | return array( 48 | 'cmd' => '_express-checkout', 49 | 'useraction' => 'commit', 50 | 'token' => $this->getTransactionReference(), 51 | ); 52 | } 53 | 54 | protected function getCheckoutEndpoint() 55 | { 56 | return $this->getRequest()->getTestMode() ? $this->testCheckoutEndpoint : $this->liveCheckoutEndpoint; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Message/RestTokenRequest.php: -------------------------------------------------------------------------------- 1 | 'client_credentials'); 24 | } 25 | 26 | protected function getEndpoint() 27 | { 28 | return parent::getEndpoint() . '/oauth2/token'; 29 | } 30 | 31 | public function sendData($data) 32 | { 33 | $body = $data ? http_build_query($data, '', '&') : null; 34 | $httpResponse = $this->httpClient->request( 35 | $this->getHttpMethod(), 36 | $this->getEndpoint(), 37 | array( 38 | 'Accept' => 'application/json', 39 | 'Authorization' => 'Basic ' . base64_encode("{$this->getClientId()}:{$this->getSecret()}"), 40 | ), 41 | $body 42 | ); 43 | // Empty response body should be parsed also as and empty array 44 | $body = (string) $httpResponse->getBody()->getContents(); 45 | $jsonToArrayResponse = !empty($body) ? json_decode($body, true) : array(); 46 | return $this->response = new RestResponse($this, $jsonToArrayResponse, $httpResponse->getStatusCode()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Message/ProAuthorizeRequest.php: -------------------------------------------------------------------------------- 1 | validate('amount', 'card'); 13 | $this->getCard()->validate(); 14 | 15 | $data = $this->getBaseData(); 16 | $data['METHOD'] = 'DoDirectPayment'; 17 | $data['PAYMENTACTION'] = 'Authorization'; 18 | $data['AMT'] = $this->getAmount(); 19 | $data['CURRENCYCODE'] = $this->getCurrency(); 20 | $data['INVNUM'] = $this->getTransactionId(); 21 | $data['DESC'] = $this->getDescription(); 22 | 23 | // add credit card details 24 | $data['ACCT'] = $this->getCard()->getNumber(); 25 | $data['CREDITCARDTYPE'] = $this->getCard()->getBrand(); 26 | $data['EXPDATE'] = $this->getCard()->getExpiryDate('mY'); 27 | $data['STARTDATE'] = $this->getCard()->getStartDate('mY'); 28 | $data['CVV2'] = $this->getCard()->getCvv(); 29 | $data['ISSUENUMBER'] = $this->getCard()->getIssueNumber(); 30 | $data['IPADDRESS'] = $this->getClientIp(); 31 | $data['FIRSTNAME'] = $this->getCard()->getFirstName(); 32 | $data['LASTNAME'] = $this->getCard()->getLastName(); 33 | $data['EMAIL'] = $this->getCard()->getEmail(); 34 | $data['STREET'] = $this->getCard()->getAddress1(); 35 | $data['STREET2'] = $this->getCard()->getAddress2(); 36 | $data['CITY'] = $this->getCard()->getCity(); 37 | $data['STATE'] = $this->getCard()->getState(); 38 | $data['ZIP'] = $this->getCard()->getPostcode(); 39 | $data['COUNTRYCODE'] = strtoupper($this->getCard()->getCountry()); 40 | 41 | return $data; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Message/RestCreateWebhookRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 17 | $request = $this->getHttpRequest(); 18 | $this->request = new RestCreateWebhookRequest($client, $request); 19 | } 20 | 21 | public function testGetData() 22 | { 23 | $event1 = 'PAYMENT.AUTHORIZATION.CREATED'; 24 | $event2 = 'PAYMENT.AUTHORIZATION.VOIDED'; 25 | $url = 'https://foo.bar/baz'; 26 | $this->request->initialize( 27 | [ 28 | 'event_types' => [$event1, $event2], 29 | 'url' => $url, 30 | ] 31 | ); 32 | 33 | $this->assertEquals( 34 | [ 35 | 'event_types' => [['name' => $event1], ['name' => $event2]], 36 | 'url' => $url, 37 | ], 38 | $this->request->getData() 39 | ); 40 | } 41 | 42 | public function testGetEndpoint() 43 | { 44 | $this->assertStringEndsWith('/notifications/webhooks', $this->request->getEndpoint()); 45 | } 46 | 47 | public function testGetEventTypes() 48 | { 49 | $value = ['PAYMENT.AUTHORIZATION.CREATED']; 50 | $this->request->setEventTypes($value); 51 | self::assertSame($value, $this->request->getEventTypes()); 52 | } 53 | 54 | public function testGetUrl() 55 | { 56 | $value = 'https://foo.bar/baz'; 57 | $this->request->setUrl($value); 58 | self::assertSame($value, $this->request->getUrl()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Message/ExpressTransactionSearchResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockRequest(), 'ACK=Success&BUILD=18308778'); 13 | 14 | $this->assertEquals( 15 | array('ACK' => 'Success', 'BUILD' => '18308778', 'payments' => array()), 16 | $response->getData() 17 | ); 18 | } 19 | 20 | public function testExpressTransactionSearch() 21 | { 22 | $httpResponse = $this->getMockHttpResponse('ExpressTransactionSearchResponse.txt'); 23 | 24 | $response = new ExpressTransactionSearchResponse($this->getMockRequest(), $httpResponse->getBody()); 25 | 26 | $this->assertTrue($response->isSuccessful()); 27 | $this->assertNull($response->getMessage()); 28 | $this->assertArrayHasKey('payments', $response->getData()); 29 | 30 | foreach ($response->getPayments() as $payment) { 31 | $this->assertArrayHasKey('TIMESTAMP', $payment); 32 | $this->assertArrayHasKey('TIMEZONE', $payment); 33 | $this->assertArrayHasKey('TYPE', $payment); 34 | $this->assertArrayHasKey('EMAIL', $payment); 35 | $this->assertArrayHasKey('NAME', $payment); 36 | $this->assertArrayHasKey('TRANSACTIONID', $payment); 37 | $this->assertArrayHasKey('STATUS', $payment); 38 | $this->assertArrayHasKey('AMT', $payment); 39 | $this->assertArrayHasKey('CURRENCYCODE', $payment); 40 | $this->assertArrayHasKey('FEEAMT', $payment); 41 | $this->assertArrayHasKey('NETAMT', $payment); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Message/ExpressCompleteAuthorizeRequest.php: -------------------------------------------------------------------------------- 1 | validate('amount'); 13 | 14 | $data = $this->getBaseData(); 15 | $data['METHOD'] = 'DoExpressCheckoutPayment'; 16 | $data['PAYMENTREQUEST_0_PAYMENTACTION'] = 'Authorization'; 17 | $data['PAYMENTREQUEST_0_AMT'] = $this->getAmount(); 18 | $data['PAYMENTREQUEST_0_CURRENCYCODE'] = $this->getCurrency(); 19 | $data['PAYMENTREQUEST_0_INVNUM'] = $this->getTransactionId(); 20 | $data['PAYMENTREQUEST_0_DESC'] = $this->getDescription(); 21 | $data['PAYMENTREQUEST_0_NOTIFYURL'] = $this->getNotifyUrl(); 22 | 23 | $data['MAXAMT'] = $this->getMaxAmount(); 24 | $data['PAYMENTREQUEST_0_TAXAMT'] = $this->getTaxAmount(); 25 | $data['PAYMENTREQUEST_0_SHIPPINGAMT'] = $this->getShippingAmount(); 26 | $data['PAYMENTREQUEST_0_HANDLINGAMT'] = $this->getHandlingAmount(); 27 | $data['PAYMENTREQUEST_0_SHIPDISCAMT'] = $this->getShippingDiscount(); 28 | $data['PAYMENTREQUEST_0_INSURANCEAMT'] = $this->getInsuranceAmount(); 29 | 30 | $data['TOKEN'] = $this->getToken() ? $this->getToken() : $this->httpRequest->query->get('token'); 31 | $data['PAYERID'] = $this->getPayerID() ? $this->getPayerID() : $this->httpRequest->query->get('PayerID'); 32 | 33 | $data = array_merge($data, $this->getItemData()); 34 | 35 | return $data; 36 | } 37 | 38 | public function getPayerID() 39 | { 40 | return $this->getParameter('payerID'); 41 | } 42 | 43 | public function setPayerID($value) 44 | { 45 | return $this->setParameter('payerID', $value); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Mock/RestAuthorizationSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava3.slc.paypal.com;threadId=3224 4 | Paypal-Debug-Id: 2a1b9202663bc 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=206&TopLevelTxnStartTime=14701b4cb42&Host=slcsbjm1.slc.paypal.com&pid=20119 6 | CORRELATION-ID: 2a1b9202663bc 7 | Content-Language: * 8 | Date: Fri, 04 Jul 2014 14:09:00 GMT 9 | Content-Type: application/json 10 | Content-Length: 1435 11 | 12 | {"id":"PAY-66G66446716792522KO3LK4Y","create_time":"2014-07-04T14:08:51Z","update_time":"2014-07-04T14:09:01Z","state":"approved","intent":"authorize","payer":{"payment_method":"credit_card","funding_instruments":[{"credit_card":{"type":"mastercard","number":"xxxxxxxxxxxx5559","expire_month":"12","expire_year":"2018","first_name":"Betsy","last_name":"Buyer"}}]},"transactions":[{"amount":{"total":"7.47","currency":"USD","details":{"subtotal":"7.47"}},"description":"This is the payment transaction description.","related_resources":[{"authorization":{"id":"58N7596879166930B","create_time":"2014-07-04T14:08:51Z","update_time":"2014-07-04T14:09:01Z","state":"authorized","amount":{"total":"7.47","currency":"USD"},"parent_payment":"PAY-66G66446716792522KO3LK4Y","valid_until":"2014-08-02T14:08:51Z","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/authorization/58N7596879166930B","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/authorization/58N7596879166930B/capture","rel":"capture","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/authorization/58N7596879166930B/void","rel":"void","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-66G66446716792522KO3LK4Y","rel":"parent_payment","method":"GET"}]}}]}],"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-66G66446716792522KO3LK4Y","rel":"self","method":"GET"}]} -------------------------------------------------------------------------------- /src/Message/RestFetchTransactionRequest.php: -------------------------------------------------------------------------------- 1 | 20 | * // Fetch the transaction so that details can be found for refund, etc. 21 | * $transaction = $gateway->fetchTransaction(); 22 | * $transaction->setTransactionReference($sale_id); 23 | * $response = $transaction->send(); 24 | * $data = $response->getData(); 25 | * echo "Gateway fetchTransaction response data == " . print_r($data, true) . "\n"; 26 | * 27 | * 28 | * @see RestPurchaseRequest 29 | * @link https://developer.paypal.com/docs/api/#sale-transactions 30 | */ 31 | class RestFetchTransactionRequest extends AbstractRestRequest 32 | { 33 | public function getData() 34 | { 35 | $this->validate('transactionReference'); 36 | return array(); 37 | } 38 | 39 | /** 40 | * Get HTTP Method. 41 | * 42 | * The HTTP method for fetchTransaction requests must be GET. 43 | * Using POST results in an error 500 from PayPal. 44 | * 45 | * @return string 46 | */ 47 | protected function getHttpMethod() 48 | { 49 | return 'GET'; 50 | } 51 | 52 | public function getEndpoint() 53 | { 54 | return parent::getEndpoint() . '/payments/sale/' . $this->getTransactionReference(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Message/RestCreateCardRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new RestCreateCardRequest($this->getHttpClient(), $this->getHttpRequest()); 21 | 22 | $card = $this->getValidCard(); 23 | $this->card = new CreditCard($card); 24 | 25 | $this->request->initialize(array('card' => $card)); 26 | } 27 | 28 | public function testGetData() 29 | { 30 | $card = $this->card; 31 | $data = $this->request->getData(); 32 | 33 | $this->assertSame($card->getNumber(), $data['number']); 34 | $this->assertSame($card->getBrand(), $data['type']); 35 | $this->assertSame($card->getExpiryMonth(), $data['expire_month']); 36 | $this->assertSame($card->getExpiryYear(), $data['expire_year']); 37 | $this->assertSame($card->getCvv(), $data['cvv2']); 38 | $this->assertSame($card->getFirstName(), $data['first_name']); 39 | $this->assertSame($card->getLastName(), $data['last_name']); 40 | $this->assertSame($card->getAddress1(), $data['billing_address']['line1']); 41 | $this->assertSame($card->getAddress2(), $data['billing_address']['line2']); 42 | $this->assertSame($card->getCity(), $data['billing_address']['city']); 43 | $this->assertSame($card->getState(), $data['billing_address']['state']); 44 | $this->assertSame($card->getPostcode(), $data['billing_address']['postal_code']); 45 | $this->assertSame($card->getCountry(), $data['billing_address']['country_code']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Mock/RestFetchPurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 201 Created 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbjava2.slc.paypal.com;threadId=1534 4 | Paypal-Debug-Id: 98cbd3ab19dfe 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=129&TopLevelTxnStartTime=146fc9074ec&Host=slcsbjm3.slc.paypal.com&pid=15797 6 | CORRELATION-ID: 98cbd3ab19dfe 7 | Content-Language: * 8 | Date: Thu, 03 Jul 2014 14:11:10 GMT 9 | Content-Type: application/json 10 | 11 | { 12 | "id": "PAY-0WB587530N302915SKXWVCSQ", 13 | "create_time": "2015-09-07T08:56:42Z", 14 | "update_time": "2015-09-07T08:56:42Z", 15 | "state": "created", 16 | "intent": "sale", 17 | "payer": { 18 | "payment_method": "paypal", 19 | "payer_info": { 20 | "shipping_address": [] 21 | } 22 | }, 23 | "transactions": [ 24 | { 25 | "amount": { 26 | "total": "10.00", 27 | "currency": "AUD", 28 | "details": { 29 | "subtotal": "10.00" 30 | } 31 | }, 32 | "description": "This is a test purchase transaction.", 33 | "related_resources": [] 34 | } 35 | ], 36 | "links": [ 37 | { 38 | "href": "https:\/\/api.sandbox.paypal.com\/v1\/payments\/payment\/PAY-0WB587530N302915SKXWVCSQ", 39 | "rel": "self", 40 | "method": "GET" 41 | }, 42 | { 43 | "href": "https:\/\/www.sandbox.paypal.com\/cgi-bin\/webscr?cmd=_express-checkout&token=EC-3DR71034MD528800J", 44 | "rel": "approval_url", 45 | "method": "REDIRECT" 46 | }, 47 | { 48 | "href": "https:\/\/api.sandbox.paypal.com\/v1\/payments\/payment\/PAY-0WB587530N302915SKXWVCSQ\/execute", 49 | "rel": "execute", 50 | "method": "POST" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /tests/Message/RestCreatePlanRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 16 | $request = $this->getHttpRequest(); 17 | $this->request = new RestCreatePlanRequest($client, $request); 18 | 19 | $this->request->initialize(array( 20 | 'name' => 'Super Duper Billing Plan', 21 | 'description' => 'Test Billing Plan', 22 | 'type' => RestGateway::BILLING_PLAN_TYPE_FIXED, 23 | 'paymentDefinitions' => array( 24 | array( 25 | 'name' => 'Monthly Payments', 26 | 'type' => RestGateway::PAYMENT_REGULAR, 27 | 'frequency' => RestGateway::BILLING_PLAN_FREQUENCY_MONTH, 28 | 'frequency_interval' => 1, 29 | 'cycles' => 12, 30 | 'amount' => array( 31 | 'value' => 10.00, 32 | 'currency' => 'USD', 33 | ) 34 | ) 35 | ), 36 | 'merchantPreferences' => array( 37 | 'name' => 'asdf', 38 | ), 39 | )); 40 | } 41 | 42 | public function testGetData() 43 | { 44 | $data = $this->request->getData(); 45 | $this->assertEquals('Super Duper Billing Plan', $data['name']); 46 | $this->assertEquals(RestGateway::BILLING_PLAN_TYPE_FIXED, $data['type']); 47 | $this->assertEquals('Monthly Payments', $data['payment_definitions'][0]['name']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ProGatewayTest.php: -------------------------------------------------------------------------------- 1 | gateway = new ProGateway($this->getHttpClient(), $this->getHttpRequest()); 15 | 16 | $this->options = array( 17 | 'amount' => '10.00', 18 | 'card' => new CreditCard(array( 19 | 'firstName' => 'Example', 20 | 'lastName' => 'User', 21 | 'number' => '4111111111111111', 22 | 'expiryMonth' => '12', 23 | 'expiryYear' => date('Y'), 24 | 'cvv' => '123', 25 | )), 26 | ); 27 | } 28 | 29 | public function testAuthorize() 30 | { 31 | $this->setMockHttpResponse('ProPurchaseSuccess.txt'); 32 | 33 | $response = $this->gateway->authorize($this->options)->send(); 34 | 35 | $this->assertTrue($response->isSuccessful()); 36 | $this->assertEquals('96U93778BD657313D', $response->getTransactionReference()); 37 | $this->assertNull($response->getMessage()); 38 | } 39 | 40 | public function testPurchase() 41 | { 42 | $this->setMockHttpResponse('ProPurchaseSuccess.txt'); 43 | 44 | $response = $this->gateway->purchase($this->options)->send(); 45 | 46 | $this->assertTrue($response->isSuccessful()); 47 | $this->assertEquals('96U93778BD657313D', $response->getTransactionReference()); 48 | $this->assertNull($response->getMessage()); 49 | } 50 | 51 | public function testFetchTransaction() 52 | { 53 | $request = $this->gateway->fetchTransaction(array('transactionReference' => 'abc123')); 54 | 55 | $this->assertInstanceOf('\Omnipay\PayPal\Message\FetchTransactionRequest', $request); 56 | $this->assertSame('abc123', $request->getTransactionReference()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Mock/RestCompletePurchaseSuccess.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: Apache-Coyote/1.1 3 | PROXY_SERVER_INFO: host=slcsbplatformapiserv3001.slc.paypal.com;threadId=216 4 | Paypal-Debug-Id: 539b1c0678b08 5 | SERVER_INFO: paymentsplatformserv:v1.payments.payment&CalThreadId=4342&TopLevelTxnStartTime=14b8c84b1f3&Host=slcsbpaymentsplatformserv3002.slc.paypal.com&pid=8929 6 | Content-Language: * 7 | Date: Sun, 15 Feb 2015 09:14:43 GMT 8 | Content-Type: application/json 9 | Content-Length: 1632 10 | 11 | {"id":"PAY-4BF374015W374910XKTQGGBZ","create_time":"2015-02-15T09:12:36Z","update_time":"2015-02-15T09:14:43Z","state":"approved","intent":"sale","payer":{"payment_method":"paypal","payer_info":{"email":"test.buyer@example.com","first_name":"Betsy","last_name":"Buyer","payer_id":"Payer12345","shipping_address":{"line1":"test","city":"","state":"","postal_code":"123456","country_code":"US","recipient_name":"Buyer Betsy"}}},"transactions":[{"amount":{"total":"100.00","currency":"USD","details":{"subtotal":"100.00","fee":"3.90"}},"description":"The product description","item_list":{"items":[{"name":"Item 1","price":"100.00","currency":"USD","quantity":"1","description":"Standard Item"}],"shipping_address":{"recipient_name":"Buyer Betsy","line1":"test","city":"","state":"","postal_code":"123456","country_code":"US"}},"related_resources":[{"sale":{"id":"9EA05739TH369572R","create_time":"2015-02-15T09:12:36Z","update_time":"2015-02-15T09:14:43Z","amount":{"total":"100.00","currency":"USD"},"payment_mode":"INSTANT_TRANSFER","state":"completed","protection_eligibility":"INELIGIBLE","parent_payment":"PAY-4BF374015W374910XKTQGGBZ","links":[{"href":"https://api.sandbox.paypal.com/v1/payments/sale/9EA05739TH369572R","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/payments/sale/9EA05739TH369572R/refund","rel":"refund","method":"POST"},{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-4BF374015W374910XKTQGGBZ","rel":"parent_payment","method":"GET"}]}}]}],"links":[{"href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-4BF374015W374910XKTQGGBZ","rel":"self","method":"GET"}]} -------------------------------------------------------------------------------- /src/Message/RestDeleteCardRequest.php: -------------------------------------------------------------------------------- 1 | 30 | * $transaction = $gateway->deleteCard(); 31 | * $transaction->setCardReference($card_id); 32 | * $response = $transaction->send(); 33 | * if ($response->isSuccessful()) { 34 | * echo "Gateway deleteCard was successful.\n"; 35 | * } else { 36 | * echo "Gateway deleteCard failed.\n"; 37 | * } 38 | * 39 | * 40 | * @link https://developer.paypal.com/docs/api/#vault 41 | * @link https://developer.paypal.com/docs/api/#delete-a-stored-credit-card 42 | * @link http://bit.ly/1wUQ33R 43 | * @see RestCreateCardRequest 44 | */ 45 | class RestDeleteCardRequest extends AbstractRestRequest 46 | { 47 | public function getHttpMethod() 48 | { 49 | return 'DELETE'; 50 | } 51 | 52 | public function getData() 53 | { 54 | $this->validate('cardReference'); 55 | return array(); 56 | } 57 | 58 | public function getEndpoint() 59 | { 60 | return parent::getEndpoint() . '/vault/credit-cards/' . $this->getCardReference(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Message/RefundRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | 19 | $request = $this->getHttpRequest(); 20 | 21 | $this->request = new RefundRequest($client, $request); 22 | } 23 | 24 | /** 25 | * @dataProvider provideRefundTypes 26 | */ 27 | public function testGetData($type, $amount) 28 | { 29 | $this->request->setAmount($amount); 30 | $this->request->setCurrency('USD'); 31 | $this->request->setTransactionReference('ABC-123'); 32 | $this->request->setUsername('testuser'); 33 | $this->request->setPassword('testpass'); 34 | $this->request->setSignature('SIG'); 35 | $this->request->setSubject('SUB'); 36 | 37 | $expected = array(); 38 | $expected['REFUNDTYPE'] = $type; 39 | $expected['METHOD'] = 'RefundTransaction'; 40 | $expected['TRANSACTIONID'] = 'ABC-123'; 41 | $expected['USER'] = 'testuser'; 42 | $expected['PWD'] = 'testpass'; 43 | $expected['SIGNATURE'] = 'SIG'; 44 | $expected['SUBJECT'] = 'SUB'; 45 | $expected['VERSION'] = RefundRequest::API_VERSION; 46 | // $amount will be a formatted string, and '0.00' evaluates to true 47 | if ((float)$amount) { 48 | $expected['AMT'] = $amount; 49 | $expected['CURRENCYCODE'] = 'USD'; 50 | } 51 | 52 | $this->assertEquals($expected, $this->request->getData()); 53 | } 54 | 55 | public function provideRefundTypes() 56 | { 57 | return array( 58 | 'Partial' => array('Partial', '1.23'), 59 | // All amounts must include decimals or be a float if the currency supports decimals. 60 | 'Full' => array('Full', '0.00'), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Message/RestResponse.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 22 | } 23 | 24 | public function isSuccessful() 25 | { 26 | return empty($this->data['error']) && $this->getCode() < 400; 27 | } 28 | 29 | public function getTransactionReference() 30 | { 31 | // This is usually correct for payments, authorizations, etc 32 | if (!empty($this->data['transactions']) && !empty($this->data['transactions'][0]['related_resources'])) { 33 | foreach (array('sale', 'authorization') as $type) { 34 | if (!empty($this->data['transactions'][0]['related_resources'][0][$type])) { 35 | return $this->data['transactions'][0]['related_resources'][0][$type]['id']; 36 | } 37 | } 38 | } 39 | 40 | // This is a fallback, but is correct for fetch transaction and possibly others 41 | if (!empty($this->data['id'])) { 42 | return $this->data['id']; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | public function getMessage() 49 | { 50 | if (isset($this->data['error_description'])) { 51 | return $this->data['error_description']; 52 | } 53 | 54 | if (isset($this->data['message'])) { 55 | return $this->data['message']; 56 | } 57 | 58 | return null; 59 | } 60 | 61 | public function getCode() 62 | { 63 | return $this->statusCode; 64 | } 65 | 66 | public function getCardReference() 67 | { 68 | if (isset($this->data['id'])) { 69 | return $this->data['id']; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Message/RestCaptureRequest.php: -------------------------------------------------------------------------------- 1 | 28 | * // Once the transaction has been authorized, we can capture it for final payment. 29 | * $transaction = $gateway->capture(array( 30 | * 'amount' => '10.00', 31 | * 'currency' => 'AUD', 32 | * )); 33 | * $transaction->setTransactionReference($auth_id); 34 | * $response = $transaction->send(); 35 | * 36 | * 37 | * @see RestAuthorizeRequest 38 | * @link https://developer.paypal.com/docs/api/#capture-an-authorization 39 | */ 40 | class RestCaptureRequest extends AbstractRestRequest 41 | { 42 | public function getData() 43 | { 44 | $this->validate('transactionReference', 'amount'); 45 | 46 | return array( 47 | 'amount' => array( 48 | 'currency' => $this->getCurrency(), 49 | 'total' => $this->getAmount(), 50 | ), 51 | 'is_final_capture' => true, 52 | ); 53 | } 54 | 55 | public function getEndpoint() 56 | { 57 | return parent::getEndpoint() . '/payments/authorization/' . $this->getTransactionReference() . '/capture'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Message/ExpressAuthorizeResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockRequest(), 'example=value&foo=bar'); 14 | 15 | $this->assertEquals(array('example' => 'value', 'foo' => 'bar'), $response->getData()); 16 | } 17 | 18 | public function testExpressPurchaseSuccess() 19 | { 20 | $httpResponse = $this->getMockHttpResponse('ExpressPurchaseSuccess.txt'); 21 | $request = $this->getMockRequest(); 22 | $request->shouldReceive('getTestMode')->once()->andReturn(true); 23 | $response = new ExpressAuthorizeResponse($request, $httpResponse->getBody()); 24 | 25 | $this->assertFalse($response->isPending()); 26 | $this->assertFalse($response->isSuccessful()); 27 | $this->assertSame('EC-42721413K79637829', $response->getTransactionReference()); 28 | $this->assertNull($response->getMessage()); 29 | $this->assertNull($response->getRedirectData()); 30 | $this->assertSame('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); 31 | $this->assertSame('GET', $response->getRedirectMethod()); 32 | } 33 | 34 | public function testExpressPurchaseFailure() 35 | { 36 | $httpResponse = $this->getMockHttpResponse('ExpressPurchaseFailure.txt'); 37 | $response = new ExpressAuthorizeResponse($this->getMockRequest(), $httpResponse->getBody()); 38 | 39 | $this->assertFalse($response->isPending()); 40 | $this->assertFalse($response->isSuccessful()); 41 | $this->assertNull($response->getTransactionReference()); 42 | $this->assertNull($response->getTransactionReference()); 43 | $this->assertSame('This transaction cannot be processed. The amount to be charged is zero.', $response->getMessage()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Message/ExpressInContextAuthorizeResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockRequest(), 'example=value&foo=bar'); 14 | 15 | $this->assertEquals(array('example' => 'value', 'foo' => 'bar'), $response->getData()); 16 | } 17 | 18 | public function testExpressPurchaseSuccess() 19 | { 20 | $httpResponse = $this->getMockHttpResponse('ExpressPurchaseSuccess.txt'); 21 | $request = $this->getMockRequest(); 22 | $request->shouldReceive('getTestMode')->once()->andReturn(true); 23 | $response = new ExpressInContextAuthorizeResponse($request, $httpResponse->getBody()); 24 | 25 | $this->assertFalse($response->isPending()); 26 | $this->assertFalse($response->isSuccessful()); 27 | $this->assertSame('EC-42721413K79637829', $response->getTransactionReference()); 28 | $this->assertNull($response->getMessage()); 29 | $this->assertNull($response->getRedirectData()); 30 | $this->assertSame('https://www.sandbox.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); 31 | $this->assertSame('GET', $response->getRedirectMethod()); 32 | } 33 | 34 | public function testExpressPurchaseFailure() 35 | { 36 | $httpResponse = $this->getMockHttpResponse('ExpressPurchaseFailure.txt'); 37 | $response = new ExpressInContextAuthorizeResponse($this->getMockRequest(), $httpResponse->getBody()); 38 | 39 | $this->assertFalse($response->isPending()); 40 | $this->assertFalse($response->isSuccessful()); 41 | $this->assertNull($response->getTransactionReference()); 42 | $this->assertNull($response->getTransactionReference()); 43 | $this->assertSame('This transaction cannot be processed. The amount to be charged is zero.', $response->getMessage()); 44 | } 45 | } -------------------------------------------------------------------------------- /src/ProGateway.php: -------------------------------------------------------------------------------- 1 | '', 21 | 'password' => '', 22 | 'signature' => '', 23 | 'testMode' => false, 24 | ); 25 | } 26 | 27 | public function getUsername() 28 | { 29 | return $this->getParameter('username'); 30 | } 31 | 32 | public function setUsername($value) 33 | { 34 | return $this->setParameter('username', $value); 35 | } 36 | 37 | public function getPassword() 38 | { 39 | return $this->getParameter('password'); 40 | } 41 | 42 | public function setPassword($value) 43 | { 44 | return $this->setParameter('password', $value); 45 | } 46 | 47 | public function getSignature() 48 | { 49 | return $this->getParameter('signature'); 50 | } 51 | 52 | public function setSignature($value) 53 | { 54 | return $this->setParameter('signature', $value); 55 | } 56 | 57 | public function authorize(array $parameters = array()) 58 | { 59 | return $this->createRequest('\Omnipay\PayPal\Message\ProAuthorizeRequest', $parameters); 60 | } 61 | 62 | public function purchase(array $parameters = array()) 63 | { 64 | return $this->createRequest('\Omnipay\PayPal\Message\ProPurchaseRequest', $parameters); 65 | } 66 | 67 | public function capture(array $parameters = array()) 68 | { 69 | return $this->createRequest('\Omnipay\PayPal\Message\CaptureRequest', $parameters); 70 | } 71 | 72 | public function refund(array $parameters = array()) 73 | { 74 | return $this->createRequest('\Omnipay\PayPal\Message\RefundRequest', $parameters); 75 | } 76 | 77 | public function fetchTransaction(array $parameters = array()) 78 | { 79 | return $this->createRequest('\Omnipay\PayPal\Message\FetchTransactionRequest', $parameters); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Message/RestVerifyWebhookSignatureRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 17 | $request = $this->getHttpRequest(); 18 | $this->request = new RestVerifyWebhookSignatureRequest($client, $request); 19 | } 20 | 21 | public function testGetData() 22 | { 23 | $data = [ 24 | 'transmission_id' => 'foo', 25 | 'auth_algo' => 'bar', 26 | 'cert_url' => 'baz', 27 | 'transmission_sig' => 'qux', 28 | 'transmission_time' => 'foobar', 29 | 'webhook_event' => ['bar' => 'baz'], 30 | 'webhook_id' => 'barbaz', 31 | ]; 32 | 33 | $this->request->initialize($data); 34 | 35 | $this->assertEquals($data, $this->request->getData()); 36 | } 37 | 38 | public function testGettersAndSetters() { 39 | $authAlgo = 'foo'; 40 | $certUrl = 'bar'; 41 | $transmissionId = 'baz'; 42 | $transmissionTime = 'qux'; 43 | $transmissionSig = 'foobar'; 44 | $webhookEvent = ['bar' => 'baz']; 45 | $webhookId = 'barfoo'; 46 | 47 | $this->request->setAuthAlgo($authAlgo); 48 | $this->request->setCertUrl($certUrl); 49 | $this->request->setTransmissionId($transmissionId); 50 | $this->request->setTransmissionTime($transmissionTime); 51 | $this->request->setTransmissionSig($transmissionSig); 52 | $this->request->setWebhookEvent($webhookEvent); 53 | $this->request->setWebhookId($webhookId); 54 | 55 | $this->assertEquals($authAlgo, $this->request->getAuthAlgo()); 56 | $this->assertEquals($certUrl,$this->request->getCertUrl()); 57 | $this->assertEquals($transmissionId, $this->request->getTransmissionId()); 58 | $this->assertEquals($transmissionTime, $this->request->getTransmissionTime()); 59 | $this->assertEquals($transmissionSig, $this->request->getTransmissionSig()); 60 | $this->assertEquals($webhookEvent, $this->request->getWebhookEvent()); 61 | $this->assertEquals($webhookId, $this->request->getWebhookId()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Message/ExpressFetchCheckoutRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | 19 | $request = $this->getHttpRequest(); 20 | $request->query->set('token', 'TOKEN1234'); 21 | 22 | $this->request = new ExpressFetchCheckoutRequest($client, $request); 23 | } 24 | 25 | public function testGetData() 26 | { 27 | $this->request->setUsername('testuser'); 28 | $this->request->setPassword('testpass'); 29 | $this->request->setSignature('SIG'); 30 | 31 | $expected = array(); 32 | $expected['METHOD'] = 'GetExpressCheckoutDetails'; 33 | $expected['USER'] = 'testuser'; 34 | $expected['PWD'] = 'testpass'; 35 | $expected['SIGNATURE'] = 'SIG'; 36 | $expected['SUBJECT'] = null; 37 | $expected['VERSION'] = ExpressCompletePurchaseRequest::API_VERSION; 38 | $expected['TOKEN'] = 'TOKEN1234'; 39 | 40 | $this->assertEquals($expected, $this->request->getData()); 41 | } 42 | 43 | public function testGetDataTokenOverride() 44 | { 45 | $this->request->setToken('TOKEN2000'); 46 | 47 | $data = $this->request->getData(); 48 | 49 | $this->assertSame('TOKEN2000', $data['TOKEN']); 50 | } 51 | 52 | public function testSendSuccess() 53 | { 54 | $this->setMockHttpResponse('ExpressFetchCheckoutSuccess.txt'); 55 | 56 | $response = $this->request->send(); 57 | $this->assertFalse($response->isPending()); 58 | $this->assertTrue($response->isSuccessful()); 59 | $this->assertFalse($response->isRedirect()); 60 | } 61 | 62 | public function testSendFailure() 63 | { 64 | $this->setMockHttpResponse('ExpressFetchCheckoutFailure.txt'); 65 | 66 | $response = $this->request->send(); 67 | $this->assertFalse($response->isPending()); 68 | $this->assertFalse($response->isSuccessful()); 69 | $this->assertFalse($response->isRedirect()); 70 | $this->assertSame('The amount exceeds the maximum amount for a single transaction.', $response->getMessage()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Message/RestCompletePurchaseRequest.php: -------------------------------------------------------------------------------- 1 | 27 | * $paymentId = $_GET['paymentId']; 28 | * $payerId = $_GET['payerId']; 29 | * 30 | * // Once the transaction has been approved, we need to complete it. 31 | * $transaction = $gateway->completePurchase(array( 32 | * 'payer_id' => $payerId, 33 | * 'transactionReference' => $paymentId, 34 | * )); 35 | * $response = $transaction->send(); 36 | * if ($response->isSuccessful()) { 37 | * // The customer has successfully paid. 38 | * } else { 39 | * // There was an error returned by completePurchase(). You should 40 | * // check the error code and message from PayPal, which may be something 41 | * // like "card declined", etc. 42 | * } 43 | * 44 | * 45 | * @see RestPurchaseRequest 46 | * @link https://developer.paypal.com/docs/api/#execute-an-approved-paypal-payment 47 | */ 48 | class RestCompletePurchaseRequest extends AbstractRestRequest 49 | { 50 | /** 51 | * Get the raw data array for this message. The format of this varies from gateway to 52 | * gateway, but will usually be either an associative array, or a SimpleXMLElement. 53 | * 54 | * @return mixed 55 | */ 56 | public function getData() 57 | { 58 | $this->validate('transactionReference', 'payerId'); 59 | 60 | $data = array( 61 | 'payer_id' => $this->getPayerId() 62 | ); 63 | 64 | return $data; 65 | } 66 | 67 | public function getEndpoint() 68 | { 69 | return parent::getEndpoint() . '/payments/payment/' . $this->getTransactionReference() . '/execute'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Omnipay: PayPal 2 | 3 | **PayPal driver for the Omnipay PHP payment processing library** 4 | 5 | [![Unit Tests](https://github.com/thephpleague/omnipay-paypal/actions/workflows/run-tests.yml/badge.svg)](https://github.com/thephpleague/omnipay-paypal/actions/workflows/run-tests.yml) 6 | [![Latest Stable Version](https://poser.pugx.org/omnipay/paypal/version.png)](https://packagist.org/packages/omnipay/paypal) 7 | [![Total Downloads](https://poser.pugx.org/omnipay/paypal/d/total.png)](https://packagist.org/packages/omnipay/paypal) 8 | 9 | [Omnipay](https://github.com/thephpleague/omnipay) is a framework agnostic, multi-gateway payment 10 | processing library for PHP. This package implements PayPal support for Omnipay. 11 | 12 | ## Installation 13 | 14 | Omnipay is installed via [Composer](http://getcomposer.org/). To install, simply require `league/omnipay` and `omnipay/paypal` with Composer: 15 | 16 | ``` 17 | composer require league/omnipay omnipay/paypal 18 | ``` 19 | 20 | 21 | ## Basic Usage 22 | 23 | The following gateways are provided by this package: 24 | 25 | * PayPal_Express (PayPal Express Checkout) 26 | * PayPal_ExpressInContext (PayPal Express In-Context Checkout) 27 | * PayPal_Pro (PayPal Website Payments Pro) 28 | * PayPal_Rest (Paypal Rest API) 29 | 30 | For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay) 31 | repository. 32 | 33 | ## Quirks 34 | 35 | The transaction reference obtained from the purchase() response can't be used to refund a purchase. The transaction reference from the completePurchase() response is the one that should be used. 36 | 37 | ## Out Of Scope 38 | 39 | Omnipay does not cover recurring payments or billing agreements, and so those features are not included in this package. Extensions to this gateway are always welcome. 40 | 41 | ## Support 42 | 43 | If you are having general issues with Omnipay, we suggest posting on 44 | [Stack Overflow](http://stackoverflow.com/). Be sure to add the 45 | [omnipay tag](http://stackoverflow.com/questions/tagged/omnipay) so it can be easily found. 46 | 47 | If you want to keep up to date with release anouncements, discuss ideas for the project, 48 | or ask more detailed questions, there is also a [mailing list](https://groups.google.com/forum/#!forum/omnipay) which 49 | you can subscribe to. 50 | 51 | If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/thephpleague/omnipay-paypal/issues), 52 | or better yet, fork the library and submit a pull request. 53 | -------------------------------------------------------------------------------- /src/Support/InstantUpdateApi/BillingAgreement.php: -------------------------------------------------------------------------------- 1 | 'MerchantInitiatedBillingSingleAgreement', 16 | 'recurring' => 'MerchantInitiatedBilling', 17 | ); 18 | 19 | /** @var string */ 20 | private $type; 21 | 22 | /** @var string */ 23 | private $description; 24 | 25 | /** @var string */ 26 | private $paymentType; 27 | 28 | /** @var string */ 29 | private $customAnnotation; 30 | 31 | /** 32 | * @param bool $recurring L_BILLINGTYPE0 33 | * @param string $description L_BILLINGAGREEMENTDESCRIPTION0 34 | * @param null|string $paymentType L_PAYMENTTYPE0 35 | * @param null|string $customAnnotation L_BILLINGAGREEMENTCUSTOM0 36 | * @throws \Exception 37 | */ 38 | public function __construct($recurring, $description, $paymentType = null, $customAnnotation = null) 39 | { 40 | if (!$recurring && !is_null($paymentType) && !in_array($paymentType, array('Any', 'InstantOnly'))) { 41 | throw new InvalidRequestException("The 'paymentType' parameter can be only 'Any' or 'InstantOnly'"); 42 | } 43 | 44 | $this->type = $recurring ? $this->types['recurring'] : $this->types['single']; 45 | $this->description = $description; 46 | $this->customAnnotation = $customAnnotation; 47 | $this->paymentType = $paymentType; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getType() 54 | { 55 | return $this->type; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getDescription() 62 | { 63 | return $this->description; 64 | } 65 | 66 | /** 67 | * @return bool 68 | */ 69 | public function hasPaymentType() 70 | { 71 | return !is_null($this->paymentType); 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function getPaymentType() 78 | { 79 | return $this->paymentType; 80 | } 81 | 82 | /** 83 | * @return bool 84 | */ 85 | public function hasCustomAnnotation() 86 | { 87 | return !is_null($this->customAnnotation); 88 | } 89 | 90 | /** 91 | * @return string 92 | */ 93 | public function getCustomAnnotation() 94 | { 95 | return $this->customAnnotation; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Message/ProPurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new ProPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | $this->request->initialize( 21 | array( 22 | 'amount' => '10.00', 23 | 'currency' => 'USD', 24 | 'card' => $this->getValidCard(), 25 | ) 26 | ); 27 | } 28 | 29 | public function testGetData() 30 | { 31 | $card = new CreditCard($this->getValidCard()); 32 | $card->setStartMonth(1); 33 | $card->setStartYear(2000); 34 | 35 | $this->request->setCard($card); 36 | $this->request->setTransactionId('abc123'); 37 | $this->request->setDescription('Sheep'); 38 | $this->request->setClientIp('127.0.0.1'); 39 | 40 | $data = $this->request->getData(); 41 | 42 | $this->assertSame('DoDirectPayment', $data['METHOD']); 43 | $this->assertSame('Sale', $data['PAYMENTACTION']); 44 | $this->assertSame('10.00', $data['AMT']); 45 | $this->assertSame('USD', $data['CURRENCYCODE']); 46 | $this->assertSame('abc123', $data['INVNUM']); 47 | $this->assertSame('Sheep', $data['DESC']); 48 | $this->assertSame('127.0.0.1', $data['IPADDRESS']); 49 | 50 | $this->assertSame($card->getNumber(), $data['ACCT']); 51 | $this->assertSame($card->getBrand(), $data['CREDITCARDTYPE']); 52 | $this->assertSame($card->getExpiryDate('mY'), $data['EXPDATE']); 53 | $this->assertSame('012000', $data['STARTDATE']); 54 | $this->assertSame($card->getCvv(), $data['CVV2']); 55 | $this->assertSame($card->getIssueNumber(), $data['ISSUENUMBER']); 56 | 57 | $this->assertSame($card->getFirstName(), $data['FIRSTNAME']); 58 | $this->assertSame($card->getLastName(), $data['LASTNAME']); 59 | $this->assertSame($card->getEmail(), $data['EMAIL']); 60 | $this->assertSame($card->getAddress1(), $data['STREET']); 61 | $this->assertSame($card->getAddress2(), $data['STREET2']); 62 | $this->assertSame($card->getCity(), $data['CITY']); 63 | $this->assertSame($card->getState(), $data['STATE']); 64 | $this->assertSame($card->getPostcode(), $data['ZIP']); 65 | $this->assertSame($card->getCountry(), $data['COUNTRYCODE']); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Message/ProAuthorizeRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new ProAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | $this->request->initialize( 21 | array( 22 | 'amount' => '10.00', 23 | 'currency' => 'USD', 24 | 'card' => $this->getValidCard(), 25 | ) 26 | ); 27 | } 28 | 29 | public function testGetData() 30 | { 31 | $card = new CreditCard($this->getValidCard()); 32 | $card->setStartMonth(1); 33 | $card->setStartYear(2000); 34 | 35 | $this->request->setCard($card); 36 | $this->request->setTransactionId('abc123'); 37 | $this->request->setDescription('Sheep'); 38 | $this->request->setClientIp('127.0.0.1'); 39 | 40 | $data = $this->request->getData(); 41 | 42 | $this->assertSame('DoDirectPayment', $data['METHOD']); 43 | $this->assertSame('Authorization', $data['PAYMENTACTION']); 44 | $this->assertSame('10.00', $data['AMT']); 45 | $this->assertSame('USD', $data['CURRENCYCODE']); 46 | $this->assertSame('abc123', $data['INVNUM']); 47 | $this->assertSame('Sheep', $data['DESC']); 48 | $this->assertSame('127.0.0.1', $data['IPADDRESS']); 49 | 50 | $this->assertSame($card->getNumber(), $data['ACCT']); 51 | $this->assertSame($card->getBrand(), $data['CREDITCARDTYPE']); 52 | $this->assertSame($card->getExpiryDate('mY'), $data['EXPDATE']); 53 | $this->assertSame('012000', $data['STARTDATE']); 54 | $this->assertSame($card->getCvv(), $data['CVV2']); 55 | $this->assertSame($card->getIssueNumber(), $data['ISSUENUMBER']); 56 | 57 | $this->assertSame($card->getFirstName(), $data['FIRSTNAME']); 58 | $this->assertSame($card->getLastName(), $data['LASTNAME']); 59 | $this->assertSame($card->getEmail(), $data['EMAIL']); 60 | $this->assertSame($card->getAddress1(), $data['STREET']); 61 | $this->assertSame($card->getAddress2(), $data['STREET2']); 62 | $this->assertSame($card->getCity(), $data['CITY']); 63 | $this->assertSame($card->getState(), $data['STATE']); 64 | $this->assertSame($card->getPostcode(), $data['ZIP']); 65 | $this->assertSame($card->getCountry(), $data['COUNTRYCODE']); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Message/RestRefundRequest.php: -------------------------------------------------------------------------------- 1 | 29 | * $transaction = $gateway->refund(array( 30 | * 'amount' => '10.00', 31 | * 'currency' => 'AUD', 32 | * )); 33 | * $transaction->setTransactionReference($sale_id); 34 | * $response = $transaction->send(); 35 | * if ($response->isSuccessful()) { 36 | * echo "Refund transaction was successful!\n"; 37 | * $data = $response->getData(); 38 | * echo "Gateway refund response data == " . print_r($data, true) . "\n"; 39 | * } 40 | * 41 | * 42 | * ### Known Issues 43 | * 44 | * PayPal subscription payments cannot be refunded. PayPal is working on this functionality 45 | * for their future API release. In order to refund a PayPal subscription payment, you will 46 | * need to use the PayPal web interface to refund it manually. 47 | * 48 | * @see RestPurchaseRequest 49 | */ 50 | class RestRefundRequest extends AbstractRestRequest 51 | { 52 | public function getData() 53 | { 54 | $this->validate('transactionReference'); 55 | 56 | if ($this->getAmount() > 0) { 57 | return array( 58 | 'amount' => array( 59 | 'currency' => $this->getCurrency(), 60 | 'total' => $this->getAmount(), 61 | ), 62 | 'description' => $this->getDescription(), 63 | ); 64 | } else { 65 | return new \stdClass(); 66 | } 67 | } 68 | 69 | public function getEndpoint() 70 | { 71 | return parent::getEndpoint() . '/payments/sale/' . $this->getTransactionReference() . '/refund'; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/ExpressInContextGatewayTest.php: -------------------------------------------------------------------------------- 1 | gateway = new ExpressInContextGateway($this->getHttpClient(), $this->getHttpRequest()); 29 | 30 | $this->options = array( 31 | 'amount' => '10.00', 32 | 'returnUrl' => 'https://www.example.com/return', 33 | 'cancelUrl' => 'https://www.example.com/cancel', 34 | ); 35 | $this->voidOptions = array( 36 | 'transactionReference' => 'ASDFASDFASDF', 37 | ); 38 | } 39 | 40 | public function testAuthorizeSuccess() 41 | { 42 | $this->setMockHttpResponse('ExpressPurchaseSuccess.txt'); 43 | 44 | $response = $this->gateway->authorize($this->options)->send(); 45 | 46 | $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); 47 | $this->assertFalse($response->isPending()); 48 | $this->assertFalse($response->isSuccessful()); 49 | $this->assertTrue($response->isRedirect()); 50 | $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); 51 | } 52 | 53 | public function testPurchaseSuccess() 54 | { 55 | $this->setMockHttpResponse('ExpressPurchaseSuccess.txt'); 56 | 57 | $response = $this->gateway->purchase($this->options)->send(); 58 | 59 | $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); 60 | $this->assertFalse($response->isPending()); 61 | $this->assertFalse($response->isSuccessful()); 62 | $this->assertTrue($response->isRedirect()); 63 | $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); 64 | } 65 | 66 | public function testOrderSuccess() 67 | { 68 | $this->setMockHttpResponse('ExpressOrderSuccess.txt'); 69 | 70 | $response = $this->gateway->order($this->options)->send(); 71 | 72 | $this->assertInstanceOf('\Omnipay\PayPal\Message\ExpressInContextAuthorizeResponse', $response); 73 | $this->assertFalse($response->isPending()); 74 | $this->assertFalse($response->isSuccessful()); 75 | $this->assertTrue($response->isRedirect()); 76 | $this->assertEquals('https://www.paypal.com/checkoutnow?useraction=commit&token=EC-42721413K79637829', $response->getRedirectUrl()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Message/RestAuthorizeResponse.php: -------------------------------------------------------------------------------- 1 | data['error']) && $this->getCode() == 201; 18 | } 19 | 20 | public function isRedirect() 21 | { 22 | return $this->getRedirectUrl() !== null; 23 | } 24 | 25 | public function getRedirectUrl() 26 | { 27 | if (isset($this->data['links']) && is_array($this->data['links'])) { 28 | foreach ($this->data['links'] as $key => $value) { 29 | if ($value['rel'] == 'approval_url') { 30 | return $value['href']; 31 | } 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | 38 | /** 39 | * Get the URL to complete (execute) the purchase or agreement. 40 | * 41 | * The URL is embedded in the links section of the purchase or create 42 | * subscription request response. 43 | * 44 | * @return string 45 | */ 46 | public function getCompleteUrl() 47 | { 48 | if (isset($this->data['links']) && is_array($this->data['links'])) { 49 | foreach ($this->data['links'] as $key => $value) { 50 | if ($value['rel'] == 'execute') { 51 | return $value['href']; 52 | } 53 | } 54 | } 55 | 56 | return null; 57 | } 58 | 59 | public function getTransactionReference() 60 | { 61 | // The transaction reference for a paypal purchase request or for a 62 | // paypal create subscription request ends up in the execute URL 63 | // in the links section of the response. 64 | $completeUrl = $this->getCompleteUrl(); 65 | if (empty($completeUrl)) { 66 | return parent::getTransactionReference(); 67 | } 68 | 69 | $urlParts = explode('/', $completeUrl); 70 | 71 | // The last element of the URL should be "execute" 72 | $execute = end($urlParts); 73 | if (!in_array($execute, array('execute', 'agreement-execute'))) { 74 | return parent::getTransactionReference(); 75 | } 76 | 77 | // The penultimate element should be the transaction reference 78 | return prev($urlParts); 79 | } 80 | 81 | /** 82 | * Get the required redirect method (either GET or POST). 83 | * 84 | * @return string 85 | */ 86 | public function getRedirectMethod() 87 | { 88 | return 'GET'; 89 | } 90 | 91 | /** 92 | * Gets the redirect form data array, if the redirect method is POST. 93 | * 94 | * @return null 95 | */ 96 | public function getRedirectData() 97 | { 98 | return null; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Message/RestSuspendSubscriptionRequest.php: -------------------------------------------------------------------------------- 1 | 23 | * // Create a gateway for the PayPal REST Gateway 24 | * // (routes to GatewayFactory::create) 25 | * $gateway = Omnipay::create('PayPal_Rest'); 26 | * 27 | * // Initialise the gateway 28 | * $gateway->initialize(array( 29 | * 'clientId' => 'MyPayPalClientId', 30 | * 'secret' => 'MyPayPalSecret', 31 | * 'testMode' => true, // Or false when you are ready for live transactions 32 | * )); 33 | * 34 | * // Do a suspend subscription transaction on the gateway 35 | * $transaction = $gateway->suspendSubscription(array( 36 | * 'transactionReference' => $subscription_id, 37 | * 'description' => "Suspending the agreement.", 38 | * )); 39 | * $response = $transaction->send(); 40 | * if ($response->isSuccessful()) { 41 | * echo "Suspend Subscription transaction was successful!\n"; 42 | * } 43 | * 44 | * 45 | * Note that the subscription_id that you get from calling the response's 46 | * getTransactionReference() method at the end of the completeSubscription 47 | * call will be different to the one that you got after calling the response's 48 | * getTransactionReference() method at the end of the createSubscription 49 | * call. The one that you get from completeSubscription is the correct 50 | * one to use going forwards (e.g. for cancelling or updating the subscription). 51 | * 52 | * ### Request Sample 53 | * 54 | * This is from the PayPal web site: 55 | * 56 | * 57 | * curl -v POST https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/suspend \ 58 | * -H 'Content-Type:application/json' \ 59 | * -H 'Authorization: Bearer ' \ 60 | * -d '{ 61 | * "note": "Suspending the agreement." 62 | * }' 63 | * 64 | * 65 | * @link https://developer.paypal.com/docs/api/#suspend-an-agreement 66 | * @see RestCreateSubscriptionRequest 67 | * @see Omnipay\PayPal\RestGateway 68 | */ 69 | class RestSuspendSubscriptionRequest extends AbstractRestRequest 70 | { 71 | public function getData() 72 | { 73 | $this->validate('transactionReference', 'description'); 74 | $data = array( 75 | 'note' => $this->getDescription(), 76 | ); 77 | 78 | return $data; 79 | } 80 | 81 | /** 82 | * Get transaction endpoint. 83 | * 84 | * Subscriptions are executed using the /billing-agreements resource. 85 | * 86 | * @return string 87 | */ 88 | protected function getEndpoint() 89 | { 90 | return parent::getEndpoint() . '/payments/billing-agreements/' . 91 | $this->getTransactionReference() . '/suspend'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Message/RestCancelSubscriptionRequest.php: -------------------------------------------------------------------------------- 1 | 23 | * // Create a gateway for the PayPal REST Gateway 24 | * // (routes to GatewayFactory::create) 25 | * $gateway = Omnipay::create('PayPal_Rest'); 26 | * 27 | * // Initialise the gateway 28 | * $gateway->initialize(array( 29 | * 'clientId' => 'MyPayPalClientId', 30 | * 'secret' => 'MyPayPalSecret', 31 | * 'testMode' => true, // Or false when you are ready for live transactions 32 | * )); 33 | * 34 | * // Do a cancel subscription transaction on the gateway 35 | * $transaction = $gateway->cancelSubscription(array( 36 | * 'transactionReference' => $subscription_id, 37 | * 'description' => "Cancelling the agreement.", 38 | * )); 39 | * $response = $transaction->send(); 40 | * if ($response->isSuccessful()) { 41 | * echo "Cancel Subscription transaction was successful!\n"; 42 | * } 43 | * 44 | * 45 | * Note that the subscription_id that you get from calling the response's 46 | * getTransactionReference() method at the end of the completeSubscription 47 | * call will be different to the one that you got after calling the response's 48 | * getTransactionReference() method at the end of the createSubscription 49 | * call. The one that you get from completeSubscription is the correct 50 | * one to use going forwards (e.g. for cancelling or updating the subscription). 51 | * 52 | * ### Request Sample 53 | * 54 | * This is from the PayPal web site: 55 | * 56 | * 57 | * curl -v POST https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/cancel \ 58 | * -H 'Content-Type:application/json' \ 59 | * -H 'Authorization: Bearer ' \ 60 | * -d '{ 61 | * "note": "Canceling the agreement." 62 | * }' 63 | * 64 | * 65 | * @link https://developer.paypal.com/docs/api/#cancel-an-agreement 66 | * @see RestCreateSubscriptionRequest 67 | * @see Omnipay\PayPal\RestGateway 68 | */ 69 | class RestCancelSubscriptionRequest extends AbstractRestRequest 70 | { 71 | public function getData() 72 | { 73 | $this->validate('transactionReference', 'description'); 74 | $data = array( 75 | 'note' => $this->getDescription(), 76 | ); 77 | 78 | return $data; 79 | } 80 | 81 | /** 82 | * Get transaction endpoint. 83 | * 84 | * Subscriptions are executed using the /billing-agreements resource. 85 | * 86 | * @return string 87 | */ 88 | protected function getEndpoint() 89 | { 90 | return parent::getEndpoint() . '/payments/billing-agreements/' . 91 | $this->getTransactionReference() . '/cancel'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Message/RestReactivateSubscriptionRequest.php: -------------------------------------------------------------------------------- 1 | 23 | * // Create a gateway for the PayPal REST Gateway 24 | * // (routes to GatewayFactory::create) 25 | * $gateway = Omnipay::create('PayPal_Rest'); 26 | * 27 | * // Initialise the gateway 28 | * $gateway->initialize(array( 29 | * 'clientId' => 'MyPayPalClientId', 30 | * 'secret' => 'MyPayPalSecret', 31 | * 'testMode' => true, // Or false when you are ready for live transactions 32 | * )); 33 | * 34 | * // Do a reactivate subscription transaction on the gateway 35 | * $transaction = $gateway->reactivateSubscription(array( 36 | * 'transactionReference' => $subscription_id, 37 | * 'description' => "Reactivating the agreement.", 38 | * )); 39 | * $response = $transaction->send(); 40 | * if ($response->isSuccessful()) { 41 | * echo "Reactivate Subscription transaction was successful!\n"; 42 | * } 43 | * 44 | * 45 | * Note that the subscription_id that you get from calling the response's 46 | * getTransactionReference() method at the end of the completeSubscription 47 | * call will be different to the one that you got after calling the response's 48 | * getTransactionReference() method at the end of the createSubscription 49 | * call. The one that you get from completeSubscription is the correct 50 | * one to use going forwards (e.g. for cancelling or updating the subscription). 51 | * 52 | * ### Request Sample 53 | * 54 | * This is from the PayPal web site: 55 | * 56 | * 57 | * curl -v POST https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/re-activate \ 58 | * -H 'Content-Type:application/json' \ 59 | * -H 'Authorization: Bearer ' \ 60 | * -d '{ 61 | * "note": "Reactivating the agreement." 62 | * }' 63 | * 64 | * 65 | * @link https://developer.paypal.com/docs/api/#reactivate-an-agreement 66 | * @see RestCreateSubscriptionRequest 67 | * @see Omnipay\PayPal\RestGateway 68 | */ 69 | class RestReactivateSubscriptionRequest extends AbstractRestRequest 70 | { 71 | public function getData() 72 | { 73 | $this->validate('transactionReference', 'description'); 74 | $data = array( 75 | 'note' => $this->getDescription(), 76 | ); 77 | 78 | return $data; 79 | } 80 | 81 | /** 82 | * Get transaction endpoint. 83 | * 84 | * Subscriptions are executed using the /billing-agreements resource. 85 | * 86 | * @return string 87 | */ 88 | protected function getEndpoint() 89 | { 90 | return parent::getEndpoint() . '/payments/billing-agreements/' . 91 | $this->getTransactionReference() . '/re-activate'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Message/ExpressTransactionSearchRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new ExpressTransactionSearchRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | } 21 | 22 | public function testGetData() 23 | { 24 | $startDate = '2015-01-01'; 25 | $endDate = '2016-01-01'; 26 | 27 | $this->request->initialize(array( 28 | 'amount' => '10.00', 29 | 'currency' => 'USD', 30 | 'startDate' => $startDate, 31 | 'endDate' => $endDate, 32 | 'salutation' => 'Mr.', 33 | 'firstName' => 'Jhon', 34 | 'middleName' => 'Carter', 35 | 'lastName' => 'Macgiver', 36 | 'suffix' => 'Jh', 37 | 'email' => 'test@email.com', 38 | 'receiver' => 'Patt Doret', 39 | 'receiptId' => '1111', 40 | 'transactionId' => 'XKCD', 41 | 'invoiceNumber' => '123456789', 42 | 'card' => array('number' => '376449047333005'), 43 | 'auctionItemNumber' => '321564', 44 | 'transactionClass' => 'Received', 45 | 'status' => 'Success', 46 | 'profileId' => '00000000000' 47 | )); 48 | 49 | $data = $this->request->getData(); 50 | 51 | $startDate = new \DateTime($startDate); 52 | $endDate = new \DateTime($endDate); 53 | 54 | $this->assertSame('10.00', $data['AMT']); 55 | $this->assertSame('USD', $data['CURRENCYCODE']); 56 | $this->assertSame($startDate->format(\DateTime::ISO8601), $data['STARTDATE']); 57 | $this->assertSame($endDate->format(\DateTime::ISO8601), $data['ENDDATE']); 58 | $this->assertSame('Mr.', $data['SALUTATION']); 59 | $this->assertSame('Jhon', $data['FIRSTNAME']); 60 | $this->assertSame('Carter', $data['MIDDLENAME']); 61 | $this->assertSame('Macgiver', $data['LASTNAME']); 62 | $this->assertSame('Jh', $data['SUFFIX']); 63 | $this->assertSame('test@email.com', $data['EMAIL']); 64 | $this->assertSame('XKCD', $data['TRANSACTIONID']); 65 | $this->assertSame('123456789', $data['INVNUM']); 66 | $this->assertSame('376449047333005', $data['ACCT']); 67 | $this->assertSame('321564', $data['AUCTIONITEMNUMBER']); 68 | $this->assertSame('Received', $data['TRANSACTIONCLASS']); 69 | $this->assertSame('Success', $data['STATUS']); 70 | $this->assertSame('00000000000', $data['PROFILEID']); 71 | } 72 | 73 | public function testWithoutStartDate() 74 | { 75 | $this->request->initialize(array()); 76 | 77 | $this->expectException(InvalidRequestException::class); 78 | $this->expectExceptionMessage('The startDate parameter is required'); 79 | 80 | $this->request->getData(); 81 | } 82 | 83 | public function testAmountWithoutCurrency() 84 | { 85 | $this->request->setStartDate('2015-01-01'); 86 | $this->request->setAmount(150.00); 87 | 88 | $this->expectException(InvalidRequestException::class); 89 | $this->expectExceptionMessage('The currency parameter is required'); 90 | 91 | $this->request->getData(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Message/RestUpdatePlanRequest.php: -------------------------------------------------------------------------------- 1 | 25 | * // Create a gateway for the PayPal REST Gateway 26 | * // (routes to GatewayFactory::create) 27 | * $gateway = Omnipay::create('PayPal_Rest'); 28 | * 29 | * // Initialise the gateway 30 | * $gateway->initialize(array( 31 | * 'clientId' => 'MyPayPalClientId', 32 | * 'secret' => 'MyPayPalSecret', 33 | * 'testMode' => true, // Or false when you are ready for live transactions 34 | * )); 35 | * 36 | * // Update the billing plan 37 | * $transaction = $gateway->updatePlan(array( 38 | * 'transactionReference' => $plan_id, 39 | * 'state' => $gateway::BILLING_PLAN_STATE_ACTIVE, 40 | * )); 41 | * $response = $transaction->send(); 42 | * if ($response->isSuccessful()) { 43 | * echo "Update Plan transaction was successful!\n"; 44 | * } 45 | * 46 | * 47 | * ### Request Sample 48 | * 49 | * This is from the PayPal web site: 50 | * 51 | * 52 | * curl -v -k -X PATCH 'https://api.sandbox.paypal.com/v1/payments/billing-plans/P-94458432VR012762KRWBZEUA' \ 53 | * -H "Content-Type: application/json" \ 54 | * -H "Authorization: Bearer " \ 55 | * -d '[ 56 | * { 57 | * "path": "/", 58 | * "value": { 59 | * "state": "ACTIVE" 60 | * }, 61 | * "op": "replace" 62 | * } 63 | * ]' 64 | * 65 | * 66 | * ### Response 67 | * 68 | * Returns the HTTP status of 200 if the call is successful. 69 | * 70 | * @link https://developer.paypal.com/docs/api/#update-a-plan 71 | * @see RestCreateSubscriptionRequest 72 | * @see Omnipay\PayPal\RestGateway 73 | */ 74 | class RestUpdatePlanRequest extends AbstractRestRequest 75 | { 76 | /** 77 | * Get the plan state 78 | * 79 | * @return string 80 | */ 81 | public function getState() 82 | { 83 | return $this->getParameter('state'); 84 | } 85 | 86 | /** 87 | * Set the plan state 88 | * 89 | * @param string $value 90 | * @return RestUpdatePlanRequest provides a fluent interface. 91 | */ 92 | public function setState($value) 93 | { 94 | return $this->setParameter('state', $value); 95 | } 96 | 97 | public function getData() 98 | { 99 | $this->validate('transactionReference', 'state'); 100 | $data = array(array( 101 | 'path' => '/', 102 | 'value' => array( 103 | 'state' => $this->getState(), 104 | ), 105 | 'op' => 'replace' 106 | )); 107 | 108 | return $data; 109 | } 110 | 111 | /** 112 | * Get transaction endpoint. 113 | * 114 | * Billing plans are managed using the /billing-plans resource. 115 | * 116 | * @return string 117 | */ 118 | protected function getEndpoint() 119 | { 120 | return parent::getEndpoint() . '/payments/billing-plans/' . $this->getTransactionReference(); 121 | } 122 | 123 | protected function getHttpMethod() 124 | { 125 | return 'PATCH'; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/Message/RestResponseTest.php: -------------------------------------------------------------------------------- 1 | getMockHttpResponse('RestPurchaseSuccess.txt'); 12 | $data = json_decode($httpResponse->getBody()->getContents(), true); 13 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 14 | 15 | $this->assertTrue($response->isSuccessful()); 16 | $this->assertSame('44E89981F8714392Y', $response->getTransactionReference()); 17 | $this->assertNull($response->getMessage()); 18 | } 19 | 20 | public function testPurchaseFailure() 21 | { 22 | $httpResponse = $this->getMockHttpResponse('RestPurchaseFailure.txt'); 23 | $data = json_decode($httpResponse->getBody()->getContents(), true); 24 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 25 | 26 | $this->assertFalse($response->isSuccessful()); 27 | $this->assertNull($response->getTransactionReference()); 28 | $this->assertSame('Invalid request - see details', $response->getMessage()); 29 | } 30 | 31 | public function testCompletePurchaseSuccess() 32 | { 33 | $httpResponse = $this->getMockHttpResponse('RestCompletePurchaseSuccess.txt'); 34 | $data = json_decode($httpResponse->getBody()->getContents(), true); 35 | 36 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 37 | 38 | $this->assertTrue($response->isSuccessful()); 39 | $this->assertSame('9EA05739TH369572R', $response->getTransactionReference()); 40 | $this->assertNull($response->getMessage()); 41 | } 42 | 43 | public function testCompletePurchaseFailure() 44 | { 45 | $httpResponse = $this->getMockHttpResponse('RestCompletePurchaseFailure.txt'); 46 | $data = json_decode($httpResponse->getBody()->getContents(), true); 47 | 48 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 49 | 50 | $this->assertFalse($response->isSuccessful()); 51 | $this->assertNull($response->getTransactionReference()); 52 | $this->assertSame('This request is invalid due to the current state of the payment', $response->getMessage()); 53 | } 54 | 55 | public function testTokenFailure() 56 | { 57 | $httpResponse = $this->getMockHttpResponse('RestTokenFailure.txt'); 58 | $data = json_decode($httpResponse->getBody()->getContents(), true); 59 | 60 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 61 | 62 | $this->assertFalse($response->isSuccessful()); 63 | $this->assertSame('Client secret does not match for this client', $response->getMessage()); 64 | } 65 | 66 | public function testAuthorizeSuccess() 67 | { 68 | $httpResponse = $this->getMockHttpResponse('RestAuthorizationSuccess.txt'); 69 | $data = json_decode($httpResponse->getBody()->getContents(), true); 70 | 71 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 72 | 73 | $this->assertTrue($response->isSuccessful()); 74 | $this->assertSame('58N7596879166930B', $response->getTransactionReference()); 75 | $this->assertNull($response->getMessage()); 76 | } 77 | 78 | public function testCreateCardSuccess() 79 | { 80 | $httpResponse = $this->getMockHttpResponse('RestCreateCardSuccess.txt'); 81 | $data = json_decode($httpResponse->getBody()->getContents(), true); 82 | 83 | $response = new RestResponse($this->getMockRequest(), $data, $httpResponse->getStatusCode()); 84 | 85 | $this->assertTrue($response->isSuccessful()); 86 | $this->assertSame('CARD-70E78145XN686604FKO3L6OQ', $response->getCardReference()); 87 | $this->assertNull($response->getMessage()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Message/RestPurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | getValidCard()); 16 | $card->setStartMonth(1); 17 | $card->setStartYear(2000); 18 | 19 | $this->request = new RestPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | $this->request->initialize(array( 21 | 'amount' => '10.00', 22 | 'currency' => 'USD', 23 | 'card' => $card 24 | )); 25 | 26 | $this->request->setTransactionId('abc123'); 27 | $this->request->setDescription('Sheep'); 28 | $this->request->setClientIp('127.0.0.1'); 29 | 30 | $data = $this->request->getData(); 31 | 32 | $this->assertSame('sale', $data['intent']); 33 | $this->assertSame('credit_card', $data['payer']['payment_method']); 34 | $this->assertSame('10.00', $data['transactions'][0]['amount']['total']); 35 | $this->assertSame('USD', $data['transactions'][0]['amount']['currency']); 36 | $this->assertSame('abc123 : Sheep', $data['transactions'][0]['description']); 37 | 38 | $this->assertSame($card->getNumber(), $data['payer']['funding_instruments'][0]['credit_card']['number']); 39 | $this->assertSame($card->getBrand(), $data['payer']['funding_instruments'][0]['credit_card']['type']); 40 | $this->assertSame($card->getExpiryMonth(), $data['payer']['funding_instruments'][0]['credit_card']['expire_month']); 41 | $this->assertSame($card->getExpiryYear(), $data['payer']['funding_instruments'][0]['credit_card']['expire_year']); 42 | $this->assertSame($card->getCvv(), $data['payer']['funding_instruments'][0]['credit_card']['cvv2']); 43 | 44 | $this->assertSame($card->getFirstName(), $data['payer']['funding_instruments'][0]['credit_card']['first_name']); 45 | $this->assertSame($card->getLastName(), $data['payer']['funding_instruments'][0]['credit_card']['last_name']); 46 | $this->assertSame($card->getAddress1(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['line1']); 47 | $this->assertSame($card->getAddress2(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['line2']); 48 | $this->assertSame($card->getCity(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['city']); 49 | $this->assertSame($card->getState(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['state']); 50 | $this->assertSame($card->getPostcode(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['postal_code']); 51 | $this->assertSame($card->getCountry(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['country_code']); 52 | } 53 | 54 | public function testGetDataWithCardRef() 55 | { 56 | $this->request = new RestPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); 57 | $this->request->initialize(array( 58 | 'amount' => '10.00', 59 | 'currency' => 'USD', 60 | 'cardReference' => 'CARD-123', 61 | )); 62 | 63 | $this->request->setTransactionId('abc123'); 64 | $this->request->setDescription('Sheep'); 65 | $this->request->setClientIp('127.0.0.1'); 66 | 67 | $data = $this->request->getData(); 68 | 69 | $this->assertSame('sale', $data['intent']); 70 | $this->assertSame('credit_card', $data['payer']['payment_method']); 71 | $this->assertSame('10.00', $data['transactions'][0]['amount']['total']); 72 | $this->assertSame('USD', $data['transactions'][0]['amount']['currency']); 73 | $this->assertSame('abc123 : Sheep', $data['transactions'][0]['description']); 74 | $this->assertSame('CARD-123', $data['payer']['funding_instruments'][0]['credit_card_token']['credit_card_id']); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Message/ExpressCompletePurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | 19 | $request = $this->getHttpRequest(); 20 | $request->query->set('PayerID', 'Payer-1234'); 21 | $request->query->set('token', 'TOKEN1234'); 22 | 23 | $this->request = new ExpressCompletePurchaseRequest($client, $request); 24 | } 25 | 26 | public function testGetData() 27 | { 28 | $this->request->setAmount('1.23'); 29 | $this->request->setCurrency('USD'); 30 | $this->request->setTransactionId('ABC-123'); 31 | $this->request->setUsername('testuser'); 32 | $this->request->setPassword('testpass'); 33 | $this->request->setSignature('SIG'); 34 | $this->request->setSubject('SUB'); 35 | $this->request->setDescription('DESC'); 36 | $this->request->setNotifyUrl('https://www.example.com/notify'); 37 | $this->request->setMaxAmount('0.00'); 38 | $this->request->setTaxAmount('0.00'); 39 | $this->request->setShippingAmount('0.00'); 40 | $this->request->setHandlingAmount('0.00'); 41 | $this->request->setShippingDiscount('0.00'); 42 | $this->request->setInsuranceAmount('0.00'); 43 | 44 | $expected = array(); 45 | $expected['METHOD'] = 'DoExpressCheckoutPayment'; 46 | $expected['PAYMENTREQUEST_0_PAYMENTACTION'] = 'Sale'; 47 | $expected['PAYMENTREQUEST_0_AMT'] = '1.23'; 48 | $expected['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD'; 49 | $expected['PAYMENTREQUEST_0_INVNUM'] = 'ABC-123'; 50 | $expected['PAYMENTREQUEST_0_DESC'] = 'DESC'; 51 | $expected['PAYMENTREQUEST_0_NOTIFYURL'] = 'https://www.example.com/notify'; 52 | $expected['USER'] = 'testuser'; 53 | $expected['PWD'] = 'testpass'; 54 | $expected['SIGNATURE'] = 'SIG'; 55 | $expected['SUBJECT'] = 'SUB'; 56 | $expected['VERSION'] = ExpressCompletePurchaseRequest::API_VERSION; 57 | $expected['TOKEN'] = 'TOKEN1234'; 58 | $expected['PAYERID'] = 'Payer-1234'; 59 | $expected['MAXAMT'] = '0.00'; 60 | $expected['PAYMENTREQUEST_0_TAXAMT'] = '0.00'; 61 | $expected['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0.00'; 62 | $expected['PAYMENTREQUEST_0_HANDLINGAMT'] = '0.00'; 63 | $expected['PAYMENTREQUEST_0_SHIPDISCAMT'] = '0.00'; 64 | $expected['PAYMENTREQUEST_0_INSURANCEAMT'] = '0.00'; 65 | 66 | $this->assertEquals($expected, $this->request->getData()); 67 | } 68 | 69 | public function testGetDataWithItems() 70 | { 71 | $this->request->setAmount('50.00'); 72 | $this->request->setCurrency('USD'); 73 | $this->request->setTransactionId('ABC-123'); 74 | $this->request->setUsername('testuser'); 75 | $this->request->setPassword('testpass'); 76 | $this->request->setSignature('SIG'); 77 | $this->request->setSubject('SUB'); 78 | $this->request->setDescription('DESC'); 79 | 80 | $this->request->setItems(array( 81 | array('name' => 'Floppy Disk', 'description' => 'MS-DOS', 'quantity' => 2, 'price' => 10), 82 | array('name' => 'CD-ROM', 'description' => 'Windows 95', 'quantity' => 1, 'price' => 40), 83 | )); 84 | 85 | $data = $this->request->getData(); 86 | $this->assertSame('Floppy Disk', $data['L_PAYMENTREQUEST_0_NAME0']); 87 | $this->assertSame('MS-DOS', $data['L_PAYMENTREQUEST_0_DESC0']); 88 | $this->assertSame(2, $data['L_PAYMENTREQUEST_0_QTY0']); 89 | $this->assertSame('10.00', $data['L_PAYMENTREQUEST_0_AMT0']); 90 | 91 | $this->assertSame('CD-ROM', $data['L_PAYMENTREQUEST_0_NAME1']); 92 | $this->assertSame('Windows 95', $data['L_PAYMENTREQUEST_0_DESC1']); 93 | $this->assertSame(1, $data['L_PAYMENTREQUEST_0_QTY1']); 94 | $this->assertSame('40.00', $data['L_PAYMENTREQUEST_0_AMT1']); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Message/ExpressCompleteAuthorizeRequestTest.php: -------------------------------------------------------------------------------- 1 | getHttpClient(); 18 | 19 | $request = $this->getHttpRequest(); 20 | $request->query->set('PayerID', 'Payer-1234'); 21 | $request->query->set('token', 'TOKEN1234'); 22 | 23 | $this->request = new ExpressCompleteAuthorizeRequest($client, $request); 24 | } 25 | 26 | public function testGetData() 27 | { 28 | $this->request->setAmount('1.23'); 29 | $this->request->setCurrency('USD'); 30 | $this->request->setTransactionId('ABC-123'); 31 | $this->request->setUsername('testuser'); 32 | $this->request->setPassword('testpass'); 33 | $this->request->setSignature('SIG'); 34 | $this->request->setSubject('SUB'); 35 | $this->request->setDescription('DESC'); 36 | $this->request->setNotifyUrl('https://www.example.com/notify'); 37 | $this->request->setMaxAmount('0.00'); 38 | $this->request->setTaxAmount('0.00'); 39 | $this->request->setShippingAmount('0.00'); 40 | $this->request->setHandlingAmount('0.00'); 41 | $this->request->setShippingDiscount('0.00'); 42 | $this->request->setInsuranceAmount('0.00'); 43 | 44 | $expected = array(); 45 | $expected['METHOD'] = 'DoExpressCheckoutPayment'; 46 | $expected['PAYMENTREQUEST_0_PAYMENTACTION'] = 'Authorization'; 47 | $expected['PAYMENTREQUEST_0_AMT'] = '1.23'; 48 | $expected['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD'; 49 | $expected['PAYMENTREQUEST_0_INVNUM'] = 'ABC-123'; 50 | $expected['PAYMENTREQUEST_0_DESC'] = 'DESC'; 51 | $expected['PAYMENTREQUEST_0_NOTIFYURL'] = 'https://www.example.com/notify'; 52 | $expected['USER'] = 'testuser'; 53 | $expected['PWD'] = 'testpass'; 54 | $expected['SIGNATURE'] = 'SIG'; 55 | $expected['SUBJECT'] = 'SUB'; 56 | $expected['VERSION'] = ExpressCompleteAuthorizeRequest::API_VERSION; 57 | $expected['TOKEN'] = 'TOKEN1234'; 58 | $expected['PAYERID'] = 'Payer-1234'; 59 | $expected['MAXAMT'] = '0.00'; 60 | $expected['PAYMENTREQUEST_0_TAXAMT'] = '0.00'; 61 | $expected['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0.00'; 62 | $expected['PAYMENTREQUEST_0_HANDLINGAMT'] = '0.00'; 63 | $expected['PAYMENTREQUEST_0_SHIPDISCAMT'] = '0.00'; 64 | $expected['PAYMENTREQUEST_0_INSURANCEAMT'] = '0.00'; 65 | 66 | $this->assertEquals($expected, $this->request->getData()); 67 | } 68 | 69 | public function testGetDataWithItems() 70 | { 71 | $this->request->setAmount('50.00'); 72 | $this->request->setCurrency('USD'); 73 | $this->request->setTransactionId('ABC-123'); 74 | $this->request->setUsername('testuser'); 75 | $this->request->setPassword('testpass'); 76 | $this->request->setSignature('SIG'); 77 | $this->request->setSubject('SUB'); 78 | $this->request->setDescription('DESC'); 79 | 80 | $this->request->setItems(array( 81 | array('name' => 'Floppy Disk', 'description' => 'MS-DOS', 'quantity' => 2, 'price' => 10), 82 | array('name' => 'CD-ROM', 'description' => 'Windows 95', 'quantity' => 1, 'price' => 40), 83 | )); 84 | 85 | $data = $this->request->getData(); 86 | $this->assertSame('Floppy Disk', $data['L_PAYMENTREQUEST_0_NAME0']); 87 | $this->assertSame('MS-DOS', $data['L_PAYMENTREQUEST_0_DESC0']); 88 | $this->assertSame(2, $data['L_PAYMENTREQUEST_0_QTY0']); 89 | $this->assertSame('10.00', $data['L_PAYMENTREQUEST_0_AMT0']); 90 | 91 | $this->assertSame('CD-ROM', $data['L_PAYMENTREQUEST_0_NAME1']); 92 | $this->assertSame('Windows 95', $data['L_PAYMENTREQUEST_0_DESC1']); 93 | $this->assertSame(1, $data['L_PAYMENTREQUEST_0_QTY1']); 94 | $this->assertSame('40.00', $data['L_PAYMENTREQUEST_0_AMT1']); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Message/RestCreateCardRequest.php: -------------------------------------------------------------------------------- 1 | 27 | * // Create a gateway for the PayPal RestGateway 28 | * // (routes to GatewayFactory::create) 29 | * $gateway = Omnipay::create('PayPal_Rest'); 30 | * 31 | * // Initialise the gateway 32 | * $gateway->initialize(array( 33 | * 'clientId' => 'MyPayPalClientId', 34 | * 'secret' => 'MyPayPalSecret', 35 | * 'testMode' => true, // Or false when you are ready for live transactions 36 | * )); 37 | * 38 | * // Create a credit card object 39 | * // DO NOT USE THESE CARD VALUES -- substitute your own 40 | * // see the documentation in the class header. 41 | * $card = new CreditCard(array( 42 | * 'firstName' => 'Example', 43 | * 'lastName' => 'User', 44 | * 'number' => '4111111111111111', 45 | * 'expiryMonth' => '01', 46 | * 'expiryYear' => '2020', 47 | * 'cvv' => '123', 48 | * 'billingAddress1' => '1 Scrubby Creek Road', 49 | * 'billingCountry' => 'AU', 50 | * 'billingCity' => 'Scrubby Creek', 51 | * 'billingPostcode' => '4999', 52 | * 'billingState' => 'QLD', 53 | * )); 54 | * 55 | * // Do a create card transaction on the gateway 56 | * $transaction = $gateway->createCard(array( 57 | * 'card' => $card, 58 | * )); 59 | * $response = $transaction->send(); 60 | * if ($response->isSuccessful()) { 61 | * echo "Create card transaction was successful!\n"; 62 | * // Find the card ID 63 | * $card_id = $response->getTransactionReference(); 64 | * } 65 | * 66 | * 67 | * @link https://developer.paypal.com/docs/api/#vault 68 | * @link https://developer.paypal.com/docs/api/#store-a-credit-card 69 | * @link http://bit.ly/1wUQ33R 70 | */ 71 | class RestCreateCardRequest extends AbstractRestRequest 72 | { 73 | public function getData() 74 | { 75 | $this->validate('card'); 76 | $this->getCard()->validate(); 77 | 78 | $data = array( 79 | 'number' => $this->getCard()->getNumber(), 80 | 'type' => $this->getCard()->getBrand(), 81 | 'expire_month' => $this->getCard()->getExpiryMonth(), 82 | 'expire_year' => $this->getCard()->getExpiryYear(), 83 | 'cvv2' => $this->getCard()->getCvv(), 84 | 'first_name' => $this->getCard()->getFirstName(), 85 | 'last_name' => $this->getCard()->getLastName(), 86 | 'billing_address' => array( 87 | 'line1' => $this->getCard()->getAddress1(), 88 | //'line2' => $this->getCard()->getAddress2(), 89 | 'city' => $this->getCard()->getCity(), 90 | 'state' => $this->getCard()->getState(), 91 | 'postal_code' => $this->getCard()->getPostcode(), 92 | 'country_code' => strtoupper($this->getCard()->getCountry()), 93 | ) 94 | ); 95 | 96 | // There's currently a quirk with the REST API that requires line2 to be 97 | // non-empty if it's present. Jul 14, 2014 98 | $line2 = $this->getCard()->getAddress2(); 99 | if (!empty($line2)) { 100 | $data['billing_address']['line2'] = $line2; 101 | } 102 | 103 | return $data; 104 | } 105 | 106 | protected function getEndpoint() 107 | { 108 | return parent::getEndpoint() . '/vault/credit-cards'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Message/RestCompleteSubscriptionRequest.php: -------------------------------------------------------------------------------- 1 | getRedirectUrl(). Once 31 | * the customer has approved the agreement and be returned to the returnUrl 32 | * in the call. The returnUrl can contain the following code to complete 33 | * the agreement: 34 | * 35 | * 36 | * // Create a gateway for the PayPal REST Gateway 37 | * // (routes to GatewayFactory::create) 38 | * $gateway = Omnipay::create('PayPal_Rest'); 39 | * 40 | * // Initialise the gateway 41 | * $gateway->initialize(array( 42 | * 'clientId' => 'MyPayPalClientId', 43 | * 'secret' => 'MyPayPalSecret', 44 | * 'testMode' => true, // Or false when you are ready for live transactions 45 | * )); 46 | * 47 | * // Do a complete subscription transaction on the gateway 48 | * $transaction = $gateway->completeSubscription(array( 49 | * 'transactionReference' => $subscription_id, 50 | * )); 51 | * $response = $transaction->send(); 52 | * if ($response->isSuccessful()) { 53 | * echo "Complete Subscription transaction was successful!\n"; 54 | * $subscription_id = $response->getTransactionReference(); 55 | * echo "Subscription reference = " . $subscription_id; 56 | * } 57 | * 58 | * 59 | * Note that the subscription_id that you get from calling the response's 60 | * getTransactionReference() method at the end of the completeSubscription 61 | * call will be different to the one that you got after calling the response's 62 | * getTransactionReference() method at the end of the createSubscription 63 | * call. The one that you get from completeSubscription is the correct 64 | * one to use going forwards (e.g. for cancelling or updating the subscription). 65 | * 66 | * ### Request Sample 67 | * 68 | * This is from the PayPal web site: 69 | * 70 | * 71 | * curl -v POST https://api.sandbox.paypal.com/v1/payments/billing-agreements/EC-0JP008296V451950C/agreement-execute \ 72 | * -H 'Content-Type:application/json' \ 73 | * -H 'Authorization: Bearer ' \ 74 | * -d '{}' 75 | * 76 | * 77 | * ### Response Sample 78 | * 79 | * This is from the PayPal web site: 80 | * 81 | * 82 | * { 83 | * "id": "I-0LN988D3JACS", 84 | * "links": [ 85 | * { 86 | * "href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS", 87 | * "rel": "self", 88 | * "method": "GET" 89 | * } 90 | * ] 91 | * } 92 | * 93 | * 94 | * @link https://developer.paypal.com/docs/api/#execute-an-agreement 95 | * @see RestCreateSubscriptionRequest 96 | * @see Omnipay\PayPal\RestGateway 97 | */ 98 | class RestCompleteSubscriptionRequest extends AbstractRestRequest 99 | { 100 | public function getData() 101 | { 102 | $this->validate('transactionReference'); 103 | $data = array(); 104 | 105 | return $data; 106 | } 107 | 108 | /** 109 | * Get transaction endpoint. 110 | * 111 | * Subscriptions are executed using the /billing-agreements resource. 112 | * 113 | * @return string 114 | */ 115 | protected function getEndpoint() 116 | { 117 | return parent::getEndpoint() . '/payments/billing-agreements/' . 118 | $this->getTransactionReference() . '/agreement-execute'; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Message/RestVerifyWebhookSignatureRequest.php: -------------------------------------------------------------------------------- 1 | getParameter('auth_algo'); 16 | } 17 | 18 | /** 19 | * @return string 20 | */ 21 | public function getCertUrl() 22 | { 23 | return $this->getParameter('cert_url'); 24 | } 25 | 26 | /** 27 | * @inheritDoc 28 | */ 29 | public function getData() 30 | { 31 | return [ 32 | 'transmission_id' => $this->getTransmissionId(), 33 | 'auth_algo' => $this->getAuthAlgo(), 34 | 'cert_url' => $this->getCertUrl(), 35 | 'transmission_sig' => $this->getTransmissionSig(), 36 | 'transmission_time' => $this->getTransmissionTime(), 37 | 'webhook_event' => $this->getWebhookEvent(), 38 | 'webhook_id' => $this->getWebhookId(), 39 | ]; 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function getEndpoint() 46 | { 47 | return parent::getEndpoint().'/notifications/verify-webhook-signature'; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getTransmissionId() 54 | { 55 | return $this->getParameter('transmission_id'); 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getTransmissionSig() 62 | { 63 | return $this->getParameter('transmission_sig'); 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getTransmissionTime() 70 | { 71 | return $this->getParameter('transmission_time'); 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function getWebhookEvent() 78 | { 79 | return $this->getParameter('webhook_event'); 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getWebhookId() 86 | { 87 | return $this->getParameter('webhook_id'); 88 | } 89 | 90 | /** 91 | * @param string $authAlgo 92 | * 93 | * @return $this 94 | */ 95 | public function setAuthAlgo($authAlgo) 96 | { 97 | return $this->setParameter('auth_algo', $authAlgo); 98 | } 99 | 100 | /** 101 | * @param string $certUrl 102 | * 103 | * @return $this 104 | */ 105 | public function setCertUrl($certUrl) 106 | { 107 | return $this->setParameter('cert_url', $certUrl); 108 | } 109 | 110 | /** 111 | * @param string $transmissionId 112 | * 113 | * @return $this 114 | */ 115 | public function setTransmissionId($transmissionId) 116 | { 117 | return $this->setParameter('transmission_id', $transmissionId); 118 | } 119 | 120 | /** 121 | * @param string $transmissionSig 122 | * 123 | * @return $this 124 | */ 125 | public function setTransmissionSig($transmissionSig) 126 | { 127 | return $this->setParameter('transmission_sig', $transmissionSig); 128 | } 129 | 130 | /** 131 | * @param string $transmissionTime 132 | * 133 | * @return $this 134 | */ 135 | public function setTransmissionTime($transmissionTime) 136 | { 137 | return $this->setParameter('transmission_time', $transmissionTime); 138 | } 139 | 140 | /** 141 | * @param array $webhookEvent 142 | * 143 | * @return $this 144 | */ 145 | public function setWebhookEvent(array $webhookEvent) 146 | { 147 | return $this->setParameter('webhook_event', $webhookEvent); 148 | } 149 | 150 | /** 151 | * @param string $webhookId 152 | * 153 | * @return $this 154 | */ 155 | public function setWebhookId($webhookId) 156 | { 157 | return $this->setParameter('webhook_id', $webhookId); 158 | } 159 | 160 | /** 161 | * @param $data 162 | * @param $statusCode 163 | * 164 | * @return RestVerifyWebhookSignatureResponse 165 | */ 166 | protected function createResponse($data, $statusCode) 167 | { 168 | return $this->response = new RestVerifyWebhookSignatureResponse($this, $data, $statusCode); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Smart little documentation generator. 5 | # GPL/LGPL 6 | # (c) Del 2015 http://www.babel.com.au/ 7 | # 8 | 9 | APPNAME='Omnipay PayPal Gateway Module' 10 | CMDFILE=apigen.cmd.$$ 11 | DESTDIR=./documents 12 | 13 | # 14 | # Find apigen, either in the path or as a local phar file 15 | # 16 | if [ -f apigen.phar ]; then 17 | APIGEN="php apigen.phar" 18 | 19 | else 20 | APIGEN=`which apigen` 21 | if [ ! -f "$APIGEN" ]; then 22 | 23 | # Search for phpdoc if apigen is not found. 24 | if [ -f phpDocumentor.phar ]; then 25 | PHPDOC="php phpDocumentor.phar" 26 | 27 | else 28 | PHPDOC=`which phpdoc` 29 | if [ ! -f "$PHPDOC" ]; then 30 | echo "Neither apigen nor phpdoc is installed in the path or locally, please install one of them" 31 | echo "see http://www.apigen.org/ or http://www.phpdoc.org/" 32 | exit 1 33 | fi 34 | fi 35 | fi 36 | fi 37 | 38 | # 39 | # As of version 4 of apigen need to use the generate subcommand 40 | # 41 | if [ ! -z "$APIGEN" ]; then 42 | APIGEN="$APIGEN generate" 43 | fi 44 | 45 | # 46 | # Without any arguments this builds the entire system documentation, 47 | # making the cache file first if required. 48 | # 49 | if [ -z "$1" ]; then 50 | # 51 | # Check to see that the cache has been made. 52 | # 53 | if [ ! -f dirlist.cache ]; then 54 | echo "Making dirlist.cache file" 55 | $0 makecache 56 | fi 57 | 58 | # 59 | # Build the apigen/phpdoc command in a file. 60 | # 61 | if [ ! -z "$APIGEN" ]; then 62 | echo "$APIGEN --php --tree --title '$APPNAME API Documentation' --destination $DESTDIR/main \\" > $CMDFILE 63 | cat dirlist.cache | while read dir; do 64 | echo "--source $dir \\" >> $CMDFILE 65 | done 66 | echo "" >> $CMDFILE 67 | 68 | elif [ ! -z "$PHPDOC" ]; then 69 | echo "$PHPDOC --sourcecode --title '$APPNAME API Documentation' --target $DESTDIR/main --directory \\" > $CMDFILE 70 | cat dirlist.cache | while read dir; do 71 | echo "${dir},\\" >> $CMDFILE 72 | done 73 | echo "" >> $CMDFILE 74 | 75 | else 76 | "Neither apigen nor phpdoc are found, how did I get here?" 77 | exit 1 78 | fi 79 | 80 | # 81 | # Run the apigen command 82 | # 83 | rm -rf $DESTDIR/main 84 | mkdir -p $DESTDIR/main 85 | . ./$CMDFILE 86 | 87 | /bin/rm -f ./$CMDFILE 88 | 89 | # 90 | # The "makecache" argument causes the script to just make the cache file 91 | # 92 | elif [ "$1" = "makecache" ]; then 93 | echo "Find application source directories" 94 | find src -name \*.php -print | \ 95 | ( 96 | while read file; do 97 | grep -q 'class' $file && dirname $file 98 | done 99 | ) | sort -u | \ 100 | grep -v -E 'config|docs|migrations|phpunit|test|Test|views|web' > dirlist.app 101 | 102 | echo "Find vendor source directories" 103 | find vendor -name \*.php -print | \ 104 | ( 105 | while read file; do 106 | grep -q 'class' $file && dirname $file 107 | done 108 | ) | sort -u | \ 109 | grep -v -E 'config|docs|migrations|phpunit|codesniffer|test|Test|views' > dirlist.vendor 110 | 111 | # 112 | # Filter out any vendor directories for which apigen fails 113 | # 114 | echo "Filter source directories" 115 | mkdir -p $DESTDIR/tmp 116 | cat dirlist.app dirlist.vendor | while read dir; do 117 | if [ ! -z "$APIGEN" ]; then 118 | $APIGEN --quiet --title "Test please ignore" \ 119 | --source $dir \ 120 | --destination $DESTDIR/tmp && ( 121 | echo "Including $dir" 122 | echo $dir >> dirlist.cache 123 | ) || ( 124 | echo "Excluding $dir" 125 | ) 126 | 127 | elif [ ! -z "$PHPDOC" ]; then 128 | $PHPDOC --quiet --title "Test please ignore" \ 129 | --directory $dir \ 130 | --target $DESTDIR/tmp && ( 131 | echo "Including $dir" 132 | echo $dir >> dirlist.cache 133 | ) || ( 134 | echo "Excluding $dir" 135 | ) 136 | 137 | fi 138 | done 139 | echo "Documentation cache dirlist.cache built OK" 140 | 141 | # 142 | # Clean up 143 | # 144 | /bin/rm -rf $DESTDIR/tmp 145 | 146 | fi 147 | -------------------------------------------------------------------------------- /src/ExpressGateway.php: -------------------------------------------------------------------------------- 1 | getParameter('solutionType'); 31 | } 32 | 33 | public function setSolutionType($value) 34 | { 35 | return $this->setParameter('solutionType', $value); 36 | } 37 | 38 | public function getLandingPage() 39 | { 40 | return $this->getParameter('landingPage'); 41 | } 42 | 43 | public function setLandingPage($value) 44 | { 45 | return $this->setParameter('landingPage', $value); 46 | } 47 | 48 | public function getBrandName() 49 | { 50 | return $this->getParameter('brandName'); 51 | } 52 | 53 | public function setBrandName($value) 54 | { 55 | return $this->setParameter('brandName', $value); 56 | } 57 | 58 | public function getHeaderImageUrl() 59 | { 60 | return $this->getParameter('headerImageUrl'); 61 | } 62 | 63 | public function getLogoImageUrl() 64 | { 65 | return $this->getParameter('logoImageUrl'); 66 | } 67 | 68 | public function getBorderColor() 69 | { 70 | return $this->getParameter('borderColor'); 71 | } 72 | 73 | /** 74 | * Header Image URL (Optional) 75 | * 76 | * URL for the image you want to appear at the top left of the payment page. 77 | * The image has a maximum size of 750 pixels wide by 90 pixels high. 78 | * PayPal recommends that you provide an image that is stored on a secure 79 | * (HTTPS) server. 80 | * If you do not specify an image, the business name displays. 81 | * Character length and limitations: 127 single-byte alphanumeric characters 82 | */ 83 | public function setHeaderImageUrl($value) 84 | { 85 | return $this->setParameter('headerImageUrl', $value); 86 | } 87 | 88 | /** 89 | * Logo Image URL (Optional) 90 | * 91 | * URL for the image to appear above the order summary, in place of the 92 | * brand name. 93 | * The recommended size is 190 pixels wide and 60 pixels high. 94 | */ 95 | public function setLogoImageUrl($value) 96 | { 97 | return $this->setParameter('logoImageUrl', $value); 98 | } 99 | 100 | /** 101 | * Border Color (Optional) 102 | * 103 | * The color of the border gradient on payment pages. 104 | * Should be a six character hexadecimal code (i.e. C0C0C0). 105 | */ 106 | public function setBorderColor($value) 107 | { 108 | return $this->setParameter('borderColor', $value); 109 | } 110 | 111 | public function setSellerPaypalAccountId($value) 112 | { 113 | return $this->setParameter('sellerPaypalAccountId', $value); 114 | } 115 | 116 | public function getSellerPaypalAccountId() 117 | { 118 | return $this->getParameter('sellerPaypalAccountId'); 119 | } 120 | 121 | public function authorize(array $parameters = array()) 122 | { 123 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressAuthorizeRequest', $parameters); 124 | } 125 | 126 | public function completeAuthorize(array $parameters = array()) 127 | { 128 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressCompleteAuthorizeRequest', $parameters); 129 | } 130 | 131 | public function purchase(array $parameters = array()) 132 | { 133 | return $this->authorize($parameters); 134 | } 135 | 136 | public function completePurchase(array $parameters = array()) 137 | { 138 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressCompletePurchaseRequest', $parameters); 139 | } 140 | 141 | public function void(array $parameters = array()) 142 | { 143 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressVoidRequest', $parameters); 144 | } 145 | 146 | public function fetchCheckout(array $parameters = array()) 147 | { 148 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressFetchCheckoutRequest', $parameters); 149 | } 150 | 151 | /** 152 | * @return Message\ExpressTransactionSearchRequest 153 | */ 154 | public function transactionSearch(array $parameters = array()) 155 | { 156 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressTransactionSearchRequest', $parameters); 157 | } 158 | 159 | public function order(array $parameters = array()) 160 | { 161 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressOrderRequest', $parameters); 162 | } 163 | 164 | public function completeOrder(array $parameters = array()) 165 | { 166 | return $this->createRequest('\Omnipay\PayPal\Message\ExpressCompleteOrderRequest', $parameters); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Message/RestSearchTransactionRequest.php: -------------------------------------------------------------------------------- 1 | 22 | * // List the transactions for a billing agreement. 23 | * $transaction = $gateway->listPurchase(); 24 | * $response = $transaction->send(); 25 | * $data = $response->getData(); 26 | * echo "Gateway listPurchase response data == " . print_r($data, true) . "\n"; 27 | * 28 | * 29 | * ### Request Sample 30 | * 31 | * This is from the PayPal web site: 32 | * 33 | * 34 | * curl -v GET https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/transactions \ 35 | * -H 'Content-Type:application/json' \ 36 | * -H 'Authorization: Bearer ' 37 | * 38 | * 39 | * ### Response Sample 40 | * 41 | * This is from the PayPal web site: 42 | * 43 | * 44 | * { 45 | * "agreement_transaction_list": [ 46 | * { 47 | * "transaction_id": "I-0LN988D3JACS", 48 | * "status": "Created", 49 | * "transaction_type": "Recurring Payment", 50 | * "payer_email": "bbuyer@example.com", 51 | * "payer_name": "Betsy Buyer", 52 | * "time_stamp": "2014-06-09T09:29:36Z", 53 | * "time_zone": "GMT" 54 | * }, 55 | * { 56 | * "transaction_id": "928415314Y5640008", 57 | * "status": "Completed", 58 | * "transaction_type": "Recurring Payment", 59 | * "amount": { 60 | * "currency": "USD", 61 | * "value": "1.00" 62 | * }, 63 | * "fee_amount": { 64 | * "currency": "USD", 65 | * "value": "-0.33" 66 | * }, 67 | * "net_amount": { 68 | * "currency": "USD", 69 | * "value": "0.67" 70 | * }, 71 | * "payer_email": "bbuyer@example.com", 72 | * "payer_name": "Betsy Buyer", 73 | * "time_stamp": "2014-06-09T09:42:47Z", 74 | * "time_zone": "GMT" 75 | * }, 76 | * { 77 | * "transaction_id": "I-0LN988D3JACS", 78 | * "status": "Suspended", 79 | * "transaction_type": "Recurring Payment", 80 | * "payer_email": "bbuyer@example.com", 81 | * "payer_name": "Betsy Buyer", 82 | * "time_stamp": "2014-06-09T11:18:34Z", 83 | * "time_zone": "GMT" 84 | * }, 85 | * { 86 | * "transaction_id": "I-0LN988D3JACS", 87 | * "status": "Reactivated", 88 | * "transaction_type": "Recurring Payment", 89 | * "payer_email": "bbuyer@example.com", 90 | * "payer_name": "Betsy Buyer", 91 | * "time_stamp": "2014-06-09T11:18:48Z", 92 | * "time_zone": "GMT" 93 | * } 94 | * ] 95 | * } 96 | * 97 | * 98 | * ### Known Issues 99 | * 100 | * PayPal subscription payments cannot be refunded. PayPal is working on this functionality 101 | * for their future API release. In order to refund a PayPal subscription payment, you will 102 | * need to use the PayPal web interface to refund it manually. 103 | * 104 | * @see RestCreateSubscriptionRequest 105 | * @link https://developer.paypal.com/docs/api/#search-for-transactions 106 | */ 107 | class RestSearchTransactionRequest extends AbstractRestRequest 108 | { 109 | /** 110 | * Get the agreement ID 111 | * 112 | * @return string 113 | */ 114 | public function getAgreementId() 115 | { 116 | return $this->getParameter('agreementId'); 117 | } 118 | 119 | /** 120 | * Set the agreement ID 121 | * 122 | * @param string $value 123 | * @return RestSearchTransactionRequest provides a fluent interface. 124 | */ 125 | public function setAgreementId($value) 126 | { 127 | return $this->setParameter('agreementId', $value); 128 | } 129 | 130 | /** 131 | * Get the request startDate 132 | * 133 | * @return string 134 | */ 135 | public function getStartDate() 136 | { 137 | return $this->getParameter('startDate'); 138 | } 139 | 140 | /** 141 | * Set the request startDate 142 | * 143 | * @param string|DateTime $value 144 | * @return RestSearchTransactionRequest provides a fluent interface. 145 | */ 146 | public function setStartDate($value) 147 | { 148 | return $this->setParameter('startDate', is_string($value) ? new \DateTime($value) : $value); 149 | } 150 | 151 | /** 152 | * Get the request endDate 153 | * 154 | * @return string 155 | */ 156 | public function getEndDate() 157 | { 158 | return $this->getParameter('endDate'); 159 | } 160 | 161 | /** 162 | * Set the request endDate 163 | * 164 | * @param string|DateTime $value 165 | * @return RestSearchTransactionRequest provides a fluent interface. 166 | */ 167 | public function setEndDate($value) 168 | { 169 | return $this->setParameter('endDate', is_string($value) ? new \DateTime($value) : $value); 170 | } 171 | 172 | public function getData() 173 | { 174 | $this->validate('agreementId', 'startDate', 'endDate'); 175 | return array( 176 | 'start_date' => $this->getStartDate()->format('Y-m-d'), 177 | 'end_date' => $this->getEndDate()->format('Y-m-d'), 178 | ); 179 | } 180 | 181 | /** 182 | * Get HTTP Method. 183 | * 184 | * The HTTP method for searchTransaction requests must be GET. 185 | * 186 | * @return string 187 | */ 188 | protected function getHttpMethod() 189 | { 190 | return 'GET'; 191 | } 192 | 193 | public function getEndpoint() 194 | { 195 | return parent::getEndpoint() . '/payments/billing-agreements/' . 196 | $this->getAgreementId() . '/transactions'; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/Message/RestAuthorizeRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new RestAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest()); 20 | $this->request->initialize( 21 | array( 22 | 'amount' => '10.00', 23 | 'currency' => 'USD', 24 | 'returnUrl' => 'https://www.example.com/return', 25 | 'cancelUrl' => 'https://www.example.com/cancel', 26 | ) 27 | ); 28 | } 29 | 30 | public function testGetDataWithoutCard() 31 | { 32 | $this->request->setTransactionId('abc123'); 33 | $this->request->setDescription('Sheep'); 34 | 35 | $data = $this->request->getData(); 36 | 37 | $this->assertSame('authorize', $data['intent']); 38 | $this->assertSame('paypal', $data['payer']['payment_method']); 39 | $this->assertSame('10.00', $data['transactions'][0]['amount']['total']); 40 | $this->assertSame('USD', $data['transactions'][0]['amount']['currency']); 41 | $this->assertSame('abc123 : Sheep', $data['transactions'][0]['description']); 42 | 43 | // Funding instruments must not be set, otherwise paypal API will give error 500. 44 | $this->assertArrayNotHasKey('funding_instruments', $data['payer']); 45 | 46 | $this->assertSame('https://www.example.com/return', $data['redirect_urls']['return_url']); 47 | $this->assertSame('https://www.example.com/cancel', $data['redirect_urls']['cancel_url']); 48 | } 49 | 50 | public function testGetDataWithCard() 51 | { 52 | $card = new CreditCard($this->getValidCard()); 53 | $card->setStartMonth(1); 54 | $card->setStartYear(2000); 55 | 56 | $this->request->setCard($card); 57 | $this->request->setTransactionId('abc123'); 58 | $this->request->setDescription('Sheep'); 59 | $this->request->setClientIp('127.0.0.1'); 60 | 61 | $data = $this->request->getData(); 62 | 63 | $this->assertSame('authorize', $data['intent']); 64 | $this->assertSame('credit_card', $data['payer']['payment_method']); 65 | $this->assertSame('10.00', $data['transactions'][0]['amount']['total']); 66 | $this->assertSame('USD', $data['transactions'][0]['amount']['currency']); 67 | $this->assertSame('abc123 : Sheep', $data['transactions'][0]['description']); 68 | 69 | $this->assertSame($card->getNumber(), $data['payer']['funding_instruments'][0]['credit_card']['number']); 70 | $this->assertSame($card->getBrand(), $data['payer']['funding_instruments'][0]['credit_card']['type']); 71 | $this->assertSame($card->getExpiryMonth(), $data['payer']['funding_instruments'][0]['credit_card']['expire_month']); 72 | $this->assertSame($card->getExpiryYear(), $data['payer']['funding_instruments'][0]['credit_card']['expire_year']); 73 | $this->assertSame($card->getCvv(), $data['payer']['funding_instruments'][0]['credit_card']['cvv2']); 74 | 75 | $this->assertSame($card->getFirstName(), $data['payer']['funding_instruments'][0]['credit_card']['first_name']); 76 | $this->assertSame($card->getLastName(), $data['payer']['funding_instruments'][0]['credit_card']['last_name']); 77 | $this->assertSame($card->getAddress1(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['line1']); 78 | $this->assertSame($card->getAddress2(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['line2']); 79 | $this->assertSame($card->getCity(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['city']); 80 | $this->assertSame($card->getState(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['state']); 81 | $this->assertSame($card->getPostcode(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['postal_code']); 82 | $this->assertSame($card->getCountry(), $data['payer']['funding_instruments'][0]['credit_card']['billing_address']['country_code']); 83 | } 84 | 85 | public function testGetDataWithItems() 86 | { 87 | $this->request->setAmount('50.00'); 88 | $this->request->setCurrency('USD'); 89 | $this->request->setItems(array( 90 | array('name' => 'Floppy Disk', 'description' => 'MS-DOS', 'quantity' => 2, 'price' => 10), 91 | array('name' => 'CD-ROM', 'description' => 'Windows 95', 'quantity' => 1, 'price' => 40), 92 | )); 93 | 94 | $data = $this->request->getData(); 95 | $transactionData = $data['transactions'][0]; 96 | 97 | $this->assertSame('Floppy Disk', $transactionData['item_list']['items'][0]['name']); 98 | $this->assertSame('MS-DOS', $transactionData['item_list']['items'][0]['description']); 99 | $this->assertSame(2, $transactionData['item_list']['items'][0]['quantity']); 100 | $this->assertSame('10.00', $transactionData['item_list']['items'][0]['price']); 101 | 102 | $this->assertSame('CD-ROM', $transactionData['item_list']['items'][1]['name']); 103 | $this->assertSame('Windows 95', $transactionData['item_list']['items'][1]['description']); 104 | $this->assertSame(1, $transactionData['item_list']['items'][1]['quantity']); 105 | $this->assertSame('40.00', $transactionData['item_list']['items'][1]['price']); 106 | 107 | $this->assertSame('50.00', $transactionData['amount']['total']); 108 | $this->assertSame('USD', $transactionData['amount']['currency']); 109 | } 110 | 111 | public function testDescription() 112 | { 113 | $this->request->setTransactionId(''); 114 | $this->request->setDescription(''); 115 | $this->assertEmpty($this->request->getDescription()); 116 | 117 | $this->request->setTransactionId(''); 118 | $this->request->setDescription('Sheep'); 119 | $this->assertEquals('Sheep', $this->request->getDescription()); 120 | 121 | $this->request->setTransactionId('abc123'); 122 | $this->request->setDescription(''); 123 | $this->assertEquals('abc123', $this->request->getDescription()); 124 | 125 | $this->request->setTransactionId('abc123'); 126 | $this->request->setDescription('Sheep'); 127 | $this->assertEquals('abc123 : Sheep', $this->request->getDescription()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Message/RestListPlanRequest.php: -------------------------------------------------------------------------------- 1 | 20 | * // Create a gateway for the PayPal RestGateway 21 | * // (routes to GatewayFactory::create) 22 | * $gateway = Omnipay::create('PayPal_Rest'); 23 | * 24 | * // Initialise the gateway 25 | * $gateway->initialize(array( 26 | * 'clientId' => 'MyPayPalClientId', 27 | * 'secret' => 'MyPayPalSecret', 28 | * 'testMode' => true, // Or false when you are ready for live transactions 29 | * )); 30 | * 31 | * 32 | * #### List all plans that have state CREATED 33 | * 34 | * 35 | * // List all billing plans 36 | * $transaction = $gateway->listPlan([ 37 | * 'state' => CREATED, 38 | * ]); 39 | * $response = $transaction->send(); 40 | * $data = $response->getData(); 41 | * echo "Gateway listPlan response data == " . print_r($data, true) . "\n"; 42 | * 43 | * 44 | * ### Request Sample 45 | * 46 | * This is from the PayPal web site: 47 | * 48 | * 49 | * curl -v -X GET https://api.sandbox.paypal.com/v1/payments/billing-plans?page_size=3&status=ACTIVE&page=1\ 50 | * -H "Content-Type:application/json" \ 51 | * -H "Authorization: Bearer Access-Token" 52 | * 53 | * 54 | * ### Response Sample 55 | * 56 | * This is from the PayPal web site: 57 | * 58 | * 59 | * { 60 | * "total_items": "166", 61 | * "total_pages": "83", 62 | * "plans": [ 63 | * { 64 | * "id": "P-7DC96732KA7763723UOPKETA", 65 | * "state": "ACTIVE", 66 | * "name": "Plan with Regular and Trial Payment Definitions", 67 | * "description": "Plan with regular and trial billing payment definitions.", 68 | * "type": "FIXED", 69 | * "create_time": "2017-08-22T04:41:52.836Z", 70 | * "update_time": "2017-08-22T04:41:53.169Z", 71 | * "links": [ 72 | * { 73 | * "href": "https://api.sandbox.paypal.com//v1/payments/billing-plans/P-7DC96732KA7763723UOPKETA", 74 | * "rel": "self", 75 | * "method": "GET" 76 | * } 77 | * ] 78 | * }, 79 | * { 80 | * "id": "P-1TV69435N82273154UPWDU4I", 81 | * "state": "ACTIVE", 82 | * "name": "Plan with Regular Payment Definition", 83 | * "description": "Plan with one regular payment definition, minimal merchant preferences, and no shipping fee", 84 | * "type": "INFINITE", 85 | * "create_time": "2017-08-22T04:41:55.623Z", 86 | * "update_time": "2017-08-22T04:41:56.055Z", 87 | * "links": [ 88 | * { 89 | * "href": "https://api.sandbox.paypal.com//v1/payments/billing-plans/P-1TV69435N82273154UPWDU4I", 90 | * "rel": "self", 91 | * "method": "GET" 92 | * } 93 | * ] 94 | * } 95 | * ], 96 | * "links": [ 97 | * { 98 | * "href": "https://api.sandbox.paypal.com/v1/payments/billing-plans?page_size=2&page=1&start=3&status=active", 99 | * "rel": "start", 100 | * "method": "GET" 101 | * }, 102 | * { 103 | * "href": "https://api.sandbox.paypal.com/v1/payments/billing-plans?page_size=2&page=0&status=active", 104 | * "rel": "previous_page", 105 | * "method": "GET" 106 | * }, 107 | * { 108 | * "href": "https://api.sandbox.paypal.com/v1/payments/billing-plans?page_size=2&page=2&status=active", 109 | * "rel": "next_page", 110 | * "method": "GET" 111 | * }, 112 | * { 113 | * "href": "https://api.sandbox.paypal.com/v1/payments/billing-plans?page_size=2&page=82&status=active", 114 | * "rel": "last", 115 | * "method": "GET" 116 | * } 117 | * ] 118 | * } 119 | * 120 | * 121 | * 122 | * @link https://developer.paypal.com/docs/api/payments.billing-plans#plan_list 123 | */ 124 | class RestListPlanRequest extends AbstractRestRequest 125 | { 126 | /** 127 | * 128 | * Get the request page 129 | * 130 | * @return integer 131 | */ 132 | 133 | public function getPage() 134 | { 135 | return $this->getParameter('page'); 136 | } 137 | 138 | 139 | /** 140 | * Set the request page 141 | * 142 | * @param integer $value 143 | * @return AbstractRestRequest provides a fluent interface. 144 | */ 145 | public function setPage($value) 146 | { 147 | return $this->setParameter('page', $value); 148 | } 149 | 150 | /** 151 | * Get the request status 152 | * 153 | * @return string 154 | */ 155 | public function getStatus() 156 | { 157 | return $this->getParameter('status'); 158 | } 159 | 160 | /** 161 | * Set the request status 162 | * 163 | * @param string $value 164 | * @return AbstractRestRequest provides a fluent interface. 165 | */ 166 | public function setStatus($value) 167 | { 168 | return $this->setParameter('status', $value); 169 | } 170 | 171 | /** 172 | * Get the request page size 173 | * 174 | * @return string 175 | */ 176 | public function getPageSize() 177 | { 178 | return $this->getParameter('pageSize'); 179 | } 180 | 181 | /** 182 | * Set the request page size 183 | * 184 | * @param string $value 185 | * @return AbstractRestRequest provides a fluent interface. 186 | */ 187 | public function setPageSize($value) 188 | { 189 | return $this->setParameter('pageSize', $value); 190 | } 191 | 192 | /** 193 | * Get the request total required 194 | * 195 | * @return string 196 | */ 197 | public function getTotalRequired() 198 | { 199 | return $this->getParameter('totalRequired'); 200 | } 201 | 202 | /** 203 | * Set the request total required 204 | * 205 | * @param string $value 206 | * @return AbstractRestRequest provides a fluent interface. 207 | */ 208 | public function setTotalRequired($value) 209 | { 210 | return $this->setParameter('totalRequired', $value); 211 | } 212 | 213 | 214 | 215 | 216 | public function getData() 217 | { 218 | return array( 219 | 'page' => $this->getPage(), 220 | 'status' => $this->getStatus(), 221 | 'page_size' => $this->getPageSize(), 222 | 'total_required' => $this->getTotalRequired() 223 | ); 224 | } 225 | 226 | /** 227 | * Get HTTP Method. 228 | * 229 | * The HTTP method for list plans requests must be GET. 230 | * 231 | * @return string 232 | */ 233 | protected function getHttpMethod() 234 | { 235 | return 'GET'; 236 | } 237 | 238 | public function getEndpoint() 239 | { 240 | return parent::getEndpoint() . '/payments/billing-plans'; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Message/AbstractRestRequest.php: -------------------------------------------------------------------------------- 1 | 20 | * POST https://api.paypal.com/v1/payments/payment 21 | * 22 | * 23 | * To create a complete request, combine the operation with the appropriate HTTP headers 24 | * and any required JSON payload. 25 | * 26 | * @link https://developer.paypal.com/docs/api/ 27 | * @link https://devtools-paypal.com/integrationwizard/ 28 | * @link http://paypal.github.io/sdk/ 29 | * @see Omnipay\PayPal\RestGateway 30 | */ 31 | abstract class AbstractRestRequest extends \Omnipay\Common\Message\AbstractRequest 32 | { 33 | const API_VERSION = 'v1'; 34 | 35 | /** 36 | * Sandbox Endpoint URL 37 | * 38 | * The PayPal REST APIs are supported in two environments. Use the Sandbox environment 39 | * for testing purposes, then move to the live environment for production processing. 40 | * When testing, generate an access token with your test credentials to make calls to 41 | * the Sandbox URIs. When you’re set to go live, use the live credentials assigned to 42 | * your app to generate a new access token to be used with the live URIs. 43 | * 44 | * @var string URL 45 | */ 46 | protected $testEndpoint = 'https://api.sandbox.paypal.com'; 47 | 48 | /** 49 | * Live Endpoint URL 50 | * 51 | * When you’re set to go live, use the live credentials assigned to 52 | * your app to generate a new access token to be used with the live URIs. 53 | * 54 | * @var string URL 55 | */ 56 | protected $liveEndpoint = 'https://api.paypal.com'; 57 | 58 | /** 59 | * PayPal Payer ID 60 | * 61 | * @var string PayerID 62 | */ 63 | protected $payerId = null; 64 | 65 | protected $referrerCode; 66 | 67 | /** 68 | * @var bool 69 | */ 70 | protected $negativeAmountAllowed = true; 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getReferrerCode() 76 | { 77 | return $this->referrerCode; 78 | } 79 | 80 | /** 81 | * @param string $referrerCode 82 | */ 83 | public function setReferrerCode($referrerCode) 84 | { 85 | $this->referrerCode = $referrerCode; 86 | } 87 | 88 | public function getClientId() 89 | { 90 | return $this->getParameter('clientId'); 91 | } 92 | 93 | public function setClientId($value) 94 | { 95 | return $this->setParameter('clientId', $value); 96 | } 97 | 98 | public function getSecret() 99 | { 100 | return $this->getParameter('secret'); 101 | } 102 | 103 | public function setSecret($value) 104 | { 105 | return $this->setParameter('secret', $value); 106 | } 107 | 108 | public function getToken() 109 | { 110 | return $this->getParameter('token'); 111 | } 112 | 113 | public function setToken($value) 114 | { 115 | return $this->setParameter('token', $value); 116 | } 117 | 118 | public function getPayerId() 119 | { 120 | return $this->getParameter('payerId'); 121 | } 122 | 123 | public function setPayerId($value) 124 | { 125 | return $this->setParameter('payerId', $value); 126 | } 127 | 128 | /** 129 | * Get HTTP Method. 130 | * 131 | * This is nearly always POST but can be over-ridden in sub classes. 132 | * 133 | * @return string 134 | */ 135 | protected function getHttpMethod() 136 | { 137 | return 'POST'; 138 | } 139 | 140 | protected function getEndpoint() 141 | { 142 | $base = $this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint; 143 | return $base . '/' . self::API_VERSION; 144 | } 145 | 146 | public function sendData($data) 147 | { 148 | 149 | // Guzzle HTTP Client createRequest does funny things when a GET request 150 | // has attached data, so don't send the data if the method is GET. 151 | if ($this->getHttpMethod() == 'GET') { 152 | $requestUrl = $this->getEndpoint() . '?' . http_build_query($data); 153 | $body = null; 154 | } else { 155 | $body = $this->toJSON($data); 156 | $requestUrl = $this->getEndpoint(); 157 | } 158 | 159 | // Might be useful to have some debug code here, PayPal especially can be 160 | // a bit fussy about data formats and ordering. Perhaps hook to whatever 161 | // logging engine is being used. 162 | // echo "Data == " . json_encode($data) . "\n"; 163 | 164 | try { 165 | $httpResponse = $this->httpClient->request( 166 | $this->getHttpMethod(), 167 | $this->getEndpoint(), 168 | array( 169 | 'Accept' => 'application/json', 170 | 'Authorization' => 'Bearer ' . $this->getToken(), 171 | 'Content-type' => 'application/json', 172 | 'PayPal-Partner-Attribution-Id' => $this->getReferrerCode(), 173 | ), 174 | $body 175 | ); 176 | // Empty response body should be parsed also as and empty array 177 | $body = (string) $httpResponse->getBody()->getContents(); 178 | $jsonToArrayResponse = !empty($body) ? json_decode($body, true) : array(); 179 | return $this->response = $this->createResponse($jsonToArrayResponse, $httpResponse->getStatusCode()); 180 | } catch (\Exception $e) { 181 | throw new InvalidResponseException( 182 | 'Error communicating with payment gateway: ' . $e->getMessage(), 183 | $e->getCode() 184 | ); 185 | } 186 | } 187 | 188 | /** 189 | * Returns object JSON representation required by PayPal. 190 | * The PayPal REST API requires the use of JSON_UNESCAPED_SLASHES. 191 | * 192 | * Adapted from the official PayPal REST API PHP SDK. 193 | * (https://github.com/paypal/PayPal-PHP-SDK/blob/master/lib/PayPal/Common/PayPalModel.php) 194 | * 195 | * @param int $options http://php.net/manual/en/json.constants.php 196 | * @return string 197 | */ 198 | public function toJSON($data, $options = 0) 199 | { 200 | // Because of PHP Version 5.3, we cannot use JSON_UNESCAPED_SLASHES option 201 | // Instead we would use the str_replace command for now. 202 | // TODO: Replace this code with return json_encode($this->toArray(), $options | 64); once we support PHP >= 5.4 203 | if (version_compare(phpversion(), '5.4.0', '>=') === true) { 204 | return json_encode($data, $options | 64); 205 | } 206 | return str_replace('\\/', '/', json_encode($data, $options)); 207 | } 208 | 209 | protected function createResponse($data, $statusCode) 210 | { 211 | return $this->response = new RestResponse($this, $data, $statusCode); 212 | } 213 | } 214 | --------------------------------------------------------------------------------