Overview
Spree has a highly flexible payments model which allows multiple payment methods to be available during the checkout. The logic for processing payments is decoupled from orders, making it easy to define custom payment methods with their own processing logic. Payment methods typically represent a payment gateway. Gateways will process card payments, online bank transfers, buy-now-pay-later, wallet payments, and other types of payments. Spree also comes with a Check option for offline processing. ThePayment model in Spree tracks payments against Orders. Payments relate to a source which indicates how the payment was made, and a PaymentMethod, indicating the processor used for this payment.
A payment’s number is derived from what it belongs to — an order R1001 numbers its payments R1001-P1, R1001-P2 — so a gateway reference traces straight back to the order without a lookup.
Payment Architecture Diagram
Key relationships:- Payment tracks each payment attempt against an Order
- Payment Method defines how payments are processed (Stripe, Adyen, PayPal, Check, etc.)
- Payment Session manages the gateway-side payment lifecycle (e.g., Stripe PaymentIntent, Adyen Session)
- Payment Setup Session manages saving payment methods for future use without an immediate charge (e.g., Stripe SetupIntent)
- Source is polymorphic - can be a Credit Card, Payment Source (for alternative methods like Klarna, iDEAL), or Store Credit
- Gateway Customer stores the provider-specific customer profile (e.g., Stripe Customer ID)
- Refunds track money returned to customers
Payment Methods
Payment methods represent the different options a customer has for making a payment. Most sites will accept credit card payments through a payment gateway, but there are other options. Spree also comes with built-in support for a Check payment, which can be used to represent any offline payment. Gateway providers such as Stripe, Adyen, and PayPal provide a wide range of payment methods, including credit cards, bank transfers, buy-now-pay-later, and digital wallets (Apple Pay, Google Pay, etc.). APaymentMethod can have the following attributes:
Each payment method is associated to a Store, so you can decide which Payment Method will appear on which Store. This allows you to create different experiences for your customers in different countries.
Session-based vs direct payment methods
Payment methods indicate whether they use the modern session-based flow via thesession_required? method:
Gateways like Stripe and Adyen set
session_required? to true; offline methods leave it false. Neither path is deprecated. The Store API serializer includes this as the session_required field so your frontend knows which flow to use.
Direct payment methods (manual and offline)
Payment methods wheresession_required? returns false don’t need a payment session. These are typically offline or manual payment methods such as:
- Check — built in
- Cash on Delivery — customer pays upon delivery
- Bank Transfer / Wire — customer transfers money to a bank account
- Purchase Order — common in B2B, customer provides a PO number
checkout. Completing the cart processes it — for a manual method there is no provider to call, so it succeeds immediately and lands on pending, or completed when the store charges at checkout.
Once the money actually arrives — the cheque clears, the transfer lands, the
driver is paid — staff capture the payment from the dashboard, or you do it
through the Admin API:
Admin SDK
Payment Flow
Spree supports two payment flows depending on the payment method type:Session-Based Flow (Stripe, Adyen, PayPal, etc.)
Modern payment gateways use a three-phase approach: first a Payment Session is created with the gateway, then the customer completes payment on the frontend, and finally the order is completed via an explicit API call. Payment processing and order completion are intentionally separated — this prevents race conditions and ensures reliable checkout regardless of payment method type (cards, wallets, offsite redirects).1
Create Payment Session
The frontend calls the API to create a Payment Session for a specific payment method and order. Spree calls the gateway to create a provider-side session (e.g., Stripe PaymentIntent, Adyen Session) and returns the session data including a
client_secret for the frontend SDK.The payment session should be created (or recreated) after the shipping method is selected, so the amount includes shipping costs. If the order total changes (e.g., customer selects a different shipping rate or applies a coupon), create a new payment session with the updated amount.
2
Customer pays on the frontend
The frontend uses the gateway’s JavaScript SDK (e.g., Stripe.js, Adyen Drop-in) with the
client_secret to securely collect payment details. Card data never touches your server — it goes directly to the payment provider, ensuring PCI compliance. If the payment requires 3D Secure authentication or redirects to an offsite gateway (CashApp, Klarna, etc.), the gateway SDK handles it automatically.3
Complete Payment Session
After the customer completes payment, the frontend calls the Complete Payment Session endpoint. Spree verifies the payment status with the gateway, creates a
Payment record, creates the appropriate payment source (Credit Card, wallet, etc.), and marks the session as completed.This step does NOT complete the order — it only handles payment processing. For wallet payments (Apple Pay, Google Pay), the gateway also patches the order’s billing address with data from the wallet at this stage.4
Complete Order
The frontend calls
POST /carts/:id/complete to finalize the order. Spree checks the cart has everything it needs — addresses, fulfillments, payment — and turns it into an order.This separation ensures the same flow works for all payment types — inline cards, offsite redirects, and wallet payments.Offsite Payment Flow (CashApp, 3D Secure, Klarna, etc.)
For payment methods that redirect the customer away from your site, use an intermediate confirm-payment page:Webhook-Driven Completion (Browser Closed)
If the customer closes the browser after paying but before the frontend callscomplete, Spree handles this via payment webhooks:
Gateway extensions implement parse_webhook_event to normalize provider-specific payloads into a standard format. Spree core handles the rest — creating the payment record, completing the session, and finalizing the order.
Direct Payment Flow (Check, Cash on Delivery, Bank Transfer, etc.)
Non-session payment methods use a simpler flow where a payment is created directly without involving an external payment provider:1
List payment methods
The frontend reads the cart’s embedded
payment_methods (returned by GET /carts/:id) and checks the session_required flag on each method. Methods with session_required: false use this direct flow.2
Create payment
The frontend calls
POST /payments with the payment_method_id. Spree creates a payment with status checkout. No provider is contacted.Two refusals to handle: a method needing a session returns payment_session_required, and one unavailable for this order returns payment_method_unavailable — both HTTP 422 with that code.3
Complete order
The frontend completes the order. Completing the cart processes the payment. For a manual method there is nothing to call, so it succeeds immediately and lands on
pending, or completed when the store charges at checkout. Staff capture pending payments from the dashboard once the money arrives.Payment Session
A payment session represents a server-side session with the payment gateway. It is the entry point for every payment attempt and holds the provider-specific data needed by the frontend SDK.Attributes
A session also carries
order_id, but during checkout there is no order yet —
it reports the cart’s ID until the cart completes. Read cart_id while paying,
and order_id once you have an order.States
There is no cancel endpoint. A session reaches
canceled when the provider
says so through its webhook, and expired when it passes expires_at.
Neither is something a storefront drives.API
Create a Payment Session:StorePaymentSession):
Response
Payment Webhooks
Spree provides a generic webhook endpoint atPOST /api/v3/webhooks/payments/:payment_method_id that payment gateway extensions can use. When a payment provider sends a webhook (e.g., Stripe payment_intent.succeeded), Spree:
- Verifies the webhook signature synchronously (returns
401if invalid) - Enqueues a background job to process the event
- Returns
200 OKimmediately
Gateway Interface
Gateway extensions implementparse_webhook_event to normalize provider-specific payloads:
server/app/models/my_gateway.rb
:captured, :authorized, :failed, :canceled.
Payment
Once a payment session completes, Spree creates a payment to record the result, linked to the session byresponse_code matching its external_id.
Attributes
These are the fields the Store API returns:source_type is a plain name — credit_card, not a class name — so you can
switch on it directly.Payment statuses
When the money is taken follows the store’s
capture_method, which a payment
method can override for its own payments:
The last two leave the payment at
pending until capture. See
Configuration for where the store
setting lives.
There is no state machine behind these. A payment’s status is written by the
workflows that process, capture and void it, so nothing transitions on its own
and there is no sequence a client has to drive.
Order Payment Status
Each payment update also recalculates the order’spayment_status, derived from its payments and refunds against the order total:
Refunds
Refunds are an Admin API operation — there is no Store API route, so a customer cannot start one from your storefront:Admin SDK
payment.refunded and moves the order’s payment_status to
partially_refunded or refunded.
Payment Sources
Payment sources represent the actual instrument used for a payment. They are created automatically when a Payment Session completes.Saved cards
Stores non-sensitive credit card information. With modern gateways, the actual card data is tokenized by the provider - Spree only stores reference IDs and display information.Spree never stores full credit card numbers. With modern gateways, card data is collected entirely by the gateway’s frontend SDK (e.g., Stripe.js, Adyen Drop-in) and never touches your server. Spree only stores the tokenized reference (
gateway_payment_profile_id) returned by the provider.Payment Sources
A generic payment source model for non-card payment methods such as digital wallets, bank transfers, and buy-now-pay-later services. Gateway integrations create subtypes for each payment method type (e.g., Klarna, Afterpay, iDEAL, Apple Pay, Google Pay, PayPal).Gateway customers
Maps a Spree customer to their provider-specific customer profile. This enables features like saved payment methods, recurring billing, and customer-level fraud detection.
A customer has at most one record per payment method, and
profile_id is encrypted with Active Record encryption where it is configured.
This one is internal — the API does not expose it. It is listed here because
gateway integrations rely on it; nothing a storefront does will read it.
Payment Setup Sessions
Payment setup sessions let customers save payment methods for future use without making an immediate payment. This maps to concepts like Stripe’s SetupIntent - a secure way to collect and tokenize payment details for later charges.Use Cases
- Saving a credit card to the customer’s account for faster future checkouts
- Authorizing a payment method for subscription billing
- Adding a payment method during account onboarding (before any purchase)
How Payment Setup Sessions Work
Payment Setup Session Attributes
Payment Setup Session API
Payment Setup Sessions require customer authentication. The customer must be logged in.
StorePaymentSetupSession):
Response
external_client_secret):
Supported Gateways
Spree team maintains several payment gateway integrations. All of these gateways are fully PCI compliant, using native gateway SDKs, meaning no sensitive payment data is stored or processed through Spree.Stripe
Stripe integration, supports all Stripe payment methods, including credit cards, bank transfers, Apple Pay, Google Pay, Klarna, Afterpay, and more. Also supports quick checkout.
Adyen
Adyen integration, supports all Adyen payment methods, including credit cards, bank transfers, Apple Pay, Google Pay, Klarna, and more.
PayPal
Native PayPal integration, supports PayPal, PayPal Credit, and PayPal Pay Later.
Payment Events
Spree publishes events throughout the payment lifecycle that you can subscribe to. For the delivered payload schemas of these events (e.g.payment.paid, payment_session.completed), see the Webhooks & Events reference:
Payment Events
Payment Session Events
Payment Setup Session Events
See Events for more details on subscribing to events.
Two paths to a completed order
A payment can finish in two places, and both have to end at the same result. The customer’s browser confirms the payment and your storefront completes the cart. Or the provider’s own webhook arrives first — sometimes seconds later, sometimes because the customer closed the tab mid-redirect. Whichever arrives first completes the order; the other finds the work already done and does nothing. That is what makes a closed tab or a flaky connection recoverable rather than a lost sale with a real charge attached. Both paths can be replaced if your integration needs different behaviour — see Dependencies.Related Documentation
- Payments (Store SDK) - SDK how-to for payment sessions, payments, and setup sessions
- Build a Custom Payment Method - Step-by-step guide to creating your own payment gateway integration
- Orders - Order lifecycle, payment and fulfillment status
- Checkout Customization - Customizing the checkout flow
- Events - Subscribe to payment events

