@labdigital/commercetools-mock 2.58.0 → 2.59.1
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/dist/index.d.ts +24 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +234 -131
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/repositories/cart/actions.ts +78 -0
- package/src/repositories/cart/index.ts +11 -137
- package/src/repositories/order/actions.ts +81 -0
- package/src/repositories/order/index.test.ts +307 -0
- package/src/repositories/order/index.ts +98 -2
- package/src/services/cart.test.ts +170 -0
- package/src/services/order.test.ts +236 -1
- package/src/shipping.ts +157 -0
package/src/shipping.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Cart,
|
|
3
3
|
CartValueTier,
|
|
4
|
+
CentPrecisionMoney,
|
|
4
5
|
InvalidOperationError,
|
|
6
|
+
MissingTaxRateForCountryError,
|
|
7
|
+
Order,
|
|
8
|
+
ShippingInfo,
|
|
9
|
+
ShippingMethod,
|
|
10
|
+
ShippingMethodDoesNotMatchCartError,
|
|
5
11
|
ShippingRate,
|
|
6
12
|
ShippingRatePriceTier,
|
|
13
|
+
TaxPortion,
|
|
14
|
+
TaxRate,
|
|
15
|
+
TaxedItemPrice,
|
|
7
16
|
} from "@commercetools/platform-sdk";
|
|
17
|
+
import { Decimal } from "decimal.js";
|
|
8
18
|
import { CommercetoolsError } from "./exceptions";
|
|
9
19
|
import type { GetParams, RepositoryContext } from "./repositories/abstract";
|
|
20
|
+
import { createCentPrecisionMoney, roundDecimal } from "./repositories/helpers";
|
|
10
21
|
import type { AbstractStorage } from "./storage/abstract";
|
|
11
22
|
|
|
12
23
|
export const markMatchingShippingRate = (
|
|
@@ -145,3 +156,149 @@ export const getShippingMethodsMatchingCart = (
|
|
|
145
156
|
results: results,
|
|
146
157
|
};
|
|
147
158
|
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Interface for cart-like objects that can be used for shipping calculations
|
|
162
|
+
*/
|
|
163
|
+
interface ShippingCalculationSource {
|
|
164
|
+
id: string;
|
|
165
|
+
totalPrice: CentPrecisionMoney;
|
|
166
|
+
shippingAddress?: {
|
|
167
|
+
country: string;
|
|
168
|
+
[key: string]: any;
|
|
169
|
+
};
|
|
170
|
+
taxRoundingMode?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Creates shipping info from a shipping method, handling all tax calculations and pricing logic.
|
|
175
|
+
*/
|
|
176
|
+
export const createShippingInfoFromMethod = (
|
|
177
|
+
context: RepositoryContext,
|
|
178
|
+
storage: AbstractStorage,
|
|
179
|
+
resource: ShippingCalculationSource,
|
|
180
|
+
method: ShippingMethod,
|
|
181
|
+
): Omit<ShippingInfo, "deliveries"> => {
|
|
182
|
+
const country = resource.shippingAddress!.country;
|
|
183
|
+
|
|
184
|
+
// There should only be one zone rate matching the address, since
|
|
185
|
+
// Locations cannot be assigned to more than one zone.
|
|
186
|
+
// See https://docs.commercetools.com/api/projects/zones#location
|
|
187
|
+
const zoneRate = method.zoneRates.find((rate) =>
|
|
188
|
+
rate.zone.obj?.locations.some((loc) => loc.country === country),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!zoneRate) {
|
|
192
|
+
// This shouldn't happen because getShippingMethodsMatchingCart already
|
|
193
|
+
// filtered out shipping methods without any zones matching the address
|
|
194
|
+
throw new Error("Zone rate not found");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Shipping rates are defined by currency, and getShippingMethodsMatchingCart
|
|
198
|
+
// also matches on currency, so there should only be one in the array.
|
|
199
|
+
// See https://docs.commercetools.com/api/projects/shippingMethods#zonerate
|
|
200
|
+
const shippingRate = zoneRate.shippingRates[0];
|
|
201
|
+
if (!shippingRate) {
|
|
202
|
+
// This shouldn't happen because getShippingMethodsMatchingCart already
|
|
203
|
+
// filtered out shipping methods without any matching rates
|
|
204
|
+
throw new Error("Shipping rate not found");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const taxCategory = storage.getByResourceIdentifier<"tax-category">(
|
|
208
|
+
context.projectKey,
|
|
209
|
+
method.taxCategory,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// TODO: match state in addition to country
|
|
213
|
+
const taxRate = taxCategory.rates.find((rate) => rate.country === country);
|
|
214
|
+
|
|
215
|
+
if (!taxRate) {
|
|
216
|
+
throw new CommercetoolsError<MissingTaxRateForCountryError>({
|
|
217
|
+
code: "MissingTaxRateForCountry",
|
|
218
|
+
message: `Tax category '${taxCategory.id}' is missing a tax rate for country '${country}'.`,
|
|
219
|
+
taxCategoryId: taxCategory.id,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const shippingRateTier = shippingRate.tiers.find((tier) => tier.isMatching);
|
|
224
|
+
if (shippingRateTier && shippingRateTier.type !== "CartValue") {
|
|
225
|
+
throw new Error("Non-CartValue shipping rate tier is not supported");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let shippingPrice = shippingRateTier
|
|
229
|
+
? createCentPrecisionMoney(shippingRateTier.price)
|
|
230
|
+
: shippingRate.price;
|
|
231
|
+
|
|
232
|
+
// Handle freeAbove: if total is above the freeAbove threshold, shipping is free
|
|
233
|
+
if (
|
|
234
|
+
shippingRate.freeAbove &&
|
|
235
|
+
shippingRate.freeAbove.currencyCode === resource.totalPrice.currencyCode &&
|
|
236
|
+
resource.totalPrice.centAmount >= shippingRate.freeAbove.centAmount
|
|
237
|
+
) {
|
|
238
|
+
shippingPrice = {
|
|
239
|
+
...shippingPrice,
|
|
240
|
+
centAmount: 0,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Calculate tax amounts
|
|
245
|
+
const totalGross: CentPrecisionMoney = taxRate.includedInPrice
|
|
246
|
+
? shippingPrice
|
|
247
|
+
: {
|
|
248
|
+
...shippingPrice,
|
|
249
|
+
centAmount: roundDecimal(
|
|
250
|
+
new Decimal(shippingPrice.centAmount).mul(1 + taxRate.amount),
|
|
251
|
+
resource.taxRoundingMode || "HalfEven",
|
|
252
|
+
).toNumber(),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const totalNet: CentPrecisionMoney = taxRate.includedInPrice
|
|
256
|
+
? {
|
|
257
|
+
...shippingPrice,
|
|
258
|
+
centAmount: roundDecimal(
|
|
259
|
+
new Decimal(shippingPrice.centAmount).div(1 + taxRate.amount),
|
|
260
|
+
resource.taxRoundingMode || "HalfEven",
|
|
261
|
+
).toNumber(),
|
|
262
|
+
}
|
|
263
|
+
: shippingPrice;
|
|
264
|
+
|
|
265
|
+
const taxPortions: TaxPortion[] = [
|
|
266
|
+
{
|
|
267
|
+
name: taxRate.name,
|
|
268
|
+
rate: taxRate.amount,
|
|
269
|
+
amount: {
|
|
270
|
+
...shippingPrice,
|
|
271
|
+
centAmount: totalGross.centAmount - totalNet.centAmount,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const totalTax: CentPrecisionMoney = {
|
|
277
|
+
...shippingPrice,
|
|
278
|
+
centAmount: taxPortions.reduce(
|
|
279
|
+
(acc, portion) => acc + portion.amount.centAmount,
|
|
280
|
+
0,
|
|
281
|
+
),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const taxedPrice: TaxedItemPrice = {
|
|
285
|
+
totalNet,
|
|
286
|
+
totalGross,
|
|
287
|
+
taxPortions,
|
|
288
|
+
totalTax,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
shippingMethod: {
|
|
293
|
+
typeId: "shipping-method" as const,
|
|
294
|
+
id: method.id,
|
|
295
|
+
},
|
|
296
|
+
shippingMethodName: method.name,
|
|
297
|
+
price: shippingPrice,
|
|
298
|
+
shippingRate,
|
|
299
|
+
taxedPrice,
|
|
300
|
+
taxRate,
|
|
301
|
+
taxCategory: method.taxCategory,
|
|
302
|
+
shippingMethodState: "MatchesCart",
|
|
303
|
+
};
|
|
304
|
+
};
|