@spree/docs 0.1.240 → 0.1.242

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 (28) hide show
  1. package/dist/api-reference/store-api/metadata.md +6 -6
  2. package/dist/developer/core-concepts/architecture.md +15 -19
  3. package/dist/developer/core-concepts/carts.md +71 -1
  4. package/dist/developer/core-concepts/delivery-setup.md +3 -1
  5. package/dist/developer/core-concepts/freight.md +279 -0
  6. package/dist/developer/core-concepts/metafields.md +0 -2
  7. package/dist/developer/core-concepts/payments.md +123 -72
  8. package/dist/developer/core-concepts/sellers.md +50 -5
  9. package/dist/developer/create-spree-app/quickstart.md +22 -19
  10. package/dist/developer/customization/api.md +2 -2
  11. package/dist/developer/customization/checkout.md +117 -0
  12. package/dist/developer/customization/configuration.md +76 -175
  13. package/dist/developer/customization/decorators.md +120 -130
  14. package/dist/developer/customization/metadata.md +9 -9
  15. package/dist/developer/customization/model-preferences.md +34 -68
  16. package/dist/developer/customization/permissions.md +13 -28
  17. package/dist/developer/customization/validations.md +16 -43
  18. package/dist/developer/getting-started/quickstart.md +25 -24
  19. package/dist/developer/how-to/custom-promotion.md +1 -1
  20. package/dist/developer/how-to/custom-search-provider.md +2 -2
  21. package/dist/developer/how-to/custom-stock-splitter.md +2 -2
  22. package/dist/developer/sdk/admin/extending.md +1 -1
  23. package/dist/developer/sdk/admin/quickstart.md +1 -1
  24. package/dist/developer/sdk/admin/resources.md +1 -1
  25. package/dist/developer/sdk/configuration.md +1 -1
  26. package/dist/developer/sdk/extending.md +1 -1
  27. package/dist/developer/tutorial/testing.md +45 -91
  28. package/package.json +1 -1
@@ -11,7 +11,7 @@ Payment methods typically represent a payment gateway. Gateways will process car
11
11
 
12
12
  The `Payment` model in Spree tracks payments against [Orders](orders.md). Payments relate to a `source` which indicates how the payment was made, and a `PaymentMethod`, indicating the processor used for this payment.
13
13
 
14
- When a payment is created, it is given a unique, 8-character identifier. This is used when sending the payment details to the payment processor. Without this identifier, some payment gateways mistakenly reported duplicate payments.
14
+ A payment's `number` is derived from what it belongs to — an order `R1001` numbers its payments `R1001-P1`, `R1001-P2` — so a gateway reference traces straight back to the order without a lookup.
15
15
 
16
16
  ### Payment Architecture Diagram
17
17
 
@@ -20,9 +20,8 @@ erDiagram
20
20
  Payment {
21
21
  string number
22
22
  decimal amount
23
- string state
23
+ string status
24
24
  string response_code
25
- string avs_response
26
25
  string source_type
27
26
  string source_id
28
27
  }
@@ -32,7 +31,7 @@ erDiagram
32
31
  string type
33
32
  string description
34
33
  boolean active
35
- string display_on
34
+ boolean storefront_visible
36
35
  integer position
37
36
  }
38
37
 
@@ -58,11 +57,12 @@ erDiagram
58
57
  }
59
58
 
60
59
  CreditCard {
61
- string last_digits
62
- string cc_type
60
+ string last4
61
+ string brand
63
62
  integer month
64
63
  integer year
65
64
  string name
65
+ boolean default
66
66
  }
67
67
 
