@01.software/sdk 0.48.0 → 0.51.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 +157 -1
- package/MIGRATION.md +56 -0
- package/README.md +276 -49
- package/dist/client.cjs +375 -161
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +7 -6
- package/dist/client.d.ts +7 -6
- package/dist/client.js +375 -161
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-CBup9yOx.d.cts → collection-client-CWAeuzV4.d.cts} +6 -4
- package/dist/{collection-client-D3ExJU-7.d.ts → collection-client-hYvVfHIm.d.ts} +6 -4
- package/dist/{const-k9gHylYa.d.ts → const-D8LixhAJ.d.cts} +5 -5
- package/dist/{const-BXEi1Kix.d.cts → const-JD8obaKg.d.ts} +5 -5
- package/dist/embedded-admin/server.cjs +306 -0
- package/dist/embedded-admin/server.cjs.map +1 -0
- package/dist/embedded-admin/server.d.cts +33 -0
- package/dist/embedded-admin/server.d.ts +33 -0
- package/dist/embedded-admin/server.js +288 -0
- package/dist/embedded-admin/server.js.map +1 -0
- package/dist/embedded-admin.cjs +400 -0
- package/dist/embedded-admin.cjs.map +1 -0
- package/dist/embedded-admin.d.cts +61 -0
- package/dist/embedded-admin.d.ts +61 -0
- package/dist/embedded-admin.js +377 -0
- package/dist/embedded-admin.js.map +1 -0
- package/dist/errors.cjs +64 -15
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +13 -4
- package/dist/errors.d.ts +13 -4
- package/dist/errors.js +64 -15
- package/dist/errors.js.map +1 -1
- package/dist/{index-BCM9CTL7.d.cts → index-B0Hjh3Ey.d.ts} +72 -10
- package/dist/{index-CHGGkcLj.d.ts → index-Bu0kCc6o.d.cts} +72 -10
- package/dist/index.cjs +470 -429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -67
- package/dist/index.d.ts +9 -67
- package/dist/index.js +470 -429
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-B6AZQcB7.d.cts → payload-types-BOsckg8B.d.cts} +396 -57
- package/dist/{payload-types-B6AZQcB7.d.ts → payload-types-BOsckg8B.d.ts} +396 -57
- package/dist/query.cjs +115 -70
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +5 -5
- package/dist/query.d.ts +5 -5
- package/dist/query.js +115 -70
- package/dist/query.js.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/server.cjs +591 -186
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +8 -7
- package/dist/server.d.ts +8 -7
- package/dist/server.js +591 -186
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +22 -0
- package/dist/storefront-cache.cjs.map +1 -1
- package/dist/storefront-cache.js +22 -0
- package/dist/storefront-cache.js.map +1 -1
- package/dist/{types-ByD2zii1.d.ts → types-5ZlcAFuU.d.ts} +2 -2
- package/dist/{types-8us-_C-Q.d.cts → types-B14sEt1z.d.cts} +2 -2
- package/dist/{types-CM2je9Kw.d.cts → types-Bj8j7VHU.d.ts} +107 -26
- package/dist/{types-DbA5V3mt.d.ts → types-CM5qiR2k.d.cts} +107 -26
- 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 +124 -80
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +21 -5
- package/dist/webhook.d.ts +21 -5
- package/dist/webhook.js +124 -80
- package/dist/webhook.js.map +1 -1
- package/package.json +27 -5
package/README.md
CHANGED
|
@@ -29,11 +29,11 @@ pnpm add @01.software/sdk
|
|
|
29
29
|
import { createClient } from '@01.software/sdk'
|
|
30
30
|
|
|
31
31
|
// Server-only entry - keep Secret Key code out of browser-facing imports
|
|
32
|
-
import { createServerClient } from '@01.software/sdk/server'
|
|
32
|
+
import { createServerClient, isCommerceSDKError } from '@01.software/sdk/server'
|
|
33
33
|
|
|
34
34
|
// Webhook only - webhook handlers
|
|
35
35
|
import {
|
|
36
|
-
|
|
36
|
+
handleSignedWebhook,
|
|
37
37
|
createTypedWebhookHandler,
|
|
38
38
|
} from '@01.software/sdk/webhook'
|
|
39
39
|
|
|
@@ -43,6 +43,12 @@ import { RealtimeConnection } from '@01.software/sdk/realtime'
|
|
|
43
43
|
// Storefront cache resource names for SSG/ISR adapters
|
|
44
44
|
import { storefrontCacheResources } from '@01.software/sdk/storefront-cache'
|
|
45
45
|
|
|
46
|
+
// Embedded app browser handshake
|
|
47
|
+
import { receiveEmbeddedAdminSession } from '@01.software/sdk/embedded-admin'
|
|
48
|
+
|
|
49
|
+
// Embedded app backend verification
|
|
50
|
+
import { verifyEmbeddedAdminSession } from '@01.software/sdk/embedded-admin/server'
|
|
51
|
+
|
|
46
52
|
// Components - sub-path imports per domain
|
|
47
53
|
import { Analytics } from '@01.software/sdk/analytics/react'
|
|
48
54
|
import { RichTextContent } from '@01.software/sdk/ui/rich-text'
|
|
@@ -58,26 +64,28 @@ types lightweight. Server, React Query, and UI features live behind explicit
|
|
|
58
64
|
sub-paths so consumers install feature peers only when they import the matching
|
|
59
65
|
entry.
|
|
60
66
|
|
|
61
|
-
| Import
|
|
62
|
-
|
|
|
63
|
-
| `@01.software/sdk`
|
|
64
|
-
| `@01.software/sdk/client`
|
|
65
|
-
| `@01.software/sdk/server`
|
|
66
|
-
| `@01.software/sdk/errors`
|
|
67
|
-
| `@01.software/sdk/analytics`
|
|
68
|
-
| `@01.software/sdk/metadata`
|
|
69
|
-
| `@01.software/sdk/webhook`
|
|
70
|
-
| `@01.software/sdk/query`
|
|
71
|
-
| `@01.software/sdk/realtime`
|
|
72
|
-
| `@01.software/sdk/storefront-cache`
|
|
73
|
-
| `@01.software/sdk/
|
|
74
|
-
| `@01.software/sdk/
|
|
75
|
-
| `@01.software/sdk/
|
|
76
|
-
| `@01.software/sdk/ui/
|
|
77
|
-
| `@01.software/sdk/ui/
|
|
78
|
-
| `@01.software/sdk/ui/
|
|
79
|
-
| `@01.software/sdk/ui/
|
|
80
|
-
| `@01.software/sdk/ui/
|
|
67
|
+
| Import | Feature(s) | Install when used |
|
|
68
|
+
| ---------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `@01.software/sdk` | browser-safe `createClient`, commerce helpers, collection helpers, types | none |
|
|
70
|
+
| `@01.software/sdk/client` | browser-safe `createClient` entry and SDK error guards | none |
|
|
71
|
+
| `@01.software/sdk/server` | `createServerClient`, server-only APIs, and SDK error guards | none; keep `secretKey` code on the server |
|
|
72
|
+
| `@01.software/sdk/errors` | SDK error classes and guards | none |
|
|
73
|
+
| `@01.software/sdk/analytics` | browser analytics client and typed event helpers | none |
|
|
74
|
+
| `@01.software/sdk/metadata` | SEO metadata extraction and generation helpers | none |
|
|
75
|
+
| `@01.software/sdk/webhook` | webhook handlers, event guards, and webhook types | none |
|
|
76
|
+
| `@01.software/sdk/query` | React Query hooks, cache helpers, `getQueryClient` | `@tanstack/react-query`, `react`, `react-dom` |
|
|
77
|
+
| `@01.software/sdk/realtime` | `RealtimeConnection`, `useRealtimeQuery` | `@tanstack/react-query`, `react`, `react-dom` |
|
|
78
|
+
| `@01.software/sdk/storefront-cache` | product storefront cache resource name helpers | none |
|
|
79
|
+
| `@01.software/sdk/embedded-admin` | embedded app browser handshake and shared protocol types | none |
|
|
80
|
+
| `@01.software/sdk/embedded-admin/server` | assertion verification and JWKS loading | none; keep receiver session creation on the server |
|
|
81
|
+
| `@01.software/sdk/analytics/react` | `<Analytics />` | `react`, `react-dom` |
|
|
82
|
+
| `@01.software/sdk/ui/rich-text` | `RichTextContent`, `StyledRichTextContent` | `react`, `react-dom`, `@payloadcms/richtext-lexical` |
|
|
83
|
+
| `@01.software/sdk/ui/form` | `FormRenderer` | `react`, `react-dom` |
|
|
84
|
+
| `@01.software/sdk/ui/code-block` | `CodeBlock`, `highlight` | `react`, `react-dom`, `shiki`, `hast-util-to-jsx-runtime` |
|
|
85
|
+
| `@01.software/sdk/ui/canvas` | `CanvasRenderer`, `CanvasFrame`, `useCanvas`, `prefetchCanvas` | `react`, `react-dom`, `@tanstack/react-query`, `@xyflow/react`, `quickjs-emscripten`, `postcss`, `sucrase` |
|
|
86
|
+
| `@01.software/sdk/ui/canvas/server` | canvas server helpers | none |
|
|
87
|
+
| `@01.software/sdk/ui/video` | `VideoPlayer` | `react`, `react-dom`, `@mux/mux-player-react` |
|
|
88
|
+
| `@01.software/sdk/ui/image` | `Image` | `react`, `react-dom` |
|
|
81
89
|
|
|
82
90
|
`CanvasRenderer` renders reserved `shape` nodes (`terminator`, `process`,
|
|
83
91
|
`decision`, `io`, `subprocess`) with first-party SVG geometry, without a tenant
|
|
@@ -87,11 +95,62 @@ If a feature is not listed here, it does not need a separate peer install.
|
|
|
87
95
|
For the full component-to-peer mapping, see
|
|
88
96
|
`packages/published/sdk/.claude/rules/components-reference.md`.
|
|
89
97
|
|
|
98
|
+
### Embedded Admin App
|
|
99
|
+
|
|
100
|
+
An embedded admin app is one tenant-owned management UI rendered inside the
|
|
101
|
+
Console Developer Settings surface. The browser helper accepts exactly one
|
|
102
|
+
short-lived Console assertion through the pinned parent/origin challenge. Send
|
|
103
|
+
the receipt immediately to your own backend; do not decode it into a browser
|
|
104
|
+
session or persist it.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { receiveEmbeddedAdminSession } from '@01.software/sdk/embedded-admin'
|
|
108
|
+
|
|
109
|
+
const receipt = await receiveEmbeddedAdminSession({
|
|
110
|
+
consoleOrigin: 'https://console.01.software',
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
await fetch('/api/embedded-admin/session', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify(receipt),
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The receiver backend verifies the assertion against the Console JWKS and pins
|
|
121
|
+
its own exact origin. Establish an app-local session only after this succeeds.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { verifyEmbeddedAdminSession } from '@01.software/sdk/embedded-admin/server'
|
|
125
|
+
|
|
126
|
+
const claims = await verifyEmbeddedAdminSession({
|
|
127
|
+
channelId: body.requestId,
|
|
128
|
+
consoleOrigin: 'https://console.01.software',
|
|
129
|
+
destination: 'https://admin.example.com',
|
|
130
|
+
token: body.token,
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Tenant admins configure the active tenant's single binding through the Human
|
|
135
|
+
CLI (`01 embedded-app configure`) with a user-scoped PAT. Binding management is
|
|
136
|
+
an operator control-plane action and is intentionally not exposed as a public
|
|
137
|
+
SDK mutation facade. A tenant API key (`sk01_`) is rejected.
|
|
138
|
+
|
|
139
|
+
Loopback HTTP is disabled by default. Only non-production fixtures may set
|
|
140
|
+
`allowLoopbackHttp: true` on the receiver and verifier options.
|
|
141
|
+
|
|
142
|
+
The assertion proves operator and tenant context only. It is not a Console API
|
|
143
|
+
credential; the external backend must use its own server-held credential and
|
|
144
|
+
authorization policy for tenant data.
|
|
145
|
+
|
|
90
146
|
Migration quick reference:
|
|
91
147
|
|
|
92
148
|
- `createClient` remains available from `@01.software/sdk` and
|
|
93
149
|
`@01.software/sdk/client`.
|
|
94
150
|
- `createServerClient` must be imported from `@01.software/sdk/server`.
|
|
151
|
+
- Error guards are available from the same `@01.software/sdk/client` or
|
|
152
|
+
`@01.software/sdk/server` entry as the client that throws; the root and
|
|
153
|
+
`@01.software/sdk/errors` guards also classify errors across entries.
|
|
95
154
|
- React Query hooks and cache helpers must be imported from
|
|
96
155
|
`@01.software/sdk/query`.
|
|
97
156
|
- Product storefront cache resource helpers must be imported from
|
|
@@ -193,6 +252,8 @@ honored only when `mode` is unset.
|
|
|
193
252
|
|
|
194
253
|
### Client
|
|
195
254
|
|
|
255
|
+
<!-- sdk-docs-sync-shaped-first: commerce.product.listingPage -->
|
|
256
|
+
|
|
196
257
|
```typescript
|
|
197
258
|
import { createClient } from '@01.software/sdk'
|
|
198
259
|
|
|
@@ -200,13 +261,16 @@ const client = createClient({
|
|
|
200
261
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
|
|
201
262
|
})
|
|
202
263
|
|
|
203
|
-
//
|
|
204
|
-
const
|
|
264
|
+
// Fetch public-safe, card-ready catalog data through the shaped helper.
|
|
265
|
+
const page = await client.commerce.product.listingPage({
|
|
205
266
|
limit: 10,
|
|
206
|
-
select: { title: true, slug: true },
|
|
207
267
|
})
|
|
268
|
+
const cards = page.cards
|
|
208
269
|
```
|
|
209
270
|
|
|
271
|
+
For Payload-native reads that shaped helpers do not cover, use the
|
|
272
|
+
[advanced raw collection escape hatch](#advanced-direct-payload-queries-escape-hatch).
|
|
273
|
+
|
|
210
274
|
### Server Client
|
|
211
275
|
|
|
212
276
|
```typescript
|
|
@@ -329,7 +393,7 @@ gallery item DTOs include title, description, content, gallery reference, and
|
|
|
329
393
|
image media. Tenant, metadata, draft status, and storage/provider internals are
|
|
330
394
|
omitted. Gallery item reads require either `gallerySlug` or `galleryId`; prefer
|
|
331
395
|
`gallerySlug` for storefront routes. The default gallery item sort is `manual`,
|
|
332
|
-
matching curator order from the
|
|
396
|
+
matching curator order from the Console.
|
|
333
397
|
|
|
334
398
|
Both helpers return Payload-style pagination (`docs`, `totalDocs`, `page`,
|
|
335
399
|
`limit`, etc.). `limit` is bounded server-side to `1..100`; invalid query,
|
|
@@ -880,6 +944,20 @@ export default async function ProductPage({ params }) {
|
|
|
880
944
|
}
|
|
881
945
|
```
|
|
882
946
|
|
|
947
|
+
The SDK sends
|
|
948
|
+
`Accept: application/vnd.01software.portone-action-capabilities.v1+json` for
|
|
949
|
+
Standard create and browser/server PortOne retrieve. Hosted create and
|
|
950
|
+
browser/server cancel do not send it. It accepts either the negotiated exact action
|
|
951
|
+
or the released Console's exact action, normalizing the latter to
|
|
952
|
+
`cancelable: false` and `retryPolicy: 'same_action_only'`. Partial capability
|
|
953
|
+
fields, different literals, and unknown response fields still fail closed.
|
|
954
|
+
The Accept value must be that sole exact media type after optional surrounding
|
|
955
|
+
whitespace. Lists, wildcards, parameters, and q-values deliberately retain the
|
|
956
|
+
default projection; this makes either Console-first or SDK-first rollout safe.
|
|
957
|
+
Direct HTTP create/retrieve responses use the vendor `Content-Type` only when
|
|
958
|
+
the successful body actually contains the Standard capability action. Hosted,
|
|
959
|
+
Toss, cancel, confirm, and error responses remain `application/json`.
|
|
960
|
+
|
|
883
961
|
## API
|
|
884
962
|
|
|
885
963
|
### Client Configuration
|
|
@@ -1325,10 +1403,8 @@ Treat a replayed Cart result as an operation receipt: never replace a newer
|
|
|
1325
1403
|
cached Cart with a lower `revision`; retrieve when response ordering is unclear.
|
|
1326
1404
|
|
|
1327
1405
|
```typescript
|
|
1328
|
-
import {
|
|
1329
|
-
|
|
1330
|
-
isCommerceSDKError,
|
|
1331
|
-
} from '@01.software/sdk'
|
|
1406
|
+
import { createCommerceIdempotencyKey } from '@01.software/sdk'
|
|
1407
|
+
import { isCommerceSDKError } from '@01.software/sdk/errors'
|
|
1332
1408
|
|
|
1333
1409
|
const checkoutIdempotencyKey = createCommerceIdempotencyKey()
|
|
1334
1410
|
const { checkout, checkoutToken } = await client.commerce.checkouts.create({
|
|
@@ -1369,6 +1445,32 @@ if (
|
|
|
1369
1445
|
window.location.assign(portOneSession.nextAction.checkoutUrl)
|
|
1370
1446
|
}
|
|
1371
1447
|
|
|
1448
|
+
// Standard V2 is a distinct Browser SDK capability, not a Hosted fallback.
|
|
1449
|
+
// Do not treat the SDK result or mobile redirect as payment finality.
|
|
1450
|
+
const standardPortOneSession =
|
|
1451
|
+
await client.commerce.paymentSessions.createPortOne({
|
|
1452
|
+
checkoutToken,
|
|
1453
|
+
provider: 'portone',
|
|
1454
|
+
providerOptions: { profile: 'standard_v2', paymentMethod: 'CARD' },
|
|
1455
|
+
idempotencyKey: createCommerceIdempotencyKey(),
|
|
1456
|
+
})
|
|
1457
|
+
if (
|
|
1458
|
+
standardPortOneSession.presentationStatus === 'actionable' &&
|
|
1459
|
+
standardPortOneSession.nextAction.type === 'portone_sdk'
|
|
1460
|
+
) {
|
|
1461
|
+
const { cancelable, retryPolicy, ...paymentRequest } =
|
|
1462
|
+
standardPortOneSession.nextAction
|
|
1463
|
+
|
|
1464
|
+
// PortOne V2 requires its currency constant (`CURRENCY_KRW`), not ISO `KRW`.
|
|
1465
|
+
// A Standard action cannot be canceled or replaced. After browser failure,
|
|
1466
|
+
// retry only this exact payment request and paymentId instead of calling
|
|
1467
|
+
// cancelPortOne() or creating a sibling PaymentSession.
|
|
1468
|
+
if (!cancelable && retryPolicy === 'same_action_only') {
|
|
1469
|
+
// await PortOne.requestPayment(paymentRequest)
|
|
1470
|
+
}
|
|
1471
|
+
// Always retrieve the session afterwards; Console lookup/webhook is authoritative.
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1372
1474
|
// Browser recovery requires the parent capability.
|
|
1373
1475
|
await client.commerce.paymentSessions.retrieve({
|
|
1374
1476
|
paymentSessionId: paymentSession.id,
|
|
@@ -1386,6 +1488,24 @@ await server.commerce.paymentSessions.retrievePortOne({
|
|
|
1386
1488
|
paymentSessionId: portOneSession.id,
|
|
1387
1489
|
})
|
|
1388
1490
|
|
|
1491
|
+
// Operator recovery can start from PortOne's canonical provider identity.
|
|
1492
|
+
// `findPortOne` is strictly local/read-only. `reconcilePortOne` is an explicit
|
|
1493
|
+
// mutation and must reuse one durable key for retries of the same operation;
|
|
1494
|
+
// its domain result and response complete atomically before delivery.
|
|
1495
|
+
const portOneIdentity = {
|
|
1496
|
+
provider: 'portone' as const,
|
|
1497
|
+
providerAccountKey: 'store-id-from-the-registered-connection',
|
|
1498
|
+
environment: 'test' as const,
|
|
1499
|
+
providerPaymentId: 'provider-assigned-payment-id',
|
|
1500
|
+
}
|
|
1501
|
+
const localPortOneState =
|
|
1502
|
+
await server.commerce.paymentSessions.findPortOne(portOneIdentity)
|
|
1503
|
+
const reconciledPortOneState =
|
|
1504
|
+
await server.commerce.paymentSessions.reconcilePortOne({
|
|
1505
|
+
...portOneIdentity,
|
|
1506
|
+
idempotencyKey: createCommerceIdempotencyKey(),
|
|
1507
|
+
})
|
|
1508
|
+
|
|
1389
1509
|
// Toss confirmation is a typed server-only adapter action. There is no generic
|
|
1390
1510
|
// `confirmVerified` or provider-paid Order creation escape hatch.
|
|
1391
1511
|
const confirmationIdempotencyKey = createCommerceIdempotencyKey()
|
|
@@ -1424,6 +1544,12 @@ authoritative API response or signed webhook, then report the exact captured
|
|
|
1424
1544
|
identity and Money. The create response is intentionally limited to
|
|
1425
1545
|
`{ id, provider, providerSessionId, money }`.
|
|
1426
1546
|
|
|
1547
|
+
Custom bridge sessions intentionally remain `null` in
|
|
1548
|
+
`checkout.paymentState.activePaymentSession`: Checkout exposes only managed
|
|
1549
|
+
adapters from its closed provider vocabulary. Persist the `providerSessionId`
|
|
1550
|
+
returned by `custom.create` and use `custom.retrieve` with that identity as the
|
|
1551
|
+
supported custom-lane settlement recovery path.
|
|
1552
|
+
|
|
1427
1553
|
```typescript
|
|
1428
1554
|
const customSession = await server.commerce.paymentSessions.custom.create({
|
|
1429
1555
|
provider: 'my-provider',
|
|
@@ -1467,6 +1593,20 @@ if (recovered.finalizationStatus === 'committed') {
|
|
|
1467
1593
|
}
|
|
1468
1594
|
```
|
|
1469
1595
|
|
|
1596
|
+
### Server-only PortOne identity reconciliation
|
|
1597
|
+
|
|
1598
|
+
The PortOne identity facade is available only from `@01.software/sdk/server`.
|
|
1599
|
+
Both methods require the complete `provider`, `providerAccountKey`,
|
|
1600
|
+
`environment`, and `providerPaymentId` tuple; tenant identity is derived from
|
|
1601
|
+
the server credential and is not accepted in the body. `findPortOne` never
|
|
1602
|
+
calls the provider or mutates local state. `reconcilePortOne` requires write scope and a
|
|
1603
|
+
durable idempotency key, then delegates to the existing managed PortOne
|
|
1604
|
+
convergence engine. A retry after an interrupted response replays the first
|
|
1605
|
+
atomically completed result without repeating provider I/O. Its result distinguishes `applied`, `observed`,
|
|
1606
|
+
`recovered_after_ambiguous_result`, and `requires_attention`. `found: false` is
|
|
1607
|
+
the one HTTP/SDK not-found result; reconciliation does not claim or
|
|
1608
|
+
heuristically attach an unknown provider payment.
|
|
1609
|
+
|
|
1470
1610
|
`custom.retrieve` is an exact lookup by either `providerSessionId` or
|
|
1471
1611
|
`providerPaymentId` (exactly one). It returns only bounded recovery state:
|
|
1472
1612
|
provider identity, session/provider/finalization status, Money, and the resolved
|
|
@@ -1491,6 +1631,38 @@ Canonical commerce error envelopes become `CommerceSDKError`. Branch on its
|
|
|
1491
1631
|
stable `reason`, inspect `retryable`, and log `requestId`; a newer server reason
|
|
1492
1632
|
is preserved as `reason: 'unknown_commerce_error'` plus `rawReason`.
|
|
1493
1633
|
|
|
1634
|
+
Non-commerce endpoints preserve their open-ended machine reason separately as
|
|
1635
|
+
`SDKError.apiReason`. Use `getSDKErrorReason(error)` when shared error handling
|
|
1636
|
+
must read either family: it returns the canonical commerce reason, the verbatim
|
|
1637
|
+
unknown commerce `rawReason`, or the non-commerce `apiReason`. Existing
|
|
1638
|
+
`details` payloads remain available for compatibility, but consumers no longer
|
|
1639
|
+
need to normalize `details.reason` and `details.body.reason` themselves.
|
|
1640
|
+
|
|
1641
|
+
#### Idempotent mutation results are not exactly-once receipts
|
|
1642
|
+
|
|
1643
|
+
No commerce mutation response field is a safe exactly-once gate for a
|
|
1644
|
+
non-idempotent side effect such as an operational alert, outbound webhook, or
|
|
1645
|
+
ledger entry. Use a consumer-owned durable claim keyed by the side effect's
|
|
1646
|
+
stable logical identity. The public webhook guide's
|
|
1647
|
+
[`processOnce`](https://docs.01.software/developers/webhooks) pattern uses a
|
|
1648
|
+
database unique key, queue idempotency store, or provider-safe send ledger.
|
|
1649
|
+
|
|
1650
|
+
`custom.confirm` returns `alreadyConfirmed: true` when the session was already
|
|
1651
|
+
confirmed at observation time. It does not attribute the transition to the
|
|
1652
|
+
current call. The field is absent from the initial successful confirmation, so
|
|
1653
|
+
code that only interprets the response shape must test
|
|
1654
|
+
`'alreadyConfirmed' in result`, not `result.alreadyConfirmed === false`. That
|
|
1655
|
+
presence check is still not a side-effect gate: when the initial commit succeeds
|
|
1656
|
+
but its response is lost, a retry returns `alreadyConfirmed: true` and would
|
|
1657
|
+
otherwise silently miss the transition.
|
|
1658
|
+
|
|
1659
|
+
Likewise, the `orders.cancelOrder` fields `cancelCommitted` and
|
|
1660
|
+
`alreadyCanceled` are domain effect dispositions, not transport replay markers.
|
|
1661
|
+
A same-key retry replays the cached initial 2xx body byte-for-byte and can
|
|
1662
|
+
therefore return the original `cancelCommitted: true` again. Neither field says
|
|
1663
|
+
whether the response was replayed or whether an external side effect has already
|
|
1664
|
+
run.
|
|
1665
|
+
|
|
1494
1666
|
### Commerce Orders (post-placement operations)
|
|
1495
1667
|
|
|
1496
1668
|
Provider-paid `orders.create`, `orders.confirmPayment`, and ordinary
|
|
@@ -1505,8 +1677,30 @@ The SDK root no longer exports raw generated `Order`, `Transaction`, `Cart`, or
|
|
|
1505
1677
|
`CartItem` Payload documents. Use the canonical `PublicOrder` / `PublicCart`
|
|
1506
1678
|
DTOs for storefront code and explicit server collection types only inside
|
|
1507
1679
|
trusted operational code. Order cancellation no longer returns the deprecated
|
|
1508
|
-
inline provider-refund branch
|
|
1509
|
-
|
|
1680
|
+
inline provider-refund branch. `refundPending` is a local financial marker, not
|
|
1681
|
+
authority for caller provider I/O:
|
|
1682
|
+
|
|
1683
|
+
| Captured payment lane | Provider refund owner | `resolveCancelRefund` |
|
|
1684
|
+
| ------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
|
|
1685
|
+
| Registered managed PaymentSession (Toss or PortOne) | Console only | Rejects `409 managed_refund_attestation_forbidden`; the pending marker is preserved |
|
|
1686
|
+
| Explicit `custom_bridge` capture | Trusted external integration | May report the already-completed provider outcome |
|
|
1687
|
+
| Legacy NULL-origin capture without a registered account | Bounded migration drain only | May report the already-completed provider outcome |
|
|
1688
|
+
|
|
1689
|
+
Do not infer the external lane from a defaulted `sessionOrigin`, an
|
|
1690
|
+
`orderCanceled` notification, or provider-observed cancellation. New managed
|
|
1691
|
+
captures must use Console's refund operation; `resolveCancelRefund` remains
|
|
1692
|
+
only for the two external-drain rows above.
|
|
1693
|
+
|
|
1694
|
+
Managed ownership follows the captured Transaction's exact tenant-bound
|
|
1695
|
+
PaymentSession and historical registered account. Order/provider/environment
|
|
1696
|
+
drift remains Console-owned corruption. Unresolved or cross-tenant identity
|
|
1697
|
+
fails closed opaquely as `payment_transaction_mismatch`; another session is
|
|
1698
|
+
never substitute evidence.
|
|
1699
|
+
|
|
1700
|
+
`managed_refund_attestation_forbidden` is the resolver's typed commerce error.
|
|
1701
|
+
Pre-existing resolver reasons remain generic SDK errors available through
|
|
1702
|
+
`getSDKErrorReason()` / `apiReason`, with legacy extra fields at the top level
|
|
1703
|
+
of the raw body in `error.details.body`.
|
|
1510
1704
|
|
|
1511
1705
|
```typescript
|
|
1512
1706
|
await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
|
|
@@ -1571,7 +1765,7 @@ options, option values, and variants. It is the tenant-admin safe path because
|
|
|
1571
1765
|
it applies the product/option/variant transaction that raw collection writes do
|
|
1572
1766
|
not provide.
|
|
1573
1767
|
|
|
1574
|
-
`commerce.product.upsert()` is a graph-write API. For
|
|
1768
|
+
`commerce.product.upsert()` is a graph-write API. For Console product
|
|
1575
1769
|
documents, Payload saves product fields first; upsert receives `productId`,
|
|
1576
1770
|
`graphRevision` (required when updating an existing product via `productId` -
|
|
1577
1771
|
load from `GET /api/products/:id/composer-draft`), options, and variants. Do
|
|
@@ -1778,10 +1972,21 @@ await server.community.moderation.unbanCustomer({ customerId })
|
|
|
1778
1972
|
|
|
1779
1973
|
### Webhook
|
|
1780
1974
|
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1975
|
+
Webhooks created in Console start inactive. Use this setup order so no event is
|
|
1976
|
+
sent before the receiver can verify it:
|
|
1977
|
+
|
|
1978
|
+
1. Save the inactive endpoint in Console → Integrations → Webhooks.
|
|
1979
|
+
2. Use **Reveal signing secret** once and copy the plaintext value.
|
|
1980
|
+
3. Deploy the receiver with that value set as `WEBHOOK_SECRET`.
|
|
1981
|
+
4. Return to the saved endpoint and choose **Activate webhook**.
|
|
1982
|
+
|
|
1983
|
+
This inactive-first behavior is Console-only and changes no API default.
|
|
1984
|
+
Creating an endpoint through the API with `isActive` omitted still yields an
|
|
1985
|
+
active endpoint (`true`), and an explicit `isActive` value is honored — so send
|
|
1986
|
+
`isActive: false` for the same staged setup outside Console.
|
|
1987
|
+
|
|
1988
|
+
After initial setup, **Rotate secret** invalidates the previous secret and
|
|
1989
|
+
returns a new one-time plaintext value for receiver deployment.
|
|
1785
1990
|
|
|
1786
1991
|
`Webhook` list/read responses do not include plaintext signing secrets — only
|
|
1787
1992
|
the Console reveal/rotate flows return them. Use `WebhookWithoutSecret` and
|
|
@@ -1793,7 +1998,10 @@ secrets.
|
|
|
1793
1998
|
Use HMAC-SHA256 signature verification:
|
|
1794
1999
|
|
|
1795
2000
|
```typescript
|
|
1796
|
-
import {
|
|
2001
|
+
import {
|
|
2002
|
+
handleSignedWebhook,
|
|
2003
|
+
createTypedWebhookHandler,
|
|
2004
|
+
} from '@01.software/sdk/webhook'
|
|
1797
2005
|
|
|
1798
2006
|
const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
1799
2007
|
// event.data is typed as Order
|
|
@@ -1802,16 +2010,34 @@ const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
|
1802
2010
|
|
|
1803
2011
|
export async function POST(request: Request) {
|
|
1804
2012
|
const secret = process.env.WEBHOOK_SECRET
|
|
1805
|
-
if (!secret) throw new Error('WEBHOOK_SECRET
|
|
2013
|
+
if (!secret?.trim()) throw new Error('WEBHOOK_SECRET must be nonblank')
|
|
1806
2014
|
|
|
1807
|
-
return
|
|
2015
|
+
return handleSignedWebhook(request, handler, {
|
|
1808
2016
|
secret,
|
|
1809
2017
|
})
|
|
1810
2018
|
}
|
|
1811
2019
|
|
|
1812
2020
|
// Signed deliveries include x-webhook-signature, x-webhook-timestamp,
|
|
1813
|
-
// and x-webhook-delivery-id.
|
|
1814
|
-
//
|
|
2021
|
+
// and x-webhook-delivery-id. handleSignedWebhook rejects stale, invalid,
|
|
2022
|
+
// or unsigned deliveries.
|
|
2023
|
+
```
|
|
2024
|
+
|
|
2025
|
+
`handleSignedWebhook()` requires `secret` at the type boundary and also checks
|
|
2026
|
+
it at runtime. A missing, empty, or whitespace-only secret returns a
|
|
2027
|
+
configuration `500` before the request body or handler is processed. Missing or
|
|
2028
|
+
invalid signature metadata returns `401`. The deprecated `handleWebhook()` name
|
|
2029
|
+
remains as a signed compatibility alias with the same fail-closed behavior; it
|
|
2030
|
+
no longer selects unsigned mode when the secret is absent.
|
|
2031
|
+
|
|
2032
|
+
Only endpoints intentionally configured without a signing secret should use the
|
|
2033
|
+
explicit unsigned receiver. It performs no signature authentication:
|
|
2034
|
+
|
|
2035
|
+
```typescript
|
|
2036
|
+
import { handleUnsignedWebhook } from '@01.software/sdk/webhook'
|
|
2037
|
+
|
|
2038
|
+
export async function POST(request: Request) {
|
|
2039
|
+
return handleUnsignedWebhook(request, handler)
|
|
2040
|
+
}
|
|
1815
2041
|
```
|
|
1816
2042
|
|
|
1817
2043
|
```typescript
|
|
@@ -1837,18 +2063,18 @@ const customerAuthHandler = createCustomerAuthWebhookHandler({
|
|
|
1837
2063
|
// Use isOrderChangedWebhookEvent when you need to distinguish manual ordering
|
|
1838
2064
|
// from content field edits.
|
|
1839
2065
|
import {
|
|
1840
|
-
|
|
2066
|
+
handleSignedWebhook,
|
|
1841
2067
|
isOrderChangedWebhookEvent,
|
|
1842
2068
|
} from '@01.software/sdk/webhook'
|
|
1843
2069
|
|
|
1844
2070
|
function getWebhookSecret(): string {
|
|
1845
2071
|
const secret = process.env.WEBHOOK_SECRET
|
|
1846
|
-
if (!secret) throw new Error('WEBHOOK_SECRET
|
|
2072
|
+
if (!secret?.trim()) throw new Error('WEBHOOK_SECRET must be nonblank')
|
|
1847
2073
|
return secret
|
|
1848
2074
|
}
|
|
1849
2075
|
|
|
1850
2076
|
export async function POST(request: Request) {
|
|
1851
|
-
return
|
|
2077
|
+
return handleSignedWebhook(
|
|
1852
2078
|
request,
|
|
1853
2079
|
async (event) => {
|
|
1854
2080
|
if (isOrderChangedWebhookEvent(event)) {
|
|
@@ -1862,7 +2088,7 @@ export async function POST(request: Request) {
|
|
|
1862
2088
|
}
|
|
1863
2089
|
```
|
|
1864
2090
|
|
|
1865
|
-
Orderable
|
|
2091
|
+
Orderable Console drag operations are delivered as `operation: "update"`
|
|
1866
2092
|
with `eventType: "collection.orderChanged"`. For join ordering, the webhook
|
|
1867
2093
|
uses the public parent collection as `collection` and points `event.change.moved`
|
|
1868
2094
|
at the public moved entity when one exists, so handlers do not need to depend on
|
|
@@ -1879,7 +2105,7 @@ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECT
|
|
|
1879
2105
|
| Tenant | `tenant-metadata` |
|
|
1880
2106
|
| Products | `products`, `product-variants`, `product-options`, `product-option-values`, `product-categories`, `product-tags`, `product-collections`, `markets`, `brands` |
|
|
1881
2107
|
| Customers | `customer-profiles` |
|
|
1882
|
-
| Commerce | `
|
|
2108
|
+
| Commerce | `shipping-policies`, `shipping-zones` |
|
|
1883
2109
|
| Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
|
|
1884
2110
|
| Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
|
|
1885
2111
|
| Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
|
|
@@ -1889,8 +2115,9 @@ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECT
|
|
|
1889
2115
|
| Community | `reaction-types`, `post-categories`, `post-tags`, `customer-profile-lists` |
|
|
1890
2116
|
| Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
|
|
1891
2117
|
|
|
1892
|
-
Server-only collections include raw media/logo records,
|
|
1893
|
-
|
|
2118
|
+
Server-only collections include raw media/logo records, raw discount
|
|
2119
|
+
definitions (use `client.commerce.discounts.validate()` for browser validation),
|
|
2120
|
+
customer and order operational records, raw cart/cart-item records, form submissions, raw community documents
|
|
1894
2121
|
(`posts`, `comments`, `reactions`, `bookmarks`), live stream provider records,
|
|
1895
2122
|
Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
|
|
1896
2123
|
and moderation records. They remain available from
|