Implement a payment adapter

Implement the current payment contract in a local module or external route app.

Use features/keystone/utils/paymentProviderAdapter.ts as the dispatch contract and the built-in stripe.ts, paypal.ts and manual.ts modules in features/integrations/payment as examples. A payment app can also live outside Openfront: store its HTTPS URLs in the operation fields of the PaymentProvider record.

Operations

createPaymentFunction({ provider, cart, amount, currency })
capturePaymentFunction({ provider, paymentId, amount, currency, idempotencyKey })
refundPaymentFunction({ provider, paymentId, amount, currency, idempotencyKey })
getPaymentStatusFunction({ provider, paymentId })
generatePaymentLinkFunction({ provider, paymentId })
handleWebhookFunction({ provider, event, headers })

A local module exports these functions. An HTTP route receives POST JSON with provider plus the same arguments and returns JSON. Amounts are integers in minor units.

What the callers expect back

  • create: an object stored as the payment session's data. Include paymentIntentId, clientSecret or orderId. Checkout reads the provider reference from those keys.
  • status and capture: status, amount and currency. Checkout captures when status is requires_capture, approved or authorized, and treats succeeded, completed or captured as paid. The amount must equal the cart or invoice total exactly.
  • refund: the provider result; Openfront writes the Refund record.
  • webhook: verify the signature using headers.__rawBody, then return type, resource and event (with event.id). Event handling matches Stripe (payment_intent.*) and PayPal (PAYMENT.CAPTURE.*, PAYMENT.AUTHORIZATION.*) event types only.

Rules

  • Never trust an amount from the browser. Use the amount and currency Openfront passes.
  • Pass idempotencyKey to the provider for capture and refund.
  • Return only the fields the browser needs in create's result. It is readable on the cart's payment session.
  • Fail with an error for operations you do not support. Do not return a fake success.
  • For HTTP adapters, authenticate requests from Openfront yourself. The bridge sends no signature.

Make checkout accept it

Use a provider code that starts with pp_stripe, pp_paypal or pp_manual. Other codes are treated as not configured and are hidden from checkout. Codes containing manual are refused at completion and refund. In practice a new external provider must reuse the Stripe or PayPal code prefix and their environment variables, or you must extend features/keystone/utils/paymentProviderConfig.ts.

Verify

  • Run create, status, capture, refund and webhook in the provider sandbox.
  • Retry capture and refund with the same key and confirm one provider effect.
  • Send a webhook twice and confirm it is acknowledged as a duplicate.
  • Send a webhook with a bad signature and confirm it is rejected.
  • Change the cart after creating a session and confirm completion fails on the amount check.

See Add a payment adapter for provider configuration.

On this page