68
68
  GatewayCustomer {
@@ -124,12 +124,12 @@ A `PaymentMethod` can have the following attributes:
124
124
  | `name` | The visible name for this payment method | `Check` |
125
125
  | `description`| The description for this payment method | `Pay by check.` |
126
126
  | `active` | Whether or not this payment method is active. Set it `false` to hide it in the Store API. | `true` |
127
- | `display_on` | Determines where the payment method can be visible. Values can be `front_end` (Store API), `back_end` (admin panel only), or `both` (both). | `both` |
127
+ | `storefront_visible` | Whether customers can choose this method. Leave it off for a method only staff use. | `true` |
128
128
  | `position` | The position of the payment method in lists. Lower numbers appear first. | `1` |
129
129
 
130
130
  > **INFO:** Each payment method is associated to a Store, so you can decide which Payment Method will appear on which Store. This allows you to create different experiences for your customers in different countries.
131
131
 
132
- ### Session-based vs Legacy Payment Methods
132
+ ### Session-based vs direct payment methods
133
133
 
134
134
  Payment methods indicate whether they use the modern session-based flow via the `session_required?` method:
135
135
 
@@ -140,13 +140,13 @@ Payment methods indicate whether they use the modern session-based flow via the
140
140
  | `payment_session_class` | Returns the STI subclass of `Spree::PaymentSession` for this gateway (e.g., `Spree::PaymentSessions::Stripe`). | `nil` |
141
141
  | `payment_setup_session_class` | Returns the STI subclass of `Spree::PaymentSetupSession` for this gateway. | `nil` |
142
142
 
143
- Modern gateways like Stripe and Adyen set `session_required?` to `true`. The Store API serializer includes this as the `session_required` field so your frontend knows which flow to use.
143
+ Gateways like Stripe and Adyen set `session_required?` to `true`; offline methods leave it `false`. Neither path is deprecated. The Store API serializer includes this as the `session_required` field so your frontend knows which flow to use.
144
144
 
145
- ### Non-Session Payment Methods (Manual/Offline)
145
+ ### Direct payment methods (manual and offline)
146
146
 
147
147
  Payment methods where `session_required?` returns `false` don't need a payment session. These are typically offline or manual payment methods such as:
148
148
 
149
- - **Check** — built-in (`Spree::PaymentMethod::Check`)
149
+ - **Check** — built in
150
150
  - **Cash on Delivery** — customer pays upon delivery
151
151
  - **Bank Transfer / Wire** — customer transfers money to a bank account
152
152
  - **Purchase Order** — common in B2B, customer provides a PO number
@@ -180,9 +180,16 @@ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payments' \
180
180
  ```
181
181
 
182
182
 
183
- The payment is created in `checkout` state. When the order transitions to `complete`, Spree calls `process_payments!` which runs the payment method's `authorize` (or `purchase` if auto-capture is enabled). For manual payment methods like Check, this is a no-op that succeeds immediately — the payment moves to `pending` (or `completed` with auto-capture), allowing the order to complete.
183
+ The payment starts at `checkout`. Completing the cart processes it — for a manual method there is no provider to call, so it succeeds immediately and lands on `pending`, or `completed` when the store charges at checkout.
184
+
185
+ Once the money actually arrives — the cheque clears, the transfer lands, the
186
+ driver is paid — staff capture the payment from the dashboard, or you do it
187
+ through the Admin API:
184
188
 
185
- The merchant can later capture or void the payment from the Admin Panel once the actual payment is received (e.g., check arrives, bank transfer clears, cash is collected on delivery).
189
+ ```typescript Admin SDK
190
+ await adminClient.orders.payments.capture(orderId, paymentId)
191
+ await adminClient.orders.payments.void(orderId, paymentId)
192
+ ```
186
193
 
187
194
  ## Payment Flow
188
195
 
@@ -247,7 +254,7 @@ After the customer completes payment, the frontend calls the Complete Payment Se
247
254
 
248
255
  **Step 4: Complete Order**
249
256
 
250
- The frontend calls `POST /carts/:id/complete` to finalize the order. Spree validates the order is ready (addresses, fulfillments, payment), advances through any remaining checkout states, and marks the order as complete.
257
+ The frontend calls `POST /carts/:id/complete` to finalize the order. Spree checks the cart has everything it needs — addresses, fulfillments, payment — and turns it into an order.
251
258
 
252
259
  This separation ensures the same flow works for all payment types — inline cards, offsite redirects, and wallet payments.
253
260
 
@@ -305,11 +312,11 @@ sequenceDiagram
305
312
  Spree API-->>Frontend: Cart (with embedded payment_methods, each carrying session_required)
306
313
 
307
314
  Frontend->>Spree API: POST /payments (payment_method_id)
308
- Spree API->>Spree API: Create Payment (checkout state)
315
+ Spree API->>Spree API: Create payment (status checkout)
309
316
  Spree API-->>Frontend: Payment created
310
317
 
311
318
  Frontend->>Spree API: POST /carts/:id/complete
312
- Spree API->>Spree API: process_payments! (authorize succeeds immediately)
319
+ Spree API->>Spree API: Process payment (succeeds immediately)
313
320
  Spree API->>Spree API: Payment → pending/completed
314
321
  Spree API-->>Frontend: Order completed
315
322
  ```
@@ -320,16 +327,18 @@ The frontend reads the cart's embedded `payment_methods` (returned by `GET /cart
320
327
 
321
328
  **Step 2: Create payment**
322
329
 
323
- The frontend calls `POST /payments` with the `payment_method_id`. Spree creates a `Payment` record in `checkout` state. No external provider interaction is needed.
330
+ The frontend calls `POST /payments` with the `payment_method_id`. Spree creates a payment with status `checkout`. No provider is contacted.
331
+
332
+ Two refusals to handle: a method needing a session returns `payment_session_required`, and one unavailable for this order returns `payment_method_unavailable` — both HTTP 422 with that `code`.
324
333
 
325
334
  **Step 3: Complete order**
326
335
 
327
- The frontend completes the order. Spree's `process_payments!` runs the payment method's `authorize` (or `purchase` with auto-capture). For manual methods like Check, these return an immediate success — no external service is called. The payment transitions to `pending` (without auto-capture) or `completed` (with auto-capture) and the order completes. The merchant can later capture `pending` payments from the Admin Panel once the physical payment is received.
336
+ The frontend completes the order. Completing the cart processes the payment. For a manual method there is nothing to call, so it succeeds immediately and lands on `pending`, or `completed` when the store charges at checkout. Staff capture `pending` payments from the dashboard once the money arrives.
328
337
 
329
338
 
330
339
  ### Payment Session
331
340
 
332
- A `PaymentSession` (`Spree::PaymentSession`) represents a server-side session with the payment gateway. It is the entry point for every payment attempt and holds the provider-specific data needed by the frontend SDK.
341
+ A payment session represents a server-side session with the payment gateway. It is the entry point for every payment attempt and holds the provider-specific data needed by the frontend SDK.
333
342
 
334
343
  #### Attributes
335
344
 
@@ -342,6 +351,11 @@ A `PaymentSession` (`Spree::PaymentSession`) represents a server-side session wi
342
351
  | `external_data` | Provider-specific data including `client_secret` for frontend SDK | `{"client_secret": "pi_3ABC_secret_xyz"}` |
343
352
  | `customer_external_id` | The provider's customer ID | `cus_ABC123` |
344
353
  | `expires_at` | When the session expires | `2025-01-01T12:00:00Z` |
354
+ | `cart_id` | The cart being paid for | `cart_k5nR8xLq` |
355
+
356
+ > **NOTE:** A session also carries `order_id`, but during checkout there is no order yet —
357
+ > it reports the cart's ID until the cart completes. Read `cart_id` while paying,
358
+ > and `order_id` once you have an order.
345
359
 
346
360
  #### States
347
361
 
@@ -359,6 +373,10 @@ stateDiagram-v2
359
373
  processing --> expired
360
374
  ```
361
375
 
376
+ > **NOTE:** There is no cancel endpoint. A session reaches `canceled` when the provider
377
+ > says so through its webhook, and `expired` when it passes `expires_at`.
378
+ > Neither is something a storefront drives.
379
+
362
380
  #### API
363
381
 
364
382
  **Create a Payment Session:**
@@ -441,7 +459,7 @@ console.log(completed.status) // 'completed'
441
459
  ```
442
460
 
443
461
  ```bash cURL
444
- curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx/complete' \
462
+ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx/complete' \
445
463
  -H 'X-Spree-API-Key: pk_xxx' \
446
464
  -H 'X-Spree-Token: CART_TOKEN' \
447
465
  -H 'Content-Type: application/json' \
@@ -487,10 +505,10 @@ class MyGateway < Spree::Gateway
487
505
 
488
506
  case event.type
489
507
  when 'payment.captured'
490
- session = Spree::PaymentSession.find_by(external_id: event.payment_id)
508
+ session = payment_sessions.find_by(external_id: event.payment_id)
491
509
  { action: :captured, payment_session: session }
492
510
  when 'payment.failed'
493
- session = Spree::PaymentSession.find_by(external_id: event.payment_id)
511
+ session = payment_sessions.find_by(external_id: event.payment_id)
494
512
  { action: :failed, payment_session: session }
495
513
  else
496
514
  nil # unsupported event
@@ -503,48 +521,55 @@ Supported actions: `:captured`, `:authorized`, `:failed`, `:canceled`.
503
521
 
504
522
  ### Payment
505
523
 
506
- Once a Payment Session is completed, Spree creates a `Payment` record (`Spree::Payment`) to track the result. The Payment is linked to the session via `response_code` matching the session's `external_id`.
524
+ Once a payment session completes, Spree creates a payment to record the result,
525
+ linked to the session by `response_code` matching its `external_id`.
507
526
 
508
527
  #### Attributes
509
528
 
510
- | Attribute | Description | Example Value |
511
- |-------------------|-----------------------------------------------------------------------------|---------------------|
512
- | `number` | A unique identifier for the payment. | `PAB12CD34` |
513
- | `source_type` | The type of source used for the payment. | `Spree::CreditCard` |
514
- | `source_id` | The ID of the source used for the payment. | `1` |
515
- | `amount` | The amount of the payment. | `99.99` |
516
- | `payment_method_id`| The ID of the payment method used. | `2` |
517
- | `state` | The current state of the payment (e.g., processing, completed, failed). | `completed` |
518
- | `response_code` | The gateway transaction ID. Links to the Payment Session's `external_id`. | `pi_3ABC123` |
519
- | `avs_response` | The address verification system response code. | `D` |
520
-
521
- #### Payment States
522
-
523
- After a Payment Session completes, the resulting Payment transitions through these states:
524
-
525
- ```mermaid
526
- stateDiagram-v2
527
- [*] --> checkout
528
- checkout --> processing : process!
529
- processing --> completed : purchase! succeeds
530
- processing --> pending : authorize! succeeds
531
- processing --> failed : payment declined
532
- pending --> completed : capture!
533
- pending --> void : void!
534
- completed --> void : void!
535
- checkout --> void : void!
536
- ```
537
-
538
- | State | Description |
539
- |-------|-------------|
540
- | `checkout` | Initial state. The payment has been created but not yet processed. |
541
- | `processing` | The payment is being processed (temporary - prevents double submission). |
542
- | `pending` | The payment has been authorized but not yet captured. Awaiting manual or scheduled capture. |
543
- | `failed` | The payment was rejected (e.g., card declined, insufficient funds). |
544
- | `void` | The payment has been voided and should not be counted against the order. |
545
- | `completed` | The payment has been captured. Only payments in this state count against the order total. |
546
-
547
- With **auto-capture** enabled (default for most gateways), the Payment goes directly from `checkout` → `processing` → `completed`. With **manual capture**, it stops at `pending` until an admin captures it.
529
+ These are the fields the Store API returns:
530
+
531
+ | Attribute | Description | Example |
532
+ |---|---|---|
533
+ | `number` | Derived from the owner — see above. | `R1001-P1` |
534
+ | `status` | Where the payment stands. | `completed` |
535
+ | `amount` / `display_amount` | The amount, raw and formatted for its currency. | `99.99` / `$99.99` |
536
+ | `payment_method_id` | Which method took it. | `pm_xyz789` |
537
+ | `source_type` | `credit_card`, `store_credit` or `payment_source`. | `credit_card` |
538
+ | `source_id` | The source's ID. | `cc_abc123` |
539
+ | `source` | The source itself, embedded. | `{ brand: "visa", last4: "4242" }` |
540
+ | `response_code` | The gateway's transaction reference. | `pi_3ABC123` |
541
+
542
+ > **NOTE:** `source_type` is a plain name — `credit_card`, not a class name — so you can
543
+ > switch on it directly.
544
+
545
+ #### Payment statuses
546
+
547
+ | Status | Meaning |
548
+ |---|---|
549
+ | `checkout` | Created, not yet processed. |
550
+ | `processing` | Being processed. Brief, and it stops a double submission. |
551
+ | `pending` | Authorized but not captured. Waiting for capture. |
552
+ | `completed` | Captured. Only these count towards the order total. |
553
+ | `failed` | The gateway rejected it. |
554
+ | `void` | Voided; it no longer counts against the order. |
555
+ | `invalid` | Superseded — written to an old checkout payment when a new one replaces it. |
556
+
557
+ When the money is taken follows the store's `capture_method`, which a payment
558
+ method can override for its own payments:
559
+
560
+ | `capture_method` | What happens |
561
+ |---|---|
562
+ | `checkout` | Charged when the order is placed — the payment goes straight to `completed`. |
563
+ | `on_dispatch` | Authorized at checkout, captured when the goods go out. |
564
+ | `manual` | Authorized at checkout; staff capture it when they choose. |
565
+
566
+ The last two leave the payment at `pending` until capture. See
567
+ [Configuration](../customization/configuration.md) for where the store
568
+ setting lives.
569
+
570
+ > **NOTE:** There is no state machine behind these. A payment's status is written by the
571
+ > workflows that process, capture and void it, so nothing transitions on its own
572
+ > and there is no sequence a client has to drive.
548
573
 
549
574
  #### Order Payment Status
550
575
 
@@ -563,11 +588,27 @@ Each payment update also recalculates the order's `payment_status`, derived from
563
588
 
564
589
  > **WARNING:** Keep an eye on orders sitting at `authorized` or `partially_paid` long after placement — a sudden increase can indicate a problem with your payment gateway that is affecting customers. Check the gateway's own dashboard for recent transactions.
565
590
 
591
+ ## Refunds
592
+
593
+ Refunds are an Admin API operation — there is no Store API route, so a customer
594
+ cannot start one from your storefront:
595
+
596
+ ```typescript Admin SDK
597
+ await adminClient.orders.refunds.create(orderId, {
598
+ payment_id: 'py_abc123',
599
+ amount: '25.00',
600
+ reason_id: 'rr_xyz789',
601
+ })
602
+ ```
603
+
604
+ A refund publishes `payment.refunded` and moves the order's `payment_status` to
605
+ `partially_refunded` or `refunded`.
606
+
566
607
  ## Payment Sources
567
608
 
568
609
  Payment sources represent the actual instrument used for a payment. They are created automatically when a Payment Session completes.
569
610
 
570
- ### Credit Cards (`Spree::CreditCard`)
611
+ ### Saved cards
571
612
 
572
613
  Stores non-sensitive credit card information. With modern gateways, the actual card data is tokenized by the provider - Spree only stores reference IDs and display information.
573
614
 
@@ -575,9 +616,10 @@ Stores non-sensitive credit card information. With modern gateways, the actual c
575
616
  |---------------------|-----------------------------------------------------------------------------------------------|------------------------|
576
617
  | `month` | The month the credit card expires. | `6` |
577
618
  | `year` | The year the credit card expires. | `2026` |
578
- | `cc_type` | The type of credit card (e.g., visa, mastercard). | `visa` |
579
- | `last_digits` | The last four digits of the credit card number. | `1234` |
580
- | `name` | The name of the credit card holder. | `John Doe` |
619
+ | `brand` | The card brand. | `visa` |
620
+ | `last4` | The last four digits. | `4242` |
621
+ | `name` | The cardholder's name. | `John Doe` |
622
+ | `default` | Whether this is the customer's default card. | `true` |
581
623
  | `gateway_payment_profile_id` | The payment token from the gateway (e.g., Stripe `pm_xxx`, Adyen `storedPaymentMethodId`). | `pm_1ABC123` |
582
624
 
583
625
  > **NOTE:** Spree never stores full credit card numbers. With modern gateways, card data is collected entirely by the gateway's frontend SDK (e.g., Stripe.js, Adyen Drop-in) and never touches your server. Spree only stores the tokenized reference (`gateway_payment_profile_id`) returned by the provider.
@@ -586,7 +628,7 @@ Stores non-sensitive credit card information. With modern gateways, the actual c
586
628
 
587
629
  A generic payment source model for non-card payment methods such as digital wallets, bank transfers, and buy-now-pay-later services. Gateway integrations create subtypes for each payment method type (e.g., Klarna, Afterpay, iDEAL, Apple Pay, Google Pay, PayPal).
588
630
 
589
- ### Gateway Customers (`Spree::GatewayCustomer`)
631
+ ### Gateway customers
590
632
 
591
633
  Maps a Spree customer to their provider-specific customer profile. This enables features like saved payment methods, recurring billing, and customer-level fraud detection.
592
634
 
@@ -594,13 +636,16 @@ Maps a Spree customer to their provider-specific customer profile. This enables
594
636
  |-----------|-------------|---------------|
595
637
  | `profile_id` | The provider's customer ID (encrypted at rest) | `cus_ABC123` |
596
638
  | `payment_method_id` | The gateway this customer belongs to | `1` |
597
- | `user_id` | The Spree user | `42` |
639
+ | `customer_id` | The Spree customer | `42` |
640
+
641
+ A customer has at most one record per payment method, and `profile_id` is encrypted with Active Record Encryption where it is configured.
598
642
 
599
- Each customer has at most one `GatewayCustomer` record per payment method. The `profile_id` is encrypted using Active Record Encryption when available.
643
+ > **NOTE:** This one is internal — the API does not expose it. It is listed here because
644
+ > gateway integrations rely on it; nothing a storefront does will read it.
600
645
 
601
646
  ## Payment Setup Sessions
602
647
 
603
- Payment Setup Sessions (`Spree::PaymentSetupSession`) allow customers to save payment methods for future use **without making an immediate payment**. This maps to concepts like Stripe's SetupIntent - a secure way to collect and tokenize payment details for later charges.
648
+ Payment setup sessions let customers save payment methods for future use **without making an immediate payment**. This maps to concepts like Stripe's SetupIntent - a secure way to collect and tokenize payment details for later charges.
604
649
 
605
650
  ### Use Cases
606
651
 
@@ -714,7 +759,7 @@ console.log(completed.payment_source_id) // e.g. 'card_xyz789' - the saved paym
714
759
  ```
715
760
 
716
761
  ```bash cURL
717
- curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123/complete' \
762
+ curl -X PATCH 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123/complete' \
718
763
  -H 'X-Spree-API-Key: pk_xxx' \
719
764
  -H 'Authorization: Bearer $CUSTOMER_JWT' \
720
765
  -H 'Content-Type: application/json' \
@@ -722,7 +767,7 @@ curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_se
722
767
  ```
723
768
 
724
769
 
725
- Spree will verify the result with the provider and create a saved payment source (e.g., `Spree::CreditCard`) that can be used for future payments.
770
+ Spree verifies the result with the provider and saves a payment source — usually a card — for future payments.
726
771
 
727
772
  ## Supported Gateways
728
773
 
@@ -741,7 +786,11 @@ Spree publishes events throughout the payment lifecycle that you can subscribe t
741
786
  ### Payment Events
742
787
  | Event | Description |
743
788
  |-------|-------------|
744
- | `payment.paid` | Payment was completed |
789
+ | `payment.completed` | Payment reached `completed` |
790
+ | `payment.paid` | Payment was paid |
791
+ | `payment.captured` | An authorized payment was captured |
792
+ | `payment.voided` | Payment was voided |
793
+ | `payment.refunded` | Payment was refunded |
745
794
  | `order.paid` | Order is fully paid |
746
795
 
747
796
  ### Payment Session Events
@@ -756,9 +805,11 @@ Spree publishes events throughout the payment lifecycle that you can subscribe t
756
805
  ### Payment Setup Session Events
757
806
  | Event | Description |
758
807
  |-------|-------------|
808
+ | `payment_setup_session.processing` | Setup is being processed |
759
809
  | `payment_setup_session.completed` | Setup completed, payment source saved |
760
810
  | `payment_setup_session.failed` | Setup failed |
761
811
  | `payment_setup_session.canceled` | Setup was canceled |
812
+ | `payment_setup_session.expired` | Setup expired |
762
813
 
763
814
  See [Events](events.md) for more details on subscribing to events.
764
815
 
@@ -308,7 +308,21 @@ The scheduler is the host app's. Two jobs fan out per seller and the interval lo
308
308
  | `Spree::SellerPayouts::SweepDueJob` | daily | settles every seller who is due |
309
309
  | `Spree::SellerTransfers::ExecutePendingDueJob` | hourly | retries earnings a provider refused — usually a passing thing |
310
310
 
311
- `spree/spree-starter` ships both entries in `server/config/recurring.yml`. A seller on the `manual` interval is skipped by the sweep and settled by hand instead:
311
+ > **WARNING:** **You have to schedule these yourself** — a new project does not ship them, so
312
+ > a marketplace that skips this step never pays a seller. Add both to your
313
+ > scheduler; with Solid Queue that is `server/config/recurring.yml`:
314
+ >
315
+ > ```yaml server/config/recurring.yml
316
+ production:
317
+ sweep_due_seller_payouts:
318
+ class: Spree::SellerPayouts::SweepDueJob
319
+ schedule: every day at 3am
320
+ execute_pending_seller_transfers:
321
+ class: Spree::SellerTransfers::ExecutePendingDueJob
322
+ schedule: every hour
323
+ ```
324
+
325
+ A seller on the `manual` interval is skipped by the sweep and settled by hand instead:
312
326
 
313
327
 
314
328
  ```typescript Admin SDK
@@ -319,7 +333,7 @@ const { data: balances } = await adminClient.sellers.balances('sel_xxx')
319
333
  await adminClient.sellers.settle('sel_xxx')
320
334
 
321
335
  // The built-in provider: record that the bank transfer went out
322
- await adminClient.sellerPayouts.complete('spo_xxx', { reference: 'TRN-2026-0912-0042' })
336
+ await adminClient.sellerPayouts.complete('vpo_xxx', { reference: 'TRN-2026-0912-0042' })
323
337
  ```
324
338
 
325
339
  ```bash cURL
@@ -329,14 +343,43 @@ curl 'https://api.mystore.com/api/v3/admin/sellers/sel_xxx/balances' \
329
343
  curl -X POST 'https://api.mystore.com/api/v3/admin/sellers/sel_xxx/payouts' \
330
344
  -H 'X-Spree-API-Key: sk_xxx'
331
345
 
332
- curl -X PATCH 'https://api.mystore.com/api/v3/admin/seller_payouts/spo_xxx/complete' \
346
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/seller_payouts/vpo_xxx/complete' \
333
347
  -H 'X-Spree-API-Key: sk_xxx' \
334
348
  -H 'Content-Type: application/json' \
335
349
  -d '{ "reference": "TRN-2026-0912-0042" }'
336
350
  ```
337
351
 
338
352
 
339
- `GET /admin/seller_transfers` and `GET /admin/seller_payouts` list the ledger across every seller; the seller panel reads its own through `GET /seller/balances`, `/seller/transfers` and `/seller/payouts`, all read-only — the ledger is written by fulfilment and the sweep, never by the seller.
353
+ Settling answers `201` with the payouts it created, one per currency. When one
354
+ currency settles and another is refused it still answers `201`, listing what
355
+ failed under `meta.failures` — so check that rather than assuming success. With
356
+ nothing to settle it answers `422`.
357
+
358
+ ### Reading the ledger
359
+
360
+ Across every seller, from the marketplace side:
361
+
362
+ ```typescript Admin SDK
363
+ const { data: transfers } = await adminClient.sellerTransfers.list({ seller_id_eq: 'sel_xxx' })
364
+ const { data: payouts } = await adminClient.sellerPayouts.list({ status_eq: 'completed' })
365
+
366
+ const payout = await adminClient.sellerPayouts.get('vpo_xxx')
367
+ ```
368
+
369
+ And from the seller's own panel, where every request is already scoped to the
370
+ seller signed in — which is why none of these takes a seller ID:
371
+
372
+ ```typescript Seller SDK
373
+ // What I'm owed, one row per currency
374
+ const { data: balances } = await sellerClient.balances.list()
375
+
376
+ // What I earned, and which payout settled it
377
+ const { data: transfers } = await sellerClient.transfers.list({ payout_id_eq: 'vpo_xxx' })
378
+ const { data: payouts } = await sellerClient.payouts.list()
379
+ ```
380
+
381
+ All of it is read-only: the ledger is written by fulfilment and the sweep, never
382
+ by a seller.
340
383
 
341
384
  ### Payout providers
342
385
 
@@ -347,7 +390,9 @@ A provider is a stateless class registered in `Spree.payout_providers` and chose
347
390
  | `Spree::PayoutProvider::System` (default) | No — keeps the books | The operator marks each payout paid, with the bank reference |
348
391
  | Stripe Connect (`spree_stripe`) | Yes — transfers each earning to the seller's connected account as goods ship, tied to the charge that funds it | A Stripe webhook, when the money lands |
349
392
 
350
- The contract is small: `transfer!` credits one earning, `pay!` sends one settlement, `reverse!` takes back part of an earning after a refund, and every call carries an idempotency key derived from the ledger row so a retry finds the movement it already made. Providers that need the seller to hold an account with them answer `requires_payout_account?` and implement `onboarding_url` — which is what the **payout account** [onboarding requirement](#onboarding-requirements) drives. To pay sellers through rails of your own, see [Seller payouts](../providers/payouts.md).
393
+ The contract is small: `transfer!` credits one earning, `pay!` sends one settlement, `reverse!` takes back part of an earning after a refund, and every call carries an idempotency key derived from the ledger row so a retry finds the movement it already made. Providers that need the seller to hold an account with them answer `requires_payout_account?` and implement `onboarding_url` — which is what the **payout account** [onboarding requirement](#onboarding-requirements) drives. A seller gets a fresh link from `POST /seller/onboarding/payout_account`, and `GET /admin/payout_providers` lists what is registered, for a picker.
394
+
395
+ A connected provider confirms settlements through `POST /api/v3/webhooks/payouts/:payment_method_id` — separate from the payment webhook, because providers scope seller-account events to their own subscription and signing secret. To pay sellers through rails of your own, see [Seller payouts](../providers/payouts.md).
351
396
 
352
397
  See [Stripe Connect for marketplaces](../../integrations/payments/stripe-connect.md) for the shipped provider.
353
398
 
@@ -18,7 +18,7 @@ The CLI walks you through an interactive setup:
18
18
 
19
19
  Once complete, your store is running at [http://localhost:3000](http://localhost:3000) — setup pulls the latest Spree image, seeds the database, and configures API keys, then prints a summary with your admin credentials and keys. If you skipped starting services, the first `pnpm dev` completes setup automatically.
20
20
 
21
- The **React Dashboard** (Developer Preview of the next-generation admin) is available with the `--react-dashboard` flag, or later via `spree add dashboard`.
21
+ The **dashboard** and the marketplace **seller panel** are included in every project — there is no flag to add them.
22
22
 
23
23
  ## Prerequisites
24
24
 
@@ -35,7 +35,7 @@ npx create-spree-app@latest my-store --no-storefront --no-sample-data --no-start
35
35
 
36
36
  | Flag | Description |
37
37
  |------|-------------|
38
- | `--react-dashboard` | Include the React Dashboard (Developer Preview — also available later via `spree add dashboard`) |
38
+ | `--react-dashboard` | No-op — the dashboard is always included. Kept so existing scripts keep working |
39
39
  | `--no-storefront` | Skip Next.js storefront setup |
40
40
  | `--no-sample-data` | Skip loading sample products and categories |
41
41
  | `--no-start` | Don't start Docker services after scaffolding |
@@ -52,29 +52,32 @@ npx create-spree-app@latest my-store --no-storefront --no-sample-data --no-start
52
52
 
53
53
  ```text
54
54
  my-store/
55
+ ├── server/ # The Spree API — a full Rails app from spree/spree-starter
56
+ │ ├── app/ # Your models, controllers, serializers and jobs
57
+ │ ├── config/
58
+ │ ├── Dockerfile # Also builds the production image (API + dashboard)
59
+ │ └── Gemfile
60
+ ├── apps/
61
+ │ ├── dashboard/ # Admin dashboard (React)
62
+ │ │ └── .env.local # Dev proxy target — no credentials
63
+ │ ├── seller-dashboard/ # Seller panel, for marketplaces (React)
64
+ │ └── storefront/ # Next.js storefront (unless --no-storefront)
65
+ │ └── .env.local # API URL + publishable key
66
+ ├── .env # SECRET_KEY_BASE, SPREE_PORT, SPREE_VERSION_TAG, SPREE_SAMPLE_DATA
67
+ ├── .spree/ # CLI credentials — gitignored, created on first run
55
68
  ├── docker-compose.yml # Spree backend (prebuilt image) + Postgres + Meilisearch
56
69
  ├── docker-compose.dev.yml # Alternative: build from local server/
57
70
  ├── render.yaml # Render Blueprint — one Docker service built from this repo
58
- ├── .env # SECRET_KEY_BASE, SPREE_PORT, SPREE_VERSION_TAG, SPREE_SAMPLE_DATA
59
71
  ├── .dockerignore # Keeps the production build context to sources
60
72
  ├── .gitignore
61
- ├── package.json # Convenience scripts
62
- ├── README.md
63
- ├── server/ # Full Rails app — the Spree API (from spree/spree-starter)
64
- │ ├── Gemfile
65
- │ ├── Dockerfile # Also builds the production image (API + dashboard)
66
- │ ├── config/
67
- │ ├── app/
68
- │ └── ...
69
- └── apps/
70
- ├── storefront/ # Next.js storefront (unless --no-storefront)
71
- │ ├── .env.local # API URL + API key
72
- │ └── ...
73
- └── dashboard/ # React Dashboard (with --react-dashboard)
74
- ├── .env.local # Dev proxy target — no credentials
75
- └── ...
73
+ ├── package.json # Scripts and the pinned @spree/cli
74
+ ├── AGENTS.md # Instructions for coding agents
75
+ ├── CLAUDE.md # Project layout and conventions
76
+ └── README.md # How to run everything
76
77
  ```
77
78
 
79
+ **`server/` is the only part that is Spree's.** The apps under `apps/` are ordinary Vite and Next.js projects you own — restyle, restructure or replace them without forking anything. Each carries its own instructions (`server/CLAUDE.md`, `apps/dashboard/AGENTS.md`), which is also what a coding agent reads.
80
+
78
81
  ### What's in docker-compose.yml
79
82
 
80
83
  - **Spree** — one `web` container running the `ghcr.io/spree/spree:latest` image on the configured port (default `3000`); background jobs run in-process via Solid Queue (stored in Postgres — job dashboard at `/jobs`)
@@ -127,7 +130,7 @@ Open [http://localhost:3000/admin](http://localhost:3000/admin) and log in with:
127
130
  | **Email** | `spree@example.com` |
128
131
  | **Password** | `spree123` |
129
132
 
130
- With `--react-dashboard`, the React Dashboard's dev server is the admin instead: `spree dev` starts it alongside the API at [http://localhost:5173](http://localhost:5173) — same credentials, live-reloading from `apps/dashboard/` (the classic admin remains at `/admin`). See the [React Dashboard docs](../dashboard/overview.md).
133
+ The dashboard runs as its own dev server: `spree dev` starts it alongside the API at [http://localhost:5173](http://localhost:5173), with the same credentials, live-reloading from `apps/dashboard/`. See the [dashboard docs](../dashboard/overview.md).
131
134
 
132
135
  ### Store API
133
136
 
@@ -5,7 +5,7 @@ description: Add new Store API endpoints, customize existing JSON responses with
5
5
 
6
6
  Before you start customizing Spree API endpoints, make sure you reviewed all existing API endpoints in the [Spree API docs](../../api-reference/admin-api/endpoints.md).
7
7
 
8
- For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [API tutorial](../tutorial/admin-api.md).
8
+ For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [API tutorial](../tutorial/model-and-api.md).
9
9
 
10
10
  ## Customizing JSON Responses
11
11
 
@@ -35,7 +35,7 @@ Restart the server and the Product API will include your new attribute.
35
35
 
36
36
  ### Adding an Association
37
37
 
38
- Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](../tutorial/admin-api.md) for the full example).
38
+ Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](../tutorial/model-and-api.md) for the full example).
39
39
 
40
40
  Create a serializer for the new model:
41
41