@sixtyninecommerce/admin 0.8.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 +234 -9
  104. package/dist/payments/index.js +90 -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
@@ -1,4 +1,6 @@
1
1
  import { type Page, type Transport } from '@sixtyninecommerce/core';
2
+ import { type CatalogueOwnership } from '../catalog/types.js';
3
+ import { type ComplianceRequest } from '../compliance/index.js';
2
4
  import type { Call, ListOptions } from '../types.js';
3
5
  /**
4
6
  * Customers — the commerce master record (headless spec §6.5).
@@ -16,6 +18,31 @@ import type { Call, ListOptions } from '../types.js';
16
18
  * 2. **`erase` is a scrub, not a delete.** It removes the person and keeps the
17
19
  * record, the orders and the documents. It is the only destructive customer
18
20
  * operation the platform has.
21
+ *
22
+ * ## Acting inside a customer's cart: `actAs`
23
+ *
24
+ * `actAs(customerId, { reason })` answers a trusted-agent session: a Shop API
25
+ * bearer for that customer, valid 15 minutes and never renewed. It is usable
26
+ * in exactly ONE way — as the customer token of a storefront client for the
27
+ * same store:
28
+ *
29
+ * ```ts
30
+ * const session = await admin.customers.actAs(customerId, { reason: 'Phone order' });
31
+ * const shop = await createStorefrontClient({
32
+ * storeCode: 'my-store',
33
+ * cdnUrl,
34
+ * customerToken: session.token, // @sixtyninecommerce/client
35
+ * });
36
+ * await shop.commerce.addToCart(variantId, 2); // the customer's own cart
37
+ * await admin.customers.endAgentSession(session.id);
38
+ * ```
39
+ *
40
+ * The Admin API, the CMS and the plugins service all refuse it. On the Shop
41
+ * API it may run cart and checkout only; an address-book edit, a password or
42
+ * email change, order history beyond the active order, or anything else is
43
+ * refused with `PermissionDeniedError` `AGENT_SESSION_SCOPE`. Every order row
44
+ * it writes records the caller as `on_behalf_of`; the customer sees the order
45
+ * as their own.
19
46
  */
20
47
  export declare const CUSTOMER_FIELDS = "\n id\n createdAt\n updatedAt\n title\n firstName\n lastName\n emailAddress\n phoneNumber\n groups {\n id\n name\n }\n";
21
48
  export declare const CUSTOMER_ADDRESS_FIELDS = "\n id\n fullName\n company\n streetLine1\n streetLine2\n city\n province\n postalCode\n country {\n code\n name\n }\n phoneNumber\n defaultShippingAddress\n defaultBillingAddress\n";
@@ -25,15 +52,48 @@ export declare const ADDRESSES_QUERY = "\n query CustomerAddresses($id: ID!) {\
25
52
  /**
26
53
  * The CHANNEL-SCOPED creation mutation. See the module docblock — core
27
54
  * `createCustomer` is not an option, and this SDK exposes no path to it.
55
+ *
56
+ * No `password` argument: the server ignores one (and a later release refuses
57
+ * it with `CUSTOMER_PASSWORD_NOT_ALLOWED`), because staff and API tokens never
58
+ * set a customer's password. `invite` is how a customer gets one.
28
59
  */
29
- export declare const CREATE_MUTATION = "\n mutation CreateChannelCustomer($input: CreateCustomerInput!, $password: String) {\n createChannelCustomer(input: $input, password: $password) {\n id\n emailAddress\n createdNew\n }\n }\n";
60
+ export declare const CREATE_MUTATION = "\n mutation CreateChannelCustomer($input: CreateCustomerInput!) {\n createChannelCustomer(input: $input) {\n id\n emailAddress\n createdNew\n }\n }\n";
30
61
  export declare const UPDATE_MUTATION = "\n mutation UpdateCustomer($input: UpdateCustomerInput!) {\n updateCustomer(input: $input) {\n ... on Customer { \n id\n createdAt\n updatedAt\n title\n firstName\n lastName\n emailAddress\n phoneNumber\n groups {\n id\n name\n }\n }\n ... on ErrorResult { errorCode message }\n }\n }\n";
31
62
  export declare const INVITE_MUTATION = "\n mutation InviteCustomer($customerId: ID!, $mode: CustomerInviteMode) {\n inviteCustomer(customerId: $customerId, mode: $mode) {\n customerId\n emailAddress\n mode\n sentAt\n }\n }\n";
32
63
  export declare const RESEND_INVITE_MUTATION = "\n mutation ResendCustomerInvitation($customerId: ID!, $mode: CustomerInviteMode) {\n resendCustomerInvitation(customerId: $customerId, mode: $mode) {\n customerId\n emailAddress\n mode\n sentAt\n }\n }\n";
