@spree/docs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/api-reference/platform/authentication.md +38 -0
- package/dist/api-reference/store-api/authentication.md +188 -0
- package/dist/api-reference/store-api/errors.md +277 -0
- package/dist/api-reference/store-api/idempotency.md +129 -0
- package/dist/api-reference/store-api/introduction.md +34 -0
- package/dist/api-reference/store-api/localization.md +279 -0
- package/dist/api-reference/store-api/metadata.md +160 -0
- package/dist/api-reference/store-api/monetary-amounts.md +65 -0
- package/dist/api-reference/store-api/querying.md +399 -0
- package/dist/api-reference/store-api/rate-limitting.md +103 -0
- package/dist/api-reference/store-api/relations.md +185 -0
- package/dist/api-reference/storefront/authentication.md +88 -0
- package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +165 -0
- package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +194 -0
- package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +248 -0
- package/dist/api-reference/v2/fetching-multiple-resources.md +26 -0
- package/dist/api-reference/v2/filtering-and-sorting.md +53 -0
- package/dist/api-reference/v2/introduction.md +22 -0
- package/dist/api-reference/v2/pagination.md +37 -0
- package/dist/api-reference/webhooks-events.md +883 -0
- package/dist/developer/admin/admin.md +205 -0
- package/dist/developer/admin/authentication.md +59 -0
- package/dist/developer/admin/components.md +711 -0
- package/dist/developer/admin/custom-css.md +243 -0
- package/dist/developer/admin/custom-javascript.md +116 -0
- package/dist/developer/admin/extending-ui.md +1964 -0
- package/dist/developer/admin/form-builder.md +444 -0
- package/dist/developer/admin/helper-methods.md +531 -0
- package/dist/developer/admin/navigation.md +805 -0
- package/dist/developer/admin/tables.md +491 -0
- package/dist/developer/advanced/adding_spree_to_rails_app.md +106 -0
- package/dist/developer/cli/quickstart.md +137 -0
- package/dist/developer/contributing/creating-an-extension.md +258 -0
- package/dist/developer/contributing/developing-spree.md +339 -0
- package/dist/developer/contributing/quickstart.md +32 -0
- package/dist/developer/contributing/updating-extensions.md +67 -0
- package/dist/developer/core-concepts/addresses.md +265 -0
- package/dist/developer/core-concepts/adjustments.md +107 -0
- package/dist/developer/core-concepts/architecture.md +177 -0
- package/dist/developer/core-concepts/calculators.md +323 -0
- package/dist/developer/core-concepts/customers.md +230 -0
- package/dist/developer/core-concepts/events.md +624 -0
- package/dist/developer/core-concepts/imports-exports.md +698 -0
- package/dist/developer/core-concepts/inventory.md +191 -0
- package/dist/developer/core-concepts/markets.md +250 -0
- package/dist/developer/core-concepts/media.md +167 -0
- package/dist/developer/core-concepts/metafields.md +187 -0
- package/dist/developer/core-concepts/orders.md +328 -0
- package/dist/developer/core-concepts/payments.md +710 -0
- package/dist/developer/core-concepts/pricing.md +163 -0
- package/dist/developer/core-concepts/products.md +360 -0
- package/dist/developer/core-concepts/promotions.md +322 -0
- package/dist/developer/core-concepts/reports.md +206 -0
- package/dist/developer/core-concepts/search-filtering.md +237 -0
- package/dist/developer/core-concepts/shipments.md +212 -0
- package/dist/developer/core-concepts/slugs.md +111 -0
- package/dist/developer/core-concepts/staff-roles.md +123 -0
- package/dist/developer/core-concepts/store-credits-gift-cards.md +317 -0
- package/dist/developer/core-concepts/stores.md +117 -0
- package/dist/developer/core-concepts/taxes.md +135 -0
- package/dist/developer/core-concepts/translations.md +120 -0
- package/dist/developer/core-concepts/users.md +299 -0
- package/dist/developer/core-concepts/webhooks.md +378 -0
- package/dist/developer/create-spree-app/quickstart.md +158 -0
- package/dist/developer/customization/api.md +93 -0
- package/dist/developer/customization/authentication.md +88 -0
- package/dist/developer/customization/checkout.md +204 -0
- package/dist/developer/customization/configuration.md +55 -0
- package/dist/developer/customization/decorators.md +523 -0
- package/dist/developer/customization/dependencies.md +232 -0
- package/dist/developer/customization/emails.md +21 -0
- package/dist/developer/customization/extensions.md +92 -0
- package/dist/developer/customization/metadata.md +236 -0
- package/dist/developer/customization/model-preferences.md +130 -0
- package/dist/developer/customization/permissions.md +265 -0
- package/dist/developer/customization/quickstart.md +229 -0
- package/dist/developer/customization/routes.md +24 -0
- package/dist/developer/customization/v4/admin-panel.md +78 -0
- package/dist/developer/customization/v4/authentication.md +210 -0
- package/dist/developer/customization/v4/checkout.md +212 -0
- package/dist/developer/customization/v4/deface.md +251 -0
- package/dist/developer/customization/v4/images.md +86 -0
- package/dist/developer/customization/v4/storefront.md +450 -0
- package/dist/developer/deployment/assets.md +87 -0
- package/dist/developer/deployment/aws.md +335 -0
- package/dist/developer/deployment/caching.md +27 -0
- package/dist/developer/deployment/cdn.md +39 -0
- package/dist/developer/deployment/database.md +155 -0
- package/dist/developer/deployment/docker.md +128 -0
- package/dist/developer/deployment/emails.md +77 -0
- package/dist/developer/deployment/environment_variables.md +111 -0
- package/dist/developer/deployment/heroku.md +51 -0
- package/dist/developer/deployment/render.md +95 -0
- package/dist/developer/getting-started/quickstart.md +82 -0
- package/dist/developer/how-to/custom-payment-method.md +374 -0
- package/dist/developer/how-to/custom-promotion.md +373 -0
- package/dist/developer/how-to/custom-report.md +387 -0
- package/dist/developer/how-to/custom-search-provider.md +230 -0
- package/dist/developer/multi-store/quickstart.md +71 -0
- package/dist/developer/multi-store/setup.md +38 -0
- package/dist/developer/multi-tenant/configuration.md +41 -0
- package/dist/developer/multi-tenant/core-concepts.md +75 -0
- package/dist/developer/multi-tenant/installation.md +96 -0
- package/dist/developer/multi-tenant/quickstart.md +20 -0
- package/dist/developer/multi-vendor/installation.md +45 -0
- package/dist/developer/multi-vendor/quickstart.md +17 -0
- package/dist/developer/sdk/admin/quickstart.md +22 -0
- package/dist/developer/sdk/authentication.md +89 -0
- package/dist/developer/sdk/configuration.md +225 -0
- package/dist/developer/sdk/quickstart.md +82 -0
- package/dist/developer/sdk/store/account.md +67 -0
- package/dist/developer/sdk/store/cart-checkout.md +140 -0
- package/dist/developer/sdk/store/markets.md +151 -0
- package/dist/developer/sdk/store/payments.md +96 -0
- package/dist/developer/sdk/store/products.md +149 -0
- package/dist/developer/sdk/store/wishlists.md +52 -0
- package/dist/developer/security/pci_compliance.md +15 -0
- package/dist/developer/security/security_policy.md +68 -0
- package/dist/developer/storefront/blocks.md +285 -0
- package/dist/developer/storefront/custom-css.md +260 -0
- package/dist/developer/storefront/custom-javascript.md +166 -0
- package/dist/developer/storefront/helper-methods.md +1288 -0
- package/dist/developer/storefront/links.md +298 -0
- package/dist/developer/storefront/nextjs/architecture.md +150 -0
- package/dist/developer/storefront/nextjs/customization.md +141 -0
- package/dist/developer/storefront/nextjs/deployment.md +180 -0
- package/dist/developer/storefront/nextjs/quickstart.md +92 -0
- package/dist/developer/storefront/nextjs/spree-next-package.md +314 -0
- package/dist/developer/storefront/pages.md +163 -0
- package/dist/developer/storefront/sections.md +569 -0
- package/dist/developer/storefront/storefront.md +56 -0
- package/dist/developer/storefront/themes.md +161 -0
- package/dist/developer/tutorial/admin.md +134 -0
- package/dist/developer/tutorial/extending-models.md +380 -0
- package/dist/developer/tutorial/file-uploads.md +121 -0
- package/dist/developer/tutorial/introduction.md +33 -0
- package/dist/developer/tutorial/model.md +41 -0
- package/dist/developer/tutorial/page-builder.md +487 -0
- package/dist/developer/tutorial/rich-text.md +73 -0
- package/dist/developer/tutorial/seo.md +332 -0
- package/dist/developer/tutorial/storefront.md +352 -0
- package/dist/developer/tutorial/testing.md +558 -0
- package/dist/developer/upgrades/2.0-to-2.1.md +46 -0
- package/dist/developer/upgrades/2.1-to-2.2.md +59 -0
- package/dist/developer/upgrades/2.2-to-2.3.md +44 -0
- package/dist/developer/upgrades/2.3-to-2.4.md +42 -0
- package/dist/developer/upgrades/3.0-to-3.1.md +47 -0
- package/dist/developer/upgrades/3.1-to-3.2.md +34 -0
- package/dist/developer/upgrades/3.2-to-3.3.md +70 -0
- package/dist/developer/upgrades/3.3-to-3.4.md +36 -0
- package/dist/developer/upgrades/3.4-to-3.5.md +44 -0
- package/dist/developer/upgrades/3.5-to-3.6.md +40 -0
- package/dist/developer/upgrades/3.6-to-3.7.md +62 -0
- package/dist/developer/upgrades/3.7-to-4.0.md +152 -0
- package/dist/developer/upgrades/4.0-to-4.1.md +92 -0
- package/dist/developer/upgrades/4.1-to-4.2.md +109 -0
- package/dist/developer/upgrades/4.10-to-5.0.md +129 -0
- package/dist/developer/upgrades/4.2-to-4.3.md +100 -0
- package/dist/developer/upgrades/4.3-to-4.4.md +125 -0
- package/dist/developer/upgrades/4.4-to-4.5.md +94 -0
- package/dist/developer/upgrades/4.5-to-4.6.md +119 -0
- package/dist/developer/upgrades/4.6-to-4.7.md +39 -0
- package/dist/developer/upgrades/4.8-to-4.9.md +24 -0
- package/dist/developer/upgrades/4.9-to-4.10.md +24 -0
- package/dist/developer/upgrades/4.x-to-4.8.md +52 -0
- package/dist/developer/upgrades/5.0-to-5.1.md +28 -0
- package/dist/developer/upgrades/5.1-to-5.2.md +127 -0
- package/dist/developer/upgrades/5.2-to-5.3.md +338 -0
- package/dist/developer/upgrades/5.3-to-5.4.md +248 -0
- package/dist/developer/upgrades/quickstart.md +36 -0
- package/dist/integrations/analytics/google-analytics.md +64 -0
- package/dist/integrations/analytics/google-tag-manager.md +78 -0
- package/dist/integrations/integrations.md +39 -0
- package/dist/integrations/marketing/klaviyo.md +99 -0
- package/dist/integrations/payments/adyen.md +90 -0
- package/dist/integrations/payments/paypal.md +41 -0
- package/dist/integrations/payments/razorpay.md +45 -0
- package/dist/integrations/payments/stripe.md +109 -0
- package/dist/integrations/search/meilisearch.md +236 -0
- package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +57 -0
- package/dist/integrations/sso-mfa-social-login/storefront.md +56 -0
- package/package.json +27 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Build a Custom Payment Method
|
|
3
|
+
description: Step-by-step guide to creating a custom payment gateway integration with Payment Sessions, 3D Secure, and PCI compliance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
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:
|
|
9
|
+
|
|
10
|
+
- Appears as a payment option during checkout
|
|
11
|
+
- Uses Payment Sessions for PCI-compliant payment collection
|
|
12
|
+
- Supports 3D Secure and alternative payment methods
|
|
13
|
+
- Handles webhooks for reliable payment confirmation
|
|
14
|
+
- Optionally supports saving payment methods for future use (Payment Setup Sessions)
|
|
15
|
+
|
|
16
|
+
Before starting, make sure you understand [how payments work in Spree](/developer/core-concepts/payments).
|
|
17
|
+
|
|
18
|
+
## Step 1: Create the Payment Method Model
|
|
19
|
+
|
|
20
|
+
Create a new model inheriting from `Spree::PaymentMethod`. This is the central class that defines how your gateway processes payments.
|
|
21
|
+
|
|
22
|
+
```ruby app/models/my_gateway.rb
|
|
23
|
+
class MyGateway < Spree::PaymentMethod
|
|
24
|
+
preference :api_key, :string
|
|
25
|
+
preference :publishable_key, :string
|
|
26
|
+
preference :webhook_secret, :string
|
|
27
|
+
|
|
28
|
+
def payment_icon_name
|
|
29
|
+
'my-gateway'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Step 2: Register the Payment Method
|
|
35
|
+
|
|
36
|
+
Add your gateway to the list of available payment methods so it appears in the admin panel:
|
|
37
|
+
|
|
38
|
+
```ruby config/initializers/spree.rb
|
|
39
|
+
Rails.application.config.after_initialize do
|
|
40
|
+
Spree.payment_methods << MyGateway
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
After restarting your server, you can select "MyGateway" when creating a new payment method in the admin panel under **Settings > Payments**.
|
|
45
|
+
|
|
46
|
+
## Step 3: Add Payment Session Support
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
### Define the STI Subclass
|
|
51
|
+
|
|
52
|
+
Create a Payment Session subclass for your gateway. This uses Single Table Inheritance (STI) on the `spree_payment_sessions` table:
|
|
53
|
+
|
|
54
|
+
```ruby app/models/spree/payment_sessions/my_gateway.rb
|
|
55
|
+
module Spree
|
|
56
|
+
module PaymentSessions
|
|
57
|
+
class MyGateway < Spree::PaymentSession
|
|
58
|
+
# Add gateway-specific helper methods here
|
|
59
|
+
|
|
60
|
+
def client_secret
|
|
61
|
+
external_data['client_secret']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Implement Session Methods on the Gateway
|
|
69
|
+
|
|
70
|
+
Override the key methods on your `PaymentMethod` subclass:
|
|
71
|
+
|
|
72
|
+
```ruby app/models/my_gateway.rb
|
|
73
|
+
class MyGateway < Spree::PaymentMethod
|
|
74
|
+
preference :api_key, :string
|
|
75
|
+
preference :publishable_key, :string
|
|
76
|
+
preference :webhook_secret, :string
|
|
77
|
+
|
|
78
|
+
# Tell Spree this gateway uses Payment Sessions
|
|
79
|
+
def session_required?
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Return the STI subclass
|
|
84
|
+
def payment_session_class
|
|
85
|
+
Spree::PaymentSessions::MyGateway
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# No source required upfront — it's created when the session completes
|
|
89
|
+
def source_required?
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Create a session with your payment provider
|
|
94
|
+
def create_payment_session(order:, amount: nil, external_data: {})
|
|
95
|
+
total = amount || order.total_minus_store_credits
|
|
96
|
+
|
|
97
|
+
# Call your provider's API to create a payment session
|
|
98
|
+
provider_session = MyProvider::Client.new(preferred_api_key).create_session(
|
|
99
|
+
amount: (total * 100).to_i, # amount in cents
|
|
100
|
+
currency: order.currency,
|
|
101
|
+
metadata: { order_number: order.number }
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
payment_sessions.create!(
|
|
105
|
+
order: order,
|
|
106
|
+
amount: total,
|
|
107
|
+
currency: order.currency,
|
|
108
|
+
external_id: provider_session.id,
|
|
109
|
+
external_data: {
|
|
110
|
+
client_secret: provider_session.client_secret
|
|
111
|
+
},
|
|
112
|
+
customer: order.user
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Update an existing session (e.g., when order total changes)
|
|
117
|
+
def update_payment_session(payment_session:, amount: nil, external_data: {})
|
|
118
|
+
if amount.present?
|
|
119
|
+
MyProvider::Client.new(preferred_api_key).update_session(
|
|
120
|
+
payment_session.external_id,
|
|
121
|
+
amount: (amount * 100).to_i
|
|
122
|
+
)
|
|
123
|
+
payment_session.update!(amount: amount)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
payment_session
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Complete the session after the customer pays on the frontend.
|
|
130
|
+
#
|
|
131
|
+
# Responsibilities:
|
|
132
|
+
# - Verify payment status with the provider
|
|
133
|
+
# - Create the Spree::Payment record
|
|
134
|
+
# - Transition the payment to the correct state
|
|
135
|
+
# - Mark the session as completed/failed
|
|
136
|
+
#
|
|
137
|
+
# Must NOT complete the order — that is handled by Carts::Complete
|
|
138
|
+
# (called by the frontend or by the webhook handler).
|
|
139
|
+
def complete_payment_session(payment_session:, params: {})
|
|
140
|
+
# Verify the payment with your provider
|
|
141
|
+
result = MyProvider::Client.new(preferred_api_key).retrieve_session(
|
|
142
|
+
payment_session.external_id
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if result.status == 'succeeded'
|
|
146
|
+
payment_session.process!
|
|
147
|
+
|
|
148
|
+
# Create the Spree::Payment record
|
|
149
|
+
payment = payment_session.find_or_create_payment!
|
|
150
|
+
|
|
151
|
+
# Transition the payment to completed
|
|
152
|
+
if payment.present? && !payment.completed?
|
|
153
|
+
payment.started_processing! if payment.checkout?
|
|
154
|
+
payment.complete! if payment.can_complete?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
payment_session.complete!
|
|
158
|
+
else
|
|
159
|
+
payment_session.fail!
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def payment_icon_name
|
|
164
|
+
'my-gateway'
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### How the Frontend Uses It
|
|
170
|
+
|
|
171
|
+
The frontend creates a session, then uses the provider's SDK to collect payment:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// 1. Get available payment methods
|
|
175
|
+
const methods = await client.carts.paymentMethods.list(cart.id, options)
|
|
176
|
+
|
|
177
|
+
// 2. Find your gateway (check session_required flag)
|
|
178
|
+
const myGateway = methods.find(m => m.session_required)
|
|
179
|
+
|
|
180
|
+
// 3. Create a payment session (after shipping is selected so amount is correct)
|
|
181
|
+
const session = await client.carts.paymentSessions.create(cart.id, {
|
|
182
|
+
payment_method_id: myGateway.id,
|
|
183
|
+
}, options)
|
|
184
|
+
|
|
185
|
+
// 4. Use the client_secret with your provider's frontend SDK
|
|
186
|
+
const result = await MyProviderSDK.confirmPayment(session.external_data.client_secret)
|
|
187
|
+
|
|
188
|
+
// 5. Complete the payment session (creates Payment record, does NOT complete order)
|
|
189
|
+
const completed = await client.carts.paymentSessions.complete(
|
|
190
|
+
cart.id, session.id,
|
|
191
|
+
{ session_result: result.id },
|
|
192
|
+
options
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
// 6. Complete the order
|
|
196
|
+
const order = await client.carts.complete(cart.id, options)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
> **INFO:** **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.
|
|
200
|
+
|
|
201
|
+
## Step 4: Handle Webhooks (Recommended)
|
|
202
|
+
|
|
203
|
+
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.
|
|
204
|
+
|
|
205
|
+
Your gateway just needs to implement `parse_webhook_event` to normalize the provider-specific payload:
|
|
206
|
+
|
|
207
|
+
```ruby app/models/my_gateway.rb
|
|
208
|
+
class MyGateway < Spree::PaymentMethod
|
|
209
|
+
# ... existing code ...
|
|
210
|
+
|
|
211
|
+
# Parse incoming webhook from your payment provider.
|
|
212
|
+
#
|
|
213
|
+
# @param raw_body [String] the raw request body
|
|
214
|
+
# @param headers [Hash] the request headers
|
|
215
|
+
# @return [Hash, nil] normalized result, or nil for unsupported events
|
|
216
|
+
# @raise [Spree::PaymentMethod::WebhookSignatureError] if signature is invalid
|
|
217
|
+
def parse_webhook_event(raw_body, headers)
|
|
218
|
+
# 1. Verify the webhook signature
|
|
219
|
+
signature = headers['HTTP_X_SIGNATURE']
|
|
220
|
+
unless MyProvider::Webhook.verify(raw_body, signature, preferred_webhook_secret)
|
|
221
|
+
raise Spree::PaymentMethod::WebhookSignatureError
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# 2. Parse the payload
|
|
225
|
+
event = JSON.parse(raw_body)
|
|
226
|
+
|
|
227
|
+
# 3. Return a normalized result
|
|
228
|
+
case event['type']
|
|
229
|
+
when 'payment.succeeded'
|
|
230
|
+
session = Spree::PaymentSession.find_by(external_id: event.dig('data', 'session_id'))
|
|
231
|
+
return nil unless session
|
|
232
|
+
|
|
233
|
+
{ action: :captured, payment_session: session }
|
|
234
|
+
when 'payment.failed'
|
|
235
|
+
session = Spree::PaymentSession.find_by(external_id: event.dig('data', 'session_id'))
|
|
236
|
+
return nil unless session
|
|
237
|
+
|
|
238
|
+
{ action: :failed, payment_session: session }
|
|
239
|
+
else
|
|
240
|
+
nil # Unsupported event — Spree will return 200 OK and ignore it
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**That's it.** Spree handles the rest:
|
|
247
|
+
- Verifies the signature synchronously (returns `401` if `WebhookSignatureError` is raised)
|
|
248
|
+
- Enqueues a background job (`Spree::Payments::HandleWebhookJob`) for async processing
|
|
249
|
+
- Returns `200 OK` immediately to prevent provider retries
|
|
250
|
+
- The job creates/updates the `Payment` record, marks the session completed, and completes the order
|
|
251
|
+
|
|
252
|
+
The webhook URL for your payment method is available via `payment_method.webhook_url` — register this with your provider during setup.
|
|
253
|
+
|
|
254
|
+
### Supported Webhook Actions
|
|
255
|
+
|
|
256
|
+
| Action | Description |
|
|
257
|
+
|--------|-------------|
|
|
258
|
+
| `:captured` | Payment was captured (charged). Creates payment and completes order. |
|
|
259
|
+
| `:authorized` | Payment was authorized (not yet captured). Creates payment and completes order. |
|
|
260
|
+
| `:failed` | Payment failed. Marks the session as failed. |
|
|
261
|
+
| `:canceled` | Payment was canceled. Marks the session as canceled. |
|
|
262
|
+
|
|
263
|
+
## Step 5: Control Payment Method Visibility (Optional)
|
|
264
|
+
|
|
265
|
+
Override visibility methods to control where and when your payment method appears:
|
|
266
|
+
|
|
267
|
+
```ruby app/models/my_gateway.rb
|
|
268
|
+
class MyGateway < Spree::PaymentMethod
|
|
269
|
+
# Only show for stores that support specific currencies
|
|
270
|
+
def available_for_store?(store)
|
|
271
|
+
store.supported_currencies.include?('EUR')
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Only show for orders above a certain amount
|
|
275
|
+
def available_for_order?(order)
|
|
276
|
+
order.total > 10 && order.currency == 'EUR'
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Step 6: Add Payment Setup Session Support (Optional)
|
|
282
|
+
|
|
283
|
+
If your provider supports saving payment methods for future use (like Stripe's SetupIntent), you can add Payment Setup Session support:
|
|
284
|
+
|
|
285
|
+
```ruby app/models/my_gateway.rb
|
|
286
|
+
class MyGateway < Spree::PaymentMethod
|
|
287
|
+
# ... existing code ...
|
|
288
|
+
|
|
289
|
+
def setup_session_supported?
|
|
290
|
+
true
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def payment_setup_session_class
|
|
294
|
+
Spree::PaymentSetupSessions::MyGateway
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def create_payment_setup_session(customer:, external_data: {})
|
|
298
|
+
provider_setup = MyProvider::Client.new(preferred_api_key).create_setup_session(
|
|
299
|
+
customer_id: find_or_create_provider_customer(customer)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
payment_setup_sessions.create!(
|
|
303
|
+
customer: customer,
|
|
304
|
+
external_id: provider_setup.id,
|
|
305
|
+
external_client_secret: provider_setup.client_secret
|
|
306
|
+
)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def complete_payment_setup_session(setup_session:, params: {})
|
|
310
|
+
result = MyProvider::Client.new(preferred_api_key).retrieve_setup_session(
|
|
311
|
+
setup_session.external_id
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if result.status == 'succeeded'
|
|
315
|
+
# Create a saved payment source
|
|
316
|
+
credit_card = Spree::CreditCard.create!(
|
|
317
|
+
payment_method: self,
|
|
318
|
+
user: setup_session.customer,
|
|
319
|
+
gateway_payment_profile_id: result.payment_method_id,
|
|
320
|
+
cc_type: result.card_brand,
|
|
321
|
+
last_digits: result.card_last4,
|
|
322
|
+
month: result.card_exp_month,
|
|
323
|
+
year: result.card_exp_year,
|
|
324
|
+
name: result.cardholder_name
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
setup_session.update!(
|
|
328
|
+
payment_source: credit_card
|
|
329
|
+
)
|
|
330
|
+
setup_session.complete!
|
|
331
|
+
else
|
|
332
|
+
setup_session.fail!
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
setup_session
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
And the STI subclass:
|
|
341
|
+
|
|
342
|
+
```ruby app/models/spree/payment_setup_sessions/my_gateway.rb
|
|
343
|
+
module Spree
|
|
344
|
+
module PaymentSetupSessions
|
|
345
|
+
class MyGateway < Spree::PaymentSetupSession
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Gateway Options Reference
|
|
352
|
+
|
|
353
|
+
For every gateway action (authorize, purchase, capture, void, credit), Spree passes a hash of gateway options:
|
|
354
|
+
|
|
355
|
+
| Option | Description |
|
|
356
|
+
|--------------------|-----------------------------------------------------------------------------------------------|
|
|
357
|
+
| `email` and `customer` | The email address related to the order |
|
|
358
|
+
| `ip` | The last IP address for the order |
|
|
359
|
+
| `order_id` | The Order's `number` attribute, plus the `identifier` for each payment |
|
|
360
|
+
| `shipping` | The total shipping cost for the order, in cents |
|
|
361
|
+
| `tax` | The total tax cost for the order, in cents |
|
|
362
|
+
| `subtotal` | The item total for the order, in cents |
|
|
363
|
+
| `currency` | The 3-character currency code for the order |
|
|
364
|
+
| `discount` | The promotional discount applied to the order |
|
|
365
|
+
| `billing_address` | A hash containing billing address information |
|
|
366
|
+
| `shipping_address` | A hash containing shipping address information |
|
|
367
|
+
|
|
368
|
+
## Related Documentation
|
|
369
|
+
|
|
370
|
+
- [Payments](/developer/core-concepts/payments) - Payment architecture and core concepts
|
|
371
|
+
- [Checkout Customization](/developer/customization/checkout) - Customizing the checkout flow
|
|
372
|
+
- [Events](/developer/core-concepts/events) - Subscribe to payment events
|
|
373
|
+
- [Stripe Integration](/integrations/payments/stripe) - Reference implementation using Stripe
|
|
374
|
+
- [Adyen Integration](/integrations/payments/adyen) - Reference implementation using Adyen
|