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,paypalormanual, which loadsfeatures/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.
| Operation | Arguments besides provider |
|---|---|
| create | cart, amount, currency |
| capture | paymentId, amount, currency, idempotencyKey |
| refund | paymentId, amount, currency, idempotencyKey |
| status | paymentId |
| payment link | paymentId |
| webhook | event, headers |
Where each operation is used
initiatePaymentSessionandinitiateInvoicePaymentSessioncall create.completeActiveCartandcompleteInvoicePaymentcall status, then capture if the status isrequires_capture,approvedorauthorized. They acceptsucceeded,completedorcaptured, and then check that the returned amount and currency match the cart or invoice. Progress is stored inIdempotencyKeyrecords, so a retry resumes instead of charging again.processReturnRefundcalls 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 (asheaders.__rawBody) for signature checks. Stripe usesstripe.webhooks.constructEventwithSTRIPE_WEBHOOK_SECRET. PayPal calls PayPal's verify-signature API withPAYPAL_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_stripeneedNEXT_PUBLIC_STRIPE_KEY(orSTRIPE_PUBLISHABLE_KEY) andSTRIPE_SECRET_KEY; - codes starting
pp_paypalneedNEXT_PUBLIC_PAYPAL_CLIENT_IDandPAYPAL_CLIENT_SECRET; pp_system_defaultand codes startingpp_manualare always configured;- any other code is treated as not configured.
Current limits
- A custom provider must use a code starting with
pp_stripe,pp_paypalorpp_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,clientSecretororderId. A custom adapter must return one of them from create. - Manual tender (
pp_system_default) is refused bycompleteActiveCart, bycompleteInvoicePayment, and byprocessReturnRefund. 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-providerseditor.
Enabling a provider
- Set the environment variables for Stripe or PayPal.
- Install the provider and link it to each region at
/dashboard/platform/payment-providers. - Point the provider's webhook at
https://your-host/api/payment-webhooks/<providerId>. - 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.