From c8959b6ed48647035c1fbbdc671d6ed65145128b Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Sat, 14 Sep 2019 16:37:32 +1000 Subject: [PATCH 01/23] Added support for Connect Application Fees. --- src/Gateway.php | 10 ++++ src/Message/FetchApplicationFeeRequest.php | 69 ++++++++++++++++++++++ src/Message/Response.php | 17 ++++++ tests/Message/FetchApplicationFeeTest.php | 43 ++++++++++++++ tests/Mock/FetchApplicationFeeFailure.txt | 17 ++++++ tests/Mock/FetchApplicationFeeSuccess.txt | 32 ++++++++++ 6 files changed, 188 insertions(+) create mode 100644 src/Message/FetchApplicationFeeRequest.php create mode 100644 tests/Message/FetchApplicationFeeTest.php create mode 100644 tests/Mock/FetchApplicationFeeFailure.txt create mode 100644 tests/Mock/FetchApplicationFeeSuccess.txt diff --git a/src/Gateway.php b/src/Gateway.php index 0ed96fae..c1b91423 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -338,4 +338,14 @@ public function completePurchase(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\CompletePurchaseRequest', $parameters); } + + /** + * @param array $parameters + * + * @return \Omnipay\Stripe\Message\FetchApplicationFeeRequest + */ + public function fetchApplicationFee(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\FetchApplicationFeeRequest', $parameters); + } } diff --git a/src/Message/FetchApplicationFeeRequest.php b/src/Message/FetchApplicationFeeRequest.php new file mode 100644 index 00000000..0f708974 --- /dev/null +++ b/src/Message/FetchApplicationFeeRequest.php @@ -0,0 +1,69 @@ + + * // Fetch the transaction so that details can be found for refund, etc. + * $transaction = $gateway->fetchApplicationFee(); + * $transaction->setApplicationFeeReference($application_fee_id); + * $response = $transaction->send(); + * $data = $response->getData(); + * echo "Gateway fetchApplicationFee response data == " . print_r($data, true) . "\n"; + * + * + * @see \Omnipay\Stripe\Gateway + * + * @link https://stripe.com/docs/api#retrieve_application_fee + */ +class FetchApplicationFeeRequest extends AbstractRequest +{ + /** + * Get the application fee reference + * + * @return string + */ + public function getApplicationFeeReference() + { + return $this->getParameter('applicationFeeReference'); + } + + /** + * Set the application fee reference + * + * @param string $value + * + * @return AbstractRequest provides a fluent interface. + */ + public function setApplicationFeeReference($value) + { + return $this->setParameter('applicationFeeReference', $value); + } + + public function getData() + { + $this->validate('applicationFeeReference'); + + $data = array(); + + return $data; + } + + public function getEndpoint() + { + return $this->endpoint . '/application_fees/' . $this->getApplicationFeeReference(); + } + + public function getHttpMethod() + { + return 'GET'; + } +} diff --git a/src/Message/Response.php b/src/Message/Response.php index 6e5ea959..476ecd0f 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -100,6 +100,23 @@ public function getTransactionReference() return null; } + /** + * Get the balance transaction reference. + * + * @return string|null + */ + public function getApplicationFeeReference() + { + if (isset($this->data['object']) && 'application_fee' === $this->data['object']) { + return $this->data['id']; + } + if (isset($this->data['error']) && isset($this->data['error']['application_fee'])) { + return $this->data['error']['application_fee']; + } + + return null; + } + /** * Get the balance transaction reference. * diff --git a/tests/Message/FetchApplicationFeeTest.php b/tests/Message/FetchApplicationFeeTest.php new file mode 100644 index 00000000..6dbc97e1 --- /dev/null +++ b/tests/Message/FetchApplicationFeeTest.php @@ -0,0 +1,43 @@ +request = new FetchApplicationFeeRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setApplicationFeeReference('fee_1FITlv123YJsynqe3nOIfake'); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/application_fees/fee_1FITlv123YJsynqe3nOIfake', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('FetchApplicationFeeSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('fee_1FITlv123YJsynqe3nOIfake', $response->getApplicationFeeReference()); + $this->assertNull($response->getCardReference()); + $this->assertNull($response->getMessage()); + } + + public function testSendError() + { + $this->setMockHttpResponse('FetchApplicationFeeFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getBalanceTransactionReference()); + $this->assertNull($response->getCardReference()); + $this->assertSame('No such application fee: fee_1FITlv123YJsynqe3nOIfake', $response->getMessage()); + } +} diff --git a/tests/Mock/FetchApplicationFeeFailure.txt b/tests/Mock/FetchApplicationFeeFailure.txt new file mode 100644 index 00000000..bc5f04f0 --- /dev/null +++ b/tests/Mock/FetchApplicationFeeFailure.txt @@ -0,0 +1,17 @@ +HTTP/1.1 404 Not Found +Server: nginx +Date: Wed, 24 Jul 2013 13:40:31 GMT +Content-Type: application/json;charset=utf-8 +Content-Length: 132 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Access-Control-Max-Age: 300 +Cache-Control: no-cache, no-store + +{ + "error": { + "type": "invalid_request_error", + "message": "No such application fee: fee_1FITlv123YJsynqe3nOIfake", + "param": "id" + } +} diff --git a/tests/Mock/FetchApplicationFeeSuccess.txt b/tests/Mock/FetchApplicationFeeSuccess.txt new file mode 100644 index 00000000..d66dbafe --- /dev/null +++ b/tests/Mock/FetchApplicationFeeSuccess.txt @@ -0,0 +1,32 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Wed, 24 Jul 2013 07:14:02 GMT +Content-Type: application/json;charset=utf-8 +Content-Length: 1092 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Access-Control-Max-Age: 300 +Cache-Control: no-cache, no-store + +{ + "id": "fee_1FITlv123YJsynqe3nOIfake", + "object": "application_fee", + "account": "acct_14901h0a0fh01293", + "amount": 100, + "amount_refunded": 0, + "application": "ca_Fo5xaLt123SEtSKHui0SZOgAiuVwfake", + "balance_transaction": "txn_1FH8W123vYJsynqeQKMWfake", + "charge": "ch_1FIT123rvYJsynqeQpJOFfake", + "created": 1568438771, + "currency": "usd", + "livemode": false, + "originating_transaction": null, + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/application_fees/fee_1FITlvArvYJsynqe3nOIfake/refunds" + } +} \ No newline at end of file From 69193b2af0e25d281bd1fbd98dd8f620b881e5b5 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Sat, 14 Sep 2019 17:41:27 +1000 Subject: [PATCH 02/23] Updated unit test for Fetch Application Fee. --- tests/Message/FetchApplicationFeeTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Message/FetchApplicationFeeTest.php b/tests/Message/FetchApplicationFeeTest.php index 6dbc97e1..a41e5abf 100644 --- a/tests/Message/FetchApplicationFeeTest.php +++ b/tests/Message/FetchApplicationFeeTest.php @@ -25,7 +25,6 @@ public function testSendSuccess() $this->assertTrue($response->isSuccessful()); $this->assertFalse($response->isRedirect()); $this->assertSame('fee_1FITlv123YJsynqe3nOIfake', $response->getApplicationFeeReference()); - $this->assertNull($response->getCardReference()); $this->assertNull($response->getMessage()); } @@ -36,8 +35,7 @@ public function testSendError() $this->assertFalse($response->isSuccessful()); $this->assertFalse($response->isRedirect()); - $this->assertNull($response->getBalanceTransactionReference()); - $this->assertNull($response->getCardReference()); + $this->assertNull($response->getApplicationFeeReference()); $this->assertSame('No such application fee: fee_1FITlv123YJsynqe3nOIfake', $response->getMessage()); } } From d38b7e53893da5e92743c20c7e2b64b4a427516b Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Tue, 17 Sep 2019 23:47:12 +1000 Subject: [PATCH 03/23] Fixed application fee request parameter when creating a PaymentIntent. --- src/Message/PaymentIntents/AuthorizeRequest.php | 2 +- tests/Message/PaymentIntents/AuthorizeRequestTest.php | 2 +- tests/Message/PaymentIntents/PurchaseRequestTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index fe939d1e..0f574524 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -312,7 +312,7 @@ public function getData() } if ($this->getApplicationFee()) { - $data['application_fee'] = $this->getApplicationFeeInteger(); + $data['application_fee_amount'] = $this->getApplicationFeeInteger(); } if ($this->getTransferGroup()) { diff --git a/tests/Message/PaymentIntents/AuthorizeRequestTest.php b/tests/Message/PaymentIntents/AuthorizeRequestTest.php index 9e3b8858..32d05a69 100644 --- a/tests/Message/PaymentIntents/AuthorizeRequestTest.php +++ b/tests/Message/PaymentIntents/AuthorizeRequestTest.php @@ -41,7 +41,7 @@ public function testGetData() $this->assertSame('manual', $data['confirmation_method']); $this->assertSame('pm_valid_payment_method', $data['payment_method']); $this->assertSame(array('foo' => 'bar'), $data['metadata']); - $this->assertSame(100, $data['application_fee']); + $this->assertSame(100, $data['application_fee_amount']); } /** diff --git a/tests/Message/PaymentIntents/PurchaseRequestTest.php b/tests/Message/PaymentIntents/PurchaseRequestTest.php index 09af7fae..2f0dda5d 100644 --- a/tests/Message/PaymentIntents/PurchaseRequestTest.php +++ b/tests/Message/PaymentIntents/PurchaseRequestTest.php @@ -39,7 +39,7 @@ public function testGetData() $this->assertSame('manual', $data['confirmation_method']); $this->assertSame('pm_valid_payment_method', $data['payment_method']); $this->assertSame(array('foo' => 'bar'), $data['metadata']); - $this->assertSame(100, $data['application_fee']); + $this->assertSame(100, $data['application_fee_amount']); } public function testSendSuccessAndRequireConfirmation() From f4b689c9dd332a027bf8cc2c65c7954d9d7bd4ea Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Wed, 18 Sep 2019 15:50:39 +1000 Subject: [PATCH 04/23] Moved Fetch Application Fee to the AbstractGateway. --- src/AbstractGateway.php | 15 +++++++++++++++ src/Gateway.php | 10 ---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/AbstractGateway.php b/src/AbstractGateway.php index 91808800..417de143 100644 --- a/src/AbstractGateway.php +++ b/src/AbstractGateway.php @@ -263,6 +263,21 @@ public function fetchBalanceTransaction(array $parameters = array()) return $this->createRequest('\Omnipay\Stripe\Message\FetchBalanceTransactionRequest', $parameters); } + // + // Application Fees + // @link https://stripe.com/docs/api#application_fees + // + + /** + * @param array $parameters + * + * @return \Omnipay\Stripe\Message\FetchApplicationFeeRequest + */ + public function fetchApplicationFee(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\FetchApplicationFeeRequest', $parameters); + } + // // Transfers // @link https://stripe.com/docs/api#transfers diff --git a/src/Gateway.php b/src/Gateway.php index c1b91423..0ed96fae 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -338,14 +338,4 @@ public function completePurchase(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\CompletePurchaseRequest', $parameters); } - - /** - * @param array $parameters - * - * @return \Omnipay\Stripe\Message\FetchApplicationFeeRequest - */ - public function fetchApplicationFee(array $parameters = array()) - { - return $this->createRequest('\Omnipay\Stripe\Message\FetchApplicationFeeRequest', $parameters); - } } From 8f2fd68ad145940b7a8d62f7107ae4b52ecbef05 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Sat, 4 Jan 2020 13:36:40 -0800 Subject: [PATCH 05/23] Allow configuring `Stripe-Version` at the gateway level --- src/AbstractGateway.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/AbstractGateway.php b/src/AbstractGateway.php index 417de143..9d5653e3 100644 --- a/src/AbstractGateway.php +++ b/src/AbstractGateway.php @@ -107,6 +107,7 @@ public function getDefaultParameters() { return array( 'apiKey' => '', + 'stripeVersion' => null ); } @@ -150,6 +151,24 @@ public function setApiKey($value) return $this->setParameter('apiKey', $value); } + /** + * @return string + */ + public function getStripeVersion() + { + return $this->getParameter('stripeVersion'); + } + + /** + * @param string $value + * + * @return Gateway + */ + public function setStripeVersion($value) + { + return $this->setParameter('stripeVersion', $value); + } + /** * Authorize Request. * From 819c2caf323e8916f9a8524bcea39cc9a4a5443f Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Sat, 4 Jan 2020 16:48:15 -0800 Subject: [PATCH 06/23] Support for expanding Stripe objects in the response --- src/Message/AbstractRequest.php | 50 +++++++++++++++++++++++++-- tests/Message/AbstractRequestTest.php | 14 +++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 805b2d34..e33c7c03 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -174,6 +174,27 @@ public function setIdempotencyKeyHeader($value) return $this->setParameter('idempotencyKey', $value); } + /** + * @return array + */ + public function getExpand() + { + return $this->getParameter('expand'); + } + + /** + * Specifies which object relations (IDs) in the response should be expanded to include the entire object. + * + * @see https://stripe.com/docs/api/expanding_objects + * + * @param array $value + * @return AbstractRequest + */ + public function setExpand($value) + { + return $this->setParameter('expand', $value); + } + abstract public function getEndpoint(); /** @@ -221,17 +242,42 @@ public function sendData($data) ); $body = $data ? http_build_query($data, '', '&') : null; - $httpResponse = $this->httpClient->request($this->getHttpMethod(), $this->getEndpoint(), $headers, $body); + $httpResponse = $this->httpClient->request( + $this->getHttpMethod(), + $this->getExpandedEndpoint(), + $headers, + $body + ); return $this->createResponse($httpResponse->getBody()->getContents(), $httpResponse->getHeaders()); } + /** + * Appends the `expand` properties to the endpoint as a querystring. + * + * @return string + */ + public function getExpandedEndpoint() + { + $endpoint = $this->getEndpoint(); + $expand = $this->getExpand(); + if (is_array($expand) && count($expand) > 0) { + $queryParams = []; + foreach ($expand as $key) { + $queryParams[] = 'expand[]=' . $key; + } + $queryString = join('&', $queryParams); + $endpoint .= '?' . $queryString; + } + + return $endpoint; + } protected function createResponse($data, $headers = []) { return $this->response = new Response($this, $data, $headers); } - + /** * @return mixed */ diff --git a/tests/Message/AbstractRequestTest.php b/tests/Message/AbstractRequestTest.php index 330aea87..9528e6cf 100644 --- a/tests/Message/AbstractRequestTest.php +++ b/tests/Message/AbstractRequestTest.php @@ -8,6 +8,9 @@ class AbstractRequestTest extends TestCase { + /** @var Mockery\Mock|AbstractRequest */ + private $request; + public function setUp() { $this->request = Mockery::mock('\Omnipay\Stripe\Message\AbstractRequest')->makePartial(); @@ -98,7 +101,6 @@ public function testStripeVersion() $this->assertTrue($httpRequest->hasHeader('Stripe-Version')); } - public function testConnectedStripeAccount() { $this->request->setConnectedStripeAccountHeader('ACCOUNT_ID'); @@ -118,4 +120,14 @@ public function testConnectedStripeAccount() $this->assertTrue($httpRequest->hasHeader('Stripe-Account')); } + + public function testExpandedEndpoint() + { + $this->request->shouldReceive('getEndpoint')->andReturn('https://api.stripe.com/v1'); + $this->request->setExpand(['foo', 'bar']); + + $actual = $this->request->getExpandedEndpoint(); + + $this->assertEquals('https://api.stripe.com/v1?expand[]=foo&expand[]=bar', $actual); + } } From 4faa2cd55f0db4c2fd2a98f32affe963e709c4c6 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Sun, 5 Jan 2020 00:06:28 -0800 Subject: [PATCH 07/23] Support for level 3 data --- src/Message/AuthorizeRequest.php | 71 +++++++++++++++++++++--- src/StripeItem.php | 18 ++++++ tests/Message/AuthorizeRequestTest.php | 76 ++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/Message/AuthorizeRequest.php b/src/Message/AuthorizeRequest.php index 2850215e..703a1b56 100644 --- a/src/Message/AuthorizeRequest.php +++ b/src/Message/AuthorizeRequest.php @@ -5,9 +5,11 @@ */ namespace Omnipay\Stripe\Message; +use Money\Formatter\DecimalMoneyFormatter; +use Omnipay\Common\Exception\InvalidRequestException; use Omnipay\Common\ItemBag; +use Omnipay\Stripe\StripeItem; use Omnipay\Stripe\StripeItemBag; -use Money\Formatter\DecimalMoneyFormatter; /** * Stripe Authorize Request. @@ -155,7 +157,7 @@ public function setOnBehalfOf($value) /** * @return string - * @throws \Omnipay\Common\Exception\InvalidRequestException + * @throws InvalidRequestException */ public function getApplicationFee() { @@ -174,7 +176,7 @@ public function getApplicationFee() * Get the payment amount as an integer. * * @return integer - * @throws \Omnipay\Common\Exception\InvalidRequestException + * @throws InvalidRequestException */ public function getApplicationFeeInteger() { @@ -231,7 +233,7 @@ public function setReceiptEmail($email) /** * A list of items in this order * - * @return ItemBag|null A bag containing items in this order + * @return StripeItemBag|StripeItem[]|null A bag containing items in this order */ public function getItems() { @@ -242,6 +244,7 @@ public function getItems() * Set the items in this order * * @param array $items An array of items in this order + * @return AuthorizeRequest */ public function setItems($items) { @@ -265,11 +268,40 @@ public function getData() $data['capture'] = 'false'; if ($items = $this->getItems()) { - $itemDescriptions = []; - foreach ($items as $n => $item) { - $itemDescriptions[] = $item->getDescription(); + if (empty($this->getDescription())) { + $itemDescriptions = []; + foreach ($items as $n => $item) { + $itemDescriptions[] = $item->getDescription(); + } + $data['description'] = implode(" + ", $itemDescriptions); + } + + if ($this->validateLineItemsForLevel3($items)) { + $lineItems = []; + foreach ($items as $item) { + $lineItem = [ + 'product_code' => substr($item->getName(), 0, 12), + 'product_description' => substr($item->getDescription(), 0, 26) + ]; + if ($item->getPrice()) { + $lineItem['unit_cost'] = $this->getAmountWithCurrencyPrecision($item->getPrice()); + } + if ($item->getQuantity()) { + $lineItem['quantity'] = $item->getQuantity(); + } + if ($item->getTaxes()) { + $lineItem['tax_amount'] = $this->getAmountWithCurrencyPrecision($item->getTaxes()); + } + if ($item->getDiscount()) { + $lineItem['discount_amount'] = $this->getAmountWithCurrencyPrecision($item->getDiscount()); + } + $lineItems[] = $lineItem; + } + $data['level3'] = [ + 'merchant_reference' => $this->getTransactionId(), + 'line_items' => $lineItems + ]; } - $data['description'] = implode(" + ", $itemDescriptions); } if ($this->getStatementDescriptor()) { @@ -319,8 +351,29 @@ public function getData() return $data; } + private function getAmountWithCurrencyPrecision($amount) + { + return (int)round($amount * pow(10, $this->getCurrencyDecimalPlaces())); + } + + /** + * For Stripe to accept Level 3 data, the sum of all the line items should equal the request's `amount`. This + * method validates that the summation adds up as expected. + * + * @param StripeItemBag $items + * @return bool + */ + private function validateLineItemsForLevel3(StripeItemBag $items) + { + $actualAmount = 0; + foreach ($items as $item) { + $actualAmount += $item->getQuantity() * $item->getPrice() + $item->getTaxes() - $item->getDiscount(); + } + return (string)$actualAmount == (string)$this->getAmount(); + } + public function getEndpoint() { - return $this->endpoint.'/charges'; + return $this->endpoint . '/charges'; } } diff --git a/src/StripeItem.php b/src/StripeItem.php index 43253b9b..cb8ee8cb 100644 --- a/src/StripeItem.php +++ b/src/StripeItem.php @@ -14,5 +14,23 @@ */ class StripeItem extends Item { + public function getTaxes() + { + return $this->getParameter('taxes'); + } + public function setTaxes($value) + { + $this->setParameter('taxes', $value); + } + + public function getDiscount() + { + return $this->getParameter('discount'); + } + + public function setDiscount($value) + { + $this->setParameter('discount', $value); + } } diff --git a/tests/Message/AuthorizeRequestTest.php b/tests/Message/AuthorizeRequestTest.php index 9999d244..d2db37af 100644 --- a/tests/Message/AuthorizeRequestTest.php +++ b/tests/Message/AuthorizeRequestTest.php @@ -41,6 +41,82 @@ public function testGetData() $this->assertSame(100, $data['application_fee']); } + public function testDataWithLevel3() + { + $this->request->setItems([ + [ + 'name' => 'Cupcakes', + 'description' => 'Yummy Cupcakes', + 'price' => 4, + 'quantity' => 2, + 'taxes' => 0.4 + ], + [ + 'name' => 'Donuts', + 'description' => 'A dozen donuts', + 'price' => 1.5, + 'quantity' => 12, + 'discount' => 1.8, + 'taxes' => 0.81 + ] + ]); + $this->request->setTransactionId('ORD42-P1'); + $this->request->setAmount(25.41); + + $data = $this->request->getData(); + + $this->assertSame('Order #42', $data['description']); + $expectedLevel3 = [ + 'merchant_reference' => 'ORD42-P1', + 'line_items' => [ + [ + 'product_code' => 'Cupcakes', + 'product_description' => 'Yummy Cupcakes', + 'unit_cost' => 400, + 'quantity' => 2, + 'tax_amount' => 40, + ], + [ + 'product_code' => 'Donuts', + 'product_description' => 'A dozen donuts', + 'unit_cost' => 150, + 'quantity' => 12, + 'discount_amount' => 180, + 'tax_amount' => 81, + ] + ] + ]; + $this->assertEquals($expectedLevel3, $data['level3']); + } + + public function testDataWithInvalidLevel3() + { + $this->request->setItems([ + [ + 'name' => 'Cupcakes', + 'description' => 'Yummy Cupcakes', + 'price' => 4, + 'quantity' => 2, + 'taxes' => 0.4 + ], + [ + 'name' => 'Donuts', + 'description' => 'A dozen donuts', + 'price' => 1.5, + 'quantity' => 12, + 'discount' => 1.8, + 'taxes' => 0.8 + ] + ]); + $this->request->setTransactionId('ORD42-P1'); + $this->request->setAmount(25.41); + + $data = $this->request->getData(); + + $this->assertArrayNotHasKey('level3', $data, + 'should not include level 3 data if the line items do not add up to the amount'); + } + /** * @expectedException \Omnipay\Common\Exception\InvalidRequestException * @expectedExceptionMessage The source parameter is required From 8706887d8c892cea1ca01fc6e995f1145d3b7a63 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Mon, 6 Jan 2020 12:14:16 -0800 Subject: [PATCH 08/23] Support for card present track data --- src/Message/AbstractRequest.php | 21 ++++++++++--- tests/Message/AuthorizeRequestTest.php | 43 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index e33c7c03..0b0e3038 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -357,17 +357,30 @@ protected function getCardData() $data = array(); $data['object'] = 'card'; + + // If track data is present, only return data relevant to a card present charge + $tracks = $card->getTracks(); + $cvv = $card->getCvv(); + $postcode = $card->getPostcode(); + if (!empty($postcode)) { + $data['address_zip'] = $postcode; + } + if (!empty($cvv)) { + $data['cvc'] = $cvv; + } + if (!empty($tracks)) { + $data['swipe_data'] = $tracks; + return $data; + } + + // If we got here, it's a card not present transaction, so include everything we have $data['number'] = $card->getNumber(); $data['exp_month'] = $card->getExpiryMonth(); $data['exp_year'] = $card->getExpiryYear(); - if ($card->getCvv()) { - $data['cvc'] = $card->getCvv(); - } $data['name'] = $card->getName(); $data['address_line1'] = $card->getAddress1(); $data['address_line2'] = $card->getAddress2(); $data['address_city'] = $card->getCity(); - $data['address_zip'] = $card->getPostcode(); $data['address_state'] = $card->getState(); $data['address_country'] = $card->getCountry(); $data['email'] = $card->getEmail(); diff --git a/tests/Message/AuthorizeRequestTest.php b/tests/Message/AuthorizeRequestTest.php index 9999d244..f1fdf639 100644 --- a/tests/Message/AuthorizeRequestTest.php +++ b/tests/Message/AuthorizeRequestTest.php @@ -2,6 +2,7 @@ namespace Omnipay\Stripe\Message; +use Omnipay\Common\CreditCard; use Omnipay\Common\ItemBag; use Omnipay\Tests\TestCase; @@ -107,6 +108,48 @@ public function testDataWithCard() $this->assertSame($card['number'], $data['source']['number']); } + public function testDataWithTracks() + { + $cardData = $this->getValidCard(); + $tracks = "%25B4242424242424242%5ETESTLAST%2FTESTFIRST%5E1505201425400714000000%3F"; + $cardData['tracks'] = $tracks; + unset($cardData['cvv']); + unset($cardData['billingPostcode']); + $this->request->setCard(new CreditCard($cardData)); + $data = $this->request->getData(); + + $this->assertSame($tracks, $data['source']['swipe_data']); + $this->assertCount(2, $data['source'], "Swipe data should be present. All other fields are not required"); + + // If there is any mismatch between the track data and the parsed data, Stripe rejects the transaction, so it's + // best to suppress fields that is already present in the track data. + $this->assertArrayNotHasKey('number', $data, 'Should not send card number for card present charge'); + $this->assertArrayNotHasKey('exp_month', $data, 'Should not send expiry month for card present charge'); + $this->assertArrayNotHasKey('exp_year', $data, 'Should not send expiry year for card present charge'); + $this->assertArrayNotHasKey('name', $data, 'Should not send name for card present charge'); + + // Billing address is not accepted for card present transactions. + $this->assertArrayNotHasKey('address_line1', $data, 'Should not send billing address for card present charge'); + $this->assertArrayNotHasKey('address_line2', $data, 'Should not send billing address for card present charge'); + $this->assertArrayNotHasKey('address_city', $data, 'Should not send billing address for card present charge'); + $this->assertArrayNotHasKey('address_state', $data, 'Should not send billing address for card present charge'); + + } + + public function testDataWithTracksAndZipCVVManuallyEntered() + { + $cardData = $this->getValidCard(); + $tracks = "%25B4242424242424242%5ETESTLAST%2FTESTFIRST%5E1505201425400714000000%3F"; + $cardData['tracks'] = $tracks; + $this->request->setCard(new CreditCard($cardData)); + $data = $this->request->getData(); + + $this->assertSame($tracks, $data['source']['swipe_data']); + $this->assertSame($cardData['cvv'], $data['source']['cvc']); + $this->assertSame($cardData['billingPostcode'], $data['source']['address_zip']); + $this->assertCount(4, $data['source'], "Swipe data, cvv and zip code should be present"); + } + public function testSendSuccess() { $this->setMockHttpResponse('PurchaseSuccess.txt'); From 23f998e3fdf7f860d3096d9fb30aea19498d6ca6 Mon Sep 17 00:00:00 2001 From: "Alexander (SASh) Alexiev" Date: Fri, 24 Apr 2020 17:11:03 +0300 Subject: [PATCH 09/23] add: create setup intent request --- src/Message/SetupIntents/AbstractRequest.php | 39 ++++++ .../SetupIntents/CreateSetupIntentRequest.php | 66 +++++++++ src/Message/SetupIntents/Response.php | 130 ++++++++++++++++++ src/PaymentIntentsGateway.php | 12 ++ 4 files changed, 247 insertions(+) create mode 100644 src/Message/SetupIntents/AbstractRequest.php create mode 100644 src/Message/SetupIntents/CreateSetupIntentRequest.php create mode 100644 src/Message/SetupIntents/Response.php diff --git a/src/Message/SetupIntents/AbstractRequest.php b/src/Message/SetupIntents/AbstractRequest.php new file mode 100644 index 00000000..4ea498e7 --- /dev/null +++ b/src/Message/SetupIntents/AbstractRequest.php @@ -0,0 +1,39 @@ +setParameter('paymentIntentReference', $value); + } + + /** + * @return mixed + */ + public function getSetupIntentReference() + { + return $this->getParameter('paymentIntentReference'); + } + +} diff --git a/src/Message/SetupIntents/CreateSetupIntentRequest.php b/src/Message/SetupIntents/CreateSetupIntentRequest.php new file mode 100644 index 00000000..951325b6 --- /dev/null +++ b/src/Message/SetupIntents/CreateSetupIntentRequest.php @@ -0,0 +1,66 @@ + + * + * + * + * @see \Omnipay\Stripe\Message\PaymentIntents\AttachPaymentMethodRequest + * @see \Omnipay\Stripe\Message\PaymentIntents\DetachPaymentMethodRequest + * @see \Omnipay\Stripe\Message\PaymentIntents\UpdatePaymentMethodRequest + * @link https://stripe.com/docs/api/setup_intents/create + */ +class CreateSetupIntentRequest extends AbstractRequest +{ + /** + * @inheritdoc + */ + public function getData() + { + $data = []; + + if ($this->getCustomerReference()) { + $data['customer'] = $this->getCustomerReference(); + } + if ($this->getDescription()){ + $data['description'] = $this->getDescription(); + } + + if ($this->getMetadata()){ + $this['metadata'] = $this->getMetadata(); + } + if ($this->getPaymentMethod()){ + $this['payment_method'] = $this->getPaymentMethod(); + } + + $data['usage'] = 'off_session'; + $data['payment_method_types'][] = 'card'; + + return $data; + } + + /** + * @inheritdoc + */ + public function getEndpoint() + { + return $this->endpoint.'/setup_intents'; + } + + /** + * @inheritdoc + */ + protected function createResponse($data, $headers = []) + { + return $this->response = new Response($this, $data, $headers); + } +} diff --git a/src/Message/SetupIntents/Response.php b/src/Message/SetupIntents/Response.php new file mode 100644 index 00000000..d9d30f67 --- /dev/null +++ b/src/Message/SetupIntents/Response.php @@ -0,0 +1,130 @@ +data['object']) && 'setup_intent' === $this->data['object']) { + return $this->data['status']; + } + + return null; + } + + /** + * Return true if the payment intent requires confirmation. + * + * @return bool + */ + public function requiresConfirmation() + { + return $this->getStatus() === 'requires_confirmation'; + } + + /** + * @inheritdoc + */ + public function getClientSecret() + { + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + if (!empty($this->data['client_secret'])) { + return $this->data['client_secret']; + } + } + } + + /** + * @inheritdoc + */ + public function getCustomerReference() + { + + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + if (!empty($this->data['customer'])) { + return $this->data['customer']; + } + } + + return parent::getCustomerReference(); + } + + /** + * @inheritdoc + */ + public function isSuccessful() + { + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + return in_array($this->getStatus(), ['succeeded', 'requires_capture']); + } + + return parent::isSuccessful(); + } + + /** + * @inheritdoc + */ + public function isCancelled() + { + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + return $this->getStatus() === 'canceled'; + } + + return parent::isCancelled(); + } + + /** + * @inheritdoc + */ + public function isRedirect() + { + if ($this->getStatus() === 'requires_action' || $this->getStatus() === 'requires_source_action') { + // Currently this gateway supports only manual confirmation, so any other + // next action types pretty much mean a failed transaction for us. + return (!empty($this->data['next_action']) && $this->data['next_action']['type'] === 'redirect_to_url'); + } + + return parent::isRedirect(); + } + + /** + * @inheritdoc + */ + public function getRedirectUrl() + { + return $this->isRedirect() ? $this->data['next_action']['redirect_to_url']['url'] : parent::getRedirectUrl(); + } + + /** + * Get the payment intent reference. + * + * @return string|null + */ + public function getSetupIntentReference() + { + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + return $this->data['id']; + } + + return null; + } +} diff --git a/src/PaymentIntentsGateway.php b/src/PaymentIntentsGateway.php index 88e24571..a5ed27e2 100644 --- a/src/PaymentIntentsGateway.php +++ b/src/PaymentIntentsGateway.php @@ -166,4 +166,16 @@ public function deleteCard(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\PaymentIntents\DetachPaymentMethodRequest', $parameters); } + + // Setup Intent + + /** + * @inheritdoc + * + * @return \Omnipay\Stripe\Message\SetupIntents\CreateSetupIntentRequest + */ + public function createSetupIntent(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\SetupIntents\CreateSetupIntentRequest', $parameters); + } } From 68b490362af2d794cb1b3c2a52f5e90a8ce8458b Mon Sep 17 00:00:00 2001 From: "Alexander (SASh) Alexiev" Date: Sat, 25 Apr 2020 09:12:17 +0300 Subject: [PATCH 10/23] add: restrieve setup intent and fix the setup intents response --- src/Message/SetupIntents/AbstractRequest.php | 4 +- src/Message/SetupIntents/Response.php | 16 +++++- .../RetrieveSetupIntentRequest.php | 54 +++++++++++++++++++ src/PaymentIntentsGateway.php | 9 ++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/Message/SetupIntents/RetrieveSetupIntentRequest.php diff --git a/src/Message/SetupIntents/AbstractRequest.php b/src/Message/SetupIntents/AbstractRequest.php index 4ea498e7..c56b6dbe 100644 --- a/src/Message/SetupIntents/AbstractRequest.php +++ b/src/Message/SetupIntents/AbstractRequest.php @@ -25,7 +25,7 @@ abstract class AbstractRequest extends \Omnipay\Stripe\Message\AbstractRequest */ public function setSetupIntentReference($value) { - return $this->setParameter('paymentIntentReference', $value); + return $this->setParameter('setupIntentReference', $value); } /** @@ -33,7 +33,7 @@ public function setSetupIntentReference($value) */ public function getSetupIntentReference() { - return $this->getParameter('paymentIntentReference'); + return $this->getParameter('setupIntentReference'); } } diff --git a/src/Message/SetupIntents/Response.php b/src/Message/SetupIntents/Response.php index d9d30f67..bb643b34 100644 --- a/src/Message/SetupIntents/Response.php +++ b/src/Message/SetupIntents/Response.php @@ -74,7 +74,7 @@ public function getCustomerReference() public function isSuccessful() { if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { - return in_array($this->getStatus(), ['succeeded', 'requires_capture']); + return in_array($this->getStatus(), ['succeeded', 'requires_payment_method']); } return parent::isSuccessful(); @@ -127,4 +127,18 @@ public function getSetupIntentReference() return null; } + + /** + * Get the payment intent reference. + * + * @return string|null + */ + public function getPaymentMethod() + { + if (isset($this->data['object']) && 'setup_intent' === $this->data['object']) { + return $this->data['payment_method']; + } + + return null; + } } diff --git a/src/Message/SetupIntents/RetrieveSetupIntentRequest.php b/src/Message/SetupIntents/RetrieveSetupIntentRequest.php new file mode 100644 index 00000000..8f0df658 --- /dev/null +++ b/src/Message/SetupIntents/RetrieveSetupIntentRequest.php @@ -0,0 +1,54 @@ + + * + * + * + * @see \Omnipay\Stripe\Message\PaymentIntents\AttachPaymentMethodRequest + * @see \Omnipay\Stripe\Message\PaymentIntents\DetachPaymentMethodRequest + * @see \Omnipay\Stripe\Message\PaymentIntents\UpdatePaymentMethodRequest + * @link https://stripe.com/docs/api/setup_intents/create + */ +class RetrieveSetupIntentRequest extends AbstractRequest +{ + /** + * @inheritdoc + */ + public function getData() + { + $this->validate('setupIntentReference'); + + return []; + } + + /** + * @inheritdoc + */ + public function getEndpoint() + { + return $this->endpoint.'/setup_intents/'.$this->getSetupIntentReference(); + } + + public function getHttpMethod() + { + return 'GET'; + } + + /** + * @inheritdoc + */ + protected function createResponse($data, $headers = []) + { + return $this->response = new Response($this, $data, $headers); + } +} diff --git a/src/PaymentIntentsGateway.php b/src/PaymentIntentsGateway.php index a5ed27e2..c646d2be 100644 --- a/src/PaymentIntentsGateway.php +++ b/src/PaymentIntentsGateway.php @@ -178,4 +178,13 @@ public function createSetupIntent(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\SetupIntents\CreateSetupIntentRequest', $parameters); } + /** + * @inheritdoc + * + * @return \Omnipay\Stripe\Message\SetupIntents\CreateSetupIntentRequest + */ + public function retrieveSetupIntent(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\SetupIntents\RetrieveSetupIntentRequest', $parameters); + } } From 2e1355e661369a1e162c5369a354777ec7070d2c Mon Sep 17 00:00:00 2001 From: "Alexander (SASh) Alexiev" Date: Sat, 25 Apr 2020 19:24:12 +0300 Subject: [PATCH 11/23] add: off session option for payment intents --- .../PaymentIntents/AuthorizeRequest.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index fe939d1e..5ac708f0 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -126,6 +126,26 @@ public function getConfirm() return $this->getParameter('confirm'); } + /** + * Set the confirm parameter. + * + * @param $value + */ + public function setOffSession($value) + { + $this->setParameter('offSession', $value); + } + + /** + * Get the confirm parameter. + * + * @return mixed + */ + public function getOffSession() + { + return $this->getParameter('offSession'); + } + /** * @return mixed */ @@ -352,6 +372,8 @@ public function getData() $this->validate('returnUrl'); $data['return_url'] = $this->getReturnUrl(); } + $data['off_session'] = $this->getOffSession() ? 'true' : 'false'; + return $data; } From 2c88b82b44ca3ae4ae54b067416ab2e79914be04 Mon Sep 17 00:00:00 2001 From: "Alexander (SASh) Alexiev" Date: Sat, 25 Apr 2020 19:34:00 +0300 Subject: [PATCH 12/23] red: formatting --- src/Message/SetupIntents/AbstractRequest.php | 1 - src/Message/SetupIntents/CreateSetupIntentRequest.php | 9 +++++---- src/Message/SetupIntents/Response.php | 3 ++- src/Message/SetupIntents/RetrieveSetupIntentRequest.php | 3 ++- src/PaymentIntentsGateway.php | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Message/SetupIntents/AbstractRequest.php b/src/Message/SetupIntents/AbstractRequest.php index c56b6dbe..246fc1ac 100644 --- a/src/Message/SetupIntents/AbstractRequest.php +++ b/src/Message/SetupIntents/AbstractRequest.php @@ -35,5 +35,4 @@ public function getSetupIntentReference() { return $this->getParameter('setupIntentReference'); } - } diff --git a/src/Message/SetupIntents/CreateSetupIntentRequest.php b/src/Message/SetupIntents/CreateSetupIntentRequest.php index 951325b6..53eb08b0 100644 --- a/src/Message/SetupIntents/CreateSetupIntentRequest.php +++ b/src/Message/SetupIntents/CreateSetupIntentRequest.php @@ -3,6 +3,7 @@ /** * Stripe Create Payment Method Request. */ + namespace Omnipay\Stripe\Message\SetupIntents; /** @@ -31,14 +32,14 @@ public function getData() if ($this->getCustomerReference()) { $data['customer'] = $this->getCustomerReference(); } - if ($this->getDescription()){ + if ($this->getDescription()) { $data['description'] = $this->getDescription(); } - if ($this->getMetadata()){ + if ($this->getMetadata()) { $this['metadata'] = $this->getMetadata(); } - if ($this->getPaymentMethod()){ + if ($this->getPaymentMethod()) { $this['payment_method'] = $this->getPaymentMethod(); } @@ -53,7 +54,7 @@ public function getData() */ public function getEndpoint() { - return $this->endpoint.'/setup_intents'; + return $this->endpoint . '/setup_intents'; } /** diff --git a/src/Message/SetupIntents/Response.php b/src/Message/SetupIntents/Response.php index bb643b34..47cc3007 100644 --- a/src/Message/SetupIntents/Response.php +++ b/src/Message/SetupIntents/Response.php @@ -3,10 +3,11 @@ /** * Stripe Payment Intents Response. */ + namespace Omnipay\Stripe\Message\SetupIntents; use Omnipay\Common\Message\ResponseInterface; -use Omnipay\Stripe\Message\Response as BaseResponse;; +use Omnipay\Stripe\Message\Response as BaseResponse; /** * Stripe Payment Intents Response. diff --git a/src/Message/SetupIntents/RetrieveSetupIntentRequest.php b/src/Message/SetupIntents/RetrieveSetupIntentRequest.php index 8f0df658..ab465bd4 100644 --- a/src/Message/SetupIntents/RetrieveSetupIntentRequest.php +++ b/src/Message/SetupIntents/RetrieveSetupIntentRequest.php @@ -3,6 +3,7 @@ /** * Stripe Create Payment Method Request. */ + namespace Omnipay\Stripe\Message\SetupIntents; /** @@ -36,7 +37,7 @@ public function getData() */ public function getEndpoint() { - return $this->endpoint.'/setup_intents/'.$this->getSetupIntentReference(); + return $this->endpoint . '/setup_intents/' . $this->getSetupIntentReference(); } public function getHttpMethod() diff --git a/src/PaymentIntentsGateway.php b/src/PaymentIntentsGateway.php index c646d2be..e6ecea2f 100644 --- a/src/PaymentIntentsGateway.php +++ b/src/PaymentIntentsGateway.php @@ -3,9 +3,8 @@ /** * Stripe Payment Intents Gateway. */ -namespace Omnipay\Stripe; -use Omnipay\Stripe\Message\PaymentIntents\Response; +namespace Omnipay\Stripe; /** * Stripe Payment Intents Gateway. @@ -178,6 +177,7 @@ public function createSetupIntent(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\SetupIntents\CreateSetupIntentRequest', $parameters); } + /** * @inheritdoc * From 4ecac675d152f92d42a00bf57256148bf815700c Mon Sep 17 00:00:00 2001 From: Aimeos GmbH Date: Fri, 1 May 2020 12:28:15 +0200 Subject: [PATCH 13/23] Adds methods for required setting to do token based payments --- .../PaymentIntents/AuthorizeRequest.php | 55 ++++++++++++++++++- .../PaymentIntents/AuthorizeRequestTest.php | 4 ++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index fe939d1e..65965058 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -110,10 +110,11 @@ class AuthorizeRequest extends AbstractRequest * Set the confirm parameter. * * @param $value + * @return AbstractRequest provides a fluent interface. */ public function setConfirm($value) { - $this->setParameter('confirm', $value); + return $this->setParameter('confirm', $value); } /** @@ -286,6 +287,48 @@ public function setReceiptEmail($email) return $this; } + /** + * Set the setup_future_usage parameter. + * + * @param $value + * @return AbstractRequest provides a fluent interface. + */ + public function setSetupFutureUsage($value) + { + return $this->setParameter('setup_future_usage', $value); + } + + /** + * Get the setup_future_usage parameter. + * + * @return mixed + */ + public function getSetupFutureUsage() + { + return $this->getParameter('setup_future_usage'); + } + + /** + * Set the setup_future_usage parameter. + * + * @param $value + * @return AbstractRequest provides a fluent interface. + */ + public function setOffSession($value) + { + return $this->setParameter('off_session', $value); + } + + /** + * Get the setup_future_usage parameter. + * + * @return mixed + */ + public function getOffSession() + { + return $this->getParameter('off_session'); + } + /** * @inheritdoc */ @@ -343,12 +386,20 @@ public function getData() $data['customer'] = $this->getCustomerReference(); } + if ($this->getSetupFutureUsage()) { + $data['setup_future_usage'] = $this->getSetupFutureUsage(); + } + + if ($this->getOffSession()) { + $data['off_session'] = $this->getOffSession() ? 'true' : 'false'; + } + $data['confirmation_method'] = 'manual'; $data['capture_method'] = 'manual'; $data['confirm'] = $this->getConfirm() ? 'true' : 'false'; - if ($this->getConfirm()) { + if ($this->getReturnUrl()) { $this->validate('returnUrl'); $data['return_url'] = $this->getReturnUrl(); } diff --git a/tests/Message/PaymentIntents/AuthorizeRequestTest.php b/tests/Message/PaymentIntents/AuthorizeRequestTest.php index 9e3b8858..7254e9eb 100644 --- a/tests/Message/PaymentIntents/AuthorizeRequestTest.php +++ b/tests/Message/PaymentIntents/AuthorizeRequestTest.php @@ -25,6 +25,8 @@ public function setUp() ), 'applicationFee' => '1.00', 'returnUrl' => 'complete-payment', + 'setup_future_usage' => 'off_session', + 'off_session' => true, 'confirm' => true, ) ); @@ -42,6 +44,8 @@ public function testGetData() $this->assertSame('pm_valid_payment_method', $data['payment_method']); $this->assertSame(array('foo' => 'bar'), $data['metadata']); $this->assertSame(100, $data['application_fee']); + $this->assertSame('off_session', $data['setup_future_usage']); + $this->assertSame(true, $data['off_session']); } /** From e64b511abef93b8cafb50183ffdd793e506bfd73 Mon Sep 17 00:00:00 2001 From: Aimeos GmbH Date: Fri, 1 May 2020 12:42:13 +0200 Subject: [PATCH 14/23] Fixes tests --- src/Message/PaymentIntents/AuthorizeRequest.php | 2 +- tests/Message/PaymentIntents/AuthorizeRequestTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index 65965058..7691135a 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -399,7 +399,7 @@ public function getData() $data['confirm'] = $this->getConfirm() ? 'true' : 'false'; - if ($this->getReturnUrl()) { + if ($this->getConfirm() && !$this->getOffSession()) { $this->validate('returnUrl'); $data['return_url'] = $this->getReturnUrl(); } diff --git a/tests/Message/PaymentIntents/AuthorizeRequestTest.php b/tests/Message/PaymentIntents/AuthorizeRequestTest.php index 7254e9eb..1826f55a 100644 --- a/tests/Message/PaymentIntents/AuthorizeRequestTest.php +++ b/tests/Message/PaymentIntents/AuthorizeRequestTest.php @@ -45,7 +45,7 @@ public function testGetData() $this->assertSame(array('foo' => 'bar'), $data['metadata']); $this->assertSame(100, $data['application_fee']); $this->assertSame('off_session', $data['setup_future_usage']); - $this->assertSame(true, $data['off_session']); + $this->assertSame('true', $data['off_session']); } /** From 4e7fe74c3fb5d5a54c8e1733022f5f4fc85f58b6 Mon Sep 17 00:00:00 2001 From: Aimeos GmbH Date: Fri, 1 May 2020 12:48:50 +0200 Subject: [PATCH 15/23] Don't enable off_session in tests so existing tests still succeed --- tests/Message/PaymentIntents/AuthorizeRequestTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Message/PaymentIntents/AuthorizeRequestTest.php b/tests/Message/PaymentIntents/AuthorizeRequestTest.php index 1826f55a..5081eb01 100644 --- a/tests/Message/PaymentIntents/AuthorizeRequestTest.php +++ b/tests/Message/PaymentIntents/AuthorizeRequestTest.php @@ -26,7 +26,7 @@ public function setUp() 'applicationFee' => '1.00', 'returnUrl' => 'complete-payment', 'setup_future_usage' => 'off_session', - 'off_session' => true, + 'off_session' => false, 'confirm' => true, ) ); @@ -45,7 +45,7 @@ public function testGetData() $this->assertSame(array('foo' => 'bar'), $data['metadata']); $this->assertSame(100, $data['application_fee']); $this->assertSame('off_session', $data['setup_future_usage']); - $this->assertSame('true', $data['off_session']); + $this->assertSame('false', $data['off_session']); } /** From a9c9399ded7ebcaec4390c3f1dae14269107bb65 Mon Sep 17 00:00:00 2001 From: Aimeos GmbH Date: Fri, 1 May 2020 12:53:20 +0200 Subject: [PATCH 16/23] Always add off_session parameter to request --- src/Message/PaymentIntents/AuthorizeRequest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index 7691135a..8cf9d541 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -390,9 +390,7 @@ public function getData() $data['setup_future_usage'] = $this->getSetupFutureUsage(); } - if ($this->getOffSession()) { - $data['off_session'] = $this->getOffSession() ? 'true' : 'false'; - } + $data['off_session'] = $this->getOffSession() ? 'true' : 'false'; $data['confirmation_method'] = 'manual'; $data['capture_method'] = 'manual'; From 3878249ae9c90c298e0a83074e8fa0964fcd1217 Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Sat, 27 Jun 2020 09:58:20 +0200 Subject: [PATCH 17/23] Add trial_end option for CreateSubscription (#182) --- src/Message/CreateSubscriptionRequest.php | 25 +++++++++++++++++++ .../PaymentIntents/AuthorizeRequest.php | 20 --------------- .../Message/CreateSubscriptionRequestTest.php | 1 + tests/Mock/CreateSubscriptionSuccess.txt | 6 ++--- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Message/CreateSubscriptionRequest.php b/src/Message/CreateSubscriptionRequest.php index b3863879..498d7619 100644 --- a/src/Message/CreateSubscriptionRequest.php +++ b/src/Message/CreateSubscriptionRequest.php @@ -45,6 +45,28 @@ public function getTaxPercent() return $this->getParameter('tax_percent'); } + + /** + * Get the the trial end timestamp + * + * @return int + */ + public function getTrialEnd() + { + return $this->getParameter('trial_end'); + } + + /** + * Set the trial end timestamp. + * + * @param int $value + * @return \Omnipay\Common\Message\AbstractRequest|CreateSubscriptionRequest + */ + public function setTrialEnd($value) + { + return $this->setParameter('trial_end', $value); + } + /** * Set the tax percentage * @@ -72,6 +94,9 @@ public function getData() $data['metadata'] = $this->getMetadata(); } + if ($this->getTrialEnd()) { + $data['trial_end'] = $this->getTrialEnd(); + } return $data; } diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index 1dce1322..4a0ec8ad 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -127,26 +127,6 @@ public function getConfirm() return $this->getParameter('confirm'); } - /** - * Set the confirm parameter. - * - * @param $value - */ - public function setOffSession($value) - { - $this->setParameter('offSession', $value); - } - - /** - * Get the confirm parameter. - * - * @return mixed - */ - public function getOffSession() - { - return $this->getParameter('offSession'); - } - /** * @return mixed */ diff --git a/tests/Message/CreateSubscriptionRequestTest.php b/tests/Message/CreateSubscriptionRequestTest.php index 5726c928..199dcc91 100644 --- a/tests/Message/CreateSubscriptionRequestTest.php +++ b/tests/Message/CreateSubscriptionRequestTest.php @@ -13,6 +13,7 @@ public function setUp() { $this->request = new CreateSubscriptionRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->setCustomerReference('cus_7lqqgOm33t4xSU'); + $this->request->setTrialEnd(1606460031); $this->request->setPlan('basic'); } diff --git a/tests/Mock/CreateSubscriptionSuccess.txt b/tests/Mock/CreateSubscriptionSuccess.txt index 7b9109ed..9c288128 100644 --- a/tests/Mock/CreateSubscriptionSuccess.txt +++ b/tests/Mock/CreateSubscriptionSuccess.txt @@ -36,6 +36,6 @@ Cache-Control: no-cache, no-store "start": 1453672798, "status": "active", "tax_percent": null, - "trial_end": null, - "trial_start": null -} \ No newline at end of file + "trial_end": 1606460031, + "trial_start": 1593241598 +} From d3c2592fd5f3ee5e50e8c0e6ce4d966604dd4fff Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Sat, 27 Jun 2020 18:20:32 +0200 Subject: [PATCH 18/23] Add FetchSubscriptionSchedulesRequest (#181) --- .../FetchSubscriptionSchedulesRequest.php | 53 +++++++++++++++ src/Message/Response.php | 14 ++++ .../FetchSubscriptionSchedulesRequestTest.php | 43 ++++++++++++ .../FetchSubscriptionSchedulesFailure.txt | 17 +++++ .../FetchSubscriptionSchedulesSuccess.txt | 68 +++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 src/Message/FetchSubscriptionSchedulesRequest.php create mode 100644 tests/Message/FetchSubscriptionSchedulesRequestTest.php create mode 100644 tests/Mock/FetchSubscriptionSchedulesFailure.txt create mode 100644 tests/Mock/FetchSubscriptionSchedulesSuccess.txt diff --git a/src/Message/FetchSubscriptionSchedulesRequest.php b/src/Message/FetchSubscriptionSchedulesRequest.php new file mode 100644 index 00000000..c41006c3 --- /dev/null +++ b/src/Message/FetchSubscriptionSchedulesRequest.php @@ -0,0 +1,53 @@ +getParameter('subscriptionSchedulesReference'); + } + + /** + * Set the subscription reference. + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|FetchSubscriptionRequest + */ + public function setSubscriptionSchedulesReference($value) + { + return $this->setParameter('subscriptionSchedulesReference', $value); + } + + public function getData() + { + $this->validate('subscriptionSchedulesReference'); + + return array(); + } + + public function getEndpoint() + { + return $this->endpoint.'/subscription_schedules/'.$this->getSubscriptionSchedulesReference(); + } + + public function getHttpMethod() + { + return 'GET'; + } +} diff --git a/src/Message/Response.php b/src/Message/Response.php index 476ecd0f..6ac0ce59 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -261,6 +261,20 @@ public function getSubscriptionReference() return null; } + /** + * Get the subscription schedule reference from the response of FetchSubscriptionSchedulesRequest. + * + * @return array|null + */ + public function getSubscriptionSchedulesReference() + { + if (isset($this->data['object']) && $this->data['object'] == 'subscription_schedule') { + return $this->data['id']; + } + + return null; + } + /** * Get the event reference from the response of FetchEventRequest. * diff --git a/tests/Message/FetchSubscriptionSchedulesRequestTest.php b/tests/Message/FetchSubscriptionSchedulesRequestTest.php new file mode 100644 index 00000000..67d854b3 --- /dev/null +++ b/tests/Message/FetchSubscriptionSchedulesRequestTest.php @@ -0,0 +1,43 @@ +request = new FetchSubscriptionSchedulesRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setSubscriptionSchedulesReference('sub_sched_1GagVZKscivsTrcFhfMufnWP'); + } + + public function testEndpoint() + { + $endpoint = 'https://api.stripe.com/v1/subscription_schedules/sub_sched_1GagVZKscivsTrcFhfMufnWP'; + $this->assertSame($endpoint, $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('FetchSubscriptionSchedulesSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('sub_sched_1GagVZKscivsTrcFhfMufnWP', $response->getSubscriptionSchedulesReference()); + $this->assertNull($response->getMessage()); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('FetchSubscriptionSchedulesFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getSubscriptionReference()); + $message = 'No such subscription schedule: sub_sched_1GagVZKscivsTrcFhfMufnWP'; + $this->assertSame($message, $response->getMessage()); + } +} diff --git a/tests/Mock/FetchSubscriptionSchedulesFailure.txt b/tests/Mock/FetchSubscriptionSchedulesFailure.txt new file mode 100644 index 00000000..3a0ef437 --- /dev/null +++ b/tests/Mock/FetchSubscriptionSchedulesFailure.txt @@ -0,0 +1,17 @@ +HTTP/1.1 404 Not Found +Server: nginx +Date: Mon, 26 Jun 2020 13:41:33 GMT +Content-Type: application/json +Content-Length: 188 +Connection: keep-alive +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such subscription schedule: sub_sched_1GagVZKscivsTrcFhfMufnWP", + "param": "id", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/FetchSubscriptionSchedulesSuccess.txt b/tests/Mock/FetchSubscriptionSchedulesSuccess.txt new file mode 100644 index 00000000..4ad1fc4f --- /dev/null +++ b/tests/Mock/FetchSubscriptionSchedulesSuccess.txt @@ -0,0 +1,68 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Mon, 26 Jun 2020 13:41:33 GMT +Content-Type: application/json +Content-Length: 784 +Connection: keep-alive +Cache-Control: no-cache, no-store + +{ + "id": "sub_sched_1GagVZKscivsTrcFhfMufnWP", + "object": "subscription_schedule", + "canceled_at": null, + "completed_at": null, + "created": 1593158960, + "current_phase": null, + "customer": "cus_7lqqgOm33t4xSU", + "default_settings": { + "billing_thresholds": null, + "collection_method": "charge_automatically", + "default_payment_method": null, + "default_source": null, + "invoice_settings": null, + "transfer_data": null + }, + "end_behavior": "release", + "livemode": false, + "metadata": { + }, + "phases": [ + { + "add_invoice_items": [ + + ], + "application_fee_percent": null, + "billing_thresholds": null, + "collection_method": null, + "coupon": null, + "default_payment_method": null, + "default_tax_rates": [ + + ], + "end_date": 1596960367, + "invoice_settings": null, + "plans": [ + { + "billing_thresholds": null, + "plan": "plan_GXzqnSohdy458I", + "price": "plan_GXzqnSohdy458I", + "quantity": 1, + "tax_rates": [ + + ] + } + ], + "prorate": true, + "proration_behavior": "create_prorations", + "start_date": 1595750767, + "tax_percent": null, + "transfer_data": null, + "trial_end": null + } + ], + "released_at": null, + "released_subscription": null, + "renewal_interval": null, + "status": "not_started", + "subscription": null +} From 431dfde29f5cba0b58212154c0f3357ed9776a19 Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Sat, 27 Jun 2020 18:41:32 +0200 Subject: [PATCH 19/23] Add calls for Coupons (#183) --- src/Gateway.php | 50 ++++++ src/Message/CreateCouponRequest.php | 201 ++++++++++++++++++++++ src/Message/DeleteCouponRequest.php | 49 ++++++ src/Message/FetchCouponRequest.php | 48 ++++++ src/Message/ListCouponsRequest.php | 123 +++++++++++++ src/Message/Response.php | 32 ++++ src/Message/UpdateCouponRequest.php | 76 ++++++++ tests/Message/CreateCouponRequestTest.php | 65 +++++++ tests/Message/DeleteCouponRequestTest.php | 41 +++++ tests/Message/FetchCouponRequestTest.php | 42 +++++ tests/Message/ListCouponsTest.php | 42 +++++ tests/Message/UpdateCouponRequestTest.php | 53 ++++++ tests/Mock/CreateCouponFailure.txt | 17 ++ tests/Mock/CreateCouponSuccess.txt | 26 +++ tests/Mock/DeleteCouponFailure.txt | 18 ++ tests/Mock/DeleteCouponSuccess.txt | 14 ++ tests/Mock/FetchCouponFailure.txt | 18 ++ tests/Mock/FetchCouponSuccess.txt | 26 +++ tests/Mock/ListCouponsSuccess.txt | 50 ++++++ tests/Mock/UpdateCouponFailure.txt | 18 ++ tests/Mock/UpdateCouponSuccess.txt | 26 +++ 21 files changed, 1035 insertions(+) create mode 100644 src/Message/CreateCouponRequest.php create mode 100644 src/Message/DeleteCouponRequest.php create mode 100644 src/Message/FetchCouponRequest.php create mode 100644 src/Message/ListCouponsRequest.php create mode 100644 src/Message/UpdateCouponRequest.php create mode 100644 tests/Message/CreateCouponRequestTest.php create mode 100644 tests/Message/DeleteCouponRequestTest.php create mode 100644 tests/Message/FetchCouponRequestTest.php create mode 100644 tests/Message/ListCouponsTest.php create mode 100644 tests/Message/UpdateCouponRequestTest.php create mode 100644 tests/Mock/CreateCouponFailure.txt create mode 100644 tests/Mock/CreateCouponSuccess.txt create mode 100644 tests/Mock/DeleteCouponFailure.txt create mode 100644 tests/Mock/DeleteCouponSuccess.txt create mode 100644 tests/Mock/FetchCouponFailure.txt create mode 100644 tests/Mock/FetchCouponSuccess.txt create mode 100644 tests/Mock/ListCouponsSuccess.txt create mode 100644 tests/Mock/UpdateCouponFailure.txt create mode 100644 tests/Mock/UpdateCouponSuccess.txt diff --git a/src/Gateway.php b/src/Gateway.php index 0ed96fae..8cedf9f7 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -338,4 +338,54 @@ public function completePurchase(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\CompletePurchaseRequest', $parameters); } + + /** + * @param array $parameters + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function createCoupon(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\CreateCouponRequest', $parameters); + } + + /** + * @param array $parameters + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function fetchCoupon(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\FetchCouponRequest', $parameters); + } + + /** + * @param array $parameters + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function deleteCoupon(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\DeleteCouponRequest', $parameters); + } + + /** + * @param array $parameters + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function updateCoupon(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\UpdateCouponRequest', $parameters); + } + + /** + * @param array $parameters + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function listCoupons(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\ListCouponsRequest', $parameters); + } } diff --git a/src/Message/CreateCouponRequest.php b/src/Message/CreateCouponRequest.php new file mode 100644 index 00000000..d5cb443c --- /dev/null +++ b/src/Message/CreateCouponRequest.php @@ -0,0 +1,201 @@ +getParameter('amount_off'); + } + + /** + * @param int $value + * + * @return CreateCouponRequest + */ + public function setAmountOff($value) + { + return $this->setParameter('amount_off', $value); + } + + /** + * @return int + */ + public function getPercentOff() + { + return $this->getParameter('percent_off'); + } + + /** + * @param int $value + * + * @return CreateCouponRequest + */ + public function setPercentOff($value) + { + return $this->setParameter('percent_off', $value); + } + + /** + * @return int + */ + public function getDurationInMonths() + { + return $this->getParameter('duration_in_months'); + } + + /** + * @param int $value + * + * @return CreateCouponRequest + */ + public function setDurationInMonths($value) + { + return $this->setParameter('duration_in_months', $value); + } + + /** + * @return string + */ + public function getName() + { + return $this->getParameter('name'); + } + + /** + * @param string $value + * + * @return CreateCouponRequest + */ + public function setName($value) + { + return $this->setParameter('name', $value); + } + + /** + * @return int + */ + public function getMaxRedemptions() + { + return $this->getParameter('max_redemptions'); + } + + /** + * @param int $value + * + * @return CreateCouponRequest + */ + public function setMaxRedemptions($value) + { + return $this->setParameter('duration_in_months', $value); + } + + /** + * @return int + */ + public function getRedeemBy() + { + return $this->getParameter('redeem_by'); + } + + /** + * @param int $value + * + * @return CreateCouponRequest + */ + public function setRedeemBy($value) + { + return $this->setParameter('redeem_by', $value); + } + + /** + * @return string + */ + public function getId() + { + return $this->getParameter('id'); + } + + /** + * @param string $value + * + * @return CreateCouponRequest + */ + public function setId($value) + { + return $this->setParameter('id', $value); + } + + /** + * @return string + */ + public function getDuration() + { + return $this->getParameter('duration'); + } + + /** + * @param string $value + * + * @return CreateCouponRequest + */ + public function setDuration($value) + { + return $this->setParameter('duration', $value); + } + + /** + * {@inheritdoc} + */ + public function getData() + { + $this->validate('duration'); + + $amountOff = $this->getAmountOff(); + $percentOff = $this->getPercentOff(); + + if (!isset($amountOff) && !isset($percentOff)) { + throw new InvalidRequestException("Either amount_off or percent_off is required"); + } + + $data = [ + 'id' => $this->getId(), + 'duration' => $this->getDuration(), + 'amount_off' => $this->getAmountOff(), + 'currency' => $this->getCurrency(), + 'duration_in_months' => $this->getDurationInMonths(), + 'name' => $this->getName(), + 'max_redemptions' => $this->getMaxRedemptions(), + 'percent_off' => $this->getPercentOff(), + 'redeem_by' => $this->getRedeemBy(), + ]; + + if ($this->getMetadata()) { + $data['metadata'] = $this->getMetadata(); + } + + return $data; + } + + public function getEndpoint() + { + return $this->endpoint.'/coupons'; + } + + public function getHttpMethod() + { + return 'POST'; + } +} diff --git a/src/Message/DeleteCouponRequest.php b/src/Message/DeleteCouponRequest.php new file mode 100644 index 00000000..b23dfad0 --- /dev/null +++ b/src/Message/DeleteCouponRequest.php @@ -0,0 +1,49 @@ +getParameter('couponId'); + } + + /** + * Set the coupon id. + * + * @param string $value + * + * @return DeleteCouponRequest provides a fluent interface + */ + public function setCouponId($value) + { + return $this->setParameter('couponId', $value); + } + + public function getData() + { + $this->validate('couponId'); + } + + public function getEndpoint() + { + return $this->endpoint.'/coupons/'.$this->getCouponId(); + } + + public function getHttpMethod() + { + return 'DELETE'; + } +} diff --git a/src/Message/FetchCouponRequest.php b/src/Message/FetchCouponRequest.php new file mode 100644 index 00000000..7d22eda2 --- /dev/null +++ b/src/Message/FetchCouponRequest.php @@ -0,0 +1,48 @@ +getParameter('couponId'); + } + + /** + * Set the coupon id. + * + * @param string + * @return FetchCouponRequest provides a fluent interface. + */ + public function setCouponId($value) + { + return $this->setParameter('couponId', $value); + } + + public function getData() + { + $this->validate('couponId'); + } + + public function getEndpoint() + { + return $this->endpoint.'/coupons/'.$this->getCouponId(); + } + + public function getHttpMethod() + { + return 'GET'; + } +} diff --git a/src/Message/ListCouponsRequest.php b/src/Message/ListCouponsRequest.php new file mode 100644 index 00000000..23aba42c --- /dev/null +++ b/src/Message/ListCouponsRequest.php @@ -0,0 +1,123 @@ +getParameter('created'); + } + + /** + * @param string $value + * + * @return ListCouponsRequest + */ + public function setCreated($value) + { + return $this->setParameter('created', $value); + } + + /** + * @return mixed + */ + public function getLimit() + { + return $this->getParameter('limit'); + } + + /** + * @param string $value + * + * @return ListCouponsRequest + */ + public function setLimit($value) + { + return $this->setParameter('limit', $value); + } + + /** + * A cursor for use in pagination. `ending_before` is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, starting with `obj_bar`, your + * subsequent call can include `ending_before=obj_ba`r in order to fetch the previous page of the list. + * + * @return mixed + */ + public function getEndingBefore() + { + return $this->getParameter('endingBefore'); + } + + /** + * A cursor for use in pagination. `starting_after` is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your + * subsequent call can include `starting_after=obj_foo` in order to fetch the next page of the list. + * + * @return mixed + */ + public function getStartingAfter() + { + return $this->getParameter('startingAfter'); + } + + /** + * @param string $value + * + * @return \Omnipay\Common\Message\AbstractRequest + */ + public function setStartingAfter($value) + { + return $this->setParameter('startingAfter', $value); + } + + /** + * @param string $value + * + * @return ListCouponsRequest + */ + public function setEndingBefore($value) + { + return $this->setParameter('endingBefore', $value); + } + + public function getData() + { + $data = array(); + + if ($this->getLimit()) { + $data['created'] = $this->getCreated(); + } + if ($this->getLimit()) { + $data['limit'] = $this->getLimit(); + } + + if ($this->getEndingBefore()) { + $data['ending_before'] = $this->getEndingBefore(); + } + + if ($this->getStartingAfter()) { + $data['starting_after'] = $this->getStartingAfter(); + } + + return $data; + } + + public function getEndpoint() + { + return $this->endpoint.'/coupons'; + } + + public function getHttpMethod() + { + return 'GET'; + } +} diff --git a/src/Message/Response.php b/src/Message/Response.php index 6ac0ce59..39e3419d 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -543,4 +543,36 @@ public function getSessionId() return null; } + + /** + * Get the coupon plan from the response of CreateCouponRequest. + * + * @return array|null + */ + public function getCoupon() + { + if (isset($this->data['coupon'])) { + return $this->data['coupon']; + } elseif (array_key_exists('object', $this->data) && $this->data['object'] == 'coupon') { + return $this->data; + } + + return null; + } + + /** + * Get coupon id + * + * @return string|null + */ + public function getCouponId() + { + $coupon = $this->getCoupon(); + + if ($coupon && array_key_exists('id', $coupon)) { + return $coupon['id']; + } + + return null; + } } diff --git a/src/Message/UpdateCouponRequest.php b/src/Message/UpdateCouponRequest.php new file mode 100644 index 00000000..b709af15 --- /dev/null +++ b/src/Message/UpdateCouponRequest.php @@ -0,0 +1,76 @@ +getParameter('couponId'); + } + + /** + * Set the coupon + * + * @param $value + * @return UpdateCouponRequest + */ + public function setCouponId($value) + { + return $this->setParameter('couponId', $value); + } + + /** + * @return string + */ + public function getName() + { + return $this->getParameter('name'); + } + + /** + * @param string $value + * + * @return UpdateCouponRequest + */ + public function setName($value) + { + return $this->setParameter('name', $value); + } + + public function getData() + { + $data = array(); + + if (null !== $this->getName()) { + $data['name'] = $this->getName(); + } + + if ($this->getMetadata()) { + $data['metadata'] = $this->getMetadata(); + } + + return $data; + } + + public function getEndpoint() + { + return $this->endpoint.'/coupons/'.$this->getCouponId(); + } + + public function getHttpMethod() + { + return 'POST'; + } +} diff --git a/tests/Message/CreateCouponRequestTest.php b/tests/Message/CreateCouponRequestTest.php new file mode 100644 index 00000000..a2f2207e --- /dev/null +++ b/tests/Message/CreateCouponRequestTest.php @@ -0,0 +1,65 @@ +request = new CreateCouponRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setId('50_OFF'); + $this->request->setDuration('repeating'); + $this->request->setCurrency('EUR'); + $this->request->setRedeemBy(1606460031); + $this->request->setPercentOff(50); + $this->request->setDurationInMonths(3); + $this->request->setMaxRedemptions(10); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/coupons', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('CreateCouponSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('50_OFF', $response->getCouponId()); + $this->assertNotNull($response->getCoupon()); + $this->assertNull($response->getMessage()); + } + + /** + * @expectedException \Omnipay\Common\Exception\InvalidRequestException + * @expectedExceptionMessage Either amount_off or percent_off is required + */ + public function testAmountPercentRequired() + { + $this->request->setPercentOff(null); + $this->request->setAmountOff(null); + $this->request->getData(); + } + + + public function testSendError() + { + $this->setMockHttpResponse('CreateCouponFailure.txt'); + $response = $this->request->send(); + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getCoupon()); + $this->assertNull($response->getCouponId()); + $this->assertSame('Coupon already exists.', $response->getMessage()); + } +} diff --git a/tests/Message/DeleteCouponRequestTest.php b/tests/Message/DeleteCouponRequestTest.php new file mode 100644 index 00000000..0f0daab8 --- /dev/null +++ b/tests/Message/DeleteCouponRequestTest.php @@ -0,0 +1,41 @@ +request = new DeleteCouponRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setCouponId('50_OFF'); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/coupons/50_OFF', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('DeleteCouponSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getMessage()); + $this->assertTrue($response->getData()['deleted']); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('DeleteCouponFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getCouponId()); + $this->assertSame('No such coupon: 30_OFF', $response->getMessage()); + } +} diff --git a/tests/Message/FetchCouponRequestTest.php b/tests/Message/FetchCouponRequestTest.php new file mode 100644 index 00000000..79ea7747 --- /dev/null +++ b/tests/Message/FetchCouponRequestTest.php @@ -0,0 +1,42 @@ +request = new FetchCouponRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setCouponId('50_OFF'); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/coupons/50_OFF', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('FetchCouponSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('50_OFF', $response->getCouponId()); + $this->assertNull($response->getMessage()); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('FetchCouponFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getCoupon()); + $this->assertNull($response->getCouponId()); + $this->assertSame('No such coupon: 30_OFF', $response->getMessage()); + } +} diff --git a/tests/Message/ListCouponsTest.php b/tests/Message/ListCouponsTest.php new file mode 100644 index 00000000..d1d51800 --- /dev/null +++ b/tests/Message/ListCouponsTest.php @@ -0,0 +1,42 @@ +request = new ListCouponsRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setLimit(2); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/coupons', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('ListCouponsSuccess.txt'); + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNotNull($response->getList()); + $this->assertNull($response->getMessage()); + } + + /** + * According to documentation: https://stripe.com/docs/api/coupons/list + * This request should never throw an error. + */ + public function testSendFailure() + { + $this->assertTrue(true); + } +} diff --git a/tests/Message/UpdateCouponRequestTest.php b/tests/Message/UpdateCouponRequestTest.php new file mode 100644 index 00000000..9752df6d --- /dev/null +++ b/tests/Message/UpdateCouponRequestTest.php @@ -0,0 +1,53 @@ +request = new UpdateCouponRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setCouponId('50_OFF'); + $this->request->setName('50% Discount'); + } + + public function testEndpoint() + { + $endpoint = 'https://api.stripe.com/v1/coupons/50_OFF'; + $this->assertSame($endpoint, $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('UpdateCouponSuccess.txt'); + /** @var Response */ + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('50_OFF', $response->getCouponId()); + $this->assertNotNull($response->getCoupon()); + $this->assertNull($response->getMessage()); + } + + + public function testSendError() + { + $this->setMockHttpResponse('UpdateCouponFailure.txt'); + + /** @var Response */ + $response = $this->request->send(); + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getCouponId()); + $this->assertNull($response->getCoupon()); + $this->assertSame('No such coupon: 50_OFF', $response->getMessage()); + } +} diff --git a/tests/Mock/CreateCouponFailure.txt b/tests/Mock/CreateCouponFailure.txt new file mode 100644 index 00000000..7dbb55e3 --- /dev/null +++ b/tests/Mock/CreateCouponFailure.txt @@ -0,0 +1,17 @@ +HTTP/1.1 400 Bad Request +Server: nginx +Date: Fri, 11 Feb 2016 19:23:31 GMT +Content-Type: application/json +Content-Length: 96 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_already_exists", + "doc_url": "https://stripe.com/docs/error-codes/resource-already-exists", + "message": "Coupon already exists.", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/CreateCouponSuccess.txt b/tests/Mock/CreateCouponSuccess.txt new file mode 100644 index 00000000..93e01feb --- /dev/null +++ b/tests/Mock/CreateCouponSuccess.txt @@ -0,0 +1,26 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id": "50_OFF", + "object": "coupon", + "amount_off": null, + "created": 1593245873, + "currency": null, + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 10, + "metadata": {}, + "name": "50% off", + "percent_off": 50.0, + "redeem_by": 1606460031, + "times_redeemed": 0, + "valid": true +} diff --git a/tests/Mock/DeleteCouponFailure.txt b/tests/Mock/DeleteCouponFailure.txt new file mode 100644 index 00000000..d89e0360 --- /dev/null +++ b/tests/Mock/DeleteCouponFailure.txt @@ -0,0 +1,18 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such coupon: 30_OFF", + "param": "coupon", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/DeleteCouponSuccess.txt b/tests/Mock/DeleteCouponSuccess.txt new file mode 100644 index 00000000..d6e902e5 --- /dev/null +++ b/tests/Mock/DeleteCouponSuccess.txt @@ -0,0 +1,14 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id": "50_OFF", + "object": "coupon", + "deleted": true +} diff --git a/tests/Mock/FetchCouponFailure.txt b/tests/Mock/FetchCouponFailure.txt new file mode 100644 index 00000000..d89e0360 --- /dev/null +++ b/tests/Mock/FetchCouponFailure.txt @@ -0,0 +1,18 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such coupon: 30_OFF", + "param": "coupon", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/FetchCouponSuccess.txt b/tests/Mock/FetchCouponSuccess.txt new file mode 100644 index 00000000..93e01feb --- /dev/null +++ b/tests/Mock/FetchCouponSuccess.txt @@ -0,0 +1,26 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id": "50_OFF", + "object": "coupon", + "amount_off": null, + "created": 1593245873, + "currency": null, + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 10, + "metadata": {}, + "name": "50% off", + "percent_off": 50.0, + "redeem_by": 1606460031, + "times_redeemed": 0, + "valid": true +} diff --git a/tests/Mock/ListCouponsSuccess.txt b/tests/Mock/ListCouponsSuccess.txt new file mode 100644 index 00000000..0eba7094 --- /dev/null +++ b/tests/Mock/ListCouponsSuccess.txt @@ -0,0 +1,50 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 18:28:24 GMT +Content-Type: application/json +Content-Length: 6247 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "object": "list", + "data": [ + { + "id": "50_OFF", + "object": "coupon", + "amount_off": null, + "created": 1593259732, + "currency": null, + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 10, + "metadata": {}, + "name": "50% foo", + "percent_off": 50.0, + "redeem_by": 1606460031, + "times_redeemed": 0, + "valid": true + }, + { + "id": "50_OFF_2", + "object": "coupon", + "amount_off": null, + "created": 1593245806, + "currency": null, + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 10, + "metadata": {}, + "name": "50% off", + "percent_off": 50.0, + "redeem_by": 1606460031, + "times_redeemed": 0, + "valid": true + } + ], + "has_more": true, + "url": "/v1/coupons" +} diff --git a/tests/Mock/UpdateCouponFailure.txt b/tests/Mock/UpdateCouponFailure.txt new file mode 100644 index 00000000..08d02d4f --- /dev/null +++ b/tests/Mock/UpdateCouponFailure.txt @@ -0,0 +1,18 @@ +HTTP/1.1 400 Bad Request +Server: nginx +Date: Fri, 11 Feb 2016 19:23:31 GMT +Content-Type: application/json +Content-Length: 96 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such coupon: 50_OFF", + "param": "coupon", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/UpdateCouponSuccess.txt b/tests/Mock/UpdateCouponSuccess.txt new file mode 100644 index 00000000..c90a3da8 --- /dev/null +++ b/tests/Mock/UpdateCouponSuccess.txt @@ -0,0 +1,26 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 10:05:26 GMT +Content-Type: application/json +Content-Length: 281 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id": "50_OFF", + "object": "coupon", + "amount_off": null, + "created": 1593259732, + "currency": null, + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 10, + "metadata": {}, + "name": "50% Discount", + "percent_off": 50.0, + "redeem_by": 1606460031, + "times_redeemed": 0, + "valid": true +} From d287bd361931291127b6e390c5a8017a1c26bca7 Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Sat, 27 Jun 2020 22:23:55 +0200 Subject: [PATCH 20/23] Add Detach & Fetch source (#184) --- src/Gateway.php | 20 ++++++++- src/Message/AttachSourceRequest.php | 2 +- src/Message/CreateSourceRequest.php | 2 +- src/Message/DetachSourceRequest.php | 34 ++++++++++++++ src/Message/FetchSourceRequest.php | 54 +++++++++++++++++++++++ src/Message/Response.php | 18 ++++++++ tests/Message/DetachSourceRequestTest.php | 42 ++++++++++++++++++ tests/Message/FetchSourceRequestTest.php | 43 ++++++++++++++++++ tests/Mock/DetachSourceFailure.txt | 20 +++++++++ tests/Mock/DetachSourceSuccess.txt | 51 +++++++++++++++++++++ tests/Mock/FetchSourceFailure.txt | 19 ++++++++ tests/Mock/FetchSourceSuccess.txt | 50 +++++++++++++++++++++ 12 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 src/Message/DetachSourceRequest.php create mode 100644 src/Message/FetchSourceRequest.php create mode 100644 tests/Message/DetachSourceRequestTest.php create mode 100644 tests/Message/FetchSourceRequestTest.php create mode 100644 tests/Mock/DetachSourceFailure.txt create mode 100644 tests/Mock/DetachSourceSuccess.txt create mode 100644 tests/Mock/FetchSourceFailure.txt create mode 100644 tests/Mock/FetchSourceSuccess.txt diff --git a/src/Gateway.php b/src/Gateway.php index 8cedf9f7..04f035e6 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -321,13 +321,31 @@ public function createSource(array $parameters = array()) /** * @param array $parameters - * @return \Omnipay\Stripe\Message\CreateSourceRequest + * @return \Omnipay\Stripe\Message\AttachSourceRequest */ public function attachSource(array $parameters = array()) { return $this->createRequest('\Omnipay\Stripe\Message\AttachSourceRequest', $parameters); } + /** + * @param array $parameters + * @return \Omnipay\Stripe\Message\FetchSourceRequest + */ + public function detachSource(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\DetachSourceRequest', $parameters); + } + + /** + * @param array $parameters + * @return \Omnipay\Stripe\Message\FetchSourceRequest + */ + public function fetchSource(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\FetchSourceRequest', $parameters); + } + /** * Create a completePurchase request. * diff --git a/src/Message/AttachSourceRequest.php b/src/Message/AttachSourceRequest.php index d81e4651..48e690bd 100644 --- a/src/Message/AttachSourceRequest.php +++ b/src/Message/AttachSourceRequest.php @@ -7,7 +7,7 @@ /** * Class CreateSourceRequest * - * TODO : Add docblock + * @link https://stripe.com/docs/api/sources/attach */ class AttachSourceRequest extends AbstractRequest { diff --git a/src/Message/CreateSourceRequest.php b/src/Message/CreateSourceRequest.php index 63cb91f1..a10ca829 100644 --- a/src/Message/CreateSourceRequest.php +++ b/src/Message/CreateSourceRequest.php @@ -9,7 +9,7 @@ /** * Class CreateSourceRequest * - * TODO : Add docblock + * @link https://stripe.com/docs/api/sources/create */ class CreateSourceRequest extends AbstractRequest { diff --git a/src/Message/DetachSourceRequest.php b/src/Message/DetachSourceRequest.php new file mode 100644 index 00000000..e9e32ef9 --- /dev/null +++ b/src/Message/DetachSourceRequest.php @@ -0,0 +1,34 @@ +validate('customerReference', 'source'); + + return; + } + + public function getEndpoint() + { + return $this->endpoint.'/customers/'.$this->getCustomerReference().'/sources/'.$this->getSource(); + } + + public function getHttpMethod() + { + return 'DELETE'; + } +} diff --git a/src/Message/FetchSourceRequest.php b/src/Message/FetchSourceRequest.php new file mode 100644 index 00000000..cb35b1ee --- /dev/null +++ b/src/Message/FetchSourceRequest.php @@ -0,0 +1,54 @@ +getParameter('clientSecret'); + } + + /** + * @param string $value + * + * @return FetchSourceRequest + */ + public function setClientSecret($value) + { + return $this->setParameter('clientSecret', $value); + } + + public function getData() + { + $this->validate('source'); + $data = array(); + + if ($clientSecret = $this->getClientSecret()) { + $data['client_secret'] = $clientSecret; + } + + return $data; + } + + public function getEndpoint() + { + return $this->endpoint.'/sources/'.$this->getSource(); + } + + public function getHttpMethod() + { + return 'GET'; + } +} diff --git a/src/Message/Response.php b/src/Message/Response.php index 39e3419d..46d263c0 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -244,6 +244,10 @@ public function getSource() return $this->data['source']; } + if (isset($this->data['object']) && 'source' === $this->data['object']) { + return $this->data; + } + return null; } @@ -379,6 +383,20 @@ public function getPlanId() return null; } + /** + * Get plan id + * + * @return string|null + */ + public function getSourceId() + { + if (isset($this->data['object']) && 'source' === $this->data['object']) { + return $this->data['id']; + } + + return null; + } + /** * Get invoice-item reference * diff --git a/tests/Message/DetachSourceRequestTest.php b/tests/Message/DetachSourceRequestTest.php new file mode 100644 index 00000000..e907698f --- /dev/null +++ b/tests/Message/DetachSourceRequestTest.php @@ -0,0 +1,42 @@ +request = new DetachSourceRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setSource('src_1GyjQZK1civsTrrUGHtiV3AN'); + $this->request->setCustomerReference('cus_HVUs00WcT4j06R'); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/customers/cus_HVUs00WcT4j06R/sources/src_1GyjQZK1civsTrrUGHtiV3AN', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('DetachSourceSuccess.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNotNull($response->getSource()); + $this->assertNull($response->getMessage()); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('DetachSourceFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getSourceId()); + $this->assertSame('No such source: src_1Gyk9dK1civsTrCUNB7v9XoFo', $response->getMessage()); + } +} diff --git a/tests/Message/FetchSourceRequestTest.php b/tests/Message/FetchSourceRequestTest.php new file mode 100644 index 00000000..b21a2798 --- /dev/null +++ b/tests/Message/FetchSourceRequestTest.php @@ -0,0 +1,43 @@ +request = new FetchSourceRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->setSource('src_1GyjQZK1civsTrrUGHtiV3AN'); + $this->request->setClientSecret('src_client_secret_kO8U38RMu0NedTxDoTkOJbTc'); + } + + public function testEndpoint() + { + $this->assertSame('https://api.stripe.com/v1/sources/src_1GyjQZK1civsTrrUGHtiV3AN', $this->request->getEndpoint()); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('FetchSourceSuccess.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertSame('src_1GyjQZK1civsTrrUGHtiV3AN', $response->getSourceId()); + $this->assertNull($response->getMessage()); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('FetchSourceFailure.txt'); + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getSource()); + $this->assertNull($response->getSourceId()); + $this->assertSame('No such source: src_1GyjQZK1civsTrrUGHtiV3ANo', $response->getMessage()); + } +} diff --git a/tests/Mock/DetachSourceFailure.txt b/tests/Mock/DetachSourceFailure.txt new file mode 100644 index 00000000..d270ceeb --- /dev/null +++ b/tests/Mock/DetachSourceFailure.txt @@ -0,0 +1,20 @@ +HTTP/1.1 404 Not Found +Server: nginx +Date: Tue, 26 Feb 2013 16:32:51 GMT +Content-Type: application/json;charset=utf-8 +Content-Length: 131 +Connection: keep-alive +Access-Control-Max-Age: 300 +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such source: src_1Gyk9dK1civsTrCUNB7v9XoFo", + "param": "id", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/DetachSourceSuccess.txt b/tests/Mock/DetachSourceSuccess.txt new file mode 100644 index 00000000..6c2d85e8 --- /dev/null +++ b/tests/Mock/DetachSourceSuccess.txt @@ -0,0 +1,51 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 21:12:02 GMT +Content-Type: application/json +Content-Length: 943 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id": "src_1Gyk9dK1civsTrCUNB7v9XoF", + "object": "source", + "amount": null, + "card": { + "exp_month": 6, + "exp_year": 2021, + "last4": "4242", + "country": "US", + "brand": "Visa", + "funding": "credit", + "fingerprint": "f1JoQaLEG7ovd7N4", + "three_d_secure": "optional", + "name": null, + "address_line1_check": null, + "address_zip_check": null, + "cvc_check": null, + "tokenization_method": null, + "dynamic_last4": null + }, + "client_secret": "src_client_secret_ojSdoIOHML61Ar89cb2sXyuo", + "created": 1593287857, + "currency": null, + "flow": "none", + "livemode": false, + "metadata": { + }, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "statement_descriptor": null, + "status": "consumed", + "type": "card", + "usage": "reusable" +} diff --git a/tests/Mock/FetchSourceFailure.txt b/tests/Mock/FetchSourceFailure.txt new file mode 100644 index 00000000..5371c0fd --- /dev/null +++ b/tests/Mock/FetchSourceFailure.txt @@ -0,0 +1,19 @@ +HTTP/1.1 404 Not Found +Server: nginx +Date: Sat, 27 Jun 2020 21:12:02 GMT +Content-Type: application/json +Content-Length: 241 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Access-Control-Max-Age: 300 +Cache-Control: no-cache, no-store + +{ + "error": { + "code": "resource_missing", + "doc_url": "https://stripe.com/docs/error-codes/resource-missing", + "message": "No such source: src_1GyjQZK1civsTrrUGHtiV3ANo", + "param": "", + "type": "invalid_request_error" + } +} diff --git a/tests/Mock/FetchSourceSuccess.txt b/tests/Mock/FetchSourceSuccess.txt new file mode 100644 index 00000000..e9fd4a1d --- /dev/null +++ b/tests/Mock/FetchSourceSuccess.txt @@ -0,0 +1,50 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 27 Jun 2020 21:12:02 GMT +Content-Type: application/json +Content-Length: 943 +Connection: keep-alive +Access-Control-Allow-Credentials: true +Cache-Control: no-cache, no-store + +{ + "id":"src_1GyjQZK1civsTrrUGHtiV3AN", + "object":"source", + "amount":null, + "card":{ + "exp_month":6, + "exp_year":2021, + "last4":"4242", + "country":"US", + "brand":"Visa", + "funding":"credit", + "fingerprint":"f1JoQaLEG7ovd7N4", + "three_d_secure":"optional", + "name":null, + "address_line1_check":null, + "address_zip_check":null, + "cvc_check":null, + "tokenization_method":null, + "dynamic_last4":null + }, + "client_secret":"src_client_secret_kO8e38RMu0NedTxDoTkOJbTc", + "created":1593285063, + "currency":null, + "flow":"none", + "livemode":false, + "metadata":{}, + "owner":{ + "address":null, + "email":null, + "name":null, + "phone":null, + "verified_address":null, + "verified_email":null, + "verified_name":null, + "verified_phone":null + }, + "statement_descriptor":null, + "status":"chargeable", + "type":"card", + "usage":"reusable" +} From 2c7cc0aa500ce3de1ede97d3678ff5695ba06d5f Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Sun, 28 Jun 2020 10:31:28 +0200 Subject: [PATCH 21/23] Update Create plan request (#185) * Update Create plan request * Remove BC and add deprecated --- src/Message/CreatePlanRequest.php | 305 ++++++++++++++++++++++-- tests/Message/CreatePlanRequestTest.php | 6 +- tests/Mock/CreatePlanSuccess.txt | 14 +- 3 files changed, 304 insertions(+), 21 deletions(-) diff --git a/src/Message/CreatePlanRequest.php b/src/Message/CreatePlanRequest.php index ca67730e..a23f68d5 100644 --- a/src/Message/CreatePlanRequest.php +++ b/src/Message/CreatePlanRequest.php @@ -10,7 +10,7 @@ * Stripe Create Plan Request * * @see \Omnipay\Stripe\Gateway - * @link https://stripe.com/docs/api#create_plan + * @link https://stripe.com/docs/api/plans/create */ class CreatePlanRequest extends AbstractRequest { @@ -79,27 +79,30 @@ public function getIntervalCount() /** * Set the plan name + * @deprecated use setNickname() instead * * @param $value * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest */ public function setName($value) { - return $this->setParameter('name', $value); + return $this->setNickname($value); } /** * Get the plan name + * @deprecated use getNickname() instead * * @return string */ public function getName() { - return $this->getParameter('name'); + return $this->getNickname(); } /** * Set the plan statement descriptor + * @deprecated Not used anymore * * @param $planStatementDescriptor * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest @@ -111,6 +114,7 @@ public function setStatementDescriptor($planStatementDescriptor) /** * Get the plan statement descriptor + * @deprecated Not used anymore * * @return string */ @@ -119,6 +123,192 @@ public function getStatementDescriptor() return $this->getParameter('statement_descriptor'); } + /** + * Set the plan product + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setProduct($value) + { + return $this->setParameter('product', $value); + } + + /** + * Get the plan product + * + * @return string|array + */ + public function getProduct() + { + return $this->getParameter('product'); + } + + /** + * Set the plan amount + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setAmount($value) + { + return $this->setParameter('amount', (integer)$value); + } + + /** + * Get the plan amount + * + * @return int + */ + public function getAmount() + { + return $this->getParameter('amount'); + } + + /** + * Set the plan tiers + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setTiers($value) + { + return $this->setParameter('tiers', $value); + } + + /** + * Get the plan tiers + * + * @return int + */ + public function getTiers() + { + return $this->getParameter('tiers'); + } + + /** + * Set the plan tiers mode + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setTiersMode($value) + { + return $this->setParameter('tiers_mode', $value); + } + + /** + * Get the plan tiers mode + * + * @return int + */ + public function getTiersMode() + { + return $this->getParameter('tiers_mode'); + } + + /** + * Set the plan nickname + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setNickname($value) + { + return $this->setParameter('nickname', $value); + } + + /** + * Get the plan nickname + * + * @return string|array + */ + public function getNickname() + { + return $this->getParameter('nickname'); + } + + /** + * Set the plan metadata + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setMetadata($value) + { + return $this->setParameter('metadata', $value); + } + + /** + * Get the plan metadata + * + * @return string|array + */ + public function getMetadata() + { + return $this->getParameter('metadata'); + } + + /** + * Set the plan active + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setActive($value) + { + return $this->setParameter('active', $value); + } + + /** + * Get the plan active + * + * @return string|array + */ + public function getActive() + { + return $this->getParameter('active'); + } + + /** + * Set the plan billingScheme + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setBillingScheme($value) + { + return $this->setParameter('billing_scheme', $value); + } + + /** + * Get the plan billingScheme + * + * @return string|array + */ + public function getBillingScheme() + { + return $this->getParameter('billing_scheme'); + } + + /** + * Set the plan aggregate usage + * + * @param $planAggregateUsage + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setAggregateUsage($planAggregateUsage) + { + return $this->setParameter('aggregate_usage', $planAggregateUsage); + } + + /** + * Get the plan aggregate usage + * + * @return string + */ + public function getAggregateUsage() + { + return $this->getParameter('aggregate_usage'); + } + /** * Set the plan trial period days * @@ -140,31 +330,114 @@ public function getTrialPeriodDays() return $this->getParameter('trial_period_days'); } + /** + * Set the plan transform usage + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setTransformUsage($value) + { + return $this->setParameter('transform_usage', $value); + } + + /** + * Get the plan transform usage + * + * @return int + */ + public function getTransformUsage() + { + return $this->getParameter('transform_usage'); + } + + /** + * Set the plan usage type + * + * @param $value + * @return \Omnipay\Common\Message\AbstractRequest|CreatePlanRequest + */ + public function setUsageType($value) + { + return $this->setParameter('usage_type', $value); + } + + /** + * Get the plan usage type + * + * @return int + */ + public function getUsageType() + { + return $this->getParameter('usage_type'); + } + public function getData() { - $this->validate('id', 'amount', 'currency', 'interval', 'name'); + $this->validate('currency', 'interval', 'product'); + + if (null == $this->getBillingScheme() || 'per_unit' == $this->getBillingScheme()) { + $this->validate('amount'); + } elseif ('tiered' == $this->getBillingScheme()) { + $this->validate('tiers', 'tiers_mode'); + } $data = array( - 'id' => $this->getId(), - 'amount' => $this->getAmountInteger(), 'currency' => $this->getCurrency(), 'interval' => $this->getInterval(), - 'name' => $this->getName() + 'product' => $this->getProduct() ); - $intervalCount = $this->getIntervalCount(); - if ($intervalCount != null) { - $data['interval_count'] = $intervalCount; + if (null != $this->getBillingScheme()) { + $data['billing_scheme'] = $this->getBillingScheme(); + } + + if (null != $this->getId()) { + $data['id'] = $this->getId(); + } + + if (null != $this->getAmount()) { + $data['amount'] = $this->getAmount(); + } + + if (null != $this->getNickName()) { + $data['nickname'] = $this->getNickName(); + } + + if (null != $this->getMetadata()) { + $data['metadata'] = $this->getMetadata(); + } + + if (null != $this->getActive()) { + $data['active'] = $this->getActive(); + } + + if (null != $this->getIntervalCount()) { + $data['interval_count'] = $this->getIntervalCount(); + } + + if (null != $this->getAggregateUsage()) { + $data['aggregate_usage'] = $this->getAggregateUsage(); + } + + if (null != $this->getTrialPeriodDays()) { + $data['trial_period_days'] = $this->getTrialPeriodDays(); + } + + if (null != $this->getTransformUsage()) { + $data['transform_usage'] = $this->getTransformUsage(); + } + + if (null != $this->getUsageType()) { + $data['usage_type'] = $this->getUsageType(); } - $statementDescriptor = $this->getStatementDescriptor(); - if ($statementDescriptor != null) { - $data['statement_descriptor'] = $statementDescriptor; + if (null != $this->getTiers()) { + $data['tiers'] = $this->getTiers(); } - $trialPeriodDays = $this->getTrialPeriodDays(); - if ($trialPeriodDays != null) { - $data['trial_period_days'] = $trialPeriodDays; + if (null != $this->getTiersMode()) { + $data['tiers_mode'] = $this->getTiersMode(); } return $data; diff --git a/tests/Message/CreatePlanRequestTest.php b/tests/Message/CreatePlanRequestTest.php index 45371f00..f07d1bea 100644 --- a/tests/Message/CreatePlanRequestTest.php +++ b/tests/Message/CreatePlanRequestTest.php @@ -18,9 +18,10 @@ public function setUp() $this->request->setAmount('19.00'); $this->request->setCurrency('usd'); $this->request->setInterval('month'); - $this->request->setName('Amazing Gold Plan'); + $this->request->setNickname('Amazing Gold Plan'); + $this->request->setProduct('prod_GWN5y0jpQeU9yj'); $this->request->setIntervalCount(1); - $this->request->setStatementDescriptor('Omnipay Basic Plan'); + $this->request->setActive(false); $this->request->setTrialPeriodDays(3); } @@ -38,6 +39,7 @@ public function testSendSuccess() $this->assertFalse($response->isRedirect()); $this->assertSame('basic', $response->getPlanId()); $this->assertNotNull($response->getPlan()); + $this->assertFalse($response->getPlan()['active']); $this->assertNull($response->getMessage()); } diff --git a/tests/Mock/CreatePlanSuccess.txt b/tests/Mock/CreatePlanSuccess.txt index ea4df1db..431682f7 100644 --- a/tests/Mock/CreatePlanSuccess.txt +++ b/tests/Mock/CreatePlanSuccess.txt @@ -10,14 +10,22 @@ Cache-Control: no-cache, no-store { "id": "basic", "object": "plan", + "active": false, + "aggregate_usage": null, "amount": 1900, "created": 1455308594, "currency": "usd", + "amount_decimal": "2000", + "billing_scheme": "per_unit", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, - "name": "Amazing Gold Plan", - "statement_descriptor": null, - "trial_period_days": 3 + "nickname": "Amazing Gold Plan", + "product": "prod_GWN5y0jpQeU9yj", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" } From fbf57b33c34f227695e83c1cc92d9844f88ed5e8 Mon Sep 17 00:00:00 2001 From: Antoine Lemaire Date: Tue, 30 Jun 2020 23:21:16 +0200 Subject: [PATCH 22/23] Add missing fetchSubscriptionSchedules in Gateway (#187) --- src/Gateway.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Gateway.php b/src/Gateway.php index 04f035e6..aca7fe60 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -233,6 +233,17 @@ public function cancelSubscription(array $parameters = array()) return $this->createRequest('\Omnipay\Stripe\Message\CancelSubscriptionRequest', $parameters); } + /** + * Fetch Schedule Subscription + * + * @param array $parameters + * @return \Omnipay\Stripe\Message\FetchSubscriptionSchedulesRequest + */ + public function fetchSubscriptionSchedules(array $parameters = array()) + { + return $this->createRequest('\Omnipay\Stripe\Message\FetchSubscriptionSchedulesRequest', $parameters); + } + /** * Fetch Event * From 76e8fd130b8a488b78608aed08cfeab8a5a44fb3 Mon Sep 17 00:00:00 2001 From: Gabriel Moreira Date: Tue, 17 Sep 2019 23:47:12 +1000 Subject: [PATCH 23/23] Fixed application fee request parameter when creating a PaymentIntent. --- src/Message/PaymentIntents/AuthorizeRequest.php | 2 +- tests/Message/PaymentIntents/AuthorizeRequestTest.php | 2 +- tests/Message/PaymentIntents/PurchaseRequestTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Message/PaymentIntents/AuthorizeRequest.php b/src/Message/PaymentIntents/AuthorizeRequest.php index 4a0ec8ad..5a44bad9 100644 --- a/src/Message/PaymentIntents/AuthorizeRequest.php +++ b/src/Message/PaymentIntents/AuthorizeRequest.php @@ -355,7 +355,7 @@ public function getData() } if ($this->getApplicationFee()) { - $data['application_fee'] = $this->getApplicationFeeInteger(); + $data['application_fee_amount'] = $this->getApplicationFeeInteger(); } if ($this->getTransferGroup()) { diff --git a/tests/Message/PaymentIntents/AuthorizeRequestTest.php b/tests/Message/PaymentIntents/AuthorizeRequestTest.php index 5081eb01..28344ad5 100644 --- a/tests/Message/PaymentIntents/AuthorizeRequestTest.php +++ b/tests/Message/PaymentIntents/AuthorizeRequestTest.php @@ -43,7 +43,7 @@ public function testGetData() $this->assertSame('manual', $data['confirmation_method']); $this->assertSame('pm_valid_payment_method', $data['payment_method']); $this->assertSame(array('foo' => 'bar'), $data['metadata']); - $this->assertSame(100, $data['application_fee']); + $this->assertSame(100, $data['application_fee_amount']); $this->assertSame('off_session', $data['setup_future_usage']); $this->assertSame('false', $data['off_session']); } diff --git a/tests/Message/PaymentIntents/PurchaseRequestTest.php b/tests/Message/PaymentIntents/PurchaseRequestTest.php index 09af7fae..2f0dda5d 100644 --- a/tests/Message/PaymentIntents/PurchaseRequestTest.php +++ b/tests/Message/PaymentIntents/PurchaseRequestTest.php @@ -39,7 +39,7 @@ public function testGetData() $this->assertSame('manual', $data['confirmation_method']); $this->assertSame('pm_valid_payment_method', $data['payment_method']); $this->assertSame(array('foo' => 'bar'), $data['metadata']); - $this->assertSame(100, $data['application_fee']); + $this->assertSame(100, $data['application_fee_amount']); } public function testSendSuccessAndRequireConfirmation()