@sixtyninecommerce/admin 0.7.0 → 0.9.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.
Files changed (161) hide show
  1. package/README.md +2445 -17
  2. package/dist/api-tokens/index.d.ts +15 -5
  3. package/dist/api-tokens/index.js +1 -0
  4. package/dist/apps/event-subscription.d.ts +59 -0
  5. package/dist/apps/event-subscription.js +32 -0
  6. package/dist/apps/hook-secret.d.ts +22 -0
  7. package/dist/apps/hook-secret.js +36 -0
  8. package/dist/apps/index.d.ts +54 -3
  9. package/dist/apps/index.js +17 -0
  10. package/dist/audit/index.d.ts +7 -0
  11. package/dist/auth/queries.d.ts +5 -1
  12. package/dist/auth/queries.js +6 -0
  13. package/dist/bulk/index.d.ts +323 -0
  14. package/dist/bulk/index.js +162 -0
  15. package/dist/catalog/assets.d.ts +147 -0
  16. package/dist/catalog/assets.js +164 -0
  17. package/dist/catalog/collections.d.ts +109 -0
  18. package/dist/catalog/collections.js +91 -0
  19. package/dist/catalog/facets.d.ts +103 -0
  20. package/dist/catalog/facets.js +100 -0
  21. package/dist/catalog/index.d.ts +115 -0
  22. package/dist/catalog/index.js +21 -0
  23. package/dist/catalog/options.d.ts +89 -0
  24. package/dist/catalog/options.js +88 -0
  25. package/dist/catalog/products-bulk.d.ts +21 -0
  26. package/dist/catalog/products-bulk.js +58 -0
  27. package/dist/catalog/products.d.ts +489 -0
  28. package/dist/catalog/products.js +365 -0
  29. package/dist/catalog/properties.d.ts +234 -0
  30. package/dist/catalog/properties.js +158 -0
  31. package/dist/catalog/types.d.ts +174 -0
  32. package/dist/catalog/types.js +141 -0
  33. package/dist/catalog/variants.d.ts +274 -0
  34. package/dist/catalog/variants.js +258 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +171 -3
  38. package/dist/client.js +81 -12
  39. package/dist/compliance/index.d.ts +88 -0
  40. package/dist/compliance/index.js +88 -0
  41. package/dist/customer-data-redaction.d.ts +14 -0
  42. package/dist/customer-data-redaction.js +22 -0
  43. package/dist/customers/index.d.ts +428 -17
  44. package/dist/customers/index.js +322 -15
  45. package/dist/documents/index.d.ts +513 -16
  46. package/dist/documents/index.js +229 -3
  47. package/dist/events/index.d.ts +259 -0
  48. package/dist/events/index.js +147 -0
  49. package/dist/feeds/ai.d.ts +46 -0
  50. package/dist/feeds/ai.js +43 -0
  51. package/dist/feeds/categories.d.ts +50 -0
  52. package/dist/feeds/categories.js +56 -0
  53. package/dist/feeds/configs.d.ts +54 -0
  54. package/dist/feeds/configs.js +85 -0
  55. package/dist/feeds/identifier-fields.d.ts +25 -0
  56. package/dist/feeds/identifier-fields.js +29 -0
  57. package/dist/feeds/index.d.ts +54 -0
  58. package/dist/feeds/index.js +46 -0
  59. package/dist/feeds/preserve.d.ts +38 -0
  60. package/dist/feeds/preserve.js +74 -0
  61. package/dist/feeds/rules.d.ts +42 -0
  62. package/dist/feeds/rules.js +56 -0
  63. package/dist/feeds/types.d.ts +654 -0
  64. package/dist/feeds/types.js +135 -0
  65. package/dist/fulfillments/index.d.ts +360 -0
  66. package/dist/fulfillments/index.js +425 -0
  67. package/dist/heads/index.d.ts +60 -0
  68. package/dist/heads/index.js +45 -0
  69. package/dist/hooks/assignments.d.ts +90 -0
  70. package/dist/hooks/assignments.js +53 -0
  71. package/dist/hooks/index.d.ts +97 -0
  72. package/dist/hooks/index.js +76 -0
  73. package/dist/index.d.ts +84 -9
  74. package/dist/index.js +16 -1
  75. package/dist/jobs/bulk-action-run.d.ts +65 -0
  76. package/dist/jobs/bulk-action-run.js +70 -0
  77. package/dist/locations/index.d.ts +6 -2
  78. package/dist/meta/index.d.ts +32 -0
  79. package/dist/orders/commit-edit.d.ts +123 -0
  80. package/dist/orders/commit-edit.js +142 -0
  81. package/dist/orders/get.d.ts +722 -0
  82. package/dist/orders/get.js +530 -0
  83. package/dist/orders/index.d.ts +105 -20
  84. package/dist/orders/index.js +44 -0
  85. package/dist/orders/line-price-preview.d.ts +297 -0
  86. package/dist/orders/line-price-preview.js +50 -0
  87. package/dist/orders/manual-discounts.d.ts +132 -0
  88. package/dist/orders/manual-discounts.js +95 -0
  89. package/dist/orders/manual-pricing.d.ts +192 -0
  90. package/dist/orders/manual-pricing.js +72 -0
  91. package/dist/orders/order-entry.d.ts +566 -0
  92. package/dist/orders/order-entry.js +129 -0
  93. package/dist/orders/parcel-charge.d.ts +45 -0
  94. package/dist/orders/parcel-charge.js +55 -0
  95. package/dist/orders/plan-edit.d.ts +489 -0
  96. package/dist/orders/plan-edit.js +93 -0
  97. package/dist/orders/status.d.ts +235 -0
  98. package/dist/orders/status.js +96 -0
  99. package/dist/orders/surcharges.d.ts +75 -2
  100. package/dist/orders/surcharges.js +110 -4
  101. package/dist/payments/account-credit.d.ts +171 -0
  102. package/dist/payments/account-credit.js +107 -0
  103. package/dist/payments/index.d.ts +368 -2
  104. package/dist/payments/index.js +138 -0
  105. package/dist/platform/catalog.d.ts +148 -0
  106. package/dist/platform/catalog.js +74 -0
  107. package/dist/platform/entitlements.d.ts +111 -0
  108. package/dist/platform/entitlements.js +86 -0
  109. package/dist/platform/legal-entities.d.ts +139 -0
  110. package/dist/platform/legal-entities.js +126 -0
  111. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  112. package/dist/platform/legal-entity-numbering.js +101 -0
  113. package/dist/platform/organization-membership.d.ts +80 -0
  114. package/dist/platform/organization-membership.js +71 -0
  115. package/dist/platform/organization-settings.d.ts +33 -0
  116. package/dist/platform/organization-settings.js +42 -0
  117. package/dist/platform/organization-sharing.d.ts +172 -0
  118. package/dist/platform/organization-sharing.js +120 -0
  119. package/dist/platform/organizations.d.ts +205 -0
  120. package/dist/platform/organizations.js +180 -0
  121. package/dist/platform/provision-store.d.ts +88 -8
  122. package/dist/platform/provision-store.js +12 -0
  123. package/dist/platform/reports.d.ts +46 -0
  124. package/dist/platform/reports.js +31 -0
  125. package/dist/platform/server-metrics.d.ts +58 -0
  126. package/dist/platform/server-metrics.js +11 -0
  127. package/dist/pos/index.d.ts +567 -0
  128. package/dist/pos/index.js +205 -0
  129. package/dist/pricing/index.d.ts +405 -0
  130. package/dist/pricing/index.js +351 -0
  131. package/dist/provenance/index.d.ts +93 -0
  132. package/dist/provenance/index.js +53 -0
  133. package/dist/reports/index.d.ts +199 -0
  134. package/dist/reports/index.js +154 -0
  135. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  136. package/dist/shipping/parcel-count-suggestion.js +73 -0
  137. package/dist/shipping/parcel-rule.d.ts +48 -0
  138. package/dist/shipping/parcel-rule.js +91 -0
  139. package/dist/stock/index.d.ts +166 -0
  140. package/dist/stock/index.js +115 -0
  141. package/dist/stock/transfers.d.ts +520 -0
  142. package/dist/stock/transfers.js +437 -0
  143. package/dist/stored-value/index.d.ts +154 -0
  144. package/dist/stored-value/index.js +130 -0
  145. package/dist/tenant/capabilities.d.ts +10 -0
  146. package/dist/tenant/entitlements.d.ts +35 -0
  147. package/dist/tenant/entitlements.js +15 -0
  148. package/dist/tenant/field-groups.d.ts +64 -0
  149. package/dist/tenant/field-groups.js +34 -0
  150. package/dist/tenant/legal-entity.d.ts +32 -0
  151. package/dist/tenant/legal-entity.js +41 -0
  152. package/dist/tenant/notifications.d.ts +148 -0
  153. package/dist/tenant/notifications.js +79 -0
  154. package/dist/tenant/retention.d.ts +10 -4
  155. package/dist/tenant/settings-sources.d.ts +103 -0
  156. package/dist/tenant/settings-sources.js +92 -0
  157. package/dist/types.d.ts +86 -11
  158. package/dist/uploads/index.d.ts +11 -7
  159. package/dist/users/index.d.ts +104 -0
  160. package/dist/users/index.js +80 -0
  161. package/package.json +3 -2
