Skip to main content

Overview

This guide walks you through building a custom payment method integration for Spree. By the end, you’ll have a fully functional payment gateway that:
  • Appears as a payment option during checkout
  • Uses Payment Sessions for PCI-compliant payment collection
  • Supports 3D Secure and alternative payment methods
  • Handles webhooks for reliable payment confirmation
  • Optionally supports saving payment methods for future use (Payment Setup Sessions)
Before starting, make sure you understand how payments work in Spree.

Step 1: Create the Payment Method Model

Create a new model inheriting from Spree::PaymentMethod. This is the central class that defines how your gateway processes payments.
app/models/my_gateway.rb

Step 2: Register the Payment Method

Add your gateway to the list of available payment methods so it appears in the admin panel:
config/initializers/spree.rb
After restarting your server, you can select “MyGateway” when creating a new payment method in the admin panel under Settings > Payments.

Show a logo and setup guide (optional)

Payment methods backed by an external provider also appear in the admin dashboard under Settings > Integrations, next to other connected services. A method is listed there when it inherits from Spree::Gateway, or when its class defines self.third_party? to return true. Declare a logo and a link to your setup guide so merchants recognise the provider and know how to connect it:
app/models/my_gateway.rb
logo_url accepts anything an image tag accepts, including a data: URI if you don’t want to host the file. Both are optional: without a logo the card shows the provider’s first letter, and without a guide the link is hidden. Integrations declare the same two methods — see custom delivery rate providers.

Step 3: Add Payment Session Support

Payment Sessions are the modern, PCI-compliant way to handle payments. Your gateway creates a session with the provider, the frontend collects payment details using the provider’s SDK, and Spree records the result.

Define the STI Subclass

Create a Payment Session subclass for your gateway. This uses Single Table Inheritance (STI) on the spree_payment_sessions table:
app/models/spree/payment_sessions/my_gateway.rb

Implement Session Methods on the Gateway

Override the key methods on your PaymentMethod subclass:
app/models/my_gateway.rb
If your provider lets each open session be paid on its own (as Stripe does with PaymentIntents), a customer with two open sessions can be charged twice — for example, with checkout open in two tabs. Have create_payment_session cancel the cart’s open sessions for your gateway before opening a new one, and implement cancel_payment_session. Leave a session the provider has already accepted, and do not open another. The built-in Stripe gateway does both. If you leave out cancel_payment_session, sessions still pending when the order is placed stay open at your provider.

How the Frontend Uses It

The frontend creates a session, then uses the provider’s SDK to collect payment:
Important: Always create the payment session after shipping is selected. If the order total changes (shipping rate change, coupon applied), update the session with the new amount (client.carts.paymentSessions.update) rather than creating another one. The complete call in step 5 only handles payment — step 6 finalizes the order.
Webhooks ensure payments are captured even if the customer closes their browser after paying. Spree provides a generic webhook endpoint at POST /api/v3/webhooks/payments/:payment_method_id — you don’t need to create your own controller or routes. Your gateway just needs to implement parse_webhook_event to normalize the provider-specific payload:
app/models/my_gateway.rb
That’s it. Spree handles the rest:
  • Verifies the signature synchronously (returns 401 if WebhookSignatureError is raised)
  • Enqueues a background job (Spree::Payments::HandleWebhookJob) for async processing
  • Returns 200 OK immediately to prevent provider retries
  • The job creates/updates the Payment record, marks the session completed, and completes the order
The webhook URL for your payment method is available via payment_method.webhook_url — register this with your provider during setup.

Supported Webhook Actions

Step 5: Control Payment Method Visibility (Optional)

Override visibility methods to control where and when your payment method appears:
app/models/my_gateway.rb

Step 6: Add Payment Setup Session Support (Optional)

If your provider supports saving payment methods for future use (like Stripe’s SetupIntent), you can add Payment Setup Session support:
app/models/my_gateway.rb
And the STI subclass:
app/models/spree/payment_setup_sessions/my_gateway.rb

Gateway Options Reference

For every gateway action (authorize, purchase, capture, void, credit), Spree passes a hash of gateway options: