-
Notifications
You must be signed in to change notification settings - Fork 2
StripeGateway
Pair\Services\StripeGateway wraps common Stripe payment operations while keeping Stripe as an optional provider.
The Pair core does not require the Stripe SDK. Install it only in applications or extension packages that need payments:
composer require stripe/stripe-phpSTRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_API_VERSION=Keys:
-
STRIPE_SECRET_KEYis required when the default SDK client is created. -
STRIPE_WEBHOOK_SECRETis required only for webhook signature verification. -
STRIPE_API_VERSIONis optional. Leave it empty to use the Stripe account default.
Pair v4 integrations should be registered explicitly. A Stripe package or application bootstrap can expose the gateway as the payments adapter:
use Pair\Core\AdapterKeys;
use Pair\Core\Application;
use Pair\Services\StripeGateway;
$app = Application::getInstance();
$app->setAdapter(AdapterKeys::PAYMENTS, new StripeGateway());
$payments = $app->adapter(AdapterKeys::PAYMENTS, StripeGateway::class);This keeps provider SDKs optional and avoids automatic discovery.
Creates the gateway with an injected Stripe-compatible client or a default StripeClient configured from Env.
Injecting a client is useful for tests or for extension packages that decorate the Stripe SDK.
createCheckoutSession(array $lineItems, string $successUrl, string $cancelUrl, array $options = []): stringcreateHostedCheckoutSession(array $lineItems, string $successUrl, string $cancelUrl, array $options = []): arraycreateEmbeddedCheckoutSession(array $lineItems, string $returnUrl, array $options = []): arraycreateSubscriptionCheckoutSession(array $lineItems, string $successUrl, string $cancelUrl, array $options = []): arraycreateCustomerPortalSession(string $customerId, string $returnUrl, array $options = []): stringcreatePaymentIntent(int $amountInMinorUnits, string $currency, array $options = []): arraycapture(string $paymentIntentId): arrayrefund(string $paymentIntentId, ?int $amountInMinorUnits = null): arrayconstructWebhookEvent(string $payload, string $signature): Stripe\EventconstructWebhookEventFromRequest(?string $payload = null, ?string $signature = null): Stripe\EventwebhookResponse(string $payload, string $signature, array $handlers = []): JsonResponsewebhookResponseFromEvent(object $event, array $handlers = []): JsonResponsetoMinorUnits(float $amount): int
Use hosted Checkout when Stripe should own the payment page and redirect back to the application.
use Pair\Http\JsonResponse;
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
$session = $gateway->createHostedCheckoutSession(
[
['price' => 'price_123', 'quantity' => 1],
],
'https://example.test/billing/success',
'https://example.test/billing/cancel',
[
'metadata' => ['order_id' => 'order_123'],
'idempotency_key' => 'checkout_order_123',
]
);
return new JsonResponse([
'checkout_url' => $session['url'],
]);createCheckoutSession() is kept as the backward-compatible shortcut that returns only the hosted session URL.
Use embedded Checkout when the frontend renders Stripe Checkout inside the application.
use Pair\Http\JsonResponse;
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
$session = $gateway->createEmbeddedCheckoutSession(
[
['price' => 'price_123', 'quantity' => 1],
],
'https://example.test/billing/return?session_id={CHECKOUT_SESSION_ID}',
[
'redirect_on_completion' => 'if_required',
'idempotency_key' => 'embedded_order_123',
]
);
return new JsonResponse([
'client_secret' => $session['client_secret'],
]);Use createSubscriptionCheckoutSession() for subscription prices. It forces Stripe Checkout mode=subscription.
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
$session = $gateway->createSubscriptionCheckoutSession(
[
['price' => 'price_monthly_123', 'quantity' => 1],
],
'https://example.test/billing/success',
'https://example.test/billing/cancel',
[
'customer' => 'cus_123',
'idempotency_key' => 'subscription_customer_123',
]
);Use Customer Portal Sessions when customers need to manage payment methods, invoices, and subscriptions through Stripe.
use Pair\Http\JsonResponse;
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
$portalUrl = $gateway->createCustomerPortalSession(
'cus_123',
'https://example.test/account',
[
'idempotency_key' => 'portal_customer_123',
]
);
return new JsonResponse([
'portal_url' => $portalUrl,
]);Amounts must be expressed in minor units. For example, 4990 means 49.90 EUR.
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
$intent = $gateway->createPaymentIntent(
StripeGateway::toMinorUnits(49.90),
'eur',
[
'description' => 'Order #123',
'metadata' => ['order_id' => 'order_123'],
'idempotency_key' => 'payment_order_123',
]
);When no idempotency_key is provided for PaymentIntent and Checkout creation, Pair generates one for the Stripe request. Project code should still prefer deterministic keys for retryable domain operations.
Use webhookResponse() when the controller receives a raw payload and the Stripe-Signature header.
use Pair\Services\StripeGateway;
$gateway = new StripeGateway();
return $gateway->webhookResponse(
(string)file_get_contents('php://input'),
(string)($_SERVER['HTTP_STRIPE_SIGNATURE'] ?? ''),
[
'checkout.session.completed' => function (object $event): void {
$session = $event->data->object ?? null;
if (!$session) {
return;
}
// Persist domain-side payment state here.
},
]
);webhookResponse() verifies the signature through Stripe before invoking a handler. Use webhookResponseFromEvent() only after the event has already been verified.
- Keep Stripe secrets in
.env, never in Git. - Store payment state in project tables, not inside the gateway.
- Use deterministic idempotency keys for retries tied to orders, invoices, or subscriptions.
- Webhook handlers should be safe to run more than once because Stripe may retry deliveries.
- Keep provider-specific domain mapping in application services instead of controllers.
- Checkout Sessions
- Embedded Checkout
- Customer Portal Sessions
- Webhook signature verification
- Idempotent requests
See also: API, Integrations, AdapterRegistry, Options.