@darkpos/pricing 1.0.151 → 1.0.153

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.
@@ -0,0 +1,387 @@
1
+ const usePricing = require('../../index');
2
+ const mockStores = require('../mocks/stores');
3
+
4
+ const session = {
5
+ store: mockStores[0],
6
+ };
7
+
8
+ const pricingService = usePricing(session);
9
+
10
+ const createOptions = items => ({
11
+ 'order-1': {
12
+ items,
13
+ },
14
+ });
15
+
16
+ describe('Apply refund', () => {
17
+ test('keeps applyPayment working for positive amounts', () => {
18
+ const options = createOptions({
19
+ 'item-1': {
20
+ total: 8,
21
+ totalPaid: 0,
22
+ status: {},
23
+ },
24
+ 'item-2': {
25
+ total: 7,
26
+ totalPaid: 0,
27
+ status: {},
28
+ },
29
+ });
30
+
31
+ const result = pricingService.order.applyPayment(options, 10);
32
+
33
+ expect(result['order-1'].items['item-1']).toMatchObject({
34
+ orderId: 'order-1',
35
+ amount: 8,
36
+ totalPaid: 8,
37
+ status: {
38
+ paid: {
39
+ value: true,
40
+ },
41
+ },
42
+ });
43
+
44
+ expect(result['order-1'].items['item-2']).toMatchObject({
45
+ orderId: 'order-1',
46
+ amount: 2,
47
+ totalPaid: 2,
48
+ status: {
49
+ paid: {
50
+ value: false,
51
+ },
52
+ },
53
+ });
54
+ });
55
+
56
+ test('applies refunds when amount to process is negative', () => {
57
+ const options = createOptions({
58
+ 'item-1': {
59
+ total: 8,
60
+ totalPaid: 8,
61
+ status: {
62
+ paid: {
63
+ value: true,
64
+ date: new Date('2024-01-01T00:00:00.000Z'),
65
+ },
66
+ },
67
+ },
68
+ 'item-2': {
69
+ total: 7,
70
+ totalPaid: 7,
71
+ status: {
72
+ paid: {
73
+ value: true,
74
+ date: new Date('2024-01-01T00:00:00.000Z'),
75
+ },
76
+ },
77
+ },
78
+ });
79
+
80
+ const result = pricingService.order.applyRefund(options, -10);
81
+
82
+ expect(result.refundedAmount).toBe(10);
83
+
84
+ expect(result.options['order-1'].items['item-1']).toMatchObject({
85
+ orderId: 'order-1',
86
+ amount: -8,
87
+ totalPaid: 0,
88
+ status: {
89
+ paid: {
90
+ value: false,
91
+ },
92
+ },
93
+ });
94
+
95
+ expect(result.options['order-1'].items['item-2']).toMatchObject({
96
+ orderId: 'order-1',
97
+ amount: -2,
98
+ totalPaid: 5,
99
+ status: {
100
+ paid: {
101
+ value: false,
102
+ },
103
+ },
104
+ });
105
+ });
106
+
107
+ test('accepts positive refund amounts and only applies the visible overpayment on current items', () => {
108
+ const options = createOptions({
109
+ 'item-1': {
110
+ total: 1,
111
+ totalPaid: 5,
112
+ status: {
113
+ paid: {
114
+ value: true,
115
+ date: new Date('2024-01-01T00:00:00.000Z'),
116
+ },
117
+ },
118
+ },
119
+ 'item-2': {
120
+ total: 1,
121
+ totalPaid: 5,
122
+ status: {
123
+ paid: {
124
+ value: true,
125
+ date: new Date('2024-01-01T00:00:00.000Z'),
126
+ },
127
+ },
128
+ },
129
+ });
130
+
131
+ const result = pricingService.order.applyRefund(options, 3);
132
+
133
+ expect(result.refundedAmount).toBe(3);
134
+
135
+ expect(result.options['order-1'].items['item-1']).toMatchObject({
136
+ orderId: 'order-1',
137
+ amount: -3,
138
+ totalPaid: 2,
139
+ status: {
140
+ paid: {
141
+ value: true,
142
+ },
143
+ },
144
+ });
145
+
146
+ expect(result.options['order-1'].items['item-2']).toMatchObject({
147
+ orderId: 'order-1',
148
+ amount: 0,
149
+ totalPaid: 5,
150
+ status: {
151
+ paid: {
152
+ value: true,
153
+ },
154
+ },
155
+ });
156
+ });
157
+
158
+ test('normalizes overpaid items after refund so no item keeps totalPaid above total', () => {
159
+ const options = createOptions({
160
+ '698a364799a15511959c9135': {
161
+ status: {
162
+ picked: {
163
+ value: false,
164
+ date: '',
165
+ },
166
+ paid: {
167
+ value: true,
168
+ date: new Date('2026-04-01T16:32:04.514Z'),
169
+ },
170
+ tracker: [],
171
+ },
172
+ total: 1.05,
173
+ totalPaid: 14.7,
174
+ },
175
+ '698a364799a15511959c9130': {
176
+ status: {
177
+ picked: {
178
+ value: false,
179
+ date: '',
180
+ },
181
+ paid: {
182
+ value: false,
183
+ date: new Date('2026-04-01T16:32:04.514Z'),
184
+ },
185
+ tracker: [],
186
+ },
187
+ total: 1.05,
188
+ totalPaid: 0,
189
+ },
190
+ });
191
+
192
+ const result = pricingService.order.applyRefund(options, -12.6);
193
+
194
+ expect(result.refundedAmount).toBe(12.6);
195
+
196
+ expect(
197
+ result.options['order-1'].items['698a364799a15511959c9135']
198
+ ).toMatchObject({
199
+ orderId: 'order-1',
200
+ amount: -13.65,
201
+ totalPaid: 1.05,
202
+ status: {
203
+ paid: {
204
+ value: true,
205
+ },
206
+ },
207
+ });
208
+
209
+ expect(
210
+ result.options['order-1'].items['698a364799a15511959c9130']
211
+ ).toMatchObject({
212
+ orderId: 'order-1',
213
+ amount: 1.05,
214
+ totalPaid: 1.05,
215
+ status: {
216
+ paid: {
217
+ value: true,
218
+ },
219
+ },
220
+ });
221
+ });
222
+
223
+ test('ignores refund amounts larger than the refundable balance', () => {
224
+ const options = createOptions({
225
+ 'item-1': {
226
+ total: 8,
227
+ totalPaid: 8,
228
+ status: {
229
+ paid: {
230
+ value: true,
231
+ date: new Date('2024-01-01T00:00:00.000Z'),
232
+ },
233
+ },
234
+ },
235
+ 'item-2': {
236
+ total: 7,
237
+ totalPaid: 2,
238
+ status: {
239
+ paid: {
240
+ value: false,
241
+ date: new Date('2024-01-01T00:00:00.000Z'),
242
+ },
243
+ },
244
+ },
245
+ });
246
+
247
+ const result = pricingService.order.applyRefund(options, -100);
248
+
249
+ expect(result.refundedAmount).toBe(10);
250
+
251
+ expect(result.options['order-1'].items['item-1']).toMatchObject({
252
+ orderId: 'order-1',
253
+ amount: -8,
254
+ totalPaid: 0,
255
+ status: {
256
+ paid: {
257
+ value: false,
258
+ },
259
+ },
260
+ });
261
+
262
+ expect(result.options['order-1'].items['item-2']).toMatchObject({
263
+ orderId: 'order-1',
264
+ amount: -2,
265
+ totalPaid: 0,
266
+ status: {
267
+ paid: {
268
+ value: false,
269
+ },
270
+ },
271
+ });
272
+ });
273
+
274
+ test('refunds overpaid zero-total items before touching correctly paid items', () => {
275
+ const options = createOptions({
276
+ shorts: {
277
+ total: 6.3,
278
+ totalPaid: 6.3,
279
+ status: {
280
+ paid: {
281
+ value: true,
282
+ date: new Date('2024-01-01T00:00:00.000Z'),
283
+ },
284
+ },
285
+ },
286
+ jacket1: {
287
+ total: 0,
288
+ totalPaid: 8.93,
289
+ status: {
290
+ paid: {
291
+ value: true,
292
+ date: new Date('2024-01-01T00:00:00.000Z'),
293
+ },
294
+ },
295
+ },
296
+ longJacket: {
297
+ total: 0,
298
+ totalPaid: 12.6,
299
+ status: {
300
+ paid: {
301
+ value: true,
302
+ date: new Date('2024-01-01T00:00:00.000Z'),
303
+ },
304
+ },
305
+ },
306
+ jacket2: {
307
+ total: 0,
308
+ totalPaid: 8.93,
309
+ status: {
310
+ paid: {
311
+ value: true,
312
+ date: new Date('2024-01-01T00:00:00.000Z'),
313
+ },
314
+ },
315
+ },
316
+ coat: {
317
+ total: 13.65,
318
+ totalPaid: 3.24,
319
+ status: {
320
+ paid: {
321
+ value: false,
322
+ date: new Date('2024-01-01T00:00:00.000Z'),
323
+ },
324
+ },
325
+ },
326
+ });
327
+
328
+ const result = pricingService.order.applyRefund(options, 20.05);
329
+
330
+ expect(result.refundedAmount).toBe(20.05);
331
+
332
+ expect(result.options['order-1'].items.shorts).toMatchObject({
333
+ orderId: 'order-1',
334
+ amount: 0,
335
+ totalPaid: 6.3,
336
+ status: {
337
+ paid: {
338
+ value: true,
339
+ },
340
+ },
341
+ });
342
+
343
+ expect(result.options['order-1'].items.jacket1).toMatchObject({
344
+ orderId: 'order-1',
345
+ amount: -8.93,
346
+ totalPaid: 0,
347
+ status: {
348
+ paid: {
349
+ value: true,
350
+ },
351
+ },
352
+ });
353
+
354
+ expect(result.options['order-1'].items.longJacket).toMatchObject({
355
+ orderId: 'order-1',
356
+ amount: -12.6,
357
+ totalPaid: 0,
358
+ status: {
359
+ paid: {
360
+ value: true,
361
+ },
362
+ },
363
+ });
364
+
365
+ expect(result.options['order-1'].items.jacket2).toMatchObject({
366
+ orderId: 'order-1',
367
+ amount: -8.93,
368
+ totalPaid: 0,
369
+ status: {
370
+ paid: {
371
+ value: true,
372
+ },
373
+ },
374
+ });
375
+
376
+ expect(result.options['order-1'].items.coat).toMatchObject({
377
+ orderId: 'order-1',
378
+ amount: 10.41,
379
+ totalPaid: 13.65,
380
+ status: {
381
+ paid: {
382
+ value: true,
383
+ },
384
+ },
385
+ });
386
+ });
387
+ });
@@ -574,9 +574,11 @@ describe('Order actions', () => {
574
574
  lockPaymentModifiers: true,
575
575
  });
