@doswiftly/storefront-operations 16.1.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/AGENTS.md CHANGED
@@ -27,10 +27,10 @@ consumer's `codegen.ts` references this package's `.graphql` files as
27
27
  live in the consumer's repo.
28
28
 
29
29
  <!-- AUTOGEN:STATS:BEGIN — auto-regenerated, do not edit by hand -->
30
- - **Schema version**: 16.1.0
30
+ - **Schema version**: 17.0.0
31
31
  - **Queries**: 52
32
- - **Mutations**: 40
33
- - **Fragments**: 102
32
+ - **Mutations**: 41
33
+ - **Fragments**: 104
34
34
  <!-- AUTOGEN:STATS:END -->
35
35
 
36
36
  ## Loading order
package/CHANGELOG.md CHANGED
@@ -1,5 +1,341 @@
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
+
3
339
  ## 16.1.0
4
340
 
5
341
  ### Minor Changes
package/README.md CHANGED
@@ -380,12 +380,13 @@ full executable body of each operation.
380
380
  | `CartSetShippingAddress` | Phase 3 unify-cart-graphql-surface: wszystkie fulfillment + payment + completion operations teraz na Cart aggregate (zamiast Checkout dual-aggregate). Klient robi typowy checkout flow: cart create/add items → setShipping/Billing/Method → selectPayment → (optional) applyGiftCard → cartComplete → Order created. Sets the shipping address on the cart (full replace, not patch). Triggers cart re-pricing (tax recalculation per address country/region). Address format validated against `CartAddressInput` constraints (firstName/lastName/streetLine1/city/country/postalCode required). Errors: `INVALID_ADDRESS`, `CART_NOT_FOUND`. |
381
381
  | `CartSetBillingAddress` | Sets the billing address on the cart (full replace). Independent of shipping address — pass it explicitly even when "billing same as shipping". Errors: `INVALID_ADDRESS`, `CART_NOT_FOUND`. |
382
382
  | `CartSelectShippingMethod` | Selects a shipping method by `shippingMethodId` (typed `ID!`, a stable shipping-method UUID — NOT a per-request token). The id comes from a list of methods available for the current address + cart subtotal (queryable separately). Errors: `SHIPPING_METHOD_REQUIRED`, `ZIP_CODE_NOT_SUPPORTED`, `CART_NOT_FOUND`. |
