@01.software/sdk 0.43.0 → 0.45.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 +375 -2
- package/MIGRATION.md +89 -10
- package/README.md +451 -109
- package/dist/{server-nXOezi4b.d.cts → catalog-mappers-sIshKNb3.d.cts} +14 -3
- package/dist/{server-nXOezi4b.d.ts → catalog-mappers-sIshKNb3.d.ts} +14 -3
- package/dist/client.cjs +2680 -345
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +10 -7
- package/dist/client.d.ts +10 -7
- package/dist/client.js +2680 -345
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-U70KizLf.d.cts → collection-client-BSo7VXQE.d.cts} +8 -5
- package/dist/{collection-client-gWsA7BVP.d.ts → collection-client-Ckfusuee.d.ts} +8 -5
- package/dist/{const-_WWHU-gX.d.ts → const-BAG0MH0u.d.cts} +13 -10
- package/dist/{const-BBynifk0.d.cts → const-CaPq2AE4.d.ts} +13 -10
- package/dist/errors.cjs +74 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +38 -1
- package/dist/errors.d.ts +38 -1
- package/dist/errors.js +74 -1
- package/dist/errors.js.map +1 -1
- package/dist/{index-DPi5NWU8.d.ts → index-BNICJxBL.d.ts} +4 -4
- package/dist/{index-Dg4Y6Aq7.d.cts → index-YGrZrxNn.d.cts} +4 -4
- package/dist/index.cjs +2741 -368
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -19
- package/dist/index.d.ts +18 -19
- package/dist/index.js +2741 -368
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-DLxkB7Mv.d.cts → payload-types-CJZuXcgF.d.cts} +6873 -4208
- package/dist/{payload-types-DLxkB7Mv.d.ts → payload-types-CJZuXcgF.d.ts} +6873 -4208
- package/dist/query.cjs +550 -101
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +43 -45
- package/dist/query.d.ts +43 -45
- package/dist/query.js +550 -101
- package/dist/query.js.map +1 -1
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js.map +1 -1
- package/dist/server.cjs +2252 -444
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +11 -10
- package/dist/server.d.ts +11 -10
- package/dist/server.js +2252 -444
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +65 -1
- package/dist/storefront-cache.cjs.map +1 -1
- package/dist/storefront-cache.js +65 -1
- package/dist/storefront-cache.js.map +1 -1
- package/dist/types-BC6rVmJa.d.ts +47 -0
- package/dist/{types-B9lGhZlB.d.ts → types-BlOvjoTX.d.ts} +1095 -504
- package/dist/{types-Bpx5sYKo.d.cts → types-C4WuJfw7.d.cts} +1095 -504
- package/dist/types-D5p7-m1l.d.cts +47 -0
- package/dist/ui/canvas/server.cjs +244 -2
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.d.cts +27 -1
- package/dist/ui/canvas/server.d.ts +27 -1
- package/dist/ui/canvas/server.js +244 -2
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs +419 -103
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.d.cts +19 -7
- package/dist/ui/canvas.d.ts +19 -7
- package/dist/ui/canvas.js +419 -103
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.cjs +2 -1
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.ts +4 -4
- package/dist/webhook.js +2 -1
- package/dist/webhook.js.map +1 -1
- package/package.json +12 -11
- package/dist/types-B1ngKbWI.d.cts +0 -33
- package/dist/types-BiKNBwRA.d.ts +0 -33
package/README.md
CHANGED
|
@@ -63,6 +63,10 @@ entry.
|
|
|
63
63
|
| `@01.software/sdk` | browser-safe `createClient`, commerce helpers, collection helpers, types | none |
|
|
64
64
|
| `@01.software/sdk/client` | browser-safe `createClient` entry | none |
|
|
65
65
|
| `@01.software/sdk/server` | `createServerClient`, server-only collection, commerce, and preview APIs | none; keep `secretKey` code on the server |
|
|
66
|
+
| `@01.software/sdk/errors` | SDK error classes and guards | none |
|
|
67
|
+
| `@01.software/sdk/analytics` | browser analytics client and typed event helpers | none |
|
|
68
|
+
| `@01.software/sdk/metadata` | SEO metadata extraction and generation helpers | none |
|
|
69
|
+
| `@01.software/sdk/webhook` | webhook handlers, event guards, and webhook types | none |
|
|
66
70
|
| `@01.software/sdk/query` | React Query hooks, cache helpers, `getQueryClient` | `@tanstack/react-query`, `react`, `react-dom` |
|
|
67
71
|
| `@01.software/sdk/realtime` | `RealtimeConnection`, `useRealtimeQuery` | `@tanstack/react-query`, `react`, `react-dom` |
|
|
68
72
|
| `@01.software/sdk/storefront-cache` | product storefront cache resource name helpers | none |
|
|
@@ -75,9 +79,13 @@ entry.
|
|
|
75
79
|
| `@01.software/sdk/ui/video` | `VideoPlayer` | `react`, `react-dom`, `@mux/mux-player-react` |
|
|
76
80
|
| `@01.software/sdk/ui/image` | `Image` | `react`, `react-dom` |
|
|
77
81
|
|
|
82
|
+
`CanvasRenderer` renders reserved `shape` nodes (`terminator`, `process`,
|
|
83
|
+
`decision`, `io`, `subprocess`) with first-party SVG geometry, without a tenant
|
|
84
|
+
catalog row or QuickJS template.
|
|
85
|
+
|
|
78
86
|
If a feature is not listed here, it does not need a separate peer install.
|
|
79
87
|
For the full component-to-peer mapping, see
|
|
80
|
-
`packages/sdk/.claude/rules/components-reference.md`.
|
|
88
|
+
`packages/published/sdk/.claude/rules/components-reference.md`.
|
|
81
89
|
|
|
82
90
|
Migration quick reference:
|
|
83
91
|
|
|
@@ -211,23 +219,20 @@ const server = createServerClient({
|
|
|
211
219
|
})
|
|
212
220
|
const serverQuery = createServerQueryHooks(server)
|
|
213
221
|
|
|
214
|
-
//
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
totalAmount: 10000,
|
|
229
|
-
pgPaymentId: 'provider-payment-id', // optional (omit for free orders)
|
|
230
|
-
discountCode: 'WELCOME10', // optional
|
|
222
|
+
// Start the canonical Cart -> Checkout -> PaymentSession flow.
|
|
223
|
+
const { cart, cartToken } = await server.commerce.carts.create({ market: 'kr' })
|
|
224
|
+
await server.commerce.carts.addItem({
|
|
225
|
+
cartToken,
|
|
226
|
+
productId,
|
|
227
|
+
variantId,
|
|
228
|
+
quantity: 1,
|
|
229
|
+
expectedRevision: cart.revision,
|
|
230
|
+
idempotencyKey: crypto.randomUUID(),
|
|
231
|
+
})
|
|
232
|
+
const checkout = await server.commerce.checkouts.create({
|
|
233
|
+
cartToken,
|
|
234
|
+
customer: { email: 'user@example.com' },
|
|
235
|
+
idempotencyKey: crypto.randomUUID(),
|
|
231
236
|
})
|
|
232
237
|
|
|
233
238
|
// SSR prefetch (server)
|
|
@@ -237,13 +242,31 @@ await serverQuery.prefetchQuery({
|
|
|
237
242
|
})
|
|
238
243
|
```
|
|
239
244
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
245
|
+
Provider-paid and manual Order creation are not part of the 0.45 SDK surface.
|
|
246
|
+
An Order is emitted only when the Checkout is placed. Canonical Order reads
|
|
247
|
+
expose line snapshots directly at `order.items`.
|
|
243
248
|
|
|
244
249
|
Always import `createServerClient` from `@01.software/sdk/server` so generated
|
|
245
250
|
code and bundlers do not blur the Secret Key boundary.
|
|
246
251
|
|
|
252
|
+
### Tenant context introspection
|
|
253
|
+
|
|
254
|
+
Trusted server workflows can inspect the resolved tenant capability context
|
|
255
|
+
without calling the Console endpoint directly:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const context = await server.tenant.context()
|
|
259
|
+
|
|
260
|
+
console.log(context.features)
|
|
261
|
+
console.log(context.collections.active)
|
|
262
|
+
console.log(context.fieldConfigs)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Pass `{ includeCounts: true }` only when document counts and webhook
|
|
266
|
+
configuration are needed. That mode bypasses the endpoint cache and performs
|
|
267
|
+
additional collection reads. Tenant introspection is server-only and is not
|
|
268
|
+
exported from the browser-safe root entry.
|
|
269
|
+
|
|
247
270
|
Server-rendered preview routes can use `server.preview.detail()` with the
|
|
248
271
|
short-lived preview token issued by Console:
|
|
249
272
|
|
|
@@ -337,34 +360,34 @@ const { product } = result
|
|
|
337
360
|
// featuredImage, priceRange, compareAtPriceRange, availableForSale, selectedOrFirstAvailableVariant }
|
|
338
361
|
```
|
|
339
362
|
|
|
340
|
-
`detail()` returns `
|
|
341
|
-
|
|
342
|
-
`reason` value is one of
|
|
343
|
-
`feature_disabled`, so storefronts can choose
|
|
344
|
-
CTA, or feature gating UI. Permission/auth
|
|
345
|
-
mismatches, still throw
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
Public product visibility
|
|
350
|
-
`published`
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
`{ found: false, reason: 'not_published' }`
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
363
|
+
Browser `detail()` returns `ProductDetailCatalogResult`, the same public-safe
|
|
364
|
+
catalog detail shape as `detailCatalog()`: `{ found: true, product:
|
|
365
|
+
ProductDetailCatalog } | { found: false, reason }`. The `reason` value is one of
|
|
366
|
+
`not_found`, `not_published`, or `feature_disabled`, so storefronts can choose
|
|
367
|
+
between a standard 404, preview CTA, or feature gating UI. Permission/auth
|
|
368
|
+
errors, including 403 tenant mismatches, still throw typed `SDKError` subclasses
|
|
369
|
+
and preserve request IDs through the existing `lastRequestId` / `onRequestId`
|
|
370
|
+
path.
|
|
371
|
+
|
|
372
|
+
Public product visibility is driven by product `status` and `publishedAt`.
|
|
373
|
+
Listing helpers return products with `status: 'published'` when `publishedAt`
|
|
374
|
+
is current or empty. Direct detail and checkout admission also allow
|
|
375
|
+
`status: 'unlisted'`, so an unlisted product can be opened by slug/id without
|
|
376
|
+
appearing in PLP/search/curated listing helpers. Draft, archived, scheduled, or
|
|
377
|
+
malformed lifecycle states return `{ found: false, reason: 'not_published' }`
|
|
378
|
+
from detail and are omitted from listings.
|
|
379
|
+
|
|
380
|
+
The successful browser product payload omits operational inventory fields such as
|
|
381
|
+
`product.totalInventory`, variant `stock`, and variant `reservedStock`. Use
|
|
382
|
+
`stockSnapshot()` / `stockCheck()` for live storefront inventory overlays, or use
|
|
383
|
+
`createServerClient().commerce.product.detail()` from trusted server code when a
|
|
384
|
+
full operational detail read is required.
|
|
362
385
|
|
|
363
386
|
### Edge-cached catalog + live stock (storefront migration)
|
|
364
387
|
|
|
365
388
|
When a PDP or listing UI is served behind the Console Edge CDN, prefer the
|
|
366
|
-
catalog/stock split instead of
|
|
367
|
-
|
|
389
|
+
catalog/stock split instead of treating cached catalog responses as live
|
|
390
|
+
inventory authorities.
|
|
368
391
|
|
|
369
392
|
```typescript
|
|
370
393
|
import { createClient, mergeProductDetailWithStock } from '@01.software/sdk'
|
|
@@ -388,8 +411,8 @@ const { product, stockMergeStatus } = mergeProductDetailWithStock(
|
|
|
388
411
|
Listing UIs use the same pattern: `listingPage()` for PLP/search grids, or
|
|
389
412
|
`listingGroupsCatalog({ productIds })` when curated product IDs are already
|
|
390
413
|
known, then `stockSnapshot()` (or `stock-check` at cart/checkout) for live
|
|
391
|
-
availability. `detail()`
|
|
392
|
-
|
|
414
|
+
availability. Browser `detail()` is now catalog-safe as well; see ADR 0012
|
|
415
|
+
addendum in `docs/decisions/0012-sdk-public-commerce-contract.md`.
|
|
393
416
|
|
|
394
417
|
### Product Listing Pages (PLP) — join-safe queries
|
|
395
418
|
|
|
@@ -457,13 +480,6 @@ const { docs } = await server.collections.from('products').find({
|
|
|
457
480
|
{ publishedAt: { equals: null } },
|
|
458
481
|
],
|
|
459
482
|
},
|
|
460
|
-
{
|
|
461
|
-
or: [
|
|
462
|
-
{ storefrontVisibility: { equals: 'listed' } },
|
|
463
|
-
{ storefrontVisibility: { exists: false } },
|
|
464
|
-
{ storefrontVisibility: { equals: null } },
|
|
465
|
-
],
|
|
466
|
-
},
|
|
467
483
|
],
|
|
468
484
|
},
|
|
469
485
|
limit: 24,
|
|
@@ -684,6 +700,57 @@ selection param. Each swatch carries a hint-only slug-compat href such as
|
|
|
684
700
|
`preferCompleteVariantFromHint: true` on `buildProductListingCard()` only when
|
|
685
701
|
the card should deep-link a complete hint variant.
|
|
686
702
|
|
|
703
|
+
## Memberships
|
|
704
|
+
|
|
705
|
+
Membership self-service is split by credential boundary:
|
|
706
|
+
|
|
707
|
+
- Browser/customer JWT clients can read the signed-in customer's current or
|
|
708
|
+
latest membership and request cancellation for active, trialing, or past-due
|
|
709
|
+
memberships.
|
|
710
|
+
- Server clients with a Secret Key can subscribe a customer after the storefront
|
|
711
|
+
has completed the delegated first charge.
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
import { createClient } from '@01.software/sdk'
|
|
715
|
+
import { createServerClient } from '@01.software/sdk/server'
|
|
716
|
+
|
|
717
|
+
const client = createClient({ publishableKey: 'pk_live_...' })
|
|
718
|
+
|
|
719
|
+
const membership = await client.memberships.me()
|
|
720
|
+
if (
|
|
721
|
+
membership &&
|
|
722
|
+
['active', 'trialing', 'past_due'].includes(membership.status)
|
|
723
|
+
) {
|
|
724
|
+
await client.memberships.cancel({ id: membership.id })
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const server = createServerClient({
|
|
728
|
+
publishableKey: 'pk_live_...',
|
|
729
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
await server.memberships.subscribe({
|
|
733
|
+
planId: 'plan_123',
|
|
734
|
+
customerId: 'customer_123',
|
|
735
|
+
customerRef: 'cus_provider_123',
|
|
736
|
+
pgPaymentId: 'pay_123',
|
|
737
|
+
amount: 9900,
|
|
738
|
+
currency: 'KRW',
|
|
739
|
+
subscribeIntentKey: 'checkout-session-123',
|
|
740
|
+
card: { last4: '4242', brand: 'visa' },
|
|
741
|
+
})
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
`subscribeIntentKey` is sent as `X-Idempotency-Key`; it is not part of the JSON
|
|
745
|
+
body. Replays must use the same plan, customer reference, amount, currency, and
|
|
746
|
+
paid-start provider payment id snapshot as the original incomplete membership.
|
|
747
|
+
Trial subscriptions may omit `pgPaymentId` because no first-period charge has
|
|
748
|
+
been captured yet; paid starts must include the provider payment id that funded
|
|
749
|
+
the first period.
|
|
750
|
+
`memberships.me()` and browser `memberships.cancel()` require the customer JWT
|
|
751
|
+
configured on the client. Refund webhook `refundedAmount` values are cumulative
|
|
752
|
+
charge totals, not per-event deltas.
|
|
753
|
+
|
|
687
754
|
## Storefront performance defaults
|
|
688
755
|
|
|
689
756
|
- **PLP:** prefer `commerce.product.listingPage()` or `useProductListingPage()` (GET `/api/products/listing-groups/query/catalog`, CDN-cacheable, card-ready). Use `listingGroupsCatalog({ productIds })` only when IDs are already known. Treat the full listing-groups response shape as a server-auth escape hatch because it can include operational stock fields. Avoid fetching a product list and then calling `detail()` per card.
|
|
@@ -775,7 +842,7 @@ Dotted-path filters (`where: { 'product.slug': { equals } }`) are Payload-native
|
|
|
775
842
|
|
|
776
843
|
Checklist when `find()` returns `docs: []` unexpectedly, in order of likelihood:
|
|
777
844
|
|
|
778
|
-
1. **Access control filtered the document.** Many collections enforce public read filters.
|
|
845
|
+
1. **Access control filtered the document.** Many collections enforce public read filters. Product listing reads require `status: 'published'` plus a current or unset `publishedAt`. Draft, future, `unlisted`, archived, or malformed products silently disappear from raw listing results even when their slug or ID matches. Use `commerce.product.detail({ slug | id })` for `unlisted` products. Correlate with backend logs via `client.lastRequestId` (or catch `SDKError.requestId`).
|
|
779
846
|
2. **Build-time publishable key / API URL differs from runtime.** SSG `generateStaticParams` / `generateMetadata` / the page render must all see the same tenant context. A wrong or missing key at build time produces a baked-in empty response.
|
|
780
847
|
3. **Next.js SSG fetch cache served a stale empty response.** Use `cache: 'no-store'` or `export const revalidate = 0` on server components that should reflect live data.
|
|
781
848
|
4. **`where: { slug: 'x' }` string shorthand.** Always use `{ slug: { equals: 'x' } }` — bare strings silently match nothing.
|
|
@@ -875,6 +942,24 @@ const product = await server.collections.from('products').findById(id, {
|
|
|
875
942
|
const product = await client.collections.from('products').findById('id')
|
|
876
943
|
```
|
|
877
944
|
|
|
945
|
+
Localized raw collection reads can request a supported Payload locale. The SDK
|
|
946
|
+
currently exposes `en` and `ko`; it does not expose Payload's `locale=all`
|
|
947
|
+
shape. `fallbackLocale` serializes to Payload's REST `fallback-locale` query
|
|
948
|
+
key, and `fallbackLocale: false` serializes as `fallback-locale=none`.
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
const { docs } = await client.collections.from('articles').find({
|
|
952
|
+
locale: 'ko',
|
|
953
|
+
fallbackLocale: 'en',
|
|
954
|
+
limit: 10,
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
const article = await server.collections.from('articles').findById(id, {
|
|
958
|
+
locale: 'ko',
|
|
959
|
+
fallbackLocale: false,
|
|
960
|
+
})
|
|
961
|
+
```
|
|
962
|
+
|
|
878
963
|
Raw collection mutations are an escape hatch. For ecommerce product catalog
|
|
879
964
|
writes, prefer `server.commerce.product.upsert()` so options, option-values,
|
|
880
965
|
and variants are written through the domain transaction.
|
|
@@ -1052,13 +1137,58 @@ await server.collections.from('articles').update('article_id', {
|
|
|
1052
1137
|
|
|
1053
1138
|
### Customer Auth
|
|
1054
1139
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1140
|
+
New storefronts should use hosted customer OAuth through
|
|
1141
|
+
`client.customer.oauth.*`. The SDK generates PKCE authorization URLs, parses the
|
|
1142
|
+
callback, exchanges codes, rotates refresh tokens, and calls hosted logout, but
|
|
1143
|
+
it does not store tokens for you. Store OAuth access, refresh, and transient
|
|
1144
|
+
state in trusted route handlers, preferably HttpOnly cookies or a server-side
|
|
1145
|
+
session store. Do not put OAuth tokens in browser-readable storage.
|
|
1146
|
+
|
|
1147
|
+
```typescript
|
|
1148
|
+
const client = createClient({
|
|
1149
|
+
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
|
|
1150
|
+
customer: {
|
|
1151
|
+
persist: false,
|
|
1152
|
+
oauth: {
|
|
1153
|
+
issuer: process.env.NEXT_PUBLIC_SOFTWARE_CUSTOMER_AUTH_ISSUER!,
|
|
1154
|
+
clientId: process.env.NEXT_PUBLIC_SOFTWARE_CUSTOMER_CLIENT_ID!,
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
})
|
|
1060
1158
|
|
|
1061
|
-
|
|
1159
|
+
const customerOAuth = client.customer.oauth
|
|
1160
|
+
if (!customerOAuth) throw new Error('Customer OAuth is not configured')
|
|
1161
|
+
|
|
1162
|
+
const authorization = await customerOAuth.createAuthorizationUrl({
|
|
1163
|
+
redirectUri: `${origin}/api/auth/customer/callback`,
|
|
1164
|
+
returnTo: '/account',
|
|
1165
|
+
})
|
|
1166
|
+
|
|
1167
|
+
const callback = customerOAuth.parseCallback(callbackUrl, {
|
|
1168
|
+
expectedState: authorization.state,
|
|
1169
|
+
})
|
|
1170
|
+
|
|
1171
|
+
const tokens = await customerOAuth.exchangeCode({
|
|
1172
|
+
code: callback.code,
|
|
1173
|
+
codeVerifier: authorization.codeVerifier,
|
|
1174
|
+
redirectUri: `${origin}/api/auth/customer/callback`,
|
|
1175
|
+
})
|
|
1176
|
+
|
|
1177
|
+
await customerOAuth.refresh({ refreshToken: tokens.refreshToken })
|
|
1178
|
+
await customerOAuth.logout({ refreshToken: tokens.refreshToken })
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
`exchangeCode()` may return an `idToken` when the `openid` scope is granted. In
|
|
1182
|
+
this SDK release, treat `idToken` as opaque unless your server verifies issuer,
|
|
1183
|
+
audience, expiry, nonce, and hosted JWKS signature itself. Use `me()` after
|
|
1184
|
+
establishing an access token for trusted customer profile display.
|
|
1185
|
+
|
|
1186
|
+
#### Legacy direct local auth
|
|
1187
|
+
|
|
1188
|
+
`client.customer.auth.*` remains available for existing local email/password
|
|
1189
|
+
flows: register, login, refresh, password reset, profile read/update, and
|
|
1190
|
+
password change. Keep this path as explicit legacy compatibility for existing
|
|
1191
|
+
integrations; do not overload `login()` to sometimes redirect into hosted auth.
|
|
1062
1192
|
|
|
1063
1193
|
```typescript
|
|
1064
1194
|
const client = createClient({
|
|
@@ -1086,7 +1216,7 @@ client.customer.auth.logout()
|
|
|
1086
1216
|
const orders = await client.commerce.orders.listMine({
|
|
1087
1217
|
page: 1,
|
|
1088
1218
|
limit: 10,
|
|
1089
|
-
|
|
1219
|
+
financialStatus: 'paid',
|
|
1090
1220
|
})
|
|
1091
1221
|
|
|
1092
1222
|
// Password
|
|
@@ -1095,15 +1225,191 @@ await client.customer.auth.resetPassword(token, newPassword)
|
|
|
1095
1225
|
await client.customer.auth.changePassword(currentPassword, newPassword)
|
|
1096
1226
|
```
|
|
1097
1227
|
|
|
1098
|
-
|
|
1228
|
+
`forgotPassword()` keeps the same SDK signature in both delivery modes. A
|
|
1229
|
+
tenant administrator chooses either backward-compatible signed webhook
|
|
1230
|
+
delivery or platform-managed email in the Console customer-authentication
|
|
1231
|
+
settings. Platform email requires the storefront reset URL to be configured;
|
|
1232
|
+
the platform appends the `token` query parameter and sends through the verified
|
|
1233
|
+
tenant sender or the platform fallback. Do not add reset tokens or dynamic
|
|
1234
|
+
redirect URLs to the SDK call.
|
|
1235
|
+
|
|
1236
|
+
### Market discovery
|
|
1237
|
+
|
|
1238
|
+
Discover a tenant's active buyer markets and resolve the default market. These
|
|
1239
|
+
reads are safe for a publishable key — server-only FX/pricing fields are never
|
|
1240
|
+
returned.
|
|
1241
|
+
|
|
1242
|
+
```ts
|
|
1243
|
+
const markets = await client.markets.list() // MarketDTO[] (active markets)
|
|
1244
|
+
const primary = await client.markets.default() // MarketDTO | null (active primary)
|
|
1245
|
+
|
|
1246
|
+
// Pass the resolved handle to market-aware commerce calls:
|
|
1247
|
+
const cart = await client.commerce.carts.create({ market: primary?.handle })
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
`MarketDTO` is `{ id, handle, name, countryCode, targetCountries, currency,
|
|
1251
|
+
isPrimary, isActive }` — `countryCode`/`targetCountries` are ISO 3166-1 alpha-2,
|
|
1252
|
+
`currency` is ISO 4217.
|
|
1253
|
+
|
|
1254
|
+
> `client.markets.default()` resolves the tenant's primary market from server
|
|
1255
|
+
> data. It is distinct from the client's _configured_ default market handle
|
|
1256
|
+
> (`CommerceClient.getDefaultMarket()`), which is the handle you passed to
|
|
1257
|
+
> `createClient({ market })`.
|
|
1258
|
+
|
|
1259
|
+
### Commerce Market Context
|
|
1260
|
+
|
|
1261
|
+
Pass `market` when constructing a client to default market-aware product and cart
|
|
1262
|
+
helpers to that market. Per-call `market` still wins when supplied.
|
|
1263
|
+
|
|
1264
|
+
```typescript
|
|
1265
|
+
const client = createClient({
|
|
1266
|
+
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
|
|
1267
|
+
market: 'kr',
|
|
1268
|
+
})
|
|
1269
|
+
|
|
1270
|
+
await client.commerce.product.detail({ slug: 'shirt' }) // uses kr
|
|
1271
|
+
await client.commerce.carts.create() // creates a kr cart
|
|
1272
|
+
await client.commerce.product.detail({ slug: 'shirt', market: 'us' }) // override
|
|
1273
|
+
|
|
1274
|
+
const usClient = client.withMarket('us')
|
|
1275
|
+
const { cart } = await usClient.commerce.carts.retrieve({ cartToken })
|
|
1276
|
+
await usClient.commerce.carts.addItem({
|
|
1277
|
+
cartToken,
|
|
1278
|
+
productId,
|
|
1279
|
+
variantId,
|
|
1280
|
+
quantity,
|
|
1281
|
+
expectedRevision: cart.revision,
|
|
1282
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1283
|
+
})
|
|
1284
|
+
```
|
|
1099
1285
|
|
|
1100
|
-
|
|
1286
|
+
`createServerClient({ market })` and `server.withMarket(market)` provide the
|
|
1287
|
+
same request-scoped default for trusted server code.
|
|
1288
|
+
|
|
1289
|
+
### Commerce Checkout and PaymentSession
|
|
1290
|
+
|
|
1291
|
+
The canonical payment lifecycle is Cart → Checkout → PaymentSession → Order.
|
|
1292
|
+
Checkout and PaymentSession results are required discriminated unions: narrow
|
|
1293
|
+
their state fields before reading state-specific data such as `nextAction` or
|
|
1294
|
+
`order`. Browser calls use publishable-key capability tokens and the matching
|
|
1295
|
+
customer JWT when a resource is customer-bound. Server calls use the secret
|
|
1296
|
+
credential and never expose provider secrets in returned DTOs.
|
|
1297
|
+
|
|
1298
|
+
Browser and server commerce writes use separate transports even when their JSON
|
|
1299
|
+
shapes match. `createServerClient()` routes every Cart operation, Checkout
|
|
1300
|
+
create/retrieve/query, and PaymentSession create/retrieve/cancel/confirm through
|
|
1301
|
+
the resource-owned `/server/*` namespace. Public paths retain only their exact
|
|
1302
|
+
capability/customer bodies and never advertise the server credential.
|
|
1303
|
+
|
|
1304
|
+
Generate one idempotency key per logical mutation, persist it with the pending
|
|
1305
|
+
operation, and reuse it after timeouts or lost responses. Creating a fresh key
|
|
1306
|
+
for each retry starts a different operation. Do not derive ordinary keys from a
|
|
1307
|
+
Cart, Checkout, PaymentSession, Order, or provider id: reuse of that resource for
|
|
1308
|
+
a later operation would collide. Use `createCommerceIdempotencyKey()` and retain
|
|
1309
|
+
the generated value with the pending operation.
|
|
1310
|
+
|
|
1311
|
+
Cart add/update/remove/clear/discount/attribute mutations additionally require
|
|
1312
|
+
the last observed `cart.revision` as `expectedRevision`. A
|
|
1313
|
+
`cart_revision_conflict` means refetch the Cart and start a new logical mutation
|
|
1314
|
+
with a new idempotency key. Cart merge requires the key but not a single revision
|
|
1315
|
+
because it atomically resolves two current Cart states.
|
|
1316
|
+
Treat a replayed Cart result as an operation receipt: never replace a newer
|
|
1317
|
+
cached Cart with a lower `revision`; retrieve when response ordering is unclear.
|
|
1318
|
+
|
|
1319
|
+
```typescript
|
|
1320
|
+
import {
|
|
1321
|
+
createCommerceIdempotencyKey,
|
|
1322
|
+
isCommerceSDKError,
|
|
1323
|
+
} from '@01.software/sdk'
|
|
1324
|
+
|
|
1325
|
+
const checkoutIdempotencyKey = createCommerceIdempotencyKey()
|
|
1326
|
+
const { checkout, checkoutToken } = await client.commerce.checkouts.create({
|
|
1327
|
+
cartToken,
|
|
1328
|
+
customer: { email: 'buyer@example.com', name: 'Buyer' },
|
|
1329
|
+
shippingAddress,
|
|
1330
|
+
idempotencyKey: checkoutIdempotencyKey,
|
|
1331
|
+
})
|
|
1332
|
+
|
|
1333
|
+
const paymentIdempotencyKey = createCommerceIdempotencyKey()
|
|
1334
|
+
const paymentSession = await client.commerce.paymentSessions.create({
|
|
1335
|
+
checkoutToken,
|
|
1336
|
+
provider: 'toss',
|
|
1337
|
+
providerOptions: { profile: 'widget_v2' },
|
|
1338
|
+
idempotencyKey: paymentIdempotencyKey,
|
|
1339
|
+
})
|
|
1340
|
+
|
|
1341
|
+
if (
|
|
1342
|
+
paymentSession.presentationStatus === 'actionable' &&
|
|
1343
|
+
paymentSession.nextAction.type === 'toss_widget'
|
|
1344
|
+
) {
|
|
1345
|
+
// Initialize the Toss widget with the returned public client/variant fields.
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Browser recovery requires the parent capability.
|
|
1349
|
+
await client.commerce.paymentSessions.retrieve({
|
|
1350
|
+
paymentSessionId: paymentSession.id,
|
|
1351
|
+
checkoutToken,
|
|
1352
|
+
})
|
|
1353
|
+
|
|
1354
|
+
// Toss confirmation is a typed server-only adapter action. There is no generic
|
|
1355
|
+
// `confirmVerified` or provider-paid Order creation escape hatch.
|
|
1356
|
+
const confirmationIdempotencyKey = createCommerceIdempotencyKey()
|
|
1357
|
+
let confirmed
|
|
1358
|
+
try {
|
|
1359
|
+
confirmed = await server.commerce.paymentSessions.confirm({
|
|
1360
|
+
paymentSessionId: paymentSession.id,
|
|
1361
|
+
idempotencyKey: confirmationIdempotencyKey,
|
|
1362
|
+
action: {
|
|
1363
|
+
type: 'toss_authorization',
|
|
1364
|
+
paymentKey,
|
|
1365
|
+
providerOrderId,
|
|
1366
|
+
},
|
|
1367
|
+
})
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if (isCommerceSDKError(error) && error.retryable) {
|
|
1370
|
+
// Retry the same logical operation with confirmationIdempotencyKey.
|
|
1371
|
+
}
|
|
1372
|
+
throw error
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (confirmed.finalizationStatus === 'committed') {
|
|
1376
|
+
console.log(confirmed.order.orderNumber)
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const checkoutPage = await server.commerce.checkouts.query({
|
|
1380
|
+
status: ['open'],
|
|
1381
|
+
limit: 20,
|
|
1382
|
+
})
|
|
1383
|
+
void checkoutPage.nextCursor
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
`checkoutToken` is returned only alongside Checkout creation; ordinary Checkout
|
|
1387
|
+
DTOs never echo the bearer capability. Browser `checkouts.retrieve` and
|
|
1388
|
+
PaymentSession retrieve/cancel calls accept that token. Server
|
|
1389
|
+
`checkouts.retrieve({ checkoutId })` uses tenant-scoped authority, and
|
|
1390
|
+
`checkouts.query` returns lightweight rows without capabilities.
|
|
1391
|
+
|
|
1392
|
+
Canonical commerce error envelopes become `CommerceSDKError`. Branch on its
|
|
1393
|
+
stable `reason`, inspect `retryable`, and log `requestId`; a newer server reason
|
|
1394
|
+
is preserved as `reason: 'unknown_commerce_error'` plus `rawReason`.
|
|
1395
|
+
|
|
1396
|
+
### Commerce Orders (post-placement operations)
|
|
1397
|
+
|
|
1398
|
+
Provider-paid `orders.create`, `orders.confirmPayment`, and ordinary
|
|
1399
|
+
`orders.updateTransaction` were removed in SDK 0.45. No general manual Order API
|
|
1400
|
+
is exposed. After a PaymentSession commits an Order, trusted server code can use
|
|
1401
|
+
the remaining fulfillment, cancellation, and return operations. Customer order
|
|
1402
|
+
reads remain available through `client.commerce.orders.listMine()`.
|
|
1403
|
+
|
|
1404
|
+
The SDK root no longer exports raw generated `Order`, `Transaction`, `Cart`, or
|
|
1405
|
+
`CartItem` Payload documents. Use the canonical `PublicOrder` / `PublicCart`
|
|
1406
|
+
DTOs for storefront code and explicit server collection types only inside
|
|
1407
|
+
trusted operational code. Order cancellation no longer returns the deprecated
|
|
1408
|
+
inline provider-refund branch: a paid cancellation returns `refundPending`, and
|
|
1409
|
+
the dedicated refund-resolution flow reports provider completion.
|
|
1101
1410
|
|
|
1102
1411
|
```typescript
|
|
1103
|
-
// Orders
|
|
1104
|
-
await server.commerce.orders.create(params)
|
|
1105
1412
|
await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
|
|
1106
|
-
await server.commerce.orders.checkout({ cartId, pgPaymentId?, orderNumber, customerSnapshot, discountCode? })
|
|
1107
1413
|
|
|
1108
1414
|
// Single-order lookup (getOrder endpoint removed — use collection find)
|
|
1109
1415
|
const { docs: [order] } = await server.collections.from('orders').find({
|
|
@@ -1118,23 +1424,6 @@ await server.commerce.orders.createFulfillment({ orderNumber, items })
|
|
|
1118
1424
|
await server.commerce.orders.updateFulfillment({ fulfillmentId, carrier, trackingNumber })
|
|
1119
1425
|
await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier, trackingNumber }] })
|
|
1120
1426
|
|
|
1121
|
-
// Provider-verified payment confirmation
|
|
1122
|
-
// Provider webhook handlers should verify with the provider first, then call:
|
|
1123
|
-
await server.commerce.orders.confirmPayment({
|
|
1124
|
-
orderNumber,
|
|
1125
|
-
pgProvider: 'provider-name',
|
|
1126
|
-
pgPaymentId,
|
|
1127
|
-
amount,
|
|
1128
|
-
providerStatus: 'PAID',
|
|
1129
|
-
providerEventId,
|
|
1130
|
-
})
|
|
1131
|
-
|
|
1132
|
-
// Low-level transaction annotation / compatibility path. Prefer confirmPayment()
|
|
1133
|
-
// for normal provider-verified paid transitions.
|
|
1134
|
-
// status: 'failed' records a retryable PG failure on the Transaction only; the
|
|
1135
|
-
// Order stays pending until verified payment succeeds or the merchant cancels it.
|
|
1136
|
-
await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
|
|
1137
|
-
|
|
1138
1427
|
// Returns
|
|
1139
1428
|
await server.commerce.orders.createReturn({
|
|
1140
1429
|
orderNumber,
|
|
@@ -1163,8 +1452,14 @@ Available on both Client (customer JWT) and ServerClient (secretKey).
|
|
|
1163
1452
|
|
|
1164
1453
|
```typescript
|
|
1165
1454
|
await client.commerce.discounts.validate({ code, orderAmount })
|
|
1166
|
-
await client.commerce.shipping.calculate({
|
|
1167
|
-
|
|
1455
|
+
await client.commerce.shipping.calculate({
|
|
1456
|
+
shippingPolicyId?,
|
|
1457
|
+
orderAmount,
|
|
1458
|
+
postalCode?,
|
|
1459
|
+
countryCode?,
|
|
1460
|
+
regionCode?,
|
|
1461
|
+
})
|
|
1462
|
+
// calculateShipping returns: { shippingAmount, shippingLines, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }
|
|
1168
1463
|
```
|
|
1169
1464
|
|
|
1170
1465
|
### Commerce Product
|
|
@@ -1282,31 +1577,77 @@ for (const item of results) {
|
|
|
1282
1577
|
|
|
1283
1578
|
### Commerce Cart
|
|
1284
1579
|
|
|
1285
|
-
Available on both Client and ServerClient via `commerce.
|
|
1580
|
+
Available on both Client and ServerClient via `commerce.carts.*`. These helpers
|
|
1581
|
+
return required, browser-safe DTOs; use
|
|
1582
|
+
`server.collections.from('carts' | 'cart-items')` for raw operational documents.
|
|
1286
1583
|
|
|
1287
1584
|
```typescript
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1585
|
+
const created = await client.commerce.carts.create({
|
|
1586
|
+
market: 'kr',
|
|
1587
|
+
attributes: [{ key: 'channel', value: 'web' }],
|
|
1588
|
+
})
|
|
1589
|
+
const { cartToken, warnings } = created
|
|
1590
|
+
let cart = created.cart
|
|
1591
|
+
|
|
1592
|
+
cart = (
|
|
1593
|
+
await client.commerce.carts.addItem({
|
|
1594
|
+
cartToken,
|
|
1595
|
+
productId,
|
|
1596
|
+
variantId,
|
|
1597
|
+
quantity,
|
|
1598
|
+
admissions: [{ eventId, occurrenceId, admissionTypeId }],
|
|
1599
|
+
expectedRevision: cart.revision,
|
|
1600
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1601
|
+
})
|
|
1602
|
+
).cart
|
|
1603
|
+
|
|
1604
|
+
cart = (
|
|
1605
|
+
await client.commerce.carts.updateItem({
|
|
1606
|
+
cartToken,
|
|
1607
|
+
cartItemId,
|
|
1608
|
+
quantity,
|
|
1609
|
+
expectedRevision: cart.revision,
|
|
1610
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1611
|
+
})
|
|
1612
|
+
).cart
|
|
1613
|
+
cart = (
|
|
1614
|
+
await client.commerce.carts.removeItem({
|
|
1615
|
+
cartToken,
|
|
1616
|
+
cartItemId,
|
|
1617
|
+
expectedRevision: cart.revision,
|
|
1618
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1619
|
+
})
|
|
1620
|
+
).cart
|
|
1621
|
+
await client.commerce.carts.retrieve({ cartToken })
|
|
1622
|
+
cart = (
|
|
1623
|
+
await client.commerce.carts.updateDiscountCodes({
|
|
1624
|
+
cartToken,
|
|
1625
|
+
discountCodes: ['WELCOME10', 'FREESHIP'],
|
|
1626
|
+
expectedRevision: cart.revision,
|
|
1627
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1628
|
+
})
|
|
1629
|
+
).cart
|
|
1630
|
+
cart = (
|
|
1631
|
+
await client.commerce.carts.updateAttributes({
|
|
1632
|
+
cartToken,
|
|
1633
|
+
attributes: [], // replace/clear
|
|
1634
|
+
expectedRevision: cart.revision,
|
|
1635
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1636
|
+
})
|
|
1637
|
+
).cart
|
|
1638
|
+
await client.commerce.carts.clear({
|
|
1639
|
+
cartToken,
|
|
1640
|
+
expectedRevision: cart.revision,
|
|
1641
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1295
1642
|
})
|
|
1296
|
-
|
|
1297
|
-
// Update item quantity
|
|
1298
|
-
await client.commerce.cart.updateItem({ cartItemId, quantity })
|
|
1299
|
-
|
|
1300
|
-
// Remove item from cart
|
|
1301
|
-
await client.commerce.cart.removeItem({ cartItemId })
|
|
1302
|
-
|
|
1303
|
-
// Other cart operations return sanitized customer-facing cart DTOs.
|
|
1304
|
-
await client.commerce.cart.get(cartId)
|
|
1305
|
-
await client.commerce.cart.applyDiscount({ cartId, discountCode })
|
|
1306
|
-
await client.commerce.cart.removeDiscount({ cartId })
|
|
1307
|
-
await client.commerce.cart.clear({ cartId })
|
|
1308
1643
|
```
|
|
1309
1644
|
|
|
1645
|
+
`updateDiscountCodes({ cartToken, discountCodes })` is the canonical full-list
|
|
1646
|
+
replacement API. Pass an empty list to clear codes. Singular `admission`,
|
|
1647
|
+
`applyDiscount()`, and `removeDiscount()` are not part of the 0.45 surface.
|
|
1648
|
+
Browser `mine()`/`merge({ guestCartToken })` derive customer identity from the
|
|
1649
|
+
JWT; server calls require an explicit `customerToken`.
|
|
1650
|
+
|
|
1310
1651
|
### Community Moderation (ServerClient-only)
|
|
1311
1652
|
|
|
1312
1653
|
Available only on ServerClient via `server.community.moderation.*`.
|
|
@@ -1412,7 +1753,7 @@ join-order surface and does not emit a semantic order-change webhook.
|
|
|
1412
1753
|
|
|
1413
1754
|
## Supported Collections
|
|
1414
1755
|
|
|
1415
|
-
Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
|
|
1756
|
+
Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
|
|
1416
1757
|
|
|
1417
1758
|
| Category | Browser-public generic collections |
|
|
1418
1759
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -1435,8 +1776,9 @@ operational records, raw cart/cart-item records, form submissions, raw community
|
|
|
1435
1776
|
Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
|
|
1436
1777
|
and moderation records. They remain available from
|
|
1437
1778
|
`createServerClient().collections` with secret/PAT credentials. Shaped
|
|
1438
|
-
browser/customer helpers such as `commerce.
|
|
1439
|
-
`commerce.
|
|
1779
|
+
browser/customer helpers such as `commerce.carts.*`,
|
|
1780
|
+
`commerce.checkouts.create/retrieve`, `commerce.paymentSessions.*`, and
|
|
1781
|
+
`commerce.orders.listMine()` remain available
|
|
1440
1782
|
where customer-facing DTOs are needed, but raw slugs are intentionally absent
|
|
1441
1783
|
from browser collection discovery.
|
|
1442
1784
|
|