@spree/docs 0.1.288 → 0.1.290

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.
@@ -239,7 +239,9 @@ sequenceDiagram
239
239
 
240
240
  The frontend calls the API to create a Payment Session for a specific payment method and order. Spree calls the gateway to create a provider-side session (e.g., Stripe PaymentIntent, Adyen Session) and returns the session data including a `client_secret` for the frontend SDK.
241
241
 
242
- > **INFO:** The payment session should be created (or recreated) **after** the shipping method is selected, so the amount includes shipping costs. If the order total changes (e.g., customer selects a different shipping rate or applies a coupon), create a new payment session with the updated amount.
242
+ > **INFO:** Create the payment session **after** the shipping method is selected, so the amount includes shipping costs. If the order total changes (a different shipping rate, a coupon), create the session again.
243
+
244
+ A cart has at most one open session per gateway. Asking Stripe for a session again (a reloaded checkout page, a second tab) cancels the cart's open session and opens a new one, because every open Stripe PaymentIntent can be paid on its own. If that session has already been paid, the request fails with a `gateway_error` and the paid session stays open. Once the order is placed, a background job cancels any session still pending — for example, one left behind when the customer switched to a different payment method — so it can no longer be paid.
243
245
 
244
246
  **Step 2: Customer pays on the frontend**
245
247
 
@@ -373,8 +375,9 @@ stateDiagram-v2
373
375
  ```
374
376
 
375
377
  > **NOTE:** There is no cancel endpoint. A session reaches `canceled` when the provider
376
- > says so through its webhook, and `expired` when it passes `expires_at`.
377
- > Neither is something a storefront drives.
378
+ > says so through its webhook, or after the order is placed while the session
379
+ > is still pending with nothing paid through it. It reaches `expired` when it
380
+ > passes `expires_at`. Neither is something a storefront drives.
378
381
 
379
382
  #### API
380
383
 
@@ -158,12 +158,22 @@ class MyGateway < Spree::PaymentMethod
158
158
  payment_session
159
159
  end
160
160
 
161
+ # Cancel a session the customer left behind, so it can no longer be paid.
162
+ # Spree calls this from a background job, once the order is placed, for
163
+ # every session still pending with nothing paid through it.
164
+ def cancel_payment_session(payment_session:)
165
+ MyProvider::Client.new(preferred_api_key).cancel_session(payment_session.external_id)
166
+ payment_session.cancel
167
+ end
168
+
161
169
  def payment_icon_name
162
170
  'my-gateway'
163
171
  end
164
172
  end
165
173
  ```
166
174
 
175
+ > **WARNING:** If your provider lets each open session be paid on its own (as Stripe does with PaymentIntents), a customer with two open sessions can be charged twice — for example, with checkout open in two tabs. Have `create_payment_session` cancel the cart's open sessions for your gateway before opening a new one, and implement `cancel_payment_session`. Leave a session the provider has already accepted, and do not open another. The built-in Stripe gateway does both. If you leave out `cancel_payment_session`, sessions still pending when the order is placed stay open at your provider.
176
+
167
177
  ### How the Frontend Uses It
168
178
 
169
179
  The frontend creates a session, then uses the provider's SDK to collect payment:
@@ -194,7 +204,7 @@ const completed = await client.carts.paymentSessions.complete(
194
204
  const order = await client.carts.complete(cart.id, options)
195
205
  ```
196
206
 
197
- > **INFO:** **Important:** Always create the payment session **after** shipping is selected. If the order total changes (shipping rate change, coupon applied), create a new payment session with the updated amount. The `complete` call in step 5 only handles payment — step 6 finalizes the order.
207
+ > **INFO:** **Important:** Always create the payment session **after** shipping is selected. If the order total changes (shipping rate change, coupon applied), update the session with the new amount (`client.carts.paymentSessions.update`) rather than creating another one. The `complete` call in step 5 only handles payment — step 6 finalizes the order.
198
208
 
199
209
  ## Step 4: Handle Webhooks (Recommended)
200
210
 
@@ -408,7 +408,7 @@ New seams with no legacy counterpart:
408
408
  | `order_discount_create_service` | `Spree::Orders::Discounts::Create` | renamed from `order_add_manual_discount_service` (never released) |
409
409
  | `order_update_statuses_service` | `Spree::Orders::UpdateStatuses` | the sole status writer |
410
410
 
411
- Removed keys (their classes no longer exist): `carts_validate_service` (completion validation is `Spree::Checkout::Requirements` directly), plus the dead legacy `Spree::Cart::*` service namespace registrations.
411
+ Removed keys (their classes no longer exist): `carts_validate_service` (completion validation is `Spree::Checkout::Requirements` directly), `cart_empty_service` and `payment_create_service` (see [Unused services are gone](#unused-services-are-gone)), plus the dead legacy `Spree::Cart::*` service namespace registrations.
412
412
 
413
413
  If you override a workflow seam, subclass the shipped workflow (or implement the same `perform` keyword contract) — workflow arguments are plain Ruby keywords, so a mismatch raises `ArgumentError` at call time, not silently.
414
414
 
@@ -497,6 +497,16 @@ Two behavior notes for extension authors: `Spree::Metadata` no longer includes t
497
497
 
498
498
  Single-store installations notice nothing. **A multi-store installation ends up with its whole schema on the default store**, and its other stores start empty: create the fields you want on each store, or run `bin/rake spree:upgrade:backfill_custom_field_definition_stores` first if the migration ran before any store existed. Existing values keep pointing at the definitions they always did, which is why the rows are not copied — copies would render blank anyway. Two definitions whose `namespace`/`key` pair flattens to one `cf_…` key (`("a_b", "c")` and `("a", "b_c")`) can no longer coexist in a store; the migration suffixes the later one and names it in its output so you can rename it.
499
499
 
500
+ ### Unused services are gone
501
+
502
+ These had no caller left in Spree, so they are removed with no replacement shim. Setting or reading their `Spree::Dependencies` keys now raises `NoMethodError`.
503
+
504
+ | Removed | Use instead |
505
+ |---|---|
506
+ | `Spree::PromotionHandler::Page` (activating a promotion by visiting its `path`) | apply the promotion with a coupon code, or as an automatic promotion |
507
+ | `Spree::Carts::Empty` and `cart_empty_service` | remove items through the Store API cart items endpoints, or delete the cart with `Spree.cart_destroy_service` |
508
+ | `Spree::Payments::Create` and `payment_create_service` | the Store API payments endpoint, `Spree::StoreCredits::Apply` or `Spree.gift_card_apply_workflow` |
509
+
500
510
  ### The Legacy order-routing strategy is gone
501
511
 
502
512
  `Spree::OrderRouting::Strategy::Legacy` — the pre-5.5 escape hatch that delegated straight to `Spree::Stock::Coordinator` and consulted no routing rules — is removed and no longer registered in `Spree.order_routing.strategies`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.288",
3
+ "version": "0.1.290",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",