@doswiftly/storefront-sdk 16.0.0 → 17.0.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/CHANGELOG.md +466 -0
- package/dist/core/cart/cart-client.d.ts +10 -1
- package/dist/core/cart/cart-client.d.ts.map +1 -1
- package/dist/core/cart/cart-client.js +17 -1
- package/dist/core/cart/cart-recovery.d.ts +63 -0
- package/dist/core/cart/cart-recovery.d.ts.map +1 -1
- package/dist/core/cart/cart-recovery.js +91 -8
- package/dist/core/cart/types.d.ts +1 -1
- package/dist/core/cart/types.d.ts.map +1 -1
- package/dist/core/generated/operation-types.d.ts +198 -32
- package/dist/core/generated/operation-types.d.ts.map +1 -1
- package/dist/core/operations/cart.d.ts +7 -0
- package/dist/core/operations/cart.d.ts.map +1 -1
- package/dist/core/operations/cart.js +52 -0
- package/dist/react/components/PaymentInstrumentSection.d.ts +56 -0
- package/dist/react/components/PaymentInstrumentSection.d.ts.map +1 -0
- package/dist/react/components/PaymentInstrumentSection.js +89 -0
- package/dist/react/components/PaymentInstrumentTile.d.ts +56 -0
- package/dist/react/components/PaymentInstrumentTile.d.ts.map +1 -0
- package/dist/react/components/PaymentInstrumentTile.js +40 -0
- package/dist/react/components/index.d.ts +2 -0
- package/dist/react/components/index.d.ts.map +1 -1
- package/dist/react/components/index.js +2 -0
- package/dist/react/helpers/browser-data.d.ts +89 -0
- package/dist/react/helpers/browser-data.d.ts.map +1 -0
- package/dist/react/helpers/browser-data.js +84 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,471 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- d3f0f04: `cartSelectPaymentMethod` mutation is now method-centric.
|
|
8
|
+
|
|
9
|
+
The buyer picks a payment **category** (`BLIK`, `CARD`, `BANK_TRANSFER`, ...) and the backend resolves the preferred gateway from the merchant's `MerchantPaymentConfig` at `paymentCreate` time. Previously the storefront had to look up a per-provider tile UUID via `availablePaymentMethods` and pass it as `paymentMethodId`.
|
|
10
|
+
|
|
11
|
+
**Breaking** — `CartSelectPaymentMethodInput` shape changed:
|
|
12
|
+
|
|
13
|
+
```graphql
|
|
14
|
+
# Before
|
|
15
|
+
input CartSelectPaymentMethodInput {
|
|
16
|
+
cartId: ID!
|
|
17
|
+
paymentMethodId: ID! # UUID of a per-provider tile
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# After
|
|
21
|
+
input CartSelectPaymentMethodInput {
|
|
22
|
+
cartId: ID!
|
|
23
|
+
methodType: PaymentMethodType! # BLIK, CARD, BANK_TRANSFER, ...
|
|
24
|
+
preferredProviderId: String # optional override (provider code)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Migration:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Before
|
|
32
|
+
await sdk.cartSelectPaymentMethod({ input: { cartId, paymentMethodId } });
|
|
33
|
+
|
|
34
|
+
// After
|
|
35
|
+
await sdk.cartSelectPaymentMethod({ input: { cartId, methodType: "BLIK" } });
|
|
36
|
+
// Or with explicit gateway override:
|
|
37
|
+
await sdk.cartSelectPaymentMethod({
|
|
38
|
+
input: { cartId, methodType: "BLIK", preferredProviderId: "payu" },
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The `availablePaymentMethods` query now returns deduplicated rows per `PaymentMethodType` (one row per BLIK, CARD, ...) with `providersAvailable` (gateway codes sorted by merchant priority) and `preferredProvider` (head of the list). The legacy `group: BY_PROVIDER` argument was removed — there is one canonical shape.
|
|
43
|
+
|
|
44
|
+
**Why** — A merchant configuring PayU + Przelewy24 would previously see "PayU BLIK" and "P24 BLIK" as two separate tiles on the storefront. After this change the storefront shows one BLIK tile; the merchant chooses which gateway handles BLIK via priority settings, and the buyer never sees the internal routing.
|
|
45
|
+
|
|
46
|
+
**Live capability sync (opt-in)** — gateway adapters may expose an optional `getAvailablePaymentMethods(credentials, { currency, amount, country })` capability returning the gateway-reported active methods (min/max amount, brand image, enabled flag). When supported, the platform caches the response per shop/currency and uses it to refine the storefront picker. Gateways without the capability fall back to a static capability matrix. Gateway HTTP failures are soft-failed — the buyer always sees the merchant's configured methods even when the upstream is temporarily unreachable.
|
|
47
|
+
|
|
48
|
+
- 7931668: Payment instrument preselection — direct deep-link to a specific instrument screen on the hosted gateway page (BLIK code, branded bank, wallet, card brand) instead of the gateway default landing.
|
|
49
|
+
|
|
50
|
+
**Why**: shoppers picking BLIK from the storefront UI now skip the gateway method picker entirely — the redirect lands directly on the BLIK code entry. Same flow for picking mBank, ING, Apple Pay, etc. Industry shorthand calls this "deep-link checkout" — 5-15% conversion uplift on single-instrument funnels.
|
|
51
|
+
|
|
52
|
+
**Breaking schema changes**:
|
|
53
|
+
1. `CartSelectPaymentMethodInput.preferredProviderId` → `preferredProviderCode`. Rename only — same semantics (override the merchant's preferred provider). Aligns with the `*Code` naming convention (`*Id` reserved for UUIDs, `*Code` for lowercase string identifiers like `payu` / `przelewy24`).
|
|
54
|
+
|
|
55
|
+
```graphql
|
|
56
|
+
# Before
|
|
57
|
+
input CartSelectPaymentMethodInput {
|
|
58
|
+
cartId: ID!
|
|
59
|
+
methodType: PaymentMethodType!
|
|
60
|
+
preferredProviderId: String # ← removed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# After
|
|
64
|
+
input CartSelectPaymentMethodInput {
|
|
65
|
+
cartId: ID!
|
|
66
|
+
methodType: PaymentMethodType!
|
|
67
|
+
preferredProviderCode: String # ← renamed
|
|
68
|
+
preferredInstrumentCode: String # ← new (additive)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
2. `PaymentMethod.provider` / `PaymentMethod.providersAvailable` / `PaymentMethod.preferredProvider` / `Order.paymentMethod` / `Payment.provider` are now the typed `ProviderCode` enum (UPPERCASE: `PAYU`, `PRZELEWY24`, `STRIPE`, `CASH_ON_DELIVERY`, `BANK_TRANSFER`, `MANUAL_PAYMENT`, `GIFT_CARD`, `TEST_GATEWAY`) instead of `String`. Replace any `if (provider === 'payu')` storefront branches with the imported enum.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Before
|
|
76
|
+
if (paymentMethod.provider === "payu") {
|
|
77
|
+
/* ... */
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// After
|
|
81
|
+
import { ProviderCode } from "@doswiftly/storefront-sdk";
|
|
82
|
+
if (paymentMethod.provider === ProviderCode.PAYU) {
|
|
83
|
+
/* ... */
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
3. `PaymentMethod.amountLimits` removed from the SDK fragment. The field still exists on the schema for backward-compat, but the SDK no longer selects it — most storefronts never used it and the data is provider-specific (PayU/P24 report limits per method, not per checkout). Re-add it to your local fragment if you need it.
|
|
88
|
+
|
|
89
|
+
**Additive (backward-compatible)**:
|
|
90
|
+
- `PaymentMethod.instruments: [PaymentMethodInstrument!]` — concrete instruments exposed by the gateway (BLIK code, branded banks, wallets, card brands). Each instrument carries `providerCode`, `instrumentCode`, `displayName`, `brandImageUrl`, `displayHint` (semantic UX hint: `PIN_ENTRY` / `WALLET_TAP` / `BANK_LIST` / `CARD_FORM`), `enabled`. `null` when the gateway doesn't expose granular data; empty array when all instruments are disabled.
|
|
91
|
+
- `cartSelectPaymentMethod(input: { preferredInstrumentCode })` — pass the chosen instrument's `instrumentCode` to persist it on the cart. Eager validation cross-checks against the gateway's live capabilities — invalid codes return `userErrors[].code = 'CART_INVALID_INSTRUMENT_CODE'`.
|
|
92
|
+
- `Cart.selectedPaymentInstrumentCode: String` — round-trips the persisted choice so the storefront can re-render the selected instrument on cart refresh.
|
|
93
|
+
- `Order.paymentInstrumentCode: String` — propagated from cart at `cartComplete`; the gateway adapter uses it to build the deep-link payload at `paymentCreate`.
|
|
94
|
+
|
|
95
|
+
**Usage example** — instrument-level deep-link checkout:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { useCartManager, ProviderCode } from "@doswiftly/storefront-sdk";
|
|
99
|
+
|
|
100
|
+
const { selectPaymentMethod, complete, createPayment } = useCartManager();
|
|
101
|
+
|
|
102
|
+
// 1. Render the instrument tiles from the gateway-reported list
|
|
103
|
+
const method = availablePaymentMethods.methods.find((m) => m.type === "BLIK");
|
|
104
|
+
const instrument = method?.instruments?.find(
|
|
105
|
+
(i) => i.instrumentCode === "blik",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// 2. Persist the buyer's choice on the cart
|
|
109
|
+
await selectPaymentMethod({
|
|
110
|
+
methodType: "BLIK",
|
|
111
|
+
preferredProviderCode: "payu",
|
|
112
|
+
preferredInstrumentCode: instrument.instrumentCode,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 3. Complete the cart — instrument propagates to the Order
|
|
116
|
+
const { order } = await complete();
|
|
117
|
+
|
|
118
|
+
// 4. Initiate payment — gateway redirects directly to the BLIK code screen
|
|
119
|
+
const session = await createPayment(order.id);
|
|
120
|
+
window.location.href = session.redirectUrl;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Error handling**:
|
|
124
|
+
- `userErrors[].code === 'CART_INVALID_INSTRUMENT_CODE'` — instrument code is not available with the chosen provider (gateway disabled it, merchant config changed, typo). Refresh `availablePaymentMethods` and re-prompt the buyer.
|
|
125
|
+
- `paymentCreate` failed gateway initiation with the preselected instrument? The order's `paymentInstrumentCode` is automatically cleared (best-effort) — the next `paymentCreate` call falls back to the gateway default landing page. Storefront just retries.
|
|
126
|
+
|
|
127
|
+
**Rate limits**:
|
|
128
|
+
- `availablePaymentMethods` Query: 60 requests/min per IP+shop. Live capability lookups (OAuth + gateway list call) are expensive — hitting the limit returns `extensions.code: 'THROTTLED'` with `retryAfter`. Don't poll; cache the result for the duration of the checkout step.
|
|
129
|
+
|
|
130
|
+
**Gateway API version pinning**:
|
|
131
|
+
- PayU API pinned to `v2_1`, Przelewy24 API pinned to `v1`. When the upstream gateway ships a major version migration, the SDK / operations packages will bump major together with the constant update.
|
|
132
|
+
|
|
133
|
+
**Migration checklist for existing storefronts**:
|
|
134
|
+
- [ ] Rename `preferredProviderId` → `preferredProviderCode` everywhere in storefront mutations.
|
|
135
|
+
- [ ] Replace string comparisons (`provider === 'payu'`) with `ProviderCode` enum.
|
|
136
|
+
- [ ] Re-add `amountLimits` to your local PaymentMethod fragment if you used it.
|
|
137
|
+
- [ ] (Optional) Render the new `instruments` array as tiles to enable instrument-level deep-link checkout — 1-2 extra hours of UI work, measurable conversion impact on single-instrument flows.
|
|
138
|
+
|
|
139
|
+
### Minor Changes
|
|
140
|
+
|
|
141
|
+
- fd82b62: `PaymentMethod` type gains availability + amount limit fields.
|
|
142
|
+
|
|
143
|
+
The storefront `PaymentMethod` returned by `availablePaymentMethods` (and `Cart.availablePaymentMethods` / `Cart.selectedPaymentMethod`) now carries three additional fields:
|
|
144
|
+
- **`available: Boolean!`** — `false` when the resolving gateway is temporarily unavailable (incident, maintenance) or reported the method as disabled. Storefront should gray-out the tile instead of hiding it — gives merchants observability into routing failures.
|
|
145
|
+
- **`unavailableReason: PaymentMethodUnavailableReason`** — diagnostic enum (`GATEWAY_DOWN`, `GATEWAY_DISABLED`, `NO_INSTRUMENTS`, `CREDENTIALS_INVALID`). Null when `available` is true. Use for context-aware copy ("Provider is temporarily down" vs "Method not configured").
|
|
146
|
+
- **`amountLimits: PaymentMethodAmountLimits`** — gateway-reported min/max transaction amount in the resolving currency (e.g. BLIK 1-20000 PLN). Filter the picker against cart total. Null when the gateway does not report limits.
|
|
147
|
+
|
|
148
|
+
```graphql
|
|
149
|
+
fragment PaymentMethod on PaymentMethod {
|
|
150
|
+
# existing fields (id, name, provider, type, icon, ...)
|
|
151
|
+
providersAvailable
|
|
152
|
+
preferredProvider
|
|
153
|
+
available
|
|
154
|
+
unavailableReason
|
|
155
|
+
amountLimits {
|
|
156
|
+
min
|
|
157
|
+
max
|
|
158
|
+
currency
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Currency** is automatically resolved from the storefront context (cascade: `@inContext(currency:)` directive → `X-Preferred-Currency` header → cookie → `Accept-Language` → shop default). Override per query via the `@inContext(currency: "EUR")` directive — no new schema argument.
|
|
164
|
+
|
|
165
|
+
**Migration example** for storefront pickers:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const { methods } = await sdk.cart.availablePaymentMethods();
|
|
169
|
+
return methods.map((method) => (
|
|
170
|
+
<PaymentTile
|
|
171
|
+
key={method.type}
|
|
172
|
+
type={method.type}
|
|
173
|
+
disabled={!method.available}
|
|
174
|
+
tooltip={method.unavailableReason && reasonCopy[method.unavailableReason]}
|
|
175
|
+
badge={method.amountLimits && cart.total > method.amountLimits.max ? 'Cart too large' : null}
|
|
176
|
+
/>
|
|
177
|
+
));
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Backend additionally sanitizes provider error messages (strips OAuth tokens, URLs, JSON secret keys, stack traces) before returning them in admin diagnostic responses — internal logs keep the full error for diagnostics.
|
|
181
|
+
|
|
182
|
+
- c878d14: Payment instrument preselection — storefront UX completion (distinct error code + warnings array + explicit deselect mutation).
|
|
183
|
+
|
|
184
|
+
**Why**: storefront dispatching dla instrument failure stał się context-aware. Dziś generic `PAYMENT_FAILED` post-auto-clear nie pozwalał odróżnić "instrument cleared, retry default landing OK" od "real gateway outage, retry kolejnym attempt". Plus brak explicit deselect — accordion "wróć do wyboru metody" UI wymagał hack typu re-select method.
|
|
185
|
+
|
|
186
|
+
**Additive (backward-compatible)**:
|
|
187
|
+
1. `PaymentErrorCode.INSTRUMENT_PRESELECTION_FAILED` — new enum value. Wystawiany po auto-clear `Order.paymentInstrumentCode` post-`InvalidPaymentInstrumentError`. Distinct od `PAYMENT_FAILED` (generic gateway failure fallback dla credentials / network / 5xx / circuit breaker).
|
|
188
|
+
2. `PaymentCreatePayload.warnings: [PaymentWarning!]!` — new field, default empty array. Emitted post-auto-clear z entry `{ code: INSTRUMENT_CLEARED_FOR_RETRY, message, retryHint: null }`. Storefront dispatches retry hint differently (accordion reset / progress bar / itp.).
|
|
189
|
+
3. `cartClearPaymentSelection(input: { cartId: ID! })` — new mutation. Atomic NULL across all payment selection fields. Idempotent. The cart must be `ACTIVE` — `CONVERTED` carts reject with `userErrors[0].code = 'ALREADY_COMPLETED'`. Rate limit 30/min.
|
|
190
|
+
|
|
191
|
+
**Storefront usage** — handle instrument failure z context-aware retry:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import {
|
|
195
|
+
useCartManager,
|
|
196
|
+
type CartMutationOutcome,
|
|
197
|
+
} from "@doswiftly/storefront-sdk";
|
|
198
|
+
|
|
199
|
+
const { selectPaymentMethod, clearPaymentSelection, createPayment } =
|
|
200
|
+
useCartManager();
|
|
201
|
+
|
|
202
|
+
// 1. Klient wybiera BLIK preselect
|
|
203
|
+
await selectPaymentMethod({
|
|
204
|
+
methodType: "BLIK",
|
|
205
|
+
preferredProviderCode: "payu",
|
|
206
|
+
preferredInstrumentCode: "blik",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// 2. Try paymentCreate — może rzucić instrument-specific error
|
|
210
|
+
try {
|
|
211
|
+
const session = await createPayment(orderId);
|
|
212
|
+
window.location.href = session.redirectUrl;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err.userErrors?.[0]?.code === "INSTRUMENT_PRESELECTION_FAILED") {
|
|
215
|
+
// Backend auto-cleared instrument — show retry button
|
|
216
|
+
// Warning message available via err.warnings[0].message
|
|
217
|
+
showRetryUI({
|
|
218
|
+
title: "Instrument płatności niedostępny",
|
|
219
|
+
message: err.warnings?.[0]?.message ?? err.message,
|
|
220
|
+
onRetry: () => createPayment(orderId), // next attempt = default landing
|
|
221
|
+
});
|
|
222
|
+
} else if (err.userErrors?.[0]?.code === "PAYMENT_FAILED") {
|
|
223
|
+
// Generic gateway outage — retry z tym samym instrumentem
|
|
224
|
+
showOutageUI({
|
|
225
|
+
message: err.message,
|
|
226
|
+
onRetry: () => createPayment(orderId),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 3. Accordion "wróć do wyboru metody" — explicit deselect
|
|
232
|
+
await clearPaymentSelection({ cartId });
|
|
233
|
+
// Cart.selectedPaymentMethod === null, Cart.selectedPaymentInstrumentCode === null
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Migration guide for existing storefronts**:
|
|
237
|
+
- [ ] Add `warnings[]` to your `paymentCreate` selection set (if you use a custom GraphQL document instead of the SDK helpers).
|
|
238
|
+
- [ ] Branch UI dispatch on `userErrors[0].code === 'INSTRUMENT_PRESELECTION_FAILED'` distinct from `PAYMENT_FAILED`.
|
|
239
|
+
- [ ] (Optional) Replace any accordion-reset hack with `cartClearPaymentSelection({ cartId })` — clearer API, idempotent, single round-trip.
|
|
240
|
+
|
|
241
|
+
**Standards note**: splitting `userErrors[]` (blocking) and `warnings[]` (non-blocking) is a common e-commerce convention. The `INSTRUMENT_PRESELECTION_FAILED` semantics match retry-hint patterns used by major payment gateway SDKs (Stripe `payment_method_unavailable`, etc.).
|
|
242
|
+
|
|
243
|
+
- c553c80: Payment instrument preselection — cart re-validation stale signal + headless instrument components + browser data helper.
|
|
244
|
+
|
|
245
|
+
**Why**: previously the storefront kept showing an obsolete instrument selection until the buyer clicked "pay" — only then did the gateway reject it. The signal is now **proactive**: every cart query re-validates the selection against live gateway capabilities and emits a warning when the method or instrument disappeared. Plus a set of pre-built headless React components so the instrument picker no longer requires manual `displayHint` dispatch.
|
|
246
|
+
|
|
247
|
+
**Additive (backward-compatible)**:
|
|
248
|
+
1. `Cart.warnings: [CartWarning!]!` — new field, default empty array. Computed at query time. Currently emitted: `PAYMENT_SELECTION_STALE` (code) when `selectedPaymentMethod` or `selectedPaymentInstrumentCode` no longer matches live gateway capabilities. Read-only signal — backend persistence is preserved (clear via `cartClearPaymentSelection` or a fresh `cartSelectPaymentMethod`).
|
|
249
|
+
2. `Cart.selectedPaymentMethod` re-validation — when the method disappeared from live caps the field returns `null` plus a warning with `target: 'selectedPaymentMethod'`.
|
|
250
|
+
3. `Cart.selectedPaymentInstrumentCode` re-validation — when only the instrument disappeared (method preserved), the field returns `null` plus a warning with `target: 'selectedPaymentInstrumentCode'` (method-level signal stays intact). Backend persistence is preserved — a re-select gets a fresh state.
|
|
251
|
+
4. `CartWarningCode.PAYMENT_SELECTION_STALE` — new enum value in the existing `CartWarning` envelope.
|
|
252
|
+
5. Graceful degradation — when the live capability check fails (gateway timeout / network outage), `Cart` returns the existing selection without a warning (UX continuity over freshness; `cartComplete` time-of-payment validation is the final safety net).
|
|
253
|
+
6. `<PaymentInstrumentTile>` — new pre-built headless component. Single instrument button with full ARIA contract (`role="radio"`, `aria-checked`, `aria-label`, `data-instrument-code`, `data-display-hint`, `data-selected`). Zero opinionated styling — class props per part (button, icon, label). `displayHint` is emitted as a `data-display-hint` attribute so you can style via CSS attribute selectors.
|
|
254
|
+
7. `<PaymentInstrumentSection>` — new pre-built headless component. Radio-group container with keyboard navigation (ArrowUp/Down/Left/Right wrap, Home/End jump). Renders one `<PaymentInstrumentTile>` per instrument in the order received from `availablePaymentMethods` (no client-side resort — the backend ordering is the source of truth).
|
|
255
|
+
8. `getBrowserDataForPayment()` — new helper. Collects PSD2/3DS2 browser context (`userAgent`, `language`, screen dimensions, color depth, timezone offset, IANA timezone, `javaEnabled` fallback). Browser-only — throws `BrowserDataNotAvailableError` in SSR. Forward-looking utility for future 3DS challenge flows.
|
|
256
|
+
|
|
257
|
+
**Storefront usage** — listen to the stale signal in your cart query:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const { cart } = useCart(cartId);
|
|
261
|
+
|
|
262
|
+
const staleWarning = cart?.warnings?.find(
|
|
263
|
+
(w) => w.code === "PAYMENT_SELECTION_STALE",
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (staleWarning) {
|
|
267
|
+
// The backend signals that the buyer picked a method which is no longer
|
|
268
|
+
// available at the gateway — show a "pick another method" dialog.
|
|
269
|
+
// `target` distinguishes method vs instrument level:
|
|
270
|
+
if (staleWarning.target === "selectedPaymentMethod") {
|
|
271
|
+
// cart.selectedPaymentMethod === null — full re-select required
|
|
272
|
+
} else if (staleWarning.target === "selectedPaymentInstrumentCode") {
|
|
273
|
+
// cart.selectedPaymentMethod is still set, only the instrument cleared
|
|
274
|
+
}
|
|
275
|
+
// Backend persistence is preserved — `cartClearPaymentSelection` or
|
|
276
|
+
// a fresh `cartSelectPaymentMethod` is the consumer's responsibility.
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Storefront usage** — instrument picker with pre-built components:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
import { PaymentInstrumentSection } from "@doswiftly/storefront-sdk/react";
|
|
284
|
+
import { useState } from "react";
|
|
285
|
+
|
|
286
|
+
function CheckoutPaymentStep({ method }: { method: PaymentMethod }) {
|
|
287
|
+
const [instrumentCode, setInstrumentCode] = useState<string | undefined>(
|
|
288
|
+
undefined,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<PaymentInstrumentSection
|
|
293
|
+
method={method}
|
|
294
|
+
selectedInstrumentCode={instrumentCode}
|
|
295
|
+
onSelectInstrument={(code) => {
|
|
296
|
+
setInstrumentCode(code);
|
|
297
|
+
cart.selectPaymentMethod({
|
|
298
|
+
methodType: method.type,
|
|
299
|
+
preferredProviderCode: method.preferredProvider,
|
|
300
|
+
preferredInstrumentCode: code,
|
|
301
|
+
});
|
|
302
|
+
}}
|
|
303
|
+
sectionClassName="grid grid-cols-2 gap-2"
|
|
304
|
+
tileClassName="rounded border p-3 hover:bg-gray-50 data-[selected=true]:border-blue-500"
|
|
305
|
+
labelClassName="font-semibold"
|
|
306
|
+
ariaLabel="Pick a payment instrument"
|
|
307
|
+
/>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Storefront usage** — browser data for future 3DS flows:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { getBrowserDataForPayment, BrowserDataNotAvailableError } from '@doswiftly/storefront-sdk/react';
|
|
316
|
+
|
|
317
|
+
function handleCheckoutSubmit() {
|
|
318
|
+
try {
|
|
319
|
+
const browserData = getBrowserDataForPayment();
|
|
320
|
+
// Pass to paymentCreate input when the gateway requires a 3DS challenge
|
|
321
|
+
await cart.createPayment({ ..., browserData });
|
|
322
|
+
} catch (err) {
|
|
323
|
+
if (err instanceof BrowserDataNotAvailableError) {
|
|
324
|
+
// SSR / no DOM — skip browser data; the gateway uses its default flow
|
|
325
|
+
}
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Migration checklist for existing storefronts**:
|
|
332
|
+
- [ ] Add `warnings { message code target }` to your cart query selection set (if you use a custom GraphQL document instead of the SDK fragments — SDK fragments are updated automatically).
|
|
333
|
+
- [ ] Branch UI on `cart.warnings[].code === 'PAYMENT_SELECTION_STALE'` to show a re-prompt dialog before checkout submit.
|
|
334
|
+
- [ ] (Optional) Replace a custom instrument picker with `<PaymentInstrumentSection>` — saves boilerplate, ships full ARIA + keyboard nav out of the box.
|
|
335
|
+
- [ ] (Forward-looking) Adopt `getBrowserDataForPayment()` in checkout submit handlers when 3DS flows become relevant (currently optional — the gateway accepts an undefined `browserData`).
|
|
336
|
+
|
|
337
|
+
**Standards reference**: `PaymentInstrumentSection` follows the WAI-ARIA radiogroup pattern (https://www.w3.org/WAI/ARIA/apg/patterns/radio/). The browser data helper shape matches the EMVCo 3DS2 BrowserData specification.
|
|
338
|
+
|
|
339
|
+
## 16.1.0
|
|
340
|
+
|
|
341
|
+
### Minor Changes
|
|
342
|
+
|
|
343
|
+
- 1e6062e: Read the gift card recipient back from the cart.
|
|
344
|
+
|
|
345
|
+
`cartUpdateGiftCardRecipient` already persisted the recipient (email, name,
|
|
346
|
+
personalised message) on a gift card line — but the `CartLine` type didn't
|
|
347
|
+
expose any field to read it. Storefronts that render the checkout server-side
|
|
348
|
+
(SSR, deep link, "back to cart" navigation) had no way to know that the buyer
|
|
349
|
+
had already filled in the recipient form on a previous visit. The recipient
|
|
350
|
+
dialog opened blank, like nothing had been saved.
|
|
351
|
+
|
|
352
|
+
**New field** — `CartLine.giftCardRecipient: GiftCardLineRecipient`
|
|
353
|
+
|
|
354
|
+
```graphql
|
|
355
|
+
type GiftCardLineRecipient {
|
|
356
|
+
recipientEmail: String
|
|
357
|
+
recipientName: String
|
|
358
|
+
message: String
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
All three fields are individually nullable so a partial set (email only, no
|
|
363
|
+
name or message) is representable as-is. The field on `CartLine` itself is
|
|
364
|
+
nullable — `null` means the buyer has not set a recipient yet, in which case
|
|
365
|
+
the gift card is delivered to the order `customerEmail` on completion.
|
|
366
|
+
|
|
367
|
+
`null` is also what you get for any non-gift-card line — there is no risk of
|
|
368
|
+
the field reporting a stale recipient from a previous line at the same index.
|
|
369
|
+
|
|
370
|
+
**Use it to seed your form**
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
const cart = await cartClient.get(cartId);
|
|
374
|
+
const giftLine = cart?.lines.edges.find(
|
|
375
|
+
(e) => e.node.productType === "GIFT_CARD",
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
<GiftRecipientForm
|
|
379
|
+
defaultValues={
|
|
380
|
+
giftLine?.node.giftCardRecipient ?? {
|
|
381
|
+
recipientEmail: "",
|
|
382
|
+
recipientName: "",
|
|
383
|
+
message: "",
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
onSubmit={(values) =>
|
|
387
|
+
cartClient.updateGiftCardRecipient(cartId, giftLine!.node.id, values)
|
|
388
|
+
}
|
|
389
|
+
/>;
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Migration**
|
|
393
|
+
|
|
394
|
+
Additive change — no rename, no signature change. The `CartLine` fragment
|
|
395
|
+
shipped by the SDK already includes the new field after this release, so
|
|
396
|
+
upgrading immediately unlocks the read path. No client code changes required
|
|
397
|
+
to keep existing flows working.
|
|
398
|
+
|
|
399
|
+
- ac2e306: Distinguish recoverable session loss from permanent cart denial.
|
|
400
|
+
|
|
401
|
+
Adds a new `CART_UNAUTHENTICATED` code to the cart error envelope. Until now,
|
|
402
|
+
both "your sign-in expired" and "this cart belongs to someone else" surfaced
|
|
403
|
+
as `CART_FORBIDDEN`, so a storefront had no way to know whether to send the
|
|
404
|
+
buyer to `/login` (recoverable) or to drop the cart cookie (permanent).
|
|
405
|
+
After the typical ~24h session TTL, the buyer would silently lose their
|
|
406
|
+
checkout state instead of being prompted to re-authenticate.
|
|
407
|
+
|
|
408
|
+
**New code** — `CART_UNAUTHENTICATED`
|
|
409
|
+
|
|
410
|
+
| Condition | Code | Recovery |
|
|
411
|
+
| ------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------- |
|
|
412
|
+
| Anonymous request **or** expired/invalid session on a cart that belongs to a customer | `CART_UNAUTHENTICATED` | Re-authenticate. Cart is preserved (`cart-id` cookie kept). |
|
|
413
|
+
| Authenticated as a **different** customer than the cart owner | `CART_FORBIDDEN` (unchanged) | Drop the cart cookie and start over. |
|
|
414
|
+
|
|
415
|
+
The cart resource itself is intact in the `CART_UNAUTHENTICATED` case — the
|
|
416
|
+
buyer's saved address, line items, discount codes, and gift cards return as
|
|
417
|
+
soon as they sign in.
|
|
418
|
+
|
|
419
|
+
**New SDK signal** — `runner.onSessionExpired(...)`
|
|
420
|
+
|
|
421
|
+
The recovery runner now exposes a separate event for session loss, distinct
|
|
422
|
+
from `onExpired` (which is for cart-resource lifecycle: cart not found, cart
|
|
423
|
+
already completed). Subscribe once at the provider level and route the buyer
|
|
424
|
+
to `/login` with a `return_to` parameter; the cart cookie stays put.
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
// app/providers.tsx
|
|
428
|
+
const unsubExpired = runner.onExpired(() => {
|
|
429
|
+
// cart resource gone — start fresh
|
|
430
|
+
router.push("/cart/new");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const unsubSession = runner.onSessionExpired(() => {
|
|
434
|
+
// session gone, cart preserved — sign back in
|
|
435
|
+
router.push(`/login?return_to=${encodeURIComponent(location.pathname)}`);
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The session branch throws `CartSessionRequiredError` (re-exported alongside
|
|
440
|
+
`CartRecoveryNotPossibleError`) and does **not** invoke `recreateAndRun` —
|
|
441
|
+
recreating the cart on a stale session would discard the buyer's progress.
|
|
442
|
+
|
|
443
|
+
**Imperative handling**
|
|
444
|
+
|
|
445
|
+
If you handle mutation errors directly (Server Action, custom hook), add one
|
|
446
|
+
case to your existing switch:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
switch (err.userErrors[0]?.code) {
|
|
450
|
+
case "CART_UNAUTHENTICATED":
|
|
451
|
+
redirect(`/login?return_to=${encodeURIComponent("/checkout")}`);
|
|
452
|
+
break;
|
|
453
|
+
case "CART_FORBIDDEN":
|
|
454
|
+
cookies().delete("cart-id");
|
|
455
|
+
redirect("/cart/expired");
|
|
456
|
+
break;
|
|
457
|
+
// ...existing cases for CART_NOT_FOUND / ALREADY_COMPLETED
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**Migration**
|
|
462
|
+
|
|
463
|
+
Additive change — no rename, no signature change. Existing `case 'CART_FORBIDDEN'`
|
|
464
|
+
branches keep working as before. Opt in to the new code at your own pace; until
|
|
465
|
+
you add the case, anonymous and cross-customer denials fall into your existing
|
|
466
|
+
default branch (one will be recoverable, one will not — adding the case lets
|
|
467
|
+
you distinguish them).
|
|
468
|
+
|
|
3
469
|
## 16.0.0
|
|
4
470
|
|
|
5
471
|
### Major Changes
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import type { StorefrontClient } from '../client/types';
|
|
25
|
-
import type { Cart, CartCreateInput, CartLineInput, CartLineUpdateInput, CartBuyerIdentityInput, CartAttributeInput, CartSetShippingAddressInput, CartSetBillingAddressInput, CartSelectShippingMethodInput, CartSelectPaymentMethodInput, CartApplyGiftCardInput, CartRemoveGiftCardInput, CartUpdateGiftCardRecipientInput, CartCompleteInput, CartWarning, Order, DiscountValidationResult, PaymentSession, PaymentCreateInput, AvailableShippingMethodsPayload, AvailablePaymentMethods, ShippingAddressInput } from './types';
|
|
25
|
+
import type { Cart, CartCreateInput, CartLineInput, CartLineUpdateInput, CartBuyerIdentityInput, CartAttributeInput, CartSetShippingAddressInput, CartSetBillingAddressInput, CartSelectShippingMethodInput, CartSelectPaymentMethodInput, CartClearPaymentSelectionInput, CartApplyGiftCardInput, CartRemoveGiftCardInput, CartUpdateGiftCardRecipientInput, CartCompleteInput, CartWarning, Order, DiscountValidationResult, PaymentSession, PaymentCreateInput, AvailableShippingMethodsPayload, AvailablePaymentMethods, ShippingAddressInput } from './types';
|
|
26
26
|
/**
|
|
27
27
|
* Standard mutation return shape — `cart` is non-null on success (userErrors
|
|
28
28
|
* cause assertNoUserErrors to throw), `warnings` may be empty.
|
|
@@ -149,6 +149,15 @@ export declare class CartClient {
|
|
|
149
149
|
* no pre-authorization.
|
|
150
150
|
*/
|
|
151
151
|
selectPaymentMethod(input: CartSelectPaymentMethodInput): Promise<CartMutationOutcome>;
|
|
152
|
+
/**
|
|
153
|
+
* Clear all payment selection state on the cart — atomic NULL na method type,
|
|
154
|
+
* provider code, instrument code i legacy method ID. Idempotent — wielokrotne
|
|
155
|
+
* call = same final state. Use dla accordion "wróć do wyboru metody" UI flow.
|
|
156
|
+
* Cart MUSI być `ACTIVE` (CONVERTED carts throw z `ALREADY_COMPLETED`).
|
|
157
|
+
*
|
|
158
|
+
* Added by payment-instrument-preselection-advanced Adv-1 Req 3.6.
|
|
159
|
+
*/
|
|
160
|
+
clearPaymentSelection(input: CartClearPaymentSelectionInput): Promise<CartMutationOutcome>;
|
|
152
161
|
/**
|
|
153
162
|
* Apply gift card to cart (stackable z discount codes, FIFO consumption).
|
|
154
163
|
* Balance NOT debited yet — actual deduction at cartComplete.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart-client.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cart-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EACV,IAAI,EACJ,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,EAC3B,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,iBAAiB,EACjB,WAAW,EACX,KAAK,EACL,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EAClB,+BAA+B,EAC/B,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"cart-client.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cart-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import { assertNoUserErrors } from '../helpers/assert-no-user-errors';
|
|
25
|
-
import { CART_QUERY, ORDER_BY_TOKEN_QUERY, CART_AVAILABLE_SHIPPING_METHODS_QUERY, AVAILABLE_PAYMENT_METHODS_QUERY, CART_CREATE, CART_ADD_LINES, CART_UPDATE_LINES, CART_REMOVE_LINES, CART_DISCOUNT_CODES_UPDATE, CART_UPDATE_NOTE, CART_UPDATE_ATTRIBUTES, CART_UPDATE_BUYER_IDENTITY, CART_SET_SHIPPING_ADDRESS, CART_SET_BILLING_ADDRESS, CART_SELECT_SHIPPING_METHOD, CART_SELECT_PAYMENT_METHOD, CART_APPLY_GIFT_CARD, CART_REMOVE_GIFT_CARD, CART_UPDATE_GIFT_CARD_RECIPIENT, CART_COMPLETE, CART_VALIDATE_DISCOUNT_CODE, PAYMENT_CREATE, } from '../operations/cart';
|
|
25
|
+
import { CART_QUERY, ORDER_BY_TOKEN_QUERY, CART_AVAILABLE_SHIPPING_METHODS_QUERY, AVAILABLE_PAYMENT_METHODS_QUERY, CART_CREATE, CART_ADD_LINES, CART_UPDATE_LINES, CART_REMOVE_LINES, CART_DISCOUNT_CODES_UPDATE, CART_UPDATE_NOTE, CART_UPDATE_ATTRIBUTES, CART_UPDATE_BUYER_IDENTITY, CART_SET_SHIPPING_ADDRESS, CART_SET_BILLING_ADDRESS, CART_SELECT_SHIPPING_METHOD, CART_SELECT_PAYMENT_METHOD, CART_CLEAR_PAYMENT_SELECTION, CART_APPLY_GIFT_CARD, CART_REMOVE_GIFT_CARD, CART_UPDATE_GIFT_CARD_RECIPIENT, CART_COMPLETE, CART_VALIDATE_DISCOUNT_CODE, PAYMENT_CREATE, } from '../operations/cart';
|
|
26
26
|
export class CartClient {
|
|
27
27
|
client;
|
|
28
28
|
constructor(client) {
|
|
@@ -194,6 +194,22 @@ export class CartClient {
|
|
|
194
194
|
assertNoUserErrors(data.cartSelectPaymentMethod);
|
|
195
195
|
return { cart: data.cartSelectPaymentMethod.cart, warnings: data.cartSelectPaymentMethod.warnings ?? [] };
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Clear all payment selection state on the cart — atomic NULL na method type,
|
|
199
|
+
* provider code, instrument code i legacy method ID. Idempotent — wielokrotne
|
|
200
|
+
* call = same final state. Use dla accordion "wróć do wyboru metody" UI flow.
|
|
201
|
+
* Cart MUSI być `ACTIVE` (CONVERTED carts throw z `ALREADY_COMPLETED`).
|
|
202
|
+
*
|
|
203
|
+
* Added by payment-instrument-preselection-advanced Adv-1 Req 3.6.
|
|
204
|
+
*/
|
|
205
|
+
async clearPaymentSelection(input) {
|
|
206
|
+
const data = await this.client.mutate(CART_CLEAR_PAYMENT_SELECTION, { input });
|
|
207
|
+
assertNoUserErrors(data.cartClearPaymentSelection);
|
|
208
|
+
return {
|
|
209
|
+
cart: data.cartClearPaymentSelection.cart,
|
|
210
|
+
warnings: data.cartClearPaymentSelection.warnings ?? [],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
197
213
|
/**
|
|
198
214
|
* Apply gift card to cart (stackable z discount codes, FIFO consumption).
|
|
199
215
|
* Balance NOT debited yet — actual deduction at cartComplete.
|
|
@@ -55,6 +55,54 @@ export type CartRecoverableErrorCode = (typeof CART_RECOVERABLE_ERROR_CODES)[num
|
|
|
55
55
|
* `StorefrontError` carries at least one userError with a recoverable code.
|
|
56
56
|
*/
|
|
57
57
|
export declare function isCartRecoverableError(err: unknown): err is StorefrontError;
|
|
58
|
+
/**
|
|
59
|
+
* Cart error codes that signal a missing or invalid auth context on a
|
|
60
|
+
* customer-owned cart — distinct from cart-resource recovery
|
|
61
|
+
* (`CART_RECOVERABLE_ERROR_CODES`).
|
|
62
|
+
*
|
|
63
|
+
* - `CART_UNAUTHENTICATED` — anonymous request OR present-but-invalid token
|
|
64
|
+
* (expired JWT, malformed). The cart resource is intact and reachable
|
|
65
|
+
* again after re-auth.
|
|
66
|
+
*
|
|
67
|
+
* Storefronts SHOULD preserve the `cart-id` cookie when handling these
|
|
68
|
+
* codes and prompt sign-in — the same cart resumes after a successful login.
|
|
69
|
+
*/
|
|
70
|
+
export declare const CART_SESSION_ERROR_CODES: readonly ["CART_UNAUTHENTICATED"];
|
|
71
|
+
export type CartSessionErrorCode = (typeof CART_SESSION_ERROR_CODES)[number];
|
|
72
|
+
/**
|
|
73
|
+
* Type-safe predicate for "this error means the session is gone, but the
|
|
74
|
+
* cart resource is intact". Distinct from `isCartRecoverableError`
|
|
75
|
+
* (cart-resource recovery) — `isCartSessionError` matches auth-recoverable
|
|
76
|
+
* codes that should trigger re-auth flow, NOT cart cookie cleanup.
|
|
77
|
+
*
|
|
78
|
+
* Inspects `err.userErrors[].code` (structured field) — never matches against
|
|
79
|
+
* `err.message` (locale-dependent, non-stable).
|
|
80
|
+
*/
|
|
81
|
+
export declare function isCartSessionError(err: unknown): err is StorefrontError;
|
|
82
|
+
/**
|
|
83
|
+
* Event fired when the runner observes a session error (`CART_UNAUTHENTICATED`).
|
|
84
|
+
* Cart-id cookie is NOT cleared — the cart resource is intact and the buyer
|
|
85
|
+
* should be prompted to re-authenticate; after a successful login the same
|
|
86
|
+
* cart resumes.
|
|
87
|
+
*/
|
|
88
|
+
export interface CartSessionExpiredEvent {
|
|
89
|
+
/** Cart id from the cookie at the time of the failure (null when no cart was set). */
|
|
90
|
+
oldCartId: string | null;
|
|
91
|
+
/** Operation name from `CartRecoveryOperation.name` if provided, otherwise 'unknown'. */
|
|
92
|
+
operation: string;
|
|
93
|
+
/** Original `StorefrontError` carrying the `CART_UNAUTHENTICATED` userError. */
|
|
94
|
+
cause: unknown;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Thrown when a cart operation fails with a session error
|
|
98
|
+
* (`CART_UNAUTHENTICATED`). Distinct from `CartRecoveryNotPossibleError`
|
|
99
|
+
* (cart-resource issue) — storefronts should redirect to a sign-in flow
|
|
100
|
+
* and retry the operation after re-auth.
|
|
101
|
+
*/
|
|
102
|
+
export declare class CartSessionRequiredError extends Error {
|
|
103
|
+
readonly cause: unknown;
|
|
104
|
+
constructor(cause: unknown, message?: string);
|
|
105
|
+
}
|
|
58
106
|
/**
|
|
59
107
|
* Cookie I/O port. Caller's responsibility — core never touches `document.cookie`.
|
|
60
108
|
*
|
|
@@ -137,6 +185,14 @@ export interface ExecuteWithCartRecoveryOptions<T> {
|
|
|
137
185
|
cookieMaxAge?: number;
|
|
138
186
|
/** Optional listener fired before the runner throws `CartRecoveryNotPossibleError`. */
|
|
139
187
|
onExpired?: (event: CartExpiredEvent) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Optional listener fired before the runner throws
|
|
190
|
+
* `CartSessionRequiredError`. Distinct from `onExpired` — the cart
|
|
191
|
+
* resource is intact, but the session/auth context is missing or invalid.
|
|
192
|
+
* Storefront should redirect to sign-in and retry the operation after
|
|
193
|
+
* re-auth; the same cart resumes.
|
|
194
|
+
*/
|
|
195
|
+
onSessionExpired?: (event: CartSessionExpiredEvent) => void;
|
|
140
196
|
}
|
|
141
197
|
/**
|
|
142
198
|
* Runs an operation against the current cart, recovering once on
|
|
@@ -158,6 +214,13 @@ export interface CartRecoveryRunner {
|
|
|
158
214
|
* fails. Returns an unsubscribe function. Multiple subscribers are supported.
|
|
159
215
|
*/
|
|
160
216
|
onExpired(listener: (event: CartExpiredEvent) => void): () => void;
|
|
217
|
+
/**
|
|
218
|
+
* Subscribe to `session-expired` events fired when the runner observes a
|
|
219
|
+
* `CART_UNAUTHENTICATED` userError (cart resource intact, re-auth required).
|
|
220
|
+
* The cart-id cookie is preserved so the same cart resumes after a successful
|
|
221
|
+
* sign-in. Returns an unsubscribe function. Multiple subscribers are supported.
|
|
222
|
+
*/
|
|
223
|
+
onSessionExpired(listener: (event: CartSessionExpiredEvent) => void): () => void;
|
|
161
224
|
/** Underlying cart client (for advanced flows / read-only helpers). */
|
|
162
225
|
readonly cartClient: CartClient;
|
|
163
226
|
/** Underlying cookie store (for advanced flows). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart-recovery.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cart-recovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAMrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,kDAAmD,CAAC;AAE7F,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAQrF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,eAAe,CAM3E;AAMD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,GAAG,IAAI,MAAM,GAAG,IAAI,CAAC;IACrB,qFAAqF;IACrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,6EAA6E;IAC7E,KAAK,IAAI,IAAI,CAAC;CACf;AAMD;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,4CAA4C;IAC5C,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACpC;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAChF,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAMD,MAAM,MAAM,yBAAyB,GACjC,iBAAiB,GACjB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,MAAM,EAAE,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM;CAMhF;AAaD,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,MAAM,EAAE,yBAAyB,CAAC;IAClC,4FAA4F;IAC5F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,KAAK,EAAE,OAAO,CAAC;CAChB;AAmCD,MAAM,WAAW,8BAA8B,CAAC,CAAC;IAC/C,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACpC,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"cart-recovery.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cart-recovery.ts"],"names":[],"mappings":"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"}
|