Payment providers

How restaurant payments are wired through Stripe, PayPal, and manual adapters.

Openfront Restaurant uses adapter-based payment providers. Each PaymentProvider record names the adapter for create, capture, refund, status, payment link, and webhook work. The adapters live in:

  • features/integrations/payment/stripe.ts
  • features/integrations/payment/paypal.ts
  • features/integrations/payment/manual.ts

The record's function fields must be stripe, paypal, or manual. Manage records at /dashboard/platform/payment-providers.

Providers

Stripe (pp_stripe_stripe)

Needs STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_KEY, and STRIPE_WEBHOOK_SECRET. Used for:

  • storefront card checkout with payment intents
  • POS card payments (credit_card, debit_card, apple_pay, and google_pay tenders all route to Stripe)
  • capture, status checks, and refunds
  • signed webhooks

PayPal (pp_paypal_paypal)

Needs NEXT_PUBLIC_PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, and PAYPAL_WEBHOOK_ID. Sandbox is used unless NEXT_PUBLIC_PAYPAL_SANDBOX=false. Used for storefront checkout with PayPal approval, capture, refunds, and verified webhooks. The POS payment screen has no PayPal tender.

Manual (pp_system_default)

No credentials. Used for cash in the POS and for storefront orders paid outside the app. Storefront orders using it are created as open with a pending payment. The manual adapter does not support webhooks.

Onboarding creates these three records. Stripe and PayPal count as configured only when their environment variables are set.

How the provider model works

Create or seed the provider record

Onboarding creates Stripe, PayPal, and Manual. You can add or toggle records on the Payment Providers page.

The runtime resolves the adapter

features/keystone/utils/paymentProviderAdapter.ts reads the record and calls the named adapter function.

Payments attach to orders

Each Payment links to a RestaurantOrder and its provider. Payments carry an idempotency key, status, method, tip, split index, and refunded amount.

Webhooks are verified and logged

Providers post to /api/integrations/payment/[providerCode]/webhook, for example /api/integrations/payment/pp_stripe_stripe/webhook. The route passes the raw body and headers to handlePaymentProviderWebhook, which checks the signature through the adapter and stores the event as a PaymentWebhookEvent. Duplicate events are ignored. A verified payment event can also complete a pending storefront checkout.

Payment statuses

pending, processing, authorized, unknown, succeeded, failed, cancelled, refunded, partially_refunded. A check cannot close while any of its payments is processing, authorized, or unknown.

Refunds and reconciliation

  • refundPayment needs an approved ManagerApproval from a different user and an idempotency key. It writes a Refund and a refund Receipt.
  • reconcilePayment and reconcileRefund ask the provider for the current state when a result was unclear.
  • retryVerifiedRefundWebhookEvent re-processes a stored, verified refund webhook.
  • capturePayment and getPaymentStatus are also exposed.

The refund and webhook recovery panels are in the POS "Checkout recovery" section and on the payment screen.

Provider fields

  • name, code, isInstalled
  • createPaymentFunction, capturePaymentFunction, refundPaymentFunction, getPaymentStatusFunction, generatePaymentLinkFunction, handleWebhookFunction
  • credentials and metadata

The only webhook route is /api/integrations/payment/[providerCode]/webhook.

On this page