diff --git a/tests/Gateways/Contract/GatewayContractTestCase.php b/tests/Gateways/Contract/GatewayContractTestCase.php new file mode 100644 index 0000000..12a4446 --- /dev/null +++ b/tests/Gateways/Contract/GatewayContractTestCase.php @@ -0,0 +1,128 @@ +http = new TestHttpClient($factory); + $this->gateway = $this->createGateway($this->http, $factory); + } + + abstract protected function createGateway(TestHttpClient $http, Psr17Factory $factory): PaymentGatewayInterface; + + abstract protected function givenCreatePaymentIntent(): PaymentIntent; + + abstract protected function expectedCreatedIntent(): IntentExpectation; + + abstract protected function givenRetrievePaymentIntent(): string; + + abstract protected function expectedRetrievedId(): string; + + abstract protected function expectedRetrievedStatus(): string; + + abstract protected function givenCreateRefund(): string; + + /** + * @return array + */ + abstract protected function refundParams(): array; + + /** + * @param array $refund + */ + abstract protected function assertRefundShape(array $refund): void; + + abstract protected function givenCreateCustomer(): Customer; + + protected function customerApiIsRemote(): bool + { + return false; + } + + protected function expectedRemoteCustomerId(): string + { + return ''; + } + + public function testCreatePaymentIntentReturnsNormalizedIntent(): void + { + $intent = $this->givenCreatePaymentIntent(); + $expected = $this->expectedCreatedIntent(); + + $result = $this->gateway->createPaymentIntent($intent); + + $this->assertSame($expected->id, $result->id); + $this->assertSame($expected->amount, $result->amount); + $this->assertSame($expected->currency, $result->currency); + $this->assertSame($expected->status, $result->status); + } + + public function testCreatePaymentIntentNormalizesCurrencyToUpperCaseIso(): void + { + $intent = $this->givenCreatePaymentIntent(); + + $currency = $this->gateway->createPaymentIntent($intent)->currency; + + $this->assertNotNull($currency); + $this->assertSame(strtoupper($currency), $currency); + $this->assertSame(1, preg_match('/^[A-Z]{3}$/', $currency)); + } + + public function testRetrievePaymentIntentReturnsRequestedIntent(): void + { + $intentId = $this->givenRetrievePaymentIntent(); + + $result = $this->gateway->retrievePaymentIntent($intentId); + + $this->assertSame($this->expectedRetrievedId(), $result->id); + $this->assertSame($this->expectedRetrievedStatus(), $result->status); + } + + public function testCreateRefundReturnsNonEmptyResult(): void + { + $paymentIntentId = $this->givenCreateRefund(); + + $refund = $this->gateway->createRefund($paymentIntentId, $this->refundParams()); + + $this->assertNotEmpty($refund); + $this->assertRefundShape($refund); + } + + public function testCreateCustomerPreservesIdentity(): void + { + $customer = $this->givenCreateCustomer(); + + $result = $this->gateway->createCustomer($customer); + + $this->assertSame($customer->email, $result->email); + $this->assertSame($customer->name, $result->name); + } + + public function testCreateCustomerAssignsRemoteIdWhenSupported(): void + { + if (!$this->customerApiIsRemote()) { + $this->markTestSkipped('Provider has no remote customer API; createCustomer is a local operation.'); + } + + $customer = $this->givenCreateCustomer(); + + $result = $this->gateway->createCustomer($customer); + + $this->assertSame($this->expectedRemoteCustomerId(), $result->id); + } +} diff --git a/tests/Gateways/Contract/IntentExpectation.php b/tests/Gateways/Contract/IntentExpectation.php new file mode 100644 index 0000000..74cf7fa --- /dev/null +++ b/tests/Gateways/Contract/IntentExpectation.php @@ -0,0 +1,16 @@ +queueAccessToken(); + $this->http->queueJsonResponse([ + 'id' => 'ORDER-123', + 'intent' => 'CAPTURE', + 'status' => 'CREATED', + 'purchase_units' => [ + [ + 'amount' => ['currency_code' => 'USD', 'value' => '10.00'], + 'custom_id' => '12345', + ], + ], + 'links' => [ + ['rel' => 'approve', 'href' => 'https://www.paypal.com/checkoutnow?token=ORDER-123'], + ], + ]); + + return new PaymentIntent( + amount: 1000, + currency: 'USD', + metadata: ['order_id' => '12345'], + captureMethod: false, + ); + } + + protected function expectedCreatedIntent(): IntentExpectation + { + return new IntentExpectation('ORDER-123', 1000, 'USD', 'CREATED'); + } + + protected function givenRetrievePaymentIntent(): string + { + $this->queueAccessToken(); + $this->http->queueJsonResponse([ + 'id' => 'ORDER-123', + 'status' => 'COMPLETED', + 'purchase_units' => [ + [ + 'amount' => ['currency_code' => 'USD', 'value' => '10.00'], + ], + ], + ]); + + return 'ORDER-123'; + } + + protected function expectedRetrievedId(): string + { + return 'ORDER-123'; + } + + protected function expectedRetrievedStatus(): string + { + return 'COMPLETED'; + } + + protected function givenCreateRefund(): string + { + $this->queueAccessToken(); + $this->http->queueJsonResponse([ + 'id' => 'RFD-123', + 'status' => 'COMPLETED', + 'amount' => ['value' => '10.00', 'currency_code' => 'USD'], + ]); + + return 'CAPTURE-123'; + } + + protected function refundParams(): array + { + return ['amount' => 1000]; + } + + protected function assertRefundShape(array $refund): void + { + $this->assertArrayHasKey('id', $refund); + $this->assertSame('RFD-123', $refund['id']); + $this->assertSame('COMPLETED', $refund['status']); + $this->assertSame(['value' => '10.00', 'currency_code' => 'USD'], $refund['amount']); + } + + protected function givenCreateCustomer(): Customer + { + return new Customer(email: 'buyer@example.com', name: 'Test Buyer'); + } + + private function queueAccessToken(): void + { + $this->http->queueJsonResponse([ + 'access_token' => 'test_access_token', + 'expires_in' => 3600, + 'token_type' => 'Bearer', + ]); + } +} diff --git a/tests/Gateways/Contract/RobokassaGatewayContractTest.php b/tests/Gateways/Contract/RobokassaGatewayContractTest.php new file mode 100644 index 0000000..48008f7 --- /dev/null +++ b/tests/Gateways/Contract/RobokassaGatewayContractTest.php @@ -0,0 +1,111 @@ +http->queueJsonResponse([ + 'InvoiceID' => 123, + 'InvoiceUrl' => 'https://auth.robokassa.ru/Merchant/Index.aspx?InvoiceID=123', + 'Status' => 'CREATED', + ]); + + return new PaymentIntent( + amount: 2500, + currency: 'RUB', + metadata: ['InvId' => 123, 'Description' => 'Test invoice'], + captureMethod: false, + ); + } + + protected function expectedCreatedIntent(): IntentExpectation + { + return new IntentExpectation('123', 2500, 'RUB', 'CREATED'); + } + + protected function givenRetrievePaymentIntent(): string + { + $xml = << + + + 0 + OK + + + 5 + + + OP-123 + + + XML; + + $this->http->queueRawResponse($xml, 200, ['Content-Type' => ['text/xml']]); + + return '123'; + } + + protected function expectedRetrievedId(): string + { + return '123'; + } + + protected function expectedRetrievedStatus(): string + { + return 'SUCCEEDED'; + } + + protected function givenCreateRefund(): string + { + $this->http->queueJsonResponse([ + 'success' => true, + 'requestId' => 'REQ-123', + 'message' => null, + ]); + + return '123'; + } + + protected function refundParams(): array + { + return ['amount' => 1000, 'op_key' => 'OP-123']; + } + + protected function assertRefundShape(array $refund): void + { + $this->assertArrayHasKey('requestId', $refund); + $this->assertTrue($refund['success']); + $this->assertSame('REQ-123', $refund['requestId']); + } + + protected function givenCreateCustomer(): Customer + { + return new Customer(email: 'buyer@example.com', name: 'Test Buyer'); + } +} diff --git a/tests/Gateways/Contract/StripeGatewayContractTest.php b/tests/Gateways/Contract/StripeGatewayContractTest.php new file mode 100644 index 0000000..4f08440 --- /dev/null +++ b/tests/Gateways/Contract/StripeGatewayContractTest.php @@ -0,0 +1,114 @@ +http->queueJsonResponse([ + 'id' => 'pi_test123', + 'amount' => 1000, + 'currency' => 'usd', + 'status' => 'requires_confirmation', + 'client_secret' => 'pi_test123_secret', + ]); + + return new PaymentIntent(amount: 1000, currency: 'usd'); + } + + protected function expectedCreatedIntent(): IntentExpectation + { + return new IntentExpectation('pi_test123', 1000, 'USD', 'requires_confirmation'); + } + + protected function givenRetrievePaymentIntent(): string + { + $this->http->queueJsonResponse([ + 'id' => 'pi_test123', + 'amount' => 1000, + 'currency' => 'usd', + 'customer' => 'cus_test123', + 'payment_method' => 'pm_test123', + 'description' => 'Test payment', + 'metadata' => [], + 'status' => 'succeeded', + 'created' => 1700000000, + ]); + + return 'pi_test123'; + } + + protected function expectedRetrievedId(): string + { + return 'pi_test123'; + } + + protected function expectedRetrievedStatus(): string + { + return 'succeeded'; + } + + protected function givenCreateRefund(): string + { + $this->http->queueJsonResponse([ + 'id' => 're_test123', + 'amount' => 1000, + 'currency' => 'usd', + 'status' => 'succeeded', + 'payment_intent' => 'pi_test123', + ]); + + return 'pi_test123'; + } + + protected function refundParams(): array + { + return ['amount' => 1000]; + } + + protected function assertRefundShape(array $refund): void + { + $this->assertArrayHasKey('id', $refund); + $this->assertSame('re_test123', $refund['id']); + $this->assertSame(1000, $refund['amount']); + $this->assertSame('usd', $refund['currency']); + $this->assertSame('succeeded', $refund['status']); + } + + protected function givenCreateCustomer(): Customer + { + $this->http->queueJsonResponse([ + 'id' => 'cus_test123', + 'email' => 'buyer@example.com', + 'name' => 'Test Buyer', + ]); + + return new Customer(email: 'buyer@example.com', name: 'Test Buyer'); + } + + protected function customerApiIsRemote(): bool + { + return true; + } + + protected function expectedRemoteCustomerId(): string + { + return 'cus_test123'; + } +} diff --git a/tests/Gateways/Contract/YooKassaGatewayContractTest.php b/tests/Gateways/Contract/YooKassaGatewayContractTest.php new file mode 100644 index 0000000..7b26167 --- /dev/null +++ b/tests/Gateways/Contract/YooKassaGatewayContractTest.php @@ -0,0 +1,107 @@ +http->queueJsonResponse([ + 'id' => '30ae77b9-000f-5001-8000-13e0de458932', + 'status' => 'pending', + 'amount' => ['value' => '100.00', 'currency' => 'RUB'], + 'description' => 'Test payment', + 'payment_method' => ['type' => 'bank_card', 'id' => 'pm_test123'], + 'created_at' => '2025-11-18T12:18:01.563Z', + 'confirmation' => [ + 'type' => 'redirect', + 'confirmation_url' => 'https://yoomoney.ru/checkout/payments/v2/contract?orderId=30ae77b9', + ], + 'metadata' => [], + ]); + + return new PaymentIntent( + amount: 10000, + currency: 'rub', + paymentMethodId: 'bank_card', + metadata: ['return_url' => 'https://example.com/return'], + ); + } + + protected function expectedCreatedIntent(): IntentExpectation + { + return new IntentExpectation('30ae77b9-000f-5001-8000-13e0de458932', 10000, 'RUB', 'pending'); + } + + protected function givenRetrievePaymentIntent(): string + { + $this->http->queueJsonResponse([ + 'id' => '30ae77b9-000f-5001-8000-13e0de458932', + 'status' => 'succeeded', + 'amount' => ['value' => '100.00', 'currency' => 'RUB'], + 'description' => 'Test payment', + 'payment_method' => ['type' => 'bank_card', 'id' => 'pm_test123'], + 'created_at' => '2025-11-19T03:44:43.200Z', + ]); + + return '30ae77b9-000f-5001-8000-13e0de458932'; + } + + protected function expectedRetrievedId(): string + { + return '30ae77b9-000f-5001-8000-13e0de458932'; + } + + protected function expectedRetrievedStatus(): string + { + return 'succeeded'; + } + + protected function givenCreateRefund(): string + { + $this->http->queueJsonResponse([ + 'id' => '30af6093-0015-5001-8000-196e1cbaceef', + 'payment_id' => '30af50eb-000f-5001-8000-1533ca71a452', + 'status' => 'succeeded', + 'created_at' => '2025-11-19T04:51:31.067Z', + 'amount' => ['value' => '100.00', 'currency' => 'RUB'], + ]); + + return '30af50eb-000f-5001-8000-1533ca71a452'; + } + + protected function refundParams(): array + { + return ['amount' => 10000, 'currency' => 'rub']; + } + + protected function assertRefundShape(array $refund): void + { + $this->assertArrayHasKey('id', $refund); + $this->assertSame('30af6093-0015-5001-8000-196e1cbaceef', $refund['id']); + $this->assertSame('30af50eb-000f-5001-8000-1533ca71a452', $refund['payment_id']); + $this->assertSame(10000, $refund['amount']); + $this->assertSame('rub', $refund['currency']); + $this->assertSame('succeeded', $refund['status']); + } + + protected function givenCreateCustomer(): Customer + { + return new Customer(email: 'buyer@example.com', name: 'Test Buyer'); + } +}