@reactionary/commercetools 0.10.47 → 0.10.49
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/cart.capability.js +6 -2
- package/capabilities/product-list.capability.js +3 -3
- package/capabilities/product-reviews.capability.js +2 -2
- package/core/attribute-utils.js +36 -0
- package/core/client.js +62 -17
- package/core/order-status.js +15 -0
- package/factories/cart/cart.factory.js +62 -1
- package/factories/checkout/checkout.factory.js +3 -2
- package/factories/order/order.factory.js +3 -11
- package/factories/order-search/order-search.factory.js +2 -10
- package/factories/product-associations/product-associations.factory.js +2 -7
- package/factories/product-search/product-search.factory.js +2 -7
- package/package.json +2 -2
- package/schema/oauth.schema.js +13 -0
- package/src/core/attribute-utils.d.ts +14 -0
- package/src/core/client.d.ts +11 -0
- package/src/core/order-status.d.ts +9 -0
- package/src/factories/cart/cart.factory.d.ts +16 -1
- package/src/schema/oauth.schema.d.ts +9 -0
|
@@ -33,7 +33,11 @@ import { getLanguageCodeFromLocale } from "../core/locale-utils.js";
|
|
|
33
33
|
class CommercetoolsCartCapability extends CartCapability {
|
|
34
34
|
config;
|
|
35
35
|
commercetools;
|
|
36
|
-
expandedCartFields = [
|
|
36
|
+
expandedCartFields = [
|
|
37
|
+
"discountCodes[*].discountCode",
|
|
38
|
+
"lineItems[*].discountedPricePerQuantity[*].discountedPrice.includedDiscounts[*].discount",
|
|
39
|
+
"discountOnTotalPrice.includedDiscounts[*].discount"
|
|
40
|
+
];
|
|
37
41
|
factory;
|
|
38
42
|
constructor(config, cache, context, commercetools, factory) {
|
|
39
43
|
super(cache, context);
|
|
@@ -180,7 +184,7 @@ class CommercetoolsCartCapability extends CartCapability {
|
|
|
180
184
|
...lineIdentifier.originalPrice && {
|
|
181
185
|
externalPrice: {
|
|
182
186
|
currencyCode: lineIdentifier.originalPrice.currency,
|
|
183
|
-
centAmount: Math.
|
|
187
|
+
centAmount: Math.round(lineIdentifier.originalPrice.value * 100)
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
},
|
|
@@ -394,7 +394,7 @@ class CommercetoolsProductListCapability extends ProductListCapability {
|
|
|
394
394
|
}
|
|
395
395
|
__decorateClass([
|
|
396
396
|
Reactionary({
|
|
397
|
-
cache:
|
|
397
|
+
cache: false,
|
|
398
398
|
cacheTimeToLiveInSeconds: 300,
|
|
399
399
|
currencyDependentCaching: false,
|
|
400
400
|
localeDependentCaching: false,
|
|
@@ -404,7 +404,7 @@ __decorateClass([
|
|
|
404
404
|
], CommercetoolsProductListCapability.prototype, "getById", 1);
|
|
405
405
|
__decorateClass([
|
|
406
406
|
Reactionary({
|
|
407
|
-
cache:
|
|
407
|
+
cache: false,
|
|
408
408
|
cacheTimeToLiveInSeconds: 300,
|
|
409
409
|
currencyDependentCaching: false,
|
|
410
410
|
localeDependentCaching: false,
|
|
@@ -435,7 +435,7 @@ __decorateClass([
|
|
|
435
435
|
], CommercetoolsProductListCapability.prototype, "deleteList", 1);
|
|
436
436
|
__decorateClass([
|
|
437
437
|
Reactionary({
|
|
438
|
-
cache:
|
|
438
|
+
cache: false,
|
|
439
439
|
cacheTimeToLiveInSeconds: 300,
|
|
440
440
|
currencyDependentCaching: false,
|
|
441
441
|
localeDependentCaching: false,
|
|
@@ -133,7 +133,7 @@ class CommercetoolsProductReviewsCapability extends ProductReviewsCapability {
|
|
|
133
133
|
},
|
|
134
134
|
target: {
|
|
135
135
|
typeId: "product",
|
|
136
|
-
|
|
136
|
+
key: mutation.product.key
|
|
137
137
|
},
|
|
138
138
|
locale: getLanguageCodeFromLocale(this.context.languageContext.locale)
|
|
139
139
|
}
|
|
@@ -144,7 +144,7 @@ class CommercetoolsProductReviewsCapability extends ProductReviewsCapability {
|
|
|
144
144
|
parseSingle(review, productKey) {
|
|
145
145
|
return {
|
|
146
146
|
identifier: {
|
|
147
|
-
key: review.key
|
|
147
|
+
key: review.key ?? review.id
|
|
148
148
|
},
|
|
149
149
|
product: {
|
|
150
150
|
key: productKey
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function getAttributeValueKeyAndLabel(value, language) {
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return getAttributeValueKeyAndLabel(value[0], language);
|
|
7
|
+
}
|
|
8
|
+
if (isRecord(value)) {
|
|
9
|
+
if ("key" in value && "label" in value) {
|
|
10
|
+
const key = String(value["key"]);
|
|
11
|
+
const label = getAttributeValueKeyAndLabel(value["label"], language).label;
|
|
12
|
+
return { key, label: label || key };
|
|
13
|
+
}
|
|
14
|
+
const localized = value[language] ?? Object.values(value).find((text3) => typeof text3 === "string");
|
|
15
|
+
const text2 = typeof localized === "string" ? localized : "";
|
|
16
|
+
return { key: text2, label: text2 };
|
|
17
|
+
}
|
|
18
|
+
const text = value === void 0 || value === null ? "" : String(value);
|
|
19
|
+
return { key: text, label: text };
|
|
20
|
+
}
|
|
21
|
+
function parseVariantOptionFromAttribute(attribute, language) {
|
|
22
|
+
const option = { key: attribute.name };
|
|
23
|
+
const { key, label } = getAttributeValueKeyAndLabel(attribute.value, language);
|
|
24
|
+
return {
|
|
25
|
+
identifier: option,
|
|
26
|
+
name: attribute.name,
|
|
27
|
+
value: {
|
|
28
|
+
identifier: { option, key },
|
|
29
|
+
label
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
getAttributeValueKeyAndLabel,
|
|
35
|
+
parseVariantOptionFromAttribute
|
|
36
|
+
};
|
package/core/client.js
CHANGED
|
@@ -13,6 +13,10 @@ import { RequestContextTokenCache } from "./token-cache.js";
|
|
|
13
13
|
import {
|
|
14
14
|
CommercetoolsSessionSchema
|
|
15
15
|
} from "../schema/session.schema.js";
|
|
16
|
+
import {
|
|
17
|
+
CommercetoolsAccessTokenSchema,
|
|
18
|
+
CommercetoolsTokenIntrospectionSchema
|
|
19
|
+
} from "../schema/oauth.schema.js";
|
|
16
20
|
const debug = createDebug("reactionary:commercetools");
|
|
17
21
|
const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
|
|
18
22
|
class CommercetoolsAPI {
|
|
@@ -81,7 +85,7 @@ class CommercetoolsAPI {
|
|
|
81
85
|
const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key }).get().execute();
|
|
82
86
|
const channel = response.body;
|
|
83
87
|
this.setSessionData({
|
|
84
|
-
cacheKey: channel.id
|
|
88
|
+
[cacheKey]: channel.id
|
|
85
89
|
});
|
|
86
90
|
if (debug.enabled) {
|
|
87
91
|
debug(`Resolved channel ${key} from API and cached it`);
|
|
@@ -215,7 +219,6 @@ class CommercetoolsAPI {
|
|
|
215
219
|
};
|
|
216
220
|
return identity;
|
|
217
221
|
}
|
|
218
|
-
// FIXME: This can fail if the short-lived access token has expired. In other words, probably missing a token refresh.
|
|
219
222
|
async introspect() {
|
|
220
223
|
const session = await this.tokenCache.get();
|
|
221
224
|
if (!session || !session.token) {
|
|
@@ -224,21 +227,13 @@ class CommercetoolsAPI {
|
|
|
224
227
|
};
|
|
225
228
|
return identity;
|
|
226
229
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Authorization: authHeader,
|
|
235
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
236
|
-
},
|
|
237
|
-
body: new URLSearchParams({
|
|
238
|
-
token: session.token
|
|
239
|
-
})
|
|
240
|
-
});
|
|
241
|
-
const body = await response.json();
|
|
230
|
+
let body = await this.introspectToken(session.token);
|
|
231
|
+
if (body && !body.active && session.refreshToken) {
|
|
232
|
+
const refreshedToken = await this.refreshAccessToken(session.refreshToken);
|
|
233
|
+
if (refreshedToken) {
|
|
234
|
+
body = await this.introspectToken(refreshedToken);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
242
237
|
if (!body) {
|
|
243
238
|
return AnonymousIdentitySchema.parse({});
|
|
244
239
|
}
|
|
@@ -271,6 +266,56 @@ class CommercetoolsAPI {
|
|
|
271
266
|
type: "Anonymous"
|
|
272
267
|
};
|
|
273
268
|
}
|
|
269
|
+
getBasicAuthHeader() {
|
|
270
|
+
return "Basic " + Buffer.from(
|
|
271
|
+
`${this.config.clientId}:${this.config.clientSecret}`
|
|
272
|
+
).toString("base64");
|
|
273
|
+
}
|
|
274
|
+
async introspectToken(token) {
|
|
275
|
+
const response = await fetch(`${this.config.authUrl}/oauth/introspect`, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: {
|
|
278
|
+
Authorization: this.getBasicAuthHeader(),
|
|
279
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
280
|
+
},
|
|
281
|
+
body: new URLSearchParams({
|
|
282
|
+
token
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
const result = CommercetoolsTokenIntrospectionSchema.safeParse(await response.json());
|
|
286
|
+
return result.success ? result.data : void 0;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Exchanges the refresh token for a new access token and stores it in the token cache.
|
|
290
|
+
* Returns the new access token, or undefined if the refresh token is no longer valid.
|
|
291
|
+
*/
|
|
292
|
+
async refreshAccessToken(refreshToken) {
|
|
293
|
+
const response = await fetch(`${this.config.authUrl}/oauth/token`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: {
|
|
296
|
+
Authorization: this.getBasicAuthHeader(),
|
|
297
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
298
|
+
},
|
|
299
|
+
body: new URLSearchParams({
|
|
300
|
+
grant_type: "refresh_token",
|
|
301
|
+
refresh_token: refreshToken
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
const parsed = CommercetoolsAccessTokenSchema.safeParse(await response.json());
|
|
308
|
+
if (!parsed.success) {
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
const result = parsed.data;
|
|
312
|
+
await this.tokenCache.set({
|
|
313
|
+
expirationTime: Date.now() + result.expires_in * 1e3 - 5 * 60 * 1e3,
|
|
314
|
+
token: result.access_token,
|
|
315
|
+
refreshToken
|
|
316
|
+
});
|
|
317
|
+
return result.access_token;
|
|
318
|
+
}
|
|
274
319
|
async becomeGuest() {
|
|
275
320
|
const credentials = Buffer.from(
|
|
276
321
|
`${this.config.clientId}:${this.config.clientSecret}`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function getOrderStatusFromOrderState(orderState) {
|
|
2
|
+
switch (orderState) {
|
|
3
|
+
case "Cancelled":
|
|
4
|
+
return "Cancelled";
|
|
5
|
+
case "Complete":
|
|
6
|
+
return "Shipped";
|
|
7
|
+
case "Confirmed":
|
|
8
|
+
return "ReleasedToFulfillment";
|
|
9
|
+
default:
|
|
10
|
+
return "AwaitingPayment";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
getOrderStatusFromOrderState
|
|
15
|
+
};
|
|
@@ -104,17 +104,38 @@ class CommercetoolsCartFactory {
|
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
106
|
const localeString = getLanguageCodeFromLocale(context.languageContext.locale) || "en";
|
|
107
|
+
const discountedAmountByCartDiscountId = this.groupDiscountedAmountsByCartDiscountId(data);
|
|
107
108
|
const appliedPromotions = [];
|
|
109
|
+
const codeClaimedCartDiscountIds = /* @__PURE__ */ new Set();
|
|
108
110
|
if (data.discountCodes) {
|
|
109
111
|
for (const promo of data.discountCodes) {
|
|
112
|
+
const cartDiscountIds = promo.discountCode.obj?.cartDiscounts.map((ref) => ref.id) || [];
|
|
113
|
+
cartDiscountIds.forEach((id) => codeClaimedCartDiscountIds.add(id));
|
|
114
|
+
const discountedCentAmount = cartDiscountIds.reduce(
|
|
115
|
+
(sum, id) => sum + (discountedAmountByCartDiscountId.get(id)?.centAmount || 0),
|
|
116
|
+
0
|
|
117
|
+
);
|
|
110
118
|
appliedPromotions.push({
|
|
111
119
|
code: promo.discountCode.obj?.code || "",
|
|
112
120
|
isCouponCode: true,
|
|
113
121
|
name: promo.discountCode.obj?.name?.[localeString] || "",
|
|
114
|
-
description: promo.discountCode.obj?.description?.[localeString] || ""
|
|
122
|
+
description: promo.discountCode.obj?.description?.[localeString] || "",
|
|
123
|
+
...discountedCentAmount > 0 ? { amount: { value: discountedCentAmount / 100, currency } } : {}
|
|
115
124
|
});
|
|
116
125
|
}
|
|
117
126
|
}
|
|
127
|
+
for (const [cartDiscountId, applied] of discountedAmountByCartDiscountId) {
|
|
128
|
+
if (codeClaimedCartDiscountIds.has(cartDiscountId)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
appliedPromotions.push({
|
|
132
|
+
code: "",
|
|
133
|
+
isCouponCode: false,
|
|
134
|
+
name: applied.cartDiscount?.name?.[localeString] || "",
|
|
135
|
+
description: applied.cartDiscount?.description?.[localeString] || "",
|
|
136
|
+
...applied.centAmount > 0 ? { amount: { value: applied.centAmount / 100, currency } } : {}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
118
139
|
const result = {
|
|
119
140
|
identifier,
|
|
120
141
|
user: {
|
|
@@ -129,6 +150,46 @@ class CommercetoolsCartFactory {
|
|
|
129
150
|
};
|
|
130
151
|
return this.cartSchema.parse(result);
|
|
131
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* commercetools links a discount code to the amount it discounted only
|
|
155
|
+
* indirectly: `discountCode.obj.cartDiscounts` gives the CartDiscount ids a
|
|
156
|
+
* code triggers, while the actual discounted amounts are reported per line
|
|
157
|
+
* item (`discountedPricePerQuantity[].discountedPrice.includedDiscounts`)
|
|
158
|
+
* and on the cart total (`discountOnTotalPrice.includedDiscounts`), each
|
|
159
|
+
* keyed by that same CartDiscount id. Sum both sources by CartDiscount id
|
|
160
|
+
* so a discount code's total can be looked up by joining on its ids, and
|
|
161
|
+
* keep the expanded CartDiscount (when available) so automatically applied
|
|
162
|
+
* discounts — those not claimed by any code — can be surfaced by name.
|
|
163
|
+
*/
|
|
164
|
+
groupDiscountedAmountsByCartDiscountId(data) {
|
|
165
|
+
const totals = /* @__PURE__ */ new Map();
|
|
166
|
+
const add = (cartDiscountId, centAmount, cartDiscount) => {
|
|
167
|
+
const entry = totals.get(cartDiscountId) || { centAmount: 0 };
|
|
168
|
+
entry.centAmount += centAmount;
|
|
169
|
+
entry.cartDiscount = entry.cartDiscount || cartDiscount;
|
|
170
|
+
totals.set(cartDiscountId, entry);
|
|
171
|
+
};
|
|
172
|
+
const expandedCartDiscount = (reference) => reference.typeId === "cart-discount" ? reference.obj : void 0;
|
|
173
|
+
for (const lineItem of data.lineItems) {
|
|
174
|
+
for (const discPrQty of lineItem.discountedPricePerQuantity || []) {
|
|
175
|
+
for (const included of discPrQty.discountedPrice?.includedDiscounts || []) {
|
|
176
|
+
add(
|
|
177
|
+
included.discount.id,
|
|
178
|
+
included.discountedAmount.centAmount * discPrQty.quantity,
|
|
179
|
+
expandedCartDiscount(included.discount)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const included of data.discountOnTotalPrice?.includedDiscounts || []) {
|
|
185
|
+
add(
|
|
186
|
+
included.discount.id,
|
|
187
|
+
included.discountedAmount.centAmount,
|
|
188
|
+
expandedCartDiscount(included.discount)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return totals;
|
|
192
|
+
}
|
|
132
193
|
parseCartItem(lineItem) {
|
|
133
194
|
const unitPrice = lineItem.price.value.centAmount;
|
|
134
195
|
const totalPrice = lineItem.totalPrice.centAmount || 0;
|
|
@@ -160,8 +160,9 @@ class CommercetoolsCheckoutFactory {
|
|
|
160
160
|
if (!shippingInstruction) return false;
|
|
161
161
|
if (!shippingAddress && !shippingInstruction.pickupPoint) return false;
|
|
162
162
|
if (paymentInstructions.length === 0) return false;
|
|
163
|
-
const
|
|
164
|
-
|
|
163
|
+
const toCents = (value) => Math.round(value * 100);
|
|
164
|
+
const authorizedPayments = paymentInstructions.filter((paymentInstruction) => paymentInstruction.status === "authorized").map((paymentInstruction) => toCents(paymentInstruction.amount.value)).reduce((sum, value) => sum + value, 0);
|
|
165
|
+
if (toCents(price.grandTotal.value) !== authorizedPayments) return false;
|
|
165
166
|
return true;
|
|
166
167
|
}
|
|
167
168
|
parsePaymentInstruction(context, data) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
} from "@reactionary/core";
|
|
3
|
+
import { getOrderStatusFromOrderState } from "../../core/order-status.js";
|
|
3
4
|
class CommercetoolsOrderFactory {
|
|
4
5
|
orderSchema;
|
|
5
6
|
constructor(orderSchema) {
|
|
@@ -34,7 +35,7 @@ class CommercetoolsOrderFactory {
|
|
|
34
35
|
},
|
|
35
36
|
totalShipping: {
|
|
36
37
|
value: shippingTotal / 100,
|
|
37
|
-
currency
|
|
38
|
+
currency
|
|
38
39
|
},
|
|
39
40
|
totalProductPrice: {
|
|
40
41
|
value: productTotal / 100,
|
|
@@ -45,16 +46,7 @@ class CommercetoolsOrderFactory {
|
|
|
45
46
|
currency
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
-
if (data.paymentState === "Paid" && data.orderState === "Confirmed") {
|
|
50
|
-
orderStatus = "ReleasedToFulfillment";
|
|
51
|
-
}
|
|
52
|
-
if (data.shipmentState === "Ready" && data.orderState === "Confirmed") {
|
|
53
|
-
orderStatus = "ReleasedToFulfillment";
|
|
54
|
-
}
|
|
55
|
-
if ((data.shipmentState === "Shipped" || data.shipmentState === "Delivered") && data.orderState === "Completed") {
|
|
56
|
-
orderStatus = "Shipped";
|
|
57
|
-
}
|
|
49
|
+
const orderStatus = getOrderStatusFromOrderState(data.orderState);
|
|
58
50
|
const items = [];
|
|
59
51
|
for (const remoteItem of data.lineItems) {
|
|
60
52
|
const lineIdentifier = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
} from "@reactionary/core";
|
|
3
|
+
import { getOrderStatusFromOrderState } from "../../core/order-status.js";
|
|
3
4
|
class CommercetoolsOrderSearchFactory {
|
|
4
5
|
orderSearchResultSchema;
|
|
5
6
|
constructor(orderSearchResultSchema) {
|
|
@@ -37,16 +38,7 @@ class CommercetoolsOrderSearchFactory {
|
|
|
37
38
|
const userId = {
|
|
38
39
|
userId: data.customerId || data.anonymousId || ""
|
|
39
40
|
};
|
|
40
|
-
|
|
41
|
-
if (data.paymentState === "Paid" && data.orderState === "Confirmed") {
|
|
42
|
-
orderStatus = "ReleasedToFulfillment";
|
|
43
|
-
}
|
|
44
|
-
if (data.shipmentState === "Ready" && data.orderState === "Confirmed") {
|
|
45
|
-
orderStatus = "ReleasedToFulfillment";
|
|
46
|
-
}
|
|
47
|
-
if ((data.shipmentState === "Shipped" || data.shipmentState === "Delivered") && data.orderState === "Completed") {
|
|
48
|
-
orderStatus = "Shipped";
|
|
49
|
-
}
|
|
41
|
+
const orderStatus = getOrderStatusFromOrderState(data.orderState);
|
|
50
42
|
const totalAmount = {
|
|
51
43
|
currency: data.totalPrice ? data.totalPrice.currencyCode : context.languageContext.currencyCode,
|
|
52
44
|
value: data.totalPrice ? data.totalPrice.centAmount / 100 : 0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ImageSchema,
|
|
3
|
-
ProductOptionIdentifierSchema,
|
|
4
3
|
ProductSearchResultItemComplianceDataSchema,
|
|
5
4
|
ProductSearchResultItemVariantSchema,
|
|
6
5
|
ProductVariantIdentifierSchema,
|
|
7
6
|
ProductVariantOptionSchema
|
|
8
7
|
} from "@reactionary/core";
|
|
8
|
+
import { parseVariantOptionFromAttribute } from "../../core/attribute-utils.js";
|
|
9
9
|
import { getLanguageCodeFromLocale } from "../../core/locale-utils.js";
|
|
10
10
|
class CommercetoolsProductAssociationsFactory {
|
|
11
11
|
productAssociationSchema;
|
|
@@ -45,12 +45,7 @@ class CommercetoolsProductAssociationsFactory {
|
|
|
45
45
|
altText: sourceImage?.label || product.name[getLanguageCodeFromLocale(context.languageContext.locale)] || void 0
|
|
46
46
|
});
|
|
47
47
|
const mappedOptions = variant.attributes?.filter((attribute) => attribute.name === "Color").map(
|
|
48
|
-
(option) => ProductVariantOptionSchema.parse(
|
|
49
|
-
identifier: ProductOptionIdentifierSchema.parse({
|
|
50
|
-
key: option.name
|
|
51
|
-
}),
|
|
52
|
-
name: option.value || ""
|
|
53
|
-
})
|
|
48
|
+
(option) => ProductVariantOptionSchema.parse(parseVariantOptionFromAttribute(option, getLanguageCodeFromLocale(context.languageContext.locale)))
|
|
54
49
|
) || [];
|
|
55
50
|
const mappedOption = mappedOptions?.[0];
|
|
56
51
|
return ProductSearchResultItemVariantSchema.parse({
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
FacetIdentifierSchema,
|
|
3
3
|
FacetValueIdentifierSchema,
|
|
4
4
|
ImageSchema,
|
|
5
|
-
ProductOptionIdentifierSchema,
|
|
6
5
|
ProductSearchResultFacetSchema,
|
|
7
6
|
ProductSearchResultFacetValueSchema,
|
|
8
7
|
ProductSearchResultItemComplianceDataSchema,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
ProductVariantIdentifierSchema,
|
|
11
10
|
ProductVariantOptionSchema
|
|
12
11
|
} from "@reactionary/core";
|
|
12
|
+
import { parseVariantOptionFromAttribute } from "../../core/attribute-utils.js";
|
|
13
13
|
import { getLanguageCodeFromLocale } from "../../core/locale-utils.js";
|
|
14
14
|
class CommercetoolsProductSearchFactory {
|
|
15
15
|
productSearchResultSchema;
|
|
@@ -100,12 +100,7 @@ class CommercetoolsProductSearchFactory {
|
|
|
100
100
|
altText: sourceImage?.label || product.name[localeStr] || void 0
|
|
101
101
|
});
|
|
102
102
|
const mappedOptions = variant.attributes?.filter((attribute) => attribute.name === "Color").map(
|
|
103
|
-
(option) => ProductVariantOptionSchema.parse(
|
|
104
|
-
identifier: ProductOptionIdentifierSchema.parse({
|
|
105
|
-
key: option.name
|
|
106
|
-
}),
|
|
107
|
-
name: option.value || ""
|
|
108
|
-
})
|
|
103
|
+
(option) => ProductVariantOptionSchema.parse(parseVariantOptionFromAttribute(option, localeStr))
|
|
109
104
|
) || [];
|
|
110
105
|
const mappedOption = mappedOptions?.[0];
|
|
111
106
|
return ProductSearchResultItemVariantSchema.parse({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/commercetools",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.49",
|
|
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.49",
|
|
13
13
|
"zod": "4.1.9",
|
|
14
14
|
"@commercetools/ts-client": "^4.9.1",
|
|
15
15
|
"@commercetools/platform-sdk": "^8.25.0",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
const CommercetoolsTokenIntrospectionSchema = z.looseObject({
|
|
3
|
+
active: z.boolean(),
|
|
4
|
+
scope: z.string().optional()
|
|
5
|
+
});
|
|
6
|
+
const CommercetoolsAccessTokenSchema = z.looseObject({
|
|
7
|
+
access_token: z.string(),
|
|
8
|
+
expires_in: z.number()
|
|
9
|
+
});
|
|
10
|
+
export {
|
|
11
|
+
CommercetoolsAccessTokenSchema,
|
|
12
|
+
CommercetoolsTokenIntrospectionSchema
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Attribute as CTAttribute } from '@commercetools/platform-sdk';
|
|
2
|
+
import type { ProductVariantOption } from '@reactionary/core';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves an attribute value to a key and a display label, covering plain values,
|
|
5
|
+
* localized text and (localized) enums. Sets and other complex values use their first entry.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getAttributeValueKeyAndLabel(value: unknown, language: string): {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Maps a variant attribute to a variant option, using the attribute name as the option.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseVariantOptionFromAttribute(attribute: CTAttribute, language: string): ProductVariantOption;
|
package/src/core/client.d.ts
CHANGED
|
@@ -53,6 +53,17 @@ export declare class CommercetoolsAPI {
|
|
|
53
53
|
type: "Anonymous";
|
|
54
54
|
}>;
|
|
55
55
|
introspect(): Promise<AnonymousIdentity | GuestIdentity | RegisteredIdentity>;
|
|
56
|
+
protected getBasicAuthHeader(): string;
|
|
57
|
+
protected introspectToken(token: string): Promise<{
|
|
58
|
+
[x: string]: unknown;
|
|
59
|
+
active: boolean;
|
|
60
|
+
scope?: string | undefined;
|
|
61
|
+
} | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* Exchanges the refresh token for a new access token and stores it in the token cache.
|
|
64
|
+
* Returns the new access token, or undefined if the refresh token is no longer valid.
|
|
65
|
+
*/
|
|
66
|
+
protected refreshAccessToken(refreshToken: string): Promise<string | undefined>;
|
|
56
67
|
protected becomeGuest(): Promise<void>;
|
|
57
68
|
protected createBaseClientBuilder(): ClientBuilder;
|
|
58
69
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OrderState } from '@commercetools/platform-sdk';
|
|
2
|
+
import type { OrderStatus } from '@reactionary/core';
|
|
3
|
+
/**
|
|
4
|
+
* Maps a Commercetools order state to an order status.
|
|
5
|
+
*
|
|
6
|
+
* This is the inverse of the order state filter applied by order search, so orders
|
|
7
|
+
* found by searching for a status are also reported with that status.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getOrderStatusFromOrderState(orderState: OrderState): OrderStatus;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CartPagedQueryResponse, Cart as CTCart, LineItem } from '@commercetools/platform-sdk';
|
|
1
|
+
import type { CartDiscount, CartPagedQueryResponse, Cart as CTCart, LineItem } from '@commercetools/platform-sdk';
|
|
2
2
|
import type { AnyCartPaginatedSearchResult, CartPaginatedSearchResultSchema, CartQueryList, CartSchema, CartSearchResultItem } from '@reactionary/core';
|
|
3
3
|
import { type AnyCartIdentifierSchema, type AnyCartSchema, type CartFactory, type CartItem, type RequestContext } from '@reactionary/core';
|
|
4
4
|
import type * as z from 'zod';
|
|
@@ -12,5 +12,20 @@ export declare class CommercetoolsCartFactory<TCartSchema extends AnyCartSchema
|
|
|
12
12
|
parseCartIdentifier(_context: RequestContext, data: CTCart): z.output<TCartIdentifierSchema>;
|
|
13
13
|
parseCartSearchResultItem(context: RequestContext, data: CTCart): CartSearchResultItem;
|
|
14
14
|
parseCart(context: RequestContext, data: CTCart): z.output<TCartSchema>;
|
|
15
|
+
/**
|
|
16
|
+
* commercetools links a discount code to the amount it discounted only
|
|
17
|
+
* indirectly: `discountCode.obj.cartDiscounts` gives the CartDiscount ids a
|
|
18
|
+
* code triggers, while the actual discounted amounts are reported per line
|
|
19
|
+
* item (`discountedPricePerQuantity[].discountedPrice.includedDiscounts`)
|
|
20
|
+
* and on the cart total (`discountOnTotalPrice.includedDiscounts`), each
|
|
21
|
+
* keyed by that same CartDiscount id. Sum both sources by CartDiscount id
|
|
22
|
+
* so a discount code's total can be looked up by joining on its ids, and
|
|
23
|
+
* keep the expanded CartDiscount (when available) so automatically applied
|
|
24
|
+
* discounts — those not claimed by any code — can be surfaced by name.
|
|
25
|
+
*/
|
|
26
|
+
protected groupDiscountedAmountsByCartDiscountId(data: CTCart): Map<string, {
|
|
27
|
+
centAmount: number;
|
|
28
|
+
cartDiscount?: CartDiscount;
|
|
29
|
+
}>;
|
|
15
30
|
protected parseCartItem(lineItem: LineItem): CartItem;
|
|
16
31
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
export declare const CommercetoolsTokenIntrospectionSchema: z.ZodObject<{
|
|
3
|
+
active: z.ZodBoolean;
|
|
4
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, z.core.$loose>;
|
|
6
|
+
export declare const CommercetoolsAccessTokenSchema: z.ZodObject<{
|
|
7
|
+
access_token: z.ZodString;
|
|
8
|
+
expires_in: z.ZodNumber;
|
|
9
|
+
}, z.core.$loose>;
|