@reactionary/commercetools 0.10.52 → 0.10.54
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/capabilities/checkout.capability.js +36 -1
- package/capabilities/store.capability.js +65 -1
- package/core/shipping-fulfillment.js +6 -0
- package/core/shipping-protocol-data.js +33 -0
- package/factories/checkout/checkout.factory.js +13 -1
- package/package.json +2 -2
- package/schema/configuration.schema.js +4 -1
- package/src/capabilities/checkout.capability.d.ts +7 -0
- package/src/capabilities/store.capability.d.ts +8 -1
- package/src/core/shipping-fulfillment.d.ts +8 -0
- package/src/core/shipping-protocol-data.d.ts +16 -0
- package/src/schema/configuration.schema.d.ts +3 -0
|
@@ -9,6 +9,12 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
11
|
import { PROTOCOL_DATA_FIELD, serializeProtocolData } from "../core/payment-protocol-data.js";
|
|
12
|
+
import {
|
|
13
|
+
PICKUP_STORE_FIELD,
|
|
14
|
+
SHIPPING_PROTOCOL_DATA_FIELD,
|
|
15
|
+
serializeShippingProtocolData
|
|
16
|
+
} from "../core/shipping-protocol-data.js";
|
|
17
|
+
import { resolveShippingFulfillmentType } from "../core/shipping-fulfillment.js";
|
|
12
18
|
import {
|
|
13
19
|
CheckoutMutationAddPaymentInstructionSchema,
|
|
14
20
|
CheckoutMutationFinalizeCheckoutSchema,
|
|
@@ -174,10 +180,25 @@ class CommercetoolsCheckoutCapability extends CheckoutCapability {
|
|
|
174
180
|
client.carts.withId({ ID: payload.checkout.key }).get().execute()
|
|
175
181
|
]);
|
|
176
182
|
const result = shippingMethodsResponse.body.results.map(
|
|
177
|
-
(shippingMethod) => this.
|
|
183
|
+
(shippingMethod) => this.withFulfillmentType(
|
|
184
|
+
this.factory.parseShippingMethod(this.context, this.withFreeShipping(shippingMethod, cartResponse.body)),
|
|
185
|
+
shippingMethod
|
|
186
|
+
)
|
|
178
187
|
);
|
|
179
188
|
return success(result);
|
|
180
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Tags a parsed shipping method with its fulfillment type: methods whose
|
|
192
|
+
* key is listed in the `bopis.shippingMethodKeys` configuration are
|
|
193
|
+
* in-store pickup (BOPIS), everything else is a delivery. The tagging lives
|
|
194
|
+
* here rather than in the factory, so factories stay configuration-free.
|
|
195
|
+
*/
|
|
196
|
+
withFulfillmentType(shippingMethod, remote) {
|
|
197
|
+
return {
|
|
198
|
+
...shippingMethod,
|
|
199
|
+
fulfillmentType: resolveShippingFulfillmentType(remote, this.config.bopis?.shippingMethodKeys)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
181
202
|
/**
|
|
182
203
|
* Quotes the matching shipping rates as free where the cart reaches the
|
|
183
204
|
* rate's free-above threshold. commercetools' matching-cart query returns
|
|
@@ -289,6 +310,20 @@ class CommercetoolsCheckoutCapability extends CheckoutCapability {
|
|
|
289
310
|
name: "pickupPointId",
|
|
290
311
|
value: payload.shippingInstruction.pickupPoint
|
|
291
312
|
});
|
|
313
|
+
if (payload.shippingInstruction.store?.key) {
|
|
314
|
+
actions.push({
|
|
315
|
+
action: "setCustomField",
|
|
316
|
+
name: PICKUP_STORE_FIELD,
|
|
317
|
+
value: payload.shippingInstruction.store.key
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (payload.shippingInstruction.protocolData && payload.shippingInstruction.protocolData.length > 0) {
|
|
321
|
+
actions.push({
|
|
322
|
+
action: "setCustomField",
|
|
323
|
+
name: SHIPPING_PROTOCOL_DATA_FIELD,
|
|
324
|
+
value: serializeShippingProtocolData(payload.shippingInstruction.protocolData)
|
|
325
|
+
});
|
|
326
|
+
}
|
|
292
327
|
const result = await this.applyActions(
|
|
293
328
|
payload.checkout,
|
|
294
329
|
actions
|
|
@@ -8,7 +8,17 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
8
8
|
if (kind && result) __defProp(target, key, result);
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Reactionary,
|
|
13
|
+
StoreCapability,
|
|
14
|
+
StorePaginatedResultSchema,
|
|
15
|
+
StoreQueryByIdSchema,
|
|
16
|
+
StoreQueryByProximitySchema,
|
|
17
|
+
StoreQueryForListSchema,
|
|
18
|
+
StoreSchema,
|
|
19
|
+
success,
|
|
20
|
+
error
|
|
21
|
+
} from "@reactionary/core";
|
|
12
22
|
import * as z from "zod";
|
|
13
23
|
class CommercetoolsStoreCapability extends StoreCapability {
|
|
14
24
|
config;
|
|
@@ -38,6 +48,48 @@ class CommercetoolsStoreCapability extends StoreCapability {
|
|
|
38
48
|
}
|
|
39
49
|
return success(results);
|
|
40
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The channel predicate selecting the channels that represent the brand's
|
|
53
|
+
* own stores. Override in a subclass if a project models stores differently.
|
|
54
|
+
*/
|
|
55
|
+
getStoreChannelPredicate() {
|
|
56
|
+
return 'roles contains any ("InventorySupply")';
|
|
57
|
+
}
|
|
58
|
+
async list(payload) {
|
|
59
|
+
const client = await this.getClient();
|
|
60
|
+
const { pageNumber, pageSize } = payload.paginationOptions;
|
|
61
|
+
const remote = await client.channels().get({
|
|
62
|
+
queryArgs: {
|
|
63
|
+
limit: pageSize,
|
|
64
|
+
offset: (pageNumber - 1) * pageSize,
|
|
65
|
+
withTotal: true,
|
|
66
|
+
where: this.getStoreChannelPredicate()
|
|
67
|
+
}
|
|
68
|
+
}).execute();
|
|
69
|
+
const totalCount = remote.body.total ?? remote.body.results.length;
|
|
70
|
+
return success({
|
|
71
|
+
pageNumber,
|
|
72
|
+
pageSize,
|
|
73
|
+
totalCount,
|
|
74
|
+
totalPages: Math.ceil(totalCount / pageSize),
|
|
75
|
+
items: remote.body.results.map((r) => this.factory.parseStore(this.context, r))
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async getById(payload) {
|
|
79
|
+
const client = await this.getClient();
|
|
80
|
+
const remote = await client.channels().get({
|
|
81
|
+
queryArgs: {
|
|
82
|
+
limit: 1,
|
|
83
|
+
where: `key = :key AND ${this.getStoreChannelPredicate()}`,
|
|
84
|
+
"var.key": payload.identifier.key
|
|
85
|
+
}
|
|
86
|
+
}).execute();
|
|
87
|
+
const channel = remote.body.results[0];
|
|
88
|
+
if (!channel) {
|
|
89
|
+
return error({ type: "NotFound", identifier: payload.identifier });
|
|
90
|
+
}
|
|
91
|
+
return success(this.factory.parseStore(this.context, channel));
|
|
92
|
+
}
|
|
41
93
|
}
|
|
42
94
|
__decorateClass([
|
|
43
95
|
Reactionary({
|
|
@@ -45,6 +97,18 @@ __decorateClass([
|
|
|
45
97
|
outputSchema: z.array(StoreSchema)
|
|
46
98
|
})
|
|
47
99
|
], CommercetoolsStoreCapability.prototype, "queryByProximity", 1);
|
|
100
|
+
__decorateClass([
|
|
101
|
+
Reactionary({
|
|
102
|
+
inputSchema: StoreQueryForListSchema,
|
|
103
|
+
outputSchema: StorePaginatedResultSchema
|
|
104
|
+
})
|
|
105
|
+
], CommercetoolsStoreCapability.prototype, "list", 1);
|
|
106
|
+
__decorateClass([
|
|
107
|
+
Reactionary({
|
|
108
|
+
inputSchema: StoreQueryByIdSchema,
|
|
109
|
+
outputSchema: StoreSchema
|
|
110
|
+
})
|
|
111
|
+
], CommercetoolsStoreCapability.prototype, "getById", 1);
|
|
48
112
|
export {
|
|
49
113
|
CommercetoolsStoreCapability
|
|
50
114
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const SHIPPING_PROTOCOL_DATA_FIELD = "shippingProtocolData";
|
|
2
|
+
const PICKUP_STORE_FIELD = "pickupStoreKey";
|
|
3
|
+
function serializeShippingProtocolData(entries) {
|
|
4
|
+
return JSON.stringify(Object.fromEntries(entries.map((entry) => [entry.key, entry.value])));
|
|
5
|
+
}
|
|
6
|
+
function parseShippingProtocolDataField(value) {
|
|
7
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(value);
|
|
12
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
const entries = [];
|
|
16
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
17
|
+
if (typeof entry === "string") {
|
|
18
|
+
entries.push({ key, value: entry });
|
|
19
|
+
} else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
20
|
+
entries.push({ key, value: entry });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return entries.length > 0 ? entries : void 0;
|
|
24
|
+
} catch {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
PICKUP_STORE_FIELD,
|
|
30
|
+
SHIPPING_PROTOCOL_DATA_FIELD,
|
|
31
|
+
parseShippingProtocolDataField,
|
|
32
|
+
serializeShippingProtocolData
|
|
33
|
+
};
|
|
@@ -3,6 +3,11 @@ import {
|
|
|
3
3
|
CheckoutItemSchema
|
|
4
4
|
} from "@reactionary/core";
|
|
5
5
|
import { getLanguageCodeFromLocale } from "../../core/locale-utils.js";
|
|
6
|
+
import {
|
|
7
|
+
PICKUP_STORE_FIELD,
|
|
8
|
+
SHIPPING_PROTOCOL_DATA_FIELD,
|
|
9
|
+
parseShippingProtocolDataField
|
|
10
|
+
} from "../../core/shipping-protocol-data.js";
|
|
6
11
|
class CommercetoolsCheckoutFactory {
|
|
7
12
|
checkoutSchema;
|
|
8
13
|
shippingMethodSchema;
|
|
@@ -221,13 +226,20 @@ class CommercetoolsCheckoutFactory {
|
|
|
221
226
|
const instructions = data.custom?.fields["shippingInstruction"] || "";
|
|
222
227
|
const consentForUnattendedDelivery = data.custom?.fields["consentForUnattendedDelivery"] === "true";
|
|
223
228
|
const pickupPoint = data.custom?.fields["pickupPointId"] || "";
|
|
229
|
+
const pickupStoreKey = data.custom?.fields[PICKUP_STORE_FIELD];
|
|
230
|
+
const store = typeof pickupStoreKey === "string" && pickupStoreKey.length > 0 ? { key: pickupStoreKey } : void 0;
|
|
231
|
+
const protocolData = parseShippingProtocolDataField(
|
|
232
|
+
data.custom?.fields[SHIPPING_PROTOCOL_DATA_FIELD]
|
|
233
|
+
);
|
|
224
234
|
const result = {
|
|
225
235
|
shippingMethod: {
|
|
226
236
|
key: data.shippingInfo.shippingMethod?.obj?.key || ""
|
|
227
237
|
},
|
|
228
238
|
pickupPoint,
|
|
229
239
|
instructions,
|
|
230
|
-
consentForUnattendedDelivery
|
|
240
|
+
consentForUnattendedDelivery,
|
|
241
|
+
store,
|
|
242
|
+
protocolData
|
|
231
243
|
};
|
|
232
244
|
return result;
|
|
233
245
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/commercetools",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"url": "git+https://github.com/Solteq/reactionary.git"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@reactionary/core": "0.10.
|
|
12
|
+
"@reactionary/core": "0.10.54",
|
|
13
13
|
"zod": "4.1.9",
|
|
14
14
|
"@commercetools/ts-client": "^4.9.1",
|
|
15
15
|
"@commercetools/platform-sdk": "^8.25.0",
|
|
@@ -12,7 +12,10 @@ const CommercetoolsConfigurationSchema = z.looseObject({
|
|
|
12
12
|
paymentMethods: PaymentMethodSchema.array().optional().default(() => []),
|
|
13
13
|
facetFieldsForSearch: z.array(z.string()).default(() => []),
|
|
14
14
|
listPriceChannelKey: z.string().optional(),
|
|
15
|
-
customerPriceChannelKey: z.string().optional()
|
|
15
|
+
customerPriceChannelKey: z.string().optional(),
|
|
16
|
+
bopis: z.looseObject({
|
|
17
|
+
shippingMethodKeys: z.array(z.string()).default(() => []).meta({ description: 'The keys of the commercetools shipping methods that represent in-store pickup (BOPIS). Shipping methods with these keys are tagged with fulfillmentType "pickup"; all others are tagged "delivery".' })
|
|
18
|
+
}).optional().meta({ description: "Optional buy-online-pickup-in-store (BOPIS) configuration." })
|
|
16
19
|
});
|
|
17
20
|
export {
|
|
18
21
|
CommercetoolsConfigurationSchema
|
|
@@ -23,6 +23,13 @@ export declare class CommercetoolsCheckoutCapability<TFactory extends CheckoutFa
|
|
|
23
23
|
getById(payload: CheckoutQueryById): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>, NotFoundError>>;
|
|
24
24
|
setShippingAddress(payload: CheckoutMutationSetShippingAddress): Promise<Result<CheckoutFactoryCheckoutOutput<TFactory>>>;
|
|
25
25
|
getAvailableShippingMethods(payload: CheckoutQueryForAvailableShippingMethods): Promise<Result<CheckoutFactoryShippingMethodOutput<TFactory>[]>>;
|
|
26
|
+
/**
|
|
27
|
+
* Tags a parsed shipping method with its fulfillment type: methods whose
|
|
28
|
+
* key is listed in the `bopis.shippingMethodKeys` configuration are
|
|
29
|
+
* in-store pickup (BOPIS), everything else is a delivery. The tagging lives
|
|
30
|
+
* here rather than in the factory, so factories stay configuration-free.
|
|
31
|
+
*/
|
|
32
|
+
protected withFulfillmentType(shippingMethod: CheckoutFactoryShippingMethodOutput<TFactory>, remote: CTShippingMethod): CheckoutFactoryShippingMethodOutput<TFactory>;
|
|
26
33
|
/**
|
|
27
34
|
* Quotes the matching shipping rates as free where the cart reaches the
|
|
28
35
|
* rate's free-above threshold. commercetools' matching-cart query returns
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RequestContext, Cache, StoreFactory, StoreFactoryOutput, StoreFactoryWithOutput, StoreQueryByProximity, Result } from '@reactionary/core';
|
|
1
|
+
import type { RequestContext, Cache, NotFoundError, StoreFactory, StoreFactoryOutput, StoreFactoryWithOutput, StorePaginatedResult, StoreQueryById, StoreQueryByProximity, StoreQueryForList, Result } from '@reactionary/core';
|
|
2
2
|
import { StoreCapability } from '@reactionary/core';
|
|
3
3
|
import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
|
|
4
4
|
import type { CommercetoolsAPI } from '../core/client.js';
|
|
@@ -10,4 +10,11 @@ export declare class CommercetoolsStoreCapability<TFactory extends StoreFactory
|
|
|
10
10
|
constructor(config: CommercetoolsConfiguration, cache: Cache, context: RequestContext, commercetools: CommercetoolsAPI, factory: StoreFactoryWithOutput<TFactory>);
|
|
11
11
|
protected getClient(): Promise<import("@commercetools/platform-sdk").ByProjectKeyRequestBuilder>;
|
|
12
12
|
queryByProximity(payload: StoreQueryByProximity): Promise<Result<Array<StoreFactoryOutput<TFactory>>>>;
|
|
13
|
+
/**
|
|
14
|
+
* The channel predicate selecting the channels that represent the brand's
|
|
15
|
+
* own stores. Override in a subclass if a project models stores differently.
|
|
16
|
+
*/
|
|
17
|
+
protected getStoreChannelPredicate(): string;
|
|
18
|
+
list(payload: StoreQueryForList): Promise<Result<StorePaginatedResult>>;
|
|
19
|
+
getById(payload: StoreQueryById): Promise<Result<StoreFactoryOutput<TFactory>, NotFoundError>>;
|
|
13
20
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ShippingMethod as CTShippingMethod } from '@commercetools/platform-sdk';
|
|
2
|
+
import type { ShippingFulfillmentType } from '@reactionary/core';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the core fulfillment type of a commercetools shipping method from
|
|
5
|
+
* the `bopis.shippingMethodKeys` configuration: shipping methods whose key is
|
|
6
|
+
* listed are in-store pickup (BOPIS), everything else is a delivery.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveShippingFulfillmentType(shippingMethod: CTShippingMethod, bopisShippingMethodKeys: string[] | undefined): ShippingFulfillmentType;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ShippingProtocolData } from '@reactionary/core';
|
|
2
|
+
/**
|
|
3
|
+
* The cart custom field carrying the shipping instruction's protocol data,
|
|
4
|
+
* following the same one-declared-JSON-field mechanism as the payment
|
|
5
|
+
* protocol data (see payment-protocol-data.ts): commercetools rejects
|
|
6
|
+
* undeclared custom fields, so one declared field carries arbitrary keys.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SHIPPING_PROTOCOL_DATA_FIELD = "shippingProtocolData";
|
|
9
|
+
/**
|
|
10
|
+
* The cart custom field carrying the key of the brand's own store selected
|
|
11
|
+
* for in-store pickup (BOPIS). Distinct from `pickupPointId`, which is
|
|
12
|
+
* reserved for third-party carrier service points.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PICKUP_STORE_FIELD = "pickupStoreKey";
|
|
15
|
+
export declare function serializeShippingProtocolData(entries: ShippingProtocolData[]): string;
|
|
16
|
+
export declare function parseShippingProtocolDataField(value: unknown): ShippingProtocolData[] | undefined;
|
|
@@ -26,5 +26,8 @@ export declare const CommercetoolsConfigurationSchema: z.ZodObject<{
|
|
|
26
26
|
facetFieldsForSearch: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
27
27
|
listPriceChannelKey: z.ZodOptional<z.ZodString>;
|
|
28
28
|
customerPriceChannelKey: z.ZodOptional<z.ZodString>;
|
|
29
|
+
bopis: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
shippingMethodKeys: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
31
|
+
}, z.core.$loose>>;
|
|
29
32
|
}, z.core.$loose>;
|
|
30
33
|
export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
|