@@ -0,0 +1,425 @@
1
+ import { errorFromErrorResult, isErrorResult, NotFoundError, ValidationError, } from '@sixtyninecommerce/core';
2
+ /** Every field of `OrderOverpaymentOption`. */
3
+ const OVERPAYMENT_OPTION_FIELDS = /* GraphQL */ `
4
+ available
5
+ code
6
+ message
7
+ `;
8
+ /**
9
+ * Its own document, like every call here: on an engine without the query this call alone fails
10
+ * validation.
11
+ */
12
+ export const FULFILLMENT_CANCEL_PREVIEW_QUERY = /* GraphQL */ `
13
+ query FulfillmentCancelPreview($fulfillmentId: ID!) {
14
+ fulfillmentCancelPreview(fulfillmentId: $fulfillmentId) {
15
+ fulfillmentId
16
+ overpayments {
17
+ orderId
18
+ orderCode
19
+ amount
20
+ currencyCode
21
+ refund { ${OVERPAYMENT_OPTION_FIELDS} }
22
+ accountCredit { ${OVERPAYMENT_OPTION_FIELDS} }
23
+ }
24
+ }
25
+ }
26
+ `;
27
+ /** `$overpayment` and `$idempotencyKey` are optional and left out of the variables when not given. */
28
+ export const CANCEL_FULFILLMENT_MUTATION = /* GraphQL */ `
29
+ mutation CancelFulfillment(
30
+ $fulfillmentId: ID!
31
+ $overpayment: [FulfillmentCancelOverpaymentInput!]
32
+ $idempotencyKey: String
33
+ ) {
34
+ cancelFulfillment(
35
+ fulfillmentId: $fulfillmentId
36
+ overpayment: $overpayment
37
+ idempotencyKey: $idempotencyKey
38
+ ) {
39
+ fulfillment {
40
+ id
41
+ state
42
+ }
43
+ settlements {
44
+ orderId
45
+ orderCode
46
+ amount
47
+ currencyCode
48
+ choice
49
+ }
50
+ }
51
+ }
52
+ `;
53
+ const OVERPAYMENT_CHOICES = new Set([
54
+ 'REFUND',
55
+ 'ACCOUNT_CREDIT',
56
+ ]);
57
+ function invalidCancel(field, message) {
58
+ return new ValidationError({
59
+ code: 'FULFILLMENT_CANCEL_OVERPAYMENT_INVALID',
60
+ message,
61
+ status: 400,
62
+ service: 'vendure',
63
+ details: [{ field, code: 'FULFILLMENT_CANCEL_OVERPAYMENT_INVALID', message }],
64
+ });
65
+ }
66
+ /** Refused before any request: the engine would refuse each, less legibly. */
67
+ function assertCancelRequest(fulfillmentId, input) {
68
+ if (typeof fulfillmentId !== 'string' || fulfillmentId.trim() === '') {
69
+ throw invalidCancel('fulfillmentId', 'A fulfillment id is required.');
70
+ }
71
+ const seen = new Set();
72
+ for (const [index, entry] of (input.overpayment ?? []).entries()) {
73
+ const orderId = entry?.orderId;
74
+ if (typeof orderId !== 'string' || orderId.trim() === '') {
75
+ throw invalidCancel(`overpayment[${String(index)}].orderId`, 'Each choice names an order.');
76
+ }
77
+ if (!OVERPAYMENT_CHOICES.has(entry.choice)) {
78
+ throw invalidCancel(`overpayment[${String(index)}].choice`, `The choice for order ${orderId} must be REFUND or ACCOUNT_CREDIT; got ${JSON.stringify(entry.choice)}.`);
79
+ }
80
+ if (seen.has(orderId)) {
81
+ throw invalidCancel(`overpayment[${String(index)}].orderId`, `Order ${orderId} is named twice: give one choice per order.`);
82
+ }
83
+ seen.add(orderId);
84
+ }
85
+ }
86
+ /** An option as the SDK types it: `code` and `message` are null when it is open. */
87
+ function toOption(raw) {
88
+ return { available: raw.available, code: raw.code ?? null, message: raw.message ?? null };
89
+ }
90
+ export const REPORT_APP_FULFILLMENT_MUTATION = /* GraphQL */ `
91
+ mutation ReportAppFulfillment(
92
+ $fulfillmentId: ID!
93
+ $input: ReportAppFulfillmentInput!
94
+ $idempotencyKey: String
95
+ ) {
96
+ reportAppFulfillment(
97
+ fulfillmentId: $fulfillmentId
98
+ input: $input
99
+ idempotencyKey: $idempotencyKey
100
+ ) {
101
+ id
102
+ state
103
+ trackingCode
104
+ method
105
+ }
106
+ }
107
+ `;
108
+ export const ORDER_FULFILLMENT_PARCELS_QUERY = /* GraphQL */ `
109
+ query OrderFulfillmentParcels($id: ID!) {
110
+ order(id: $id) {
111
+ id
112
+ fulfillments {
113
+ id
114
+ trackingCode
115
+ customFields {
116
+ myparcelShipmentId
117
+ myparcelShipmentIds
118
+ parcelCount
119
+ trackingCodes
120
+ }
121
+ }
122
+ }
123
+ }
124
+ `;
125
+ /** Every field of `MyparcelUnretractedShipment`, shared with the stock-transfer twin. */
126
+ export const UNRETRACTED_SHIPMENT_FIELDS = /* GraphQL */ `
127
+ shipmentId
128
+ reference
129
+ verdict
130
+ status
131
+ statusName
132
+ billable
133
+ hidden
134
+ reason
135
+ cause
136
+ firstRecordedAt
137
+ lastCheckedAt
138
+ resolvedAt
139
+ `;
140
+ /**
141
+ * Its own document, never part of the parcel read: an engine without the
142
+ * field refuses this call alone, and the order card still renders.
143
+ */
144
+ export const ORDER_FULFILLMENT_UNRETRACTED_SHIPMENTS_QUERY = /* GraphQL */ `
145
+ query OrderFulfillmentUnretractedShipments($id: ID!) {
146
+ order(id: $id) {
147
+ id
148
+ fulfillments {
149
+ id
150
+ myparcelUnretractedShipments { ${UNRETRACTED_SHIPMENT_FIELDS} }
151
+ }
152
+ }
153
+ }
154
+ `;
155
+ export const FULFILLMENT_PARCEL_TRACKING_QUERY = /* GraphQL */ `
156
+ query FulfillmentParcelTracking($id: ID!) {
157
+ warehouseFulfillment(id: $id) {
158
+ trackingCodes
159
+ parcelTrackingCodes
160
+ parcelCount
161
+ }
162
+ }
163
+ `;
164
+ /**
165
+ * Its own document on purpose: `chargedParcelCount` exists only on an engine
166
+ * that serves it, so on an older one this call alone fails validation and the
167
+ * demand the create flow reads beside it does not.
168
+ */
169
+ export const WAREHOUSE_CHARGED_PARCEL_COUNT_QUERY = /* GraphQL */ `
170
+ query WarehouseChargedParcelCount($input: WarehouseDemandInput!) {
171
+ warehouseDemand(input: $input) {
172
+ items {
173
+ orderId
174
+ chargedParcelCount
175
+ }
176
+ }
177
+ }
178
+ `;
179
+ export const MYPARCEL_FULFILLMENT_LABEL_MUTATION = /* GraphQL */ `
180
+ mutation MyparcelFulfillmentLabel($fulfillmentId: ID!, $format: MyparcelLabelFormatEnum!) {
181
+ getMyparcelFulfillmentLabel(fulfillmentId: $fulfillmentId, format: $format) {
182
+ format
183
+ base64
184
+ contentType
185
+ filename
186
+ }
187
+ }
188
+ `;
189
+ /**
190
+ * Its own document, like every call here: on an engine without the mutation
191
+ * this call alone fails validation. `$format` is optional and left out of the
192
+ * variables when not asked for.
193
+ */
194
+ export const LABEL_MYPARCEL_FULFILLMENT_PARCELS_MUTATION = /* GraphQL */ `
195
+ mutation LabelMyparcelFulfillmentParcels($fulfillmentId: ID!, $format: MyparcelLabelFormatEnum) {
196
+ labelMyparcelFulfillmentParcels(fulfillmentId: $fulfillmentId, format: $format) {
197
+ __typename
198
+ ... on MyparcelLabelParcelsQueued {
199
+ fulfillmentId
200
+ parcels
201
+ missing
202
+ queued
203
+ }
204
+ ... on ErrorResult {
205
+ errorCode
206
+ message
207
+ }
208
+ }
209
+ }
210
+ `;
211
+ /**
212
+ * Clears the engine's MyParcel budget: 3 × 10s attempts plus two capped waits.
213
+ * Every merged MyParcel label (a fulfillment's, a stock transfer's) runs the
214
+ * same engine download, so they share it.
215
+ */
216
+ export const MYPARCEL_LABEL_TIMEOUT_MS = 55_000;
217
+ /** The engine's entries as they are: the fields are the SDK's, one to one. */
218
+ export function toUnretractedShipments(raw) {
219
+ return (raw ?? []).map((entry) => ({
220
+ shipmentId: entry.shipmentId,
221
+ reference: entry.reference ?? null,
222
+ verdict: entry.verdict,
223
+ status: entry.status ?? null,
224
+ statusName: entry.statusName ?? null,
225
+ billable: entry.billable ?? null,
226
+ hidden: entry.hidden,
227
+ reason: entry.reason,
228
+ cause: entry.cause,
229
+ firstRecordedAt: entry.firstRecordedAt,
230
+ lastCheckedAt: entry.lastCheckedAt,
231
+ resolvedAt: entry.resolvedAt ?? null,
232
+ }));
233
+ }
234
+ function orderNotFound(orderId) {
235
+ return new NotFoundError({
236
+ code: 'ORDER_NOT_FOUND',
237
+ message: `This store has no order ${orderId}.`,
238
+ status: 404,
239
+ service: 'vendure',
240
+ });
241
+ }
242
+ /** The stale-list rule: trust the carrier's list only while it starts with `trackingCode`. */
243
+ function trustedTrackingCodes(list, trackingCode) {
244
+ const single = trackingCode?.trim() ?? '';
245
+ const listed = (list ?? []).filter((code) => typeof code === 'string' && code.trim() !== '');
246
+ if (listed.length > 0 && listed[0] === single)
247
+ return listed;
248
+ return single ? [single] : [];
249
+ }
250
+ /** The engine's `authoritativeShipmentIds`: the list only while it starts with the single id. */
251
+ function trustedShipmentIds(list, single) {
252
+ if (typeof single !== 'number' || !Number.isInteger(single) || single <= 0)
253
+ return [];
254
+ const first = String(single);
255
+ return list?.[0] === first ? [...list] : [first];
256
+ }
257
+ /** The bytes of a base64 label document (`atob`: no Node `Buffer`, it ships to browsers). */
258
+ export function decodeBase64(base64) {
259
+ const binary = atob(base64);
260
+ const bytes = new Uint8Array(binary.length);
261
+ for (let index = 0; index < binary.length; index += 1)
262
+ bytes[index] = binary.charCodeAt(index);
263
+ return bytes;
264
+ }
265
+ const LABEL_FORMATS = new Set(['A4', 'A6']);
266
+ function invalidLabelRequest(field, message) {
267
+ return new ValidationError({
268
+ code: 'MYPARCEL_LABEL_INPUT_INVALID',
269
+ message,
270
+ status: 400,
271
+ service: 'vendure',
272
+ details: [{ field, code: 'MYPARCEL_LABEL_INPUT_INVALID', message }],
273
+ });
274
+ }
275
+ /** Refused before any request: the engine would refuse both, less legibly. */
276
+ function assertLabelRequest(fulfillmentId, format) {
277
+ if (typeof fulfillmentId !== 'string' || fulfillmentId.trim() === '') {
278
+ throw invalidLabelRequest('fulfillmentId', 'A fulfillment id is required.');
279
+ }
280
+ if (format !== undefined && !LABEL_FORMATS.has(format)) {
281
+ throw invalidLabelRequest('format', `format must be 'A4' or 'A6', or left out to label every parcel still missing one; got ${JSON.stringify(format)}.`);
282
+ }
283
+ }
284
+ export function createFulfillmentsModule(transport) {
285
+ return {
286
+ async parcelsOfOrder(orderId, options) {
287
+ const data = await transport.graphql({ api: 'admin', query: ORDER_FULFILLMENT_PARCELS_QUERY, variables: { id: orderId } }, options);
288
+ if (!data.order)
289
+ throw orderNotFound(orderId);
290
+ return (data.order.fulfillments ?? []).map((fulfillment) => ({
291
+ fulfillmentId: fulfillment.id,
292
+ shipmentIds: trustedShipmentIds(fulfillment.customFields?.myparcelShipmentIds, fulfillment.customFields?.myparcelShipmentId),
293
+ parcelCount: fulfillment.customFields?.parcelCount ?? null,
294
+ trackingCode: fulfillment.trackingCode ?? null,
295
+ trackingCodes: trustedTrackingCodes(fulfillment.customFields?.trackingCodes, fulfillment.trackingCode ?? null),
296
+ }));
297
+ },
298
+ async unretractedShipmentsOfOrder(orderId, options) {
299
+ const data = await transport.graphql({
300
+ api: 'admin',
301
+ query: ORDER_FULFILLMENT_UNRETRACTED_SHIPMENTS_QUERY,
302
+ variables: { id: orderId },
303
+ }, options);
304
+ if (!data.order)
305
+ throw orderNotFound(orderId);
306
+ return (data.order.fulfillments ?? []).map((fulfillment) => ({
307
+ fulfillmentId: fulfillment.id,
308
+ shipments: toUnretractedShipments(fulfillment.myparcelUnretractedShipments),
309
+ }));
310
+ },
311
+ async parcelTracking(fulfillmentId, options) {
312
+ const data = await transport.graphql({
313
+ api: 'admin',
314
+ query: FULFILLMENT_PARCEL_TRACKING_QUERY,
315
+ variables: { id: fulfillmentId },
316
+ }, options);
317
+ return data.warehouseFulfillment;
318
+ },
319
+ async chargedParcelCount(input, options) {
320
+ const data = await transport.graphql({
321
+ api: 'admin',
322
+ query: WAREHOUSE_CHARGED_PARCEL_COUNT_QUERY,
323
+ variables: {
324
+ input: {
325
+ stockLocationId: input.stockLocationId,
326
+ orderIds: [input.orderId],
327
+ ...(input.includeIneligible ? { includeIneligible: true } : {}),
328
+ ...(input.routing ? { routing: [...input.routing] } : {}),
329
+ take: 1,
330
+ },
331
+ },
332
+ }, options);
333
+ const entry = data.warehouseDemand.items.find((item) => item.orderId === input.orderId);
334
+ return entry?.chargedParcelCount ?? null;
335
+ },
336
+ async myparcelLabel(fulfillmentId, format, options) {
337
+ const data = await transport.graphql({
338
+ api: 'admin',
339
+ query: MYPARCEL_FULFILLMENT_LABEL_MUTATION,
340
+ variables: { fulfillmentId, format },
341
+ }, { timeoutMs: MYPARCEL_LABEL_TIMEOUT_MS, ...options });
342
+ const { base64, ...document } = data.getMyparcelFulfillmentLabel;
343
+ return { ...document, bytes: decodeBase64(base64) };
344
+ },
345
+ async labelMyparcelParcels(fulfillmentId, input = {}, options) {
346
+ const format = input?.format;
347
+ assertLabelRequest(fulfillmentId, format);
348
+ const data = await transport.graphql({
349
+ api: 'admin',
350
+ query: LABEL_MYPARCEL_FULFILLMENT_PARCELS_MUTATION,
351
+ variables: { fulfillmentId, ...(format === undefined ? {} : { format }) },
352
+ }, options);
353
+ const result = data.labelMyparcelFulfillmentParcels;
354
+ if (isErrorResult(result))
355
+ throw errorFromErrorResult(result);
356
+ const queued = result;
357
+ return {
358
+ fulfillmentId: queued.fulfillmentId,
359
+ parcels: queued.parcels,
360
+ missing: queued.missing,
361
+ queued: queued.queued,
362
+ };
363
+ },
364
+ async cancelPreview(fulfillmentId, options) {
365
+ if (typeof fulfillmentId !== 'string' || fulfillmentId.trim() === '') {
366
+ throw invalidCancel('fulfillmentId', 'A fulfillment id is required.');
367
+ }
368
+ const data = await transport.graphql({ api: 'admin', query: FULFILLMENT_CANCEL_PREVIEW_QUERY, variables: { fulfillmentId } }, options);
369
+ const preview = data.fulfillmentCancelPreview;
370
+ return {
371
+ fulfillmentId: preview.fulfillmentId,
372
+ overpayments: preview.overpayments.map((entry) => ({
373
+ orderId: entry.orderId,
374
+ orderCode: entry.orderCode,
375
+ amount: entry.amount,
376
+ currencyCode: entry.currencyCode,
377
+ refund: toOption(entry.refund),
378
+ accountCredit: toOption(entry.accountCredit),
379
+ })),
380
+ };
381
+ },
382
+ async cancel(fulfillmentId, input = {}, options) {
383
+ assertCancelRequest(fulfillmentId, input);
384
+ const overpayment = input.overpayment ?? [];
385
+ const data = await transport.graphql({
386
+ api: 'admin',
387
+ query: CANCEL_FULFILLMENT_MUTATION,
388
+ // A variable, not a header: the Admin API takes the key as an argument.
389
+ variables: {
390
+ fulfillmentId,
391
+ ...(overpayment.length > 0
392
+ ? { overpayment: overpayment.map(({ orderId, choice }) => ({ orderId, choice })) }
393
+ : {}),
394
+ ...(options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
395
+ },
396
+ }, options);
397
+ const result = data.cancelFulfillment;
398
+ return {
399
+ fulfillment: { id: result.fulfillment.id, state: result.fulfillment.state },
400
+ settlements: result.settlements.map((entry) => ({
401
+ orderId: entry.orderId,
402
+ orderCode: entry.orderCode,
403
+ amount: entry.amount,
404
+ currencyCode: entry.currencyCode,
405
+ choice: entry.choice,
406
+ })),
407
+ };
408
+ },
409
+ async reportAppFulfillment(fulfillmentId, input, options) {
410
+ const data = await transport.graphql({
411
+ api: 'admin',
412
+ query: REPORT_APP_FULFILLMENT_MUTATION,
413
+ // A variable, not a header: the Admin API takes the key as an argument.
414
+ variables: {
415
+ fulfillmentId,
416
+ input,
417
+ ...(options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
418
+ },
419
+ }, options);
420
+ const reported = data.reportAppFulfillment;
421
+ // Core stores "no tracking code" as an empty string.
422
+ return { ...reported, trackingCode: reported.trackingCode || null };
423
+ },
424
+ };
425
+ }
@@ -83,6 +83,64 @@ export declare const CREATE_MUTATION = "\n mutation CreateChannelHead($input: C
83
83
  export declare const UPDATE_MUTATION = "\n mutation UpdateChannelHead($id: ID!, $input: UpdateChannelHeadInput!) {\n updateChannelHead(id: $id, input: $input) { \n id\n channelId\n installationId\n key\n kind\n name\n origins\n deepLinkBase\n deepLinkScheme\n defaultOrderScope\n eventSubscriptionId\n isDefault\n createdAt\n updatedAt\n }\n }\n";
84
84
  export declare const DELETE_MUTATION = "\n mutation DeleteChannelHead($id: ID!) {\n deleteChannelHead(id: $id)\n }\n";
85
85
  export declare const SET_DEFAULT_MUTATION = "\n mutation SetDefaultChannelHead($id: ID!) {\n setDefaultChannelHead(id: $id) { \n id\n channelId\n installationId\n key\n kind\n name\n origins\n deepLinkBase\n deepLinkScheme\n defaultOrderScope\n eventSubscriptionId\n isDefault\n createdAt\n updatedAt\n }\n }\n";
86
+ /**
87
+ * A head's PRIVATE STOREFRONT TOKEN (`s69p_`, spec §6.3, §15.21) — never the
88
+ * token itself, which {@link HeadPrivateTokensModule.issue} returns once.
89
+ */
90
+ export interface HeadStorefrontToken {
91
+ id: string;
92
+ headId: string;
93
+ name: string;
94
+ /** `s69p_<lookup id>` — enough to recognise the token in a list, never to use it. */
95
+ prefix: string;
96
+ createdAt: string;
97
+ /** When a service last verified it, to within about a minute. */
98
+ lastUsedAt: string | null;
99
+ /** When it was revoked. A revoked token never verifies again. */
100
+ revokedAt: string | null;
101
+ }
102
+ export interface IssuedHeadStorefrontToken {
103
+ /** The whole token. Shown ONCE: put it in the storefront server's secrets now. */
104
+ token: string;
105
+ record: HeadStorefrontToken;
106
+ }
107
+ export declare const STOREFRONT_TOKEN_FIELDS = "\n id\n headId\n name\n prefix\n createdAt\n lastUsedAt\n revokedAt\n";
108
+ export declare const PRIVATE_TOKENS_QUERY = "\n query HeadStorefrontTokens($headId: ID!) {\n headStorefrontTokens(headId: $headId) { \n id\n headId\n name\n prefix\n createdAt\n lastUsedAt\n revokedAt\n }\n }\n";
109
+ export declare const ISSUE_PRIVATE_TOKEN_MUTATION = "\n mutation IssueHeadStorefrontToken($headId: ID!, $name: String!) {\n issueHeadStorefrontToken(headId: $headId, name: $name) {\n token\n record { \n id\n headId\n name\n prefix\n createdAt\n lastUsedAt\n revokedAt\n }\n }\n }\n";
110
+ export declare const REVOKE_PRIVATE_TOKEN_MUTATION = "\n mutation RevokeHeadStorefrontToken($id: ID!) {\n revokeHeadStorefrontToken(id: $id) { \n id\n headId\n name\n prefix\n createdAt\n lastUsedAt\n revokedAt\n }\n }\n";
111
+ /**
112
+ * Private storefront tokens per head (spec §6.3, §6.7, §15.21).
113
+ *
114
+ * A server-side storefront sends its token with
115
+ * `createStorefrontClient({ storeCode, privateToken, buyerIp })` from
116
+ * `@sixtyninecommerce/client`. It grants NO permission: it gives that server its
117
+ * own rate-limit budget and makes the platform key per-shopper budgets (and the
118
+ * form limiter) on the shopper address the server forwards. Never ship it to a
119
+ * browser — the storefront SDK refuses to (`CREDENTIAL_IN_BROWSER`).
120
+ *
121
+ * Read with `ReadSettings`, issued and revoked with `UpdateSettings`. At most 10
122
+ * live tokens per head (`HEAD_STOREFRONT_TOKEN_LIMIT`), which is room to rotate:
123
+ * issue the new one, deploy it, revoke the old. `issue` takes no idempotency key
124
+ * — its response is the credential, and no replay store may keep it.
125
+ */
126
+ export interface HeadPrivateTokensModule {
127
+ /** A head's tokens, newest first, revoked ones included. `HEAD_NOT_FOUND` for another store's head. */
128
+ list(headId: string, options?: Call): Promise<HeadStorefrontToken[]>;
129
+ /**
130
+ * Issue a token for a head. The plaintext is in `token`, once; it cannot be
131
+ * read again. Refused `INVALID_STOREFRONT_TOKEN_NAME` (1-100 characters, no
132
+ * control characters) or `HEAD_STOREFRONT_TOKEN_LIMIT`.
133
+ */
134
+ issue(headId: string, input: {
135
+ name: string;
136
+ }, options?: Call): Promise<IssuedHeadStorefrontToken>;
137
+ /**
138
+ * Revoke a token. Terminal and idempotent. Every service stops honouring it
139
+ * within 15 seconds; the store's public channel token is untouched.
140
+ * `HEAD_STOREFRONT_TOKEN_NOT_FOUND` for another store's token.
141
+ */
142
+ revoke(id: string, options?: Call): Promise<HeadStorefrontToken>;
143
+ }
86
144
  /**
87
145
  * The store's head registry (spec §6.7).
88
146
  *
@@ -99,5 +157,7 @@ export interface HeadsModule {
99
157
  delete(id: string, options?: Call): Promise<boolean>;
100
158
  /** Move the store's default head. There is no way to unset one, only to move it. */
101
159
  setDefault(id: string, options?: Call): Promise<Head>;
160
+ /** Each head's private storefront tokens (`s69p_`). */
161
+ privateTokens: HeadPrivateTokensModule;
102
162
  }
103
163
  export declare function createHeadsModule(transport: Transport): HeadsModule;
@@ -44,6 +44,33 @@ export const SET_DEFAULT_MUTATION = /* GraphQL */ `
44
44
  setDefaultChannelHead(id: $id) { ${HEAD_FIELDS} }
45
45
  }
46
46
  `;
47
+ export const STOREFRONT_TOKEN_FIELDS = /* GraphQL */ `
48
+ id
49
+ headId
50
+ name
51
+ prefix
52
+ createdAt
53
+ lastUsedAt
54
+ revokedAt
55
+ `;
56
+ export const PRIVATE_TOKENS_QUERY = /* GraphQL */ `
57
+ query HeadStorefrontTokens($headId: ID!) {
58
+ headStorefrontTokens(headId: $headId) { ${STOREFRONT_TOKEN_FIELDS} }
59
+ }
60
+ `;
61
+ export const ISSUE_PRIVATE_TOKEN_MUTATION = /* GraphQL */ `
62
+ mutation IssueHeadStorefrontToken($headId: ID!, $name: String!) {
63
+ issueHeadStorefrontToken(headId: $headId, name: $name) {
64
+ token
65
+ record { ${STOREFRONT_TOKEN_FIELDS} }
66
+ }
67
+ }
68
+ `;
69
+ export const REVOKE_PRIVATE_TOKEN_MUTATION = /* GraphQL */ `
70
+ mutation RevokeHeadStorefrontToken($id: ID!) {
71
+ revokeHeadStorefrontToken(id: $id) { ${STOREFRONT_TOKEN_FIELDS} }
72
+ }
73
+ `;
47
74
  export function createHeadsModule(transport) {
48
75
  return {
49
76
  async list(options) {
@@ -70,5 +97,23 @@ export function createHeadsModule(transport) {
70
97
  const data = await transport.graphql({ api: 'admin', query: SET_DEFAULT_MUTATION, variables: { id } }, options);
71
98
  return data.setDefaultChannelHead;
72
99
  },
100
+ privateTokens: {
101
+ async list(headId, options) {
102
+ const data = await transport.graphql({ api: 'admin', query: PRIVATE_TOKENS_QUERY, variables: { headId } }, options);
103
+ return data.headStorefrontTokens;
104
+ },
105
+ async issue(headId, input, options) {
106
+ const data = await transport.graphql({
107
+ api: 'admin',
108
+ query: ISSUE_PRIVATE_TOKEN_MUTATION,
109
+ variables: { headId, name: input.name },
110
+ }, options);
111
+ return data.issueHeadStorefrontToken;
112
+ },
113
+ async revoke(id, options) {
114
+ const data = await transport.graphql({ api: 'admin', query: REVOKE_PRIVATE_TOKEN_MUTATION, variables: { id } }, options);
115
+ return data.revokeHeadStorefrontToken;
116
+ },
117
+ },
73
118
  };
74
119
  }
@@ -0,0 +1,90 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /**
4
+ * Provider assignments (spec §8.14 "Tenants assign providers").
5
+ *
6
+ * An installed app becomes this store's `tax`, `payment` or `fulfillment`
7
+ * provider by assignment. Validation and rate hooks need none: they apply to
8
+ * every order once the app is installed.
9
+ *
10
+ * - One `tax` provider per store.
11
+ * - One `fulfillment` provider per stock location.
12
+ * - One `payment` assignment per installation. Each backs its own payment
13
+ * method, so a store can offer several payment apps.
14
+ *
15
+ * Assigning needs `InstallApps` AND the kind's permission: `UpdateTaxRate`,
16
+ * `UpdatePaymentMethod` or `UpdateStockLocation`. Reading needs
17
+ * `ReadSettings`. Every change emits `app.provider_assignment_changed`.
18
+ * Uninstalling the app removes its assignments.
19
+ */
20
+ export type AppProviderKind = 'tax' | 'payment' | 'fulfillment';
21
+ export interface AppProviderAssignment {
22
+ id: string;
23
+ kind: AppProviderKind;
24
+ installationId: string;
25
+ /** The installation's app slug. */
26
+ appSlug: string;
27
+ /** The stock location a `fulfillment` provider serves; `null` for the other kinds. */
28
+ locationId: string | null;
29
+ /** The payment method a `payment` provider backs, once one exists; `null` otherwise. */
30
+ paymentMethodId: string | null;
31
+ /** ISO-8601. */
32
+ createdAt: string;
33
+ /** ISO-8601; `null` while the assignment is live. */
34
+ removedAt: string | null;
35
+ }
36
+ export interface AssignAppProviderInput {
37
+ installationId: string;
38
+ kind: AppProviderKind;
39
+ /** Required for `fulfillment`, refused for the other kinds. */
40
+ locationId?: string;
41
+ /**
42
+ * Required for `payment`, refused for the other kinds. `code` is lower-case
43
+ * letters, digits and single hyphens, at most 64 characters.
44
+ */
45
+ paymentMethod?: {
46
+ code: string;
47
+ name: string;
48
+ };
49
+ }
50
+ export interface AppProviderAssignmentsModule {
51
+ /**
52
+ * The store's live assignments, oldest first; optionally one kind. Not
53
+ * paginated: a store holds at most one tax provider, one fulfillment
54
+ * provider per location and one payment assignment per installation.
55
+ */
56
+ list(options?: {
57
+ kind?: AppProviderKind;
58
+ } & Call): Promise<AppProviderAssignment[]>;
59
+ /**
60
+ * Make an installed app this store's provider of `kind`. Refusals:
61
+ * - `PermissionDeniedError` `PROVIDER_ASSIGNMENT_NOT_ALLOWED`: the caller lacks the kind's permission.
62
+ * - `NotFoundError` `APP_INSTALLATION_NOT_FOUND`: no such installation on this store.
63
+ * - `BusinessRuleError` `INSTALLATION_NOT_ACTIVE`: not active, or its app is suspended.
64
+ * - `BusinessRuleError` `HOOK_NOT_DECLARED`: the app does not declare `tax.calculate`,
65
+ * `payment.provider` or `fulfillment.service`, whichever the kind needs.
66
+ * - `BusinessRuleError` `HOOK_NOT_APPROVED`: the app declares that hook, but this store has not
67
+ * approved it yet (the installation awaits consent for it); approve its consent first.
68
+ * - `ConflictError` `PROVIDER_ALREADY_ASSIGNED`: the slot is taken; unassign the holder first.
69
+ * - `ValidationError` `LOCATION_NOT_IN_CHANNEL`: the location is not one of this store's.
70
+ * - `BusinessRuleError` `LOCATION_NOT_GRANTED`: the installation is restricted to other locations.
71
+ * - `PermissionDeniedError` `LOCATION_NOT_ALLOWED`: your own credential is restricted to other
72
+ * locations (also on `unassign`).
73
+ * - `ValidationError` `PROVIDER_ASSIGNMENT_INPUT_INVALID`: a location or payment method
74
+ * missing or given where the kind does not take it.
75
+ *
76
+ * With `idempotencyKey`, a repeat within 24 h answers the first assignment.
77
+ */
78
+ assign(input: AssignAppProviderInput, options?: Call): Promise<AppProviderAssignment>;
79
+ /**
80
+ * Remove an assignment. Needs the same permissions as assigning it.
81
+ * Removing twice answers the removed assignment; an unknown id is
82
+ * `NotFoundError` `PROVIDER_ASSIGNMENT_NOT_FOUND`.
83
+ */
84
+ unassign(id: string, options?: Call): Promise<AppProviderAssignment>;
85
+ }
86
+ export declare const ASSIGNMENT_FIELDS = "\n id\n kind\n installationId\n appSlug\n locationId\n paymentMethodId\n createdAt\n removedAt\n";
87
+ export declare const LIST_QUERY = "\n query AppProviderAssignments($kind: String) {\n appProviderAssignments(kind: $kind) { \n id\n kind\n installationId\n appSlug\n locationId\n paymentMethodId\n createdAt\n removedAt\n }\n }\n";
88
+ export declare const ASSIGN_MUTATION = "\n mutation AssignAppProvider($input: AssignAppProviderInput!) {\n assignAppProvider(input: $input) { \n id\n kind\n installationId\n appSlug\n locationId\n paymentMethodId\n createdAt\n removedAt\n }\n }\n";
89
+ export declare const UNASSIGN_MUTATION = "\n mutation UnassignAppProvider($id: ID!) {\n unassignAppProvider(id: $id) { \n id\n kind\n installationId\n appSlug\n locationId\n paymentMethodId\n createdAt\n removedAt\n }\n }\n";
90
+ export declare function createAppProviderAssignmentsModule(transport: Transport): AppProviderAssignmentsModule;