@01.software/sdk 0.44.0 → 0.46.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 +366 -2
- package/MIGRATION.md +108 -10
- package/README.md +486 -98
- 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 +2693 -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 +2693 -345
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-DWooaiTr.d.ts → collection-client-D6kKrYdq.d.ts} +8 -5
- package/dist/{collection-client-Dyl-CDcj.d.cts → collection-client-DolMjCoC.d.cts} +8 -5
- package/dist/{const-pGDnsy0o.d.ts → const-BAG0MH0u.d.cts} +13 -10
- package/dist/{const-1vleZ6ov.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-DUNySyxV.d.ts → index-BNICJxBL.d.ts} +4 -4
- package/dist/{index-a6g7vqAD.d.cts → index-YGrZrxNn.d.cts} +4 -4
- package/dist/index.cjs +2754 -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 +2754 -368
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-CDsuOEfM.d.cts → payload-types-CJZuXcgF.d.cts} +6671 -4001
- package/dist/{payload-types-CDsuOEfM.d.ts → payload-types-CJZuXcgF.d.ts} +6671 -4001
- package/dist/query.cjs +558 -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 +558 -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 +2343 -437
- 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 +2343 -437
- 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-CDND3hzq.d.cts → types-C60vHIkY.d.ts} +1134 -509
- package/dist/types-D5p7-m1l.d.cts +47 -0
- package/dist/{types-Dt9FJisi.d.ts → types-xlqBeW2N.d.cts} +1134 -509
- 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 +10 -9
- package/dist/types-SoXsbKhj.d.cts +0 -33
- package/dist/types-oiae_LMm.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,24 +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
|
-
pgProvider: 'portone', // required with pgPaymentId
|
|
230
|
-
pgPaymentId: 'provider-payment-id', // omit both fields for free orders
|
|
231
|
-
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(),
|
|
232
236
|
})
|
|
233
237
|
|
|
234
238
|
// SSR prefetch (server)
|
|
@@ -238,13 +242,31 @@ await serverQuery.prefetchQuery({
|
|
|
238
242
|
})
|
|
239
243
|
```
|
|
240
244
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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`.
|
|
244
248
|
|
|
245
249
|
Always import `createServerClient` from `@01.software/sdk/server` so generated
|
|
246
250
|
code and bundlers do not blur the Secret Key boundary.
|
|
247
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
|
+
|
|
248
270
|
Server-rendered preview routes can use `server.preview.detail()` with the
|
|
249
271
|
short-lived preview token issued by Console:
|
|
250
272
|
|
|
@@ -338,14 +360,14 @@ const { product } = result
|
|
|
338
360
|
// featuredImage, priceRange, compareAtPriceRange, availableForSale, selectedOrFirstAvailableVariant }
|
|
339
361
|
```
|
|
340
362
|
|
|
341
|
-
`detail()` returns `
|
|
342
|
-
|
|
343
|
-
`reason` value is one of
|
|
344
|
-
`feature_disabled`, so storefronts can choose
|
|
345
|
-
CTA, or feature gating UI. Permission/auth
|
|
346
|
-
mismatches, still throw
|
|
347
|
-
|
|
348
|
-
|
|
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.
|
|
349
371
|
|
|
350
372
|
Public product visibility is driven by product `status` and `publishedAt`.
|
|
351
373
|
Listing helpers return products with `status: 'published'` when `publishedAt`
|
|
@@ -355,16 +377,17 @@ appearing in PLP/search/curated listing helpers. Draft, archived, scheduled, or
|
|
|
355
377
|
malformed lifecycle states return `{ found: false, reason: 'not_published' }`
|
|
356
378
|
from detail and are omitted from listings.
|
|
357
379
|
|
|
358
|
-
The successful product payload
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
`product.
|
|
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
|
|
|
@@ -677,6 +700,57 @@ selection param. Each swatch carries a hint-only slug-compat href such as
|
|
|
677
700
|
`preferCompleteVariantFromHint: true` on `buildProductListingCard()` only when
|
|
678
701
|
the card should deep-link a complete hint variant.
|
|
679
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
|
+
|
|
680
754
|
## Storefront performance defaults
|
|
681
755
|
|
|
682
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.
|
|
@@ -868,6 +942,24 @@ const product = await server.collections.from('products').findById(id, {
|
|
|
868
942
|
const product = await client.collections.from('products').findById('id')
|
|
869
943
|
```
|
|
870
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
|
+
|
|
871
963
|
Raw collection mutations are an escape hatch. For ecommerce product catalog
|
|
872
964
|
writes, prefer `server.commerce.product.upsert()` so options, option-values,
|
|
873
965
|
and variants are written through the domain transaction.
|
|
@@ -1045,13 +1137,58 @@ await server.collections.from('articles').update('article_id', {
|
|
|
1045
1137
|
|
|
1046
1138
|
### Customer Auth
|
|
1047
1139
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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.
|
|
1053
1146
|
|
|
1054
|
-
|
|
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
|
+
})
|
|
1158
|
+
|
|
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.
|
|
1055
1192
|
|
|
1056
1193
|
```typescript
|
|
1057
1194
|
const client = createClient({
|
|
@@ -1079,7 +1216,7 @@ client.customer.auth.logout()
|
|
|
1079
1216
|
const orders = await client.commerce.orders.listMine({
|
|
1080
1217
|
page: 1,
|
|
1081
1218
|
limit: 10,
|
|
1082
|
-
|
|
1219
|
+
financialStatus: 'paid',
|
|
1083
1220
|
})
|
|
1084
1221
|
|
|
1085
1222
|
// Password
|
|
@@ -1088,15 +1225,236 @@ await client.customer.auth.resetPassword(token, newPassword)
|
|
|
1088
1225
|
await client.customer.auth.changePassword(currentPassword, newPassword)
|
|
1089
1226
|
```
|
|
1090
1227
|
|
|
1091
|
-
|
|
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
|
+
```
|
|
1285
|
+
|
|
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
|
+
For a provider that is not backed by a first-party adapter, trusted server code
|
|
1387
|
+
can use the bounded custom bridge. The caller must first verify the provider's
|
|
1388
|
+
authoritative API response or signed webhook, then report the exact captured
|
|
1389
|
+
identity and Money. The create response is intentionally limited to
|
|
1390
|
+
`{ id, provider, providerSessionId, money }`.
|
|
1092
1391
|
|
|
1093
|
-
|
|
1392
|
+
```typescript
|
|
1393
|
+
const customSession = await server.commerce.paymentSessions.custom.create({
|
|
1394
|
+
provider: 'my-provider',
|
|
1395
|
+
checkoutToken,
|
|
1396
|
+
idempotencyKey: createCommerceIdempotencyKey(),
|
|
1397
|
+
})
|
|
1398
|
+
|
|
1399
|
+
const verifiedCapture = await verifyMyProviderCapture({
|
|
1400
|
+
expectedAmount: customSession.money.amountMinor,
|
|
1401
|
+
expectedCurrency: customSession.money.currency,
|
|
1402
|
+
})
|
|
1403
|
+
|
|
1404
|
+
if (!verifiedCapture.ok) throw new Error('Provider capture was not verified')
|
|
1405
|
+
|
|
1406
|
+
const customConfirmation = await server.commerce.paymentSessions.custom.confirm(
|
|
1407
|
+
{
|
|
1408
|
+
provider: customSession.provider,
|
|
1409
|
+
providerSessionId: customSession.providerSessionId,
|
|
1410
|
+
providerPaymentId: verifiedCapture.paymentId,
|
|
1411
|
+
providerEventId: verifiedCapture.eventId,
|
|
1412
|
+
amount: customSession.money.amountMinor,
|
|
1413
|
+
currency: customSession.money.currency,
|
|
1414
|
+
occurredAt: verifiedCapture.occurredAt,
|
|
1415
|
+
},
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
console.log(customConfirmation.orderId)
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
If the tenant's custom-provider policy requires HMAC, pass
|
|
1422
|
+
`{ createProof(rawBody) }` as the second argument to `custom.confirm`. The
|
|
1423
|
+
callback receives the exact JSON bytes the SDK sends and returns `{ timestamp,
|
|
1424
|
+
signature }`; signature headers are redacted from SDK debug output. This is not
|
|
1425
|
+
a generic “mark paid” API, adapter support, refund authority, or a manual/offline
|
|
1426
|
+
pending-order flow. Use Draft Orders for current back-office manual pending
|
|
1427
|
+
orders; storefront manual/offline placement is a separate capability.
|
|
1428
|
+
|
|
1429
|
+
`checkoutToken` is returned only alongside Checkout creation; ordinary Checkout
|
|
1430
|
+
DTOs never echo the bearer capability. Browser `checkouts.retrieve` and
|
|
1431
|
+
PaymentSession retrieve/cancel calls accept that token. Server
|
|
1432
|
+
`checkouts.retrieve({ checkoutId })` uses tenant-scoped authority, and
|
|
1433
|
+
`checkouts.query` returns lightweight rows without capabilities.
|
|
1434
|
+
|
|
1435
|
+
Canonical commerce error envelopes become `CommerceSDKError`. Branch on its
|
|
1436
|
+
stable `reason`, inspect `retryable`, and log `requestId`; a newer server reason
|
|
1437
|
+
is preserved as `reason: 'unknown_commerce_error'` plus `rawReason`.
|
|
1438
|
+
|
|
1439
|
+
### Commerce Orders (post-placement operations)
|
|
1440
|
+
|
|
1441
|
+
Provider-paid `orders.create`, `orders.confirmPayment`, and ordinary
|
|
1442
|
+
`orders.updateTransaction` were removed in SDK 0.45. The server-only custom
|
|
1443
|
+
PaymentSession bridge above does not restore those methods or expose a general
|
|
1444
|
+
manual Order API. After a PaymentSession commits an Order, trusted server code
|
|
1445
|
+
can use the remaining fulfillment, cancellation, and return operations.
|
|
1446
|
+
Customer order reads remain available through
|
|
1447
|
+
`client.commerce.orders.listMine()`.
|
|
1448
|
+
|
|
1449
|
+
The SDK root no longer exports raw generated `Order`, `Transaction`, `Cart`, or
|
|
1450
|
+
`CartItem` Payload documents. Use the canonical `PublicOrder` / `PublicCart`
|
|
1451
|
+
DTOs for storefront code and explicit server collection types only inside
|
|
1452
|
+
trusted operational code. Order cancellation no longer returns the deprecated
|
|
1453
|
+
inline provider-refund branch: a paid cancellation returns `refundPending`, and
|
|
1454
|
+
the dedicated refund-resolution flow reports provider completion.
|
|
1094
1455
|
|
|
1095
1456
|
```typescript
|
|
1096
|
-
// Orders
|
|
1097
|
-
await server.commerce.orders.create(params)
|
|
1098
1457
|
await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
|
|
1099
|
-
await server.commerce.orders.checkout({ cartId, orderNumber, customerSnapshot, pgProvider, pgPaymentId, discountCode? })
|
|
1100
1458
|
|
|
1101
1459
|
// Single-order lookup (getOrder endpoint removed — use collection find)
|
|
1102
1460
|
const { docs: [order] } = await server.collections.from('orders').find({
|
|
@@ -1111,29 +1469,6 @@ await server.commerce.orders.createFulfillment({ orderNumber, items })
|
|
|
1111
1469
|
await server.commerce.orders.updateFulfillment({ fulfillmentId, carrier, trackingNumber })
|
|
1112
1470
|
await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier, trackingNumber }] })
|
|
1113
1471
|
|
|
1114
|
-
// Provider-verified payment confirmation
|
|
1115
|
-
// Provider webhook handlers should verify with the provider first, then call:
|
|
1116
|
-
await server.commerce.orders.confirmPayment({
|
|
1117
|
-
orderNumber,
|
|
1118
|
-
pgProvider: 'provider-name',
|
|
1119
|
-
pgPaymentId,
|
|
1120
|
-
amount,
|
|
1121
|
-
providerStatus: 'PAID',
|
|
1122
|
-
providerEventId,
|
|
1123
|
-
})
|
|
1124
|
-
|
|
1125
|
-
// For paid create/checkout flows, pass pgProvider with the same provider slug
|
|
1126
|
-
// you will later use for confirmPayment and getByPaymentId. pgProvider and
|
|
1127
|
-
// pgPaymentId are a pair: omit both for free orders, or provide both for paid
|
|
1128
|
-
// payment windows. getByPaymentId({ pgProvider, pgPaymentId }) is strict and
|
|
1129
|
-
// only resolves payment transactions stored with the same provider.
|
|
1130
|
-
|
|
1131
|
-
// Low-level transaction annotation / compatibility path. Prefer confirmPayment()
|
|
1132
|
-
// for normal provider-verified paid transitions.
|
|
1133
|
-
// status: 'failed' records a retryable PG failure on the Transaction only; the
|
|
1134
|
-
// Order stays pending until verified payment succeeds or the merchant cancels it.
|
|
1135
|
-
await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
|
|
1136
|
-
|
|
1137
1472
|
// Returns
|
|
1138
1473
|
await server.commerce.orders.createReturn({
|
|
1139
1474
|
orderNumber,
|
|
@@ -1162,8 +1497,14 @@ Available on both Client (customer JWT) and ServerClient (secretKey).
|
|
|
1162
1497
|
|
|
1163
1498
|
```typescript
|
|
1164
1499
|
await client.commerce.discounts.validate({ code, orderAmount })
|
|
1165
|
-
await client.commerce.shipping.calculate({
|
|
1166
|
-
|
|
1500
|
+
await client.commerce.shipping.calculate({
|
|
1501
|
+
shippingPolicyId?,
|
|
1502
|
+
orderAmount,
|
|
1503
|
+
postalCode?,
|
|
1504
|
+
countryCode?,
|
|
1505
|
+
regionCode?,
|
|
1506
|
+
})
|
|
1507
|
+
// calculateShipping returns: { shippingAmount, shippingLines, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }
|
|
1167
1508
|
```
|
|
1168
1509
|
|
|
1169
1510
|
### Commerce Product
|
|
@@ -1281,31 +1622,77 @@ for (const item of results) {
|
|
|
1281
1622
|
|
|
1282
1623
|
### Commerce Cart
|
|
1283
1624
|
|
|
1284
|
-
Available on both Client and ServerClient via `commerce.
|
|
1625
|
+
Available on both Client and ServerClient via `commerce.carts.*`. These helpers
|
|
1626
|
+
return required, browser-safe DTOs; use
|
|
1627
|
+
`server.collections.from('carts' | 'cart-items')` for raw operational documents.
|
|
1285
1628
|
|
|
1286
1629
|
```typescript
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1630
|
+
const created = await client.commerce.carts.create({
|
|
1631
|
+
market: 'kr',
|
|
1632
|
+
attributes: [{ key: 'channel', value: 'web' }],
|
|
1633
|
+
})
|
|
1634
|
+
const { cartToken, warnings } = created
|
|
1635
|
+
let cart = created.cart
|
|
1636
|
+
|
|
1637
|
+
cart = (
|
|
1638
|
+
await client.commerce.carts.addItem({
|
|
1639
|
+
cartToken,
|
|
1640
|
+
productId,
|
|
1641
|
+
variantId,
|
|
1642
|
+
quantity,
|
|
1643
|
+
admissions: [{ eventId, occurrenceId, admissionTypeId }],
|
|
1644
|
+
expectedRevision: cart.revision,
|
|
1645
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1646
|
+
})
|
|
1647
|
+
).cart
|
|
1648
|
+
|
|
1649
|
+
cart = (
|
|
1650
|
+
await client.commerce.carts.updateItem({
|
|
1651
|
+
cartToken,
|
|
1652
|
+
cartItemId,
|
|
1653
|
+
quantity,
|
|
1654
|
+
expectedRevision: cart.revision,
|
|
1655
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1656
|
+
})
|
|
1657
|
+
).cart
|
|
1658
|
+
cart = (
|
|
1659
|
+
await client.commerce.carts.removeItem({
|
|
1660
|
+
cartToken,
|
|
1661
|
+
cartItemId,
|
|
1662
|
+
expectedRevision: cart.revision,
|
|
1663
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1664
|
+
})
|
|
1665
|
+
).cart
|
|
1666
|
+
await client.commerce.carts.retrieve({ cartToken })
|
|
1667
|
+
cart = (
|
|
1668
|
+
await client.commerce.carts.updateDiscountCodes({
|
|
1669
|
+
cartToken,
|
|
1670
|
+
discountCodes: ['WELCOME10', 'FREESHIP'],
|
|
1671
|
+
expectedRevision: cart.revision,
|
|
1672
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1673
|
+
})
|
|
1674
|
+
).cart
|
|
1675
|
+
cart = (
|
|
1676
|
+
await client.commerce.carts.updateAttributes({
|
|
1677
|
+
cartToken,
|
|
1678
|
+
attributes: [], // replace/clear
|
|
1679
|
+
expectedRevision: cart.revision,
|
|
1680
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1681
|
+
})
|
|
1682
|
+
).cart
|
|
1683
|
+
await client.commerce.carts.clear({
|
|
1684
|
+
cartToken,
|
|
1685
|
+
expectedRevision: cart.revision,
|
|
1686
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1294
1687
|
})
|
|
1295
|
-
|
|
1296
|
-
// Update item quantity
|
|
1297
|
-
await client.commerce.cart.updateItem({ cartItemId, quantity })
|
|
1298
|
-
|
|
1299
|
-
// Remove item from cart
|
|
1300
|
-
await client.commerce.cart.removeItem({ cartItemId })
|
|
1301
|
-
|
|
1302
|
-
// Other cart operations return sanitized customer-facing cart DTOs.
|
|
1303
|
-
await client.commerce.cart.get(cartId)
|
|
1304
|
-
await client.commerce.cart.applyDiscount({ cartId, discountCode })
|
|
1305
|
-
await client.commerce.cart.removeDiscount({ cartId })
|
|
1306
|
-
await client.commerce.cart.clear({ cartId })
|
|
1307
1688
|
```
|
|
1308
1689
|
|
|
1690
|
+
`updateDiscountCodes({ cartToken, discountCodes })` is the canonical full-list
|
|
1691
|
+
replacement API. Pass an empty list to clear codes. Singular `admission`,
|
|
1692
|
+
`applyDiscount()`, and `removeDiscount()` are not part of the 0.45 surface.
|
|
1693
|
+
Browser `mine()`/`merge({ guestCartToken })` derive customer identity from the
|
|
1694
|
+
JWT; server calls require an explicit `customerToken`.
|
|
1695
|
+
|
|
1309
1696
|
### Community Moderation (ServerClient-only)
|
|
1310
1697
|
|
|
1311
1698
|
Available only on ServerClient via `server.community.moderation.*`.
|
|
@@ -1411,7 +1798,7 @@ join-order surface and does not emit a semantic order-change webhook.
|
|
|
1411
1798
|
|
|
1412
1799
|
## Supported Collections
|
|
1413
1800
|
|
|
1414
|
-
Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
|
|
1801
|
+
Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECTIONS`).
|
|
1415
1802
|
|
|
1416
1803
|
| Category | Browser-public generic collections |
|
|
1417
1804
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -1434,8 +1821,9 @@ operational records, raw cart/cart-item records, form submissions, raw community
|
|
|
1434
1821
|
Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
|
|
1435
1822
|
and moderation records. They remain available from
|
|
1436
1823
|
`createServerClient().collections` with secret/PAT credentials. Shaped
|
|
1437
|
-
browser/customer helpers such as `commerce.
|
|
1438
|
-
`commerce.
|
|
1824
|
+
browser/customer helpers such as `commerce.carts.*`,
|
|
1825
|
+
`commerce.checkouts.create/retrieve`, `commerce.paymentSessions.*`, and
|
|
1826
|
+
`commerce.orders.listMine()` remain available
|
|
1439
1827
|
where customer-facing DTOs are needed, but raw slugs are intentionally absent
|
|
1440
1828
|
from browser collection discovery.
|
|
1441
1829
|
|