washday-sdk 1.6.67 → 1.6.70

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.
@@ -75,13 +75,27 @@ export const getAutomaticDiscountById = function (storeId, discountId) {
75
75
  }
76
76
  });
77
77
  };
78
- export const verifyDiscountCode = function (storeId, code) {
78
+ const buildVerifyQueryString = (params) => {
79
+ if (!params)
80
+ return '';
81
+ const entries = [];
82
+ if (params.currentDate)
83
+ entries.push(`currentDate=${encodeURIComponent(params.currentDate)}`);
84
+ if (params.orderSubtotal !== undefined &&
85
+ params.orderSubtotal !== null &&
86
+ Number.isFinite(Number(params.orderSubtotal))) {
87
+ entries.push(`orderSubtotal=${encodeURIComponent(String(params.orderSubtotal))}`);
88
+ }
89
+ return entries.length ? `?${entries.join('&')}` : '';
90
+ };
91
+ export const verifyDiscountCode = function (storeId, code, params) {
79
92
  return __awaiter(this, void 0, void 0, function* () {
80
93
  try {
81
94
  const config = {
82
95
  headers: { Authorization: `Bearer ${this.apiToken}` }
83
96
  };
84
- return yield this.axiosInstance.get(`${GET_SET_DISCOUNT_CODES}/${storeId}/${code}/verify`, config);
97
+ const qs = buildVerifyQueryString(params);
98
+ return yield this.axiosInstance.get(`${GET_SET_DISCOUNT_CODES}/${storeId}/${code}/verify${qs}`, config);
85
99
  }
86
100
  catch (error) {
87
101
  console.error('Error fetching verifyDiscountCode:', error);
@@ -89,13 +103,14 @@ export const verifyDiscountCode = function (storeId, code) {
89
103
  }
90
104
  });
91
105
  };
92
- export const verifyDiscountCodeCustomersApp = function (storeId, code) {
106
+ export const verifyDiscountCodeCustomersApp = function (storeId, code, params) {
93
107
  return __awaiter(this, void 0, void 0, function* () {
94
108
  try {
95
109
  const config = {
96
110
  headers: { Authorization: `Bearer ${this.apiToken}` }
97
111
  };
98
- return yield this.axiosInstance.get(`${GET_DISCOUNT_CODES_CUSTOMER_APP}/${storeId}/${code}/verify`, config);
112
+ const qs = buildVerifyQueryString(params);
113
+ return yield this.axiosInstance.get(`${GET_DISCOUNT_CODES_CUSTOMER_APP}/${storeId}/${code}/verify${qs}`, config);
99
114
  }
100
115
  catch (error) {
101
116
  console.error('Error fetching verifyDiscountCodeCustomersApp:', error);
@@ -136,6 +136,7 @@ export const payAndCollect = function (params) {
136
136
  headers: { Authorization: `Bearer ${this.apiToken}` }
137
137
  };
138
138
  return yield this.axiosInstance.post(PAY_AND_COLLECT(params.sequence, params.storeId), {
139
+ orderId: params.orderId,
139
140
  paymentMethod: params.paymentMethod,
140
141
  cashierBoxId: params.cashierBoxId,
141
142
  amount: params.amount,
@@ -38,7 +38,7 @@ const hasMatchingDiscountTarget = (targets = [], product) => {
38
38
  };
39
39
  export const calculateOrderTotal = (order, selectedCustomer, storeSettings, hasShippingCost, storeDiscounts = [], discountCodeObj, redeemPointsDiscount = 0 // 💡 NUEVO parámetro
40
40
  ) => {
41
- var _a;
41
+ var _a, _b, _c;
42
42
  try {
43
43
  const appliedOrderDiscounts = {};
44
44
  const productTableCalculator = (storeSettings === null || storeSettings === void 0 ? void 0 : storeSettings.taxesType) === 'over_price'
@@ -54,7 +54,11 @@ export const calculateOrderTotal = (order, selectedCustomer, storeSettings, hasS
54
54
  discountCodeObj.applyOnceOnOrder) {
55
55
  const includesProducts = discountCodeObj.applyToAllProducts ||
56
56
  order.products.some((curr) => hasMatchingDiscountTarget(discountCodeObj.products, curr));
57
- discountCodeAmount = includesProducts ? discountCodeObj.value : 0;
57
+ const minimumOrderSubtotal = Number((_a = discountCodeObj.minimumOrderSubtotal) !== null && _a !== void 0 ? _a : 0);
58
+ const meetsMinimum = !Number.isFinite(minimumOrderSubtotal) ||
59
+ minimumOrderSubtotal <= 0 ||
60
+ Number((_b = totalImportWithoutDiscount !== null && totalImportWithoutDiscount !== void 0 ? totalImportWithoutDiscount : totalSubtotalAmount) !== null && _b !== void 0 ? _b : 0) >= minimumOrderSubtotal;
61
+ discountCodeAmount = includesProducts && meetsMinimum ? discountCodeObj.value : 0;
58
62
  }
59
63
  // === SHIPPING COST ===
60
64
  const shippingCost = getShippingCost(discountCodeObj, hasShippingCost, storeSettings);
@@ -68,7 +72,7 @@ export const calculateOrderTotal = (order, selectedCustomer, storeSettings, hasS
68
72
  creditApplied -
69
73
  redeemPointsDiscount).toFixed(2);
70
74
  // === RETURN FINAL OBJECT ===
71
- return Object.assign(Object.assign({}, order), { totalQuantity, customerDiscount: order.discountCode ? 0 : (_a = selectedCustomer === null || selectedCustomer === void 0 ? void 0 : selectedCustomer.customer) === null || _a === void 0 ? void 0 : _a.discount, productTotal: totalImportWithDiscount, taxesTotal: totalImporTaxes, taxBreakdown, total: orderTotal, creditApplied, redeemPointsApplied: redeemPointsDiscount, productTotalWithoutDiscount: totalImportWithoutDiscount, totalDiscountAmount: totalDiscountAmount + discountCodeAmount, shippingServiceTotal: shippingCost, appliedOrderDiscounts, subtotal: totalSubtotalAmount });
75
+ return Object.assign(Object.assign({}, order), { totalQuantity, customerDiscount: order.discountCode ? 0 : (_c = selectedCustomer === null || selectedCustomer === void 0 ? void 0 : selectedCustomer.customer) === null || _c === void 0 ? void 0 : _c.discount, productTotal: totalImportWithDiscount, taxesTotal: totalImporTaxes, taxBreakdown, total: orderTotal, creditApplied, redeemPointsApplied: redeemPointsDiscount, productTotalWithoutDiscount: totalImportWithoutDiscount, totalDiscountAmount: totalDiscountAmount + discountCodeAmount, shippingServiceTotal: shippingCost, appliedOrderDiscounts, subtotal: totalSubtotalAmount });
72
76
  }
73
77
  catch (error) {
74
78
  throw error;
@@ -225,7 +225,28 @@ export const getCreditApplied = (selectedCustomer, orderTotal) => {
225
225
  }
226
226
  return Math.min(customerCredit, orderTotal);
227
227
  };
228
+ const productsMeetMinimumSubtotal = (productsArr, isExpress, minimumOrderSubtotal) => {
229
+ if (!Number.isFinite(minimumOrderSubtotal) || minimumOrderSubtotal <= 0)
230
+ return true;
231
+ const subtotal = (productsArr || []).reduce((sum, prod) => {
232
+ var _a, _b, _c, _d, _e;
233
+ if (!prod || prod.isBuyAndGetProduct || prod.isFreeItem)
234
+ return sum;
235
+ const price = Number((_b = (_a = (isExpress ? prod.expressPrice : prod.price)) !== null && _a !== void 0 ? _a : prod.price) !== null && _b !== void 0 ? _b : 0);
236
+ const qty = Number((_d = (_c = prod.quantity) !== null && _c !== void 0 ? _c : prod.qty) !== null && _d !== void 0 ? _d : 0);
237
+ const extra = Number((_e = prod.extraAmount) !== null && _e !== void 0 ? _e : 0);
238
+ if (!Number.isFinite(price) || !Number.isFinite(qty))
239
+ return sum;
240
+ return sum + price * qty + (Number.isFinite(extra) ? extra : 0);
241
+ }, 0);
242
+ return subtotal >= minimumOrderSubtotal;
243
+ };
228
244
  export const applyDiscountToProducts = (discountCode, productsArr, isExpress, storeDiscounts = [], selectedCustomer = { discount: 0 }) => {
245
+ var _a;
246
+ if (discountCode &&
247
+ !productsMeetMinimumSubtotal(productsArr, isExpress, Number((_a = discountCode.minimumOrderSubtotal) !== null && _a !== void 0 ? _a : 0))) {
248
+ discountCode = null;
249
+ }
229
250
  if (!discountCode) {
230
251
  // Caso sin discountCode: se aplican descuentos automáticos solo sobre el precio del producto
231
252
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "washday-sdk",
3
- "version": "1.6.67",
3
+ "version": "1.6.70",
4
4
  "description": "Washday utilities functions and API",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -63,24 +63,50 @@ export const getAutomaticDiscountById = async function (this: WashdayClientInsta
63
63
  }
64
64
  };
65
65
 
66
- export const verifyDiscountCode = async function (this: WashdayClientInstance, storeId: string, code: string,): Promise<any> {
66
+ const buildVerifyQueryString = (params?: { orderSubtotal?: number; currentDate?: string }): string => {
67
+ if (!params) return '';
68
+ const entries: string[] = [];
69
+ if (params.currentDate) entries.push(`currentDate=${encodeURIComponent(params.currentDate)}`);
70
+ if (
71
+ params.orderSubtotal !== undefined &&
72
+ params.orderSubtotal !== null &&
73
+ Number.isFinite(Number(params.orderSubtotal))
74
+ ) {
75
+ entries.push(`orderSubtotal=${encodeURIComponent(String(params.orderSubtotal))}`);
76
+ }
77
+ return entries.length ? `?${entries.join('&')}` : '';
78
+ };
79
+
80
+ export const verifyDiscountCode = async function (
81
+ this: WashdayClientInstance,
82
+ storeId: string,
83
+ code: string,
84
+ params?: { orderSubtotal?: number; currentDate?: string },
85
+ ): Promise<any> {
67
86
  try {
68
87
  const config = {
69
88
  headers: { Authorization: `Bearer ${this.apiToken}` }
70
89
  };
71
- return await this.axiosInstance.get(`${GET_SET_DISCOUNT_CODES}/${storeId}/${code}/verify`, config);
90
+ const qs = buildVerifyQueryString(params);
91
+ return await this.axiosInstance.get(`${GET_SET_DISCOUNT_CODES}/${storeId}/${code}/verify${qs}`, config);
72
92
  } catch (error) {
73
93
  console.error('Error fetching verifyDiscountCode:', error);
74
94
  throw error;
75
95
  }
76
96
  };
77
97
 
78
- export const verifyDiscountCodeCustomersApp = async function (this: WashdayClientInstance, storeId: string, code: string,): Promise<any> {
98
+ export const verifyDiscountCodeCustomersApp = async function (
99
+ this: WashdayClientInstance,
100
+ storeId: string,
101
+ code: string,
102
+ params?: { orderSubtotal?: number; currentDate?: string },
103
+ ): Promise<any> {
79
104
  try {
80
105
  const config = {
81
106
  headers: { Authorization: `Bearer ${this.apiToken}` }
82
107
  };
83
- return await this.axiosInstance.get(`${GET_DISCOUNT_CODES_CUSTOMER_APP}/${storeId}/${code}/verify`, config);
108
+ const qs = buildVerifyQueryString(params);
109
+ return await this.axiosInstance.get(`${GET_DISCOUNT_CODES_CUSTOMER_APP}/${storeId}/${code}/verify${qs}`, config);
84
110
  } catch (error) {
85
111
  console.error('Error fetching verifyDiscountCodeCustomersApp:', error);
86
112
  throw error;
@@ -52,6 +52,9 @@ export const updateDiscountCodeById = async function (this: WashdayClientInstanc
52
52
  code?: string;
53
53
  fromDate?: string;
54
54
  toDate?: string;
55
+ applyOnceOnOrder?: boolean;
56
+ applyToAllProducts?: boolean;
57
+ minimumOrderSubtotal?: number;
55
58
  }): Promise<any> {
56
59
  try {
57
60
  const config = {
@@ -138,6 +138,7 @@ export const setOrderUncollected = async function (this: WashdayClientInstance,
138
138
  };
139
139
 
140
140
  export const payAndCollect = async function (this: WashdayClientInstance, params: {
141
+ orderId?: string;
141
142
  sequence: string;
142
143
  storeId: string;
143
144
  paymentMethod: string;
@@ -156,6 +157,7 @@ export const payAndCollect = async function (this: WashdayClientInstance, params
156
157
  return await this.axiosInstance.post(
157
158
  PAY_AND_COLLECT(params.sequence, params.storeId),
158
159
  {
160
+ orderId: params.orderId,
159
161
  paymentMethod: params.paymentMethod,
160
162
  cashierBoxId: params.cashierBoxId,
161
163
  amount: params.amount,
@@ -1,12 +1,18 @@
1
1
  import { AxiosResponse } from "axios";
2
2
  import { WashdayClientInstance } from "../../interfaces/Api";
3
3
  import { IUpdateOrderByIdDto } from "../../interfaces/Order";
4
+ import { PaymentMethodsEnum } from "../../enum";
4
5
  import axiosInstance from "../axiosInstance";
5
6
  import { generateQueryParamsStr } from "../../utils/apiUtils";
6
7
  const GET_SET_ORDER = 'api/v2/order';
7
8
  const GET_SET_ORDER_OLD = 'api/order';
8
9
  const GET_SET_ORDER_PAYMENTLINES = (orderId: string) => `/api/v2/order/${orderId}/paymentLines`;
9
10
 
11
+ type EditablePaymentMethod =
12
+ | PaymentMethodsEnum.Cash
13
+ | PaymentMethodsEnum.Card
14
+ | PaymentMethodsEnum.Transfer;
15
+
10
16
  export const updateById = async function (this: WashdayClientInstance, id: string, data: IUpdateOrderByIdDto): Promise<any> {
11
17
  try {
12
18
  const config = {
@@ -21,6 +27,7 @@ export const updateById = async function (this: WashdayClientInstance, id: strin
21
27
 
22
28
  export const updatePaymentLineById = async function (this: WashdayClientInstance, orderId: string, id: string, data: {
23
29
  amountPaid: number
30
+ paymentMethod?: EditablePaymentMethod
24
31
  updatedDate: Date
25
32
  pinUserId?: string
26
33
  }): Promise<any> {
@@ -36,6 +43,7 @@ export const updatePaymentLineById = async function (this: WashdayClientInstance
36
43
  };
37
44
 
38
45
  export const setOrderCancelledBySequence = async function (this: WashdayClientInstance, sequence: string, storeId: string, data: {
46
+ orderId?: string,
39
47
  cancelledDateTime: string
40
48
  }): Promise<any> {
41
49
  try {
@@ -82,6 +90,7 @@ export const setOrderCleanedBySequence = async function (this: WashdayClientInst
82
90
  };
83
91
 
84
92
  export const setOrderCollectedBySequence = async function (this: WashdayClientInstance, sequence: string, storeId: string, data: {
93
+ orderId?: string,
85
94
  collectedDateTime: string;
86
95
  collectWithAmountDue?: boolean,
87
96
  pinUserId?: string
@@ -422,6 +422,7 @@ export interface IDiscountCode {
422
422
  createdBy: IUser | string,
423
423
  isActive: boolean,
424
424
  applyOnceOnOrder: boolean,
425
+ minimumOrderSubtotal?: number,
425
426
  applyToAllProducts: boolean,
426
427
  hasUseLimit: boolean,
427
428
  useLimitQty: number,
@@ -92,7 +92,13 @@ export const calculateOrderTotal = (
92
92
  hasMatchingDiscountTarget(discountCodeObj.products, curr)
93
93
  );
94
94
 
95
- discountCodeAmount = includesProducts ? discountCodeObj.value : 0;
95
+ const minimumOrderSubtotal = Number(discountCodeObj.minimumOrderSubtotal ?? 0);
96
+ const meetsMinimum =
97
+ !Number.isFinite(minimumOrderSubtotal) ||
98
+ minimumOrderSubtotal <= 0 ||
99
+ Number(totalImportWithoutDiscount ?? totalSubtotalAmount ?? 0) >= minimumOrderSubtotal;
100
+
101
+ discountCodeAmount = includesProducts && meetsMinimum ? discountCodeObj.value : 0;
96
102
  }
97
103
 
98
104
  // === SHIPPING COST ===
@@ -307,6 +307,23 @@ export const getCreditApplied = (selectedCustomer: { customer: ICustomer } | und
307
307
  return Math.min(customerCredit, orderTotal);
308
308
  };
309
309
 
310
+ const productsMeetMinimumSubtotal = (
311
+ productsArr: any[] | undefined,
312
+ isExpress: boolean,
313
+ minimumOrderSubtotal: number,
314
+ ): boolean => {
315
+ if (!Number.isFinite(minimumOrderSubtotal) || minimumOrderSubtotal <= 0) return true;
316
+ const subtotal = (productsArr || []).reduce((sum: number, prod: any) => {
317
+ if (!prod || prod.isBuyAndGetProduct || prod.isFreeItem) return sum;
318
+ const price = Number((isExpress ? prod.expressPrice : prod.price) ?? prod.price ?? 0);
319
+ const qty = Number(prod.quantity ?? prod.qty ?? 0);
320
+ const extra = Number(prod.extraAmount ?? 0);
321
+ if (!Number.isFinite(price) || !Number.isFinite(qty)) return sum;
322
+ return sum + price * qty + (Number.isFinite(extra) ? extra : 0);
323
+ }, 0);
324
+ return subtotal >= minimumOrderSubtotal;
325
+ };
326
+
310
327
  export const applyDiscountToProducts = (
311
328
  discountCode: {
312
329
  freeProductSetting: any;
@@ -315,6 +332,7 @@ export const applyDiscountToProducts = (
315
332
  type: string;
316
333
  applyToAllProducts: boolean;
317
334
  applyOnceOnOrder: boolean;
335
+ minimumOrderSubtotal?: number;
318
336
  buyAndGetConditions: any,
319
337
  },
320
338
  productsArr: any,
@@ -322,6 +340,12 @@ export const applyDiscountToProducts = (
322
340
  storeDiscounts: any = [],
323
341
  selectedCustomer = { discount: 0 }
324
342
  ) => {
343
+ if (
344
+ discountCode &&
345
+ !productsMeetMinimumSubtotal(productsArr, isExpress, Number(discountCode.minimumOrderSubtotal ?? 0))
346
+ ) {
347
+ discountCode = null as any;
348
+ }
325
349
  if (!discountCode) {
326
350
  // Caso sin discountCode: se aplican descuentos automáticos solo sobre el precio del producto
327
351
  return {
@@ -0,0 +1,291 @@
1
+ import { utils } from "../src";
2
+
3
+ const baseSelectedCustomer = {
4
+ customer: {
5
+ credit: 0,
6
+ discount: 0,
7
+ },
8
+ };
9
+
10
+ const baseStore = {
11
+ taxesType: "over_price" as const,
12
+ taxOne: { name: "IVA", value: 16 },
13
+ taxTwo: null,
14
+ taxThree: null,
15
+ orderPageConfig: { shippingServiceCost: 0 },
16
+ };
17
+
18
+ const buildOrder = (overrides: any = {}) => ({
19
+ express: false,
20
+ products: [
21
+ { _id: "p1", qty: 1, quantity: 1, price: 100, expressPrice: 100, extraAmount: 0 },
22
+ { _id: "p2", qty: 1, quantity: 1, price: 150, expressPrice: 150, extraAmount: 0 },
23
+ ],
24
+ buyAndGetProducts: [],
25
+ discountCode: null,
26
+ taxesType: "over_price",
27
+ ...overrides,
28
+ });
29
+
30
+ describe("calculateOrderTotal — fixed-amount coupons", () => {
31
+ it("applyOnceOnOrder=true subtracts the coupon value once, regardless of quantity", () => {
32
+ const order = buildOrder({
33
+ products: [
34
+ { _id: "p1", qty: 5, quantity: 5, price: 100, expressPrice: 100, extraAmount: 0 },
35
+ ],
36
+ });
37
+ const discountCode = {
38
+ type: "number",
39
+ value: 20,
40
+ applyToAllProducts: true,
41
+ products: [],
42
+ applyOnceOnOrder: true,
43
+ };
44
+
45
+ const result = utils.calculateOrderTotal(
46
+ order,
47
+ baseSelectedCustomer as any,
48
+ baseStore as any,
49
+ false,
50
+ [],
51
+ discountCode,
52
+ 0
53
+ );
54
+
55
+ // baseline (no coupon): 5*100=500 subtotal, +16% taxes over_price = 580.
56
+ // once-on-order applies a flat $20 reduction to the order total only.
57
+ expect(result.total).toBe(560);
58
+ // discount is order-level, not per-line, so totalDiscountAmount keeps the once-on-order $20.
59
+ expect(result.totalDiscountAmount).toBe(20);
60
+ });
61
+
62
+ it("minimumOrderSubtotal blocks the once-on-order coupon when subtotal is below the minimum", () => {
63
+ const order = buildOrder();
64
+ const discountCode = {
65
+ type: "number",
66
+ value: 50,
67
+ applyToAllProducts: true,
68
+ products: [],
69
+ applyOnceOnOrder: true,
70
+ minimumOrderSubtotal: 300,
71
+ };
72
+
73
+ const result = utils.calculateOrderTotal(
74
+ order,
75
+ baseSelectedCustomer as any,
76
+ baseStore as any,
77
+ false,
78
+ [],
79
+ discountCode,
80
+ 0
81
+ );
82
+
83
+ // 250 < 300 → no discount applied, totals match the no-coupon baseline (250 + 40 taxes).
84
+ expect(result.totalDiscountAmount).toBe(0);
85
+ expect(result.total).toBe(290);
86
+ });
87
+
88
+ it("minimumOrderSubtotal allows the once-on-order coupon at exactly the minimum", () => {
89
+ const order = buildOrder();
90
+ const discountCode = {
91
+ type: "number",
92
+ value: 50,
93
+ applyToAllProducts: true,
94
+ products: [],
95
+ applyOnceOnOrder: true,
96
+ minimumOrderSubtotal: 250,
97
+ };
98
+
99
+ const result = utils.calculateOrderTotal(
100
+ order,
101
+ baseSelectedCustomer as any,
102
+ baseStore as any,
103
+ false,
104
+ [],
105
+ discountCode,
106
+ 0
107
+ );
108
+
109
+ // 250 == 250 → discount applied. Baseline 290, minus $50 = 240.
110
+ expect(result.total).toBe(240);
111
+ });
112
+
113
+ it("zero/undefined minimumOrderSubtotal keeps current behavior (backwards compatible)", () => {
114
+ const order = buildOrder();
115
+ const discountCode = {
116
+ type: "number",
117
+ value: 50,
118
+ applyToAllProducts: true,
119
+ products: [],
120
+ applyOnceOnOrder: true,
121
+ minimumOrderSubtotal: 0,
122
+ };
123
+
124
+ const result = utils.calculateOrderTotal(
125
+ order,
126
+ baseSelectedCustomer as any,
127
+ baseStore as any,
128
+ false,
129
+ [],
130
+ discountCode,
131
+ 0
132
+ );
133
+
134
+ expect(result.total).toBe(240);
135
+ });
136
+ });
137
+
138
+ describe("applyDiscountToProducts — minimumOrderSubtotal gate", () => {
139
+ const sharedProducts = [
140
+ { _id: "p1", price: 100, expressPrice: 100, qty: 1, quantity: 1 },
141
+ { _id: "p2", price: 150, expressPrice: 150, qty: 1, quantity: 1 },
142
+ ];
143
+
144
+ it("applies per-product fixed discount when no minimum is configured", () => {
145
+ const { newOrderProds } = utils.applyDiscountToProducts(
146
+ {
147
+ type: "number",
148
+ value: 10,
149
+ applyToAllProducts: true,
150
+ products: [],
151
+ applyOnceOnOrder: false,
152
+ buyAndGetConditions: null,
153
+ freeProductSetting: null,
154
+ } as any,
155
+ sharedProducts,
156
+ false,
157
+ [],
158
+ { discount: 0 }
159
+ );
160
+ expect(newOrderProds.every((p: any) => p.discountAmount === 10)).toBe(true);
161
+ });
162
+
163
+ it("skips the discount when subtotal is below minimumOrderSubtotal", () => {
164
+ const { newOrderProds } = utils.applyDiscountToProducts(
165
+ {
166
+ type: "number",
167
+ value: 10,
168
+ applyToAllProducts: true,
169
+ products: [],
170
+ applyOnceOnOrder: false,
171
+ minimumOrderSubtotal: 999,
172
+ buyAndGetConditions: null,
173
+ freeProductSetting: null,
174
+ } as any,
175
+ sharedProducts,
176
+ false,
177
+ [],
178
+ { discount: 0 }
179
+ );
180
+ expect(newOrderProds.every((p: any) => (p.discountAmount ?? 0) === 0)).toBe(true);
181
+ });
182
+
183
+ it("applies the discount when subtotal meets the minimum", () => {
184
+ const { newOrderProds } = utils.applyDiscountToProducts(
185
+ {
186
+ type: "number",
187
+ value: 10,
188
+ applyToAllProducts: true,
189
+ products: [],
190
+ applyOnceOnOrder: false,
191
+ minimumOrderSubtotal: 200,
192
+ buyAndGetConditions: null,
193
+ freeProductSetting: null,
194
+ } as any,
195
+ sharedProducts,
196
+ false,
197
+ [],
198
+ { discount: 0 }
199
+ );
200
+ expect(newOrderProds.every((p: any) => p.discountAmount === 10)).toBe(true);
201
+ });
202
+
203
+ it("uses expressPrice when isExpress=true for the minimum check", () => {
204
+ const expressProducts = [
205
+ { _id: "p1", price: 100, expressPrice: 160, qty: 1, quantity: 1 },
206
+ ];
207
+
208
+ // Non-express: subtotal=100, below minimum → discount skipped
209
+ const nonExpress = utils.applyDiscountToProducts(
210
+ {
211
+ type: "number",
212
+ value: 10,
213
+ applyToAllProducts: true,
214
+ products: [],
215
+ applyOnceOnOrder: false,
216
+ minimumOrderSubtotal: 150,
217
+ buyAndGetConditions: null,
218
+ freeProductSetting: null,
219
+ } as any,
220
+ expressProducts,
221
+ false,
222
+ [],
223
+ { discount: 0 }
224
+ );
225
+ expect((nonExpress.newOrderProds[0] as any).discountAmount ?? 0).toBe(0);
226
+
227
+ // Express: subtotal=160, meets minimum → discount applied
228
+ const express = utils.applyDiscountToProducts(
229
+ {
230
+ type: "number",
231
+ value: 10,
232
+ applyToAllProducts: true,
233
+ products: [],
234
+ applyOnceOnOrder: false,
235
+ minimumOrderSubtotal: 150,
236
+ buyAndGetConditions: null,
237
+ freeProductSetting: null,
238
+ } as any,
239
+ expressProducts,
240
+ true,
241
+ [],
242
+ { discount: 0 }
243
+ );
244
+ expect((express.newOrderProds[0] as any).discountAmount).toBe(10);
245
+ });
246
+
247
+ it("includes extraAmount when checking the minimum subtotal", () => {
248
+ const productsWithExtra = [
249
+ { _id: "p1", price: 100, expressPrice: 100, qty: 1, quantity: 1, extraAmount: 25 },
250
+ ];
251
+
252
+ // Base price * qty = 100, + extraAmount 25 = 125 → minimum 120 should clear
253
+ const meetsWithExtra = utils.applyDiscountToProducts(
254
+ {
255
+ type: "number",
256
+ value: 5,
257
+ applyToAllProducts: true,
258
+ products: [],
259
+ applyOnceOnOrder: false,
260
+ minimumOrderSubtotal: 120,
261
+ buyAndGetConditions: null,
262
+ freeProductSetting: null,
263
+ } as any,
264
+ productsWithExtra,
265
+ false,
266
+ [],
267
+ { discount: 0 }
268
+ );
269
+ expect((meetsWithExtra.newOrderProds[0] as any).discountAmount).toBe(5);
270
+
271
+ // Without extra (extraAmount removed), 100 < 120 → skipped
272
+ const productsNoExtra = [{ _id: "p1", price: 100, expressPrice: 100, qty: 1, quantity: 1 }];
273
+ const failsWithoutExtra = utils.applyDiscountToProducts(
274
+ {
275
+ type: "number",
276
+ value: 5,
277
+ applyToAllProducts: true,
278
+ products: [],
279
+ applyOnceOnOrder: false,
280
+ minimumOrderSubtotal: 120,
281
+ buyAndGetConditions: null,
282
+ freeProductSetting: null,
283
+ } as any,
284
+ productsNoExtra,
285
+ false,
286
+ [],
287
+ { discount: 0 }
288
+ );
289
+ expect((failsWithoutExtra.newOrderProds[0] as any).discountAmount ?? 0).toBe(0);
290
+ });
291
+ });
@@ -0,0 +1,49 @@
1
+ import { payAndCollect } from "../src/api/order/post";
2
+ import { setOrderCancelledBySequence } from "../src/api/order/put";
3
+
4
+ describe("orders orderId body fallback", () => {
5
+ it("sends orderId in payAndCollect body while keeping sequence/store route", async () => {
6
+ const post = jest.fn().mockResolvedValue({ data: { ok: true } });
7
+ const client = {
8
+ apiToken: "token-123",
9
+ axiosInstance: { post },
10
+ } as any;
11
+
12
+ await payAndCollect.call(client, {
13
+ orderId: "order-1",
14
+ sequence: "4",
15
+ storeId: "store-1",
16
+ paymentMethod: "card",
17
+ amount: 25,
18
+ });
19
+
20
+ expect(post).toHaveBeenCalledWith(
21
+ "/api/v2/order/4/store-1/pay-and-collect",
22
+ expect.objectContaining({ orderId: "order-1" }),
23
+ {
24
+ headers: { Authorization: "Bearer token-123" },
25
+ }
26
+ );
27
+ });
28
+
29
+ it("sends orderId in setOrderCancelledBySequence body", async () => {
30
+ const put = jest.fn().mockResolvedValue({ data: { data: true } });
31
+ const client = {
32
+ apiToken: "token-123",
33
+ axiosInstance: { put },
34
+ } as any;
35
+
36
+ await setOrderCancelledBySequence.call(client, "4", "store-1", {
37
+ orderId: "order-1",
38
+ cancelledDateTime: "2026-06-05T00:00:00.000Z",
39
+ });
40
+
41
+ expect(put).toHaveBeenCalledWith(
42
+ "api/order/4/store-1/cancelled",
43
+ expect.objectContaining({ orderId: "order-1" }),
44
+ {
45
+ headers: { Authorization: "Bearer token-123" },
46
+ }
47
+ );
48
+ });
49
+ });
@@ -0,0 +1,27 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const repoRoot = path.resolve(__dirname, "../../..");
5
+ const workflowPath = path.join(repoRoot, ".github/workflows/sdk-publish.yml");
6
+
7
+ describe("sdk publish workflow", () => {
8
+ it("is manually runnable and bumps patch before publishing", () => {
9
+ expect(fs.existsSync(workflowPath)).toBe(true);
10
+
11
+ const workflow = fs.readFileSync(workflowPath, "utf8");
12
+
13
+ expect(workflow).toContain("workflow_dispatch:");
14
+ expect(workflow).not.toContain("push:");
15
+ expect(workflow).toContain("contents: write");
16
+ expect(workflow).toContain("id-token: write");
17
+ expect(workflow).toContain("working-directory: packages/sdk");
18
+ expect(workflow).toContain("registry-url: https://registry.npmjs.org");
19
+ expect(workflow).toContain('npm view "$PACKAGE_NAME@$CURRENT_VERSION" version');
20
+ expect(workflow).toContain("npm version patch --no-git-tag-version");
21
+ expect(workflow).toContain("git commit -m \"chore(sdk): bump patch version for publish\"");
22
+ expect(workflow).toContain("git push origin HEAD:main");
23
+ expect(workflow).toContain("npm publish --access public --no-provenance");
24
+ expect(workflow).not.toContain("NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}");
25
+ expect(workflow).not.toContain('NPM_CONFIG_PROVENANCE: "false"');
26
+ });
27
+ });
@@ -1,58 +0,0 @@
1
- name: Bump Version on PR
2
-
3
- on:
4
- pull_request:
5
- types: [opened, synchronize, reopened]
6
- branches:
7
- - main
8
-
9
- permissions:
10
- contents: write
11
-
12
- jobs:
13
- bump-version:
14
- runs-on: ubuntu-latest
15
- if: github.event.pull_request.base.ref == 'main'
16
- steps:
17
- - name: Checkout repository
18
- uses: actions/checkout@v4
19
- with:
20
- ref: ${{ github.head_ref }}
21
- token: ${{ secrets.GITHUB_TOKEN }}
22
-
23
- - name: Use Node.js 24
24
- uses: actions/setup-node@v4
25
- with:
26
- node-version: 24
27
-
28
- - name: Configure git identity
29
- run: |
30
- git config --global user.name "washday-bot"
31
- git config --global user.email "ci@washday.dev"
32
-
33
- - name: Fetch main branch
34
- run: git fetch origin main:main
35
-
36
- - name: Check if version already bumped
37
- id: check_version
38
- run: |
39
- CURRENT_VERSION=$(node -p "require('./package.json').version")
40
- git show main:package.json > /tmp/main-package.json
41
- MAIN_VERSION=$(node -p "require('/tmp/main-package.json').version")
42
- echo "Current PR version: $CURRENT_VERSION"
43
- echo "Main branch version: $MAIN_VERSION"
44
- if [ "$CURRENT_VERSION" != "$MAIN_VERSION" ]; then
45
- echo "already_bumped=true" >> $GITHUB_OUTPUT
46
- echo "✅ Version already bumped in this PR"
47
- else
48
- echo "already_bumped=false" >> $GITHUB_OUTPUT
49
- echo "⚠️ Version needs bumping"
50
- fi
51
-
52
- - name: Bump version patch
53
- if: steps.check_version.outputs.already_bumped == 'false'
54
- run: |
55
- npm version patch --no-git-tag-version
56
- git add package.json package-lock.json
57
- git commit -m "chore: bump version [skip ci]"
58
- git push origin ${{ github.head_ref }}
@@ -1,35 +0,0 @@
1
- name: Publish SDK on Merge
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- permissions:
9
- contents: read
10
- id-token: write # Required for OIDC
11
-
12
- jobs:
13
- publish:
14
- runs-on: ubuntu-latest
15
- environment: publish
16
- steps:
17
- - name: Checkout
18
- uses: actions/checkout@v4
19
-
20
- - name: Use Node.js 24
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: 24
24
-
25
- - name: Install dependencies
26
- run: npm ci
27
-
28
- - name: Clear NODE_AUTH_TOKEN for OIDC
29
- run: |
30
- unset NODE_AUTH_TOKEN || true
31
-
32
- - name: Build and publish
33
- run: npm run build && npm publish --access public --no-provenance --verbose
34
- env:
35
- NODE_AUTH_TOKEN: ""
File without changes