Payment gateways
Payment gateways
The library depends on no payment SDK. A gateway is anything that implements
PaymentGateway; the bundled ManualPaymentGateway handles offline collection
with no external dependency. Stripe and Mollie live in separate opt-in adapter
packages (cboxdk/laravel-billing-stripe, -mollie).
Implement the gateway
use Cbox\Billing\Payment\Contracts\PaymentGateway;
use Cbox\Billing\Payment\ValueObjects\{PaymentIntent, PaymentResult, RefundIntent};
final class StripeGateway implements PaymentGateway
{
public function name(): string { return 'stripe'; }
public function charge(PaymentIntent $intent): PaymentResult { /* … */ }
public function refund(RefundIntent $intent): PaymentResult { /* … */ }
}
Bind it over the default:
$this->app->bind(PaymentGateway::class, StripeGateway::class);
Stored customers & saved methods
Beyond a single charge, the seam manages the gateway objects that off-session
renewals bill against. createCustomer mints (or re-resolves) the gateway customer
that saved methods and off-session charges attach to, stamping the host's account key
into its metadata for dashboard reconciliation and returning its reference (e.g.
Stripe cus_…); the host persists the account→reference mapping. createSetupIntent
returns a client secret the frontend confirms to vault a method, and
attachPaymentMethod / paymentMethods / setDefaultPaymentMethod list and manage
what is vaulted. detachPaymentMethod removes a vaulted method so future renewals can
no longer charge it — it is idempotent (detaching an already-detached method must not
error), and a vault-less gateway treats it as a no-op.
$customer = $gateway->createCustomer($account, $email, $name); // "cus_…"
// After the frontend confirms a SetupIntent element:
$method = $gateway->attachPaymentMethod($account, $paymentMethodId);
$gateway->setDefaultPaymentMethod($account, $method->id);
// Tear the method down when the customer removes it:
$gateway->detachPaymentMethod($account, $method->id);
ManualPaymentGateway implements this honestly for a vault-less gateway:
createCustomer returns a deterministic local reference ('manual:'.$account) with
no external round-trip, and detachPaymentMethod is a genuine no-op.
Verify webhooks
Webhook authentication is the adapter's job. Implement WebhookVerifier to
authenticate the raw payload and map it to a canonical WebhookEvent, then bind it
so DefaultWebhookIngest can use it:
use Cbox\Billing\Payment\Contracts\WebhookVerifier;
final class StripeWebhookVerifier implements WebhookVerifier { /* … */ }
The default binding is DenyingWebhookVerifier, which rejects everything — so an
un-configured deployment fails closed rather than trusting unsigned payloads. Once
verified, WebhookIngest handles
exactly-once application (dedup + settled guard). See
cookbook: ingest a payment webhook.