@sixtyninecommerce/admin 0.9.0 → 0.10.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 +294 -0
- package/dist/catalog/products.d.ts +162 -1
- package/dist/catalog/products.js +94 -1
- package/dist/catalog/variants.d.ts +50 -0
- package/dist/catalog/variants.js +48 -18
- package/dist/client.d.ts +21 -0
- package/dist/client.js +6 -0
- package/dist/fulfillments/index.d.ts +77 -0
- package/dist/fulfillments/index.js +109 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +5 -0
- package/dist/integrations/index.d.ts +14 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/pipelines.d.ts +113 -0
- package/dist/integrations/pipelines.js +62 -0
- package/dist/integrations/types.d.ts +176 -0
- package/dist/integrations/types.js +8 -0
- package/dist/jobs/bulk-action-run.d.ts +11 -2
- package/dist/jobs/bulk-action-run.js +4 -5
- package/dist/orders/bulk.d.ts +75 -0
- package/dist/orders/bulk.js +150 -0
- package/dist/orders/deposit-policy.d.ts +103 -0
- package/dist/orders/deposit-policy.js +202 -0
- package/dist/orders/index.d.ts +80 -0
- package/dist/orders/index.js +13 -0
- package/dist/orders/manual-discounts.d.ts +11 -2
- package/dist/orders/manual-discounts.js +2 -0
- package/dist/payments/index.d.ts +7 -0
- package/dist/payments/index.js +2 -0
- package/dist/payments/methods.d.ts +37 -0
- package/dist/payments/methods.js +33 -0
- package/dist/pricing/index.d.ts +3 -0
- package/dist/pricing/index.js +2 -0
- package/dist/pricing/vouchers.d.ts +103 -0
- package/dist/pricing/vouchers.js +69 -0
- package/dist/returns/index.d.ts +225 -0
- package/dist/returns/index.js +160 -0
- package/dist/search/index.d.ts +99 -0
- package/dist/search/index.js +62 -0
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/** The fewest days `autoCancelAfterDays` may be; `null` means never. */
|
|
4
|
+
export declare const DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN = 1;
|
|
5
|
+
/** The largest `storefront.minimumPercent`: a whole percentage of the order total. */
|
|
6
|
+
export declare const DEPOSIT_POLICY_MINIMUM_PERCENT_MAX = 100;
|
|
7
|
+
/** The most payment method codes `storefront.methods` may name. */
|
|
8
|
+
export declare const DEPOSIT_POLICY_STOREFRONT_METHODS_MAX = 50;
|
|
9
|
+
/** Deposits a SHOPPER pays online at checkout. Applies only while `enabled` is on as well. */
|
|
10
|
+
export interface StorefrontDepositPolicy {
|
|
11
|
+
/** The online switch. */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Payment method CODES a shopper may pay a deposit with: offline methods and
|
|
15
|
+
* Mollie. Empty: none, so no shopper can pay a deposit online.
|
|
16
|
+
*/
|
|
17
|
+
methods: string[];
|
|
18
|
+
/**
|
|
19
|
+
* The least an online deposit may be, as a whole percentage (0–100) of the
|
|
20
|
+
* order's total, judged in the order's currency. Beside `minimum`, not instead
|
|
21
|
+
* of it: the larger of the two applies.
|
|
22
|
+
*/
|
|
23
|
+
minimumPercent: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The store's deposit policy (`Channel.customFields.orderDepositPolicy`): whether
|
|
27
|
+
* staff may take a deposit and park an order (`orders.park`), and whether a
|
|
28
|
+
* shopper may pay one at checkout. Every field present: this is what the engine
|
|
29
|
+
* APPLIES, absent and malformed fields read as their defaults.
|
|
30
|
+
*/
|
|
31
|
+
export interface OrderDepositPolicy {
|
|
32
|
+
/**
|
|
33
|
+
* The store-wide switch. Off: a payment must cover the whole outstanding
|
|
34
|
+
* amount (`payments.record` refuses a part payment `DEPOSITS_NOT_ENABLED`) and
|
|
35
|
+
* no order can be parked.
|
|
36
|
+
*/
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The least a deposit must be before an order can be parked, in minor units of
|
|
40
|
+
* the store's DEFAULT currency; 0 means any deposit will do. An order in another
|
|
41
|
+
* currency is held to it converted at the rate checkout charges with.
|
|
42
|
+
*/
|
|
43
|
+
minimum: number;
|
|
44
|
+
/** Whether an order may be parked with nothing paid at all. */
|
|
45
|
+
allowZeroDepositPark: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the storefront shows a parked order's reason to the shopper. Off by
|
|
48
|
+
* default: staff write that reason for staff.
|
|
49
|
+
*/
|
|
50
|
+
showParkReasonToCustomer: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* RESERVED: days after which a parked order is cancelled. Saved and read back,
|
|
53
|
+
* but nothing cancels a parked order automatically yet. `null`: never.
|
|
54
|
+
*/
|
|
55
|
+
autoCancelAfterDays: number | null;
|
|
56
|
+
storefront: StorefrontDepositPolicy;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The policy of a store that never set one, and of every field that cannot be
|
|
60
|
+
* read: deposits off, no minimum, no zero-deposit park, the reason kept from the
|
|
61
|
+
* shopper, never auto-cancelled, no online deposits.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_ORDER_DEPOSIT_POLICY: Readonly<OrderDepositPolicy>;
|
|
64
|
+
/**
|
|
65
|
+
* The ACTIVE store's deposit policy (`admin.orders.depositPolicy`), stored as a
|
|
66
|
+
* Channel custom field and written through core `updateChannel`.
|
|
67
|
+
*
|
|
68
|
+
* Reading needs an authenticated administrator on the store; writing needs
|
|
69
|
+
* `UpdateChannel`.
|
|
70
|
+
*/
|
|
71
|
+
export interface OrderDepositPolicyModule {
|
|
72
|
+
/**
|
|
73
|
+
* The policy the engine applies, read as tolerantly as the engine reads it: a
|
|
74
|
+
* store that never set one, a malformed column and a field of the wrong type
|
|
75
|
+
* all answer their defaults ({@link DEFAULT_ORDER_DEPOSIT_POLICY}), so this is
|
|
76
|
+
* what `orders.park` and `payments.record` will hold an order to.
|
|
77
|
+
*/
|
|
78
|
+
get(options?: Call): Promise<OrderDepositPolicy>;
|
|
79
|
+
/**
|
|
80
|
+
* Replace the whole policy, and answer it as the engine now applies it.
|
|
81
|
+
*
|
|
82
|
+
* Every field is required: read it with {@link get}, change what you mean to,
|
|
83
|
+
* and send it back. Only the schema's fields are stored (anything else on the
|
|
84
|
+
* object is left out), and the method codes are stored as given.
|
|
85
|
+
*
|
|
86
|
+
* Refused before any request with `DEPOSIT_POLICY_INVALID` ({@link ValidationError},
|
|
87
|
+
* one `details[]` entry per failing field, named by its path such as
|
|
88
|
+
* `storefront.minimumPercent`): a non-boolean switch, a `minimum` that is not a
|
|
89
|
+
* whole number of 0 or more, an `autoCancelAfterDays` that is neither `null` nor
|
|
90
|
+
* a whole number of 1 or more, a `minimumPercent` outside 0–100, a blank or
|
|
91
|
+
* repeated method code, or more than 50 of them. The engine itself stores any
|
|
92
|
+
* JSON in the column and reads what it cannot use as the default, so this check
|
|
93
|
+
* is what keeps a bad value from silently turning deposits off.
|
|
94
|
+
*
|
|
95
|
+
* Absent and "set to the defaults" mean the same thing to the engine, and a
|
|
96
|
+
* store's column is never backfilled: write the policy when a merchant changed
|
|
97
|
+
* it, not as a side effect of saving something else.
|
|
98
|
+
*/
|
|
99
|
+
set(policy: OrderDepositPolicy, options?: Call): Promise<OrderDepositPolicy>;
|
|
100
|
+
}
|
|
101
|
+
export declare const ORDER_DEPOSIT_POLICY_QUERY = "\n query OrderDepositPolicy {\n activeChannel { \n id\n customFields {\n orderDepositPolicy\n }\n }\n }\n";
|
|
102
|
+
export declare const SET_ORDER_DEPOSIT_POLICY_MUTATION = "\n mutation SetOrderDepositPolicy($input: UpdateChannelInput!) {\n updateChannel(input: $input) {\n ... on Channel { \n id\n customFields {\n orderDepositPolicy\n }\n }\n ... on ErrorResult { errorCode message }\n }\n }\n";
|
|
103
|
+
export declare function createOrderDepositPolicyModule(transport: Transport): OrderDepositPolicyModule;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { errorFromErrorResult, isErrorResult, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
// The bounds of the meta catalog's `order_deposit_policy` schema
|
|
3
|
+
// (`packages/shared/src/meta-catalog/settings-schemas.ts`), which the spec reads
|
|
4
|
+
// back from that schema. Nothing on the engine validates the column on write, so
|
|
5
|
+
// these are the only check a value gets before the engine reads it.
|
|
6
|
+
/** The fewest days `autoCancelAfterDays` may be; `null` means never. */
|
|
7
|
+
export const DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN = 1;
|
|
8
|
+
/** The largest `storefront.minimumPercent`: a whole percentage of the order total. */
|
|
9
|
+
export const DEPOSIT_POLICY_MINIMUM_PERCENT_MAX = 100;
|
|
10
|
+
/** The most payment method codes `storefront.methods` may name. */
|
|
11
|
+
export const DEPOSIT_POLICY_STOREFRONT_METHODS_MAX = 50;
|
|
12
|
+
/**
|
|
13
|
+
* The policy of a store that never set one, and of every field that cannot be
|
|
14
|
+
* read: deposits off, no minimum, no zero-deposit park, the reason kept from the
|
|
15
|
+
* shopper, never auto-cancelled, no online deposits.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_ORDER_DEPOSIT_POLICY = Object.freeze({
|
|
18
|
+
enabled: false,
|
|
19
|
+
minimum: 0,
|
|
20
|
+
allowZeroDepositPark: false,
|
|
21
|
+
showParkReasonToCustomer: false,
|
|
22
|
+
autoCancelAfterDays: null,
|
|
23
|
+
storefront: Object.freeze({ enabled: false, methods: [], minimumPercent: 0 }),
|
|
24
|
+
});
|
|
25
|
+
const POLICY_FIELDS = /* GraphQL */ `
|
|
26
|
+
id
|
|
27
|
+
customFields {
|
|
28
|
+
orderDepositPolicy
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
export const ORDER_DEPOSIT_POLICY_QUERY = /* GraphQL */ `
|
|
32
|
+
query OrderDepositPolicy {
|
|
33
|
+
activeChannel { ${POLICY_FIELDS} }
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
export const SET_ORDER_DEPOSIT_POLICY_MUTATION = /* GraphQL */ `
|
|
37
|
+
mutation SetOrderDepositPolicy($input: UpdateChannelInput!) {
|
|
38
|
+
updateChannel(input: $input) {
|
|
39
|
+
... on Channel { ${POLICY_FIELDS} }
|
|
40
|
+
... on ErrorResult { errorCode message }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
function isRecord(value) {
|
|
45
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
const isWhole = (value) => typeof value === 'number' && Number.isSafeInteger(value);
|
|
48
|
+
/**
|
|
49
|
+
* The column, read as the engine reads it (`resolveDepositPolicy` in
|
|
50
|
+
* `services/vendure/src/payment/deposit-policy.ts`): anything unreadable is its
|
|
51
|
+
* default.
|
|
52
|
+
*/
|
|
53
|
+
function readOrderDepositPolicy(raw) {
|
|
54
|
+
let parsed = raw;
|
|
55
|
+
if (typeof raw === 'string') {
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
parsed = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!isRecord(parsed))
|
|
64
|
+
return copyOf(DEFAULT_ORDER_DEPOSIT_POLICY);
|
|
65
|
+
return {
|
|
66
|
+
enabled: parsed.enabled === true,
|
|
67
|
+
minimum: isWhole(parsed.minimum) && parsed.minimum >= 0 ? parsed.minimum : 0,
|
|
68
|
+
allowZeroDepositPark: parsed.allowZeroDepositPark === true,
|
|
69
|
+
showParkReasonToCustomer: parsed.showParkReasonToCustomer === true,
|
|
70
|
+
autoCancelAfterDays: isWhole(parsed.autoCancelAfterDays) &&
|
|
71
|
+
parsed.autoCancelAfterDays >= DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN
|
|
72
|
+
? parsed.autoCancelAfterDays
|
|
73
|
+
: null,
|
|
74
|
+
storefront: readStorefront(parsed.storefront),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function readStorefront(raw) {
|
|
78
|
+
if (!isRecord(raw))
|
|
79
|
+
return { enabled: false, methods: [], minimumPercent: 0 };
|
|
80
|
+
// Only non-blank string codes, once each and at most 50: a stray `null` or a
|
|
81
|
+
// number must not allow a method nobody named.
|
|
82
|
+
const methods = Array.isArray(raw.methods)
|
|
83
|
+
? [
|
|
84
|
+
...new Set(raw.methods.filter((code) => typeof code === 'string' && code.trim() !== '')),
|
|
85
|
+
].slice(0, DEPOSIT_POLICY_STOREFRONT_METHODS_MAX)
|
|
86
|
+
: [];
|
|
87
|
+
const percent = raw.minimumPercent;
|
|
88
|
+
return {
|
|
89
|
+
enabled: raw.enabled === true,
|
|
90
|
+
methods,
|
|
91
|
+
minimumPercent: isWhole(percent) && percent >= 0 && percent <= DEPOSIT_POLICY_MINIMUM_PERCENT_MAX
|
|
92
|
+
? percent
|
|
93
|
+
: 0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function copyOf(policy) {
|
|
97
|
+
return {
|
|
98
|
+
...policy,
|
|
99
|
+
storefront: { ...policy.storefront, methods: [...policy.storefront.methods] },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Every way `policy` breaks the schema, one entry per field. */
|
|
103
|
+
function problemsOf(policy) {
|
|
104
|
+
const problems = [];
|
|
105
|
+
const add = (field, message) => problems.push({ field, code: 'DEPOSIT_POLICY_INVALID', message });
|
|
106
|
+
if (!isRecord(policy)) {
|
|
107
|
+
add('policy', 'Pass the whole policy as an object: read it with get(), change it, send it back.');
|
|
108
|
+
return problems;
|
|
109
|
+
}
|
|
110
|
+
for (const field of ['enabled', 'allowZeroDepositPark', 'showParkReasonToCustomer']) {
|
|
111
|
+
if (typeof policy[field] !== 'boolean')
|
|
112
|
+
add(field, `${field} must be true or false.`);
|
|
113
|
+
}
|
|
114
|
+
if (!(isWhole(policy.minimum) && policy.minimum >= 0)) {
|
|
115
|
+
add('minimum', 'minimum must be a whole number of minor units, 0 or more.');
|
|
116
|
+
}
|
|
117
|
+
const days = policy.autoCancelAfterDays;
|
|
118
|
+
if (days !== null && !(isWhole(days) && days >= DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN)) {
|
|
119
|
+
add('autoCancelAfterDays', 'autoCancelAfterDays must be null (never) or a whole number of days, 1 or more.');
|
|
120
|
+
}
|
|
121
|
+
const storefront = policy.storefront;
|
|
122
|
+
if (!isRecord(storefront)) {
|
|
123
|
+
add('storefront', 'storefront must be an object: { enabled, methods, minimumPercent }.');
|
|
124
|
+
return problems;
|
|
125
|
+
}
|
|
126
|
+
if (typeof storefront.enabled !== 'boolean') {
|
|
127
|
+
add('storefront.enabled', 'storefront.enabled must be true or false.');
|
|
128
|
+
}
|
|
129
|
+
const methods = storefront.methods;
|
|
130
|
+
if (!Array.isArray(methods)) {
|
|
131
|
+
add('storefront.methods', 'storefront.methods must be an array of payment method codes.');
|
|
132
|
+
}
|
|
133
|
+
else if (methods.some((code) => typeof code !== 'string' || code.trim() === '')) {
|
|
134
|
+
add('storefront.methods', 'storefront.methods may hold only non-blank payment method codes.');
|
|
135
|
+
}
|
|
136
|
+
else if (new Set(methods).size !== methods.length) {
|
|
137
|
+
add('storefront.methods', 'storefront.methods names a payment method code more than once.');
|
|
138
|
+
}
|
|
139
|
+
else if (methods.length > DEPOSIT_POLICY_STOREFRONT_METHODS_MAX) {
|
|
140
|
+
add('storefront.methods', `storefront.methods may name at most ${String(DEPOSIT_POLICY_STOREFRONT_METHODS_MAX)} payment methods.`);
|
|
141
|
+
}
|
|
142
|
+
const percent = storefront.minimumPercent;
|
|
143
|
+
if (!(isWhole(percent) && percent >= 0 && percent <= DEPOSIT_POLICY_MINIMUM_PERCENT_MAX)) {
|
|
144
|
+
add('storefront.minimumPercent', 'storefront.minimumPercent must be a whole percentage from 0 to 100.');
|
|
145
|
+
}
|
|
146
|
+
return problems;
|
|
147
|
+
}
|
|
148
|
+
/** Exactly the schema's fields, in its order: what the column stores. */
|
|
149
|
+
function storedOf(policy) {
|
|
150
|
+
return {
|
|
151
|
+
enabled: policy.enabled,
|
|
152
|
+
minimum: policy.minimum,
|
|
153
|
+
allowZeroDepositPark: policy.allowZeroDepositPark,
|
|
154
|
+
showParkReasonToCustomer: policy.showParkReasonToCustomer,
|
|
155
|
+
autoCancelAfterDays: policy.autoCancelAfterDays,
|
|
156
|
+
storefront: {
|
|
157
|
+
enabled: policy.storefront.enabled,
|
|
158
|
+
methods: [...policy.storefront.methods],
|
|
159
|
+
minimumPercent: policy.storefront.minimumPercent,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function createOrderDepositPolicyModule(transport) {
|
|
164
|
+
const read = async (options) => {
|
|
165
|
+
const data = await transport.graphql({ api: 'admin', query: ORDER_DEPOSIT_POLICY_QUERY }, options);
|
|
166
|
+
return data.activeChannel;
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
async get(options) {
|
|
170
|
+
const channel = await read(options);
|
|
171
|
+
return readOrderDepositPolicy(channel.customFields?.orderDepositPolicy ?? null);
|
|
172
|
+
},
|
|
173
|
+
async set(policy, options) {
|
|
174
|
+
const problems = problemsOf(policy);
|
|
175
|
+
if (problems.length > 0) {
|
|
176
|
+
throw new ValidationError({
|
|
177
|
+
code: 'DEPOSIT_POLICY_INVALID',
|
|
178
|
+
message: problems.map((problem) => problem.message).join(' '),
|
|
179
|
+
status: 400,
|
|
180
|
+
service: 'vendure',
|
|
181
|
+
details: problems,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// `updateChannel` needs the id, and the active channel is the one the
|
|
185
|
+
// client is scoped to — read it with the SAME call options, so a per-call
|
|
186
|
+
// tenant writes its own channel rather than the client's.
|
|
187
|
+
const { id } = await read(options);
|
|
188
|
+
const data = await transport.graphql({
|
|
189
|
+
api: 'admin',
|
|
190
|
+
query: SET_ORDER_DEPOSIT_POLICY_MUTATION,
|
|
191
|
+
variables: {
|
|
192
|
+
// The column is `text` holding JSON (the schema describes the
|
|
193
|
+
// DECODED value), so it is sent as a string.
|
|
194
|
+
input: { id, customFields: { orderDepositPolicy: JSON.stringify(storedOf(policy)) } },
|
|
195
|
+
},
|
|
196
|
+
}, options);
|
|
197
|
+
if (isErrorResult(data.updateChannel))
|
|
198
|
+
throw errorFromErrorResult(data.updateChannel);
|
|
199
|
+
return readOrderDepositPolicy(data.updateChannel.customFields?.orderDepositPolicy ?? null);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
package/dist/orders/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { JobHandle } from '../jobs/index.js';
|
|
2
3
|
import type { OrderPaymentPosition, PaidOrder } from '../payments/index.js';
|
|
3
4
|
import type { Call } from '../types.js';
|
|
5
|
+
import { type OrderBulkInput, type OrderBulkRun } from './bulk.js';
|
|
4
6
|
import { type OrderCommitEditModule } from './commit-edit.js';
|
|
7
|
+
import { type OrderDepositPolicyModule } from './deposit-policy.js';
|
|
5
8
|
import { type OrderGetModule } from './get.js';
|
|
6
9
|
import { type OrderLinePricePreviewModule } from './line-price-preview.js';
|
|
7
10
|
import { type ManualDiscountsModule } from './manual-discounts.js';
|
|
@@ -12,6 +15,9 @@ import { type OrderPlanEditModule } from './plan-edit.js';
|
|
|
12
15
|
import { type OrderStatusModule } from './status.js';
|
|
13
16
|
import { type SurchargesModule } from './surcharges.js';
|
|
14
17
|
export type { PaidOrder } from '../payments/index.js';
|
|
18
|
+
export type { OrderBulkAction, OrderBulkCapture, OrderBulkInput, OrderBulkItem, OrderBulkRun, } from './bulk.js';
|
|
19
|
+
export { MAX_BULK_ORDERS, ORDER_BULK_ACTIONS, ORDER_BULK_TRANSITION_REFUSED } from './bulk.js';
|
|
20
|
+
export type { OrderDepositPolicy, OrderDepositPolicyModule, StorefrontDepositPolicy, } from './deposit-policy.js';
|
|
15
21
|
export type { DiscountPolicy, DiscountPolicyInput, DiscountPolicyModule, DiscountReason, DiscountReasonInput, DiscountReasonsModule, ManualDiscountsModule, OrderManualDiscount, } from './manual-discounts.js';
|
|
16
22
|
export type { ExternalLinePriceBasis, ExternalOrder, ExternalOrderAddressInput, ExternalOrderCall, ExternalOrderCatalogLine, ExternalOrderExternalLine, ExternalOrderFulfilment, ExternalOrderInput, ExternalOrderLine, ExternalOrderLineInput, ExternalOrderSurcharge, ExternalOrderSurchargeInput, ExternalOrderTargetState, ExternalOrderTaxLine, } from './order-entry.js';
|
|
17
23
|
/** An app provider an order is running without (`admin.orders.providerFallbacks`). */
|
|
@@ -73,6 +79,80 @@ export interface OrdersModule extends OrderEntryModule, OrderGetModule, OrderPar
|
|
|
73
79
|
* `setOneTimeDiscounts` on this module.
|
|
74
80
|
*/
|
|
75
81
|
manualDiscounts: ManualDiscountsModule;
|
|
82
|
+
/**
|
|
83
|
+
* Move, cancel, delete or mark paid up to `MAX_BULK_ORDERS` (50) orders of the store in ONE
|
|
84
|
+
* call (headless spec §7 `orders.bulk`; rulings G19, G28).
|
|
85
|
+
*
|
|
86
|
+
* **The run is a job.** The call answers a receipt (`runId`) once the run is queued;
|
|
87
|
+
* `handle.wait()` polls it until it ends and resolves with the run, whatever the ending.
|
|
88
|
+
* `bulkRun(runId)` reads it again later, from any process. Each order's outcome is in
|
|
89
|
+
* `run.items`, in the order the ids were named.
|
|
90
|
+
*
|
|
91
|
+
* **Each order is judged as on its own page, and succeeds or fails alone.** First by its own
|
|
92
|
+
* status (`orders.status`): an action the order's status does not offer, or a TRANSITION target
|
|
93
|
+
* outside the order's own `nextStates`, answers `ORDER_ACTION_UNAVAILABLE` with `reasonCode`
|
|
94
|
+
* (that read's vocabulary), before anything is written. Then by its single door, under the
|
|
95
|
+
* door's own lock, with the door's own code: `ORDER_STATE_TRANSITION_ERROR` (and core's other
|
|
96
|
+
* order codes), `ORDER_CART_NOT_CANCELLABLE`, `ORDER_UNITS_SHIPPED`,
|
|
97
|
+
* `ORDER_HAS_ACTIVE_FULFILLMENT`, `ORDER_INVOICE_LOCKED`, `ORDER_HAS_RECORDS`,
|
|
98
|
+
* `ORDER_NOT_DELETED`. Another store's order answers `ENTITY_NOT_FOUND` on its item. A
|
|
99
|
+
* `SUCCEEDED` run can hold `FAILED` items: read them.
|
|
100
|
+
*
|
|
101
|
+
* - `TRANSITION` (`toState` required): as `transitionOrderToState`. Never `Cancelled` (use
|
|
102
|
+
* `CANCEL`), `PaymentSettled` (use `MARK_PAID`), `Modifying` or `AddingItems`.
|
|
103
|
+
* - `CANCEL` (`reason` optional): the whole order, shipping included, as `cancelOrder`. **It
|
|
104
|
+
* moves no money:** a paid order keeps its payment, to be refunded through `payments.refund`.
|
|
105
|
+
* - `DELETE`: a draft or a cart holding no payment or stock movement, as `deleteOrder`.
|
|
106
|
+
* - `MARK_PAID`: **captures** each payment of the order that is `Authorized` when the run reaches
|
|
107
|
+
* it, read by the engine (never your list's copy), each as `settlePayment` and on its own
|
|
108
|
+
* (ruling G19). Each order's `captured` lists every payment acted on. **A decline is a result,
|
|
109
|
+
* not a retry:** the order answers `SETTLE_PAYMENT_ERROR`, the payment keeps the state the
|
|
110
|
+
* provider left, and the run never asks again. **An unknown provider answer must be
|
|
111
|
+
* reconciled, never retried blind:** `IDEMPOTENCY_OUTCOME_UNKNOWN` means the provider was asked
|
|
112
|
+
* and its answer was lost; check the payment with the provider. `IDEMPOTENCY_IN_PROGRESS` means
|
|
113
|
+
* a capture under the same request may still be running. An order with no authorized payment
|
|
114
|
+
* answers `NOTHING_TO_CAPTURE`: it is never marked paid without money (ruling G28).
|
|
115
|
+
*
|
|
116
|
+
* **Idempotency.** `options.idempotencyKey` (minted when omitted): the same key with the same
|
|
117
|
+
* input answers the SAME run, so a resubmit never captures twice; another input under the key is
|
|
118
|
+
* `IDEMPOTENCY_CONFLICT` (409 `ConflictError`). Each order, and each capture, is keyed within the
|
|
119
|
+
* run, so a run the platform processes again (a worker restart) answers what it already did from
|
|
120
|
+
* that record and never asks a provider twice.
|
|
121
|
+
*
|
|
122
|
+
* **No dry run.** The refusals that matter are the doors' own, made under their locks, so a
|
|
123
|
+
* rehearsal could answer "ok" for an order the real run refuses.
|
|
124
|
+
*
|
|
125
|
+
* Needs `UpdateOrder` (`DeleteOrder` for `DELETE`); reading the run needs `ReadOrder`. Refusals
|
|
126
|
+
* of the call itself (nothing is queued): `BULK_ACTION_INPUT_INVALID` (`ValidationError`, 400:
|
|
127
|
+
* no orders, more than 50, one named twice, an unknown action, a missing or refused `toState`,
|
|
128
|
+
* an option the action takes none of; refused before sending too), `BULK_ACTION_NOT_ALLOWED`
|
|
129
|
+
* (`PermissionDeniedError`, 403), `IDEMPOTENCY_CONFLICT`.
|
|
130
|
+
*
|
|
131
|
+
* Platform releases before this method do not have the mutation and refuse it as
|
|
132
|
+
* `ValidationError` `GRAPHQL_VALIDATION_FAILED` (400): deploy the engine first.
|
|
133
|
+
*/
|
|
134
|
+
bulk(input: OrderBulkInput, options?: Call): Promise<JobHandle<OrderBulkRun>>;
|
|
135
|
+
/**
|
|
136
|
+
* One read of a bulk order run, by the `runId` `bulk()` answered. A run of another store, or one
|
|
137
|
+
* that does not exist, is `BULK_ACTION_RUN_NOT_FOUND` (`NotFoundError`, 404).
|
|
138
|
+
*/
|
|
139
|
+
bulkRun(runId: string, options?: Call): Promise<OrderBulkRun>;
|
|
140
|
+
/**
|
|
141
|
+
* Delete ONE order: a draft or a cart (`AddingItems`) holding no payment and no stock movement,
|
|
142
|
+
* as the order page's Delete (ruling G29). Needs `DeleteOrder`. Anything else is kept:
|
|
143
|
+
* `ORDER_INVOICE_LOCKED` (an invoiced order, for every caller), `ORDER_HAS_RECORDS` (cancel it
|
|
144
|
+
* instead: `orders.bulk` with `CANCEL`), or `ORDER_NOT_DELETED` (`ConflictError`, 409, as every `*_NOT_DELETED` of this SDK) with
|
|
145
|
+
* the engine's own reason for an order in another state, or one this store does not have.
|
|
146
|
+
* Placed orders are cancelled, never deleted.
|
|
147
|
+
*/
|
|
148
|
+
delete(orderId: string, options?: Call): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* The store's deposit policy (`orderDepositPolicy`): whether staff may take a
|
|
151
|
+
* deposit and {@link OrdersModule.park} an order, the minimum a deposit must be,
|
|
152
|
+
* and whether a shopper may pay one at checkout. `get` answers what the engine
|
|
153
|
+
* applies; `set` replaces it, validated against the schema's bounds first.
|
|
154
|
+
*/
|
|
155
|
+
depositPolicy: OrderDepositPolicyModule;
|
|
76
156
|
/**
|
|
77
157
|
* Hold a placed order while the rest of its money is arranged. Needs
|
|
78
158
|
* `UpdateOrder`.
|
package/dist/orders/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { bulkOrders, deleteOrder, readOrderBulkRun, } from './bulk.js';
|
|
2
3
|
import { createOrderCommitEditModule } from './commit-edit.js';
|
|
4
|
+
import { createOrderDepositPolicyModule } from './deposit-policy.js';
|
|
3
5
|
import { createOrderGetModule } from './get.js';
|
|
4
6
|
import { createOrderLinePricePreviewModule, } from './line-price-preview.js';
|
|
5
7
|
import { createManualDiscountsModule } from './manual-discounts.js';
|
|
@@ -9,6 +11,7 @@ import { createOrderParcelChargeModule } from './parcel-charge.js';
|
|
|
9
11
|
import { createOrderPlanEditModule } from './plan-edit.js';
|
|
10
12
|
import { createOrderStatusModule } from './status.js';
|
|
11
13
|
import { createSurchargesModule } from './surcharges.js';
|
|
14
|
+
export { MAX_BULK_ORDERS, ORDER_BULK_ACTIONS, ORDER_BULK_TRANSITION_REFUSED } from './bulk.js';
|
|
12
15
|
const ORDER_PROVIDER_FALLBACKS_QUERY = /* GraphQL */ `
|
|
13
16
|
query OrderProviderFallbacks($id: ID!) {
|
|
14
17
|
order(id: $id) {
|
|
@@ -100,6 +103,16 @@ export function createOrdersModule(transport) {
|
|
|
100
103
|
...createOrderStatusModule(transport),
|
|
101
104
|
surcharges: createSurchargesModule(transport),
|
|
102
105
|
manualDiscounts: createManualDiscountsModule(transport),
|
|
106
|
+
depositPolicy: createOrderDepositPolicyModule(transport),
|
|
107
|
+
bulk(input, options) {
|
|
108
|
+
return bulkOrders(transport, input, options);
|
|
109
|
+
},
|
|
110
|
+
bulkRun(runId, options) {
|
|
111
|
+
return readOrderBulkRun(transport, runId, options);
|
|
112
|
+
},
|
|
113
|
+
delete(orderId, options) {
|
|
114
|
+
return deleteOrder(transport, orderId, options);
|
|
115
|
+
},
|
|
103
116
|
async park(orderId, options) {
|
|
104
117
|
const data = await transport.graphql({
|
|
105
118
|
api: 'admin',
|
|
@@ -37,10 +37,19 @@ export interface DiscountPolicy {
|
|
|
37
37
|
* active reasons for that line. A till discount (`pos.sale.applyDiscount`) is not held to it.
|
|
38
38
|
*/
|
|
39
39
|
reasonRequired: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The store's ceiling on manual discounts in basis points (1000 = 10 %), or `null` for none: how
|
|
42
|
+
* far below the price the customer would get anyway (its contract included) staff without the
|
|
43
|
+
* `OverrideDiscountLimit` permission may take a line (`DISCOUNT_OVER_LIMIT`). Reducing a
|
|
44
|
+
* discount is never refused. The POS till is not held to it.
|
|
45
|
+
*/
|
|
46
|
+
maxDiscountBps: number | null;
|
|
40
47
|
}
|
|
41
48
|
export interface DiscountPolicyInput {
|
|
42
49
|
/** Omitted keeps the stored value. */
|
|
43
50
|
reasonRequired?: boolean;
|
|
51
|
+
/** 0..10000 basis points; omitted keeps the stored value, `null` removes the ceiling. */
|
|
52
|
+
maxDiscountBps?: number | null;
|
|
44
53
|
}
|
|
45
54
|
/**
|
|
46
55
|
* One price movement a manual-discount door made on an order. Append-only. Each row is a price
|
|
@@ -126,7 +135,7 @@ export interface ManualDiscountsModule {
|
|
|
126
135
|
}
|
|
127
136
|
export declare const DISCOUNT_REASONS_QUERY = "\n query DiscountReasons($includeInactive: Boolean) {\n discountReasons(includeInactive: $includeInactive) {\n \n id\n code\n label\n active\n createdAt\n updatedAt\n\n }\n }\n";
|
|
128
137
|
export declare const UPSERT_DISCOUNT_REASON_MUTATION = "\n mutation UpsertDiscountReason($input: UpsertDiscountReasonInput!) {\n upsertDiscountReason(input: $input) {\n \n id\n code\n label\n active\n createdAt\n updatedAt\n\n }\n }\n";
|
|
129
|
-
export declare const DISCOUNT_POLICY_QUERY = "\n query DiscountPolicy {\n discountPolicy {\n reasonRequired\n }\n }\n";
|
|
130
|
-
export declare const UPDATE_DISCOUNT_POLICY_MUTATION = "\n mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {\n updateDiscountPolicy(input: $input) {\n reasonRequired\n }\n }\n";
|
|
138
|
+
export declare const DISCOUNT_POLICY_QUERY = "\n query DiscountPolicy {\n discountPolicy {\n reasonRequired\n maxDiscountBps\n }\n }\n";
|
|
139
|
+
export declare const UPDATE_DISCOUNT_POLICY_MUTATION = "\n mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {\n updateDiscountPolicy(input: $input) {\n reasonRequired\n maxDiscountBps\n }\n }\n";
|
|
131
140
|
export declare const ORDER_MANUAL_DISCOUNTS_QUERY = "\n query OrderManualDiscounts($orderId: ID!) {\n orderManualDiscounts(orderId: $orderId) {\n \n id\n createdAt\n orderId\n orderLineId\n door\n amount\n amountWithTax\n currencyCode\n reason {\n id\n code\n label\n active\n }\n actorUserId\n onBehalfOfRef\n deviceRef\n callId\n\n }\n }\n";
|
|
132
141
|
export declare function createManualDiscountsModule(transport: Transport): ManualDiscountsModule;
|
|
@@ -44,6 +44,7 @@ export const DISCOUNT_POLICY_QUERY = /* GraphQL */ `
|
|
|
44
44
|
query DiscountPolicy {
|
|
45
45
|
discountPolicy {
|
|
46
46
|
reasonRequired
|
|
47
|
+
maxDiscountBps
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
`;
|
|
@@ -51,6 +52,7 @@ export const UPDATE_DISCOUNT_POLICY_MUTATION = /* GraphQL */ `
|
|
|
51
52
|
mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {
|
|
52
53
|
updateDiscountPolicy(input: $input) {
|
|
53
54
|
reasonRequired
|
|
55
|
+
maxDiscountBps
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
`;
|
package/dist/payments/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
2
|
import type { Call } from '../types.js';
|
|
3
3
|
import { type AccountCreditModule } from './account-credit.js';
|
|
4
|
+
import { type PaymentMethodsModule } from './methods.js';
|
|
4
5
|
import { type ProvidersModule } from './providers.js';
|
|
5
6
|
/**
|
|
6
7
|
* What an order has actually been paid, in minor units of its own currency.
|
|
@@ -284,6 +285,11 @@ export interface PaymentsModule {
|
|
|
284
285
|
* to an invoice, pay it back.
|
|
285
286
|
*/
|
|
286
287
|
accountCredit: AccountCreditModule;
|
|
288
|
+
/**
|
|
289
|
+
* The store's payment methods, as staff choose one: the `method` code
|
|
290
|
+
* {@link PaymentsModule.record} takes, and the codes a deposit policy names.
|
|
291
|
+
*/
|
|
292
|
+
methods: PaymentMethodsModule;
|
|
287
293
|
/**
|
|
288
294
|
* Record part (or all) of what an order owes, and return the order.
|
|
289
295
|
*
|
|
@@ -466,4 +472,5 @@ export declare const REPORT_APP_PAYMENT_MUTATION = "\n mutation ReportAppPaymen
|
|
|
466
472
|
export declare const APP_PAYMENTS_QUERY = "\n query AppPayments($orderId: ID!) {\n appPayments(orderId: $orderId) {\n id\n orderId\n orderCode\n state\n amount\n currencyCode\n providerReference\n methodCode\n outcomeUnknown\n createdAt\n }\n }\n";
|
|
467
473
|
export declare function createPaymentsModule(transport: Transport): PaymentsModule;
|
|
468
474
|
export type { AccountCreditModule, ApplyUnappliedCashInput, CreditOverpaymentInput, OrderMoneyOnAccount, RefundUnappliedCashInput, UnappliedCash, UnappliedCashRefund, } from './account-credit.js';
|
|
475
|
+
export type { PaymentMethod, PaymentMethodsModule } from './methods.js';
|
|
469
476
|
export type { ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, } from './providers.js';
|
package/dist/payments/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createAccountCreditModule } from './account-credit.js';
|
|
2
|
+
import { createPaymentMethodsModule } from './methods.js';
|
|
2
3
|
import { createProvidersModule } from './providers.js';
|
|
3
4
|
const PAID_ORDER_FIELDS = /* GraphQL */ `
|
|
4
5
|
id
|
|
@@ -124,6 +125,7 @@ export function createPaymentsModule(transport) {
|
|
|
124
125
|
return {
|
|
125
126
|
providers: createProvidersModule(transport),
|
|
126
127
|
accountCredit: createAccountCreditModule(transport),
|
|
128
|
+
methods: createPaymentMethodsModule(transport),
|
|
127
129
|
async record(orderId, input, options) {
|
|
128
130
|
const data = await transport.graphql({
|
|
129
131
|
api: 'admin',
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Page, Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { ListOptions } from '../types.js';
|
|
3
|
+
/** One payment method of the store, as staff choose one. */
|
|
4
|
+
export interface PaymentMethod {
|
|
5
|
+
id: string;
|
|
6
|
+
/**
|
|
7
|
+
* The method's code: what `payments.record` takes as `method`, and what a
|
|
8
|
+
* deposit policy names in `storefront.methods`.
|
|
9
|
+
*/
|
|
10
|
+
code: string;
|
|
11
|
+
name: string;
|
|
12
|
+
/** A disabled method takes no new payments; it still names the ones it took. */
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The code of the handler that takes the money (`manual-payment`, `mollie`, a
|
|
16
|
+
* payment app's handler, …): what kind of method this is.
|
|
17
|
+
*/
|
|
18
|
+
handlerCode: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The store's payment methods (`admin.payments.methods`): what staff record a
|
|
22
|
+
* payment with, and what a deposit policy names.
|
|
23
|
+
*
|
|
24
|
+
* Needs `ReadSettings` or `ReadPaymentMethod`. Scoped to the channel the client
|
|
25
|
+
* is on: another store's methods are not listed.
|
|
26
|
+
*/
|
|
27
|
+
export interface PaymentMethodsModule {
|
|
28
|
+
/**
|
|
29
|
+
* Every method of the store, enabled or not, in the engine's order. Paged
|
|
30
|
+
* (`options.limit`, default 100, at most 1,000 or `INVALID_PAGE_SIZE`; and
|
|
31
|
+
* `options.after` from a previous page); `for await (const method of page)`
|
|
32
|
+
* walks them all.
|
|
33
|
+
*/
|
|
34
|
+
list(options?: ListOptions): Promise<Page<PaymentMethod>>;
|
|
35
|
+
}
|
|
36
|
+
export declare const PAYMENT_METHODS_QUERY = "\n query StorePaymentMethods($options: PaymentMethodListOptions) {\n paymentMethods(options: $options) {\n items {\n id\n code\n name\n enabled\n handler {\n code\n }\n }\n totalItems\n }\n }\n";
|
|
37
|
+
export declare function createPaymentMethodsModule(transport: Transport): PaymentMethodsModule;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { vendureListPage } from '../catalog/types.js';
|
|
2
|
+
/** A store has a handful; one page holds them. */
|
|
3
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
4
|
+
export const PAYMENT_METHODS_QUERY = /* GraphQL */ `
|
|
5
|
+
query StorePaymentMethods($options: PaymentMethodListOptions) {
|
|
6
|
+
paymentMethods(options: $options) {
|
|
7
|
+
items {
|
|
8
|
+
id
|
|
9
|
+
code
|
|
10
|
+
name
|
|
11
|
+
enabled
|
|
12
|
+
handler {
|
|
13
|
+
code
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
totalItems
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
const toMethod = (method) => ({
|
|
21
|
+
id: method.id,
|
|
22
|
+
code: method.code,
|
|
23
|
+
name: method.name,
|
|
24
|
+
enabled: method.enabled,
|
|
25
|
+
handlerCode: method.handler.code,
|
|
26
|
+
});
|
|
27
|
+
export function createPaymentMethodsModule(transport) {
|
|
28
|
+
return {
|
|
29
|
+
list(options) {
|
|
30
|
+
return vendureListPage(transport, PAYMENT_METHODS_QUERY, 'paymentMethods', (skip, take) => ({ options: { skip, take } }), { ...options, limit: options?.limit ?? DEFAULT_PAGE_SIZE }, toMethod);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/pricing/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
2
|
import type { ConfigurableOperation, ConfigurableOperationInput, LanguageCode } from '../catalog/types.js';
|
|
3
3
|
import type { Call } from '../types.js';
|
|
4
|
+
import { type PricingVouchersModule } from './vouchers.js';
|
|
4
5
|
/**
|
|
5
6
|
* Promotions at the register (headless spec §9.2 `pricing`; incentives spec §5,
|
|
6
7
|
* sprint 1): which promotions a register can offer, coupons on the order it is
|
|
@@ -33,6 +34,8 @@ import type { Call } from '../types.js';
|
|
|
33
34
|
export interface PricingModule {
|
|
34
35
|
promotions: PricingPromotionsModule;
|
|
35
36
|
coupons: PricingCouponsModule;
|
|
37
|
+
/** Personal vouchers: templates, issuing, a customer's vouchers (`./vouchers.ts`). */
|
|
38
|
+
vouchers: PricingVouchersModule;
|
|
36
39
|
/**
|
|
37
40
|
* The order's discounts as they stand: its coupon codes, the order-level
|
|
38
41
|
* discounts, and each line's discounts, every one naming the promotion it came
|
package/dist/pricing/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createPage, errorFromErrorResult, isErrorResult, NotFoundError, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { createPricingVouchersModule } from './vouchers.js';
|
|
2
3
|
/**
|
|
3
4
|
* The targeting conditions (incentives sprint 2), encoded as the engine stores
|
|
4
5
|
* them — every argument a string, lists JSON-encoded. Their argument names and
|
|
@@ -317,6 +318,7 @@ export function createPricingModule(transport) {
|
|
|
317
318
|
return data.updatePromotion;
|
|
318
319
|
},
|
|
319
320
|
},
|
|
321
|
+
vouchers: createPricingVouchersModule(transport),
|
|
320
322
|
coupons: {
|
|
321
323
|
async apply(orderId, couponCode, options) {
|
|
322
324
|
const data = await transport.graphql({
|