Payment providers

Local and external payment adapter dispatch, provider state, webhooks, and current workflow limits.

Openfront stores payment-provider configuration in PaymentProvider records and runs provider operations through features/keystone/utils/paymentProviderAdapter.ts.

Each provider has six operation fields: createPaymentFunction, capturePaymentFunction, refundPaymentFunction, getPaymentStatusFunction, generatePaymentLinkFunction and handleWebhookFunction. Each field holds either:

  • a local module name such as stripe, paypal or manual, which loads features/integrations/payment/<name>.ts; or
  • an http... URL for a separately deployed adapter app.

Dispatch contract

For a URL, Openfront sends POST JSON with the provider record plus the operation arguments, and parses the JSON reply. For a module name, it imports the file and calls the export with the same name as the field.

OperationArguments besides provider
createcart, amount, currency
capturepaymentId, amount, currency, idempotencyKey
refundpaymentId, amount, currency, idempotencyKey
statuspaymentId
payment linkpaymentId
webhookevent, headers

Where each operation is used

  • initiatePaymentSession and initiateInvoicePaymentSession call create.
  • completeActiveCart and completeInvoicePayment call status, then capture if the status is requires_capture, approved or authorized. They accept succeeded, completed or captured, and then check that the returned amount and currency match the cart or invoice. Progress is stored in IdempotencyKey records, so a retry resumes instead of charging again.
  • processReturnRefund calls refund for a received return, refuses amounts above the captured payment, and uses an idempotency key you supply.
  • /api/payment-webhooks/<providerId> calls webhook. The route rejects bodies over 1 MB and passes the raw body to the adapter (as headers.__rawBody) for signature checks. Stripe uses stripe.webhooks.constructEvent with STRIPE_WEBHOOK_SECRET. PayPal calls PayPal's verify-signature API with PAYPAL_WEBHOOK_ID. Events are de-duplicated by provider event ID.

Which providers can be used

A provider can start a session only when it is installed (isInstalled), linked to the cart's region, and counted as configured. In features/keystone/utils/paymentProviderConfig.ts:

  • codes starting pp_stripe need NEXT_PUBLIC_STRIPE_KEY (or STRIPE_PUBLISHABLE_KEY) and STRIPE_SECRET_KEY;
  • codes starting pp_paypal need NEXT_PUBLIC_PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET;
  • pp_system_default and codes starting pp_manual are always configured;
  • any other code is treated as not configured.

Current limits

  • A custom provider must use a code starting with pp_stripe, pp_paypal or pp_manual, or it is filtered out of checkout. There is no generic "custom" code.
  • Webhook amount checks and event-type matching only understand Stripe and PayPal events. A custom adapter must return events in one of those shapes.
  • The checkout reads the provider reference from session data keys paymentIntentId, clientSecret or orderId. A custom adapter must return one of them from create.
  • Manual tender (pp_system_default) is refused by completeActiveCart, by completeInvoicePayment, and by processReturnRefund. There is no operator screen that records a manual payment as received.
  • The HTTP bridge sends only a JSON content-type header. It has no timeout, request signature, destination allowlist or response schema. Anyone who can edit a provider's operation URLs decides where provider credentials and payment data go.
  • The dashboard "add provider" drawer offers only Stripe, PayPal and Manual presets. Set URL operation fields on the generic /dashboard/payment-providers editor.

Enabling a provider

  1. Set the environment variables for Stripe or PayPal.
  2. Install the provider and link it to each region at /dashboard/platform/payment-providers.
  3. Point the provider's webhook at https://your-host/api/payment-webhooks/<providerId>.
  4. In the provider sandbox, run checkout, a declined payment, a duplicate webhook, a return refund, and a retry after a timeout.

Start with Add a payment adapter.

On this page