@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,624 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Events
|
|
3
|
+
description: Learn how Spree's event system works and how to subscribe to events.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Spree includes a powerful event system that allows you to react to various actions happening in your store. When something happens (an order is completed, a product is created, etc.), Spree publishes an event that your code can subscribe to and handle.
|
|
9
|
+
|
|
10
|
+
This pattern enables loose coupling between components and makes it easy to:
|
|
11
|
+
|
|
12
|
+
- Send email notifications when orders are placed
|
|
13
|
+
- Sync data with external services when products change
|
|
14
|
+
- Log audit trails for compliance
|
|
15
|
+
- Trigger webhooks to notify third-party systems
|
|
16
|
+
- Update caches when inventory changes
|
|
17
|
+
|
|
18
|
+
## How Events Work
|
|
19
|
+
|
|
20
|
+
Spree's event system provides a clean API through:
|
|
21
|
+
|
|
22
|
+
1. **`Spree::Events`** - The main module for publishing and subscribing to events
|
|
23
|
+
2. **`Spree::Subscriber`** - Base class for creating event subscribers
|
|
24
|
+
3. **`Spree::Publishable`** - Concern that enables models to publish events
|
|
25
|
+
|
|
26
|
+
When an event is published, all matching subscribers are notified. By default, subscribers run asynchronously via background jobs to avoid blocking the main request.
|
|
27
|
+
|
|
28
|
+
```mermaid
|
|
29
|
+
flowchart TB
|
|
30
|
+
subgraph Spree Application
|
|
31
|
+
A[Model Action] --> B[publish_event]
|
|
32
|
+
B --> C[Event Serializer]
|
|
33
|
+
C --> D[Spree::Events]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
subgraph Event Adapter
|
|
37
|
+
D --> E[Find Matching Subscribers]
|
|
38
|
+
E --> F{Async?}
|
|
39
|
+
F -->|Yes| G[Queue Background Job]
|
|
40
|
+
F -->|No| H[Execute Immediately]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
subgraph Subscribers
|
|
44
|
+
G --> I[SubscriberJob]
|
|
45
|
+
I --> J[Your Subscriber]
|
|
46
|
+
H --> J
|
|
47
|
+
J --> K[Send Email]
|
|
48
|
+
J --> L[Sync External Service]
|
|
49
|
+
J --> M[Update Cache]
|
|
50
|
+
J --> N[Trigger Webhook]
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Creating a Subscriber
|
|
55
|
+
|
|
56
|
+
Create a subscriber class in `app/subscribers/` that inherits from `Spree::Subscriber`:
|
|
57
|
+
|
|
58
|
+
```ruby app/subscribers/my_app/order_completed_subscriber.rb
|
|
59
|
+
module MyApp
|
|
60
|
+
class OrderCompletedSubscriber < Spree::Subscriber
|
|
61
|
+
subscribes_to 'order.complete'
|
|
62
|
+
|
|
63
|
+
def handle(event)
|
|
64
|
+
order_id = event.payload['id']
|
|
65
|
+
order = Spree::Order.find_by(id: order_id)
|
|
66
|
+
return unless order
|
|
67
|
+
|
|
68
|
+
# Your custom logic here
|
|
69
|
+
ExternalService.notify_order_placed(order)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Subscriber DSL
|
|
76
|
+
|
|
77
|
+
The `Spree::Subscriber` class provides a clean DSL for declaring subscriptions:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class MySubscriber < Spree::Subscriber
|
|
81
|
+
# Subscribe to a single event
|
|
82
|
+
subscribes_to 'order.complete'
|
|
83
|
+
|
|
84
|
+
# Subscribe to multiple events
|
|
85
|
+
subscribes_to 'order.complete', 'order.cancel', 'order.resume'
|
|
86
|
+
|
|
87
|
+
# Subscribe to all events matching a pattern
|
|
88
|
+
subscribes_to 'order.*' # All order events
|
|
89
|
+
subscribes_to '*.*' # All events (use sparingly!)
|
|
90
|
+
|
|
91
|
+
# Run synchronously instead of via background job
|
|
92
|
+
subscribes_to 'order.complete', async: false
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Handling Multiple Events
|
|
97
|
+
|
|
98
|
+
When subscribing to multiple events, use the `on` DSL to route events to specific methods:
|
|
99
|
+
|
|
100
|
+
```ruby app/subscribers/my_app/order_audit_subscriber.rb
|
|
101
|
+
module MyApp
|
|
102
|
+
class OrderAuditSubscriber < Spree::Subscriber
|
|
103
|
+
subscribes_to 'order.complete', 'order.cancel', 'order.resume'
|
|
104
|
+
|
|
105
|
+
on 'order.complete', :log_order_completed
|
|
106
|
+
on 'order.cancel', :log_order_canceled
|
|
107
|
+
on 'order.resume', :log_order_resumed
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def log_order_completed(event)
|
|
112
|
+
create_audit_log(event, 'completed')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def log_order_canceled(event)
|
|
116
|
+
create_audit_log(event, 'canceled')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def log_order_resumed(event)
|
|
120
|
+
create_audit_log(event, 'resumed')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def create_audit_log(event, action)
|
|
124
|
+
AuditLog.create!(
|
|
125
|
+
resource_type: 'Spree::Order',
|
|
126
|
+
resource_id: event.payload['id'],
|
|
127
|
+
action: action,
|
|
128
|
+
occurred_at: event.created_at
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Working with Events
|
|
136
|
+
|
|
137
|
+
### Event Object
|
|
138
|
+
|
|
139
|
+
When your subscriber receives an event, you get a `Spree::Event` object with:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
def handle(event)
|
|
143
|
+
event.id # => "550e8400-e29b-41d4-a716-446655440000" (UUID)
|
|
144
|
+
event.name # => "order.complete"
|
|
145
|
+
event.store_id # => 1 (ID of the store where the event originated)
|
|
146
|
+
event.payload # => { "id" => 1, "number" => "R123456", ... }
|
|
147
|
+
event.metadata # => { "spree_version" => "5.1.0" }
|
|
148
|
+
event.created_at # => Time when event was published
|
|
149
|
+
|
|
150
|
+
# Helper methods
|
|
151
|
+
event.store # => Spree::Store instance (lazy loaded)
|
|
152
|
+
event.resource_type # => "order" (extracted from name)
|
|
153
|
+
event.action # => "complete" (extracted from name)
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Finding the Record
|
|
158
|
+
|
|
159
|
+
The payload contains serialized attributes, not the actual record. To get the record:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
def handle(event)
|
|
163
|
+
record_id = event.payload['id']
|
|
164
|
+
record = Spree::Order.find_by(id: record_id)
|
|
165
|
+
return unless record
|
|
166
|
+
|
|
167
|
+
# Work with the record
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
> **WARNING:** For destroy events, the record no longer exists in the database. Use the payload data instead, or capture what you need before deletion.
|
|
172
|
+
|
|
173
|
+
## Available Events
|
|
174
|
+
|
|
175
|
+
### Lifecycle Events
|
|
176
|
+
|
|
177
|
+
Models that include `Spree::Publishable` and call `publishes_lifecycle_events` automatically publish:
|
|
178
|
+
|
|
179
|
+
| Event Pattern | Description |
|
|
180
|
+
|---------------|-------------|
|
|
181
|
+
| `{model}.created` | Record was created |
|
|
182
|
+
| `{model}.updated` | Record was updated |
|
|
183
|
+
| `{model}.deleted` | Record was deleted |
|
|
184
|
+
|
|
185
|
+
For example, `Spree::Price` publishes `price.created`, `price.updated`, and `price.deleted`.
|
|
186
|
+
|
|
187
|
+
Models with lifecycle events enabled include: `Order`, `Payment`, `Price`, `Shipment`, `Variant`, `LineItem`, `StockItem`, and many others.
|
|
188
|
+
|
|
189
|
+
### Order Events
|
|
190
|
+
|
|
191
|
+
| Event | Description |
|
|
192
|
+
|-------|-------------|
|
|
193
|
+
| `order.created` | Order was created |
|
|
194
|
+
| `order.updated` | Order was updated |
|
|
195
|
+
| `order.completed` | Order checkout completed |
|
|
196
|
+
| `order.canceled` | Order was canceled |
|
|
197
|
+
| `order.resumed` | Canceled order was resumed |
|
|
198
|
+
| `order.paid` | Order is fully paid |
|
|
199
|
+
| `order.shipped` | All order shipments are shipped |
|
|
200
|
+
|
|
201
|
+
### Shipment Events
|
|
202
|
+
|
|
203
|
+
| Event | Description |
|
|
204
|
+
|-------|-------------|
|
|
205
|
+
| `shipment.created` | Shipment was created |
|
|
206
|
+
| `shipment.updated` | Shipment was updated |
|
|
207
|
+
| `shipment.shipped` | Shipment was shipped |
|
|
208
|
+
| `shipment.canceled` | Shipment was canceled |
|
|
209
|
+
| `shipment.resumed` | Shipment was resumed |
|
|
210
|
+
|
|
211
|
+
### Payment Events
|
|
212
|
+
|
|
213
|
+
| Event | Description |
|
|
214
|
+
|-------|-------------|
|
|
215
|
+
| `payment.created` | Payment was created |
|
|
216
|
+
| `payment.updated` | Payment was updated |
|
|
217
|
+
| `payment.paid` | Payment was completed |
|
|
218
|
+
|
|
219
|
+
### Price Events
|
|
220
|
+
|
|
221
|
+
| Event | Description |
|
|
222
|
+
|-------|-------------|
|
|
223
|
+
| `price.created` | Price was created |
|
|
224
|
+
| `price.updated` | Price was updated |
|
|
225
|
+
| `price.deleted` | Price was deleted |
|
|
226
|
+
|
|
227
|
+
### Customer Events
|
|
228
|
+
|
|
229
|
+
| Event | Description |
|
|
230
|
+
|-------|-------------|
|
|
231
|
+
| `customer.created` | Customer was created |
|
|
232
|
+
| `customer.updated` | Customer was updated |
|
|
233
|
+
| `customer.deleted` | Customer was deleted |
|
|
234
|
+
|
|
235
|
+
### Admin Events
|
|
236
|
+
|
|
237
|
+
| Event | Description |
|
|
238
|
+
|-------|-------------|
|
|
239
|
+
| `admin.created` | Admin user was created |
|
|
240
|
+
| `admin.updated` | Admin user was updated |
|
|
241
|
+
| `admin.deleted` | Admin user was deleted |
|
|
242
|
+
|
|
243
|
+
### Product Events
|
|
244
|
+
|
|
245
|
+
| Event | Description |
|
|
246
|
+
|-------|-------------|
|
|
247
|
+
| `product.activate` | Product status changed to active |
|
|
248
|
+
| `product.archive` | Product status changed to archived |
|
|
249
|
+
| `product.out_of_stock` | Product has no stock left for any variant |
|
|
250
|
+
| `product.back_in_stock` | Product was out of stock and now has stock again |
|
|
251
|
+
|
|
252
|
+
## Publishing Custom Events
|
|
253
|
+
|
|
254
|
+
You can publish custom events from anywhere in your application:
|
|
255
|
+
|
|
256
|
+
### From a Model
|
|
257
|
+
|
|
258
|
+
Models including `Spree::Publishable` can use `publish_event`:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
class Spree::Order < Spree.base_class
|
|
262
|
+
def mark_as_fraudulent!
|
|
263
|
+
update!(fraudulent: true)
|
|
264
|
+
publish_event('order.marked_fraudulent')
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### From Anywhere
|
|
270
|
+
|
|
271
|
+
Use `Spree::Events.publish` directly:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
Spree::Events.publish(
|
|
275
|
+
'inventory.low_stock',
|
|
276
|
+
{ variant_id: variant.id, quantity: variant.total_on_hand }
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Event Serializers
|
|
281
|
+
|
|
282
|
+
Event payloads are generated using the same [Store API V3 serializers](/api-reference/introduction) used by the REST API. This means webhook payloads and API responses share the same schema, making it easy to reuse types in your integrations.
|
|
283
|
+
|
|
284
|
+
### How Serializers Work
|
|
285
|
+
|
|
286
|
+
When a model publishes an event, Spree looks for a V3 serializer class matching the model name:
|
|
287
|
+
|
|
288
|
+
- `Spree::Order` → `Spree::Api::V3::OrderSerializer`
|
|
289
|
+
- `Spree::Product` → `Spree::Api::V3::ProductSerializer`
|
|
290
|
+
- `Spree::Payment` → `Spree::Api::V3::PaymentSerializer`
|
|
291
|
+
|
|
292
|
+
For STI models (e.g., `Spree::Exports::Products`), the serializer lookup walks up the class hierarchy until it finds a match (e.g., → `Spree::Api::V3::ExportSerializer`).
|
|
293
|
+
|
|
294
|
+
If no serializer is found, a minimal fallback payload is returned:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{ "id": "prod_86Rf07xd4z", "created_at": "2025-01-15T10:00:00Z", "updated_at": "2025-01-15T10:30:00Z" }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Built-in Serializers
|
|
301
|
+
|
|
302
|
+
Spree includes V3 serializers for all core models in [`api/app/serializers/spree/api/v3/`](https://github.com/spree/spree/tree/main/api/app/serializers/spree/api/v3):
|
|
303
|
+
|
|
304
|
+
| Serializer | Model |
|
|
305
|
+
|------------|-------|
|
|
306
|
+
| `OrderSerializer` | Orders with totals, states, nested line items, shipments, payments, addresses |
|
|
307
|
+
| `ProductSerializer` | Products with pricing, stock status, availability |
|
|
308
|
+
| `PaymentSerializer` | Payments with amounts, states, nested payment method and source |
|
|
309
|
+
| `ShipmentSerializer` | Shipments with tracking, nested shipping method and rates |
|
|
310
|
+
| `LineItemSerializer` | Line items with quantity, pricing, nested option values |
|
|
311
|
+
| `VariantSerializer` | Variants with SKU, pricing, nested option values |
|
|
312
|
+
| `PriceSerializer` | Prices with amounts, currency, price list |
|
|
313
|
+
| ... | [And many more](https://github.com/spree/spree/tree/main/api/app/serializers/spree/api/v3) |
|
|
314
|
+
|
|
315
|
+
### Payload Context
|
|
316
|
+
|
|
317
|
+
Event serializers receive specific context parameters that control what data is included:
|
|
318
|
+
|
|
319
|
+
- **`store`** — Prefers the resource's store (e.g., `order.store`), falls back to `Spree::Current.store`
|
|
320
|
+
- **`currency`** — Uses `Spree::Current.currency` (with full fallback chain)
|
|
321
|
+
- **`user: nil`** — Events never include user-specific pricing
|
|
322
|
+
- **`includes: []`** — Conditional associations are not included in event payloads
|
|
323
|
+
|
|
324
|
+
This means event payloads contain the same top-level attributes and unconditional associations as API responses, but conditional associations (like product variants, images, or metafields) are excluded.
|
|
325
|
+
|
|
326
|
+
### Overriding Event Serializers
|
|
327
|
+
|
|
328
|
+
To customize the payload for existing events, create a custom V3 serializer and configure it via dependencies:
|
|
329
|
+
|
|
330
|
+
```ruby app/serializers/my_app/order_serializer.rb
|
|
331
|
+
module MyApp
|
|
332
|
+
class OrderSerializer < Spree::Api::V3::OrderSerializer
|
|
333
|
+
# Add custom attributes
|
|
334
|
+
attribute :loyalty_points do |order|
|
|
335
|
+
(order.total.to_f * 10).to_i
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
attribute :custom_field do |order|
|
|
339
|
+
order.custom_field
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```ruby config/initializers/spree.rb
|
|
346
|
+
Spree.api.order_serializer = 'MyApp::OrderSerializer'
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
> **WARNING:** When overriding serializers, make sure to include all attributes that webhooks and subscribers depend on. Removing attributes may break integrations.
|
|
350
|
+
|
|
351
|
+
### Serializers for Custom Models
|
|
352
|
+
|
|
353
|
+
If you add a custom model that publishes events, create a V3 serializer:
|
|
354
|
+
|
|
355
|
+
```ruby app/models/my_app/subscription.rb
|
|
356
|
+
module MyApp
|
|
357
|
+
class Subscription < Spree.base_class
|
|
358
|
+
publishes_lifecycle_events
|
|
359
|
+
|
|
360
|
+
def renew!
|
|
361
|
+
update!(renewed_at: Time.current)
|
|
362
|
+
publish_event('subscription.renewed')
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
```ruby app/serializers/spree/api/v3/subscription_serializer.rb
|
|
369
|
+
module Spree
|
|
370
|
+
module Api
|
|
371
|
+
module V3
|
|
372
|
+
class SubscriptionSerializer < BaseSerializer
|
|
373
|
+
typelize plan_name: :string, status: :string,
|
|
374
|
+
user_id: [:string, nullable: true],
|
|
375
|
+
renewed_at: [:string, nullable: true],
|
|
376
|
+
expires_at: [:string, nullable: true]
|
|
377
|
+
|
|
378
|
+
attributes :plan_name, :status,
|
|
379
|
+
renewed_at: :iso8601, expires_at: :iso8601,
|
|
380
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
381
|
+
|
|
382
|
+
attribute :user_id do |subscription|
|
|
383
|
+
subscription.user&.prefixed_id
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Models without a matching serializer will use a minimal fallback payload containing only `id`, `created_at`, and `updated_at`.
|
|
392
|
+
|
|
393
|
+
## Registering Subscribers
|
|
394
|
+
|
|
395
|
+
Subscribers in `app/subscribers/` are automatically registered during application initialization.
|
|
396
|
+
|
|
397
|
+
For subscribers in other locations, add them to the `Spree.subscribers` array in an initializer:
|
|
398
|
+
|
|
399
|
+
```ruby config/initializers/event_subscribers.rb
|
|
400
|
+
Rails.application.config.after_initialize do
|
|
401
|
+
Spree.subscribers << MyApp::CustomSubscriber
|
|
402
|
+
end
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
To remove a built-in subscriber:
|
|
406
|
+
|
|
407
|
+
```ruby config/initializers/event_subscribers.rb
|
|
408
|
+
Rails.application.config.after_initialize do
|
|
409
|
+
Spree.subscribers.delete(Spree::ExportSubscriber)
|
|
410
|
+
end
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Synchronous vs Asynchronous
|
|
414
|
+
|
|
415
|
+
By default, subscribers run asynchronously via a background job. This prevents slow subscriber code from blocking HTTP requests.
|
|
416
|
+
|
|
417
|
+
For critical operations that must complete before the request finishes, use synchronous mode:
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
class CriticalOrderHandler < Spree::Subscriber
|
|
421
|
+
subscribes_to 'order.complete', async: false
|
|
422
|
+
|
|
423
|
+
def handle(event)
|
|
424
|
+
# This runs immediately, blocking the request
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
> **WARNING:** Use synchronous subscribers sparingly. They can significantly slow down your application if the handler code is slow or makes external API calls.
|
|
430
|
+
|
|
431
|
+
## Temporarily Disabling Events
|
|
432
|
+
|
|
433
|
+
You can disable event publishing temporarily:
|
|
434
|
+
|
|
435
|
+
```ruby
|
|
436
|
+
Spree::Events.disable do
|
|
437
|
+
# Events published in this block won't trigger subscribers
|
|
438
|
+
order.complete!
|
|
439
|
+
end
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
This is useful for:
|
|
443
|
+
- Data migrations where you don't want to trigger side effects
|
|
444
|
+
- Test setup where subscribers would interfere
|
|
445
|
+
- Bulk operations where individual events would be too noisy
|
|
446
|
+
|
|
447
|
+
## Testing Subscribers
|
|
448
|
+
|
|
449
|
+
### Testing Event Handling
|
|
450
|
+
|
|
451
|
+
```ruby spec/subscribers/my_app/order_completed_subscriber_spec.rb
|
|
452
|
+
require 'spec_helper'
|
|
453
|
+
|
|
454
|
+
RSpec.describe MyApp::OrderCompletedSubscriber do
|
|
455
|
+
let(:order) { create(:completed_order_with_totals) }
|
|
456
|
+
let(:event) do
|
|
457
|
+
Spree::Event.new(
|
|
458
|
+
name: 'order.complete',
|
|
459
|
+
payload: order.event_payload
|
|
460
|
+
)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
describe '#handle' do
|
|
464
|
+
it 'notifies external service' do
|
|
465
|
+
expect(ExternalService).to receive(:notify_order_placed).with(order)
|
|
466
|
+
described_class.new.handle(event)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Testing Event Publishing
|
|
473
|
+
|
|
474
|
+
Use the `emit_webhook_event` matcher (if available) or stub the events:
|
|
475
|
+
|
|
476
|
+
```ruby
|
|
477
|
+
it 'publishes order.complete event' do
|
|
478
|
+
expect(Spree::Events).to receive(:publish).with(
|
|
479
|
+
'order.complete',
|
|
480
|
+
hash_including('id' => order.id)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
order.complete!
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Best Practices
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
- **Keep handlers fast** — Move slow operations to background jobs. Subscribers should do minimal work and delegate heavy lifting.
|
|
491
|
+
|
|
492
|
+
- **Handle missing records** — Always check if the record exists before processing. It may have been deleted between event publish and handler execution.
|
|
493
|
+
|
|
494
|
+
- **Be idempotent** — Design handlers to be safely re-run. Events might be delivered more than once in edge cases.
|
|
495
|
+
|
|
496
|
+
- **Use specific patterns** — Subscribe to specific events rather than wildcards when possible. This makes code easier to understand and debug.
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
## Example: Inventory Alert Subscriber
|
|
500
|
+
|
|
501
|
+
Here's a complete example of a subscriber that sends alerts when inventory is low:
|
|
502
|
+
|
|
503
|
+
```ruby app/subscribers/my_app/inventory_alert_subscriber.rb
|
|
504
|
+
module MyApp
|
|
505
|
+
class InventoryAlertSubscriber < Spree::Subscriber
|
|
506
|
+
subscribes_to 'stock_item.update'
|
|
507
|
+
|
|
508
|
+
LOW_STOCK_THRESHOLD = 10
|
|
509
|
+
|
|
510
|
+
def handle(event)
|
|
511
|
+
stock_item = find_stock_item(event)
|
|
512
|
+
return unless stock_item
|
|
513
|
+
return unless stock_dropped_below_threshold?(event, stock_item)
|
|
514
|
+
|
|
515
|
+
send_low_stock_alert(stock_item)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
private
|
|
519
|
+
|
|
520
|
+
def find_stock_item(event)
|
|
521
|
+
Spree::StockItem.find_by(id: event.payload['id'])
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def stock_dropped_below_threshold?(event, stock_item)
|
|
525
|
+
previous_count = event.payload['count_on_hand_before_last_save']
|
|
526
|
+
current_count = stock_item.count_on_hand
|
|
527
|
+
|
|
528
|
+
previous_count >= LOW_STOCK_THRESHOLD && current_count < LOW_STOCK_THRESHOLD
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def send_low_stock_alert(stock_item)
|
|
532
|
+
InventoryMailer.low_stock_alert(
|
|
533
|
+
variant: stock_item.variant,
|
|
534
|
+
stock_location: stock_item.stock_location,
|
|
535
|
+
count_on_hand: stock_item.count_on_hand
|
|
536
|
+
).deliver_later
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Custom Event Adapters
|
|
543
|
+
|
|
544
|
+
Spree's event system uses an adapter pattern, making it possible to swap the underlying event infrastructure. By default, Spree uses [`ActiveSupport::Notifications`](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html), but you can create custom adapters for other backends like Kafka, RabbitMQ, or Redis Pub/Sub.
|
|
545
|
+
|
|
546
|
+
### Configuring a Custom Adapter
|
|
547
|
+
|
|
548
|
+
Set your adapter class in an initializer:
|
|
549
|
+
|
|
550
|
+
```ruby config/initializers/spree.rb
|
|
551
|
+
Spree.events_adapter_class = 'MyApp::Events::KafkaAdapter'
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Creating a Custom Adapter
|
|
555
|
+
|
|
556
|
+
Inherit from `Spree::Events::Adapters::Base` and implement the required methods:
|
|
557
|
+
|
|
558
|
+
```ruby app/models/my_app/events/kafka_adapter.rb
|
|
559
|
+
module MyApp
|
|
560
|
+
module Events
|
|
561
|
+
class KafkaAdapter < Spree::Events::Adapters::Base
|
|
562
|
+
def publish(event_name, payload, metadata = {})
|
|
563
|
+
event = build_event(event_name, payload, metadata)
|
|
564
|
+
|
|
565
|
+
# Publish to Kafka
|
|
566
|
+
kafka_producer.produce(
|
|
567
|
+
event.to_json,
|
|
568
|
+
topic: "spree.#{event_name}"
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
event
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def subscribe(pattern, subscriber, options = {})
|
|
575
|
+
registry.register(pattern, subscriber, options)
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def unsubscribe(pattern, subscriber)
|
|
579
|
+
registry.unregister(pattern, subscriber)
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def activate!
|
|
583
|
+
@kafka_producer = Kafka.new(
|
|
584
|
+
seed_brokers: ENV['KAFKA_BROKERS']
|
|
585
|
+
).producer
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def deactivate!
|
|
589
|
+
@kafka_producer&.shutdown
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
private
|
|
593
|
+
|
|
594
|
+
attr_reader :kafka_producer
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Base Class Interface
|
|
601
|
+
|
|
602
|
+
The `Spree::Events::Adapters::Base` class defines the required interface:
|
|
603
|
+
|
|
604
|
+
| Method | Description |
|
|
605
|
+
|--------|-------------|
|
|
606
|
+
| `publish(event_name, payload, metadata)` | Publish an event, return `Spree::Event` |
|
|
607
|
+
| `subscribe(pattern, subscriber, options)` | Register a subscriber for a pattern |
|
|
608
|
+
| `unsubscribe(pattern, subscriber)` | Remove a subscriber |
|
|
609
|
+
| `activate!` | Called during application initialization |
|
|
610
|
+
| `deactivate!` | Called during shutdown |
|
|
611
|
+
|
|
612
|
+
The base class also provides helper methods:
|
|
613
|
+
- `build_event(name, payload, metadata)` - Creates a `Spree::Event` instance
|
|
614
|
+
- `subscriptions_for(event_name)` - Finds matching subscriptions from the registry
|
|
615
|
+
- `registry` - Access to the `Spree::Events::Registry` instance
|
|
616
|
+
|
|
617
|
+
> **INFO:** See `Spree::Events::Adapters::ActiveSupportNotifications` for a complete reference implementation.
|
|
618
|
+
|
|
619
|
+
## Related Documentation
|
|
620
|
+
|
|
621
|
+
- [Webhooks](/developer/core-concepts/webhooks) - HTTP callbacks for external integrations
|
|
622
|
+
- [Customization Quickstart](/developer/customization/quickstart) - Overview of all customization options
|
|
623
|
+
- [Decorators](/developer/customization/decorators) - When to use decorators vs events
|
|
624
|
+
- [Checkout Flow](/developer/customization/checkout) - Using events in checkout customization
|