383
- | `CartSelectPaymentMethod` | Selects a payment method by `paymentMethodId` (UUID from `availablePaymentMethods` query). Validates existence + active status; no pre-authorization performed here. Errors: `PAYMENT_METHOD_REQUIRED`, `INVALID_PAYMENT`, `CART_NOT_FOUND`. |
383
+ | `CartSelectPaymentMethod` | Selects a payment method on the cart by category (`methodType` BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProviderId` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`. |
384
384
  | `CartApplyGiftCard` | Applies a gift card to the cart, stackable with discount codes. Consumption is FIFO: each card consumes `min(remainingBalance, paymentDue)` against the current cart total in the order they were applied. The gift card balance is NOT debited yet — actual deduction happens atomically at `cartComplete`. Errors: `GIFT_CARD_NOT_FOUND`, `GIFT_CARD_DEPLETED`, `GIFT_CARD_UNUSABLE`. |
385
385
  | `CartRemoveGiftCard` | Removes a gift card from the applied list and recalculates FIFO `appliedAmount` for the remaining cards. Since gift card balances are only debited at `cartComplete`, removing before completion has no effect on the underlying gift card balance. |
386
386
  | `CartUpdateGiftCardRecipient` | Sets per-line-item recipient details (name, email, message) for digital gift card products in the cart (line items where the variant represents a gift-card SKU). Required before `cartComplete` for any line item with a gift-card variant. Recipient details propagated to the resulting order. |
387
387
  | `CartComplete` | Finalizes the cart — creates the `Order`, deducts gift cards, sends order-created confirmation — all atomically. **Idempotent on `idempotencyKey`** (auto-generated from cartId + minute timestamp if caller omits it). Returns `order` field after completion. Note: `paymentUrl` is intentionally NOT in payload — for hosted gateways (online providers) the storefront calls a separate `paymentCreate` mutation after this returns (check `order.canCreatePayment` first). Errors: `EMAIL_REQUIRED`, `SHIPPING_ADDRESS_REQUIRED`, `SHIPPING_METHOD_REQUIRED`, `PAYMENT_METHOD_REQUIRED`, `INSUFFICIENT_STOCK`, `ALREADY_COMPLETED`. |
388
- | `PaymentCreate` | Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`. |
388
+ | `PaymentCreate` | Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`, `INSTRUMENT_PRESELECTION_FAILED`. `warnings[]` array zawiera `INSTRUMENT_CLEARED_FOR_RETRY` post-auto-clear instrument-specific failure (storefront retry hint: next attempt uses gateway default landing). |
389
+ | `CartClearPaymentSelection` | Clears all payment selection state on the cart in a single atomic operation. Use for accordion "back to method picker" flows w storefront UI. Idempotent — calling twice yields the same end state. Cart MUSI być `ACTIVE` (CONVERTED carts reject z `ALREADY_COMPLETED`). Rate limit: 30 requests/minute per IP+shop. |
389
390
 
390
391
  #### Return Mutations
391
392
 
@@ -508,9 +509,11 @@ full executable body of each operation.
508
509
 
509
510
  | Fragment | On Type | Description |
510
511
  | --- | --- | --- |
511
- | `PaymentMethod` | `PaymentMethod` | Single payment method enabled for the shop type (CARD / BANK_TRANSFER / BLIK / PAYPAL / APPLE_PAY / GOOGLE_PAY / CASH_ON_DELIVERY / OTHER), provider, icon, supported currencies, default flag, sort position. Spread on the checkout payment step. |
512
+ | `PaymentMethodInstrument` | `PaymentMethodInstrument` | A single concrete instrument exposed by a gateway provider (BLIK code, branded bank, wallet, card brand). Pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. `displayHint` is a semantic UX hint (`PIN_ENTRY`, `WALLET_TAP`, `BANK_LIST`, `CARD_FORM`) — storefront branches rendering accordingly. `enabled: false` means the gateway reported the instrument as temporarily disabled — gray out, don't hide. |
513
+ | `PaymentMethod` | `PaymentMethod` | Single payment method enabled for the shop — method-centric. `type` is the category (CARD, BLIK, BANK_TRANSFER, CASH_ON_DELIVERY, OTHER) — drive iconography here. `provider`, `providersAvailable`, `preferredProvider` are `ProviderCode` enum (UPPERCASE: `PAYU`, `PRZELEWY24`, ...). `available` is `false` when the resolving gateway is temporarily unavailable or reported the method as disabled — gray-out the tile, don't hide it; `unavailableReason` carries the diagnostic. `instruments` lists concrete gateway-side instruments (BLIK code, branded banks, wallets) when the gateway exposes granular data — pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. Spread on the checkout payment step. |
512
514
  | `AvailablePaymentMethods` | `AvailablePaymentMethods` | Active payment methods list with the merchant's `defaultMethod`. Returned by the `availablePaymentMethods` query. |
513
515
  | `PaymentSession` | `PaymentSession` | Initiated payment session for an order — returned by the `paymentCreate` mutation. Branch on `flow`: `ONLINE_REDIRECT` (redirect the browser to `redirectUrl`), `ONLINE_EMBEDDED` (render an in-page widget with `clientSecret`), `INSTANT_DIRECT` (settled with no UI — read `status`). `expiresAt` is ISO 8601, present when the gateway session has a TTL. |
516
+ | `PaymentWarning` | `PaymentWarning` | Non-blocking signal emitted alongside `userErrors[]` in `paymentCreate` payload — backend state change context (e.g. an auto-clear of preselected instrument after gateway rejection). Pre-translated by backend per shop locale. `code === 'INSTRUMENT_CLEARED_FOR_RETRY'` signals storefront that the next `paymentCreate` will land on the gateway default landing page (server-side cleared `Order.paymentInstrumentCode` after `INSTRUMENT_PRESELECTION_FAILED`). `retryHint` is optional context for UI dispatch — currently unused for `INSTRUMENT_CLEARED_FOR_RETRY`, reserved for future warning codes. |
514
517
 
515
518
  #### Shipments / Tracking
516
519
 
package/fragments.graphql CHANGED
@@ -510,6 +510,7 @@ fragment Cart on Cart {
510
510
  selectedPaymentMethod {
511
511
  ...CartSelectedPaymentMethod
512
512
  }
513
+ selectedPaymentInstrumentCode
513
514
  appliedGiftCards {
514
515
  ...CartAppliedGiftCard
515
516
  }
@@ -700,7 +701,23 @@ fragment ShopConfigFields on Shop {
700
701
  # Payment Methods
701
702
  # ============================================
702
703
 
703
- # Single payment method enabled for the shop type (CARD / BANK_TRANSFER / BLIK / PAYPAL / APPLE_PAY / GOOGLE_PAY / CASH_ON_DELIVERY / OTHER), provider, icon, supported currencies, default flag, sort position. Spread on the checkout payment step.
704
+ # A single concrete instrument exposed by a gateway provider (BLIK code, branded bank, wallet, card brand). Pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. `displayHint` is a semantic UX hint (`PIN_ENTRY`, `WALLET_TAP`, `BANK_LIST`, `CARD_FORM`) — storefront branches rendering accordingly. `enabled: false` means the gateway reported the instrument as temporarily disabled — gray out, don't hide.
705
+ fragment PaymentMethodInstrument on PaymentMethodInstrument {
706
+ providerCode
707
+ instrumentCode
708
+ type
709
+ displayName
710
+ displayHint
711
+ brandImageUrl
712
+ enabled
713
+ }
714
+
715
+ # Single payment method enabled for the shop — method-centric.
716
+ # `type` is the category (CARD, BLIK, BANK_TRANSFER, CASH_ON_DELIVERY, OTHER) — drive iconography here.
717
+ # `provider`, `providersAvailable`, `preferredProvider` are `ProviderCode` enum (UPPERCASE: `PAYU`, `PRZELEWY24`, ...).
718
+ # `available` is `false` when the resolving gateway is temporarily unavailable or reported the method as disabled — gray-out the tile, don't hide it; `unavailableReason` carries the diagnostic.
719
+ # `instruments` lists concrete gateway-side instruments (BLIK code, branded banks, wallets) when the gateway exposes granular data — pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway.
720
+ # Spread on the checkout payment step.
704
721
  fragment PaymentMethod on PaymentMethod {
705
722
  id
706
723
  name
@@ -711,6 +728,13 @@ fragment PaymentMethod on PaymentMethod {
711
728
  isDefault
712
729
  supportedCurrencies
713
730
  position
731
+ providersAvailable
732
+ preferredProvider
733
+ available
734
+ unavailableReason
735
+ instruments {
736
+ ...PaymentMethodInstrument
737
+ }
714
738
  }
715
739
 
716
740
  # Active payment methods list with the merchant's `defaultMethod`. Returned by the `availablePaymentMethods` query.
@@ -735,6 +759,13 @@ fragment PaymentSession on PaymentSession {
735
759
  expiresAt
736
760
  }
737
761
 
762
+ # Non-blocking signal emitted alongside `userErrors[]` in `paymentCreate` payload — backend state change context (e.g. an auto-clear of preselected instrument after gateway rejection). Pre-translated by backend per shop locale. `code === 'INSTRUMENT_CLEARED_FOR_RETRY'` signals storefront that the next `paymentCreate` will land on the gateway default landing page (server-side cleared `Order.paymentInstrumentCode` after `INSTRUMENT_PRESELECTION_FAILED`). `retryHint` is optional context for UI dispatch — currently unused for `INSTRUMENT_CLEARED_FOR_RETRY`, reserved for future warning codes.
763
+ fragment PaymentWarning on PaymentWarning {
764
+ message
765
+ code
766
+ retryHint
767
+ }
768
+
738
769
  # ============================================
739
770
  # Discount Code Validation
740
771
  # ============================================
package/llms-full.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **16.1.0**
4
- > 52 queries · 40 mutations · 102 fragments
3
+ > Schema version: **17.0.0**
4
+ > 52 queries · 41 mutations · 104 fragments
5
5
 
6
6
  Auto-generated from `.graphql` source files. Do not edit by hand — this file is
7
7
  regenerated on every release to match the published schema.
@@ -1975,7 +1975,7 @@ mutation CartSelectShippingMethod($input: CartSelectShippingMethodInput!) {
1975
1975
 
1976
1976
  **Section**: Cart Completion Mutations
1977
1977
 
1978
- **Description**: Selects a payment method by `paymentMethodId` (UUID from `availablePaymentMethods` query). Validates existence + active status; no pre-authorization performed here. Errors: `PAYMENT_METHOD_REQUIRED`, `INVALID_PAYMENT`, `CART_NOT_FOUND`.
1978
+ **Description**: Selects a payment method on the cart by category (`methodType` BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProviderId` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.
1979
1979
 
1980
1980
  **Variables**:
1981
1981
  - `$input`: `CartSelectPaymentMethodInput!`
@@ -2115,12 +2115,12 @@ mutation CartComplete($input: CartCompleteInput!) {
2115
2115
 
2116
2116
  **Section**: Cart Completion Mutations
2117
2117
 
2118
- **Description**: Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`.
2118
+ **Description**: Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`, `INSTRUMENT_PRESELECTION_FAILED`. `warnings[]` array zawiera `INSTRUMENT_CLEARED_FOR_RETRY` post-auto-clear instrument-specific failure (storefront retry hint: next attempt uses gateway default landing).
2119
2119
 
2120
2120
  **Variables**:
2121
2121
  - `$input`: `PaymentCreateInput!`
2122
2122
 
2123
- **Fragments used**: `PaymentSession`, `UserError`
2123
+ **Fragments used**: `PaymentSession`, `PaymentWarning`, `UserError`
2124
2124
 
2125
2125
  **GraphQL**:
2126
2126
  ```graphql
@@ -2132,6 +2132,37 @@ mutation PaymentCreate($input: PaymentCreateInput!) {
2132
2132
  userErrors {
2133
2133
  ...UserError
2134
2134
  }
2135
+ warnings {
2136
+ ...PaymentWarning
2137
+ }
2138
+ }
2139
+ }
2140
+ ```
2141
+
2142
+ ### Mutation: `CartClearPaymentSelection`
2143
+
2144
+ **Section**: Cart Completion Mutations
2145
+
2146
+ **Description**: Clears all payment selection state on the cart in a single atomic operation. Use for accordion "back to method picker" flows w storefront UI. Idempotent — calling twice yields the same end state. Cart MUSI być `ACTIVE` (CONVERTED carts reject z `ALREADY_COMPLETED`). Rate limit: 30 requests/minute per IP+shop.
2147
+
2148
+ **Variables**:
2149
+ - `$input`: `CartClearPaymentSelectionInput!`
2150
+
2151
+ **Fragments used**: `Cart`, `CartWarning`, `UserError`
2152
+
2153
+ **GraphQL**:
2154
+ ```graphql
2155
+ mutation CartClearPaymentSelection($input: CartClearPaymentSelectionInput!) {
2156
+ cartClearPaymentSelection(input: $input) {
2157
+ cart {
2158
+ ...Cart
2159
+ }
2160
+ userErrors {
2161
+ ...UserError
2162
+ }
2163
+ warnings {
2164
+ ...CartWarning
2165
+ }
2135
2166
  }
2136
2167
  }
2137
2168
  ```
@@ -3171,6 +3202,7 @@ fragment Cart on Cart {
3171
3202
  selectedPaymentMethod {
3172
3203
  ...CartSelectedPaymentMethod
3173
3204
  }
3205
+ selectedPaymentInstrumentCode
3174
3206
  appliedGiftCards {
3175
3207
  ...CartAppliedGiftCard
3176
3208
  }
@@ -3470,11 +3502,32 @@ fragment ShopConfigFields on Shop {
3470
3502
  }
3471
3503
  ```
3472
3504
 
3505
+ ### Fragment: `PaymentMethodInstrument` on `PaymentMethodInstrument`
3506
+
3507
+ **Section**: Payment Methods
3508
+
3509
+ **Description**: A single concrete instrument exposed by a gateway provider (BLIK code, branded bank, wallet, card brand). Pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. `displayHint` is a semantic UX hint (`PIN_ENTRY`, `WALLET_TAP`, `BANK_LIST`, `CARD_FORM`) — storefront branches rendering accordingly. `enabled: false` means the gateway reported the instrument as temporarily disabled — gray out, don't hide.
3510
+
3511
+ **GraphQL**:
3512
+ ```graphql
3513
+ fragment PaymentMethodInstrument on PaymentMethodInstrument {
3514
+ providerCode
3515
+ instrumentCode
3516
+ type
3517
+ displayName
3518
+ displayHint
3519
+ brandImageUrl
3520
+ enabled
3521
+ }
3522
+ ```
3523
+
3473
3524
  ### Fragment: `PaymentMethod` on `PaymentMethod`
3474
3525
 
3475
3526
  **Section**: Payment Methods
3476
3527
 
3477
- **Description**: Single payment method enabled for the shop — type (CARD / BANK_TRANSFER / BLIK / PAYPAL / APPLE_PAY / GOOGLE_PAY / CASH_ON_DELIVERY / OTHER), provider, icon, supported currencies, default flag, sort position. Spread on the checkout payment step.
3528
+ **Description**: Single payment method enabled for the shop — method-centric. `type` is the category (CARD, BLIK, BANK_TRANSFER, CASH_ON_DELIVERY, OTHER) drive iconography here. `provider`, `providersAvailable`, `preferredProvider` are `ProviderCode` enum (UPPERCASE: `PAYU`, `PRZELEWY24`, ...). `available` is `false` when the resolving gateway is temporarily unavailable or reported the method as disabled — gray-out the tile, don't hide it; `unavailableReason` carries the diagnostic. `instruments` lists concrete gateway-side instruments (BLIK code, branded banks, wallets) when the gateway exposes granular data — pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. Spread on the checkout payment step.
3529
+
3530
+ **Uses fragments**: `PaymentMethodInstrument`
3478
3531
 
3479
3532
  **GraphQL**:
3480
3533
  ```graphql
@@ -3488,6 +3541,13 @@ fragment PaymentMethod on PaymentMethod {
3488
3541
  isDefault
3489
3542
  supportedCurrencies
3490
3543
  position
3544
+ providersAvailable
3545
+ preferredProvider
3546
+ available
3547
+ unavailableReason
3548
+ instruments {
3549
+ ...PaymentMethodInstrument
3550
+ }
3491
3551
  }
3492
3552
  ```
3493
3553
 
@@ -3531,6 +3591,21 @@ fragment PaymentSession on PaymentSession {
3531
3591
  }
3532
3592
  ```
3533
3593
 
3594
+ ### Fragment: `PaymentWarning` on `PaymentWarning`
3595
+
3596
+ **Section**: Payment Methods
3597
+
3598
+ **Description**: Non-blocking signal emitted alongside `userErrors[]` in `paymentCreate` payload — backend state change context (e.g. an auto-clear of preselected instrument after gateway rejection). Pre-translated by backend per shop locale. `code === 'INSTRUMENT_CLEARED_FOR_RETRY'` signals storefront that the next `paymentCreate` will land on the gateway default landing page (server-side cleared `Order.paymentInstrumentCode` after `INSTRUMENT_PRESELECTION_FAILED`). `retryHint` is optional context for UI dispatch — currently unused for `INSTRUMENT_CLEARED_FOR_RETRY`, reserved for future warning codes.
3599
+
3600
+ **GraphQL**:
3601
+ ```graphql
3602
+ fragment PaymentWarning on PaymentWarning {
3603
+ message
3604
+ code
3605
+ retryHint
3606
+ }
3607
+ ```
3608
+
3534
3609
  ### Fragment: `ShipmentEvent` on `ShipmentEvent`
3535
3610
 
3536
3611
  **Section**: Shipments / Tracking
package/mutations.graphql CHANGED
@@ -329,7 +329,11 @@ mutation CartSelectShippingMethod($input: CartSelectShippingMethodInput!) {
329
329
  }
330
330
  }
331
331
 
332
- # Selects a payment method by `paymentMethodId` (UUID from `availablePaymentMethods` query). Validates existence + active status; no pre-authorization performed here. Errors: `PAYMENT_METHOD_REQUIRED`, `INVALID_PAYMENT`, `CART_NOT_FOUND`.
332
+ # Selects a payment method on the cart by category (`methodType` BLIK, CARD, BANK_TRANSFER, ...).
333
+ # Optional `preferredProviderId` overrides the merchant priority when the buyer explicitly picks
334
+ # a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing
335
+ # from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category.
336
+ # Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.
333
337
  mutation CartSelectPaymentMethod($input: CartSelectPaymentMethodInput!) {
334
338
  cartSelectPaymentMethod(input: $input) {
335
339
  cart {
@@ -404,7 +408,7 @@ mutation CartComplete($input: CartCompleteInput!) {
404
408
  }
405
409
  }
406
410
 
407
- # Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`.
411
+ # Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`, `INSTRUMENT_PRESELECTION_FAILED`. `warnings[]` array zawiera `INSTRUMENT_CLEARED_FOR_RETRY` post-auto-clear instrument-specific failure (storefront retry hint: next attempt uses gateway default landing).
408
412
  mutation PaymentCreate($input: PaymentCreateInput!) {
409
413
  paymentCreate(input: $input) {
410
414
  payment {
@@ -413,6 +417,24 @@ mutation PaymentCreate($input: PaymentCreateInput!) {
413
417
  userErrors {
414
418
  ...UserError
415
419
  }
420
+ warnings {
421
+ ...PaymentWarning
422
+ }
423
+ }
424
+ }
425
+
426
+ # Clears all payment selection state on the cart in a single atomic operation. Use for accordion "back to method picker" flows w storefront UI. Idempotent — calling twice yields the same end state. Cart MUSI być `ACTIVE` (CONVERTED carts reject z `ALREADY_COMPLETED`). Rate limit: 30 requests/minute per IP+shop.
427
+ mutation CartClearPaymentSelection($input: CartClearPaymentSelectionInput!) {
428
+ cartClearPaymentSelection(input: $input) {
429
+ cart {
430
+ ...Cart
431
+ }
432
+ userErrors {
433
+ ...UserError
434
+ }
435
+ warnings {
436
+ ...CartWarning
437
+ }
416
438
  }
417
439
  }
418
440
 
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "16.1.0",
2
+ "schemaVersion": "17.0.0",
3
3
  "queries": [
4
4
  {
5
5
  "name": "Shop",
@@ -1518,7 +1518,7 @@
1518
1518
  "name": "CartSelectPaymentMethod",
1519
1519
  "kind": "mutation",
1520
1520
  "section": "Cart Completion Mutations",
1521
- "description": "Selects a payment method by `paymentMethodId` (UUID from `availablePaymentMethods` query). Validates existence + active status; no pre-authorization performed here. Errors: `PAYMENT_METHOD_REQUIRED`, `INVALID_PAYMENT`, `CART_NOT_FOUND`.",
1521
+ "description": "Selects a payment method on the cart by category (`methodType` BLIK, CARD, BANK_TRANSFER, ...). Optional `preferredProviderId` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. The backend resolves the gateway routing from `MerchantPaymentConfig` at `cartComplete`; the selection persisted here is the category. Errors: `PAYMENT_METHOD_REQUIRED`, `CART_NOT_FOUND`, `CART_UNAUTHENTICATED`.",
1522
1522
  "variables": [
1523
1523
  {
1524
1524
  "name": "input",
@@ -1613,7 +1613,7 @@
1613
1613
  "name": "PaymentCreate",
1614
1614
  "kind": "mutation",
1615
1615
  "section": "Cart Completion Mutations",
1616
- "description": "Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`.",
1616
+ "description": "Initiates a payment session for an order created by `cartComplete` — call this when `order.canCreatePayment` is `true` (orders with an offline payment method like cash-on-delivery skip this step). `orderId` is required; `returnUrl` / `cancelUrl` are optional and, when supplied, must point to a verified domain of the shop (open-redirect protected). Branch on the returned `payment.flow`: `ONLINE_REDIRECT` → redirect to `payment.redirectUrl`, `ONLINE_EMBEDDED` → render a widget with `payment.clientSecret`, `INSTANT_DIRECT` → already settled, read `payment.status`. Public, but ownership-checked — an authenticated customer cannot pay for another customer's order. **Idempotent** — calling it again for the same order returns the existing still-valid session instead of creating a duplicate (safe to retry). Rate limit: 5 requests/minute. `userErrors[].code`: `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `INVALID_ID_FORMAT`, `PAYMENT_FAILED`, `INSTRUMENT_PRESELECTION_FAILED`. `warnings[]` array zawiera `INSTRUMENT_CLEARED_FOR_RETRY` post-auto-clear instrument-specific failure (storefront retry hint: next attempt uses gateway default landing).",
1617
1617
  "variables": [
1618
1618
  {
1619
1619
  "name": "input",
@@ -1623,9 +1623,29 @@
1623
1623
  ],
1624
1624
  "fragmentRefs": [
1625
1625
  "PaymentSession",
1626
+ "PaymentWarning",
1626
1627
  "UserError"
1627
1628
  ],
1628
- "body": "mutation PaymentCreate($input: PaymentCreateInput!) {\n paymentCreate(input: $input) {\n payment {\n ...PaymentSession\n }\n userErrors {\n ...UserError\n }\n }\n}"
1629
+ "body": "mutation PaymentCreate($input: PaymentCreateInput!) {\n paymentCreate(input: $input) {\n payment {\n ...PaymentSession\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...PaymentWarning\n }\n }\n}"
1630
+ },
1631
+ {
1632
+ "name": "CartClearPaymentSelection",
1633
+ "kind": "mutation",
1634
+ "section": "Cart Completion Mutations",
1635
+ "description": "Clears all payment selection state on the cart in a single atomic operation. Use for accordion \"back to method picker\" flows w storefront UI. Idempotent — calling twice yields the same end state. Cart MUSI być `ACTIVE` (CONVERTED carts reject z `ALREADY_COMPLETED`). Rate limit: 30 requests/minute per IP+shop.",
1636
+ "variables": [
1637
+ {
1638
+ "name": "input",
1639
+ "type": "CartClearPaymentSelectionInput!",
1640
+ "defaultValue": null
1641
+ }
1642
+ ],
1643
+ "fragmentRefs": [
1644
+ "Cart",
1645
+ "CartWarning",
1646
+ "UserError"
1647
+ ],
1648
+ "body": "mutation CartClearPaymentSelection($input: CartClearPaymentSelectionInput!) {\n cartClearPaymentSelection(input: $input) {\n cart {\n ...Cart\n }\n userErrors {\n ...UserError\n }\n warnings {\n ...CartWarning\n }\n }\n}"
1629
1649
  },
1630
1650
  {
1631
1651
  "name": "ReturnCreate",
@@ -2177,7 +2197,7 @@
2177
2197
  "MailingAddress",
2178
2198
  "PageInfo"
2179
2199
  ],
2180
- "body": "fragment Cart on Cart {\n id\n checkoutUrl\n totalQuantity\n cost {\n ...CartCost\n }\n lines(first: 100) {\n edges {\n cursor\n node {\n ... on CartLine {\n ...CartLine\n }\n }\n }\n nodes {\n ... on CartLine {\n ...CartLine\n }\n }\n pageInfo {\n ...PageInfo\n }\n totalCount\n }\n buyerIdentity {\n ...CartBuyerIdentity\n }\n discountCodes {\n ...CartDiscountCode\n }\n discountAllocations {\n ...CartDiscountAllocation\n }\n note\n attributes {\n key\n value\n }\n email\n phone\n shippingAddress {\n ...MailingAddress\n }\n billingAddress {\n ...MailingAddress\n }\n selectedShippingMethod {\n ...CartShippingMethod\n }\n selectedPaymentMethod {\n ...CartSelectedPaymentMethod\n }\n appliedGiftCards {\n ...CartAppliedGiftCard\n }\n requiresShipping\n createdAt\n updatedAt\n status\n completedOrder {\n id\n orderNumber\n accessToken\n status\n paymentStatus\n fulfillmentStatus\n }\n}",
2200
+ "body": "fragment Cart on Cart {\n id\n checkoutUrl\n totalQuantity\n cost {\n ...CartCost\n }\n lines(first: 100) {\n edges {\n cursor\n node {\n ... on CartLine {\n ...CartLine\n }\n }\n }\n nodes {\n ... on CartLine {\n ...CartLine\n }\n }\n pageInfo {\n ...PageInfo\n }\n totalCount\n }\n buyerIdentity {\n ...CartBuyerIdentity\n }\n discountCodes {\n ...CartDiscountCode\n }\n discountAllocations {\n ...CartDiscountAllocation\n }\n note\n attributes {\n key\n value\n }\n email\n phone\n shippingAddress {\n ...MailingAddress\n }\n billingAddress {\n ...MailingAddress\n }\n selectedShippingMethod {\n ...CartShippingMethod\n }\n selectedPaymentMethod {\n ...CartSelectedPaymentMethod\n }\n selectedPaymentInstrumentCode\n appliedGiftCards {\n ...CartAppliedGiftCard\n }\n requiresShipping\n createdAt\n updatedAt\n status\n completedOrder {\n id\n orderNumber\n accessToken\n status\n paymentStatus\n fulfillmentStatus\n }\n}",
2181
2201
  "onType": "Cart"
2182
2202
  },
2183
2203
  {
@@ -2330,13 +2350,25 @@
2330
2350
  "onType": "Shop"
2331
2351
  },
2332
2352
  {
2333
- "name": "PaymentMethod",
2353
+ "name": "PaymentMethodInstrument",
2334
2354
  "kind": "fragment",
2335
2355
  "section": "Payment Methods",
2336
- "description": "Single payment method enabled for the shop type (CARD / BANK_TRANSFER / BLIK / PAYPAL / APPLE_PAY / GOOGLE_PAY / CASH_ON_DELIVERY / OTHER), provider, icon, supported currencies, default flag, sort position. Spread on the checkout payment step.",
2356
+ "description": "A single concrete instrument exposed by a gateway provider (BLIK code, branded bank, wallet, card brand). Pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. `displayHint` is a semantic UX hint (`PIN_ENTRY`, `WALLET_TAP`, `BANK_LIST`, `CARD_FORM`) — storefront branches rendering accordingly. `enabled: false` means the gateway reported the instrument as temporarily disabled — gray out, don't hide.",
2337
2357
  "variables": [],
2338
2358
  "fragmentRefs": [],
2339
- "body": "fragment PaymentMethod on PaymentMethod {\n id\n name\n provider\n type\n icon\n description\n isDefault\n supportedCurrencies\n position\n}",
2359
+ "body": "fragment PaymentMethodInstrument on PaymentMethodInstrument {\n providerCode\n instrumentCode\n type\n displayName\n displayHint\n brandImageUrl\n enabled\n}",
2360
+ "onType": "PaymentMethodInstrument"
2361
+ },
2362
+ {
2363
+ "name": "PaymentMethod",
2364
+ "kind": "fragment",
2365
+ "section": "Payment Methods",
2366
+ "description": "Single payment method enabled for the shop — method-centric. `type` is the category (CARD, BLIK, BANK_TRANSFER, CASH_ON_DELIVERY, OTHER) — drive iconography here. `provider`, `providersAvailable`, `preferredProvider` are `ProviderCode` enum (UPPERCASE: `PAYU`, `PRZELEWY24`, ...). `available` is `false` when the resolving gateway is temporarily unavailable or reported the method as disabled — gray-out the tile, don't hide it; `unavailableReason` carries the diagnostic. `instruments` lists concrete gateway-side instruments (BLIK code, branded banks, wallets) when the gateway exposes granular data — pass `instrumentCode` as `preferredInstrumentCode` in `cartSelectPaymentMethod` for direct deep-link to that instrument screen on the gateway. Spread on the checkout payment step.",
2367
+ "variables": [],
2368
+ "fragmentRefs": [
2369
+ "PaymentMethodInstrument"
2370
+ ],
2371
+ "body": "fragment PaymentMethod on PaymentMethod {\n id\n name\n provider\n type\n icon\n description\n isDefault\n supportedCurrencies\n position\n providersAvailable\n preferredProvider\n available\n unavailableReason\n instruments {\n ...PaymentMethodInstrument\n }\n}",
2340
2372
  "onType": "PaymentMethod"
2341
2373
  },
2342
2374
  {
@@ -2361,6 +2393,16 @@
2361
2393
  "body": "fragment PaymentSession on PaymentSession {\n id\n orderId\n flow\n provider\n redirectUrl\n clientSecret\n status\n expiresAt\n}",
2362
2394
  "onType": "PaymentSession"
2363
2395
  },
2396
+ {
2397
+ "name": "PaymentWarning",
2398
+ "kind": "fragment",
2399
+ "section": "Payment Methods",
2400
+ "description": "Non-blocking signal emitted alongside `userErrors[]` in `paymentCreate` payload — backend state change context (e.g. an auto-clear of preselected instrument after gateway rejection). Pre-translated by backend per shop locale. `code === 'INSTRUMENT_CLEARED_FOR_RETRY'` signals storefront that the next `paymentCreate` will land on the gateway default landing page (server-side cleared `Order.paymentInstrumentCode` after `INSTRUMENT_PRESELECTION_FAILED`). `retryHint` is optional context for UI dispatch — currently unused for `INSTRUMENT_CLEARED_FOR_RETRY`, reserved for future warning codes.",
2401
+ "variables": [],
2402
+ "fragmentRefs": [],
2403
+ "body": "fragment PaymentWarning on PaymentWarning {\n message\n code\n retryHint\n}",
2404
+ "onType": "PaymentWarning"
2405
+ },
2364
2406
  {
2365
2407
  "name": "ShipmentEvent",
2366
2408
  "kind": "fragment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "16.1.0",
3
+ "version": "17.0.0",
4
4
  "description": "GraphQL operations for DoSwiftly Storefront - SSOT from backend",
5
5
  "homepage": "https://doswiftly.pl",
6
6
  "publishConfig": {
package/schema.graphql CHANGED
@@ -880,7 +880,9 @@ type Cart implements Node {
880
880
  """
881
881
  attributes: [CartAttribute!]!
882
882
 
883
- """Available payment methods dla tego cart (shop-configured providers)"""
883
+ """
884
+ Available payment methods dla tego carta — deduplikowane per type, sorted by merchant priority.
885
+ """
884
886
  availablePaymentMethods: [PaymentMethod!]!
885
887
 
886
888
  """
@@ -955,6 +957,11 @@ type Cart implements Node {
955
957
  """
956
958
  requiresShipping: Boolean!
957
959
 
960
+ """
961
+ Optional concrete instrument code selected within `selectedPaymentMethod` (e.g. `"blik"`, `"mb"`, `"154"`). Set gdy klient klika konkretny instrument tile na storefront (per `PaymentMethod.instruments`). Pre-payment intent: skopiowany do `Order.paymentInstrumentCode` przy `cartComplete`. Storefront może cross-reference z `availablePaymentMethods.methods[].instruments[]` żeby dostać `displayName` / `brandImageUrl`. Added by payment-instrument-preselection core sprint Req 3 AC7.
962
+ """
963
+ selectedPaymentInstrumentCode: String
964
+
958
965
  """
959
966
  The payment method currently selected on the cart. Null until the buyer picks a method via `cartSelectPaymentMethod`.
960
967
  """
@@ -982,6 +989,11 @@ type Cart implements Node {
982
989
 
983
990
  """When the cart was last modified (ISO 8601)."""
984
991
  updatedAt: DateTime!
992
+
993
+ """
994
+ Non-fatal advisories computed at query time. Currently emitted: `PAYMENT_SELECTION_STALE` when `selectedPaymentMethod` / `selectedPaymentInstrumentCode` are no longer in live gateway capabilities (storefront re-prompts the buyer). Read-only — backend state is not mutated. Added by payment-instrument-preselection-advanced sub-sprint Adv-2 Req 1.
995
+ """
996
+ warnings: [CartWarning!]!
985
997
  }
986
998
 
987
999
  """Result of `cartAddLines`."""
@@ -1171,6 +1183,30 @@ input CartBuyerIdentityInput {
1171
1183
  phone: String
1172
1184
  }
1173
1185
 
1186
+ """
1187
+ Input for `cartClearPaymentSelection` — explicit deselect mutation. Use when the buyer goes back to the payment method picker w accordion UI bez hack typu re-selectując method. Atomic NULL na wszystkich payment selection fields (method type + provider code + instrument code + legacy method ID). Idempotent — wielokrotne wywołanie = same final state. Added by payment-instrument-preselection-advanced Adv-1 Req 3.1.
1188
+ """
1189
+ input CartClearPaymentSelectionInput {
1190
+ """ID of the cart to clear payment selection on."""
1191
+ cartId: ID!
1192
+ }
1193
+
1194
+ """
1195
+ Result of `cartClearPaymentSelection`. `cart` populated on success z cleared selection fields (`selectedPaymentMethod === null`, `selectedPaymentInstrumentCode === null`). `userErrors` populated gdy cart locked (CONVERTED status → `ALREADY_COMPLETED` code).
1196
+ """
1197
+ type CartClearPaymentSelectionPayload {
1198
+ """The updated cart z cleared payment selection."""
1199
+ cart: Cart
1200
+
1201
+ """
1202
+ Business / validation errors carrying a stable `CartErrorCode` in `code` (e.g. `ALREADY_COMPLETED`).
1203
+ """
1204
+ userErrors: [UserError!]!
1205
+
1206
+ """Non-fatal advisories — the mutation itself succeeded."""
1207
+ warnings: [CartWarning!]!
1208
+ }
1209
+
1174
1210
  """Input for `cartComplete` — finalises the cart into an `Order`."""
1175
1211
  input CartCompleteInput {
1176
1212
  """ID of the cart to complete."""
@@ -1551,15 +1587,27 @@ type CartRemoveLinesPayload {
1551
1587
  warnings: [CartWarning!]!
1552
1588
  }
1553
1589
 
1554
- """Input for `cartSelectPaymentMethod`."""
1590
+ """
1591
+ Input for `cartSelectPaymentMethod` (Intelligent Payment Methods). Method-centric: the buyer picks a category (BLIK, CARD, BANK_TRANSFER, ...) and the backend resolves the preferred provider from `MerchantPaymentConfig` (priority-sorted, merchant-configurable per shop). Optional `preferredProviderCode` + `preferredInstrumentCode` enable instrument-level deep-link na gateway hosted page (skipa default landing).
1592
+ """
1555
1593
  input CartSelectPaymentMethodInput {
1556
1594
  """ID of the cart to update."""
1557
1595
  cartId: ID!
1558
1596
 
1559
1597
  """
1560
- ID of the chosen payment method (matches a `PaymentMethod.id` from `availablePaymentMethods`).
1598
+ Category of the payment method the buyer picked (matches `PaymentMethod.type` from `availablePaymentMethods`).
1599
+ """
1600
+ methodType: PaymentMethodType!
1601
+
1561
1602
  """
1562
- paymentMethodId: ID!
1603
+ Optional gateway-specific instrument code (`blik`, `mb`, `c`, `154`, ...) z `PaymentMethod.instruments[].instrumentCode`. Wymaga ustawienia `preferredProviderCode` — instrument code jest provider-specific. Bez restrictive regex (różne bramki używają `.` / `:` / `/` w przyszłości). SQL injection chroniona przez Prisma parametrized queries. Added by payment-instrument-preselection core sprint Req 3.1.
1604
+ """
1605
+ preferredInstrumentCode: String
1606
+
1607
+ """
1608
+ Optional override of `PaymentMethod.preferredProvider`. Pass a provider code (`payu`, `przelewy24`, ...) from `providersAvailable` when the buyer explicitly picks one (rare — usually the merchant priority is enough). Format: lowercase alphanumerics + underscore + hyphen, 2-50 characters. Renamed from `preferredProviderId` — `*Code` is reserved for lowercase string identifiers, `*Id` for UUIDs.
1609
+ """
1610
+ preferredProviderCode: String
1563
1611
  }
1564
1612
 
1565
1613
  """Result of `cartSelectPaymentMethod`."""
@@ -1807,6 +1855,7 @@ enum CartWarningCode {
1807
1855
  MERCHANDISE_NOT_AVAILABLE
1808
1856
  MERCHANDISE_NOT_ENOUGH_STOCK
1809
1857
  PAYMENTS_AMOUNT_REGION_MISMATCH
1858
+ PAYMENT_SELECTION_STALE
1810
1859
  SHIPPING_METHOD_AUTO_CLEARED
1811
1860
  }
1812
1861
 
@@ -3843,6 +3892,11 @@ type Mutation {
3843
3892
  """
3844
3893
  cartApplyGiftCard(input: CartApplyGiftCardInput!): CartApplyGiftCardPayload!
3845
3894
 
3895
+ """
3896
+ Clear all payment selection state on the cart in a single atomic operation. Use for accordion "back to method picker" flows w storefront UI. Idempotent — calling twice yields the same end state. Cart MUSI być `ACTIVE` (CONVERTED carts reject z `ALREADY_COMPLETED`).
3897
+ """
3898
+ cartClearPaymentSelection(input: CartClearPaymentSelectionInput!): CartClearPaymentSelectionPayload!
3899
+
3846
3900
  """
3847
3901
  Finalise the cart into an `Order`. The cart is locked and no longer accepts mutations. Payment is initiated separately via `paymentCreate` — check `order.canCreatePayment` to decide whether to render a "Pay now" button or send the buyer directly to the confirmation page (for offline methods such as cash on delivery). Rate-limited to 5 requests per minute per IP+shop; pass an `idempotencyKey` to guard against duplicate orders on retry.
3848
3902
  """
@@ -3869,7 +3923,7 @@ type Mutation {
3869
3923
  cartRemoveLines(id: ID!, lineIds: [ID!]!): CartRemoveLinesPayload!
3870
3924
 
3871
3925
  """
3872
- Select a payment method on the cart. Validates against the active shop payment methods and currency. The selection is finalised at `cartComplete`; payment itself is initiated via a separate `paymentCreate` mutation after the order is created.
3926
+ Select a payment method on the cart by category (BLIK, CARD, BANK_TRANSFER, ...). Backend resolves the preferred provider from `MerchantPaymentConfig` at `paymentCreate` time. Optional `preferredProviderCode` overrides the merchant priority when the buyer explicitly picks a gateway from `PaymentMethod.providersAvailable`. Optional `preferredInstrumentCode` enables instrument-level deep-link na gateway hosted page (BLIK ekran, mBank flow, etc.) — wymaga `preferredProviderCode`.
3873
3927
  """
3874
3928
  cartSelectPaymentMethod(input: CartSelectPaymentMethodInput!): CartSelectPaymentMethodPayload!
3875
3929
 
@@ -4337,9 +4391,14 @@ type PaymentCreatePayload {
4337
4391
  payment: PaymentSession
4338
4392
 
4339
4393
  """
4340
- Business / validation errors carrying a stable `PaymentErrorCode` in `code` (e.g. `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `PAYMENT_FAILED`).
4394
+ Business / validation errors carrying a stable `PaymentErrorCode` in `code` (e.g. `ORDER_NOT_FOUND`, `ORDER_ALREADY_PAID`, `ORDER_NOT_PAYABLE`, `PAYMENT_PROVIDER_NOT_CONFIGURED`, `RETURN_URL_INVALID`, `PAYMENT_FAILED`, `INSTRUMENT_PRESELECTION_FAILED`).
4341
4395
  """
4342
4396
  userErrors: [UserError!]!
4397
+
4398
+ """
4399
+ Non-blocking signals — backend state change context (e.g. instrument auto-cleared, server-side adjustment). Default empty array. Storefront branches per `code` for UI retry hints / analytics events. Added by payment-instrument-preselection-advanced Adv-1 Req 2.3.
4400
+ """
4401
+ warnings: [PaymentWarning!]!
4343
4402
  }
4344
4403
 
4345
4404
  """
@@ -4356,6 +4415,11 @@ enum PaymentInitiationFlow {
4356
4415
  A payment method offered to the buyer at checkout — what to render in the payment picker and pass to `cartSelectPaymentMethod`.
4357
4416
  """
4358
4417
  type PaymentMethod {
4418
+ """
4419
+ True when the buyer can actually pick this method right now. False when the resolving gateway is temporarily unavailable (incident/maintenance) or reported the method as disabled. Storefront UI should gray-out the tile when false instead of hiding it — gives merchants observability into routing failures.
4420
+ """
4421
+ available: Boolean!
4422
+
4359
4423
  """
4360
4424
  Optional buyer-facing description shown under the name (e.g. "Pay with your bank app").
4361
4425
  """
@@ -4371,6 +4435,11 @@ type PaymentMethod {
4371
4435
  """
4372
4436
  id: ID!
4373
4437
 
4438
+ """
4439
+ Concrete instruments exposed by gateway providers w obrębie tej method (BLIK code, branded banks, wallets, card brands). Null gdy żaden provider nie expose'uje granular data dla tej method. Empty array gdy gateway expose'uje ale wszystkie instruments są disabled lub po post-filter (cross-provider leak prevention). Storefront może wyrenderować listę i pass `instrumentCode` jako `preferredInstrumentCode` w `cartSelectPaymentMethod` → gateway deep-link na ten ekran. Added by payment-instrument-preselection core sprint Req 1.
4440
+ """
4441
+ instruments: [PaymentMethodInstrument!]
4442
+
4374
4443
  """
4375
4444
  True when the merchant has marked this method as the default. Pre-select it in the picker.
4376
4445
  """
@@ -4387,9 +4456,19 @@ type PaymentMethod {
4387
4456
  position: Float!
4388
4457
 
4389
4458
  """
4390
- Provider code (e.g. "payu", "stripe", "p24", "cash_on_delivery"). Identifies the integration behind the method; do not branch UI on it use `type` instead.
4459
+ Preferred provider code (UPPERCASE enum) that the backend will route to when the buyer picks this method type and does not specify `preferredProviderCode`. Populated only when at least one provider supports the type. Typed enum since core sprint payment-instrument-preselection (was `String` pre-v9.0).
4391
4460
  """
4392
- provider: String!
4461
+ preferredProvider: ProviderCode
4462
+
4463
+ """
4464
+ Provider code (e.g. `PAYU`, `STRIPE`, `PRZELEWY24`, `CASH_ON_DELIVERY`). Identifies the integration behind the method; do not branch UI on it — use `type` instead. Typed enum since core sprint payment-instrument-preselection (was `String` pre-v9.0).
4465
+ """
4466
+ provider: ProviderCode!
4467
+
4468
+ """
4469
+ Provider codes (UPPERCASE enum: `PAYU`, `PRZELEWY24`, ...) that can fulfil this method type for the current shop, ordered by merchant priority. Pre-select `preferredProvider`; expose the rest only when the buyer wants to choose explicitly. Single-element array when only one provider supports the type. Typed enum since core sprint payment-instrument-preselection (was `[String]` pre-v9.0).
4470
+ """
4471
+ providersAvailable: [ProviderCode!]
4393
4472
 
4394
4473
  """
4395
4474
  ISO 4217 currency codes the method accepts. Null when the method accepts the shop currency without restriction.
@@ -4400,6 +4479,72 @@ type PaymentMethod {
4400
4479
  Category of the method (CARD, BLIK, BANK_TRANSFER, CASH_ON_DELIVERY, OTHER). Drives iconography and copy.
4401
4480
  """
4402
4481
  type: PaymentMethodType!
4482
+
4483
+ """
4484
+ When `available` is false, this enum carries the diagnostic reason (GATEWAY_DOWN, GATEWAY_DISABLED, NO_INSTRUMENTS, CREDENTIALS_INVALID). Null when `available` is true. UI can render context-aware copy ("PayU is temporarily down" vs "Method not configured").
4485
+ """
4486
+ unavailableReason: PaymentMethodUnavailableReason
4487
+ }
4488
+
4489
+ """
4490
+ A single concrete instrument exposed by a gateway provider (e.g. BLIK code, mBank Pay-By-Link, Apple Pay) within a broader PaymentMethod. Pass `instrumentCode` jako `preferredInstrumentCode` w `cartSelectPaymentMethod` żeby gateway deep-link prosto na ten ekran.
4491
+ """
4492
+ type PaymentMethodInstrument {
4493
+ """
4494
+ Optional brand image URL (bank logo, wallet icon). Storefront może użyć jako tile artwork. Null gdy gateway nie expose'uje albo instrument nie ma brand visual (BLIK code).
4495
+ """
4496
+ brandImageUrl: String
4497
+
4498
+ """
4499
+ UX rendering hint — jak storefront powinien wyrenderować ten instrument (prominent button vs branded tile vs dropdown vs radio). Backend-agnostic mapping na visual treatment.
4500
+ """
4501
+ displayHint: PaymentMethodInstrumentDisplayHint!
4502
+
4503
+ """
4504
+ Buyer-facing display name (e.g. "BLIK", "mBank", "ING Bank Śląski", "Apple Pay"). Lokalizacja zazwyczaj PL/EN — multi-language defer post-v1.
4505
+ """
4506
+ displayName: String!
4507
+
4508
+ """
4509
+ True gdy instrument jest currently enabled w gateway live capabilities. Storefront może gray-out tile gdy false zamiast hide (observability dla merchant).
4510
+ """
4511
+ enabled: Boolean!
4512
+
4513
+ """
4514
+ Gateway-specific instrument identifier (PayU: `"blik"`/`"mb"`/`"c"`, P24: numeric ID string `"154"`). Pass jako `preferredInstrumentCode` w `cartSelectPaymentMethod`. Stabilne per provider — gateway nie renumeruje.
4515
+ """
4516
+ instrumentCode: String!
4517
+
4518
+ """
4519
+ Provider obsługujący ten instrument (UPPERCASE enum). Wymagane dla cross-provider dedupe (np. BLIK code dostępny zarówno przez PayU jak P24 — różne instrumenty mimo tej samej method type).
4520
+ """
4521
+ providerCode: ProviderCode!
4522
+
4523
+ """
4524
+ Semantic type klasyfikujący instrument w obrębie method (BLIK code vs bank vs wallet vs card brand). Storefront-facing dispatch dla per-instrument UI components.
4525
+ """
4526
+ type: PaymentMethodInstrumentType!
4527
+ }
4528
+
4529
+ """
4530
+ UX rendering hint dla payment instrument — storefront może przełączać między prominent button / branded tile / dropdown / radio per hint. Backend-agnostic mapping na visual treatment.
4531
+ """
4532
+ enum PaymentMethodInstrumentDisplayHint {
4533
+ BRANDED_TILE
4534
+ DROPDOWN_OPTION
4535
+ PROMINENT_BUTTON
4536
+ RADIO_OPTION
4537
+ }
4538
+
4539
+ """
4540
+ Klasyfikacja konkretnego instrumentu w obrębie PaymentMethod (BLIK kod vs bank vs wallet vs card brand). Storefront-facing typing dla per-instrument UI dispatch.
4541
+ """
4542
+ enum PaymentMethodInstrumentType {
4543
+ BANK
4544
+ BLIK_CODE
4545
+ CARD_BRAND
4546
+ OTHER
4547
+ WALLET
4403
4548
  }
4404
4549
 
4405
4550
  """
@@ -4413,6 +4558,16 @@ enum PaymentMethodType {
4413
4558
  OTHER
4414
4559
  }
4415
4560
 
4561
+ """
4562
+ Why a payment method is currently unavailable. GATEWAY_DOWN/DISABLED/NO_INSTRUMENTS are transient (gateway-side); CREDENTIALS_INVALID needs merchant action in the admin panel.
4563
+ """
4564
+ enum PaymentMethodUnavailableReason {
4565
+ CREDENTIALS_INVALID
4566
+ GATEWAY_DISABLED
4567
+ GATEWAY_DOWN
4568
+ NO_INSTRUMENTS
4569
+ }
4570
+
4416
4571
  """
4417
4572
  Payment session created for an order. Branch on `flow` to launch payment: ONLINE_REDIRECT → redirect to `redirectUrl`; ONLINE_EMBEDDED → render an in-page widget using `clientSecret`; INSTANT_DIRECT → the payment is already settled, check `status`.
4418
4573
  """
@@ -4471,6 +4626,33 @@ type PaymentSettings {
4471
4626
  paymentProviders: [String!]!
4472
4627
  }
4473
4628
 
4629
+ """
4630
+ Non-blocking signal emitted alongside `userErrors` — backend state change context (e.g. an auto-clear, a server-side adjustment) that the storefront may surface as a retry hint or analytics event. Always pre-translated by backend per shop locale.
4631
+ """
4632
+ type PaymentWarning {
4633
+ """
4634
+ Machine-readable code — branch on this to dispatch UI retry hints. Add a new code only when there is a distinct UX response.
4635
+ """
4636
+ code: PaymentWarningCode!
4637
+
4638
+ """
4639
+ Pre-translated user-facing message describing what happened (Polish-first per shop locale).
4640
+ """
4641
+ message: String!
4642
+
4643
+ """
4644
+ Optional additional hint string for UI dispatch — currently unused for `INSTRUMENT_CLEARED_FOR_RETRY` (semantic encoded in `message`). Reserved for future warning codes (e.g. `SLOW_GATEWAY_RESPONSE` → "Retry in 30s").
4645
+ """
4646
+ retryHint: String
4647
+ }
4648
+
4649
+ """
4650
+ Machine-readable code for `PaymentWarning.code`. Non-blocking signals from payment mutations — branch on this to dispatch UI retry hints. `INSTRUMENT_CLEARED_FOR_RETRY` indicates backend auto-cleared a preselected payment instrument after gateway rejection; next `paymentCreate` will land on the gateway default page.
4651
+ """
4652
+ enum PaymentWarningCode {
4653
+ INSTRUMENT_CLEARED_FOR_RETRY
4654
+ }
4655
+
4474
4656
  """
4475
4657
  A pickup point (parcel locker or staffed collection point) attached to a delivery address. The buyer picks it in the carrier widget; it is persisted on the cart shipping address and carried through to the order.
4476
4658
  """
@@ -5161,6 +5343,20 @@ enum ProductVisibility {
5161
5343
  PUBLIC
5162
5344
  }
5163
5345
 
5346
+ """
5347
+ Canonical payment provider identifier (UPPERCASE per GraphQL convention). Use for `Order.paymentMethod`, `Payment.provider`, `PaymentMethod.providersAvailable`, `PaymentMethod.preferredProvider`. Add new providers in `@doswiftly/commerce-policy/provider-codes.ts` SSOT registry.
5348
+ """
5349
+ enum ProviderCode {
5350
+ BANK_TRANSFER
5351
+ CASH_ON_DELIVERY
5352
+ GIFT_CARD
5353
+ MANUAL_PAYMENT
5354
+ PAYU
5355
+ PRZELEWY24
5356
+ STRIPE
5357
+ TEST_GATEWAY
5358
+ }
5359
+
5164
5360
  type Query {
5165
5361
  """List of all currencies supported by the system (ECB rates)"""
5166
5362
  allSupportedCurrencies: [Currency!]!
@@ -5171,7 +5367,7 @@ type Query {
5171
5367
  attributeOptionsSearch(input: AttributeOptionsSearchInput!): AttributeFilterValueConnection!
5172
5368
 
5173
5369
  """
5174
- Payment methods active for the current shop, plus the merchant default. Shop-level independent of cart contents in this iteration. Fetch once for the checkout payment step.
5370
+ Payment methods active for the current shop, plus the merchant default. Returns one row per `PaymentMethodType` (BLIK, CARD, BANK_TRANSFER, ...) with `providersAvailable` (provider codes sorted by merchant priority) and `preferredProvider` (the head of the list). Shop-level — independent of cart contents.
5175
5371
  """
5176
5372
  availablePaymentMethods: AvailablePaymentMethods!
5177
5373