576
576
 
577
+ paidOrderWithCredit.totalPaid += 1.25;
577
578
  const orderBalanceWithCredit = pricingService.order.getOrdersBalance({
578
579
  orders: [paidOrderWithCredit],
579
580
  });
581
+
580
582
  const itemBalanceWithCredit = pricingService.item.getItemsBalance({
581
583
  items: paidOrderWithCredit.items,
582
584
  });
@@ -3818,7 +3818,7 @@ describe('Order actions', () => {
3818
3818
  const pricing = usePricing({
3819
3819
  store: { _settings: { order: { autoMarkAsPaid: false } } },
3820
3820
  });
3821
- const order = { items: [orderItem], status: {} };
3821
+ const order = { items: [orderItem], status: {}, totalPaid: 30 };
3822
3822
  const newOrder = pricing.order.calculate(order);
3823
3823
 
3824
3824
  expect(newOrder).toHaveProperty('total', 25);
@@ -6,6 +6,17 @@ const session = {
6
6
  };
7
7
 
8
8
  const pricingService = usePricing(session);
9
+ const pricingServiceMethodLabel = usePricing({
10
+ store: {
11
+ _settings: {
12
+ _settings: {
13
+ payment: {
14
+ methods: mockStores[0]._settings.payment.methods,
15
+ },
16
+ },
17
+ },
18
+ },
19
+ });
9
20
 
10
21
  describe('getMaxAmountToRefund tests', () => {
11
22
  test('Returns 0 when payment is undefined', () => {
@@ -49,3 +60,150 @@ describe('getMaxAmountToRefund tests', () => {
49
60
  expect(pricingService.payment.getMaxAmountToRefund({ payment })).toBe(50);
50
61
  });
51
62
  });
63
+
64
+ describe('getRefundableOrderAmount tests', () => {
65
+ test('Returns 0 when retained order payment still matches the remaining item total', () => {
66
+ const order = {
67
+ _id: 'order-1',
68
+ totalPaid: 5,
69
+ items: [
70
+ {
71
+ _id: 'item-2',
72
+ name: 'Simple Item 2',
73
+ total: 5,
74
+ totalPaid: 0,
75
+ status: { paid: { value: false } },
76
+ },
77
+ ],
78
+ };
79
+
80
+ expect(pricingService.payment.getRefundableOrderAmount({ order })).toBe(0);
81
+ });
82
+
83
+ test('Returns the net order overpayment (totalPaid minus total), regardless of per-item breakdown', () => {
84
+ const order = {
85
+ _id: 'order-2',
86
+ totalPaid: 14.7,
87
+ items: [
88
+ {
89
+ _id: 'item-1',
90
+ name: 'Winter Coat',
91
+ total: 1.05,
92
+ totalPaid: 14.7,
93
+ status: { paid: { value: true } },
94
+ },
95
+ {
96
+ _id: 'item-2',
97
+ name: 'Coat',
98
+ total: 1.05,
99
+ totalPaid: 0,
100
+ status: { paid: { value: false } },
101
+ },
102
+ ],
103
+ };
104
+
105
+ expect(pricingService.payment.getRefundableOrderAmount({ order })).toBe(
106
+ 12.6
107
+ );
108
+ });
109
+ });
110
+
111
+ describe('getOverPaidOrders tests', () => {
112
+ const baseOrder = pricingService.order.calculate({
113
+ _id: 'order-1',
114
+ items: [{ price: 30, quantity: 1, modifiers: [] }],
115
+ });
116
+
117
+ test('includes order and accumulates overpaid amount when customer paid more than the order total', () => {
118
+ const order = { ...baseOrder, totalPaid: 50 };
119
+
120
+ const result = pricingService.payment.getOverPaidOrders({
121
+ orders: [order],
122
+ });
123
+
124
+ expect(result.overPaidOrders).toHaveLength(1);
125
+ expect(result.overPaidAmount).toBe(20);
126
+ });
127
+
128
+ test('returns empty list and zero amount when customer paid exactly the order total', () => {
129
+ const order = { ...baseOrder, totalPaid: 30 };
130
+
131
+ const result = pricingService.payment.getOverPaidOrders({
132
+ orders: [order],
133
+ });
134
+
135
+ expect(result.overPaidOrders).toHaveLength(0);
136
+ expect(result.overPaidAmount).toBe(0);
137
+ });
138
+ });
139
+
140
+ describe('capOverPaidItem tests', () => {
141
+ test('caps totalPaid to item total and returns the excess as amountCapped', () => {
142
+ const item = { total: 10, totalPaid: 15 };
143
+
144
+ const result = pricingService.item.capOverPaidItem({ item });
145
+
146
+ expect(result.item.totalPaid).toBe(10);
147
+ expect(result.amountCapped).toBe(5);
148
+ });
149
+
150
+ test('returns item unchanged and amountCapped 0 when totalPaid does not exceed total', () => {
151
+ const item = { total: 10, totalPaid: 10 };
152
+
153
+ const result = pricingService.item.capOverPaidItem({ item });
154
+
155
+ expect(result.item.totalPaid).toBe(10);
156
+ expect(result.amountCapped).toBe(0);
157
+ });
158
+ });
159
+
160
+ describe('getTotalPaidToRedistribute tests', () => {
161
+ test('returns order with redistributableAmount when totalPaid exceeds item totalPaid sum', () => {
162
+ const order = {
163
+ _id: 'order-1',
164
+ totalPaid: 5,
165
+ items: [{ totalPaid: 0 }, { totalPaid: 0 }],
166
+ };
167
+
168
+ const result = pricingService.order.getTotalPaidToRedistribute({
169
+ orders: [order],
170
+ });
171
+
172
+ expect(result.redistributableOrders).toHaveLength(1);
173
+ expect(result.redistributableOrders[0].redistributableAmount).toBe(5);
174
+ expect(result.redistributableAmount).toBe(5);
175
+ });
176
+
177
+ test('returns empty list and zero amount when all totalPaid is already distributed to items', () => {
178
+ const order = {
179
+ _id: 'order-1',
180
+ totalPaid: 10,
181
+ items: [{ totalPaid: 6 }, { totalPaid: 4 }],
182
+ };
183
+
184
+ const result = pricingService.order.getTotalPaidToRedistribute({
185
+ orders: [order],
186
+ });
187
+
188
+ expect(result.redistributableOrders).toHaveLength(0);
189
+ expect(result.redistributableAmount).toBe(0);
190
+ });
191
+ });
192
+
193
+ describe('getMethodLabel tests', () => {
194
+ test('Returns the configured label for the payment provider', () => {
195
+ expect(
196
+ pricingServiceMethodLabel.payment.getMethodLabel({
197
+ paymentProvider: 'cash',
198
+ })
199
+ ).toBe('Cash');
200
+ });
201
+
202
+ test('Returns empty string when the provider is missing', () => {
203
+ expect(
204
+ pricingServiceMethodLabel.payment.getMethodLabel({
205
+ paymentProvider: 'missing-provider',
206
+ })
207
+ ).toBe('');
208
+ });
209
+ });
@@ -1,9 +1,5 @@
1
1
  const getStatusByItems = require('./getStatusByItems');
2
2
  const getTotalByItems = require('./getTotalByItems');
3
- const resolveItemState = require('./resolveItemState');
4
- const computeRefundables = require('./computeRefundables');
5
- const buildItemRefund = require('./buildItemRefund');
6
- const applyRefundToInvoices = require('./applyRefundToInvoices');
7
3
 
8
4
  const invoiceActions = (deps = {}) => {
9
5
  const actions = {};
@@ -16,10 +12,6 @@ const invoiceActions = (deps = {}) => {
16
12
  const freezedActions = Object.freeze({
17
13
  getStatusByItems: getStatusByItems(innerDeps),
18
14
  getTotalByItems: getTotalByItems(innerDeps),
19
- resolveItemState: resolveItemState(innerDeps),
20
- computeRefundables: computeRefundables(innerDeps),
21
- buildItemRefund: buildItemRefund(innerDeps),
22
- applyRefundToInvoices: applyRefundToInvoices(innerDeps),
23
15
  });
24
16
 
25
17
  Object.keys(freezedActions).forEach(actionName => {
@@ -0,0 +1,14 @@
1
+ module.exports = ({ utils }) =>
2
+ function capOverPaidItem({ item }) {
3
+ const total = item.total || 0;
4
+
5
+ const totalPaid = item.totalPaid || 0;
6
+
7
+ if (totalPaid > total)
8
+ return {
9
+ item: { ...item, totalPaid: total },
10
+ amountCapped: utils.math.sub(totalPaid, total),
11
+ };
12
+
13
+ return { item, amountCapped: 0 };
14
+ };
package/lib/item/index.js CHANGED
@@ -84,7 +84,7 @@ const getAmountToPayById = require('./getAmountToPayById');
84
84
  const applyPayment = require('./applyPayment');
85
85
  const getBalanceForPaymentModifier = require('./getBalanceForPaymentModifier');
86
86
  const isOverpaid = require('./isOverpaid');
87
- const getOverpaidAmount = require('./getOverpaidAmount');
87
+ const capOverPaidItem = require('./capOverPaidItem');
88
88
 
89
89
  const itemActions = (deps = {}) => {
90
90
  const actions = {};
@@ -182,7 +182,7 @@ const itemActions = (deps = {}) => {
182
182
  applyPayment: applyPayment(innerDeps),
183
183
  getBalanceForPaymentModifier: getBalanceForPaymentModifier(innerDeps),
184
184
  isOverpaid: isOverpaid(innerDeps),
185
- getOverpaidAmount: getOverpaidAmount(innerDeps),
185
+ capOverPaidItem: capOverPaidItem(innerDeps),
186
186
  });
187
187
 
188
188
  Object.keys(freezedActions).forEach(actionName => {