@stamhoofd/backend 2.106.1 → 2.107.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/index.ts +6 -1
- package/package.json +17 -11
- package/src/boot.ts +28 -22
- package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +89 -0
- package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +30 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +3 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +8 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +109 -109
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +7 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +9 -7
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +7 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +9 -2
- package/src/excel-loaders/payments.ts +5 -5
- package/src/excel-loaders/receivable-balances.ts +7 -7
- package/src/helpers/AdminPermissionChecker.ts +20 -0
- package/src/helpers/BuckarooHelper.ts +1 -1
- package/src/helpers/ServiceFeeHelper.ts +8 -4
- package/src/helpers/StripeHelper.ts +20 -35
- package/src/seeds/1752848561-groups-registration-periods.ts +35 -0
- package/src/services/BalanceItemService.ts +15 -2
- package/src/services/PaymentReallocationService.test.ts +298 -128
- package/src/services/PaymentReallocationService.ts +46 -16
- package/src/services/PaymentService.ts +49 -2
- package/src/services/uitpas/getSocialTariffForEvent.ts +2 -2
- package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +2 -2
- package/src/services/uitpas/registerTicketSales.ts +2 -2
- package/tests/e2e/bundle-discounts.test.ts +415 -391
- package/tests/e2e/documents.test.ts +21 -21
- package/tests/e2e/register.test.ts +93 -93
- package/tests/e2e/stock.test.ts +4 -4
|
@@ -277,6 +277,13 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
if (totalPrice % 100 !== 0) {
|
|
281
|
+
throw new SimpleError({
|
|
282
|
+
code: 'more_than_2_decimal_places',
|
|
283
|
+
message: 'Unexpected total price. The total price should be rounded to maximum 2 decimal places',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
280
287
|
const registrationMemberRelation = new ManyToOneRelation(Member, 'member');
|
|
281
288
|
registrationMemberRelation.foreignKey = 'memberId';
|
|
282
289
|
|
|
@@ -42,7 +42,6 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const returnedModels: BalanceItem[] = [];
|
|
45
|
-
const updateOutstandingBalance: BalanceItem[] = [];
|
|
46
45
|
|
|
47
46
|
// Tracking changes
|
|
48
47
|
const additionalItems: { memberId: string; organizationId: string }[] = [];
|
|
@@ -60,6 +59,9 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
60
59
|
model.createdAt = put.createdAt;
|
|
61
60
|
model.dueAt = put.dueAt;
|
|
62
61
|
model.status = put.status === BalanceItemStatus.Hidden ? BalanceItemStatus.Hidden : BalanceItemStatus.Due;
|
|
62
|
+
model.VATIncluded = put.VATIncluded;
|
|
63
|
+
model.VATPercentage = put.VATPercentage;
|
|
64
|
+
model.VATExcempt = put.VATExcempt;
|
|
63
65
|
|
|
64
66
|
if (put.userId) {
|
|
65
67
|
model.userId = (await this.validateUserId(model, put.userId)).id;
|
|
@@ -113,8 +115,6 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
113
115
|
|
|
114
116
|
await model.save();
|
|
115
117
|
returnedModels.push(model);
|
|
116
|
-
|
|
117
|
-
updateOutstandingBalance.push(model);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
for (const patch of request.body.getPatches()) {
|
|
@@ -186,6 +186,9 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
186
186
|
model.unitPrice = patch.unitPrice ?? model.unitPrice;
|
|
187
187
|
model.amount = patch.amount ?? model.amount;
|
|
188
188
|
model.dueAt = patch.dueAt === undefined ? model.dueAt : patch.dueAt;
|
|
189
|
+
model.VATIncluded = patch.VATIncluded === undefined ? model.VATIncluded : patch.VATIncluded;
|
|
190
|
+
model.VATPercentage = patch.VATPercentage === undefined ? model.VATPercentage : patch.VATPercentage;
|
|
191
|
+
model.VATExcempt = patch.VATExcempt === undefined ? model.VATExcempt : patch.VATExcempt;
|
|
189
192
|
|
|
190
193
|
if ((patch.dueAt !== undefined || patch.unitPrice !== undefined) && model.dueAt && model.price < 0) {
|
|
191
194
|
throw new SimpleError({
|
|
@@ -224,13 +227,12 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
224
227
|
|
|
225
228
|
await model.save();
|
|
226
229
|
returnedModels.push(model);
|
|
227
|
-
|
|
228
|
-
if (patch.unitPrice || patch.amount || patch.status || patch.dueAt !== undefined || patch.memberId || patch.userId) {
|
|
229
|
-
updateOutstandingBalance.push(model);
|
|
230
|
-
}
|
|
231
230
|
}
|
|
232
231
|
});
|
|
233
232
|
|
|
233
|
+
// Update balances before we return the up to date versions
|
|
234
|
+
await BalanceItemService.flushCaches(organization.id);
|
|
235
|
+
|
|
234
236
|
// Reload returnedModels
|
|
235
237
|
const returnedModelsReloaded = await BalanceItem.getByIDs(...returnedModels.map(m => m.id));
|
|
236
238
|
|
|
@@ -73,6 +73,10 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
73
73
|
payment.customer = put.customer;
|
|
74
74
|
payment.type = put.type;
|
|
75
75
|
|
|
76
|
+
if (payment.type === PaymentType.Reallocation) {
|
|
77
|
+
payment.method = PaymentMethod.Unknown;
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
if (payment.method === PaymentMethod.Transfer) {
|
|
77
81
|
if (!put.transferSettings) {
|
|
78
82
|
throw new SimpleError({
|
|
@@ -273,6 +277,9 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
273
277
|
});
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
// Update balances before we return the up to date versions
|
|
281
|
+
await BalanceItemService.flushCaches(organization.id);
|
|
282
|
+
|
|
276
283
|
return new Response(
|
|
277
284
|
await AuthenticatedStructures.paymentsGeneral(changedPayments, true),
|
|
278
285
|
);
|
|
@@ -156,8 +156,15 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
156
156
|
// The order now is valid, the stock is reserved for now (until the payment fails or expires)
|
|
157
157
|
const totalPrice = request.body.totalPrice;
|
|
158
158
|
|
|
159
|
+
if (totalPrice % 100 !== 0) {
|
|
160
|
+
throw new SimpleError({
|
|
161
|
+
code: 'more_than_2_decimal_places',
|
|
162
|
+
message: 'Unexpected total price. The total price should be rounded to maximum 2 decimal places',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
159
166
|
try {
|
|
160
|
-
if (totalPrice
|
|
167
|
+
if (totalPrice === 0) {
|
|
161
168
|
// Force unknown payment method
|
|
162
169
|
order.data.paymentMethod = PaymentMethod.Unknown;
|
|
163
170
|
|
|
@@ -306,7 +313,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
306
313
|
const molliePayment = await mollieClient.payments.create({
|
|
307
314
|
amount: {
|
|
308
315
|
currency: 'EUR',
|
|
309
|
-
value: (totalPrice /
|
|
316
|
+
value: (totalPrice / 10000).toFixed(2), // from 4 decimals to 0 decimals
|
|
310
317
|
},
|
|
311
318
|
method: payment.method == PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method == PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
|
|
312
319
|
testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production',
|
|
@@ -210,7 +210,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<PaymentWithItem>[] {
|
|
|
210
210
|
name: $t(`7f7fdce2-1fcd-44c9-8c98-856aea11ffc3`),
|
|
211
211
|
width: 20,
|
|
212
212
|
getValue: (object: PaymentWithItem) => ({
|
|
213
|
-
value: object.balanceItemPayment.unitPrice /
|
|
213
|
+
value: object.balanceItemPayment.unitPrice / 1_0000,
|
|
214
214
|
style: {
|
|
215
215
|
numberFormat: {
|
|
216
216
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -223,7 +223,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<PaymentWithItem>[] {
|
|
|
223
223
|
name: $t(`6f3104d4-9b8f-4946-8434-77202efae9f0`),
|
|
224
224
|
width: 20,
|
|
225
225
|
getValue: (object: PaymentWithItem) => ({
|
|
226
|
-
value: object.balanceItemPayment.price /
|
|
226
|
+
value: object.balanceItemPayment.price / 1_0000,
|
|
227
227
|
style: {
|
|
228
228
|
numberFormat: {
|
|
229
229
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -254,7 +254,7 @@ function getGeneralColumns(): XlsxTransformerConcreteColumn<PaymentGeneral>[] {
|
|
|
254
254
|
name: $t(`61b7b9cb-287a-4655-bac2-bb2d0b83fe47`),
|
|
255
255
|
width: 10,
|
|
256
256
|
getValue: (object: PaymentGeneralWithStripeAccount) => ({
|
|
257
|
-
value: object.price /
|
|
257
|
+
value: object.price / 1_0000,
|
|
258
258
|
style: {
|
|
259
259
|
numberFormat: {
|
|
260
260
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -361,7 +361,7 @@ function getSettlementColumns(): XlsxTransformerColumn<PaymentGeneral>[] {
|
|
|
361
361
|
width: 18,
|
|
362
362
|
getValue: (object: PaymentGeneralWithStripeAccount) => {
|
|
363
363
|
return {
|
|
364
|
-
value: object.settlement?.amount !== undefined ? (object.settlement?.amount /
|
|
364
|
+
value: object.settlement?.amount !== undefined ? (object.settlement?.amount / 1_0000) : null,
|
|
365
365
|
style: {
|
|
366
366
|
numberFormat: {
|
|
367
367
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -381,7 +381,7 @@ function getStripeColumns(): XlsxTransformerColumn<PaymentGeneral>[] {
|
|
|
381
381
|
width: 16,
|
|
382
382
|
getValue: (object: PaymentGeneralWithStripeAccount) => {
|
|
383
383
|
return {
|
|
384
|
-
value: object.transferFee /
|
|
384
|
+
value: object.transferFee / 1_0000,
|
|
385
385
|
style: {
|
|
386
386
|
numberFormat: {
|
|
387
387
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -108,7 +108,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithIte
|
|
|
108
108
|
name: $t(`7f7fdce2-1fcd-44c9-8c98-856aea11ffc3`),
|
|
109
109
|
width: 20,
|
|
110
110
|
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
111
|
-
value: object.balanceItem.unitPrice /
|
|
111
|
+
value: object.balanceItem.unitPrice / 1_0000,
|
|
112
112
|
style: {
|
|
113
113
|
numberFormat: {
|
|
114
114
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -121,7 +121,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithIte
|
|
|
121
121
|
name: $t(`6f3104d4-9b8f-4946-8434-77202efae9f0`),
|
|
122
122
|
width: 20,
|
|
123
123
|
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
124
|
-
value: object.balanceItem.
|
|
124
|
+
value: object.balanceItem.priceWithVAT / 1_0000,
|
|
125
125
|
style: {
|
|
126
126
|
numberFormat: {
|
|
127
127
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -134,7 +134,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithIte
|
|
|
134
134
|
name: $t(`dc9f65e0-19ce-4908-8830-da48235faa70`),
|
|
135
135
|
width: 20,
|
|
136
136
|
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
137
|
-
value: object.balanceItem.pricePaid /
|
|
137
|
+
value: object.balanceItem.pricePaid / 1_0000,
|
|
138
138
|
style: {
|
|
139
139
|
numberFormat: {
|
|
140
140
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -147,7 +147,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithIte
|
|
|
147
147
|
name: $t(`5c75e9bf-1b64-4d28-a435-6e33247d5170`),
|
|
148
148
|
width: 20,
|
|
149
149
|
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
150
|
-
value: object.balanceItem.pricePending /
|
|
150
|
+
value: object.balanceItem.pricePending / 1_0000,
|
|
151
151
|
style: {
|
|
152
152
|
numberFormat: {
|
|
153
153
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -160,7 +160,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithIte
|
|
|
160
160
|
name: $t(`eb0421f4-6ee9-4d81-b549-2bc4e16c4b63`),
|
|
161
161
|
width: 20,
|
|
162
162
|
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
163
|
-
value: object.balanceItem.priceOpen /
|
|
163
|
+
value: object.balanceItem.priceOpen / 1_0000,
|
|
164
164
|
style: {
|
|
165
165
|
numberFormat: {
|
|
166
166
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -261,7 +261,7 @@ function getGeneralColumns(): XlsxTransformerConcreteColumn<ReceivableBalance>[]
|
|
|
261
261
|
name: $t(`eb0421f4-6ee9-4d81-b549-2bc4e16c4b63`),
|
|
262
262
|
width: 10,
|
|
263
263
|
getValue: (object: ReceivableBalance) => ({
|
|
264
|
-
value: object.amountOpen /
|
|
264
|
+
value: object.amountOpen / 1_0000,
|
|
265
265
|
style: {
|
|
266
266
|
numberFormat: {
|
|
267
267
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -274,7 +274,7 @@ function getGeneralColumns(): XlsxTransformerConcreteColumn<ReceivableBalance>[]
|
|
|
274
274
|
name: $t(`5c75e9bf-1b64-4d28-a435-6e33247d5170`),
|
|
275
275
|
width: 18,
|
|
276
276
|
getValue: (object: ReceivableBalance) => ({
|
|
277
|
-
value: object.amountPending /
|
|
277
|
+
value: object.amountPending / 1_0000,
|
|
278
278
|
style: {
|
|
279
279
|
numberFormat: {
|
|
280
280
|
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
@@ -1504,6 +1504,19 @@ export class AdminPermissionChecker {
|
|
|
1504
1504
|
return cloned;
|
|
1505
1505
|
}
|
|
1506
1506
|
|
|
1507
|
+
/**
|
|
1508
|
+
* Only for creating new members
|
|
1509
|
+
*/
|
|
1510
|
+
filterMemberPut(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob, options: {asUserManager: boolean}) {
|
|
1511
|
+
if (options.asUserManager || STAMHOOFD.userMode === 'platform') {
|
|
1512
|
+
// A user manager cannot choose the member number + in platform mode, nobody can choose the member number
|
|
1513
|
+
data.details.memberNumber = null;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Do not allow setting the security code
|
|
1517
|
+
data.details.securityCode = null;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1507
1520
|
async filterMemberPatch(member: MemberWithRegistrations, data: AutoEncoderPatchType<MemberWithRegistrationsBlob>): Promise<AutoEncoderPatchType<MemberWithRegistrationsBlob>> {
|
|
1508
1521
|
if (!data.details) {
|
|
1509
1522
|
return data;
|
|
@@ -1524,6 +1537,7 @@ export class AdminPermissionChecker {
|
|
|
1524
1537
|
});
|
|
1525
1538
|
}
|
|
1526
1539
|
|
|
1540
|
+
|
|
1527
1541
|
const hasRecordAnswers = !!data.details.recordAnswers;
|
|
1528
1542
|
const hasNotes = data.details.notes !== undefined;
|
|
1529
1543
|
const isSetFinancialSupportTrue = data.details.shouldApplyReducedPrice;
|
|
@@ -1575,6 +1589,12 @@ export class AdminPermissionChecker {
|
|
|
1575
1589
|
|
|
1576
1590
|
const isUserManager = this.isUserManager(member);
|
|
1577
1591
|
|
|
1592
|
+
// Do not allow setting the member number
|
|
1593
|
+
if (isUserManager || STAMHOOFD.userMode === 'platform') {
|
|
1594
|
+
// A user manager cannot choose the member number + in platform mode, nobody can choose the member number
|
|
1595
|
+
delete data.details.memberNumber;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1578
1598
|
if (hasNotes && isUserManager && !(await this.canAccessMember(member, PermissionLevel.Full))) {
|
|
1579
1599
|
throw new SimpleError({
|
|
1580
1600
|
code: 'permission_denied',
|
|
@@ -166,7 +166,7 @@ export class BuckarooHelper {
|
|
|
166
166
|
|
|
167
167
|
const data = {
|
|
168
168
|
Currency: 'EUR',
|
|
169
|
-
AmountDebit: (payment.price /
|
|
169
|
+
AmountDebit: (payment.price / 10000).toFixed(2), // 4 decimals to zero
|
|
170
170
|
Invoice: 'ID ' + payment.id,
|
|
171
171
|
ClientIP: {
|
|
172
172
|
Type: 0, // 0 = ipv4, 1 = ipv6
|
|
@@ -18,16 +18,14 @@ export class ServiceFeeHelper {
|
|
|
18
18
|
if (price === 0 && !minimumFee) {
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
|
-
let fee = Math.round(fixed + Math.max(
|
|
21
|
+
let fee = Math.round((fixed + Math.max(100, price * percentageTimes100 / 100 / 100)) / 100) * 100; // Round to 2 decimals, minimum 1 cent
|
|
22
22
|
if (minimumFee !== null && fee < minimumFee) {
|
|
23
23
|
fee = minimumFee;
|
|
24
24
|
}
|
|
25
25
|
if (maximumFee !== null && fee > maximumFee) {
|
|
26
26
|
fee = maximumFee;
|
|
27
27
|
}
|
|
28
|
-
return
|
|
29
|
-
fee * (100 + vat) / 100,
|
|
30
|
-
); // € 0,21 + 0,2%
|
|
28
|
+
return fee;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
let serviceFee = 0;
|
|
@@ -52,6 +50,12 @@ export class ServiceFeeHelper {
|
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
|
|
53
|
+
// Add VAT
|
|
54
|
+
serviceFee = serviceFee * (100 + vat) / 100;
|
|
55
|
+
|
|
56
|
+
// Round service fee to 2 decimal places
|
|
57
|
+
serviceFee = Math.round(serviceFee / 100) * 100;
|
|
58
|
+
|
|
55
59
|
console.log('Service fee for payment', payment.id, type, 'is', serviceFee);
|
|
56
60
|
if (payment.provider === PaymentProvider.Stripe && payment.stripeAccountId) {
|
|
57
61
|
payment.serviceFeePayout = serviceFee;
|
|
@@ -219,7 +219,7 @@ export class StripeHelper {
|
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
const totalPrice = payment.price;
|
|
222
|
+
const totalPrice = Math.round(payment.price / 100); // Convert from 4 decimal places to 2 decimal places
|
|
223
223
|
|
|
224
224
|
if (totalPrice < 50) {
|
|
225
225
|
throw new SimpleError({
|
|
@@ -252,14 +252,14 @@ export class StripeHelper {
|
|
|
252
252
|
directCharge = true;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
payment.transferFee = fee;
|
|
256
|
-
const serviceFee = payment.serviceFeePayout;
|
|
255
|
+
payment.transferFee = fee * 100; // Convert back to 4 decimal places for storage
|
|
256
|
+
const serviceFee = Math.round(payment.serviceFeePayout / 100);
|
|
257
257
|
|
|
258
258
|
const fullMetadata = {
|
|
259
259
|
...(metadata ?? {}),
|
|
260
260
|
organizationVATNumber: organization.meta.VATNumber,
|
|
261
261
|
transactionFee: fee,
|
|
262
|
-
serviceFee: serviceFee,
|
|
262
|
+
serviceFee: serviceFee, // For historic reasons, this is stored in cents
|
|
263
263
|
};
|
|
264
264
|
|
|
265
265
|
const stripe = StripeHelper.getInstance(directCharge ? stripeAccount.accountId : null);
|
|
@@ -327,41 +327,24 @@ export class StripeHelper {
|
|
|
327
327
|
await paymentIntentModel.save();
|
|
328
328
|
}
|
|
329
329
|
else {
|
|
330
|
-
// Build Stripe line items
|
|
331
|
-
const stripeLineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [];
|
|
332
|
-
let lineItemsPrice = 0;
|
|
333
|
-
for (const item of lineItems) {
|
|
334
|
-
const stripeLineItem = {
|
|
335
|
-
price_data: {
|
|
336
|
-
currency: 'eur',
|
|
337
|
-
unit_amount: item.price,
|
|
338
|
-
product_data: {
|
|
339
|
-
name: item.balanceItem.description,
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
quantity: 1,
|
|
343
|
-
};
|
|
344
|
-
stripeLineItems.push(stripeLineItem);
|
|
345
|
-
lineItemsPrice += item.price;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (lineItemsPrice !== totalPrice) {
|
|
349
|
-
console.error('Total price of line items does not match total price of payment', lineItemsPrice, totalPrice, payment.id);
|
|
350
|
-
throw new SimpleError({
|
|
351
|
-
code: 'invalid_price',
|
|
352
|
-
message: 'De totale prijs van de betaling komt niet overeen met de prijs van de items',
|
|
353
|
-
human: $t(`e66b54d3-70fb-4b40-b3e5-21bc795ba704`),
|
|
354
|
-
statusCode: 500,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
|
|
358
330
|
// Use checkout flow
|
|
359
|
-
const
|
|
331
|
+
const data: Stripe.Checkout.SessionCreateParams = {
|
|
360
332
|
mode: 'payment',
|
|
361
333
|
success_url: redirectUrl,
|
|
362
334
|
cancel_url: cancelUrl,
|
|
363
335
|
payment_method_types: payment.method === PaymentMethod.DirectDebit ? ['sepa_debit'] : ['card'],
|
|
364
|
-
line_items:
|
|
336
|
+
line_items: [
|
|
337
|
+
{
|
|
338
|
+
price_data: {
|
|
339
|
+
currency: 'eur',
|
|
340
|
+
unit_amount: totalPrice,
|
|
341
|
+
product_data: {
|
|
342
|
+
name: statementDescriptor,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
quantity: 1,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
365
348
|
currency: 'eur',
|
|
366
349
|
locale: i18n.language as 'nl',
|
|
367
350
|
payment_intent_data: {
|
|
@@ -385,7 +368,9 @@ export class StripeHelper {
|
|
|
385
368
|
request_three_d_secure: 'challenge', // Force usage of string customer authentication for card payments
|
|
386
369
|
},
|
|
387
370
|
},
|
|
388
|
-
}
|
|
371
|
+
};
|
|
372
|
+
console.log('Creating Stripe session', data);
|
|
373
|
+
const session = await stripe.checkout.sessions.create(data);
|
|
389
374
|
console.log('Stripe session', session);
|
|
390
375
|
|
|
391
376
|
if (!session.url) {
|
|
@@ -54,6 +54,8 @@ async function start(dryRun: boolean) {
|
|
|
54
54
|
|
|
55
55
|
async function cleanupGroup(group: Group, dryRun: boolean) {
|
|
56
56
|
group.settings.cycleSettings = new Map();
|
|
57
|
+
group.settings.preventPreviousGroupIds = [];
|
|
58
|
+
group.settings.requirePreviousGroupIds = [];
|
|
57
59
|
group.cycle = cycleIfMigrated;
|
|
58
60
|
if (group.status === GroupStatus.Archived) {
|
|
59
61
|
group.status = GroupStatus.Closed;
|
|
@@ -157,6 +159,39 @@ async function migrateGroups({ groups, organization, periodSpan }: { groups: Gro
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
// migrate require and prevent group ids
|
|
163
|
+
for (const originalGroup of groups) {
|
|
164
|
+
// migrate requirePreviousGroupIds
|
|
165
|
+
const requirePreviousGroupIds = originalGroup.settings.requirePreviousGroupIds;
|
|
166
|
+
const requireIdSet = new Set<string>();
|
|
167
|
+
|
|
168
|
+
for (const groupIdToGetPreviousGroupOf of requirePreviousGroupIds) {
|
|
169
|
+
const previousGroups = groupMap.get(groupIdToGetPreviousGroupOf);
|
|
170
|
+
if (previousGroups && previousGroups.length > 0) {
|
|
171
|
+
// previous groups are already ordered
|
|
172
|
+
const firstPreviousGroup = previousGroups[0];
|
|
173
|
+
requireIdSet.add(firstPreviousGroup.id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
originalGroup.settings.requireGroupIds = [...requireIdSet];
|
|
178
|
+
|
|
179
|
+
// migrate preventPreviousGroupIds
|
|
180
|
+
const preventPreviousGroupIds = originalGroup.settings.preventPreviousGroupIds;
|
|
181
|
+
const preventIdSet = new Set<string>();
|
|
182
|
+
|
|
183
|
+
for (const groupIdToGetPreviousGroupOf of preventPreviousGroupIds) {
|
|
184
|
+
const previousGroups = groupMap.get(groupIdToGetPreviousGroupOf);
|
|
185
|
+
if (previousGroups && previousGroups.length > 0) {
|
|
186
|
+
// previous groups are already ordered
|
|
187
|
+
const firstPreviousGroup = previousGroups[0];
|
|
188
|
+
preventIdSet.add(firstPreviousGroup.id);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
originalGroup.settings.preventGroupIds = [...preventIdSet];
|
|
193
|
+
}
|
|
194
|
+
|
|
160
195
|
// #region create categories for current period
|
|
161
196
|
const nonArchivedGroupIds = [...new Set(groups.filter(g => g.status !== GroupStatus.Archived).map(g => g.id))];
|
|
162
197
|
const newCategoriesData = organization.meta.categories.map((c: GroupCategory) => {
|
|
@@ -70,9 +70,8 @@ export const BalanceItemService = {
|
|
|
70
70
|
// status, unitPrice, dueAt, amount
|
|
71
71
|
if (
|
|
72
72
|
'status' in event.changedFields
|
|
73
|
-
|| 'unitPrice' in event.changedFields
|
|
74
73
|
|| 'dueAt' in event.changedFields
|
|
75
|
-
|| '
|
|
74
|
+
|| 'priceTotal' in event.changedFields
|
|
76
75
|
|| 'memberId' in event.changedFields
|
|
77
76
|
|| 'userId' in event.changedFields
|
|
78
77
|
|| 'payingOrganizationId' in event.changedFields
|
|
@@ -268,6 +267,13 @@ export const BalanceItemService = {
|
|
|
268
267
|
await order.undoPaid(payment, organization);
|
|
269
268
|
}
|
|
270
269
|
}
|
|
270
|
+
|
|
271
|
+
// If a rounded payment was canceled, make sure the balance item is hidden again (will become visible again when marking paid)
|
|
272
|
+
if (this.type === BalanceItemType.Rounding && balanceItem.status !== BalanceItemStatus.Hidden) {
|
|
273
|
+
// Mark undue
|
|
274
|
+
balanceItem.status = BalanceItemStatus.Hidden;
|
|
275
|
+
await balanceItem.save();
|
|
276
|
+
}
|
|
271
277
|
},
|
|
272
278
|
|
|
273
279
|
async markFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
|
|
@@ -283,6 +289,13 @@ export const BalanceItemService = {
|
|
|
283
289
|
}
|
|
284
290
|
}
|
|
285
291
|
}
|
|
292
|
+
|
|
293
|
+
// If a rounded payment was canceled, make sure the balance item is hidden again (will become visible again when marking paid)
|
|
294
|
+
if (this.type === BalanceItemType.Rounding && balanceItem.status !== BalanceItemStatus.Hidden) {
|
|
295
|
+
// Mark undue
|
|
296
|
+
balanceItem.status = BalanceItemStatus.Hidden;
|
|
297
|
+
await balanceItem.save();
|
|
298
|
+
}
|
|
286
299
|
},
|
|
287
300
|
|
|
288
301
|
async undoFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
|