@sixtyninecommerce/admin 0.6.0 → 0.7.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/README.md +359 -2
- package/dist/apps/assertion.d.ts +30 -3
- package/dist/apps/assertion.js +40 -3
- package/dist/apps/config.d.ts +39 -0
- package/dist/apps/config.js +22 -0
- package/dist/apps/credential.d.ts +5 -0
- package/dist/apps/credential.js +55 -4
- package/dist/apps/index.d.ts +11 -0
- package/dist/client.d.ts +54 -1
- package/dist/client.js +34 -1
- package/dist/custom-objects/index.d.ts +66 -0
- package/dist/custom-objects/index.js +48 -0
- package/dist/customers/index.d.ts +225 -0
- package/dist/customers/index.js +279 -0
- package/dist/documents/index.d.ts +432 -0
- package/dist/documents/index.js +289 -0
- package/dist/index.d.ts +34 -2
- package/dist/index.js +16 -1
- package/dist/jobs/index.d.ts +143 -0
- package/dist/jobs/index.js +132 -0
- package/dist/metafields/index.d.ts +291 -0
- package/dist/metafields/index.js +141 -0
- package/dist/orders/index.d.ts +106 -0
- package/dist/orders/index.js +115 -0
- package/dist/orders/surcharges.d.ts +121 -0
- package/dist/orders/surcharges.js +110 -0
- package/dist/payments/index.d.ts +103 -0
- package/dist/payments/index.js +55 -0
- package/dist/payments/providers.d.ts +281 -0
- package/dist/payments/providers.js +205 -0
- package/dist/platform/provision-store.d.ts +9 -26
- package/dist/platform/provision-store.js +8 -66
- package/dist/tax/index.d.ts +128 -0
- package/dist/tax/index.js +101 -0
- package/dist/tenant/capabilities.d.ts +15 -0
- package/dist/types.d.ts +20 -3
- package/dist/uploads/index.d.ts +146 -0
- package/dist/uploads/index.js +72 -0
- package/package.json +3 -3
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The entities a metafield may hang off.
|
|
5
|
+
*
|
|
6
|
+
* `content_item` is in the app manifest's vocabulary and is deliberately NOT
|
|
7
|
+
* here: content rows live in the CMS, which already models tenant-defined
|
|
8
|
+
* fields, and commerce refuses a value written against it
|
|
9
|
+
* (`METAFIELD_OWNER_INVALID`). A manifest declaring one still registers; only
|
|
10
|
+
* the value surface says no.
|
|
11
|
+
*/
|
|
12
|
+
export declare const METAFIELD_OWNER_TYPES: readonly ["product", "variant", "customer", "order", "order_line", "location", "collection"];
|
|
13
|
+
export type MetafieldOwnerType = (typeof METAFIELD_OWNER_TYPES)[number];
|
|
14
|
+
/** Scalar value types. `list<T>` of any of them is also a type. */
|
|
15
|
+
export declare const METAFIELD_SCALAR_TYPES: readonly ["text", "number", "money", "boolean", "date", "json", "reference", "attachment"];
|
|
16
|
+
export type MetafieldScalarType = (typeof METAFIELD_SCALAR_TYPES)[number];
|
|
17
|
+
export type MetafieldType = MetafieldScalarType | `list<${MetafieldScalarType}>`;
|
|
18
|
+
/** §8.4's attachment: opaque to commerce, rendered as a reference on documents. */
|
|
19
|
+
export interface MetafieldAttachment {
|
|
20
|
+
kind: string;
|
|
21
|
+
ref: string;
|
|
22
|
+
label?: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** A `reference` value. The platform does not resolve or fetch it. */
|
|
25
|
+
export interface MetafieldReference {
|
|
26
|
+
entityType: string;
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A `money` value is a bare INTEGER in minor units — `1999` is 19.99 — never a
|
|
31
|
+
* decimal, and it carries no currency of its own, exactly as the app-manifest
|
|
32
|
+
* schema describes it. A channel may sell in several currencies, so an app
|
|
33
|
+
* storing money on an entity that has no currency of its own declares a
|
|
34
|
+
* separate field for the currency beside it.
|
|
35
|
+
*
|
|
36
|
+
* `min` / `max` in the definition's `validation` are minor units too, and the
|
|
37
|
+
* engine refuses a decimal bound.
|
|
38
|
+
*/
|
|
39
|
+
export type MetafieldMoney = number;
|
|
40
|
+
/**
|
|
41
|
+
* What a merchant, an app and a storefront may each do with a field.
|
|
42
|
+
*
|
|
43
|
+
* **Visibility is not authority.** `app` is always `read_write`; whether an app
|
|
44
|
+
* may actually write is decided by namespace ownership — its own slug, never
|
|
45
|
+
* another app's and never `platform`.
|
|
46
|
+
*/
|
|
47
|
+
export interface MetafieldVisibility {
|
|
48
|
+
app: 'read_write';
|
|
49
|
+
/** `edit` is the ONLY value that lets the tenant write the field. */
|
|
50
|
+
merchant: 'hidden' | 'read' | 'edit';
|
|
51
|
+
/** `read` is what the Shop API projects; `hidden` keeps it back-office. */
|
|
52
|
+
storefront: 'hidden' | 'read';
|
|
53
|
+
}
|
|
54
|
+
/** One stored value. */
|
|
55
|
+
export interface Metafield {
|
|
56
|
+
ownerType: MetafieldOwnerType;
|
|
57
|
+
ownerId: string;
|
|
58
|
+
/** The declaring app's slug, or `platform` for the built-ins of §8.3/§8.4. */
|
|
59
|
+
namespace: string;
|
|
60
|
+
key: string;
|
|
61
|
+
/** Shaped by the definition's `type`. */
|
|
62
|
+
value: unknown;
|
|
63
|
+
/** ISO-8601 instant. */
|
|
64
|
+
updatedAt: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What MAY be stored on an entity.
|
|
68
|
+
*
|
|
69
|
+
* Three kinds. A definition this STORE registered (`definitions.upsert`, `id`
|
|
70
|
+
* set and `appId` null). A `platform` built-in of §8.3/§8.4, which is code
|
|
71
|
+
* rather than a row (`id` and `appId` both null, present on every store). And an
|
|
72
|
+
* APP's, provisioned from its manifest when the app is installed and on every
|
|
73
|
+
* consent approval (`appId` set) — so `appId` is how you tell an app's field
|
|
74
|
+
* from your own, which is the distinction the engine enforces: only a store's
|
|
75
|
+
* own definitions are editable and removable through this module.
|
|
76
|
+
*
|
|
77
|
+
* `active` goes false when the declaring app withdraws the field or is
|
|
78
|
+
* uninstalled. The VALUES are kept for a redaction window, so the definition
|
|
79
|
+
* still appears here and still describes them, but nothing new can be written
|
|
80
|
+
* under it and the Shop API stops publishing it at once. Withdrawing also ENDS
|
|
81
|
+
* the merchant's consent to that field: declaring it again asks for consent
|
|
82
|
+
* afresh, however the new declaration compares with the inactive row.
|
|
83
|
+
*/
|
|
84
|
+
export interface MetafieldDefinition {
|
|
85
|
+
/** Null for a platform built-in, which is code rather than a row. */
|
|
86
|
+
id: string | null;
|
|
87
|
+
/** The declaring app, or null for a platform built-in. */
|
|
88
|
+
appId: string | null;
|
|
89
|
+
ownerType: MetafieldOwnerType;
|
|
90
|
+
namespace: string;
|
|
91
|
+
key: string;
|
|
92
|
+
type: MetafieldType;
|
|
93
|
+
/** The rules values are checked against; null when the app declared none. */
|
|
94
|
+
validation: Record<string, unknown> | null;
|
|
95
|
+
visibility: MetafieldVisibility;
|
|
96
|
+
/**
|
|
97
|
+
* Declared and stored, read by nothing yet. §8.13 promises admin filters and
|
|
98
|
+
* Meilisearch facets on an indexed field; neither exists, so treat this as
|
|
99
|
+
* the app's intent rather than a capability.
|
|
100
|
+
*/
|
|
101
|
+
indexed: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* False once the declaring app was uninstalled, or stopped declaring the
|
|
104
|
+
* field. Existing VALUES are kept — §6.9 gives an uninstalled app a 48 h
|
|
105
|
+
* redaction window — but no new value can be written.
|
|
106
|
+
*/
|
|
107
|
+
active: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface SetMetafieldInput {
|
|
110
|
+
ownerType: MetafieldOwnerType;
|
|
111
|
+
ownerId: string;
|
|
112
|
+
/** Your own namespace. An app's is its slug; `platform` is never writable. */
|
|
113
|
+
namespace: string;
|
|
114
|
+
/** Lower snake_case, as the manifest declared it. */
|
|
115
|
+
key: string;
|
|
116
|
+
value: unknown;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A definition this store registers for itself. Whole-record, not a patch: an
|
|
120
|
+
* omitted `validation` clears it.
|
|
121
|
+
*/
|
|
122
|
+
export interface UpsertMetafieldDefinitionInput {
|
|
123
|
+
ownerType: MetafieldOwnerType;
|
|
124
|
+
/**
|
|
125
|
+
* Lower snake_case. Never `platform`, and never the namespace of an app
|
|
126
|
+
* installed on this store — both answer `METAFIELD_NAMESPACE_FORBIDDEN`.
|
|
127
|
+
*/
|
|
128
|
+
namespace: string;
|
|
129
|
+
/** Lower snake_case. */
|
|
130
|
+
key: string;
|
|
131
|
+
type: MetafieldType;
|
|
132
|
+
validation?: Record<string, unknown> | null;
|
|
133
|
+
/**
|
|
134
|
+
* How the field is PRESENTED, not who owns it: a definition your store
|
|
135
|
+
* registered stays writable AND readable by your store at any `merchant`
|
|
136
|
+
* setting. The flag governs an APP's fields, where `edit` is what lets the
|
|
137
|
+
* merchant write one at all and `hidden` keeps it out of `definitions.list`
|
|
138
|
+
* and `get`.
|
|
139
|
+
*/
|
|
140
|
+
visibility: {
|
|
141
|
+
merchant: 'hidden' | 'read' | 'edit';
|
|
142
|
+
storefront: 'hidden' | 'read';
|
|
143
|
+
};
|
|
144
|
+
/** Stored; no search or facet reads it yet. */
|
|
145
|
+
indexed?: boolean;
|
|
146
|
+
}
|
|
147
|
+
export interface MetafieldDefinitionsModule {
|
|
148
|
+
/**
|
|
149
|
+
* Every definition this store has, including the platform built-ins.
|
|
150
|
+
*
|
|
151
|
+
* A `Page` because every list in this SDK is one (spec §9.2), not because
|
|
152
|
+
* this query pages: the Admin API answers the complete array, so the page is
|
|
153
|
+
* always the last one.
|
|
154
|
+
*
|
|
155
|
+
* Filtered by what the CALLER may see, on BOTH axes: an app gets its own
|
|
156
|
+
* namespace and the platform built-ins while a human or an API token gets
|
|
157
|
+
* everything but `merchant: hidden`, and either way a definition only appears
|
|
158
|
+
* when the caller holds its owner entity's READ permission — so a principal
|
|
159
|
+
* that cannot read customers never learns which customer fields an app keeps.
|
|
160
|
+
*/
|
|
161
|
+
list(options?: {
|
|
162
|
+
ownerType?: MetafieldOwnerType;
|
|
163
|
+
} & Call): Promise<Page<MetafieldDefinition>>;
|
|
164
|
+
/**
|
|
165
|
+
* Register or replace one of THIS store's own definitions.
|
|
166
|
+
*
|
|
167
|
+
* Needs the owner entity's own WRITE permission — `UpdateOrder` to declare a
|
|
168
|
+
* field on orders, `UpdateCatalog` on products — so nothing has to be granted
|
|
169
|
+
* before a store can use it. Re-registering a key that was withdrawn revives
|
|
170
|
+
* it, with the values it still holds.
|
|
171
|
+
*
|
|
172
|
+
* An APP cannot call this: an app declares its fields in its manifest, which
|
|
173
|
+
* is what the merchant consents to (`METAFIELD_NAMESPACE_FORBIDDEN`).
|
|
174
|
+
*
|
|
175
|
+
* Two refusals that are easy to trip over: changing a definition's `type`
|
|
176
|
+
* while values exist (`METAFIELD_DEFINITION_IN_USE` — nothing re-validates or
|
|
177
|
+
* migrates them, so clear them first), and a `validation.pattern` that can
|
|
178
|
+
* backtrack catastrophically such as `^(a+)+$` (`METAFIELD_INVALID` — the
|
|
179
|
+
* regex runs on the engine every store shares).
|
|
180
|
+
*/
|
|
181
|
+
upsert(input: UpsertMetafieldDefinitionInput, options?: Call): Promise<MetafieldDefinition>;
|
|
182
|
+
/**
|
|
183
|
+
* Remove one of this store's own definitions. True when one was removed,
|
|
184
|
+
* false when there was none.
|
|
185
|
+
*
|
|
186
|
+
* Refused while values still exist under it
|
|
187
|
+
* (`METAFIELD_DEFINITION_IN_USE` → `BusinessRuleError`): a value whose
|
|
188
|
+
* definition is gone can never be read back. Clear them with
|
|
189
|
+
* {@link MetafieldsModule.delete} first — which reaches them even on a
|
|
190
|
+
* deleted product or customer. An app's declaration and a `platform` built-in
|
|
191
|
+
* are never removable here.
|
|
192
|
+
*/
|
|
193
|
+
delete(ownerType: MetafieldOwnerType, namespace: string, key: string, options?: Call): Promise<boolean>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* App and platform custom data on core entities (spec §8.13, §8.4).
|
|
197
|
+
*
|
|
198
|
+
* **Nothing undeclared is stored.** A value needs a definition — one this store
|
|
199
|
+
* registered (`definitions.upsert`) or a `platform` built-in of §8.3/§8.4. An
|
|
200
|
+
* unknown `(ownerType, namespace, key)` is `METAFIELD_DEFINITION_NOT_FOUND`
|
|
201
|
+
* (→ `NotFoundError`).
|
|
202
|
+
*
|
|
203
|
+
* **An app's declarations arrive with consent.** They are provisioned when the
|
|
204
|
+
* app is installed and on every consent approval. A re-registration applies at
|
|
205
|
+
* once what only NARROWS an active field, and withdraws one at once; it holds
|
|
206
|
+
* back what widens an active field, what is brand new, and — because a
|
|
207
|
+
* withdrawal ends consent — anything declared again over an INACTIVE row, even
|
|
208
|
+
* a narrowing. So a field an app has asked to make more visible keeps its old
|
|
209
|
+
* visibility, a field it has withdrawn stops being published without waiting
|
|
210
|
+
* for anyone, and a field it withdrew and wants back waits for the merchant
|
|
211
|
+
* exactly as a new one does.
|
|
212
|
+
*
|
|
213
|
+
* **Who may write what** (`METAFIELD_NAMESPACE_FORBIDDEN` /
|
|
214
|
+
* `METAFIELD_EDIT_FORBIDDEN`, both → `PermissionDeniedError`):
|
|
215
|
+
*
|
|
216
|
+
* | Principal | May write |
|
|
217
|
+
* |---|---|
|
|
218
|
+
* | an app installation | its OWN namespace (its slug), whatever the visibility says |
|
|
219
|
+
* | a human session or an API token | a definition whose `visibility.merchant` is `edit` |
|
|
220
|
+
* | anybody | never the `platform` namespace — the platform writes its own built-ins |
|
|
221
|
+
*
|
|
222
|
+
* Reading needs the owner entity's own READ permission and writing its own
|
|
223
|
+
* WRITE permission (`UpdateOrder` for an order and its lines, `UpdateCatalog`
|
|
224
|
+
* for a product, and so on). There is no separate metafield permission, so
|
|
225
|
+
* nothing has to be granted before this works.
|
|
226
|
+
*
|
|
227
|
+
* **Everything is per channel.** The owner must belong to the client's tenant
|
|
228
|
+
* (`METAFIELD_OWNER_NOT_FOUND` → `NotFoundError`, the same answer for an id
|
|
229
|
+
* that belongs to another store and one that belongs to nobody), and the value
|
|
230
|
+
* is stored against that channel — a product shared by two stores holds two
|
|
231
|
+
* independent values.
|
|
232
|
+
*
|
|
233
|
+
* Other coded refusals: `METAFIELD_INVALID` (→ `ValidationError`) when the
|
|
234
|
+
* value does not fit the definition's `type` and `validation`, and
|
|
235
|
+
* `METAFIELD_OWNER_INVALID` (→ `ValidationError`) for an owner commerce does
|
|
236
|
+
* not store.
|
|
237
|
+
*
|
|
238
|
+
* **What a storefront sees** (Shop API, server-side in this release): only
|
|
239
|
+
* `visibility.storefront = read` definitions, only on `product`, `variant` and
|
|
240
|
+
* `collection`, and only while the owner itself is public — a disabled product,
|
|
241
|
+
* a variant of a disabled product and a private collection all answer as if
|
|
242
|
+
* they did not exist.
|
|
243
|
+
*/
|
|
244
|
+
export interface MetafieldsModule {
|
|
245
|
+
/**
|
|
246
|
+
* Every value on one entity that the caller may see.
|
|
247
|
+
*
|
|
248
|
+
* Not a `Page`: this is the set of fields attached to ONE record — bounded by
|
|
249
|
+
* the definitions the store has declared — the way an entity's own columns
|
|
250
|
+
* are, not a collection to walk.
|
|
251
|
+
*/
|
|
252
|
+
get(ownerType: MetafieldOwnerType, ownerId: string, options?: {
|
|
253
|
+
namespace?: string;
|
|
254
|
+
} & Call): Promise<Metafield[]>;
|
|
255
|
+
/**
|
|
256
|
+
* Write one value. Replaces whatever was there; there is no partial merge.
|
|
257
|
+
*
|
|
258
|
+
* The value must fit the definition's `type`: a `money` is a bare integer in
|
|
259
|
+
* minor units, a `date` is an ISO-8601 instant with an explicit offset (and a
|
|
260
|
+
* calendar date that exists — `2026-02-30` is refused rather than rolled over
|
|
261
|
+
* to March), an `attachment` is exactly `{ kind, ref, label? }` and a
|
|
262
|
+
* `reference` exactly `{ entityType, id }`.
|
|
263
|
+
*
|
|
264
|
+
* To CLEAR a field use {@link delete} — `null` is refused, because a stored
|
|
265
|
+
* JSON `null` and an absent row cannot be told apart afterwards.
|
|
266
|
+
*
|
|
267
|
+
* `options.idempotencyKey` is inert on this surface today: the Admin API has
|
|
268
|
+
* no idempotency header yet, so a repeat is a second write. It is harmless to
|
|
269
|
+
* pass and starts being honoured when the engine grows the store.
|
|
270
|
+
*/
|
|
271
|
+
set(input: SetMetafieldInput, options?: Call): Promise<Metafield>;
|
|
272
|
+
/**
|
|
273
|
+
* Remove a value. True when one was removed, false when there was none.
|
|
274
|
+
*
|
|
275
|
+
* This is the CLEANUP route, so it is deliberately more permissive than
|
|
276
|
+
* `set`: it works on a withdrawn definition, on a definition that is gone
|
|
277
|
+
* entirely, and on a soft-deleted owner — a deleted product's or customer's
|
|
278
|
+
* values are exactly what a GDPR erasure has to reach. Only WRITING needs a
|
|
279
|
+
* live declaration and a live owner. Namespace ownership is not relaxed: an
|
|
280
|
+
* app still clears only its own namespace.
|
|
281
|
+
*/
|
|
282
|
+
delete(ownerType: MetafieldOwnerType, ownerId: string, namespace: string, key: string, options?: Call): Promise<boolean>;
|
|
283
|
+
definitions: MetafieldDefinitionsModule;
|
|
284
|
+
}
|
|
285
|
+
export declare const METAFIELDS_QUERY = "\n query Metafields($ownerType: String!, $ownerId: ID!, $namespace: String) {\n metafields(ownerType: $ownerType, ownerId: $ownerId, namespace: $namespace) {\n \n ownerType\n ownerId\n namespace\n key\n value\n updatedAt\n\n }\n }\n";
|
|
286
|
+
export declare const UPSERT_METAFIELD_DEFINITION_MUTATION = "\n mutation UpsertMetafieldDefinition($input: UpsertMetafieldDefinitionInput!) {\n upsertMetafieldDefinition(input: $input) { \n id\n appId\n ownerType\n namespace\n key\n type\n validation\n visibility {\n app\n merchant\n storefront\n }\n indexed\n active\n }\n }\n";
|
|
287
|
+
export declare const DELETE_METAFIELD_DEFINITION_MUTATION = "\n mutation DeleteMetafieldDefinition($ownerType: String!, $namespace: String!, $key: String!) {\n deleteMetafieldDefinition(ownerType: $ownerType, namespace: $namespace, key: $key)\n }\n";
|
|
288
|
+
export declare const METAFIELD_DEFINITIONS_QUERY = "\n query MetafieldDefinitions($ownerType: String) {\n metafieldDefinitions(ownerType: $ownerType) { \n id\n appId\n ownerType\n namespace\n key\n type\n validation\n visibility {\n app\n merchant\n storefront\n }\n indexed\n active\n }\n }\n";
|
|
289
|
+
export declare const SET_METAFIELD_MUTATION = "\n mutation SetMetafield($input: SetMetafieldInput!) {\n setMetafield(input: $input) { \n ownerType\n ownerId\n namespace\n key\n value\n updatedAt\n }\n }\n";
|
|
290
|
+
export declare const DELETE_METAFIELD_MUTATION = "\n mutation DeleteMetafield($ownerType: String!, $ownerId: ID!, $namespace: String!, $key: String!) {\n deleteMetafield(ownerType: $ownerType, ownerId: $ownerId, namespace: $namespace, key: $key)\n }\n";
|
|
291
|
+
export declare function createMetafieldsModule(transport: Transport): MetafieldsModule;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createPage } from '@sixtyninecommerce/core';
|
|
2
|
+
/**
|
|
3
|
+
* The entities a metafield may hang off.
|
|
4
|
+
*
|
|
5
|
+
* `content_item` is in the app manifest's vocabulary and is deliberately NOT
|
|
6
|
+
* here: content rows live in the CMS, which already models tenant-defined
|
|
7
|
+
* fields, and commerce refuses a value written against it
|
|
8
|
+
* (`METAFIELD_OWNER_INVALID`). A manifest declaring one still registers; only
|
|
9
|
+
* the value surface says no.
|
|
10
|
+
*/
|
|
11
|
+
export const METAFIELD_OWNER_TYPES = [
|
|
12
|
+
'product',
|
|
13
|
+
'variant',
|
|
14
|
+
'customer',
|
|
15
|
+
'order',
|
|
16
|
+
'order_line',
|
|
17
|
+
'location',
|
|
18
|
+
'collection',
|
|
19
|
+
];
|
|
20
|
+
/** Scalar value types. `list<T>` of any of them is also a type. */
|
|
21
|
+
export const METAFIELD_SCALAR_TYPES = [
|
|
22
|
+
'text',
|
|
23
|
+
'number',
|
|
24
|
+
'money',
|
|
25
|
+
'boolean',
|
|
26
|
+
'date',
|
|
27
|
+
'json',
|
|
28
|
+
'reference',
|
|
29
|
+
'attachment',
|
|
30
|
+
];
|
|
31
|
+
const METAFIELD_FIELDS = /* GraphQL */ `
|
|
32
|
+
ownerType
|
|
33
|
+
ownerId
|
|
34
|
+
namespace
|
|
35
|
+
key
|
|
36
|
+
value
|
|
37
|
+
updatedAt
|
|
38
|
+
`;
|
|
39
|
+
const METAFIELD_DEFINITION_FIELDS = /* GraphQL */ `
|
|
40
|
+
id
|
|
41
|
+
appId
|
|
42
|
+
ownerType
|
|
43
|
+
namespace
|
|
44
|
+
key
|
|
45
|
+
type
|
|
46
|
+
validation
|
|
47
|
+
visibility {
|
|
48
|
+
app
|
|
49
|
+
merchant
|
|
50
|
+
storefront
|
|
51
|
+
}
|
|
52
|
+
indexed
|
|
53
|
+
active
|
|
54
|
+
`;
|
|
55
|
+
export const METAFIELDS_QUERY = /* GraphQL */ `
|
|
56
|
+
query Metafields($ownerType: String!, $ownerId: ID!, $namespace: String) {
|
|
57
|
+
metafields(ownerType: $ownerType, ownerId: $ownerId, namespace: $namespace) {
|
|
58
|
+
${METAFIELD_FIELDS}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
export const UPSERT_METAFIELD_DEFINITION_MUTATION = /* GraphQL */ `
|
|
63
|
+
mutation UpsertMetafieldDefinition($input: UpsertMetafieldDefinitionInput!) {
|
|
64
|
+
upsertMetafieldDefinition(input: $input) { ${METAFIELD_DEFINITION_FIELDS} }
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
export const DELETE_METAFIELD_DEFINITION_MUTATION = /* GraphQL */ `
|
|
68
|
+
mutation DeleteMetafieldDefinition($ownerType: String!, $namespace: String!, $key: String!) {
|
|
69
|
+
deleteMetafieldDefinition(ownerType: $ownerType, namespace: $namespace, key: $key)
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
export const METAFIELD_DEFINITIONS_QUERY = /* GraphQL */ `
|
|
73
|
+
query MetafieldDefinitions($ownerType: String) {
|
|
74
|
+
metafieldDefinitions(ownerType: $ownerType) { ${METAFIELD_DEFINITION_FIELDS} }
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
export const SET_METAFIELD_MUTATION = /* GraphQL */ `
|
|
78
|
+
mutation SetMetafield($input: SetMetafieldInput!) {
|
|
79
|
+
setMetafield(input: $input) { ${METAFIELD_FIELDS} }
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
export const DELETE_METAFIELD_MUTATION = /* GraphQL */ `
|
|
83
|
+
mutation DeleteMetafield($ownerType: String!, $ownerId: ID!, $namespace: String!, $key: String!) {
|
|
84
|
+
deleteMetafield(ownerType: $ownerType, ownerId: $ownerId, namespace: $namespace, key: $key)
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
export function createMetafieldsModule(transport) {
|
|
88
|
+
return {
|
|
89
|
+
async get(ownerType, ownerId, options) {
|
|
90
|
+
const { namespace, ...call } = options ?? {};
|
|
91
|
+
const data = await transport.graphql({
|
|
92
|
+
api: 'admin',
|
|
93
|
+
query: METAFIELDS_QUERY,
|
|
94
|
+
// `null`, not omitted: the argument is nullable and "every namespace"
|
|
95
|
+
// is what null means to the server.
|
|
96
|
+
variables: { ownerType, ownerId, namespace: namespace ?? null },
|
|
97
|
+
}, call);
|
|
98
|
+
return data.metafields;
|
|
99
|
+
},
|
|
100
|
+
async set(input, options) {
|
|
101
|
+
const data = await transport.graphql({ api: 'admin', query: SET_METAFIELD_MUTATION, variables: { input } }, options);
|
|
102
|
+
return data.setMetafield;
|
|
103
|
+
},
|
|
104
|
+
async delete(ownerType, ownerId, namespace, key, options) {
|
|
105
|
+
const data = await transport.graphql({
|
|
106
|
+
api: 'admin',
|
|
107
|
+
query: DELETE_METAFIELD_MUTATION,
|
|
108
|
+
variables: { ownerType, ownerId, namespace, key },
|
|
109
|
+
}, options);
|
|
110
|
+
return data.deleteMetafield;
|
|
111
|
+
},
|
|
112
|
+
definitions: {
|
|
113
|
+
async list(options) {
|
|
114
|
+
const { ownerType, ...call } = options ?? {};
|
|
115
|
+
const data = await transport.graphql({
|
|
116
|
+
api: 'admin',
|
|
117
|
+
query: METAFIELD_DEFINITIONS_QUERY,
|
|
118
|
+
variables: { ownerType: ownerType ?? null },
|
|
119
|
+
}, call);
|
|
120
|
+
const items = data.metafieldDefinitions;
|
|
121
|
+
return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
|
|
122
|
+
// Unreachable while `hasNext` is false — `Page.next()` short-circuits
|
|
123
|
+
// — and a rejection rather than a silent re-fetch if that changes.
|
|
124
|
+
throw new Error('metafieldDefinitions returns a complete list; there is no next page');
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async upsert(input, options) {
|
|
128
|
+
const data = await transport.graphql({ api: 'admin', query: UPSERT_METAFIELD_DEFINITION_MUTATION, variables: { input } }, options);
|
|
129
|
+
return data.upsertMetafieldDefinition;
|
|
130
|
+
},
|
|
131
|
+
async delete(ownerType, namespace, key, options) {
|
|
132
|
+
const data = await transport.graphql({
|
|
133
|
+
api: 'admin',
|
|
134
|
+
query: DELETE_METAFIELD_DEFINITION_MUTATION,
|
|
135
|
+
variables: { ownerType, namespace, key },
|
|
136
|
+
}, options);
|
|
137
|
+
return data.deleteMetafieldDefinition;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { OrderPaymentPosition, PaidOrder } from '../payments/index.js';
|
|
3
|
+
import type { Call } from '../types.js';
|
|
4
|
+
import { type SurchargesModule } from './surcharges.js';
|
|
5
|
+
export type { PaidOrder } from '../payments/index.js';
|
|
6
|
+
/** The result of settling an order. */
|
|
7
|
+
export interface OrderSettlement {
|
|
8
|
+
order: PaidOrder;
|
|
9
|
+
position: OrderPaymentPosition;
|
|
10
|
+
/**
|
|
11
|
+
* What you asked for. Settling does NOT create a fulfilment: choosing a
|
|
12
|
+
* handler, a method and lines for a merchant is not the platform's decision
|
|
13
|
+
* to make, so the flag is reported back rather than acted on.
|
|
14
|
+
*/
|
|
15
|
+
fulfilRequested: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Orders (spec §8.2, §12.2).
|
|
19
|
+
*
|
|
20
|
+
* `surcharges` is W5-1's module, unchanged. The three methods beside it are the
|
|
21
|
+
* deposit flow: hold an order while the rest of the money is arranged, read
|
|
22
|
+
* what it stands at, and settle it once the money is really there.
|
|
23
|
+
*/
|
|
24
|
+
export interface OrdersModule {
|
|
25
|
+
surcharges: SurchargesModule;
|
|
26
|
+
/**
|
|
27
|
+
* Hold a placed order while the rest of its money is arranged. Needs
|
|
28
|
+
* `UpdateOrder`.
|
|
29
|
+
*
|
|
30
|
+
* Reachable only from `ArrangingPayment`, and only once something has been
|
|
31
|
+
* paid — unless the store's deposit policy allows a zero-deposit park, which
|
|
32
|
+
* is off unless a merchant chose it. Refusals carry the order's position:
|
|
33
|
+
* `DEPOSITS_NOT_ENABLED` (the store has not turned deposits on — the default,
|
|
34
|
+
* so this is the first one you will meet), `DEPOSIT_REQUIRED` (nothing paid,
|
|
35
|
+
* and the store does not allow that), `DEPOSIT_BELOW_MINIMUM`,
|
|
36
|
+
* `ORDER_NOT_PARKABLE` (wrong state), `PARK_REASON_TOO_LONG` (500
|
|
37
|
+
* characters).
|
|
38
|
+
*
|
|
39
|
+
* Parking does not RESERVE stock: the platform allocates at the payment edge,
|
|
40
|
+
* so an order parked while arranging payment usually holds no allocations
|
|
41
|
+
* yet. What it protects is the ORDER — the stock-allocation sweeper leaves a
|
|
42
|
+
* parked order alone deliberately, so an expiring allocation TTL cannot
|
|
43
|
+
* cancel an order a customer has already paid a deposit on, and allocations
|
|
44
|
+
* it does hold stay put while it waits.
|
|
45
|
+
*/
|
|
46
|
+
park(orderId: string, options?: Call & {
|
|
47
|
+
reason?: string;
|
|
48
|
+
}): Promise<PaidOrder>;
|
|
49
|
+
/**
|
|
50
|
+
* End a hold: hand a parked order back to the state it was parked FROM, and
|
|
51
|
+
* clear its park reason. Needs `UpdateOrder`.
|
|
52
|
+
*
|
|
53
|
+
* The target is read from the ORDER'S OWN HISTORY, never chosen by the
|
|
54
|
+
* caller and never assumed, so un-parking reverses exactly the move that
|
|
55
|
+
* parked it. `PARK_ORIGIN_UNKNOWN` is the refusal when that history does not
|
|
56
|
+
* say — move such an order on explicitly with a state transition instead.
|
|
57
|
+
*
|
|
58
|
+
* There is no storefront equivalent today: a parked order's remainder is
|
|
59
|
+
* recorded WHERE IT STANDS with `payments.record`, and the payment that
|
|
60
|
+
* clears it settles, places and allocates the order without ever leaving
|
|
61
|
+
* `Parked` first. Use this when the order has to be EDITED, which is the one
|
|
62
|
+
* thing it cannot do while held.
|
|
63
|
+
*
|
|
64
|
+
* It moves no money and changes no payment. Other refusals:
|
|
65
|
+
* `ORDER_NOT_PARKED`, `UNPARK_TRANSITION_REFUSED` (the origin was known and
|
|
66
|
+
* another guard in the order process refused the way back — an order
|
|
67
|
+
* minimum, a stock check — with its own reason). All carry the order's
|
|
68
|
+
* position.
|
|
69
|
+
*/
|
|
70
|
+
unpark(orderId: string, options?: Call): Promise<PaidOrder>;
|
|
71
|
+
/** What the order has actually been paid. Needs `ReadOrder`. */
|
|
72
|
+
paymentPosition(orderId: string, options?: Call): Promise<OrderPaymentPosition>;
|
|
73
|
+
/**
|
|
74
|
+
* Move a fully paid order to `PaymentSettled`. Needs `UpdateOrder`.
|
|
75
|
+
*
|
|
76
|
+
* Refuses `COVERAGE_INSUFFICIENT` while the order's SETTLED money does not
|
|
77
|
+
* cover its total, with the position attached. Settled, not covered: an
|
|
78
|
+
* authorization is permission to take money, not money, so an order whose
|
|
79
|
+
* remainder is merely authorized is settled from `ArrangingPayment` after the
|
|
80
|
+
* authorization is captured, like any other order. `ORDER_NOT_SETTLEABLE` is
|
|
81
|
+
* the other refusal — a state a settlement cannot be reached from, which
|
|
82
|
+
* paying more can never fix.
|
|
83
|
+
*
|
|
84
|
+
* Calling this on an order the platform ALREADY settled succeeds rather than
|
|
85
|
+
* refusing. Expect that to be the common case: the engine settles an order as
|
|
86
|
+
* soon as its settled payments cover its total, so recording the payment that
|
|
87
|
+
* clears a deposit usually settles the order on its own, and this call is then
|
|
88
|
+
* a confirmation.
|
|
89
|
+
*
|
|
90
|
+
* So do not wait on a settle CALL to learn that a deposit was paid off — in
|
|
91
|
+
* the ordinary flow nobody makes one. The close signal is the platform event
|
|
92
|
+
* `payment.partial_recorded` with `outstanding_after: 0`, on the payment that
|
|
93
|
+
* cleared the balance. That figure nets settled PLUS authorized money, so it
|
|
94
|
+
* means fully COVERED; "fully settled" is `settled >= total` on the position,
|
|
95
|
+
* which is what this method requires. For money recorded through
|
|
96
|
+
* `payments.record` the two coincide — it only creates settled payments.
|
|
97
|
+
*/
|
|
98
|
+
settle(orderId: string, options?: Call & {
|
|
99
|
+
fulfil?: boolean;
|
|
100
|
+
}): Promise<OrderSettlement>;
|
|
101
|
+
}
|
|
102
|
+
export declare const PARK_ORDER_MUTATION = "\n mutation ParkOrder($orderId: ID!, $reason: String, $idempotencyKey: String) {\n parkOrder(orderId: $orderId, reason: $reason, idempotencyKey: $idempotencyKey) {\n id\n code\n state\n currencyCode\n total\n totalWithTax\n }\n }\n";
|
|
103
|
+
export declare const UNPARK_ORDER_MUTATION = "\n mutation UnparkOrder($orderId: ID!, $idempotencyKey: String) {\n unparkOrder(orderId: $orderId, idempotencyKey: $idempotencyKey) {\n id\n code\n state\n currencyCode\n total\n totalWithTax\n }\n }\n";
|
|
104
|
+
export declare const SETTLE_ORDER_MUTATION = "\n mutation SettleOrder($orderId: ID!, $fulfil: Boolean!, $idempotencyKey: String) {\n settleOrder(orderId: $orderId, fulfil: $fulfil, idempotencyKey: $idempotencyKey) {\n \n order {\n id\n code\n state\n currencyCode\n total\n totalWithTax\n }\n position {\n total\n covered\n settled\n authorized\n refunded\n outstanding\n overpaid\n availableToCharge\n }\n fulfilRequested\n\n }\n }\n";
|
|
105
|
+
export declare const ORDER_POSITION_QUERY = "\n query OrderPosition($orderId: ID!) {\n orderPaymentPosition(orderId: $orderId) {\n total\n covered\n settled\n authorized\n refunded\n outstanding\n overpaid\n availableToCharge\n }\n }\n";
|
|
106
|
+
export declare function createOrdersModule(transport: Transport): OrdersModule;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createSurchargesModule } from './surcharges.js';
|
|
2
|
+
const SETTLEMENT_FIELDS = /* GraphQL */ `
|
|
3
|
+
order {
|
|
4
|
+
id
|
|
5
|
+
code
|
|
6
|
+
state
|
|
7
|
+
currencyCode
|
|
8
|
+
total
|
|
9
|
+
totalWithTax
|
|
10
|
+
}
|
|
11
|
+
position {
|
|
12
|
+
total
|
|
13
|
+
covered
|
|
14
|
+
settled
|
|
15
|
+
authorized
|
|
16
|
+
refunded
|
|
17
|
+
outstanding
|
|
18
|
+
overpaid
|
|
19
|
+
availableToCharge
|
|
20
|
+
}
|
|
21
|
+
fulfilRequested
|
|
22
|
+
`;
|
|
23
|
+
export const PARK_ORDER_MUTATION = /* GraphQL */ `
|
|
24
|
+
mutation ParkOrder($orderId: ID!, $reason: String, $idempotencyKey: String) {
|
|
25
|
+
parkOrder(orderId: $orderId, reason: $reason, idempotencyKey: $idempotencyKey) {
|
|
26
|
+
id
|
|
27
|
+
code
|
|
28
|
+
state
|
|
29
|
+
currencyCode
|
|
30
|
+
total
|
|
31
|
+
totalWithTax
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export const UNPARK_ORDER_MUTATION = /* GraphQL */ `
|
|
36
|
+
mutation UnparkOrder($orderId: ID!, $idempotencyKey: String) {
|
|
37
|
+
unparkOrder(orderId: $orderId, idempotencyKey: $idempotencyKey) {
|
|
38
|
+
id
|
|
39
|
+
code
|
|
40
|
+
state
|
|
41
|
+
currencyCode
|
|
42
|
+
total
|
|
43
|
+
totalWithTax
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
export const SETTLE_ORDER_MUTATION = /* GraphQL */ `
|
|
48
|
+
mutation SettleOrder($orderId: ID!, $fulfil: Boolean!, $idempotencyKey: String) {
|
|
49
|
+
settleOrder(orderId: $orderId, fulfil: $fulfil, idempotencyKey: $idempotencyKey) {
|
|
50
|
+
${SETTLEMENT_FIELDS}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
export const ORDER_POSITION_QUERY = /* GraphQL */ `
|
|
55
|
+
query OrderPosition($orderId: ID!) {
|
|
56
|
+
orderPaymentPosition(orderId: $orderId) {
|
|
57
|
+
total
|
|
58
|
+
covered
|
|
59
|
+
settled
|
|
60
|
+
authorized
|
|
61
|
+
refunded
|
|
62
|
+
outstanding
|
|
63
|
+
overpaid
|
|
64
|
+
availableToCharge
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
export function createOrdersModule(transport) {
|
|
69
|
+
return {
|
|
70
|
+
surcharges: createSurchargesModule(transport),
|
|
71
|
+
async park(orderId, options) {
|
|
72
|
+
const data = await transport.graphql({
|
|
73
|
+
api: 'admin',
|
|
74
|
+
query: PARK_ORDER_MUTATION,
|
|
75
|
+
// `undefined`, never an explicit null: `JSON.stringify` drops it, so an
|
|
76
|
+
// omitted option stays off the wire entirely rather than arriving as a
|
|
77
|
+
// value a future server could read as one.
|
|
78
|
+
variables: {
|
|
79
|
+
orderId,
|
|
80
|
+
reason: options?.reason,
|
|
81
|
+
idempotencyKey: options?.idempotencyKey,
|
|
82
|
+
},
|
|
83
|
+
}, options);
|
|
84
|
+
return data.parkOrder;
|
|
85
|
+
},
|
|
86
|
+
async unpark(orderId, options) {
|
|
87
|
+
const data = await transport.graphql({
|
|
88
|
+
api: 'admin',
|
|
89
|
+
query: UNPARK_ORDER_MUTATION,
|
|
90
|
+
// `undefined`, never an explicit null — see `park`.
|
|
91
|
+
variables: { orderId, idempotencyKey: options?.idempotencyKey },
|
|
92
|
+
}, options);
|
|
93
|
+
return data.unparkOrder;
|
|
94
|
+
},
|
|
95
|
+
async paymentPosition(orderId, options) {
|
|
96
|
+
const data = await transport.graphql({ api: 'admin', query: ORDER_POSITION_QUERY, variables: { orderId } }, options);
|
|
97
|
+
return data.orderPaymentPosition;
|
|
98
|
+
},
|
|
99
|
+
async settle(orderId, options) {
|
|
100
|
+
const data = await transport.graphql({
|
|
101
|
+
api: 'admin',
|
|
102
|
+
query: SETTLE_ORDER_MUTATION,
|
|
103
|
+
variables: {
|
|
104
|
+
// Explicit `false` rather than omitted: the argument is non-null on
|
|
105
|
+
// the wire, and "did the caller ask for fulfilment?" is recorded on
|
|
106
|
+
// the settlement event.
|
|
107
|
+
orderId,
|
|
108
|
+
fulfil: options?.fulfil ?? false,
|
|
109
|
+
idempotencyKey: options?.idempotencyKey,
|
|
110
|
+
},
|
|
111
|
+
}, options);
|
|
112
|
+
return data.settleOrder;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|