From 09b209ee7b0c25b40a190d0951655b5c86a1614c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 3 Aug 2025 02:33:55 +0000 Subject: [PATCH 1/4] retry purchase to confirm the intent again --- src/Pay/Adapter.php | 10 ++++++++++ src/Pay/Adapter/Stripe.php | 24 ++++++++++++++++++++++++ src/Pay/Pay.php | 13 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/Pay/Adapter.php b/src/Pay/Adapter.php index e8cf196..f9685dc 100644 --- a/src/Pay/Adapter.php +++ b/src/Pay/Adapter.php @@ -80,6 +80,16 @@ public function getCurrency(): string */ abstract public function purchase(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array; + /** + * Retry a purchase for a payment intent + * + * @param string $paymentId The payment intent ID to retry + * @param string|null $paymentMethodId The payment method to use (optional) + * @param array $additionalParams Additional parameters for the retry (optional) + * @return array The result of the retry attempt + */ + abstract public function retryPurchase(string $paymentId, ?string $paymentMethodId = null, array $additionalParams = []): array; + /** * Refund payment * diff --git a/src/Pay/Adapter/Stripe.php b/src/Pay/Adapter/Stripe.php index 4930e7e..c42d936 100644 --- a/src/Pay/Adapter/Stripe.php +++ b/src/Pay/Adapter/Stripe.php @@ -47,6 +47,30 @@ public function purchase(int $amount, string $customerId, ?string $paymentMethod return $result; } + /** + * Retry a purchase for a payment intent + * + * @param string $paymentId The payment intent ID to retry + * @param string|null $paymentMethodId The payment method to use (optional) + * @param array $additionalParams Additional parameters for the retry (optional) + * @return array The result of the retry attempt + */ + public function retryPurchase(string $paymentId, ?string $paymentMethodId = null, array $additionalParams = []): array + { + $path = '/payment_intents/'.$paymentId.'/confirm'; + $requestBody = []; + if (! empty($paymentMethodId)) { + $requestBody = [ + 'payment_method' => $paymentMethodId, + ]; + } + + $requestBody = array_merge($requestBody, $additionalParams); + $result = $this->execute(self::METHOD_POST, $path, $requestBody); + + return $result; + } + /** * Refund payment */ diff --git a/src/Pay/Pay.php b/src/Pay/Pay.php index 29ce45a..333b072 100644 --- a/src/Pay/Pay.php +++ b/src/Pay/Pay.php @@ -86,6 +86,19 @@ public function purchase(int $amount, string $customerId, string $paymentMethodI return $this->adapter->purchase($amount, $customerId, $paymentMethodId, $additionalParams); } + /** + * Retry a purchase for a payment intent + * + * @param string $paymentId The payment intent ID to retry + * @param string|null $paymentMethodId The payment method to use (optional) + * @param array $additionalParams Additional parameters for the retry (optional) + * @return array The result of the retry attempt + */ + public function retryPurchase(string $paymentId, ?string $paymentMethodId = null, array $additionalParams = []): array + { + return $this->adapter->retryPurchase($paymentId, $paymentMethodId, $additionalParams); + } + /** * Refund Payment * From 7244ecb9ea19a9a5aa1f4f4b8b2c874e448a175e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 3 Aug 2025 02:35:05 +0000 Subject: [PATCH 2/4] test for retry purchase --- tests/Pay/Adapter/StripeTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Pay/Adapter/StripeTest.php b/tests/Pay/Adapter/StripeTest.php index edb6bf8..f185142 100644 --- a/tests/Pay/Adapter/StripeTest.php +++ b/tests/Pay/Adapter/StripeTest.php @@ -292,6 +292,27 @@ public function testPurchase(array $data): array return $data; } + /** + * @depends testPurchase + * + * @param array $data + * @return array + */ + public function testRetryPurchase(array $data): array + { + $paymentId = $data['paymentId']; + $paymentMethodId = $data['paymentMethodId']; + // Attempt to retry the purchase + $result = $this->stripe->retryPurchase($paymentId, $paymentMethodId); + $this->assertNotEmpty($result['id']); + $this->assertEquals($paymentId, $result['id']); + $this->assertEquals('payment_intent', $result['object']); + // Status may vary depending on Stripe's state, but should be present + $this->assertArrayHasKey('status', $result); + + return $data; + } + /** * @depends testPurchase */ From b936e5982222ffb2c5be4887b8a067092a9e36ea Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 3 Aug 2025 02:57:55 +0000 Subject: [PATCH 3/4] fix test --- tests/Pay/Adapter/StripeTest.php | 56 ++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/tests/Pay/Adapter/StripeTest.php b/tests/Pay/Adapter/StripeTest.php index f185142..fb8e196 100644 --- a/tests/Pay/Adapter/StripeTest.php +++ b/tests/Pay/Adapter/StripeTest.php @@ -293,22 +293,64 @@ public function testPurchase(array $data): array } /** - * @depends testPurchase + * Test retryPurchase: create a payment with a failing payment method, then retry with a succeeding one. + * + * @depends testCreateCustomer * * @param array $data * @return array */ public function testRetryPurchase(array $data): array { - $paymentId = $data['paymentId']; - $paymentMethodId = $data['paymentMethodId']; - // Attempt to retry the purchase - $result = $this->stripe->retryPurchase($paymentId, $paymentMethodId); + $customerId = $data['customerId']; + // Create a payment method that will fail (card_declined) + $failingPm = $this->stripe->createPaymentMethod($customerId, 'card', [ + 'number' => '4000000000000341', + 'exp_month' => 8, + 'exp_year' => 2030, + 'cvc' => 123, + ]); + $this->assertNotEmpty($failingPm['id']); + $failingPmId = $failingPm['id']; + + // Create a payment intent with the failing payment method + $paymentIntentId = null; + try { + $purchase = $this->stripe->purchase(5000, $customerId, $failingPmId); + $this->fail('Expected payment to fail'); + } catch (Exception $e) { + $this->assertEquals(Exception::GENERIC_DECLINE, $e->getType()); + $this->assertEquals(402, $e->getCode()); + $paymentIntentMeta = $e->getMetadata()['payment_intent'] ?? null; + if (is_array($paymentIntentMeta) && isset($paymentIntentMeta['id'])) { + $paymentIntentId = $paymentIntentMeta['id']; + } else { + $paymentIntentId = $paymentIntentMeta; + } + $this->assertNotEmpty($paymentIntentId); + } + + // Create a succeeding payment method + $succeedingPm = $this->stripe->createPaymentMethod($customerId, 'card', [ + 'number' => '4242424242424242', // Stripe test card: always succeeds + 'exp_month' => 8, + 'exp_year' => 2030, + 'cvc' => 123, + ]); + $this->assertNotEmpty($succeedingPm['id']); + $succeedingPmId = $succeedingPm['id']; + + // Retry the payment intent with the succeeding payment method + $result = $this->stripe->retryPurchase((string) $paymentIntentId, $succeedingPmId); $this->assertNotEmpty($result['id']); - $this->assertEquals($paymentId, $result['id']); + $this->assertEquals($paymentIntentId, $result['id']); $this->assertEquals('payment_intent', $result['object']); - // Status may vary depending on Stripe's state, but should be present $this->assertArrayHasKey('status', $result); + $this->assertEquals('succeeded', $result['status']); + + // Save for further tests if needed + $data['paymentId'] = $paymentIntentId; + $data['paymentMethodId'] = $succeedingPmId; return $data; } From 0365399b13db643948672c2d4fc2ca23f26928ec Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 3 Aug 2025 03:34:59 +0000 Subject: [PATCH 4/4] fix suggestion --- tests/Pay/Adapter/StripeTest.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/Pay/Adapter/StripeTest.php b/tests/Pay/Adapter/StripeTest.php index fb8e196..9cfb2df 100644 --- a/tests/Pay/Adapter/StripeTest.php +++ b/tests/Pay/Adapter/StripeTest.php @@ -316,27 +316,23 @@ public function testRetryPurchase(array $data): array // Create a payment intent with the failing payment method $paymentIntentId = null; try { - $purchase = $this->stripe->purchase(5000, $customerId, $failingPmId); + $this->stripe->purchase(5000, $customerId, $failingPmId); $this->fail('Expected payment to fail'); } catch (Exception $e) { $this->assertEquals(Exception::GENERIC_DECLINE, $e->getType()); $this->assertEquals(402, $e->getCode()); $paymentIntentMeta = $e->getMetadata()['payment_intent'] ?? null; - if (is_array($paymentIntentMeta) && isset($paymentIntentMeta['id'])) { - $paymentIntentId = $paymentIntentMeta['id']; - } else { - $paymentIntentId = $paymentIntentMeta; - } + $paymentIntentId = is_array($paymentIntentMeta) && isset($paymentIntentMeta['id']) ? $paymentIntentMeta['id'] : $paymentIntentMeta; $this->assertNotEmpty($paymentIntentId); } - // Create a succeeding payment method - $succeedingPm = $this->stripe->createPaymentMethod($customerId, 'card', [ - 'number' => '4242424242424242', // Stripe test card: always succeeds - 'exp_month' => 8, - 'exp_year' => 2030, - 'cvc' => 123, - ]); + // Create a succeeding payment method + $succeedingPm = $this->stripe->createPaymentMethod($customerId, 'card', [ + 'number' => '4242424242424242', // Stripe test card: always succeeds + 'exp_month' => 8, + 'exp_year' => 2030, + 'cvc' => 123, + ]); $this->assertNotEmpty($succeedingPm['id']); $succeedingPmId = $succeedingPm['id'];