Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions tests/Gateways/Contract/GatewayContractTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Payments\Tests\Gateways\Contract;

use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\TestCase;
use Yiisoft\Payments\Models\Customer;
use Yiisoft\Payments\Models\PaymentIntent;
use Yiisoft\Payments\PaymentGatewayInterface;
use Yiisoft\Payments\Tests\Support\TestHttpClient;

abstract class GatewayContractTestCase extends TestCase
{
protected TestHttpClient $http;
private PaymentGatewayInterface $gateway;

protected function setUp(): void
{
$factory = new Psr17Factory();
$this->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<string, mixed>
*/
abstract protected function refundParams(): array;

/**
* @param array<string, mixed> $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);
}
}
16 changes: 16 additions & 0 deletions tests/Gateways/Contract/IntentExpectation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Payments\Tests\Gateways\Contract;

final readonly class IntentExpectation
{
public function __construct(
public string $id,
public int $amount,
public string $currency,
public string $status,
) {
}
}
123 changes: 123 additions & 0 deletions tests/Gateways/Contract/PayPalGatewayContractTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Payments\Tests\Gateways\Contract;

use Nyholm\Psr7\Factory\Psr17Factory;
use Yiisoft\Payments\Gateways\PayPalGateway;
use Yiisoft\Payments\Models\Customer;
use Yiisoft\Payments\Models\PaymentIntent;
use Yiisoft\Payments\PaymentGatewayInterface;
use Yiisoft\Payments\Tests\Support\TestHttpClient;

final class PayPalGatewayContractTest extends GatewayContractTestCase
{
protected function createGateway(TestHttpClient $http, Psr17Factory $factory): PaymentGatewayInterface
{
return new PayPalGateway(
clientId: 'test_client_id',
clientSecret: 'test_client_secret',
sandbox: true,
httpClient: $http,
requestFactory: $factory,
streamFactory: $factory,
);
}

protected function givenCreatePaymentIntent(): PaymentIntent
{
$this->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',
]);
}
}
111 changes: 111 additions & 0 deletions tests/Gateways/Contract/RobokassaGatewayContractTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Payments\Tests\Gateways\Contract;

use Nyholm\Psr7\Factory\Psr17Factory;
use Yiisoft\Payments\Gateways\RobokassaGateway;
use Yiisoft\Payments\Models\Customer;
use Yiisoft\Payments\Models\PaymentIntent;
use Yiisoft\Payments\PaymentGatewayInterface;
use Yiisoft\Payments\Tests\Support\TestHttpClient;

final class RobokassaGatewayContractTest extends GatewayContractTestCase
{
protected function createGateway(TestHttpClient $http, Psr17Factory $factory): PaymentGatewayInterface
{
return new RobokassaGateway(
merchantLogin: 'demo',
password1: 'pass1',
password2: 'pass2',
password3: 'pass3',
testMode: true,
httpClient: $http,
requestFactory: $factory,
streamFactory: $factory,
);
}

protected function givenCreatePaymentIntent(): PaymentIntent
{
$this->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 = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<OperationStateResponse>
<Result>
<Code>0</Code>
<Description>OK</Description>
</Result>
<State>
<Code>5</Code>
</State>
<Info>
<OpKey>OP-123</OpKey>
</Info>
</OperationStateResponse>
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');
}
}
Loading
Loading