diff --git a/src/Pay/Adapter.php b/src/Pay/Adapter.php index f9685dc..6679782 100644 --- a/src/Pay/Adapter.php +++ b/src/Pay/Adapter.php @@ -72,14 +72,26 @@ public function getCurrency(): string /** * Make a purchase request * - * @param int $amount - * @param string $customerId - * @param string|null $paymentMethodId - * @param array $additionalParams - * @return array + * @param int $amount Amount to charge + * @param string $customerId Customer ID + * @param string|null $paymentMethodId Payment method ID (optional) + * @param array $additionalParams Additional parameters (optional) + * @return array Result of the purchase */ abstract public function purchase(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array; + /** + * Update a payment intent + * + * @param string $paymentId Payment intent ID + * @param string|null $paymentMethodId Payment method ID (optional) + * @param int|null $amount Amount to update (optional) + * @param string|null $currency Currency to update (optional) + * @param array $additionalParams Additional parameters (optional) + * @return array Result of the update + */ + abstract public function updatePayment(string $paymentId, ?string $paymentMethodId = null, ?int $amount = null, string $currency = null, array $additionalParams = []): array; + /** * Retry a purchase for a payment intent * diff --git a/src/Pay/Adapter/Stripe.php b/src/Pay/Adapter/Stripe.php index c42d936..3ad3e91 100644 --- a/src/Pay/Adapter/Stripe.php +++ b/src/Pay/Adapter/Stripe.php @@ -102,6 +102,36 @@ public function getPayment(string $paymentId): array return $this->execute(self::METHOD_GET, $path); } + /** + * Update a payment intent + * + * @param string $paymentId Payment intent ID + * @param string|null $paymentMethodId Payment method ID (optional) + * @param int|null $amount Amount to update (optional) + * @param string|null $currency Currency to update (optional) + * @param array $additionalParams Additional parameters (optional) + * @return array Result of the update + */ + public function updatePayment(string $paymentId, ?string $paymentMethodId = null, ?int $amount = null, string $currency = null, array $additionalParams = []): array + { + $path = '/payment_intents/'.$paymentId; + $requestBody = []; + if ($paymentMethodId != null) { + $requestBody['payment_method'] = $paymentMethodId; + } + if ($amount != null) { + $requestBody['amount'] = $amount; + } + + if ($currency != null) { + $requestBody['currency'] = $currency; + } + + $requestBody = array_merge($requestBody, $additionalParams); + + return $this->execute(self::METHOD_POST, $path, $requestBody); + } + /** * Add a credit card for customer */ diff --git a/src/Pay/Pay.php b/src/Pay/Pay.php index 333b072..a6fedac 100644 --- a/src/Pay/Pay.php +++ b/src/Pay/Pay.php @@ -122,6 +122,21 @@ public function getPayment(string $paymentId): array return $this->adapter->getPayment($paymentId); } + /** + * Update a payment intent + * + * @param string $paymentId Payment intent ID + * @param string|null $paymentMethodId Payment method ID (optional) + * @param int|null $amount Amount to update (optional) + * @param string|null $currency Currency to update (optional) + * @param array $additionalParams Additional parameters (optional) + * @return array Result of the update + */ + public function updatePayment(string $paymentId, ?string $paymentMethodId = null, ?int $amount = null, string $currency = null, array $additionalParams = []): array + { + return $this->adapter->updatePayment($paymentId, $paymentMethodId, $amount, $currency, $additionalParams); + } + /** * Delete Payment Method * diff --git a/tests/Pay/Adapter/StripeTest.php b/tests/Pay/Adapter/StripeTest.php index 9cfb2df..843ab0b 100644 --- a/tests/Pay/Adapter/StripeTest.php +++ b/tests/Pay/Adapter/StripeTest.php @@ -366,6 +366,60 @@ public function testGetPayment(array $data): array return $data; } + /** + * Test updatePayment: create a payment intent in a non-succeeded state, update its payment method and amount, and assert the update. + * + * @depends testCreateCustomer + * + * @param array $data + * @return void + */ + public function testUpdatePayment(array $data): void + { + $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 { + $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; + $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', + 'exp_month' => 8, + 'exp_year' => 2030, + 'cvc' => 123, + ]); + $this->assertNotEmpty($succeedingPm['id']); + $succeedingPmId = $succeedingPm['id']; + + // Update the payment intent with the new payment method and amount + $newAmount = 6000; + $updated = $this->stripe->updatePayment((string) $paymentIntentId, $succeedingPmId, $newAmount); + $this->assertNotEmpty($updated['id']); + $this->assertEquals($paymentIntentId, $updated['id']); + $this->assertEquals('payment_intent', $updated['object']); + $this->assertEquals($newAmount, $updated['amount']); + $this->assertEquals($succeedingPmId, $updated['payment_method']); + } + /** * @depends testPurchase *