@darkpos/pricing 1.0.150 → 1.0.152
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/__TEST__/invoice/refundInvoices.test.js +0 -260
- package/__TEST__/order/order-payment-modifier.test.js +2 -0
- package/__TEST__/order/order.test.js +1 -1
- package/__TEST__/payment.test.js +228 -0
- package/lib/invoice/index.js +0 -4
- package/lib/item/index.js +0 -2
- package/lib/order/calculate.js +0 -1
- package/lib/order/hasRemainingSubscription.js +17 -5
- package/lib/order/index.js +0 -2
- package/lib/payment/appendIfOverPaid.js +17 -0
- package/lib/payment/getMethodLabel.js +7 -0
- package/lib/payment/getOverPaidOrders.js +36 -0
- package/lib/payment/getRefundableOrderAmount.js +34 -0
- package/lib/payment/index.js +8 -0
- package/package.json +2 -2
- package/lib/invoice/applyRefundToInvoices.js +0 -52
- package/lib/invoice/buildItemRefund.js +0 -23
- package/lib/item/getOverpaidAmount.js +0 -7
- package/lib/order/getOverpaidAmount.js +0 -13
|
@@ -104,263 +104,3 @@ describe('computeRefundables', () => {
|
|
|
104
104
|
expect(refundables).toEqual([0, 0]);
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
|
-
|
|
108
|
-
describe('applyRefundToInvoices', () => {
|
|
109
|
-
const invoicesForOnePayment = [
|
|
110
|
-
{
|
|
111
|
-
_id: 'inv-1',
|
|
112
|
-
amount: 25,
|
|
113
|
-
paymentOrderItems: [overpaidInvoiceItem, partiallyPaidInvoiceItem],
|
|
114
|
-
},
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
test('the refund invoice has the correct total amount and both item refunds', () => {
|
|
118
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
119
|
-
invoicesForOnePayment,
|
|
120
|
-
ordersById,
|
|
121
|
-
4
|
|
122
|
-
);
|
|
123
|
-
expect(refundInvoices).toHaveLength(1);
|
|
124
|
-
expect(refundInvoices[0].amount).toBe(-4);
|
|
125
|
-
expect(refundInvoices[0].paymentOrderItems).toHaveLength(2);
|
|
126
|
-
expect(refundInvoices[0].paymentOrderItems[0].amount).toBe(-4.2);
|
|
127
|
-
expect(refundInvoices[0].paymentOrderItems[1].amount).toBe(0.2);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('the overpaid item is cleared to its exact total and marked as paid', () => {
|
|
131
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
132
|
-
invoicesForOnePayment,
|
|
133
|
-
ordersById,
|
|
134
|
-
4
|
|
135
|
-
);
|
|
136
|
-
expect(refundInvoices[0].paymentOrderItems[0].status.paid.value).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('the underpaid item receives a credit that covers its remaining balance and is marked as paid', () => {
|
|
140
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
141
|
-
invoicesForOnePayment,
|
|
142
|
-
ordersById,
|
|
143
|
-
4
|
|
144
|
-
);
|
|
145
|
-
expect(refundInvoices[0].paymentOrderItems[1].status.paid.value).toBe(true);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('returns an empty array when the refund amount is 0', () => {
|
|
149
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
150
|
-
invoicesForOnePayment,
|
|
151
|
-
ordersById,
|
|
152
|
-
0
|
|
153
|
-
);
|
|
154
|
-
expect(refundInvoices).toEqual([]);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('skips invoices that are already refunded or have no positive balance', () => {
|
|
158
|
-
const invoicesIncludingAlreadyRefunded = [
|
|
159
|
-
{
|
|
160
|
-
_id: 'inv-already-refunded',
|
|
161
|
-
amount: -5,
|
|
162
|
-
paymentOrderItems: [overpaidInvoiceItem],
|
|
163
|
-
},
|
|
164
|
-
...invoicesForOnePayment,
|
|
165
|
-
];
|
|
166
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
167
|
-
invoicesIncludingAlreadyRefunded,
|
|
168
|
-
ordersById,
|
|
169
|
-
4
|
|
170
|
-
);
|
|
171
|
-
expect(refundInvoices).toHaveLength(1);
|
|
172
|
-
expect(refundInvoices[0]._id).toBe('inv-1');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('distributes the refund across multiple invoices when the first is exhausted', () => {
|
|
176
|
-
const splitPaymentInvoices = [
|
|
177
|
-
{
|
|
178
|
-
_id: 'first-payment',
|
|
179
|
-
amount: 14.7,
|
|
180
|
-
paymentOrderItems: [overpaidInvoiceItem],
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
_id: 'second-payment',
|
|
184
|
-
amount: 10.3,
|
|
185
|
-
paymentOrderItems: [partiallyPaidInvoiceItem],
|
|
186
|
-
},
|
|
187
|
-
];
|
|
188
|
-
// Refund 5: overpaid item covers 4.2, the remaining 0.8 comes from the second invoice
|
|
189
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
190
|
-
splitPaymentInvoices,
|
|
191
|
-
ordersById,
|
|
192
|
-
5
|
|
193
|
-
);
|
|
194
|
-
expect(refundInvoices).toHaveLength(2);
|
|
195
|
-
expect(refundInvoices[0].amount).toBe(-4.2);
|
|
196
|
-
expect(refundInvoices[1].amount).toBe(-0.8);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('applyRefundToInvoices — 5-item order with 3 items priced to 0', () => {
|
|
201
|
-
const ORDER_5_ID = 'order-5items';
|
|
202
|
-
|
|
203
|
-
// Items as they appear in the payment invoice (stale totals from payment time)
|
|
204
|
-
const shortsInvoiceItem = {
|
|
205
|
-
orderId: ORDER_5_ID,
|
|
206
|
-
orderItemId: 'shorts',
|
|
207
|
-
total: 6.3,
|
|
208
|
-
totalPaid: 6.3,
|
|
209
|
-
amount: 6.3,
|
|
210
|
-
status: { paid: { value: true } },
|
|
211
|
-
};
|
|
212
|
-
const jacket1InvoiceItem = {
|
|
213
|
-
orderId: ORDER_5_ID,
|
|
214
|
-
orderItemId: 'jacket-1',
|
|
215
|
-
total: 8.93,
|
|
216
|
-
totalPaid: 8.93,
|
|
217
|
-
amount: 8.93,
|
|
218
|
-
status: { paid: { value: true } },
|
|
219
|
-
};
|
|
220
|
-
const longJacketInvoiceItem = {
|
|
221
|
-
orderId: ORDER_5_ID,
|
|
222
|
-
orderItemId: 'long-jacket',
|
|
223
|
-
total: 12.6,
|
|
224
|
-
totalPaid: 12.6,
|
|
225
|
-
amount: 12.6,
|
|
226
|
-
status: { paid: { value: true } },
|
|
227
|
-
};
|
|
228
|
-
const jacket2InvoiceItem = {
|
|
229
|
-
orderId: ORDER_5_ID,
|
|
230
|
-
orderItemId: 'jacket-2',
|
|
231
|
-
total: 8.93,
|
|
232
|
-
totalPaid: 8.93,
|
|
233
|
-
amount: 8.93,
|
|
234
|
-
status: { paid: { value: true } },
|
|
235
|
-
};
|
|
236
|
-
const coatInvoiceItem = {
|
|
237
|
-
orderId: ORDER_5_ID,
|
|
238
|
-
orderItemId: 'coat',
|
|
239
|
-
total: 13.65,
|
|
240
|
-
totalPaid: 3.24,
|
|
241
|
-
amount: 3.24,
|
|
242
|
-
status: { paid: { value: false } },
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Live order state after Jacket×2 and Long Jacket prices changed to 0
|
|
246
|
-
const liveOrder5 = {
|
|
247
|
-
_id: ORDER_5_ID,
|
|
248
|
-
items: [
|
|
249
|
-
{
|
|
250
|
-
_id: 'shorts',
|
|
251
|
-
total: 6.3,
|
|
252
|
-
totalPaid: 6.3,
|
|
253
|
-
status: { paid: { value: true } },
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
_id: 'jacket-1',
|
|
257
|
-
total: 0,
|
|
258
|
-
totalPaid: 8.93,
|
|
259
|
-
status: { paid: { value: true } },
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
_id: 'long-jacket',
|
|
263
|
-
total: 0,
|
|
264
|
-
totalPaid: 12.6,
|
|
265
|
-
status: { paid: { value: true } },
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
_id: 'jacket-2',
|
|
269
|
-
total: 0,
|
|
270
|
-
totalPaid: 8.93,
|
|
271
|
-
status: { paid: { value: true } },
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
_id: 'coat',
|
|
275
|
-
total: 13.65,
|
|
276
|
-
totalPaid: 3.24,
|
|
277
|
-
status: { paid: { value: false } },
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const ordersById5 = { [ORDER_5_ID]: liveOrder5 };
|
|
283
|
-
|
|
284
|
-
const paymentInvoices5 = [
|
|
285
|
-
{
|
|
286
|
-
invoiceId: 'inv-5items',
|
|
287
|
-
amount: 40,
|
|
288
|
-
paymentOrderItems: [
|
|
289
|
-
shortsInvoiceItem,
|
|
290
|
-
jacket1InvoiceItem,
|
|
291
|
-
longJacketInvoiceItem,
|
|
292
|
-
jacket2InvoiceItem,
|
|
293
|
-
coatInvoiceItem,
|
|
294
|
-
],
|
|
295
|
-
},
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
test('correctly refunds 20.05 — clears the 3 zero-priced items and credits the remaining balance to coat', () => {
|
|
299
|
-
const refundInvoices = pricing.invoice.applyRefundToInvoices(
|
|
300
|
-
paymentInvoices5,
|
|
301
|
-
ordersById5,
|
|
302
|
-
20.05
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
expect(refundInvoices).toHaveLength(1);
|
|
306
|
-
expect(refundInvoices[0].amount).toBe(-20.05);
|
|
307
|
-
expect(refundInvoices[0].paymentOrderItems).toHaveLength(4); // shorts has 0 excess, skipped
|
|
308
|
-
|
|
309
|
-
const byItemId = Object.fromEntries(
|
|
310
|
-
refundInvoices[0].paymentOrderItems.map(item => [item.orderItemId, item])
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
expect(byItemId['jacket-1'].amount).toBe(-8.93);
|
|
314
|
-
expect(byItemId['jacket-1'].status.paid.value).toBe(true);
|
|
315
|
-
|
|
316
|
-
expect(byItemId['long-jacket'].amount).toBe(-12.6);
|
|
317
|
-
expect(byItemId['long-jacket'].status.paid.value).toBe(true);
|
|
318
|
-
|
|
319
|
-
expect(byItemId['jacket-2'].amount).toBe(-8.93);
|
|
320
|
-
expect(byItemId['jacket-2'].status.paid.value).toBe(true);
|
|
321
|
-
|
|
322
|
-
expect(byItemId.coat.amount).toBe(10.41);
|
|
323
|
-
expect(byItemId.coat.status.paid.value).toBe(true);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe('buildItemRefund', () => {
|
|
328
|
-
const overpaidItemState = pricing.invoice.resolveItemState(
|
|
329
|
-
overpaidInvoiceItem,
|
|
330
|
-
ordersById
|
|
331
|
-
);
|
|
332
|
-
const underpaidItemState = pricing.invoice.resolveItemState(
|
|
333
|
-
partiallyPaidInvoiceItem,
|
|
334
|
-
ordersById
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
test('overpaid item: refunding its full excess results in a negative amount and paid = true', () => {
|
|
338
|
-
const itemRefund = pricing.invoice.buildItemRefund(overpaidItemState, 4.2);
|
|
339
|
-
expect(itemRefund.amount).toBe(-4.2);
|
|
340
|
-
expect(itemRefund.totalPaid).toBe(14.7); // pre-refund value preserved
|
|
341
|
-
expect(itemRefund.total).toBe(10.5);
|
|
342
|
-
expect(itemRefund.status.paid.value).toBe(true); // 14.7 - 4.2 = 10.5 >= 10.5
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test('underpaid item: applying a credit results in a positive amount and paid = true', () => {
|
|
346
|
-
const itemRefund = pricing.invoice.buildItemRefund(
|
|
347
|
-
underpaidItemState,
|
|
348
|
-
-0.2
|
|
349
|
-
);
|
|
350
|
-
expect(itemRefund.amount).toBe(0.2); // positive = credit
|
|
351
|
-
expect(itemRefund.totalPaid).toBe(10.3);
|
|
352
|
-
expect(itemRefund.total).toBe(10.5);
|
|
353
|
-
expect(itemRefund.status.paid.value).toBe(true); // 10.3 - (-0.2) = 10.5 >= 10.5
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
test('paid stays false when the refund does not fully cover the remaining balance', () => {
|
|
357
|
-
const itemRefund = pricing.invoice.buildItemRefund(underpaidItemState, 2); // totalPaid drops to 8.3
|
|
358
|
-
expect(itemRefund.status.paid.value).toBe(false); // 8.3 < 10.5
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test('preserves all other fields from the original invoice item', () => {
|
|
362
|
-
const itemRefund = pricing.invoice.buildItemRefund(overpaidItemState, 4.2);
|
|
363
|
-
expect(itemRefund.orderId).toBe(ORDER_ID);
|
|
364
|
-
expect(itemRefund.orderItemId).toBe(OVERPAID_ITEM_ID);
|
|
365
|
-
});
|
|
366
|
-
});
|
|
@@ -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);
|
package/__TEST__/payment.test.js
CHANGED
|
@@ -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,220 @@ describe('getMaxAmountToRefund tests', () => {
|
|
|
49
60
|
expect(pricingService.payment.getMaxAmountToRefund({ payment })).toBe(50);
|
|
50
61
|
});
|
|
51
62
|
});
|
|
63
|
+
|
|
64
|
+
describe('getRefundableOrderAmount tests', () => {
|
|
65
|
+
test('Returns paid amount not attached to current items', () => {
|
|
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(5);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('Returns only the excess from items that actually received payment', () => {
|
|
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
|
+
13.65
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('getoverPaidOrders tests', () => {
|
|
112
|
+
test('Collects refundable child orders from a mixed parent list', () => {
|
|
113
|
+
const orders = [
|
|
114
|
+
{
|
|
115
|
+
_id: 'parent-1',
|
|
116
|
+
isParent: true,
|
|
117
|
+
orders: [
|
|
118
|
+
{
|
|
119
|
+
_id: 'child-1',
|
|
120
|
+
totalPaid: 5,
|
|
121
|
+
items: [
|
|
122
|
+
{
|
|
123
|
+
_id: 'item-1',
|
|
124
|
+
total: 5,
|
|
125
|
+
totalPaid: 0,
|
|
126
|
+
status: { paid: { value: false } },
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
_id: 'child-2',
|
|
132
|
+
totalPaid: 5,
|
|
133
|
+
items: [
|
|
134
|
+
{
|
|
135
|
+
_id: 'item-2',
|
|
136
|
+
total: 5,
|
|
137
|
+
totalPaid: 5,
|
|
138
|
+
status: { paid: { value: true } },
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
expect(pricingService.payment.getOverPaidOrders({ orders })).toEqual({
|
|
147
|
+
overPaidAmount: 5,
|
|
148
|
+
overPaidOrders: [
|
|
149
|
+
{
|
|
150
|
+
_id: 'child-1',
|
|
151
|
+
totalPaid: 5,
|
|
152
|
+
items: [
|
|
153
|
+
{
|
|
154
|
+
_id: 'item-1',
|
|
155
|
+
total: 5,
|
|
156
|
+
totalPaid: 0,
|
|
157
|
+
status: { paid: { value: false } },
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
refundableAmount: 5,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('Ignores empty and non-refundable orders', () => {
|
|
167
|
+
const orders = [
|
|
168
|
+
null,
|
|
169
|
+
{
|
|
170
|
+
_id: 'order-1',
|
|
171
|
+
totalPaid: 5,
|
|
172
|
+
items: [
|
|
173
|
+
{
|
|
174
|
+
_id: 'item-1',
|
|
175
|
+
total: 5,
|
|
176
|
+
totalPaid: 5,
|
|
177
|
+
status: { paid: { value: true } },
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
isParent: true,
|
|
183
|
+
orders: [
|
|
184
|
+
{
|
|
185
|
+
_id: 'child-1',
|
|
186
|
+
totalPaid: 1,
|
|
187
|
+
items: [
|
|
188
|
+
{
|
|
189
|
+
_id: 'item-2',
|
|
190
|
+
total: 1,
|
|
191
|
+
totalPaid: 1,
|
|
192
|
+
status: { paid: { value: true } },
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
expect(pricingService.payment.getOverPaidOrders({ orders })).toEqual({
|
|
201
|
+
overPaidAmount: 0,
|
|
202
|
+
overPaidOrders: [],
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('getMethodLabel tests', () => {
|
|
208
|
+
test('Returns the configured label for the payment provider', () => {
|
|
209
|
+
expect(
|
|
210
|
+
pricingServiceMethodLabel.payment.getMethodLabel({
|
|
211
|
+
paymentProvider: 'cash',
|
|
212
|
+
})
|
|
213
|
+
).toBe('Cash');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('Returns empty string when the provider is missing', () => {
|
|
217
|
+
expect(
|
|
218
|
+
pricingServiceMethodLabel.payment.getMethodLabel({
|
|
219
|
+
paymentProvider: 'missing-provider',
|
|
220
|
+
})
|
|
221
|
+
).toBe('');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('appendIfOverPaid tests', () => {
|
|
226
|
+
test('Appends the order with refundableAmount when the order is overpaid', () => {
|
|
227
|
+
const currentOrder = {
|
|
228
|
+
_id: 'order-1',
|
|
229
|
+
displayId: '101',
|
|
230
|
+
totalPaid: 5,
|
|
231
|
+
items: [
|
|
232
|
+
{
|
|
233
|
+
_id: 'item-2',
|
|
234
|
+
name: 'Simple Item 2',
|
|
235
|
+
total: 5,
|
|
236
|
+
totalPaid: 0,
|
|
237
|
+
status: { paid: { value: false } },
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
expect(
|
|
243
|
+
pricingService.payment.appendIfOverPaid({
|
|
244
|
+
currentOrder,
|
|
245
|
+
overPaidOrders: [{ _id: 'existing-order', refundableAmount: 1 }],
|
|
246
|
+
})
|
|
247
|
+
).toEqual([
|
|
248
|
+
{ _id: 'existing-order', refundableAmount: 1 },
|
|
249
|
+
{
|
|
250
|
+
...currentOrder,
|
|
251
|
+
refundableAmount: 5,
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('Leaves the collection unchanged when the order has no refundable amount', () => {
|
|
257
|
+
const overPaidOrders = [{ _id: 'existing-order', refundableAmount: 1 }];
|
|
258
|
+
const currentOrder = {
|
|
259
|
+
_id: 'order-3',
|
|
260
|
+
totalPaid: 5,
|
|
261
|
+
items: [
|
|
262
|
+
{
|
|
263
|
+
_id: 'item-1',
|
|
264
|
+
name: 'Simple Item 1',
|
|
265
|
+
total: 5,
|
|
266
|
+
totalPaid: 5,
|
|
267
|
+
status: { paid: { value: true } },
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
expect(
|
|
273
|
+
pricingService.payment.appendIfOverPaid({
|
|
274
|
+
currentOrder,
|
|
275
|
+
overPaidOrders,
|
|
276
|
+
})
|
|
277
|
+
).toEqual(overPaidOrders);
|
|
278
|
+
});
|
|
279
|
+
});
|
package/lib/invoice/index.js
CHANGED
|
@@ -2,8 +2,6 @@ const getStatusByItems = require('./getStatusByItems');
|
|
|
2
2
|
const getTotalByItems = require('./getTotalByItems');
|
|
3
3
|
const resolveItemState = require('./resolveItemState');
|
|
4
4
|
const computeRefundables = require('./computeRefundables');
|
|
5
|
-
const buildItemRefund = require('./buildItemRefund');
|
|
6
|
-
const applyRefundToInvoices = require('./applyRefundToInvoices');
|
|
7
5
|
|
|
8
6
|
const invoiceActions = (deps = {}) => {
|
|
9
7
|
const actions = {};
|
|
@@ -18,8 +16,6 @@ const invoiceActions = (deps = {}) => {
|
|
|
18
16
|
getTotalByItems: getTotalByItems(innerDeps),
|
|
19
17
|
resolveItemState: resolveItemState(innerDeps),
|
|
20
18
|
computeRefundables: computeRefundables(innerDeps),
|
|
21
|
-
buildItemRefund: buildItemRefund(innerDeps),
|
|
22
|
-
applyRefundToInvoices: applyRefundToInvoices(innerDeps),
|
|
23
19
|
});
|
|
24
20
|
|
|
25
21
|
Object.keys(freezedActions).forEach(actionName => {
|
package/lib/item/index.js
CHANGED
|
@@ -84,7 +84,6 @@ 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');
|
|
88
87
|
|
|
89
88
|
const itemActions = (deps = {}) => {
|
|
90
89
|
const actions = {};
|
|
@@ -182,7 +181,6 @@ const itemActions = (deps = {}) => {
|
|
|
182
181
|
applyPayment: applyPayment(innerDeps),
|
|
183
182
|
getBalanceForPaymentModifier: getBalanceForPaymentModifier(innerDeps),
|
|
184
183
|
isOverpaid: isOverpaid(innerDeps),
|
|
185
|
-
getOverpaidAmount: getOverpaidAmount(innerDeps),
|
|
186
184
|
});
|
|
187
185
|
|
|
188
186
|
Object.keys(freezedActions).forEach(actionName => {
|
package/lib/order/calculate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
module.exports = ({ actions, modifierActions }) => {
|
|
1
|
+
module.exports = ({ actions, modifierActions, _ }) => {
|
|
2
2
|
const getRemaining = (subscriptions, orderId) => {
|
|
3
3
|
const remaining = subscriptions.reduce((total, subscription) => {
|
|
4
|
-
const { usesLimit = 0, uses = 0, history = {} } = subscription;
|
|
4
|
+
const { usesLimit = 0, uses = 0, history = {} } = subscription.item || {};
|
|
5
5
|
const orderHistory = history[orderId] || 0;
|
|
6
6
|
return total + usesLimit + orderHistory - uses;
|
|
7
7
|
}, 0);
|
|
@@ -37,8 +37,18 @@ module.exports = ({ actions, modifierActions }) => {
|
|
|
37
37
|
|
|
38
38
|
const getSubscriptions = subscriptions =>
|
|
39
39
|
subscriptions
|
|
40
|
-
.map(each =>
|
|
41
|
-
.filter(Boolean)
|
|
40
|
+
.map(each => _.get(each, 'properties.subscription'))
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.filter(each => {
|
|
43
|
+
const renewalType = _.get(each, 'item.renewalType');
|
|
44
|
+
const endDate = _.get(each, 'dateLimit.to');
|
|
45
|
+
const today = new Date();
|
|
46
|
+
|
|
47
|
+
if (renewalType === 'none' && endDate && new Date(endDate) < today)
|
|
48
|
+
return false;
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
42
52
|
|
|
43
53
|
return function hasRemainingSubscription({ order, item }) {
|
|
44
54
|
if (!order || !item || !item.itemId) return [false];
|
|
@@ -53,7 +63,9 @@ module.exports = ({ actions, modifierActions }) => {
|
|
|
53
63
|
subscriptions = getSubscriptions(subscriptions);
|
|
54
64
|
|
|
55
65
|
const isUnlimited = subscriptions.some(
|
|
56
|
-
each =>
|
|
66
|
+
each =>
|
|
67
|
+
!_.get(each, 'item.usesLimit') ||
|
|
68
|
+
_.get(each, 'item.renewalType') === 'limit_reached'
|
|
57
69
|
);
|
|
58
70
|
if (isUnlimited) return [true];
|
|
59
71
|
|
package/lib/order/index.js
CHANGED
|
@@ -100,7 +100,6 @@ const removeEmptyNotes = require('./removeEmptyNotes');
|
|
|
100
100
|
const getTaxes = require('./getTaxes');
|
|
101
101
|
const getPickedStatus = require('./getPickedStatus');
|
|
102
102
|
const calculateWithPayment = require('./calculateWithPayment');
|
|
103
|
-
const getOverpaidAmount = require('./getOverpaidAmount');
|
|
104
103
|
const hasSerial = require('./hasSerial');
|
|
105
104
|
|
|
106
105
|
const orderActions = (deps = {}) => {
|
|
@@ -213,7 +212,6 @@ const orderActions = (deps = {}) => {
|
|
|
213
212
|
getTaxes: getTaxes(innerDeps),
|
|
214
213
|
getPickedStatus: getPickedStatus(innerDeps),
|
|
215
214
|
calculateWithPayment: calculateWithPayment(innerDeps),
|
|
216
|
-
getOverpaidAmount: getOverpaidAmount(innerDeps),
|
|
217
215
|
hasSerial: hasSerial(innerDeps),
|
|
218
216
|
});
|
|
219
217
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = ({ actions }) =>
|
|
2
|
+
function appendIfOverPaid({ currentOrder, overPaidOrders = [] }) {
|
|
3
|
+
if (!currentOrder || !currentOrder._id) return overPaidOrders;
|
|
4
|
+
|
|
5
|
+
const refundableAmount = actions.getRefundableOrderAmount({
|
|
6
|
+
order: currentOrder,
|
|
7
|
+
});
|
|
8
|
+
if (refundableAmount <= 0) return overPaidOrders;
|
|
9
|
+
|
|
10
|
+
return [
|
|
11
|
+
...overPaidOrders,
|
|
12
|
+
{
|
|
13
|
+
...currentOrder,
|
|
14
|
+
refundableAmount,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
module.exports = ({ settings, _ }) =>
|
|
2
|
+
function getMethodLabel({ paymentProvider }) {
|
|
3
|
+
const methods = _.get(settings, '_settings.payment.methods', {});
|
|
4
|
+
|
|
5
|
+
if (!paymentProvider || !methods[paymentProvider]) return '';
|
|
6
|
+
return methods[paymentProvider].label || '';
|
|
7
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = ({ actions }) =>
|
|
2
|
+
function getOverPaidOrders({ orders = [] }) {
|
|
3
|
+
let overPaidOrders = [];
|
|
4
|
+
let overPaidAmount = 0;
|
|
5
|
+
|
|
6
|
+
const appendIfOverPaid = currentOrder => {
|
|
7
|
+
overPaidOrders = actions.appendIfOverPaid({
|
|
8
|
+
currentOrder,
|
|
9
|
+
overPaidOrders,
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
(orders || []).forEach(order => {
|
|
14
|
+
if (!order || !order._id) return;
|
|
15
|
+
if (order.isParent) {
|
|
16
|
+
const childOrders = order.orders || [];
|
|
17
|
+
|
|
18
|
+
if (childOrders.length > 0) {
|
|
19
|
+
childOrders.forEach(appendIfOverPaid);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
appendIfOverPaid(order);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
overPaidAmount = overPaidOrders.reduce(
|
|
28
|
+
(total, order) => total + Number(order.refundableAmount || 0),
|
|
29
|
+
0
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
overPaidOrders,
|
|
34
|
+
overPaidAmount,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module.exports = ({ utils }) => {
|
|
2
|
+
const { math } = utils;
|
|
3
|
+
|
|
4
|
+
return function getRefundableOrderAmount({ order }) {
|
|
5
|
+
if (!order) return 0;
|
|
6
|
+
|
|
7
|
+
const items = Array.isArray(order.items) ? order.items : [];
|
|
8
|
+
const paidItems = items.filter(item => Number(item.totalPaid || 0) > 0);
|
|
9
|
+
|
|
10
|
+
const itemsTotalPaid = items.reduce(
|
|
11
|
+
(acc, item) => math.add(acc, Number(item.totalPaid || 0)),
|
|
12
|
+
0
|
|
13
|
+
);
|
|
14
|
+
const paidItemsTotalPaid = paidItems.reduce(
|
|
15
|
+
(acc, item) => math.add(acc, Number(item.totalPaid || 0)),
|
|
16
|
+
0
|
|
17
|
+
);
|
|
18
|
+
const paidItemsTotal = paidItems.reduce(
|
|
19
|
+
(acc, item) => math.add(acc, Number(item.total || 0)),
|
|
20
|
+
0
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const orphanedPaidAmount = math.max(
|
|
24
|
+
math.sub(order.totalPaid || 0, itemsTotalPaid),
|
|
25
|
+
0
|
|
26
|
+
);
|
|
27
|
+
const refundablePaidItemsAmount = math.max(
|
|
28
|
+
math.sub(paidItemsTotalPaid, paidItemsTotal),
|
|
29
|
+
0
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return math.add(orphanedPaidAmount, refundablePaidItemsAmount);
|
|
33
|
+
};
|
|
34
|
+
};
|
package/lib/payment/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
//
|
|
2
|
+
const appendIfOverPaid = require('./appendIfOverPaid');
|
|
2
3
|
const getMaxAmountToRefund = require('./getMaxAmountToRefund');
|
|
4
|
+
const getRefundableOrderAmount = require('./getRefundableOrderAmount');
|
|
5
|
+
const getOverPaidOrders = require('./getOverPaidOrders');
|
|
6
|
+
const getMethodLabel = require('./getMethodLabel');
|
|
3
7
|
|
|
4
8
|
const orderActions = (deps = {}) => {
|
|
5
9
|
const actions = {};
|
|
@@ -10,7 +14,11 @@ const orderActions = (deps = {}) => {
|
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
const freezedActions = Object.freeze({
|
|
17
|
+
appendIfOverPaid: appendIfOverPaid(innerDeps),
|
|
13
18
|
getMaxAmountToRefund: getMaxAmountToRefund(innerDeps),
|
|
19
|
+
getRefundableOrderAmount: getRefundableOrderAmount(innerDeps),
|
|
20
|
+
getOverPaidOrders: getOverPaidOrders(innerDeps),
|
|
21
|
+
getMethodLabel: getMethodLabel(innerDeps),
|
|
14
22
|
});
|
|
15
23
|
|
|
16
24
|
Object.keys(freezedActions).forEach(actionName => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkpos/pricing",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.152",
|
|
4
4
|
"description": "Pricing calculator",
|
|
5
5
|
"author": "Dark POS",
|
|
6
6
|
"license": "ISC",
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
"supertest": "^6.2.3",
|
|
55
55
|
"supervisor": "^0.12.0"
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "1dff1f50b45bf68bd10e8d8ef7fe18c2b37c751e"
|
|
58
58
|
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
module.exports = ({ utils, actions }) => {
|
|
2
|
-
const { math } = utils;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Distributes a refund amount across a set of payment invoices.
|
|
6
|
-
*
|
|
7
|
-
* For each invoice, it resolves the live state of each item, computes
|
|
8
|
-
* the refundable amounts (order-independent), and builds the refund entries.
|
|
9
|
-
*
|
|
10
|
-
* Returns an array of refund invoices ready to be persisted — each with a
|
|
11
|
-
* negative amount (the refund) and updated paymentOrderItems.
|
|
12
|
-
*/
|
|
13
|
-
return function applyRefundToInvoices(
|
|
14
|
-
paymentInvoices,
|
|
15
|
-
ordersById,
|
|
16
|
-
totalToRefund
|
|
17
|
-
) {
|
|
18
|
-
let remaining = totalToRefund;
|
|
19
|
-
|
|
20
|
-
return paymentInvoices
|
|
21
|
-
.filter(({ amount }) => amount > 0)
|
|
22
|
-
.map(({ amount, paymentOrderItems, ...rest }) => {
|
|
23
|
-
if (remaining <= 0) return null;
|
|
24
|
-
|
|
25
|
-
const itemStates = (paymentOrderItems || []).map(item =>
|
|
26
|
-
actions.resolveItemState(item, ordersById)
|
|
27
|
-
);
|
|
28
|
-
const refundables = actions.computeRefundables(itemStates, remaining);
|
|
29
|
-
|
|
30
|
-
let invoiceRefundAmount = 0;
|
|
31
|
-
const itemRefunds = itemStates
|
|
32
|
-
.map((state, i) => {
|
|
33
|
-
const refundable = refundables[i];
|
|
34
|
-
if (refundable === 0) return null;
|
|
35
|
-
invoiceRefundAmount = math.add(invoiceRefundAmount, refundable);
|
|
36
|
-
return actions.buildItemRefund(state, refundable);
|
|
37
|
-
})
|
|
38
|
-
.filter(Boolean);
|
|
39
|
-
|
|
40
|
-
remaining = math.sub(remaining, invoiceRefundAmount);
|
|
41
|
-
|
|
42
|
-
if (invoiceRefundAmount <= 0) return null;
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
...rest,
|
|
46
|
-
amount: -invoiceRefundAmount,
|
|
47
|
-
paymentOrderItems: itemRefunds,
|
|
48
|
-
};
|
|
49
|
-
})
|
|
50
|
-
.filter(Boolean);
|
|
51
|
-
};
|
|
52
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module.exports = ({ utils }) => {
|
|
2
|
-
const { math } = utils;
|
|
3
|
-
|
|
4
|
-
return function buildItemRefund(
|
|
5
|
-
{ item, itemTotal, itemTotalPaid, currentStatus },
|
|
6
|
-
refundable
|
|
7
|
-
) {
|
|
8
|
-
const nextTotalPaid = math.sub(itemTotalPaid, refundable);
|
|
9
|
-
return {
|
|
10
|
-
...item,
|
|
11
|
-
total: itemTotal,
|
|
12
|
-
totalPaid: itemTotalPaid,
|
|
13
|
-
amount: -refundable,
|
|
14
|
-
status: {
|
|
15
|
-
...currentStatus,
|
|
16
|
-
paid: {
|
|
17
|
-
...((currentStatus && currentStatus.paid) || {}),
|
|
18
|
-
value: nextTotalPaid >= itemTotal,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
module.exports = ({ utils }) =>
|
|
2
|
-
function getOverPaidAmount({ order }) {
|
|
3
|
-
if (!order) return 0;
|
|
4
|
-
|
|
5
|
-
return (order.items || []).reduce((net, item) => {
|
|
6
|
-
const itemTotalPaid = Number(item.totalPaid || 0);
|
|
7
|
-
if (itemTotalPaid <= 0) return net;
|
|
8
|
-
return utils.math.add(
|
|
9
|
-
net,
|
|
10
|
-
utils.math.sub(itemTotalPaid, Number(item.total || 0))
|
|
11
|
-
);
|
|
12
|
-
}, 0);
|
|
13
|
-
};
|