Payments
Payments Core
Provider-agnostic payment architecture
Beztack uses a provider-agnostic payment system. Apps import types and the
factory only from @beztack/payments (core) — never from provider packages
directly. This lets you swap or add payment providers without changing app-level
code.
How it works
- The
PAYMENT_PROVIDERenv var selects the active provider ("polar"or"mercadopago"). - At runtime,
createPaymentProvider()dynamically imports only the selected provider package. - All API routes call the adapter interface — they never reference provider-specific APIs.
PaymentProviderAdapter interface
Every provider must implement this interface (defined in packages/payments/core/src/types.ts):
| Method | Description |
|---|---|
listProducts() | List all products/plans from the provider |
getProduct(id) | Get a single product by ID |
createProduct(options) | Create a new product |
updateProduct(id, options) | Update an existing product |
deleteProduct(id) | Delete a product |
createCheckout(options) | Create a checkout session, returns a URL |
createSubscription(options) | Create a subscription directly |
getSubscription(id) | Get a subscription by ID |
updateSubscription(id, options) | Update a subscription (change plan, proration) |
cancelSubscription(id, immediately?) | Cancel a subscription |
listSubscriptions(options) | List subscriptions with filters |
createCustomer(email, metadata?) | Create a customer record |
getCustomer(id) | Get a customer by ID |
getCustomerByEmail(email) | Look up a customer by email |
parseWebhook(rawBody, signature) | Parse and validate a webhook payload |
createPortalSession?(id, returnUrl) | Create a customer portal session (optional) |
Core types
Defined in packages/payments/core/src/types.ts:
Product— Unified product/plan representation with price, interval, and metadata.Plan— AProductwithtype: "plan"(recurring subscription).Subscription— Unified subscription with status, customer, and period info.Customer— Unified customer with email and optional external ID.BillingInterval—"month" | "year" | "day" | "week".SubscriptionStatus—"active" | "inactive" | "pending" | "canceled" | "paused" | "past_due".WebhookPayload— Unified webhook event with type, provider, and parsed data.WebhookEventType— Unified event types like"subscription.created","payment.success", etc.PricingTier— UI display structure with monthly/yearly prices, features, and limits.CheckoutResult— Checkout session result withidandurl.
Factory and provider selection
The factory (packages/payments/core/src/factory.ts) uses a registry pattern with dynamic imports:
import { createPaymentProvider } from "@beztack/payments"
// Create and cache the adapter (usually done once at app startup)
const adapter = await createPaymentProvider("mercadopago", {
MERCADO_PAGO_ACCESS_TOKEN: "...",
})
// Use the adapter
const products = await adapter.listProducts()Key functions:
createPaymentProvider(provider, config)— Create (or return cached) adapter instance.getPaymentProvider()— Synchronous getter for the cached adapter. Throws if not initialized.resetPaymentProvider()— Clear the cache (useful for testing).getRegisteredProviders()— List registered provider names.
In API routes, use ensurePaymentProvider() from apps/api/lib/payments/index.ts
which handles reading the env config and initializing the adapter.
Webhook utilities
Defined in packages/payments/core/src/webhooks.ts:
verifyWebhookSignature(payload, signature, secret)— HMAC SHA-256 signature verification.createDefaultWebhookHandlers(customHandlers?)— Creates a handler map for common webhook events (order.paid,subscription.active,subscription.canceled, etc.). Pass custom callbacks to override defaults.
Adding a new provider
- Create a new package under
packages/payments/(e.g.,packages/payments/stripe/). - Implement the
PaymentProviderAdapterinterface and export acreateAdapterfactory function. - Register the provider in
packages/payments/core/src/factory.tsby adding aregistry.set(...)line with a dynamic import. - Add the provider name to the
PaymentProviderNameunion type intypes.ts. - Update env validation in
packages/env/to include the new provider's variables.
Provider-specific docs
- Polar — Better Auth integration, portal sessions, usage tracking.
- Mercado Pago — Plan sync, card processing, SSE events.