@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.
Files changed (183) hide show
  1. package/README.md +54 -0
  2. package/dist/api-reference/platform/authentication.md +38 -0
  3. package/dist/api-reference/store-api/authentication.md +188 -0
  4. package/dist/api-reference/store-api/errors.md +277 -0
  5. package/dist/api-reference/store-api/idempotency.md +129 -0
  6. package/dist/api-reference/store-api/introduction.md +34 -0
  7. package/dist/api-reference/store-api/localization.md +279 -0
  8. package/dist/api-reference/store-api/metadata.md +160 -0
  9. package/dist/api-reference/store-api/monetary-amounts.md +65 -0
  10. package/dist/api-reference/store-api/querying.md +399 -0
  11. package/dist/api-reference/store-api/rate-limitting.md +103 -0
  12. package/dist/api-reference/store-api/relations.md +185 -0
  13. package/dist/api-reference/storefront/authentication.md +88 -0
  14. package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +165 -0
  15. package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +194 -0
  16. package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +248 -0
  17. package/dist/api-reference/v2/fetching-multiple-resources.md +26 -0
  18. package/dist/api-reference/v2/filtering-and-sorting.md +53 -0
  19. package/dist/api-reference/v2/introduction.md +22 -0
  20. package/dist/api-reference/v2/pagination.md +37 -0
  21. package/dist/api-reference/webhooks-events.md +883 -0
  22. package/dist/developer/admin/admin.md +205 -0
  23. package/dist/developer/admin/authentication.md +59 -0
  24. package/dist/developer/admin/components.md +711 -0
  25. package/dist/developer/admin/custom-css.md +243 -0
  26. package/dist/developer/admin/custom-javascript.md +116 -0
  27. package/dist/developer/admin/extending-ui.md +1964 -0
  28. package/dist/developer/admin/form-builder.md +444 -0
  29. package/dist/developer/admin/helper-methods.md +531 -0
  30. package/dist/developer/admin/navigation.md +805 -0
  31. package/dist/developer/admin/tables.md +491 -0
  32. package/dist/developer/advanced/adding_spree_to_rails_app.md +106 -0
  33. package/dist/developer/cli/quickstart.md +137 -0
  34. package/dist/developer/contributing/creating-an-extension.md +258 -0
  35. package/dist/developer/contributing/developing-spree.md +339 -0
  36. package/dist/developer/contributing/quickstart.md +32 -0
  37. package/dist/developer/contributing/updating-extensions.md +67 -0
  38. package/dist/developer/core-concepts/addresses.md +265 -0
  39. package/dist/developer/core-concepts/adjustments.md +107 -0
  40. package/dist/developer/core-concepts/architecture.md +177 -0
  41. package/dist/developer/core-concepts/calculators.md +323 -0
  42. package/dist/developer/core-concepts/customers.md +230 -0
  43. package/dist/developer/core-concepts/events.md +624 -0
  44. package/dist/developer/core-concepts/imports-exports.md +698 -0
  45. package/dist/developer/core-concepts/inventory.md +191 -0
  46. package/dist/developer/core-concepts/markets.md +250 -0
  47. package/dist/developer/core-concepts/media.md +167 -0
  48. package/dist/developer/core-concepts/metafields.md +187 -0
  49. package/dist/developer/core-concepts/orders.md +328 -0
  50. package/dist/developer/core-concepts/payments.md +710 -0
  51. package/dist/developer/core-concepts/pricing.md +163 -0
  52. package/dist/developer/core-concepts/products.md +360 -0
  53. package/dist/developer/core-concepts/promotions.md +322 -0
  54. package/dist/developer/core-concepts/reports.md +206 -0
  55. package/dist/developer/core-concepts/search-filtering.md +237 -0
  56. package/dist/developer/core-concepts/shipments.md +212 -0
  57. package/dist/developer/core-concepts/slugs.md +111 -0
  58. package/dist/developer/core-concepts/staff-roles.md +123 -0
  59. package/dist/developer/core-concepts/store-credits-gift-cards.md +317 -0
  60. package/dist/developer/core-concepts/stores.md +117 -0
  61. package/dist/developer/core-concepts/taxes.md +135 -0
  62. package/dist/developer/core-concepts/translations.md +120 -0
  63. package/dist/developer/core-concepts/users.md +299 -0
  64. package/dist/developer/core-concepts/webhooks.md +378 -0
  65. package/dist/developer/create-spree-app/quickstart.md +158 -0
  66. package/dist/developer/customization/api.md +93 -0
  67. package/dist/developer/customization/authentication.md +88 -0
  68. package/dist/developer/customization/checkout.md +204 -0
  69. package/dist/developer/customization/configuration.md +55 -0
  70. package/dist/developer/customization/decorators.md +523 -0
  71. package/dist/developer/customization/dependencies.md +232 -0
  72. package/dist/developer/customization/emails.md +21 -0
  73. package/dist/developer/customization/extensions.md +92 -0
  74. package/dist/developer/customization/metadata.md +236 -0
  75. package/dist/developer/customization/model-preferences.md +130 -0
  76. package/dist/developer/customization/permissions.md +265 -0
  77. package/dist/developer/customization/quickstart.md +229 -0
  78. package/dist/developer/customization/routes.md +24 -0
  79. package/dist/developer/customization/v4/admin-panel.md +78 -0
  80. package/dist/developer/customization/v4/authentication.md +210 -0
  81. package/dist/developer/customization/v4/checkout.md +212 -0
  82. package/dist/developer/customization/v4/deface.md +251 -0
  83. package/dist/developer/customization/v4/images.md +86 -0
  84. package/dist/developer/customization/v4/storefront.md +450 -0
  85. package/dist/developer/deployment/assets.md +87 -0
  86. package/dist/developer/deployment/aws.md +335 -0
  87. package/dist/developer/deployment/caching.md +27 -0
  88. package/dist/developer/deployment/cdn.md +39 -0
  89. package/dist/developer/deployment/database.md +155 -0
  90. package/dist/developer/deployment/docker.md +128 -0
  91. package/dist/developer/deployment/emails.md +77 -0
  92. package/dist/developer/deployment/environment_variables.md +111 -0
  93. package/dist/developer/deployment/heroku.md +51 -0
  94. package/dist/developer/deployment/render.md +95 -0
  95. package/dist/developer/getting-started/quickstart.md +82 -0
  96. package/dist/developer/how-to/custom-payment-method.md +374 -0
  97. package/dist/developer/how-to/custom-promotion.md +373 -0
  98. package/dist/developer/how-to/custom-report.md +387 -0
  99. package/dist/developer/how-to/custom-search-provider.md +230 -0
  100. package/dist/developer/multi-store/quickstart.md +71 -0
  101. package/dist/developer/multi-store/setup.md +38 -0
  102. package/dist/developer/multi-tenant/configuration.md +41 -0
  103. package/dist/developer/multi-tenant/core-concepts.md +75 -0
  104. package/dist/developer/multi-tenant/installation.md +96 -0
  105. package/dist/developer/multi-tenant/quickstart.md +20 -0
  106. package/dist/developer/multi-vendor/installation.md +45 -0
  107. package/dist/developer/multi-vendor/quickstart.md +17 -0
  108. package/dist/developer/sdk/admin/quickstart.md +22 -0
  109. package/dist/developer/sdk/authentication.md +89 -0
  110. package/dist/developer/sdk/configuration.md +225 -0
  111. package/dist/developer/sdk/quickstart.md +82 -0
  112. package/dist/developer/sdk/store/account.md +67 -0
  113. package/dist/developer/sdk/store/cart-checkout.md +140 -0
  114. package/dist/developer/sdk/store/markets.md +151 -0
  115. package/dist/developer/sdk/store/payments.md +96 -0
  116. package/dist/developer/sdk/store/products.md +149 -0
  117. package/dist/developer/sdk/store/wishlists.md +52 -0
  118. package/dist/developer/security/pci_compliance.md +15 -0
  119. package/dist/developer/security/security_policy.md +68 -0
  120. package/dist/developer/storefront/blocks.md +285 -0
  121. package/dist/developer/storefront/custom-css.md +260 -0
  122. package/dist/developer/storefront/custom-javascript.md +166 -0
  123. package/dist/developer/storefront/helper-methods.md +1288 -0
  124. package/dist/developer/storefront/links.md +298 -0
  125. package/dist/developer/storefront/nextjs/architecture.md +150 -0
  126. package/dist/developer/storefront/nextjs/customization.md +141 -0
  127. package/dist/developer/storefront/nextjs/deployment.md +180 -0
  128. package/dist/developer/storefront/nextjs/quickstart.md +92 -0
  129. package/dist/developer/storefront/nextjs/spree-next-package.md +314 -0
  130. package/dist/developer/storefront/pages.md +163 -0
  131. package/dist/developer/storefront/sections.md +569 -0
  132. package/dist/developer/storefront/storefront.md +56 -0
  133. package/dist/developer/storefront/themes.md +161 -0
  134. package/dist/developer/tutorial/admin.md +134 -0
  135. package/dist/developer/tutorial/extending-models.md +380 -0
  136. package/dist/developer/tutorial/file-uploads.md +121 -0
  137. package/dist/developer/tutorial/introduction.md +33 -0
  138. package/dist/developer/tutorial/model.md +41 -0
  139. package/dist/developer/tutorial/page-builder.md +487 -0
  140. package/dist/developer/tutorial/rich-text.md +73 -0
  141. package/dist/developer/tutorial/seo.md +332 -0
  142. package/dist/developer/tutorial/storefront.md +352 -0
  143. package/dist/developer/tutorial/testing.md +558 -0
  144. package/dist/developer/upgrades/2.0-to-2.1.md +46 -0
  145. package/dist/developer/upgrades/2.1-to-2.2.md +59 -0
  146. package/dist/developer/upgrades/2.2-to-2.3.md +44 -0
  147. package/dist/developer/upgrades/2.3-to-2.4.md +42 -0
  148. package/dist/developer/upgrades/3.0-to-3.1.md +47 -0
  149. package/dist/developer/upgrades/3.1-to-3.2.md +34 -0
  150. package/dist/developer/upgrades/3.2-to-3.3.md +70 -0
  151. package/dist/developer/upgrades/3.3-to-3.4.md +36 -0
  152. package/dist/developer/upgrades/3.4-to-3.5.md +44 -0
  153. package/dist/developer/upgrades/3.5-to-3.6.md +40 -0
  154. package/dist/developer/upgrades/3.6-to-3.7.md +62 -0
  155. package/dist/developer/upgrades/3.7-to-4.0.md +152 -0
  156. package/dist/developer/upgrades/4.0-to-4.1.md +92 -0
  157. package/dist/developer/upgrades/4.1-to-4.2.md +109 -0
  158. package/dist/developer/upgrades/4.10-to-5.0.md +129 -0
  159. package/dist/developer/upgrades/4.2-to-4.3.md +100 -0
  160. package/dist/developer/upgrades/4.3-to-4.4.md +125 -0
  161. package/dist/developer/upgrades/4.4-to-4.5.md +94 -0
  162. package/dist/developer/upgrades/4.5-to-4.6.md +119 -0
  163. package/dist/developer/upgrades/4.6-to-4.7.md +39 -0
  164. package/dist/developer/upgrades/4.8-to-4.9.md +24 -0
  165. package/dist/developer/upgrades/4.9-to-4.10.md +24 -0
  166. package/dist/developer/upgrades/4.x-to-4.8.md +52 -0
  167. package/dist/developer/upgrades/5.0-to-5.1.md +28 -0
  168. package/dist/developer/upgrades/5.1-to-5.2.md +127 -0
  169. package/dist/developer/upgrades/5.2-to-5.3.md +338 -0
  170. package/dist/developer/upgrades/5.3-to-5.4.md +248 -0
  171. package/dist/developer/upgrades/quickstart.md +36 -0
  172. package/dist/integrations/analytics/google-analytics.md +64 -0
  173. package/dist/integrations/analytics/google-tag-manager.md +78 -0
  174. package/dist/integrations/integrations.md +39 -0
  175. package/dist/integrations/marketing/klaviyo.md +99 -0
  176. package/dist/integrations/payments/adyen.md +90 -0
  177. package/dist/integrations/payments/paypal.md +41 -0
  178. package/dist/integrations/payments/razorpay.md +45 -0
  179. package/dist/integrations/payments/stripe.md +109 -0
  180. package/dist/integrations/search/meilisearch.md +236 -0
  181. package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +57 -0
  182. package/dist/integrations/sso-mfa-social-login/storefront.md +56 -0
  183. 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