FEATURES / BILLING
Stripe.
MercadoPago.
Polar.
Three providers behind one IPaymentProvider port. Switch in one line, run them side-by-side, or write your own adapter against the same interface.
Subscriptions, one-shot, customer portal.
IPaymentProvider port
Single interface: createCheckout, createPortal, getSubscription, cancelSubscription. Implementations live in infrastructure/.
Stripe
Subscriptions, one-time payments, customer portal, tax, proration, coupons. Webhook signatures verified.
MercadoPago
Suscripciones, preferencias, IPN webhooks. LATAM-friendly with proper currency and tax handling.
Polar.sh
Open-source-friendly billing with built-in license keys. Drop-in for OSS commercial tiers.
Read model
Webhooks update a Prisma read model so your app never queries Stripe live. Predictable, fast, cacheable.
Plan catalog
Plans defined in code (with Zod). Single source of truth across landing, checkout, and the dashboard.
One port, three adapters.
Your application code calls IPaymentProvider; the DI container picks the right adapter from env. Tests inject an in-memory adapter that records every call. No conditional code paths in your domain.
1 export interface IPaymentProvider {
2 createCheckout(input: CheckoutInput): Promise<Checkout>;
3 createPortal(customerId: CustomerId): Promise<PortalUrl>;
4 getSubscription(id: SubscriptionId): Promise<Subscription>;
5 cancelSubscription(id: SubscriptionId): Promise<void>;
6 }
7
8 // container picks: stripe | mercadopago | polar | inMemory
9 container.bind<IPaymentProvider>(TYPES.Payments)
10 .toFactory(() => providerFromEnv());
Charge customers in 3 currencies.
Stripe + MercadoPago + Polar shipping with one config flip.