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.

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

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), create a new payment session with the updated amount. 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: