@labdigital/commercetools-mock 2.34.3 → 2.35.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/dist/index.cjs +244 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +244 -158
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/product-projection-search.ts +1 -1
- package/src/product-search.ts +1 -1
- package/src/repositories/cart/actions.ts +160 -4
- package/src/repositories/helpers.ts +21 -0
- package/src/repositories/product-projection.ts +1 -1
- package/src/repositories/shipping-method/index.ts +2 -54
- package/src/services/cart.test.ts +417 -0
- package/src/{shippingCalculator.test.ts → shipping.test.ts} +1 -1
- package/src/shipping.ts +147 -0
- package/src/shippingCalculator.ts +0 -74
|
@@ -3,6 +3,12 @@ import type {
|
|
|
3
3
|
Cart,
|
|
4
4
|
CentPrecisionMoney,
|
|
5
5
|
ProductDraft,
|
|
6
|
+
ShippingMethod,
|
|
7
|
+
ShippingMethodDraft,
|
|
8
|
+
ShippingMethodResourceIdentifier,
|
|
9
|
+
TaxCategory,
|
|
10
|
+
TaxCategoryDraft,
|
|
11
|
+
Zone,
|
|
6
12
|
} from "@commercetools/platform-sdk";
|
|
7
13
|
import assert from "assert";
|
|
8
14
|
import supertest from "supertest";
|
|
@@ -89,6 +95,41 @@ describe("Cart Update Actions", () => {
|
|
|
89
95
|
cart = response.body;
|
|
90
96
|
};
|
|
91
97
|
|
|
98
|
+
const createZone = async (country: string): Promise<Zone> => {
|
|
99
|
+
const response = await supertest(ctMock.app)
|
|
100
|
+
.post("/dummy/zones")
|
|
101
|
+
.send({
|
|
102
|
+
name: country,
|
|
103
|
+
locations: [
|
|
104
|
+
{
|
|
105
|
+
country,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
expect(response.status).toBe(201);
|
|
110
|
+
return response.body;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const createTaxCategory = async (
|
|
114
|
+
draft: TaxCategoryDraft,
|
|
115
|
+
): Promise<TaxCategory> => {
|
|
116
|
+
const response = await supertest(ctMock.app)
|
|
117
|
+
.post("/dummy/tax-categories")
|
|
118
|
+
.send(draft);
|
|
119
|
+
expect(response.status).toBe(201);
|
|
120
|
+
return response.body;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const createShippingMethod = async (
|
|
124
|
+
draft: ShippingMethodDraft,
|
|
125
|
+
): Promise<ShippingMethod> => {
|
|
126
|
+
const response = await supertest(ctMock.app)
|
|
127
|
+
.post("/dummy/shipping-methods")
|
|
128
|
+
.send(draft);
|
|
129
|
+
expect(response.status).toBe(201);
|
|
130
|
+
return response.body;
|
|
131
|
+
};
|
|
132
|
+
|
|
92
133
|
const productDraft: ProductDraft = {
|
|
93
134
|
name: {
|
|
94
135
|
"nl-NL": "test product",
|
|
@@ -672,6 +713,7 @@ describe("Cart Update Actions", () => {
|
|
|
672
713
|
},
|
|
673
714
|
});
|
|
674
715
|
});
|
|
716
|
+
|
|
675
717
|
test("setShippingAddressCustomType", async () => {
|
|
676
718
|
assert(cart, "cart not created");
|
|
677
719
|
|
|
@@ -739,6 +781,381 @@ describe("Cart Update Actions", () => {
|
|
|
739
781
|
},
|
|
740
782
|
});
|
|
741
783
|
});
|
|
784
|
+
|
|
785
|
+
describe("setShippingMethod", () => {
|
|
786
|
+
let standardShippingMethod: ShippingMethod;
|
|
787
|
+
let standardExcludedShippingMethod: ShippingMethod;
|
|
788
|
+
beforeEach(async () => {
|
|
789
|
+
assert(cart, "cart not created");
|
|
790
|
+
const nlZone = await createZone("NL");
|
|
791
|
+
const frZone = await createZone("FR");
|
|
792
|
+
const standardTax = await createTaxCategory({
|
|
793
|
+
name: "Standard tax category",
|
|
794
|
+
key: "standard",
|
|
795
|
+
rates: [
|
|
796
|
+
{
|
|
797
|
+
name: "FR standard tax rate",
|
|
798
|
+
amount: 0.2,
|
|
799
|
+
includedInPrice: true,
|
|
800
|
+
country: "FR",
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "NL standard tax rate",
|
|
804
|
+
amount: 0.21,
|
|
805
|
+
includedInPrice: true,
|
|
806
|
+
country: "NL",
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
});
|
|
810
|
+
await createTaxCategory({
|
|
811
|
+
name: "Reduced tax category",
|
|
812
|
+
key: "reduced",
|
|
813
|
+
rates: [
|
|
814
|
+
{
|
|
815
|
+
name: "FR reduced tax rate",
|
|
816
|
+
amount: 0.1,
|
|
817
|
+
includedInPrice: true,
|
|
818
|
+
country: "FR",
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
name: "NL reduced tax rate",
|
|
822
|
+
amount: 0.09,
|
|
823
|
+
includedInPrice: true,
|
|
824
|
+
country: "NL",
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
});
|
|
828
|
+
const standardExcludedTax = await createTaxCategory({
|
|
829
|
+
name: "Tax category that is excluded from price",
|
|
830
|
+
key: "standard-excluded",
|
|
831
|
+
rates: [
|
|
832
|
+
{
|
|
833
|
+
name: "FR standard-excluded tax rate",
|
|
834
|
+
amount: 0.2,
|
|
835
|
+
includedInPrice: false,
|
|
836
|
+
country: "FR",
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
name: "NL standard-excluded tax rate",
|
|
840
|
+
amount: 0.21,
|
|
841
|
+
includedInPrice: false,
|
|
842
|
+
country: "NL",
|
|
843
|
+
},
|
|
844
|
+
],
|
|
845
|
+
});
|
|
846
|
+
standardShippingMethod = await createShippingMethod({
|
|
847
|
+
isDefault: false,
|
|
848
|
+
key: "standard",
|
|
849
|
+
name: "Standard shipping",
|
|
850
|
+
taxCategory: {
|
|
851
|
+
typeId: "tax-category",
|
|
852
|
+
id: standardTax.id,
|
|
853
|
+
},
|
|
854
|
+
zoneRates: [
|
|
855
|
+
{
|
|
856
|
+
zone: {
|
|
857
|
+
typeId: "zone",
|
|
858
|
+
id: nlZone.id,
|
|
859
|
+
},
|
|
860
|
+
shippingRates: [
|
|
861
|
+
{
|
|
862
|
+
price: {
|
|
863
|
+
type: "centPrecision",
|
|
864
|
+
currencyCode: "EUR",
|
|
865
|
+
centAmount: 499,
|
|
866
|
+
fractionDigits: 2,
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
zone: {
|
|
873
|
+
typeId: "zone",
|
|
874
|
+
id: frZone.id,
|
|
875
|
+
},
|
|
876
|
+
shippingRates: [
|
|
877
|
+
{
|
|
878
|
+
price: {
|
|
879
|
+
type: "centPrecision",
|
|
880
|
+
currencyCode: "EUR",
|
|
881
|
+
centAmount: 699,
|
|
882
|
+
fractionDigits: 2,
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
},
|
|
887
|
+
],
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
standardExcludedShippingMethod = await createShippingMethod({
|
|
891
|
+
isDefault: false,
|
|
892
|
+
key: "standard-excluded",
|
|
893
|
+
name: "Standard shipping with tax excluded from price",
|
|
894
|
+
taxCategory: {
|
|
895
|
+
typeId: "tax-category",
|
|
896
|
+
id: standardExcludedTax.id,
|
|
897
|
+
},
|
|
898
|
+
zoneRates: [
|
|
899
|
+
{
|
|
900
|
+
zone: {
|
|
901
|
+
typeId: "zone",
|
|
902
|
+
id: nlZone.id,
|
|
903
|
+
},
|
|
904
|
+
shippingRates: [
|
|
905
|
+
{
|
|
906
|
+
price: {
|
|
907
|
+
type: "centPrecision",
|
|
908
|
+
currencyCode: "EUR",
|
|
909
|
+
centAmount: 499,
|
|
910
|
+
fractionDigits: 2,
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
],
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
zone: {
|
|
917
|
+
typeId: "zone",
|
|
918
|
+
id: frZone.id,
|
|
919
|
+
},
|
|
920
|
+
shippingRates: [
|
|
921
|
+
{
|
|
922
|
+
price: {
|
|
923
|
+
type: "centPrecision",
|
|
924
|
+
currencyCode: "EUR",
|
|
925
|
+
centAmount: 699,
|
|
926
|
+
fractionDigits: 2,
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
],
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
});
|
|
933
|
+
await createShippingMethod({
|
|
934
|
+
isDefault: false,
|
|
935
|
+
key: "express",
|
|
936
|
+
name: "Express shipping",
|
|
937
|
+
taxCategory: {
|
|
938
|
+
typeId: "tax-category",
|
|
939
|
+
id: standardTax.id,
|
|
940
|
+
},
|
|
941
|
+
zoneRates: [
|
|
942
|
+
{
|
|
943
|
+
zone: {
|
|
944
|
+
typeId: "zone",
|
|
945
|
+
id: nlZone.id,
|
|
946
|
+
},
|
|
947
|
+
shippingRates: [
|
|
948
|
+
{
|
|
949
|
+
price: {
|
|
950
|
+
type: "centPrecision",
|
|
951
|
+
currencyCode: "EUR",
|
|
952
|
+
centAmount: 899,
|
|
953
|
+
fractionDigits: 2,
|
|
954
|
+
},
|
|
955
|
+
},
|
|
956
|
+
],
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
zone: {
|
|
960
|
+
typeId: "zone",
|
|
961
|
+
id: frZone.id,
|
|
962
|
+
},
|
|
963
|
+
shippingRates: [
|
|
964
|
+
{
|
|
965
|
+
price: {
|
|
966
|
+
type: "centPrecision",
|
|
967
|
+
currencyCode: "EUR",
|
|
968
|
+
centAmount: 1099,
|
|
969
|
+
fractionDigits: 2,
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
],
|
|
973
|
+
},
|
|
974
|
+
],
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
expect(
|
|
978
|
+
(
|
|
979
|
+
await supertest(ctMock.app)
|
|
980
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
981
|
+
.send({
|
|
982
|
+
version: 1,
|
|
983
|
+
actions: [
|
|
984
|
+
{
|
|
985
|
+
action: "setShippingAddress",
|
|
986
|
+
address: {
|
|
987
|
+
streetName: "Street name",
|
|
988
|
+
city: "Utrecht",
|
|
989
|
+
country: "NL",
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
})
|
|
994
|
+
).status,
|
|
995
|
+
).toBe(200);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test("correctly sets shipping method", async () => {
|
|
999
|
+
assert(cart, "cart not created");
|
|
1000
|
+
|
|
1001
|
+
const shippingMethod: ShippingMethodResourceIdentifier = {
|
|
1002
|
+
typeId: "shipping-method",
|
|
1003
|
+
id: standardShippingMethod.id,
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const response = await supertest(ctMock.app)
|
|
1007
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
1008
|
+
.send({
|
|
1009
|
+
version: 2,
|
|
1010
|
+
actions: [{ action: "setShippingMethod", shippingMethod }],
|
|
1011
|
+
});
|
|
1012
|
+
expect(response.status).toBe(200);
|
|
1013
|
+
expect(response.body.version).toBe(3);
|
|
1014
|
+
expect(response.body.shippingInfo.shippingMethod.id).toEqual(
|
|
1015
|
+
standardShippingMethod.id,
|
|
1016
|
+
);
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("correctly sets shippingInfo rates + tax when includedInPrice: true", async () => {
|
|
1020
|
+
assert(cart, "cart not created");
|
|
1021
|
+
assert(standardShippingMethod, "shipping method not created");
|
|
1022
|
+
|
|
1023
|
+
const shippingMethod: ShippingMethodResourceIdentifier = {
|
|
1024
|
+
typeId: "shipping-method",
|
|
1025
|
+
id: standardShippingMethod.id,
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const response = await supertest(ctMock.app)
|
|
1029
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
1030
|
+
.send({
|
|
1031
|
+
version: 2,
|
|
1032
|
+
actions: [{ action: "setShippingMethod", shippingMethod }],
|
|
1033
|
+
});
|
|
1034
|
+
expect(response.status).toBe(200);
|
|
1035
|
+
expect(response.body.version).toBe(3);
|
|
1036
|
+
expect(response.body.shippingInfo.shippingRate.price).toMatchObject({
|
|
1037
|
+
centAmount: 499,
|
|
1038
|
+
currencyCode: "EUR",
|
|
1039
|
+
fractionDigits: 2,
|
|
1040
|
+
type: "centPrecision",
|
|
1041
|
+
});
|
|
1042
|
+
expect(response.body.shippingInfo.price).toMatchObject({
|
|
1043
|
+
centAmount: 499,
|
|
1044
|
+
currencyCode: "EUR",
|
|
1045
|
+
fractionDigits: 2,
|
|
1046
|
+
type: "centPrecision",
|
|
1047
|
+
});
|
|
1048
|
+
expect(response.body.shippingInfo.taxRate).toMatchObject({
|
|
1049
|
+
name: "NL standard tax rate",
|
|
1050
|
+
amount: 0.21,
|
|
1051
|
+
includedInPrice: true,
|
|
1052
|
+
country: "NL",
|
|
1053
|
+
});
|
|
1054
|
+
expect(response.body.shippingInfo.taxedPrice).toMatchObject({
|
|
1055
|
+
totalNet: {
|
|
1056
|
+
type: "centPrecision",
|
|
1057
|
+
centAmount: 412,
|
|
1058
|
+
currencyCode: "EUR",
|
|
1059
|
+
fractionDigits: 2,
|
|
1060
|
+
},
|
|
1061
|
+
totalGross: {
|
|
1062
|
+
type: "centPrecision",
|
|
1063
|
+
centAmount: 499,
|
|
1064
|
+
currencyCode: "EUR",
|
|
1065
|
+
fractionDigits: 2,
|
|
1066
|
+
},
|
|
1067
|
+
taxPortions: [
|
|
1068
|
+
{
|
|
1069
|
+
name: "NL standard tax rate",
|
|
1070
|
+
rate: 0.21,
|
|
1071
|
+
amount: {
|
|
1072
|
+
type: "centPrecision",
|
|
1073
|
+
centAmount: 87,
|
|
1074
|
+
currencyCode: "EUR",
|
|
1075
|
+
fractionDigits: 2,
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
],
|
|
1079
|
+
totalTax: {
|
|
1080
|
+
type: "centPrecision",
|
|
1081
|
+
centAmount: 87,
|
|
1082
|
+
currencyCode: "EUR",
|
|
1083
|
+
fractionDigits: 2,
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("correctly sets shippingInfo rates + tax when includedInPrice: false", async () => {
|
|
1089
|
+
assert(cart, "cart not created");
|
|
1090
|
+
assert(standardExcludedShippingMethod, "shipping method not created");
|
|
1091
|
+
|
|
1092
|
+
const shippingMethod: ShippingMethodResourceIdentifier = {
|
|
1093
|
+
typeId: "shipping-method",
|
|
1094
|
+
id: standardExcludedShippingMethod.id,
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const response = await supertest(ctMock.app)
|
|
1098
|
+
.post(`/dummy/carts/${cart.id}`)
|
|
1099
|
+
.send({
|
|
1100
|
+
version: 2,
|
|
1101
|
+
actions: [{ action: "setShippingMethod", shippingMethod }],
|
|
1102
|
+
});
|
|
1103
|
+
expect(response.status).toBe(200);
|
|
1104
|
+
expect(response.body.version).toBe(3);
|
|
1105
|
+
expect(response.body.shippingInfo.shippingRate.price).toMatchObject({
|
|
1106
|
+
centAmount: 499,
|
|
1107
|
+
currencyCode: "EUR",
|
|
1108
|
+
fractionDigits: 2,
|
|
1109
|
+
type: "centPrecision",
|
|
1110
|
+
});
|
|
1111
|
+
// TODO: should this be gross or net? docs unclear (currently always just returns the shipping rate (tier) price)
|
|
1112
|
+
expect(response.body.shippingInfo.price).toMatchObject({
|
|
1113
|
+
centAmount: 499,
|
|
1114
|
+
currencyCode: "EUR",
|
|
1115
|
+
fractionDigits: 2,
|
|
1116
|
+
type: "centPrecision",
|
|
1117
|
+
});
|
|
1118
|
+
expect(response.body.shippingInfo.taxRate).toMatchObject({
|
|
1119
|
+
name: "NL standard-excluded tax rate",
|
|
1120
|
+
amount: 0.21,
|
|
1121
|
+
includedInPrice: false,
|
|
1122
|
+
country: "NL",
|
|
1123
|
+
});
|
|
1124
|
+
expect(response.body.shippingInfo.taxedPrice).toMatchObject({
|
|
1125
|
+
totalNet: {
|
|
1126
|
+
type: "centPrecision",
|
|
1127
|
+
centAmount: 499,
|
|
1128
|
+
currencyCode: "EUR",
|
|
1129
|
+
fractionDigits: 2,
|
|
1130
|
+
},
|
|
1131
|
+
totalGross: {
|
|
1132
|
+
type: "centPrecision",
|
|
1133
|
+
centAmount: 604,
|
|
1134
|
+
currencyCode: "EUR",
|
|
1135
|
+
fractionDigits: 2,
|
|
1136
|
+
},
|
|
1137
|
+
taxPortions: [
|
|
1138
|
+
{
|
|
1139
|
+
name: "NL standard-excluded tax rate",
|
|
1140
|
+
rate: 0.21,
|
|
1141
|
+
amount: {
|
|
1142
|
+
type: "centPrecision",
|
|
1143
|
+
centAmount: 105,
|
|
1144
|
+
currencyCode: "EUR",
|
|
1145
|
+
fractionDigits: 2,
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
totalTax: {
|
|
1150
|
+
type: "centPrecision",
|
|
1151
|
+
centAmount: 105,
|
|
1152
|
+
currencyCode: "EUR",
|
|
1153
|
+
fractionDigits: 2,
|
|
1154
|
+
},
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
|
|
742
1159
|
test("setLineItemShippingDetails", async () => {
|
|
743
1160
|
const product = await supertest(ctMock.app)
|
|
744
1161
|
.post(`/dummy/products`)
|
|
@@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";
|
|
|
7
7
|
import {
|
|
8
8
|
markMatchingShippingRate,
|
|
9
9
|
markMatchingShippingRatePriceTiers,
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./shipping";
|
|
11
11
|
|
|
12
12
|
// describe('markMatchingShippingMethods', () => {
|
|
13
13
|
// const zones: Record<string, Zone> = {
|
package/src/shipping.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Cart,
|
|
3
|
+
CartValueTier,
|
|
4
|
+
InvalidOperationError,
|
|
5
|
+
ShippingRate,
|
|
6
|
+
ShippingRatePriceTier,
|
|
7
|
+
} from "@commercetools/platform-sdk";
|
|
8
|
+
import { CommercetoolsError } from "./exceptions";
|
|
9
|
+
import { GetParams, RepositoryContext } from "./repositories/abstract";
|
|
10
|
+
import { AbstractStorage } from "./storage/abstract";
|
|
11
|
+
|
|
12
|
+
export const markMatchingShippingRate = (
|
|
13
|
+
cart: Cart,
|
|
14
|
+
shippingRate: ShippingRate,
|
|
15
|
+
): ShippingRate => {
|
|
16
|
+
const isMatching =
|
|
17
|
+
shippingRate.price.currencyCode === cart.totalPrice.currencyCode;
|
|
18
|
+
return {
|
|
19
|
+
...shippingRate,
|
|
20
|
+
tiers: markMatchingShippingRatePriceTiers(cart, shippingRate.tiers),
|
|
21
|
+
isMatching: isMatching,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const markMatchingShippingRatePriceTiers = (
|
|
26
|
+
cart: Cart,
|
|
27
|
+
tiers: ShippingRatePriceTier[],
|
|
28
|
+
): ShippingRatePriceTier[] => {
|
|
29
|
+
if (tiers.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (new Set(tiers.map((tier) => tier.type)).size > 1) {
|
|
34
|
+
throw new Error("Can't handle multiple types of tiers");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const tierType = tiers[0].type;
|
|
38
|
+
switch (tierType) {
|
|
39
|
+
case "CartValue":
|
|
40
|
+
return markMatchingCartValueTiers(cart, tiers as CartValueTier[]);
|
|
41
|
+
// case 'CartClassification':
|
|
42
|
+
// return markMatchingCartClassificationTiers(cart, tiers)
|
|
43
|
+
// case 'CartScore':
|
|
44
|
+
// return markMatchingCartScoreTiers(cart, tiers)
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`Unsupported tier type: ${tierType}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const markMatchingCartValueTiers = (
|
|
51
|
+
cart: Cart,
|
|
52
|
+
tiers: readonly CartValueTier[],
|
|
53
|
+
): ShippingRatePriceTier[] => {
|
|
54
|
+
// Sort tiers from high to low since we only want to match the highest tier
|
|
55
|
+
const sortedTiers = [...tiers].sort(
|
|
56
|
+
(a, b) => b.minimumCentAmount - a.minimumCentAmount,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Find the first tier that matches the cart and set the flag. We push
|
|
60
|
+
// the results into a map so that we can output the tiers in the same order as
|
|
61
|
+
// we received them.
|
|
62
|
+
const result: Record<number, ShippingRatePriceTier> = {};
|
|
63
|
+
let hasMatchingTier = false;
|
|
64
|
+
for (const tier of sortedTiers) {
|
|
65
|
+
const isMatching =
|
|
66
|
+
!hasMatchingTier &&
|
|
67
|
+
cart.totalPrice.currencyCode === tier.price.currencyCode &&
|
|
68
|
+
cart.totalPrice.centAmount >= tier.minimumCentAmount;
|
|
69
|
+
|
|
70
|
+
if (isMatching) hasMatchingTier = true;
|
|
71
|
+
result[tier.minimumCentAmount] = {
|
|
72
|
+
...tier,
|
|
73
|
+
isMatching: isMatching,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return tiers.map((tier) => result[tier.minimumCentAmount]);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Retrieves all the ShippingMethods that can ship to the shipping address of
|
|
82
|
+
* the given Cart. Each ShippingMethod contains exactly one ShippingRate with
|
|
83
|
+
* the flag isMatching set to true. This ShippingRate is used when the
|
|
84
|
+
* ShippingMethod is added to the Cart.
|
|
85
|
+
*/
|
|
86
|
+
export const getShippingMethodsMatchingCart = (
|
|
87
|
+
context: RepositoryContext,
|
|
88
|
+
storage: AbstractStorage,
|
|
89
|
+
cart: Cart,
|
|
90
|
+
params: GetParams = {},
|
|
91
|
+
) => {
|
|
92
|
+
if (!cart.shippingAddress?.country) {
|
|
93
|
+
throw new CommercetoolsError<InvalidOperationError>({
|
|
94
|
+
code: "InvalidOperation",
|
|
95
|
+
message: `The cart with ID '${cart.id}' does not have a shipping address set.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Get all shipping methods that have a zone that matches the shipping address
|
|
100
|
+
const zones = storage.query<"zone">(context.projectKey, "zone", {
|
|
101
|
+
where: [`locations(country="${cart.shippingAddress.country}"))`],
|
|
102
|
+
limit: 100,
|
|
103
|
+
});
|
|
104
|
+
const zoneIds = zones.results.map((zone) => zone.id);
|
|
105
|
+
const shippingMethods = storage.query<"shipping-method">(
|
|
106
|
+
context.projectKey,
|
|
107
|
+
"shipping-method",
|
|
108
|
+
{
|
|
109
|
+
"where": [
|
|
110
|
+
`zoneRates(zone(id in (:zoneIds)))`,
|
|
111
|
+
`zoneRates(shippingRates(price(currencyCode="${cart.totalPrice.currencyCode}")))`,
|
|
112
|
+
],
|
|
113
|
+
"var.zoneIds": zoneIds,
|
|
114
|
+
"expand": params.expand,
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Make sure that each shipping method has exactly one shipping rate and
|
|
119
|
+
// that the shipping rate is marked as matching
|
|
120
|
+
const results = shippingMethods.results
|
|
121
|
+
.map((shippingMethod) => {
|
|
122
|
+
// Iterate through the zoneRates, process the shipping rates and filter
|
|
123
|
+
// out all zoneRates which have no matching shipping rates left
|
|
124
|
+
const rates = shippingMethod.zoneRates
|
|
125
|
+
.map((zoneRate) => ({
|
|
126
|
+
zone: zoneRate.zone,
|
|
127
|
+
|
|
128
|
+
// Iterate through the shippingRates and mark the matching ones
|
|
129
|
+
// then we filter out the non-matching ones
|
|
130
|
+
shippingRates: zoneRate.shippingRates
|
|
131
|
+
.map((rate) => markMatchingShippingRate(cart, rate))
|
|
132
|
+
.filter((rate) => rate.isMatching),
|
|
133
|
+
}))
|
|
134
|
+
.filter((zoneRate) => zoneRate.shippingRates.length > 0);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...shippingMethod,
|
|
138
|
+
zoneRates: rates,
|
|
139
|
+
};
|
|
140
|
+
})
|
|
141
|
+
.filter((shippingMethod) => shippingMethod.zoneRates.length > 0);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...shippingMethods,
|
|
145
|
+
results: results,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Cart,
|
|
3
|
-
CartValueTier,
|
|
4
|
-
ShippingRate,
|
|
5
|
-
ShippingRatePriceTier,
|
|
6
|
-
} from "@commercetools/platform-sdk";
|
|
7
|
-
|
|
8
|
-
export const markMatchingShippingRate = (
|
|
9
|
-
cart: Cart,
|
|
10
|
-
shippingRate: ShippingRate,
|
|
11
|
-
): ShippingRate => {
|
|
12
|
-
const isMatching =
|
|
13
|
-
shippingRate.price.currencyCode === cart.totalPrice.currencyCode;
|
|
14
|
-
return {
|
|
15
|
-
...shippingRate,
|
|
16
|
-
tiers: markMatchingShippingRatePriceTiers(cart, shippingRate.tiers),
|
|
17
|
-
isMatching: isMatching,
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const markMatchingShippingRatePriceTiers = (
|
|
22
|
-
cart: Cart,
|
|
23
|
-
tiers: ShippingRatePriceTier[],
|
|
24
|
-
): ShippingRatePriceTier[] => {
|
|
25
|
-
if (tiers.length === 0) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (new Set(tiers.map((tier) => tier.type)).size > 1) {
|
|
30
|
-
throw new Error("Can't handle multiple types of tiers");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const tierType = tiers[0].type;
|
|
34
|
-
switch (tierType) {
|
|
35
|
-
case "CartValue":
|
|
36
|
-
return markMatchingCartValueTiers(cart, tiers as CartValueTier[]);
|
|
37
|
-
// case 'CartClassification':
|
|
38
|
-
// return markMatchingCartClassificationTiers(cart, tiers)
|
|
39
|
-
// case 'CartScore':
|
|
40
|
-
// return markMatchingCartScoreTiers(cart, tiers)
|
|
41
|
-
default:
|
|
42
|
-
throw new Error(`Unsupported tier type: ${tierType}`);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const markMatchingCartValueTiers = (
|
|
47
|
-
cart: Cart,
|
|
48
|
-
tiers: readonly CartValueTier[],
|
|
49
|
-
): ShippingRatePriceTier[] => {
|
|
50
|
-
// Sort tiers from high to low since we only want to match the highest tier
|
|
51
|
-
const sortedTiers = [...tiers].sort(
|
|
52
|
-
(a, b) => b.minimumCentAmount - a.minimumCentAmount,
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// Find the first tier that matches the cart and set the flag. We push
|
|
56
|
-
// the results into a map so that we can output the tiers in the same order as
|
|
57
|
-
// we received them.
|
|
58
|
-
const result: Record<number, ShippingRatePriceTier> = {};
|
|
59
|
-
let hasMatchingTier = false;
|
|
60
|
-
for (const tier of sortedTiers) {
|
|
61
|
-
const isMatching =
|
|
62
|
-
!hasMatchingTier &&
|
|
63
|
-
cart.totalPrice.currencyCode === tier.price.currencyCode &&
|
|
64
|
-
cart.totalPrice.centAmount >= tier.minimumCentAmount;
|
|
65
|
-
|
|
66
|
-
if (isMatching) hasMatchingTier = true;
|
|
67
|
-
result[tier.minimumCentAmount] = {
|
|
68
|
-
...tier,
|
|
69
|
-
isMatching: isMatching,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return tiers.map((tier) => result[tier.minimumCentAmount]);
|
|
74
|
-
};
|