A modern PHP 8.1+ library providing a unified interface for multiple payment gateways, with support for Stripe, PayPal (REST API v2), Robokassa and YooKassa.
- PHP 8.1 or higher.
The package could be installed with Composer:
composer require yiisoft/payments%%{init: {"theme":"base","themeVariables": {
"background":"transparent",
"fontFamily":"ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif",
"primaryColor":"#0f172a",
"primaryTextColor":"#e2e8f0",
"primaryBorderColor":"#94a3b8",
"lineColor":"#94a3b8",
"secondaryColor":"#052e16",
"tertiaryColor":"#1e293b",
"clusterBkg":"#0b1220",
"clusterBorder":"#334155"
}}}%%
graph TD
A[Application] -->|Uses| B[PaymentGatewayInterface]
B -->|Implemented by| C[StripeGateway]
B -->|Implemented by| D[PayPalGateway]
B -->|Implemented by| E[RobokassaGateway]
B -->|Implemented by| F[YooKassaGateway]
B -->|Can be extended to| G[CustomGateway]
subgraph "Core Components"
H[Customer] -->|Used by| B
I[PaymentIntent] -->|Used by| B
J[PaymentMethod] -->|Used by| B
K[PaymentException] -->|Thrown by| B
end
subgraph "Gateway Implementations"
C -->|Uses| L[Stripe API]
D -->|Uses| M[PayPal REST API v2]
E -->|Uses| N[Robokassa API]
F -->|Uses| O[YooKassa API]
end
%% High-contrast styling that stays readable on GitHub dark theme
classDef app fill:#111827,stroke:#cbd5e1,color:#f8fafc;
classDef iface fill:#1e293b,stroke:#38bdf8,color:#f8fafc;
classDef gateway fill:#0f172a,stroke:#60a5fa,color:#e2e8f0;
classDef model fill:#0b1220,stroke:#94a3b8,color:#e2e8f0;
classDef ex fill:#3b0a0a,stroke:#fb7185,color:#ffe4e6;
classDef api fill:#052e16,stroke:#34d399,color:#ecfdf5;
class A app;
class B iface;
class C,D,E,F,G gateway;
class H,I,J model;
class K ex;
class L,M,N,O api;
The library provides a unified interface for multiple payment gateways, with each gateway implementing the PaymentGatewayInterface. The main components are:
- PaymentGatewayInterface: Defines the common API for all payment gateways
- AbstractGateway: Base class with shared functionality
- Gateway-specific implementations:
StripeGateway,PayPalGateway,RobokassaGateway,YooKassaGateway - Data Models:
Customer,PaymentIntent,PaymentMethodfor type-safe operations
- Unified API - Single interface for multiple payment providers
- Type Safety - Strictly typed models and responses
- PSR Standards - Follows PSR-4, PSR-7, PSR-17, and PSR-18
- Extensible - Easy to add new payment gateways
- Modern PHP - Requires PHP 8.1+ with strict types and readonly properties
Represents a customer in the payment system. Contains:
id: Unique identifier in the payment systememail: Customer's email addressname: Customer's full namemetadata: Additional custom data
$customer = new Customer(
id: 'cus_123', // null for new customers
email: 'customer@example.com',
name: 'John Doe',
metadata: ['user_id' => 42]
);Represents how a customer will pay (credit card, PayPal, etc.). Contains:
id: Unique identifiertype: Payment method type (e.g., 'card', 'paypal')details: Payment method specific data (last4, brand, etc.)customerId: Reference to the customerbillingDetails: Billing details (name, email, address, etc.)
use Yiisoft\Payments\Models\PaymentMethod;
use Yiisoft\Payments\Models\PaymentMethodType;
$paymentMethod = new PaymentMethod(
id: 'pm_123',
type: PaymentMethodType::CARD,
details: [
'last4' => '4242',
'brand' => 'visa',
'exp_month' => 12,
'exp_year' => 2025,
],
customerId: 'cus_123',
billingDetails: [
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'address' => [
'line1' => '123 Main St',
'city' => 'San Francisco',
'state' => 'CA',
'postal_code' => '94105',
'country' => 'US',
],
],
);
// Available payment method types:
// - PaymentMethodType::CARD
// - PaymentMethodType::PAYPAL
// - PaymentMethodType::SEPA_DEBIT
// Check if a payment method type is valid
$isValid = PaymentMethodType::isValid('card'); // true
// Get all available payment method types
$allTypes = PaymentMethodType::all();Represents a single payment transaction. Contains:
id: Unique identifieramount: Amount in smallest currency unit (e.g., cents)currency: 3-letter ISO currency codestatus: Current status (e.g., 'requires_payment_method', 'succeeded')customerId: Reference to the customerpaymentMethodId: Reference to the payment methodmetadata: Additional custom data
$intent = new PaymentIntent(
id: 'pi_123', // null for new intents
amount: 1000, // $10.00
currency: 'usd',
status: 'requires_payment_method',
customerId: 'cus_123',
paymentMethodId: 'pm_123',
metadata: ['order_id' => 'abc123']
);$gateway = new StripeGateway(
apiKey: 'your_stripe_key',
httpClient: $httpClient,
requestFactory: $requestFactory,
streamFactory: $streamFactory
);Each gateway has a small endpoints value object that allows overriding vendor base URLs (useful for stubs, proxies or alternative environments).
use Yiisoft\Payments\Endpoints\StripeEndpoints;
use Yiisoft\Payments\Endpoints\PayPalEndpoints;
use Yiisoft\Payments\Endpoints\RobokassaEndpoints;
use Yiisoft\Payments\Endpoints\YooKassaEndpoints;
$stripe = new StripeGateway(
apiKey: 'your_stripe_key',
httpClient: $httpClient,
requestFactory: $requestFactory,
streamFactory: $streamFactory,
endpoints: new StripeEndpoints(baseUri: 'https://proxy.example/stripe/v1'),
);
$paypal = new PayPalGateway(
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
sandbox: true,
httpClient: $httpClient,
requestFactory: $requestFactory,
streamFactory: $streamFactory,
endpoints: new PayPalEndpoints(
sandboxBaseUri: 'https://api-m.sandbox.paypal.com',
liveBaseUri: 'https://api-m.paypal.com',
),
);
$robokassa = new RobokassaGateway(
merchantLogin: 'demo',
password1: 'pass1',
password2: 'pass2',
password3: 'pass3',
testMode: true,
httpClient: $httpClient,
requestFactory: $requestFactory,
streamFactory: $streamFactory,
endpoints: new RobokassaEndpoints(
invoiceApiBaseUri: 'https://services.robokassa.ru/InvoiceServiceWebApi/api',
refundApiBaseUri: 'https://services.robokassa.ru/RefundService/Refund',
xmlApiBaseUri: 'https://auth.robokassa.ru/Merchant/WebService/Service.asmx',
),
);
$yookassa = new YooKassaGateway(
shopId: 'your_shop_id',
secretKey: 'your_secret_key',
httpClient: $httpClient,
requestFactory: $requestFactory,
streamFactory: $streamFactory,
endpoints: new YooKassaEndpoints(baseUri: 'https://api.yookassa.ru/v3'),
);// Create new customer
$customer = $gateway->createCustomer(new Customer(
email: 'customer@example.com',
name: 'John Doe'
));
// Or retrieve existing customer
$customer = $gateway->retrieveCustomer('cus_existing123');// Example using Stripe.js
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement)
});
// Send paymentMethod.id to your server// If your frontend already created a payment method (e.g. via Stripe.js),
// you can simply attach it to the customer:
$paymentMethod = $gateway->attachPaymentMethod(
$_POST['payment_method_id'],
$customer->id,
);$intent = $gateway->createPaymentIntent(new PaymentIntent(
amount: 1000, // $10.00
currency: 'usd',
customerId: $customer->id,
paymentMethodId: $paymentMethod->id,
metadata: ['order_id' => 'abc123']
));const { error, paymentIntent } = await stripe.confirmCardPayment(
'{{ $intent->clientSecret }}',
{
payment_method: '{{ $paymentMethod->id }}',
receipt_email: 'customer@example.com',
}
);
if (error) {
// Handle error
} else if (paymentIntent.status === 'succeeded') {
// Payment succeeded!
}This library does not include webhook signature verification or event parsing. Handle webhooks in your application using the provider's official documentation/SDK.
Payment Intents can have these statuses:
requires_payment_method: Customer needs to add a payment methodrequires_confirmation: Payment needs to be confirmedrequires_action: Customer needs to complete additional actions (3D Secure, etc.)processing: Payment is being processedrequires_capture: Payment is authorized and needs to be capturedcanceled: Payment was canceledsucceeded: Payment was successful
$refund = $gateway->createRefund('pi_123', [
'amount' => 1000, // Optional: partial refund
'reason' => 'requested_by_customer'
]);Always wrap payment operations in try-catch blocks:
try {
$intent = $gateway->createPaymentIntent($paymentIntent);
} catch (PaymentException $e) {
// Handle specific error types
switch ($e->errorCode) {
case 'card_declined':
// Handle card decline
break;
case 'insufficient_funds':
// Handle insufficient funds
break;
default:
// Handle other errors
}
}The library relies on PSR-18 (HTTP client) and PSR-17 (request/stream factories).
In the examples below we use symfony/http-client and nyholm/psr7, but you can use any compatible implementations.
use Yiisoft\Payments\Gateways\StripeGateway;
use Symfony\Component\HttpClient\Psr18Client;
use Nyholm\Psr7\Factory\Psr17Factory;
$httpClient = new Psr18Client(); // Any PSR-18 client will work
$psr17Factory = new Psr17Factory(); // PSR-17 factories (request + stream)
$stripe = new StripeGateway(
apiKey: 'YOUR_STRIPE_SECRET_KEY',
httpClient: $httpClient,
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);use Yiisoft\Payments\Gateways\PayPalGateway;
// Reuse $httpClient and $psr17Factory from the Stripe example above (or provide your own PSR-18/PSR-17 implementations).
$paypal = new PayPalGateway(
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
sandbox: true,
httpClient: $httpClient,
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);use Yiisoft\Payments\Gateways\RobokassaGateway;
// Reuse $httpClient and $psr17Factory from the Stripe example above (or provide your own PSR-18/PSR-17 implementations).
$robokassa = new RobokassaGateway(
merchantLogin: 'YOUR_MERCHANT_LOGIN',
password1: 'YOUR_PASSWORD_1', // Invoice API (JWT signing)
password2: 'YOUR_PASSWORD_2', // XML status API (OpStateExt)
password3: 'YOUR_PASSWORD_3', // Refund API v2 (JWT signing). Set to null if you don't need refunds.
testMode: true,
httpClient: $httpClient,
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);use Yiisoft\Payments\Models\Customer;
// Create a customer
$customer = $gateway->createCustomer(new Customer(
email: 'customer@example.com',
name: 'John Doe',
metadata: ['user_id' => 42],
));
// Retrieve a customer
$customer = $gateway->retrieveCustomer($customer->id);
// Update a customer (models are readonly, create a new instance)
$customer = $gateway->updateCustomer(new Customer(
id: $customer->id,
email: 'new.email@example.com',
name: $customer->name,
phone: $customer->phone,
address: $customer->address,
metadata: $customer->metadata,
description: $customer->description,
));
// Delete a customer
$gateway->deleteCustomer($customer->id);use Yiisoft\Payments\Models\PaymentMethod;
use Yiisoft\Payments\Models\PaymentMethodType;
// Note: payment method payload is gateway-specific.
// For card payments you should avoid handling raw card data on your server.
// Use provider tokenization (e.g. Stripe.js) whenever possible.
$paymentMethod = $gateway->createPaymentMethod(new PaymentMethod(
type: PaymentMethodType::CARD,
details: [
// Example (Stripe): pass a token created on the client side.
// The gateway will send it under the "card" key because type === "card".
'token' => 'tok_visa',
],
customerId: $customer->id,
));
$paymentMethod = $gateway->attachPaymentMethod($paymentMethod->id, $customer->id);use Yiisoft\Payments\Models\PaymentIntent;
// Create a payment intent / order / invoice (gateway-specific)
$intent = $gateway->createPaymentIntent(new PaymentIntent(
amount: 1000, // in the smallest currency unit (e.g. cents)
currency: 'USD',
customerId: $customer->id,
paymentMethodId: $paymentMethod->id,
description: 'Order #123',
metadata: ['order_id' => '123'],
));
// Some gateways (PayPal, Robokassa) require a customer approval step via redirect URL:
$redirectUrl = $intent->nextAction['redirect_to_url']['url'] ?? null;
// Capture the payment (only for gateways/flows that support delayed capture)
if ($intent->status === PaymentIntent::STATUS_REQUIRES_CAPTURE) {
$intent = $gateway->capturePaymentIntent($intent->id);
}
// Refund
$refund = $gateway->createRefund($intent->id, [
'amount' => 1000, // optional partial refund
'reason' => 'requested_by_customer',
]);- Customers
- Payment Methods (create + attach)
- Payment Intents (create / retrieve / confirm / capture / cancel)
- Refunds
Webhook verification/event parsing is intentionally out of scope for this library. Implement it in your application using Stripe docs/SDK.
- Payment Intents are mapped to PayPal Orders (
/v2/checkout/orders) createPaymentIntent()creates an order and may return an approval URL inPaymentIntent::$nextAction['redirect_to_url']['url']capturePaymentIntent()captures an order (/v2/checkout/orders/{id}/capture)createRefund()refunds a capture (/v2/payments/captures/{capture_id}/refund)
PayPal does not expose generic Customer/PaymentMethod resources compatible with the library's models, so
Customer/PaymentMethodoperations are treated as lightweight placeholders (no persistent “vault” is created).
- Payment Intents are mapped to Robokassa invoices (Invoice API JWT)
createPaymentIntent()creates an invoice and returns a redirect URL inPaymentIntent::$nextAction['redirect_to_url']['url']retrievePaymentIntent()checks invoice status via OpStateExtcreateRefund()performs refund via Refund API v2 (JWT)
Robokassa customer/payment-method concepts differ from card processors, so
Customer/PaymentMethodoperations are implemented as placeholders for interface compatibility.
To add a new payment gateway, create a class that implements PaymentGatewayInterface.
For convenience you can extend Yiisoft\Payments\Gateways\AbstractGateway, which provides:
- JSON request/response handling (PSR-18 + PSR-17)
- basic error-to-exception mapping (
PaymentException,InvalidRequestException) - a helper to build requests:
createRequest() - a helper to send and decode responses:
sendRequest()
Example (minimal skeleton):
<?php
declare(strict_types=1);
namespace App\Payment\Gateways;
use Yiisoft\Payments\Gateways\AbstractGateway;
use Yiisoft\Payments\Models\Customer;
use Yiisoft\Payments\Models\PaymentIntent;
use Yiisoft\Payments\Models\PaymentMethod;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
final class AcmePayGateway extends AbstractGateway
{
public function __construct(
private string $apiKey,
ClientInterface $httpClient,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
) {
parent::__construct($httpClient, $requestFactory, $streamFactory);
}
protected function getBaseUri(): string
{
return 'https://api.acmepay.com/v1';
}
public function createCustomer(Customer $customer): Customer
{
$response = $this->sendRequest(
$this->createRequest('POST', '/customers', [
'email' => $customer->email,
'name' => $customer->name,
])
);
return Customer::fromArray($response);
}
public function createPaymentIntent(PaymentIntent $intent): PaymentIntent
{
$response = $this->sendRequest(
$this->createRequest('POST', '/payment_intents', [
'amount' => $intent->amount,
'currency' => $intent->currency,
'metadata' => $intent->metadata,
])
);
return PaymentIntent::fromArray($response);
}
// Implement the remaining methods from PaymentGatewayInterface...
public function retrieveCustomer(string $customerId): Customer { /* ... */ }
public function updateCustomer(Customer $customer): Customer { /* ... */ }
public function deleteCustomer(string $customerId): void { /* ... */ }
public function createPaymentMethod(PaymentMethod $paymentMethod): PaymentMethod { /* ... */ }
public function attachPaymentMethod(string $paymentMethodId, string $customerId): PaymentMethod { /* ... */ }
public function confirmPaymentIntent(string $intentId, array $params = []): PaymentIntent { /* ... */ }
public function capturePaymentIntent(string $intentId, array $params = []): PaymentIntent { /* ... */ }
public function cancelPaymentIntent(string $intentId, array $params = []): PaymentIntent { /* ... */ }
public function createRefund(string $paymentIntentId, array $params = []): array { /* ... */ }
public function retrievePaymentIntent(string $intentId): PaymentIntent { /* ... */ }
}After implementing your gateway (for example, AcmePayGateway above), you can use it exactly like the built-in gateways.
Instantiate it with a PSR-18 HTTP client and PSR-17 factories, then call the methods defined by PaymentGatewayInterface:
<?php
declare(strict_types=1);
use App\Payment\Gateways\AcmePayGateway;
use Yiisoft\Payments\Models\Customer;
use Yiisoft\Payments\Models\PaymentIntent;
// $httpClient: PSR-18 client
// $requestFactory: PSR-17 request factory
// $streamFactory: PSR-17 stream factory
$gateway = new AcmePayGateway($httpClient, $requestFactory, $streamFactory);
// 1) (Optional) Create a customer in the provider
$customer = $gateway->createCustomer(new Customer(
email: 'buyer@example.com',
name: 'Buyer',
));
// 2) Create a payment intent (amount is in minor units, e.g. cents)
$intent = $gateway->createPaymentIntent(new PaymentIntent(
amount: 1999,
currency: 'USD',
customerId: $customer->id,
metadata: ['order_id' => 'ORDER-1001'],
));
// 3) If the provider requires buyer approval via redirect, send the buyer to:
$approvalUrl = $intent->nextAction['redirect_to_url']['url'] ?? null;
// 4) Later (after approval), confirm / capture (if your gateway uses these steps)
$intent = $gateway->confirmPaymentIntent($intent->id);
$intent = $gateway->capturePaymentIntent($intent->id);
// 5) Refund (full or partial, depending on your gateway constraints)
$refund = $gateway->createRefund($intent->id, ['amount' => 1999]);PaymentGatewayInterface requires implementing all methods below:
- Customer:
createCustomer(),retrieveCustomer(),updateCustomer(),deleteCustomer() - Payment methods:
createPaymentMethod(),attachPaymentMethod() - Payment intents:
createPaymentIntent(),retrievePaymentIntent(),confirmPaymentIntent(),capturePaymentIntent(),cancelPaymentIntent() - Refunds:
createRefund()
For a gateway that only supports payments + refunds (and does not have a customer / payment-method concept), the minimum you typically implement with real provider calls is:
createPaymentIntent(),retrievePaymentIntent(),cancelPaymentIntent(),createRefund()- plus
confirmPaymentIntent()/capturePaymentIntent()if your provider has a multi-step confirmation/capture flow
Customer and payment-method operations can be implemented as no-ops (returning the input model) or by throwing
PaymentException if the provider does not support them. Document that behavior in README.
Best practices:
- Throw
PaymentException(or subclasses) on any gateway-side errors. - Use idempotency keys where the provider supports them.
- Add unit tests with a fake/spy HTTP client, and integration tests with real credentials (optional).
- Document any gateway-specific behavior (approval redirects, delayed capture, refund constraints).
If you need help or have a question, the Yii Forum is a good place for that. You may also check out other Yii Community Resources.
Unit tests:
vendor/bin/phpunitIntegration tests (PayPal / Robokassa real API exchange):
- Install dev dependencies
- Copy config templates and fill credentials:
cp tests/config/paypal.php.dist tests/config/paypal.php
cp tests/config/robokassa.php.dist tests/config/robokassa.php- Run integration tests (they will be skipped if config is missing):
vendor/bin/phpunit --group integrationThe Yii payments is free software. It is released under the terms of the BSD License.
Please see LICENSE for more information.
Maintained by Yii Software.