64
+ /**
65
+ * "Invite all trade customers" — the whole channel's B2B-group members in one
66
+ * call. Counts always come back; unless `dryRun`, a background job sends the
67
+ * invitations and `jobId` names it.
68
+ */
69
+ export declare const INVITE_B2B_MUTATION = "\n mutation InviteB2BCustomers($input: InviteB2BCustomersInput) {\n inviteB2BCustomers(input: $input) {\n dryRun\n total\n eligible\n skippedHasPassword\n skippedPending\n skippedUnreachable\n jobId\n }\n }\n";
33
70
  export declare const ERASE_MUTATION = "\n mutation EraseCustomer(\n $customerId: ID!\n $reason: String!\n $force: Boolean\n $idempotencyKey: String\n ) {\n eraseCustomer(\n customerId: $customerId\n reason: $reason\n force: $force\n idempotencyKey: $idempotencyKey\n ) {\n customerId\n erasedAt\n ordersRetained\n documentsRetained\n forced\n reason\n }\n }\n";
34
71
  export declare const ERASURE_QUERY = "\n query CustomerErasure($customerId: ID!) {\n customerErasure(customerId: $customerId) {\n customerId\n erasedAt\n ordersRetained\n documentsRetained\n forced\n reason\n }\n }\n";
72
+ export declare const ACT_AS_MUTATION = "\n mutation ActAsCustomer($customerId: ID!, $reason: String!) {\n actAsCustomer(customerId: $customerId, reason: $reason) {\n id\n customerId\n token\n expiresAt\n }\n }\n";
73
+ export declare const REQUEST_DATA_MUTATION = "\n mutation RequestCustomerData($customerId: ID!, $reason: String!, $idempotencyKey: String) {\n requestCustomerData(customerId: $customerId, reason: $reason, idempotencyKey: $idempotencyKey) {\n \n id\n kind\n subjectId\n requestedBy\n reason\n requestedAt\n dueAt\n addressedInstallationIds\n acknowledgements { id requestId installationId acknowledgedAt note }\n overdueInstallationIds\n overdue\n\n }\n }\n";
74
+ export declare const END_AGENT_SESSION_MUTATION = "\n mutation EndCustomerAgentSession($sessionId: ID!) {\n endCustomerAgentSession(sessionId: $sessionId) {\n id\n customerId\n revokedAt\n }\n }\n";
35
75
  export declare const ADD_TO_GROUP_MUTATION = "\n mutation AddCustomersToGroup($customerGroupId: ID!, $customerIds: [ID!]!) {\n addCustomersToGroup(customerGroupId: $customerGroupId, customerIds: $customerIds) {\n id\n name\n }\n }\n";
36
76
  export declare const REMOVE_FROM_GROUP_MUTATION = "\n mutation RemoveCustomersFromGroup($customerGroupId: ID!, $customerIds: [ID!]!) {\n removeCustomersFromGroup(customerGroupId: $customerGroupId, customerIds: $customerIds) {\n id\n name\n }\n }\n";
77
+ /**
78
+ * External keys (incentives program decision 7): when another system is the customer master it
79
+ * addresses this store's customers and segments by its own keys. Every key is this store's: another
80
+ * store's key reads as one nobody filed. Each document is its own request, so a client ahead of the
81
+ * engine fails only that call, never the customer page.
82
+ */
83
+ export declare const CUSTOMER_EXTERNAL_REF_FIELDS = "\n id\n createdAt\n updatedAt\n system\n ref\n";
84
+ export declare const EXTERNAL_KEYED_GROUP_FIELDS = "\n id\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n";
85
+ export declare const BY_EXTERNAL_REF_QUERY = "\n query CustomerByExternalRef($system: String!, $ref: String!) {\n customerByExternalRef(system: $system, ref: $ref) { \n id\n createdAt\n updatedAt\n title\n firstName\n lastName\n emailAddress\n phoneNumber\n groups {\n id\n name\n }\n }\n }\n";
86
+ export declare const EXTERNAL_REFS_QUERY = "\n query CustomerExternalRefs($id: ID!) {\n customer(id: $id) {\n id\n externalRefs { \n id\n createdAt\n updatedAt\n system\n ref\n }\n }\n }\n";
87
+ export declare const UPSERT_BY_EXTERNAL_REF_MUTATION = "\n mutation UpsertCustomerByExternalRef(\n $input: UpsertCustomerByExternalRefInput!\n $idempotencyKey: String\n ) {\n upsertCustomerByExternalRef(input: $input, idempotencyKey: $idempotencyKey) {\n customer { \n id\n createdAt\n updatedAt\n title\n firstName\n lastName\n emailAddress\n phoneNumber\n groups {\n id\n name\n }\n }\n created\n matchedByEmail\n updated\n }\n }\n";
88
+ export declare const GROUP_BY_EXTERNAL_KEY_QUERY = "\n query CustomerGroupByExternalKey($key: String!) {\n customerGroupByExternalKey(key: $key) { \n id\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
89
+ /** A customer group as {@link CustomerGroupsModule.get} reads it. */
90
+ export declare const CUSTOMER_GROUP_FIELDS = "\n id\n createdAt\n updatedAt\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n";
91
+ export declare const CUSTOMER_GROUP_QUERY = "\n query CustomerGroup($id: ID!) {\n customerGroup(id: $id) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
92
+ export declare const GROUP_EXTERNAL_KEY_QUERY = "\n query CustomerGroupExternalKey($id: ID!) {\n customerGroup(id: $id) {\n id\n externalKey\n }\n }\n";
93
+ export declare const UPSERT_GROUP_BY_EXTERNAL_KEY_MUTATION = "\n mutation UpsertCustomerGroupByExternalKey($key: String!, $name: String!) {\n upsertCustomerGroupByExternalKey(key: $key, name: $name) {\n customerGroup { \n id\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n created\n renamed\n }\n }\n";
94
+ export declare const REPLACE_GROUP_MEMBERS_MUTATION = "\n mutation ReplaceCustomerGroupMembers(\n $key: String!\n $customerExternalRefs: [CustomerExternalRefInput!]!\n $idempotencyKey: String\n ) {\n replaceCustomerGroupMembers(\n key: $key\n customerExternalRefs: $customerExternalRefs\n idempotencyKey: $idempotencyKey\n ) {\n customerGroup { \n id\n name\n externalKey\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n memberCount\n added\n removed\n unknownRefs {\n system\n ref\n }\n }\n }\n";
95
+ /** The most member keys one `customers.groups.replaceMembers` call takes. */
96
+ export declare const MAX_REPLACE_GROUP_MEMBERS = 5000;
37
97
  export declare const CREATE_ADDRESS_MUTATION = "\n mutation CreateCustomerAddress($customerId: ID!, $input: CreateAddressInput!) {\n createCustomerAddress(customerId: $customerId, input: $input) {\n \n id\n fullName\n company\n streetLine1\n streetLine2\n city\n province\n postalCode\n country {\n code\n name\n }\n phoneNumber\n defaultShippingAddress\n defaultBillingAddress\n\n }\n }\n";
