Payments and subscriptions

Taking money is split the same way as authentication: a core Payment module owns the shape of a payment without knowing any provider, and a provider module plugs in the actual charging. Stripe is the provider that ships, and MemberStripe ties it back to individual members.

A provider-agnostic core

The Payment module owns the payment concepts a site works in, without containing any provider's code. It models three things: a purchase, which is a one-off charge; a subscription, which is a recurring charge on a schedule; and a merchant, which is an account that receives money on platforms that pay several parties. Each of these keeps a history of its status, so a purchase moves through created, pending, success or error, and a subscription through pending, active, past-due and cancelled, with every change recorded rather than overwritten. Amounts are stored as whole small units rather than floating-point money so totals never drift.

Providers register with the core

The core does not charge cards itself; it defines the slots a provider fills. A provider module registers itself against the Payment core as a purchase provider, a subscription provider, or a merchant provider (or all three), and the core then routes work of that kind to it. This is the same provider pattern the framework uses for messaging and elsewhere: the core owns the durable records and the provider-independent shape, and the provider owns the conversation with the outside service. See Submodules, providers and vendors. It also means a site could swap or add a payment provider without disturbing the records or the rest of the application.

The Stripe provider

PaymentProviderStripe is that provider for Stripe. It handles the actual charging: creating and capturing payments, saving a member's payment methods, onboarding merchant accounts through Stripe Connect, and paying out. Because a real charge completes asynchronously, the provider listens for Stripe's webhooks and updates the matching record in the Payment core when the outcome arrives, so the core's status history always reflects what actually happened at the provider. A site gives the provider its Stripe keys and webhook secret through configuration, the same way every module is configured:

Config::Provide('PaymentProviderStripe', [
    'PAYMENT_PROVIDER_STRIPE_SECRET_KEY'      => 'sk_live_...',
    'PAYMENT_PROVIDER_STRIPE_PUBLISHABLE_KEY' => 'pk_live_...',
    'PAYMENT_PROVIDER_STRIPE_WEBHOOK_SECRET'   => 'whsec_...',
]);

The keys live with the provider that uses them, not in the core, so the core stays free of any Stripe-specific setting.

Members and their Stripe identity

MemberStripe links a member to their Stripe identity. It keeps, per member, the Stripe customer and any connected merchant account, along with the onboarding state Stripe reports back (whether the account is verified, can take charges, can be paid out) and cached links for onboarding and the Stripe dashboard. It provides the return and refresh pages Stripe redirects a member to during onboarding, so a member can set up as a payee and come back into the site. This is where the generic member account meets the payment provider: the account stays provider-neutral, and MemberStripe holds the Stripe-specific link beside it.

Subscriptions and billing

A subscription in the core carries who it belongs to, how much and how often, and a reference to the provider's own subscription, plus a status history that tracks its life from pending through active to cancelled. There is no separate table of plans; a subscription is described by its own fields (its owning module and identifier, its amount and its regularity), and the provider holds the recurring billing object that actually charges on schedule. So the core answers what a member is subscribed to and what state it is in, while the provider does the charging and reports each cycle's outcome back through webhooks. Operators see all of this through the PaymentAdmin and PaymentProviderStripeAdmin submodules, built the way Building an admin system describes.

Next: Messaging.