@01.software/sdk 0.49.0 → 0.56.1
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 +257 -1
- package/MIGRATION.md +56 -0
- package/README.md +269 -56
- package/dist/client.cjs +608 -190
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -6
- package/dist/client.d.ts +6 -6
- package/dist/client.js +608 -190
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-DnDt6EQz.d.ts → collection-client-Bmr53saa.d.ts} +6 -4
- package/dist/{collection-client-XfGLPuTf.d.cts → collection-client-DxuBDso_.d.cts} +6 -4
- package/dist/{const-CBba4lDo.d.cts → const-BKUqg96Q.d.ts} +5 -5
- package/dist/{const-DCNV5Kq7.d.ts → const-Cv1DCsp6.d.cts} +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 +1 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -3
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/{index-DUuIqUS8.d.ts → index-CGsT4AQ8.d.cts} +89 -10
- package/dist/{index-CbKyoEzy.d.cts → index-DFHLLqYu.d.ts} +89 -10
- package/dist/index.cjs +752 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -69
- package/dist/index.d.ts +12 -69
- package/dist/index.js +752 -436
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-MKbx5MuJ.d.cts → payload-types-Bk-duc5k.d.cts} +545 -141
- package/dist/{payload-types-MKbx5MuJ.d.ts → payload-types-Bk-duc5k.d.ts} +545 -141
- package/dist/query.cjs +108 -71
- 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 +108 -71
- 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 +502 -175
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +9 -8
- package/dist/server.d.ts +9 -8
- package/dist/server.js +502 -175
- package/dist/server.js.map +1 -1
- package/dist/storefront-cache.cjs +1 -0
- package/dist/storefront-cache.cjs.map +1 -1
- package/dist/storefront-cache.js +1 -0
- package/dist/storefront-cache.js.map +1 -1
- package/dist/{types-1UeOk1Uy.d.cts → types-CEK8uTvU.d.ts} +287 -30
- package/dist/{types-BKr_2avj.d.cts → types-CNdpj2fI.d.cts} +2 -2
- package/dist/{types-L_nh-ED7.d.ts → types-D4mjGFj4.d.ts} +2 -2
- package/dist/{types-Du4s5anw.d.ts → types-Dhk5NX7Z.d.cts} +287 -30
- 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 +135 -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 +135 -80
- package/dist/webhook.js.map +1 -1
- package/package.json +28 -6
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ 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,6 +95,54 @@ 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
|
|
@@ -196,6 +252,8 @@ honored only when `mode` is unset.
|
|
|
196
252
|
|
|
197
253
|
### Client
|
|
198
254
|
|
|
255
|
+
<!-- sdk-docs-sync-shaped-first: commerce.product.listingPage -->
|
|
256
|
+
|
|
199
257
|
```typescript
|
|
200
258
|
import { createClient } from '@01.software/sdk'
|
|
201
259
|
|
|
@@ -203,13 +261,16 @@ const client = createClient({
|
|
|
203
261
|
publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY,
|
|
204
262
|
})
|
|
205
263
|
|
|
206
|
-
//
|
|
207
|
-
const
|
|
264
|
+
// Fetch public-safe, card-ready catalog data through the shaped helper.
|
|
265
|
+
const page = await client.commerce.product.listingPage({
|
|
208
266
|
limit: 10,
|
|
209
|
-
select: { title: true, slug: true },
|
|
210
267
|
})
|
|
268
|
+
const cards = page.cards
|
|
211
269
|
```
|
|
212
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
|
+
|
|
213
274
|
### Server Client
|
|
214
275
|
|
|
215
276
|
```typescript
|
|
@@ -332,7 +393,7 @@ gallery item DTOs include title, description, content, gallery reference, and
|
|
|
332
393
|
image media. Tenant, metadata, draft status, and storage/provider internals are
|
|
333
394
|
omitted. Gallery item reads require either `gallerySlug` or `galleryId`; prefer
|
|
334
395
|
`gallerySlug` for storefront routes. The default gallery item sort is `manual`,
|
|
335
|
-
matching curator order from the
|
|
396
|
+
matching curator order from the Console.
|
|
336
397
|
|
|
337
398
|
Both helpers return Payload-style pagination (`docs`, `totalDocs`, `page`,
|
|
338
399
|
`limit`, etc.). `limit` is bounded server-side to `1..100`; invalid query,
|
|
@@ -718,8 +779,8 @@ Membership self-service is split by credential boundary:
|
|
|
718
779
|
- Browser/customer JWT clients can read the signed-in customer's current or
|
|
719
780
|
latest membership and request cancellation for active, trialing, or past-due
|
|
720
781
|
memberships.
|
|
721
|
-
- Server clients with a Secret Key
|
|
722
|
-
|
|
782
|
+
- Server clients with a Secret Key prepare a paid customer subscription before
|
|
783
|
+
the tenant's PSP executes the first charge.
|
|
723
784
|
|
|
724
785
|
```typescript
|
|
725
786
|
import { createClient } from '@01.software/sdk'
|
|
@@ -740,28 +801,71 @@ const server = createServerClient({
|
|
|
740
801
|
secretKey: process.env.SOFTWARE_SECRET_KEY!,
|
|
741
802
|
})
|
|
742
803
|
|
|
743
|
-
await server.memberships.subscribe({
|
|
804
|
+
const prepared = await server.memberships.subscribe({
|
|
744
805
|
planId: 'plan_123',
|
|
806
|
+
priceId: 'price_monthly_123',
|
|
745
807
|
customerId: 'customer_123',
|
|
746
808
|
customerRef: 'cus_provider_123',
|
|
747
|
-
pgPaymentId: 'pay_123',
|
|
748
|
-
amount: 9900,
|
|
749
|
-
currency: 'KRW',
|
|
750
809
|
subscribeIntentKey: 'checkout-session-123',
|
|
751
810
|
card: { last4: '4242', brand: 'visa' },
|
|
752
811
|
})
|
|
812
|
+
|
|
813
|
+
if (prepared.status === 'incomplete') {
|
|
814
|
+
await tenantPsp.charge({
|
|
815
|
+
idempotencyKey: prepared.firstCharge.idempotencyKey,
|
|
816
|
+
amount: prepared.firstCharge.amount,
|
|
817
|
+
currency: prepared.firstCharge.currency,
|
|
818
|
+
})
|
|
819
|
+
// Post the PSP outcome to Console's existing signed charge-result webhook.
|
|
820
|
+
}
|
|
753
821
|
```
|
|
754
822
|
|
|
755
823
|
`subscribeIntentKey` is sent as `X-Idempotency-Key`; it is not part of the JSON
|
|
756
|
-
body.
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
824
|
+
body. A paid response is `incomplete` and contains the immutable first-charge
|
|
825
|
+
instruction. The tenant charges its PSP with `firstCharge.idempotencyKey`, then
|
|
826
|
+
posts the signed outcome through the configured charge-result webhook. A trial
|
|
827
|
+
response is immediately `trialing` and has no `firstCharge` field. Before a paid
|
|
828
|
+
subscribe, configure an active delegated billing connection for
|
|
829
|
+
the tenant; otherwise Console returns `409 conflict` without lifecycle writes.
|
|
830
|
+
Do not start
|
|
831
|
+
the PSP payment at or after `expiresAt`; deduplicate PSP work with
|
|
832
|
+
`firstCharge.idempotencyKey`. A late paid result remains evidence but cannot
|
|
833
|
+
activate the Membership, so the tenant handles any refund outside this API. Replays use
|
|
834
|
+
the same plan, price, customer reference, and idempotency key while the first
|
|
835
|
+
charge is pending. A replay after settlement or `expiresAt` returns `409`
|
|
836
|
+
instead of a payment instruction.
|
|
761
837
|
`memberships.me()` and browser `memberships.cancel()` require the customer JWT
|
|
762
838
|
configured on the client. Refund webhook `refundedAmount` values are cumulative
|
|
763
839
|
charge totals, not per-event deltas.
|
|
764
840
|
|
|
841
|
+
### Source-aware customer groups (server only)
|
|
842
|
+
|
|
843
|
+
Manage manual Customer Group membership through the shaped server API. The
|
|
844
|
+
read result includes every current reason a customer qualifies, so revoking a
|
|
845
|
+
manual source does not hide a still-active Membership source.
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
const page = await server.customerGroups.listMembers({
|
|
849
|
+
groupId: '00000000-0000-4000-8000-000000000002',
|
|
850
|
+
limit: 100,
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
await server.customerGroups.grantMember({
|
|
854
|
+
groupId: '00000000-0000-4000-8000-000000000002',
|
|
855
|
+
customerId: '00000000-0000-4000-8000-000000000003',
|
|
856
|
+
reason: 'support-approved',
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
await server.customerGroups.revokeMember({
|
|
860
|
+
groupId: '00000000-0000-4000-8000-000000000002',
|
|
861
|
+
customerId: '00000000-0000-4000-8000-000000000003',
|
|
862
|
+
})
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Keep these calls in trusted server code. Do not mutate `customers.groups`
|
|
866
|
+
directly: after source-authority activation it is a read-compatible projection
|
|
867
|
+
and direct writes return `customer_groups_direct_write_retired`.
|
|
868
|
+
|
|
765
869
|
## Storefront performance defaults
|
|
766
870
|
|
|
767
871
|
- **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.
|
|
@@ -883,6 +987,20 @@ export default async function ProductPage({ params }) {
|
|
|
883
987
|
}
|
|
884
988
|
```
|
|
885
989
|
|
|
990
|
+
The SDK sends
|
|
991
|
+
`Accept: application/vnd.01software.portone-action-capabilities.v1+json` for
|
|
992
|
+
Standard create and browser/server PortOne retrieve. Hosted create and
|
|
993
|
+
browser/server cancel do not send it. It accepts either the negotiated exact action
|
|
994
|
+
or the released Console's exact action, normalizing the latter to
|
|
995
|
+
`cancelable: false` and `retryPolicy: 'same_action_only'`. Partial capability
|
|
996
|
+
fields, different literals, and unknown response fields still fail closed.
|
|
997
|
+
The Accept value must be that sole exact media type after optional surrounding
|
|
998
|
+
whitespace. Lists, wildcards, parameters, and q-values deliberately retain the
|
|
999
|
+
default projection; this makes either Console-first or SDK-first rollout safe.
|
|
1000
|
+
Direct HTTP create/retrieve responses use the vendor `Content-Type` only when
|
|
1001
|
+
the successful body actually contains the Standard capability action. Hosted,
|
|
1002
|
+
Toss, cancel, confirm, and error responses remain `application/json`.
|
|
1003
|
+
|
|
886
1004
|
## API
|
|
887
1005
|
|
|
888
1006
|
### Client Configuration
|
|
@@ -1383,8 +1501,16 @@ if (
|
|
|
1383
1501
|
standardPortOneSession.presentationStatus === 'actionable' &&
|
|
1384
1502
|
standardPortOneSession.nextAction.type === 'portone_sdk'
|
|
1385
1503
|
) {
|
|
1504
|
+
const { cancelable, retryPolicy, ...paymentRequest } =
|
|
1505
|
+
standardPortOneSession.nextAction
|
|
1506
|
+
|
|
1386
1507
|
// PortOne V2 requires its currency constant (`CURRENCY_KRW`), not ISO `KRW`.
|
|
1387
|
-
//
|
|
1508
|
+
// A Standard action cannot be canceled or replaced. After browser failure,
|
|
1509
|
+
// retry only this exact payment request and paymentId instead of calling
|
|
1510
|
+
// cancelPortOne() or creating a sibling PaymentSession.
|
|
1511
|
+
if (!cancelable && retryPolicy === 'same_action_only') {
|
|
1512
|
+
// await PortOne.requestPayment(paymentRequest)
|
|
1513
|
+
}
|
|
1388
1514
|
// Always retrieve the session afterwards; Console lookup/webhook is authoritative.
|
|
1389
1515
|
}
|
|
1390
1516
|
|
|
@@ -1405,6 +1531,24 @@ await server.commerce.paymentSessions.retrievePortOne({
|
|
|
1405
1531
|
paymentSessionId: portOneSession.id,
|
|
1406
1532
|
})
|
|
1407
1533
|
|
|
1534
|
+
// Operator recovery can start from PortOne's canonical provider identity.
|
|
1535
|
+
// `findPortOne` is strictly local/read-only. `reconcilePortOne` is an explicit
|
|
1536
|
+
// mutation and must reuse one durable key for retries of the same operation;
|
|
1537
|
+
// its domain result and response complete atomically before delivery.
|
|
1538
|
+
const portOneIdentity = {
|
|
1539
|
+
provider: 'portone' as const,
|
|
1540
|
+
providerAccountKey: 'store-id-from-the-registered-connection',
|
|
1541
|
+
environment: 'test' as const,
|
|
1542
|
+
providerPaymentId: 'provider-assigned-payment-id',
|
|
1543
|
+
}
|
|
1544
|
+
const localPortOneState =
|
|
1545
|
+
await server.commerce.paymentSessions.findPortOne(portOneIdentity)
|
|
1546
|
+
const reconciledPortOneState =
|
|
1547
|
+
await server.commerce.paymentSessions.reconcilePortOne({
|
|
1548
|
+
...portOneIdentity,
|
|
1549
|
+
idempotencyKey: createCommerceIdempotencyKey(),
|
|
1550
|
+
})
|
|
1551
|
+
|
|
1408
1552
|
// Toss confirmation is a typed server-only adapter action. There is no generic
|
|
1409
1553
|
// `confirmVerified` or provider-paid Order creation escape hatch.
|
|
1410
1554
|
const confirmationIdempotencyKey = createCommerceIdempotencyKey()
|
|
@@ -1492,6 +1636,20 @@ if (recovered.finalizationStatus === 'committed') {
|
|
|
1492
1636
|
}
|
|
1493
1637
|
```
|
|
1494
1638
|
|
|
1639
|
+
### Server-only PortOne identity reconciliation
|
|
1640
|
+
|
|
1641
|
+
The PortOne identity facade is available only from `@01.software/sdk/server`.
|
|
1642
|
+
Both methods require the complete `provider`, `providerAccountKey`,
|
|
1643
|
+
`environment`, and `providerPaymentId` tuple; tenant identity is derived from
|
|
1644
|
+
the server credential and is not accepted in the body. `findPortOne` never
|
|
1645
|
+
calls the provider or mutates local state. `reconcilePortOne` requires write scope and a
|
|
1646
|
+
durable idempotency key, then delegates to the existing managed PortOne
|
|
1647
|
+
convergence engine. A retry after an interrupted response replays the first
|
|
1648
|
+
atomically completed result without repeating provider I/O. Its result distinguishes `applied`, `observed`,
|
|
1649
|
+
`recovered_after_ambiguous_result`, and `requires_attention`. `found: false` is
|
|
1650
|
+
the one HTTP/SDK not-found result; reconciliation does not claim or
|
|
1651
|
+
heuristically attach an unknown provider payment.
|
|
1652
|
+
|
|
1495
1653
|
`custom.retrieve` is an exact lookup by either `providerSessionId` or
|
|
1496
1654
|
`providerPaymentId` (exactly one). It returns only bounded recovery state:
|
|
1497
1655
|
provider identity, session/provider/finalization status, Money, and the resolved
|
|
@@ -1562,8 +1720,30 @@ The SDK root no longer exports raw generated `Order`, `Transaction`, `Cart`, or
|
|
|
1562
1720
|
`CartItem` Payload documents. Use the canonical `PublicOrder` / `PublicCart`
|
|
1563
1721
|
DTOs for storefront code and explicit server collection types only inside
|
|
1564
1722
|
trusted operational code. Order cancellation no longer returns the deprecated
|
|
1565
|
-
inline provider-refund branch
|
|
1566
|
-
|
|
1723
|
+
inline provider-refund branch. `refundPending` is a local financial marker, not
|
|
1724
|
+
authority for caller provider I/O:
|
|
1725
|
+
|
|
1726
|
+
| Captured payment lane | Provider refund owner | `resolveCancelRefund` |
|
|
1727
|
+
| ------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
|
|
1728
|
+
| Registered managed PaymentSession (Toss or PortOne) | Console only | Rejects `409 managed_refund_attestation_forbidden`; the pending marker is preserved |
|
|
1729
|
+
| Explicit `custom_bridge` capture | Trusted external integration | May report the already-completed provider outcome |
|
|
1730
|
+
| Legacy NULL-origin capture without a registered account | Bounded migration drain only | May report the already-completed provider outcome |
|
|
1731
|
+
|
|
1732
|
+
Do not infer the external lane from a defaulted `sessionOrigin`, an
|
|
1733
|
+
`orderCanceled` notification, or provider-observed cancellation. New managed
|
|
1734
|
+
captures must use Console's refund operation; `resolveCancelRefund` remains
|
|
1735
|
+
only for the two external-drain rows above.
|
|
1736
|
+
|
|
1737
|
+
Managed ownership follows the captured Transaction's exact tenant-bound
|
|
1738
|
+
PaymentSession and historical registered account. Order/provider/environment
|
|
1739
|
+
drift remains Console-owned corruption. Unresolved or cross-tenant identity
|
|
1740
|
+
fails closed opaquely as `payment_transaction_mismatch`; another session is
|
|
1741
|
+
never substitute evidence.
|
|
1742
|
+
|
|
1743
|
+
`managed_refund_attestation_forbidden` is the resolver's typed commerce error.
|
|
1744
|
+
Pre-existing resolver reasons remain generic SDK errors available through
|
|
1745
|
+
`getSDKErrorReason()` / `apiReason`, with legacy extra fields at the top level
|
|
1746
|
+
of the raw body in `error.details.body`.
|
|
1567
1747
|
|
|
1568
1748
|
```typescript
|
|
1569
1749
|
await server.commerce.orders.update({ orderNumber, status: 'confirmed' })
|
|
@@ -1628,7 +1808,7 @@ options, option values, and variants. It is the tenant-admin safe path because
|
|
|
1628
1808
|
it applies the product/option/variant transaction that raw collection writes do
|
|
1629
1809
|
not provide.
|
|
1630
1810
|
|
|
1631
|
-
`commerce.product.upsert()` is a graph-write API. For
|
|
1811
|
+
`commerce.product.upsert()` is a graph-write API. For Console product
|
|
1632
1812
|
documents, Payload saves product fields first; upsert receives `productId`,
|
|
1633
1813
|
`graphRevision` (required when updating an existing product via `productId` -
|
|
1634
1814
|
load from `GET /api/products/:id/composer-draft`), options, and variants. Do
|
|
@@ -1835,10 +2015,21 @@ await server.community.moderation.unbanCustomer({ customerId })
|
|
|
1835
2015
|
|
|
1836
2016
|
### Webhook
|
|
1837
2017
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2018
|
+
Webhooks created in Console start inactive. Use this setup order so no event is
|
|
2019
|
+
sent before the receiver can verify it:
|
|
2020
|
+
|
|
2021
|
+
1. Save the inactive endpoint in Console → Integrations → Webhooks.
|
|
2022
|
+
2. Use **Reveal signing secret** once and copy the plaintext value.
|
|
2023
|
+
3. Deploy the receiver with that value set as `WEBHOOK_SECRET`.
|
|
2024
|
+
4. Return to the saved endpoint and choose **Activate webhook**.
|
|
2025
|
+
|
|
2026
|
+
This inactive-first behavior is Console-only and changes no API default.
|
|
2027
|
+
Creating an endpoint through the API with `isActive` omitted still yields an
|
|
2028
|
+
active endpoint (`true`), and an explicit `isActive` value is honored — so send
|
|
2029
|
+
`isActive: false` for the same staged setup outside Console.
|
|
2030
|
+
|
|
2031
|
+
After initial setup, **Rotate secret** invalidates the previous secret and
|
|
2032
|
+
returns a new one-time plaintext value for receiver deployment.
|
|
1842
2033
|
|
|
1843
2034
|
`Webhook` list/read responses do not include plaintext signing secrets — only
|
|
1844
2035
|
the Console reveal/rotate flows return them. Use `WebhookWithoutSecret` and
|
|
@@ -1850,7 +2041,10 @@ secrets.
|
|
|
1850
2041
|
Use HMAC-SHA256 signature verification:
|
|
1851
2042
|
|
|
1852
2043
|
```typescript
|
|
1853
|
-
import {
|
|
2044
|
+
import {
|
|
2045
|
+
handleSignedWebhook,
|
|
2046
|
+
createTypedWebhookHandler,
|
|
2047
|
+
} from '@01.software/sdk/webhook'
|
|
1854
2048
|
|
|
1855
2049
|
const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
1856
2050
|
// event.data is typed as Order
|
|
@@ -1859,16 +2053,34 @@ const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
|
1859
2053
|
|
|
1860
2054
|
export async function POST(request: Request) {
|
|
1861
2055
|
const secret = process.env.WEBHOOK_SECRET
|
|
1862
|
-
if (!secret) throw new Error('WEBHOOK_SECRET
|
|
2056
|
+
if (!secret?.trim()) throw new Error('WEBHOOK_SECRET must be nonblank')
|
|
1863
2057
|
|
|
1864
|
-
return
|
|
2058
|
+
return handleSignedWebhook(request, handler, {
|
|
1865
2059
|
secret,
|
|
1866
2060
|
})
|
|
1867
2061
|
}
|
|
1868
2062
|
|
|
1869
2063
|
// Signed deliveries include x-webhook-signature, x-webhook-timestamp,
|
|
1870
|
-
// and x-webhook-delivery-id.
|
|
1871
|
-
//
|
|
2064
|
+
// and x-webhook-delivery-id. handleSignedWebhook rejects stale, invalid,
|
|
2065
|
+
// or unsigned deliveries.
|
|
2066
|
+
```
|
|
2067
|
+
|
|
2068
|
+
`handleSignedWebhook()` requires `secret` at the type boundary and also checks
|
|
2069
|
+
it at runtime. A missing, empty, or whitespace-only secret returns a
|
|
2070
|
+
configuration `500` before the request body or handler is processed. Missing or
|
|
2071
|
+
invalid signature metadata returns `401`. The deprecated `handleWebhook()` name
|
|
2072
|
+
remains as a signed compatibility alias with the same fail-closed behavior; it
|
|
2073
|
+
no longer selects unsigned mode when the secret is absent.
|
|
2074
|
+
|
|
2075
|
+
Only endpoints intentionally configured without a signing secret should use the
|
|
2076
|
+
explicit unsigned receiver. It performs no signature authentication:
|
|
2077
|
+
|
|
2078
|
+
```typescript
|
|
2079
|
+
import { handleUnsignedWebhook } from '@01.software/sdk/webhook'
|
|
2080
|
+
|
|
2081
|
+
export async function POST(request: Request) {
|
|
2082
|
+
return handleUnsignedWebhook(request, handler)
|
|
2083
|
+
}
|
|
1872
2084
|
```
|
|
1873
2085
|
|
|
1874
2086
|
```typescript
|
|
@@ -1894,18 +2106,18 @@ const customerAuthHandler = createCustomerAuthWebhookHandler({
|
|
|
1894
2106
|
// Use isOrderChangedWebhookEvent when you need to distinguish manual ordering
|
|
1895
2107
|
// from content field edits.
|
|
1896
2108
|
import {
|
|
1897
|
-
|
|
2109
|
+
handleSignedWebhook,
|
|
1898
2110
|
isOrderChangedWebhookEvent,
|
|
1899
2111
|
} from '@01.software/sdk/webhook'
|
|
1900
2112
|
|
|
1901
2113
|
function getWebhookSecret(): string {
|
|
1902
2114
|
const secret = process.env.WEBHOOK_SECRET
|
|
1903
|
-
if (!secret) throw new Error('WEBHOOK_SECRET
|
|
2115
|
+
if (!secret?.trim()) throw new Error('WEBHOOK_SECRET must be nonblank')
|
|
1904
2116
|
return secret
|
|
1905
2117
|
}
|
|
1906
2118
|
|
|
1907
2119
|
export async function POST(request: Request) {
|
|
1908
|
-
return
|
|
2120
|
+
return handleSignedWebhook(
|
|
1909
2121
|
request,
|
|
1910
2122
|
async (event) => {
|
|
1911
2123
|
if (isOrderChangedWebhookEvent(event)) {
|
|
@@ -1919,7 +2131,7 @@ export async function POST(request: Request) {
|
|
|
1919
2131
|
}
|
|
1920
2132
|
```
|
|
1921
2133
|
|
|
1922
|
-
Orderable
|
|
2134
|
+
Orderable Console drag operations are delivered as `operation: "update"`
|
|
1923
2135
|
with `eventType: "collection.orderChanged"`. For join ordering, the webhook
|
|
1924
2136
|
uses the public parent collection as `collection` and points `event.change.moved`
|
|
1925
2137
|
at the public moved entity when one exists, so handlers do not need to depend on
|
|
@@ -1936,7 +2148,7 @@ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECT
|
|
|
1936
2148
|
| Tenant | `tenant-metadata` |
|
|
1937
2149
|
| Products | `products`, `product-variants`, `product-options`, `product-option-values`, `product-categories`, `product-tags`, `product-collections`, `markets`, `brands` |
|
|
1938
2150
|
| Customers | `customer-profiles` |
|
|
1939
|
-
| Commerce | `
|
|
2151
|
+
| Commerce | `shipping-policies`, `shipping-zones` |
|
|
1940
2152
|
| Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
|
|
1941
2153
|
| Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
|
|
1942
2154
|
| Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
|
|
@@ -1946,8 +2158,9 @@ Source of truth: `packages/published/sdk/src/core/collection/const.ts` (`COLLECT
|
|
|
1946
2158
|
| Community | `reaction-types`, `post-categories`, `post-tags`, `customer-profile-lists` |
|
|
1947
2159
|
| Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
|
|
1948
2160
|
|
|
1949
|
-
Server-only collections include raw media/logo records,
|
|
1950
|
-
|
|
2161
|
+
Server-only collections include raw media/logo records, raw discount
|
|
2162
|
+
definitions (use `client.commerce.discounts.validate()` for browser validation),
|
|
2163
|
+
customer and order operational records, raw cart/cart-item records, form submissions, raw community documents
|
|
1951
2164
|
(`posts`, `comments`, `reactions`, `bookmarks`), live stream provider records,
|
|
1952
2165
|
Canvas graph shell rows (`canvas-nodes`, `canvas-edges`), segmentation records,
|
|
1953
2166
|
and moderation records. They remain available from
|