38
98
  export declare const UPDATE_ADDRESS_MUTATION = "\n mutation UpdateCustomerAddress($input: UpdateAddressInput!) {\n updateCustomerAddress(input: $input) { \n id\n fullName\n company\n streetLine1\n streetLine2\n city\n province\n postalCode\n country {\n code\n name\n }\n phoneNumber\n defaultShippingAddress\n defaultBillingAddress\n }\n }\n";
39
99
  export declare const DELETE_ADDRESS_MUTATION = "\n mutation DeleteCustomerAddress($id: ID!) {\n deleteCustomerAddress(id: $id) { success }\n }\n";
@@ -41,6 +101,93 @@ export interface CustomerGroupRef {
41
101
  id: string;
42
102
  name: string;
43
103
  }
104
+ /** Another system's key for a customer of this store. */
105
+ export interface CustomerExternalRef {
106
+ id: string;
107
+ createdAt: string;
108
+ updatedAt: string;
109
+ /** The master system's name: a lower-case slug such as `visos`. */
110
+ system: string;
111
+ /** The master system's own id for the customer, compared exactly. */
112
+ ref: string;
113
+ [extra: string]: unknown;
114
+ }
115
+ /** A customer key as sent: which system, and its id there. */
116
+ export interface CustomerExternalRefKey {
117
+ system: string;
118
+ ref: string;
119
+ }
120
+ /**
121
+ * A customer as the master system holds it. Every field but the key is a PATCH: absent or `null`
122
+ * keeps what the store has.
123
+ */
124
+ export interface UpsertCustomerByExternalRefInput {
125
+ /** A lower-case slug: letters, digits, `.`, `_`, `-`, at most 64 characters. */
126
+ system: string;
127
+ /** At most 255 characters; compared exactly, surrounding whitespace trimmed. */
128
+ ref: string;
129
+ /**
130
+ * Required when the store does not know the key yet. A customer of this store with this address
131
+ * (any letter case) is LINKED to the key rather than duplicated. On a customer the key already
132
+ * names, a changed address follows {@link UpdateCustomerInput.emailAddress}'s rules.
133
+ */
134
+ emailAddress?: string | null;
135
+ firstName?: string | null;
136
+ lastName?: string | null;
137
+ phoneNumber?: string | null;
138
+ title?: string | null;
139
+ }
140
+ /** What {@link CustomersModule.upsertByExternalRef} answers. */
141
+ export interface UpsertedCustomer {
142
+ customer: Customer;
143
+ /** A customer was created for this store. */
144
+ created: boolean;
145
+ /** The key was unknown and linked to the store's existing customer with the address. */
146
+ matchedByEmail: boolean;
147
+ /** Fields of an existing customer were written. */
148
+ updated: boolean;
149
+ }
150
+ /** One of the store's customer groups, as {@link CustomerGroupsModule.get} answers it. */
151
+ export interface CustomerGroup {
152
+ id: string;
153
+ /** ISO-8601 UTC. */
154
+ createdAt: string;
155
+ /** ISO-8601 UTC. */
156
+ updatedAt: string;
157
+ name: string;
158
+ /** This store's key for the group in the customer master, or `null` when no master keys it. */
159
+ externalKey: string | null;
160
+ /**
161
+ * The store's own group, or its organization's (shared while the organization shares
162
+ * `customer_groups`); see {@link CatalogueOwnership}. `editableHere` false: rename it in the
163
+ * workspace.
164
+ */
165
+ catalogueOwnership: CatalogueOwnership;
166
+ }
167
+ /** A customer group with this store's external key, or `null` for a group nobody keyed. */
168
+ export interface ExternalKeyedCustomerGroup {
169
+ id: string;
170
+ name: string;
171
+ externalKey: string | null;
172
+ /** The store's own group, or its organization's; see {@link CatalogueOwnership}. */
173
+ catalogueOwnership: CatalogueOwnership;
174
+ }
175
+ /** What {@link CustomerGroupsModule.upsertByExternalKey} answers. */
176
+ export interface UpsertedCustomerGroup {
177
+ customerGroup: ExternalKeyedCustomerGroup;
178
+ created: boolean;
179
+ renamed: boolean;
180
+ }
181
+ /** What {@link CustomerGroupsModule.replaceMembers} answers. */
182
+ export interface ReplacedCustomerGroupMembers {
183
+ customerGroup: ExternalKeyedCustomerGroup;
184
+ /** The group's members in this store after the call. */
185
+ memberCount: number;
186
+ added: number;
187
+ removed: number;
188
+ /** Keys no customer of this store carries, in the order sent. Reported, never created. */
189
+ unknownRefs: CustomerExternalRefKey[];
190
+ }
44
191
  export interface Customer {
45
192
  id: string;
46
193
  createdAt: string;
@@ -51,6 +198,14 @@ export interface Customer {
51
198
  emailAddress: string;
52
199
  phoneNumber: string | null;
53
200
  groups: CustomerGroupRef[];
201
+ /**
202
+ * Set when this customer was read by an app installation below `customerData:
203
+ * full`: the fields its level withholds came back blanked — `null`, or `""`
204
+ * where the field is never null (`firstName`, `lastName`, `emailAddress`). An
205
+ * empty string here means "not granted", not "none on record". Absent for a
206
+ * customer nothing was withheld from.
207
+ */
208
+ redacted?: true;
54
209
  /** Forward-compat: fields a later release adds arrive camelised and untouched. */
55
210
  [extra: string]: unknown;
56
211
  }
@@ -71,6 +226,12 @@ export interface CustomerAddress {
71
226
  defaultShippingAddress: boolean | null;
72
227
  defaultBillingAddress: boolean | null;
73
228
  [extra: string]: unknown;
229
+ /**
230
+ * Set when the caller's customer-data level blanked this address (an app
231
+ * installation below `full` reads only its country): `null`/`""` then means
232
+ * "not granted", not "none on record". Never write such an address back.
233
+ */
234
+ redacted?: true;
74
235
  }
75
236
  export interface CreateCustomerInput {
76
237
  emailAddress: string;
@@ -85,6 +246,23 @@ export interface UpdateCustomerInput {
85
246
  firstName?: string;
86
247
  lastName?: string;
87
248
  phoneNumber?: string;
249
+ /**
250
+ * A new address for the customer in THIS store. For a customer with an account, the store's
251
+ * customer (orders, addresses, groups, prices, credit, ERP links) moves to the account of the
252
+ * new address: the one that already exists for it on the platform, else a new one with no
253
+ * password. No account is ever renamed, nothing is mailed, and the old address stops signing in
254
+ * to this store; send `invite` for the new address to sign in. The customer's records in other
255
+ * stores keep their address and account. A guest customer (no account) simply gets the address.
256
+ * Sending the current address back unchanged (any letter case) is not a change and passes.
257
+ *
258
+ * Refused, for every caller: `PermissionDeniedError` `CUSTOMER_EMAIL_CHANGE_NOT_ALLOWED` for an
259
+ * erased customer (add a new customer instead), `PermissionDeniedError`
260
+ * `SHARED_CUSTOMER_NOT_ALLOWED` for one customer record another store holds too, and
261
+ * `BusinessRuleError` `EMAIL_ADDRESS_CONFLICT_ERROR` when something else in this store already
262
+ * answers to the address (another customer, or another account's sign-in), and
263
+ * `ValidationError` `CUSTOMER_EMAIL_INVALID` for a value that is not an email address given to a
264
+ * customer with an account, or any value longer than 255 characters.
265
+ */
88
266
  emailAddress?: string;
89
267
  customFields?: Record<string, unknown>;
90
268
  }
@@ -98,6 +276,11 @@ export interface CreatedCustomer {
98
276
  * whole point of the channel-scoped mutation.
99
277
  */
100
278
  createdNew: boolean;
279
+ /**
280
+ * Set when the caller's customer-data level blanked `emailAddress` (an app
281
+ * installation below `identity`): "" then means "not granted".
282
+ */
283
+ redacted?: true;
101
284
  }
102
285
  export type CustomerInviteMode = 'PASSWORD_SETUP' | 'MAGIC_LINK';
103
286
  export interface CustomerInvitation {
@@ -106,6 +289,32 @@ export interface CustomerInvitation {
106
289
  mode: CustomerInviteMode;
107
290
  sentAt: string;
108
291
  }
292
+ export interface InviteB2BCustomersOptions extends Call {
293
+ /** Count only; send nothing and start no job. */
294
+ dryRun?: boolean;
295
+ /**
296
+ * Also invite customers who already hold a live, unexpired set-password link.
297
+ * Their link is re-sent, not replaced, so the one already in their inbox keeps
298
+ * working. Off by default: they are counted as `skippedPending` instead.
299
+ */
300
+ includePending?: boolean;
301
+ }
302
+ /** What {@link CustomersModule.inviteB2B} answers. */
303
+ export interface InviteB2BCustomersResult {
304
+ dryRun: boolean;
305
+ /** B2B-group customers on this store, one per login account. */
306
+ total: number;
307
+ /** Customers who would be (dry run) or were queued for an invitation. */
308
+ eligible: number;
309
+ /** Already have a password. */
310
+ skippedHasPassword: number;
311
+ /** Hold a live, unexpired set-password link; always 0 with `includePending`. */
312
+ skippedPending: number;
313
+ /** No email address, a `*.invalid` placeholder address, or no login account. */
314
+ skippedUnreachable: number;
315
+ /** The background job sending the invitations; `null` on a dry run or when `eligible` is 0. */
316
+ jobId: string | null;
317
+ }
109
318
  /** The record of an erasure. See {@link CustomersModule.erase}. */
110
319
  export interface CustomerErasureRecord {
111
320
  customerId: string;
@@ -137,6 +346,37 @@ export interface EraseCustomerOptions extends Call {
137
346
  /** Your own request id, recorded for provenance. */
138
347
  idempotencyKey?: string;
139
348
  }
349
+ /** A trusted-agent session. See {@link CustomersModule.actAs}. */
350
+ export interface CustomerAgentSession {
351
+ /** What {@link CustomersModule.endAgentSession} takes. */
352
+ id: string;
353
+ customerId: string;
354
+ /**
355
+ * The Shop API bearer, shown once. Pass it as `customerToken` to
356
+ * `createStorefrontClient` from `@sixtyninecommerce/client` — the only way it works.
357
+ */
358
+ token: string;
359
+ /** ISO-8601. Fixed: refused from this instant on, and nothing renews it. */
360
+ expiresAt: string;
361
+ }
362
+ /** The answer to {@link CustomersModule.endAgentSession}. */
363
+ export interface EndedCustomerAgentSession {
364
+ id: string;
365
+ customerId: string;
366
+ /** ISO-8601. Ending an ended session answers the first end. */
367
+ revokedAt: string;
368
+ }
369
+ export interface RequestCustomerDataOptions extends Call {
370
+ /**
371
+ * Why — recorded on the request, never sent to apps, blanked if the customer
372
+ * is later erased. 1–1000 characters.
373
+ */
374
+ reason: string;
375
+ }
376
+ export interface ActAsCustomerOptions extends Call {
377
+ /** Why — stored on the session and required. Free text, 1–1000 characters. */
378
+ reason: string;
379
+ }
140
380
  export interface CustomerAddressesModule {
141
381
  list(customerId: string, options?: Call): Promise<CustomerAddress[]>;
142
382
  create(customerId: string, input: Record<string, unknown>, options?: Call): Promise<CustomerAddress>;
@@ -146,20 +386,72 @@ export interface CustomerAddressesModule {
146
386
  delete(id: string, options?: Call): Promise<void>;
147
387
  }
148
388
  export interface CustomerGroupsModule {
149
- /** Add customers to a group. Emits one `customer.group_changed` per customer. */
389
+ /**
390
+ * One of the ACTIVE store's customer groups by id, its `catalogueOwnership` included, or `null`
391
+ * for a group the store does not hold (another store's, a deleted one, an unknown id), the same
392
+ * for each, like every other entity `get` here. A badge shows `null` as "not found", never as
393
+ * the store's own group. An id that is not a uuid is refused before the engine looks with core's
394
+ * `ENTITY_NOT_FOUND`: branch on the `NotFoundError` class, not the code. Needs `ReadCustomer` or `ReadCustomerGroup`. Requires
395
+ * a platform release that serves `catalogueOwnership`: an older engine fails the call as a whole.
396
+ */
397
+ get(id: string, options?: Call): Promise<CustomerGroup | null>;
398
+ /**
399
+ * Add customers to a group. Emits one `customer.group_changed` per customer added; a customer
400
+ * already in the group is left as it is and emits nothing, so naming only members emits none.
401
+ */
150
402
  add(groupId: string, customerIds: string[], options?: Call): Promise<CustomerGroupRef>;
151
403
  remove(groupId: string, customerIds: string[], options?: Call): Promise<CustomerGroupRef>;
404
+ /** The store's group under the master system's segment key, or `null`. Needs `ReadCustomerGroup`. */
405
+ byExternalKey(key: string, options?: Call): Promise<ExternalKeyedCustomerGroup | null>;
406
+ /** This store's external key for a group, or `null` when no master keys it. */
407
+ externalKey(groupId: string, options?: Call): Promise<string | null>;
408
+ /**
409
+ * Create the store's group for a segment key, or rename the one it names. Twice is ONE group.
410
+ * Creating needs `CreateCustomerGroup`; renaming needs `UpdateCustomerGroup`, and for a group
411
+ * other stores hold too, that permission in each of them (`SHARED_CUSTOMER_GROUP_NOT_ALLOWED`).
412
+ * Renaming a group a member store holds from its organization's workspace is refused
413
+ * `MANAGED_BY_ORGANIZATION` (422 `BusinessRuleError`): rename it in the workspace
414
+ * (`catalogueOwnership.editableHere` is false).
415
+ */
416
+ upsertByExternalKey(key: string, name: string, options?: Call): Promise<UpsertedCustomerGroup>;
417
+ /**
418
+ * Make the group under `key` hold EXACTLY the customers these keys name, in one transaction.
419
+ * At most {@link MAX_REPLACE_GROUP_MEMBERS} keys (`CUSTOMER_GROUP_MEMBERS_TOO_MANY`); a key no
420
+ * customer of this store carries comes back in `unknownRefs` and is never created; only this
421
+ * store's customers are added or removed. Needs `UpdateCustomerGroup`. A repeat under the same
422
+ * `idempotencyKey` answers the first call. Emits one `customer.group_changed` per customer
423
+ * added or removed, and a promotion on the group follows on the order's next recalculation.
424
+ *
425
+ * Known limit: atomic against other replaces and upserts of the same key, not against
426
+ * `add`/`remove` (core's membership doors) running at the same moment, which do not take the
427
+ * key's lock: a membership edit made by hand meanwhile can survive the replace or be undone.
428
+ */
429
+ replaceMembers(key: string, customerExternalRefs: CustomerExternalRefKey[], options?: Call): Promise<ReplacedCustomerGroupMembers>;
152
430
  }
153
431
  export interface CustomersModule {
154
432
  list(options?: ListOptions): Promise<Page<Customer>>;
155
433
  get(id: string, options?: Call): Promise<Customer | null>;
156
434
  /**
157
- * Create a customer ON THIS CHANNEL. Never creates credentials — `invite`
158
- * does that.
435
+ * Create a customer ON THIS CHANNEL, with no usable login: the customer
436
+ * cannot sign in until they set a password through the link `invite` mails
437
+ * them (or sign in with a magic link). Staff and API tokens never set a
438
+ * customer's password, so there is no option for one.
439
+ */
440
+ create(input: CreateCustomerInput, options?: Call): Promise<CreatedCustomer>;
441
+ /**
442
+ * Update a customer of this store. A changed `emailAddress` of a customer with an account moves
443
+ * the store's customer to the account of the new address, and is refused for an erased customer
444
+ * (`CUSTOMER_EMAIL_CHANGE_NOT_ALLOWED`), for one record another store also holds
445
+ * (`SHARED_CUSTOMER_NOT_ALLOWED`), both `PermissionDeniedError`s, and for an address the store
446
+ * already uses (`EMAIL_ADDRESS_CONFLICT_ERROR`, a `BusinessRuleError`); see
447
+ * {@link UpdateCustomerInput.emailAddress}.
448
+ *
449
+ * While an app or API token owns Customer details on the store, a change of the customer's
450
+ * title, name, email address, phone number or VAT number by anyone else is refused
451
+ * `FIELD_GROUP_LOCKED` (`BusinessRuleError`); an unchanged value passes. So is adding,
452
+ * changing or removing one of their addresses (`addresses.*`), and deleting the customer.
453
+ * Creating a customer is never refused.
159
454
  */
160
- create(input: CreateCustomerInput, options?: Call & {
161
- password?: string;
162
- }): Promise<CreatedCustomer>;
163
455
  update(id: string, input: UpdateCustomerInput, options?: Call): Promise<Customer>;
164
456
  /**
165
457
  * Mail the customer a way in: a set-password link, or a magic-link sign-in.
@@ -171,6 +463,26 @@ export interface CustomersModule {
171
463
  mode?: CustomerInviteMode;
172
464
  resend?: boolean;
173
465
  }): Promise<CustomerInvitation>;
466
+ /**
467
+ * Invite every trade (B2B) customer on this store to set a password: the
468
+ * members of the store's configured B2B customer groups, one per login
469
+ * account. Needs `UpdateCustomer`.
470
+ *
471
+ * Always answers the counts. Skipped, by reason: customers who already have a
472
+ * password, customers who cannot be reached (no email address, a `*.invalid`
473
+ * placeholder, no login account), and customers holding a live set-password
474
+ * link (unless `includePending`). With `dryRun` nothing is sent. Otherwise a
475
+ * background job sends the invitations one at a time, paced, and `jobId`
476
+ * names it; it checks every customer again when it reaches them, so what it
477
+ * finally sends can differ from `eligible`. Its result is
478
+ * `{ sent, skippedHasPassword, skippedPending, skippedUnreachable, failed }`,
479
+ * and one customer failing never stops the rest.
480
+ *
481
+ * Refused on the platform root channel, and — unless `dryRun` — when the store
482
+ * has no storefront URL or its customer-invitation email is switched off or
483
+ * has no recipient: the same refusals as {@link invite}.
484
+ */
485
+ inviteB2B(options?: InviteB2BCustomersOptions): Promise<InviteB2BCustomersResult>;
174
486
  /**
175
487
  * GDPR erasure: scrub the person, keep the record.
176
488
  *
@@ -187,17 +499,53 @@ export interface CustomersModule {
187
499
  * that name no customer at all, which is how an invoice email log kept an
188
500
  * erased person's address.
189
501
  *
190
- * Refused while a retention hold is active (`RETENTION_HOLD_ACTIVE`), while
191
- * the customer is party to an open unpaid order (`CUSTOMER_HAS_OPEN_ORDERS`;
502
+ * Vendure's own JOB PAYLOADS go too, classified per queue rather than by one
503
+ * verdict over a column that has forty-five shapes: a declared JSON path
504
+ * anchored on the customer id or their address; the whole serialized Express
505
+ * request clone of any job that is theirs, emptied rather than picked over
506
+ * because its body carries the GraphQL document the shopper sent; the session
507
+ * token and principal identifier of a job the erased person themselves
508
+ * triggered (a review or a magic-link login carries their own); and an
509
+ * exact-address sweep for the payloads whose contracts are open. A match by
510
+ * ADDRESS only reaches a row whose payload names this store's channel (the
511
+ * table has no channel column); a match by customer id reaches it whichever
512
+ * channel it names, because an id belongs to one person. A row that
513
+ * names them ONLY as a substring inside that document, with no id to anchor
514
+ * on, is left alone — a substring decision about personal data in a write
515
+ * path is what this feature's own security review forbade.
516
+ * Unbounded, because settled job rows are pruned by
517
+ * COUNT — the newest thousand survive indefinitely — and an unsettled row is
518
+ * never pruned at all. Two gaps, both stated: a job that is RUNNING at that
519
+ * instant holds its payload in memory and writes it back when it settles, and
520
+ * a job's RESULT is retained (a warehouse document job returns a storage key
521
+ * for a PDF that renders the shipping address).
522
+ *
523
+ * Refused while a legal hold placed with `placeRetentionHold` is active
524
+ * (`RETENTION_HOLD_ACTIVE`; the store's retention window, `holdUntil`, does
525
+ * not refuse), while the customer is party to an open unpaid order (`CUSTOMER_HAS_OPEN_ORDERS`;
192
526
  * a SuperAdmin may pass `force`), while they hold a subscription that has not
193
- * ended (`CUSTOMER_HAS_ACTIVE_SUBSCRIPTIONS`), when their login is shared with
194
- * another store on the platform (`CUSTOMER_SHARED_ACROSS_STORES`), and when
195
- * the tenant context names the platform root channel rather than the
196
- * customer's own (`CUSTOMER_ERASURE_CHANNEL_INVALID`).
527
+ * ended (`CUSTOMER_HAS_ACTIVE_SUBSCRIPTIONS`), while the store still holds
528
+ * their money as unapplied cash on their account
529
+ * (`CUSTOMER_HAS_UNAPPLIED_CASH`: pay it back or apply it to an invoice first
530
+ * and wait for the payout's refund to complete, `payments.accountCredit.list`
531
+ * shows what is held; `force` does not override it), when other stores on the
532
+ * platform hold them and the caller cannot delete customers in all of those
533
+ * stores (`CUSTOMER_SHARED_ACROSS_STORES`), and when the tenant context names
534
+ * the platform root channel rather than the customer's own
535
+ * (`CUSTOMER_ERASURE_CHANNEL_INVALID`).
536
+ *
537
+ * A customer other stores hold too (their login backs a customer there, or the
538
+ * row sits there too) is erased on EVERY one of those stores in the same call,
539
+ * because the login it scrubs is theirs as well. That takes a SuperAdmin, or
540
+ * `DeleteCustomer` on each of those stores. Every store's hold, unpaid orders
541
+ * and subscriptions are checked first (a refusal names the store that blocks),
542
+ * and each store gets its own record and `customer.deleted`. The answer is this
543
+ * store's record. When the login also backs a record on the platform root,
544
+ * outside any store, only a SuperAdmin may erase (same code, its own message).
197
545
  *
198
- * `force` overrides the OPEN-ORDER refusal and nothing else. A live payment
199
- * mandate will charge this person again next cycle, and a shared login belongs
200
- * to a merchant who asked for nothing — neither is a SuperAdmin's to waive.
546
+ * `force` overrides the OPEN-ORDER refusal and nothing else: a live payment
547
+ * mandate will charge this person again next cycle, which is not a
548
+ * SuperAdmin's to waive.
201
549
  *
202
550
  * Idempotent: erasing an already-erased customer returns the original record.
203
551
  *
@@ -214,11 +562,74 @@ export interface CustomersModule {
214
562
  *
215
563
  * That event keeps its ids and its `external_refs`: it is the one moment an
216
564
  * ESP, ERP, CRM or PSP can erase its own copy of the person, and blanking it
217
- * would destroy the only keys to them.
565
+ * would destroy the only keys to them. Beside the custom-field ids, each
566
+ * store's event carries the keys a customer master filed there, by system
567
+ * name ({@link upsertByExternalRef}: `{ visos: 'P-123' }`), which the erasure
568
+ * then deletes: a master that knows the person only by its own key finds the
569
+ * record to erase, and its next sync of that key makes a NEW customer.
218
570
  */
219
571
  erase(id: string, options: EraseCustomerOptions): Promise<CustomerErasureRecord>;
572
+ /**
573
+ * A SUBJECT-ACCESS REQUEST: ask every app installed on this store for the
574
+ * personal data it holds about this customer. Emits `customer.data_requested`,
575
+ * which every app installation receives whatever its event filters say; each
576
+ * acknowledges it within 30 days through `admin.compliance.acknowledge`, and
577
+ * `admin.compliance.list` shows who has not. Needs `ReadCustomer` and
578
+ * `UpdateCustomer`. Refused for an erased customer (`CUSTOMER_ERASED`). A
579
+ * repeat under the same `idempotencyKey` answers the first request.
580
+ */
581
+ requestData(id: string, options: RequestCustomerDataOptions): Promise<ComplianceRequest>;
220
582
  /** The erasure record, or `null` if this customer has not been erased. */
221
583
  erasure(id: string, options?: Call): Promise<CustomerErasureRecord | null>;
584
+ /**
585
+ * Act inside a customer's cart: a 15-minute, non-renewable Shop API session
586
+ * for them, bounded to cart and checkout (headless spec §6.5). Needs
587
+ * `UpdateCustomer` AND `UpdateOrder`. Emits `customer.agent_session_started`.
588
+ *
589
+ * THE HANDOFF: the returned `token` is usable only as the `customerToken` of a
590
+ * storefront client for the same store — `createStorefrontClient({ storeCode,
591
+ * cdnUrl, customerToken: session.token })` from `@sixtyninecommerce/client`.
592
+ * The Admin API, the CMS and the plugins service refuse it; on the Shop API
593
+ * anything beyond cart and checkout is refused with `PermissionDeniedError`
594
+ * `AGENT_SESSION_SCOPE`.
595
+ *
596
+ * NOT idempotent, deliberately: every call mints a new session and a new
597
+ * token — the answer carries a credential, and an idempotency store would
598
+ * keep and replay it. Retrying after a timeout at worst leaves an unused
599
+ * session that expires on its own; end the one you are not using.
600
+ *
601
+ * Refused for an erased customer (`CUSTOMER_ERASED`), a guest record with no
602
+ * login (`CUSTOMER_HAS_NO_ACCOUNT`), a customer of another store
603
+ * (`NotFoundError` `CUSTOMER_NOT_FOUND`), the platform root channel and a
604
+ * location-restricted credential (`AGENT_SESSION_LOCATION_NOT_ALLOWED`).
605
+ */
606
+ actAs(customerId: string, options: ActAsCustomerOptions): Promise<CustomerAgentSession>;
607
+ /**
608
+ * End an agent session now. The next Shop API call with its token answers 401.
609
+ * Ending an ended session answers the first end.
610
+ */
611
+ endAgentSession(sessionId: string, options?: Call): Promise<EndedCustomerAgentSession>;
612
+ /**
613
+ * The store's customer under another system's key, or `null` (an unknown key, another store's,
614
+ * or one whose customer was deleted). Needs `ReadCustomer`.
615
+ */
616
+ byExternalRef(system: string, ref: string, options?: Call): Promise<Customer | null>;
617
+ /** This store's external keys for a customer; `[]` for none or for a customer it does not hold. */
618
+ externalRefs(customerId: string, options?: Call): Promise<CustomerExternalRef[]>;
619
+ /**
620
+ * Create the store's customer for a master system's key, or update the one it names. Never a
621
+ * second customer: not on a repeat under the same `idempotencyKey`, not on a concurrent call,
622
+ * and not for an address the store already has (that customer is linked, `matchedByEmail`).
623
+ * Creating needs `CreateCustomer`; linking or changing an existing customer `UpdateCustomer`;
624
+ * answering a customer the key already names `ReadCustomer` or `UpdateCustomer`.
625
+ *
626
+ * Refusals: `CUSTOMER_EXTERNAL_REF_INVALID` (a malformed key),
627
+ * `CUSTOMER_EXTERNAL_REF_EMAIL_INVALID` (a new customer without a usable address),
628
+ * `CUSTOMER_EXTERNAL_REF_CONFLICT` (the customer with that address already carries another key
629
+ * of the same system), `CUSTOMER_EXTERNAL_REF_WRITE_CONFLICT` (a concurrent call won; send it again),
630
+ * and the address rules of {@link update}.
631
+ */
632
+ upsertByExternalRef(input: UpsertCustomerByExternalRefInput, options?: Call): Promise<UpsertedCustomer>;
222
633
  groups: CustomerGroupsModule;
223
634
  addresses: CustomerAddressesModule;
224
635
  }