@stamhoofd/backend 2.143.1 → 2.145.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/package.json +19 -19
- package/src/crons/fake-settlements.test.ts +13 -11
- package/src/crons/fake-settlements.ts +22 -38
- package/src/crons/mollie-chargebacks.ts +31 -29
- package/src/crons/mollie-refunds.ts +61 -59
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +16 -5
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +210 -4
- package/src/endpoints/auth/CreateTokenEndpoint.ts +9 -20
- package/src/endpoints/auth/DeleteTokenEndpoint.test.ts +29 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +2 -1
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -11
- package/src/endpoints/auth/MFA.security.test.ts +9 -7
- package/src/endpoints/auth/MFA.test.ts +42 -15
- package/src/endpoints/auth/PatchUserEndpoint.ts +3 -2
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +5 -4
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +4 -3
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +6 -5
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +5 -4
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +5 -4
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +2 -1
- package/src/endpoints/global/files/UploadFile.test.ts +2 -1
- package/src/endpoints/global/files/UploadFile.ts +53 -25
- package/src/endpoints/global/files/UploadImage.test.ts +2 -1
- package/src/endpoints/global/files/UploadImage.ts +32 -6
- package/src/endpoints/global/files/upload-security.test.ts +3 -2
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +4 -3
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +26 -25
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +57 -56
- package/src/endpoints/global/members/SendMemberSecurityCodeEndpoint.test.ts +4 -3
- package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +6 -4
- package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +6 -6
- package/src/endpoints/global/platform/SignOutPlatformAdminsEndpoint.test.ts +9 -8
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +14 -13
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +24 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +4 -3
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +2 -1
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +20 -19
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +0 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +23 -22
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +4 -4
- package/src/endpoints/organization/dashboard/payments/GetPaymentBreakdownEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.test.ts +4 -3
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +7 -6
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +9 -8
- package/src/endpoints/organization/dashboard/settlements/SettlementsExportEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/settlements/SettlementsSyncEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +5 -4
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.test.ts +3 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +6 -5
- package/src/endpoints/organization/dashboard/users/SignOutOrganizationAdminsEndpoint.test.ts +16 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.test.ts +2 -1
- package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +2 -1
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -3
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -5
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +94 -8
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +18 -18
- package/src/helpers/ApplicationFeeInvoicer.ts +1 -1
- package/src/helpers/Context.ts +14 -21
- package/src/helpers/ContextStorage.ts +5 -0
- package/src/helpers/MollieSettlementSync.test.ts +94 -41
- package/src/helpers/MollieSettlementSync.ts +174 -78
- package/src/helpers/StripeSettlementSync.test.ts +0 -85
- package/src/helpers/StripeSettlementSync.ts +47 -70
- package/src/helpers/TwoFactorHelper.ts +21 -22
- package/src/helpers/streamForBreakdown.ts +1 -1
- package/src/services/AdminSessionService.ts +5 -3
- package/src/services/InvoiceService.ts +2 -2
- package/src/services/MollieService.ts +168 -126
- package/src/services/PaymentService.ts +2 -2
- package/src/services/SSOService.test.ts +25 -1
- package/src/services/SSOService.ts +17 -6
- package/src/services/SessionService.test.ts +319 -0
- package/src/services/SessionService.ts +357 -0
- package/src/services/SettlementService.test.ts +13 -85
- package/src/services/SettlementService.ts +5 -113
- package/src/services/WebshopCrowdfundingService.test.ts +4 -3
- package/src/sql-filters/balance-items.ts +10 -0
- package/src/sql-filters/registrations.ts +59 -1
- package/tests/actions/patchUserMember.ts +2 -1
- package/tests/e2e/api-rate-limits.test.ts +2 -1
- package/tests/e2e/bundle-discounts.test.ts +4 -3
- package/tests/e2e/charge-members.test.ts +6 -5
- package/tests/e2e/documents.test.ts +4 -3
- package/tests/e2e/private-files.test.ts +9 -8
- package/tests/e2e/register.test.ts +4 -3
- package/tests/e2e/stock.test.ts +4 -3
- package/tests/e2e/tickets.test.ts +4 -3
- package/tests/helpers/ExportSlice.ts +2 -1
- package/tests/helpers/MollieMocker.ts +173 -38
- package/tests/init/initAdmin.ts +2 -1
- package/tests/init/initPlatformAdmin.ts +2 -1
- package/src/helpers/ViesHelper.test.ts +0 -401
- package/src/helpers/ViesHelper.ts +0 -215
- package/src/services/PeppolDirectoryService.test.ts +0 -139
- package/src/services/PeppolDirectoryService.ts +0 -111
|
@@ -25,6 +25,9 @@ export type MollieMockPayment = {
|
|
|
25
25
|
mandateId: string | null;
|
|
26
26
|
isCancelable: boolean;
|
|
27
27
|
details: Record<string, unknown> | null;
|
|
28
|
+
description?: string;
|
|
29
|
+
profileId?: string | null;
|
|
30
|
+
createdAt?: string;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
export type MollieMockMandate = {
|
|
@@ -53,13 +56,6 @@ export type MollieMockRefund = {
|
|
|
53
56
|
metadata: Record<string, unknown> | null;
|
|
54
57
|
};
|
|
55
58
|
|
|
56
|
-
export type MollieMockSettlementCost = {
|
|
57
|
-
description: string;
|
|
58
|
-
method: string | null;
|
|
59
|
-
amountNet: { currency: string; value: string };
|
|
60
|
-
amountVat: { currency: string; value: string } | null;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
59
|
export type MollieMockSettlement = {
|
|
64
60
|
id: string;
|
|
65
61
|
reference: string;
|
|
@@ -68,10 +64,6 @@ export type MollieMockSettlement = {
|
|
|
68
64
|
createdAt: string;
|
|
69
65
|
/** null for the still-open settlement, a date once it has been paid out */
|
|
70
66
|
settledAt: string | null;
|
|
71
|
-
/** id of the invoice Mollie created for the settlement costs, null until it exists */
|
|
72
|
-
invoiceId: string | null;
|
|
73
|
-
/** Mollie's own costs per period, keyed year → month */
|
|
74
|
-
periods: Record<string, Record<string, { costs: MollieMockSettlementCost[]; invoiceId?: string | null }>>;
|
|
75
67
|
/** Mollie payment ids (tr_...) settled in this settlement */
|
|
76
68
|
paymentIds: string[];
|
|
77
69
|
/** Mollie refund ids (re_...) settled in this settlement */
|
|
@@ -80,14 +72,21 @@ export type MollieMockSettlement = {
|
|
|
80
72
|
chargebackIds: string[];
|
|
81
73
|
};
|
|
82
74
|
|
|
75
|
+
export type MollieMockBalanceTransaction = {
|
|
76
|
+
id: string;
|
|
77
|
+
type: string;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
deductions: { currency: string; value: string } | null;
|
|
80
|
+
deductionDetails: { fees?: { currency: string; value: string } } | null;
|
|
81
|
+
context: Record<string, string> | null;
|
|
82
|
+
};
|
|
83
|
+
|
|
83
84
|
const MOLLIE_CHECKOUT_URL = 'https://molliecheckout/';
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
87
|
* Mirrors the StripeMocker (tests/helpers/StripeMocker.ts) but for Mollie.
|
|
87
88
|
*
|
|
88
|
-
* The Mollie
|
|
89
|
-
* which uses Node's http/https module under the hood. nock can therefore intercept every call to
|
|
90
|
-
* https://api.mollie.com - exactly like the StripeMocker does for Stripe.
|
|
89
|
+
* The Mollie client sends every API call to https://api.mollie.com, which nock intercepts here.
|
|
91
90
|
*
|
|
92
91
|
* Usage:
|
|
93
92
|
* const mollieMocker = new MollieMocker();
|
|
@@ -104,6 +103,7 @@ export class MollieMocker {
|
|
|
104
103
|
chargebacks: MollieMockChargeback[] = [];
|
|
105
104
|
refunds: MollieMockRefund[] = [];
|
|
106
105
|
settlements: MollieMockSettlement[] = [];
|
|
106
|
+
balanceTransactions: MollieMockBalanceTransaction[] = [];
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* Cap the settlements list page size below the requested limit, to exercise pagination
|
|
@@ -111,8 +111,30 @@ export class MollieMocker {
|
|
|
111
111
|
*/
|
|
112
112
|
settlementsPageSize: number | null = null;
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Same, for the balance transactions list.
|
|
116
|
+
*/
|
|
117
|
+
balanceTransactionsPageSize: number | null = null;
|
|
118
|
+
|
|
114
119
|
#forceFailure = false;
|
|
115
120
|
|
|
121
|
+
// The SDK validates responses with zod: these links are required on most resources
|
|
122
|
+
#dashboardLink = { href: 'https://www.mollie.com/dashboard', type: 'text/html' };
|
|
123
|
+
#documentationLink = { href: 'https://docs.mollie.com', type: 'text/html' };
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A Mollie error body, complete enough for the SDK to parse it as an ErrorResponse.
|
|
127
|
+
*/
|
|
128
|
+
#errorBody(status: number, title: string, detail: string, field?: string) {
|
|
129
|
+
return {
|
|
130
|
+
status,
|
|
131
|
+
title,
|
|
132
|
+
detail,
|
|
133
|
+
field,
|
|
134
|
+
_links: { documentation: this.#documentationLink },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
116
138
|
reset() {
|
|
117
139
|
this.payments = [];
|
|
118
140
|
this.customers = [];
|
|
@@ -120,7 +142,9 @@ export class MollieMocker {
|
|
|
120
142
|
this.chargebacks = [];
|
|
121
143
|
this.refunds = [];
|
|
122
144
|
this.settlements = [];
|
|
145
|
+
this.balanceTransactions = [];
|
|
123
146
|
this.settlementsPageSize = null;
|
|
147
|
+
this.balanceTransactionsPageSize = null;
|
|
124
148
|
this.#forceFailure = false;
|
|
125
149
|
}
|
|
126
150
|
|
|
@@ -162,15 +186,18 @@ export class MollieMocker {
|
|
|
162
186
|
const interceptor = nock('https://api.mollie.com').persist();
|
|
163
187
|
|
|
164
188
|
interceptor.get(matcher).reply((uri) => {
|
|
165
|
-
|
|
189
|
+
const [status, body] = this.#handle('GET', uri, undefined);
|
|
190
|
+
return [status, body, { 'content-type': 'application/hal+json' }];
|
|
166
191
|
});
|
|
167
192
|
|
|
168
193
|
interceptor.post(matcher).reply((uri, body) => {
|
|
169
|
-
|
|
194
|
+
const [status, responseBody] = this.#handle('POST', uri, body);
|
|
195
|
+
return [status, responseBody, { 'content-type': 'application/hal+json' }];
|
|
170
196
|
});
|
|
171
197
|
|
|
172
198
|
interceptor.delete(matcher).reply((uri, body) => {
|
|
173
|
-
|
|
199
|
+
const [status, responseBody] = this.#handle('DELETE', uri, body);
|
|
200
|
+
return [status, responseBody, { 'content-type': 'application/hal+json' }];
|
|
174
201
|
});
|
|
175
202
|
}
|
|
176
203
|
|
|
@@ -238,10 +265,19 @@ export class MollieMocker {
|
|
|
238
265
|
return [200, this.#onboarding()];
|
|
239
266
|
}
|
|
240
267
|
|
|
268
|
+
if (parts[0] === 'onboarding' && method === 'POST') {
|
|
269
|
+
return [204];
|
|
270
|
+
}
|
|
271
|
+
|
|
241
272
|
if (parts[0] === 'chargebacks' && method === 'GET') {
|
|
242
273
|
return this.#listResource('chargebacks', this.chargebacks.map(c => this.#chargebackResource(c)));
|
|
243
274
|
}
|
|
244
275
|
|
|
276
|
+
// balance transactions (drives the transaction fee part of the settlements sync)
|
|
277
|
+
if (parts[0] === 'balances' && method === 'GET' && parts.length === 3 && parts[2] === 'transactions') {
|
|
278
|
+
return this.#listBalanceTransactions(uri);
|
|
279
|
+
}
|
|
280
|
+
|
|
245
281
|
// settlements + nested payments/refunds (drives the settlements sync)
|
|
246
282
|
if (parts[0] === 'settlements' && method === 'GET') {
|
|
247
283
|
if (parts.length === 1) {
|
|
@@ -283,6 +319,9 @@ export class MollieMocker {
|
|
|
283
319
|
mandateId: body.mandateId ?? null,
|
|
284
320
|
isCancelable: true,
|
|
285
321
|
details: null,
|
|
322
|
+
description: body.description ?? '',
|
|
323
|
+
profileId: body.profileId ?? null,
|
|
324
|
+
createdAt: new Date().toISOString(),
|
|
286
325
|
};
|
|
287
326
|
this.payments.push(payment);
|
|
288
327
|
return [201, this.#paymentResource(payment)];
|
|
@@ -291,7 +330,7 @@ export class MollieMocker {
|
|
|
291
330
|
#getPayment(id: string): [number, unknown] {
|
|
292
331
|
const payment = this.payments.find(p => p.id === id);
|
|
293
332
|
if (!payment) {
|
|
294
|
-
return [404,
|
|
333
|
+
return [404, this.#errorBody(404, 'Not Found', 'No payment exists with token ' + id)];
|
|
295
334
|
}
|
|
296
335
|
return [200, this.#paymentResource(payment)];
|
|
297
336
|
}
|
|
@@ -299,7 +338,7 @@ export class MollieMocker {
|
|
|
299
338
|
#cancelPayment(id: string): [number, unknown] {
|
|
300
339
|
const payment = this.payments.find(p => p.id === id);
|
|
301
340
|
if (!payment) {
|
|
302
|
-
return [404,
|
|
341
|
+
return [404, this.#errorBody(404, 'Not Found', 'No payment exists with token ' + id)];
|
|
303
342
|
}
|
|
304
343
|
payment.status = 'canceled';
|
|
305
344
|
return [200, this.#paymentResource(payment)];
|
|
@@ -318,9 +357,14 @@ export class MollieMocker {
|
|
|
318
357
|
mandateId: payment.mandateId ?? undefined,
|
|
319
358
|
details: payment.details ?? undefined,
|
|
320
359
|
metadata: payment.internalPaymentId ? { paymentId: payment.internalPaymentId } : undefined,
|
|
360
|
+
description: payment.description ?? '',
|
|
361
|
+
profileId: payment.profileId ?? 'pfl_test',
|
|
362
|
+
createdAt: payment.createdAt ?? new Date().toISOString(),
|
|
321
363
|
_links: {
|
|
322
364
|
self: { href: 'https://api.mollie.com/v2/payments/' + payment.id, type: 'application/hal+json' },
|
|
323
365
|
checkout: { href: MOLLIE_CHECKOUT_URL, type: 'text/html' },
|
|
366
|
+
dashboard: this.#dashboardLink,
|
|
367
|
+
documentation: this.#documentationLink,
|
|
324
368
|
},
|
|
325
369
|
};
|
|
326
370
|
}
|
|
@@ -347,9 +391,15 @@ export class MollieMocker {
|
|
|
347
391
|
id,
|
|
348
392
|
mode: 'test',
|
|
349
393
|
name: body.name ?? 'Test',
|
|
350
|
-
email: body.email ??
|
|
351
|
-
|
|
352
|
-
|
|
394
|
+
email: body.email ?? null,
|
|
395
|
+
locale: null,
|
|
396
|
+
metadata: body.metadata ?? null,
|
|
397
|
+
createdAt: new Date().toISOString(),
|
|
398
|
+
_links: {
|
|
399
|
+
self: { href: 'https://api.mollie.com/v2/customers/' + id, type: 'application/hal+json' },
|
|
400
|
+
dashboard: this.#dashboardLink,
|
|
401
|
+
documentation: this.#documentationLink,
|
|
402
|
+
},
|
|
353
403
|
};
|
|
354
404
|
}
|
|
355
405
|
|
|
@@ -395,8 +445,14 @@ export class MollieMocker {
|
|
|
395
445
|
method: mandate.method,
|
|
396
446
|
details: mandate.details,
|
|
397
447
|
customerId: mandate.customerId,
|
|
448
|
+
mandateReference: null,
|
|
449
|
+
signatureDate: mandate.createdAt.substring(0, 10),
|
|
398
450
|
createdAt: mandate.createdAt,
|
|
399
|
-
_links: {
|
|
451
|
+
_links: {
|
|
452
|
+
self: { href: 'https://api.mollie.com/v2/customers/' + mandate.customerId + '/mandates/' + mandate.id, type: 'application/hal+json' },
|
|
453
|
+
customer: { href: 'https://api.mollie.com/v2/customers/' + mandate.customerId, type: 'application/hal+json' },
|
|
454
|
+
documentation: this.#documentationLink,
|
|
455
|
+
},
|
|
400
456
|
};
|
|
401
457
|
}
|
|
402
458
|
|
|
@@ -410,8 +466,16 @@ export class MollieMocker {
|
|
|
410
466
|
mode: 'test',
|
|
411
467
|
name: 'Stamhoofd',
|
|
412
468
|
website: 'https://www.stamhoofd.be',
|
|
469
|
+
email: 'hallo@stamhoofd.be',
|
|
470
|
+
phone: '+32499999999',
|
|
471
|
+
businessCategory: 'SOCIAL_ASSOCIATIONS',
|
|
413
472
|
status: 'verified',
|
|
414
|
-
|
|
473
|
+
createdAt: new Date().toISOString(),
|
|
474
|
+
_links: {
|
|
475
|
+
self: { href: 'https://api.mollie.com/v2/profiles/' + id, type: 'application/hal+json' },
|
|
476
|
+
dashboard: this.#dashboardLink,
|
|
477
|
+
documentation: this.#documentationLink,
|
|
478
|
+
},
|
|
415
479
|
};
|
|
416
480
|
}
|
|
417
481
|
|
|
@@ -422,6 +486,7 @@ export class MollieMocker {
|
|
|
422
486
|
status: 'completed',
|
|
423
487
|
canReceivePayments: true,
|
|
424
488
|
canReceiveSettlements: true,
|
|
489
|
+
signedUpAt: new Date().toISOString(),
|
|
425
490
|
_links: { self: { href: 'https://api.mollie.com/v2/onboarding/me', type: 'application/hal+json' } },
|
|
426
491
|
};
|
|
427
492
|
}
|
|
@@ -450,7 +515,11 @@ export class MollieMocker {
|
|
|
450
515
|
amount: chargeback.amount,
|
|
451
516
|
paymentId: chargeback.paymentId,
|
|
452
517
|
createdAt: chargeback.createdAt,
|
|
453
|
-
_links: {
|
|
518
|
+
_links: {
|
|
519
|
+
self: { href: 'https://api.mollie.com/v2/payments/' + chargeback.paymentId + '/chargebacks/' + chargeback.id, type: 'application/hal+json' },
|
|
520
|
+
payment: { href: 'https://api.mollie.com/v2/payments/' + chargeback.paymentId, type: 'application/hal+json' },
|
|
521
|
+
documentation: this.#documentationLink,
|
|
522
|
+
},
|
|
454
523
|
};
|
|
455
524
|
}
|
|
456
525
|
|
|
@@ -473,20 +542,20 @@ export class MollieMocker {
|
|
|
473
542
|
#createRefund(paymentId: string, body: Record<string, any>): [number, unknown] {
|
|
474
543
|
const payment = this.payments.find(p => p.id === paymentId);
|
|
475
544
|
if (!payment) {
|
|
476
|
-
return [404,
|
|
545
|
+
return [404, this.#errorBody(404, 'Not Found', 'No payment exists with token ' + paymentId)];
|
|
477
546
|
}
|
|
478
547
|
|
|
479
548
|
if (payment.status !== 'paid') {
|
|
480
|
-
return [422,
|
|
549
|
+
return [422, this.#errorBody(422, 'Unprocessable Entity', 'The payment is not paid; you can only refund paid payments')];
|
|
481
550
|
}
|
|
482
551
|
|
|
483
552
|
const value = parseFloat(String(body.amount?.value ?? '0'));
|
|
484
553
|
if (!(value > 0)) {
|
|
485
|
-
return [422,
|
|
554
|
+
return [422, this.#errorBody(422, 'Unprocessable Entity', 'The amount is invalid', 'amount')];
|
|
486
555
|
}
|
|
487
556
|
|
|
488
557
|
if (value > this.getRemainingAmount(payment) + 0.000001) {
|
|
489
|
-
return [422,
|
|
558
|
+
return [422, this.#errorBody(422, 'Unprocessable Entity', 'The amount is higher than the remaining payment amount', 'amount')];
|
|
490
559
|
}
|
|
491
560
|
|
|
492
561
|
const refund: MollieMockRefund = {
|
|
@@ -505,7 +574,7 @@ export class MollieMocker {
|
|
|
505
574
|
#getRefund(paymentId: string, refundId: string): [number, unknown] {
|
|
506
575
|
const refund = this.refunds.find(r => r.id === refundId && r.paymentId === paymentId);
|
|
507
576
|
if (!refund) {
|
|
508
|
-
return [404,
|
|
577
|
+
return [404, this.#errorBody(404, 'Not Found', 'No refund exists with token ' + refundId)];
|
|
509
578
|
}
|
|
510
579
|
return [200, this.#refundResource(refund)];
|
|
511
580
|
}
|
|
@@ -563,8 +632,12 @@ export class MollieMocker {
|
|
|
563
632
|
description: refund.description,
|
|
564
633
|
paymentId: refund.paymentId,
|
|
565
634
|
createdAt: refund.createdAt,
|
|
566
|
-
metadata: refund.metadata ??
|
|
567
|
-
_links: {
|
|
635
|
+
metadata: refund.metadata ?? null,
|
|
636
|
+
_links: {
|
|
637
|
+
self: { href: 'https://api.mollie.com/v2/payments/' + refund.paymentId + '/refunds/' + refund.id, type: 'application/hal+json' },
|
|
638
|
+
payment: { href: 'https://api.mollie.com/v2/payments/' + refund.paymentId, type: 'application/hal+json' },
|
|
639
|
+
documentation: this.#documentationLink,
|
|
640
|
+
},
|
|
568
641
|
};
|
|
569
642
|
}
|
|
570
643
|
|
|
@@ -574,7 +647,7 @@ export class MollieMocker {
|
|
|
574
647
|
* Register a settled (paid out) settlement that groups the given payments and refunds.
|
|
575
648
|
* Used to drive the MollieSettlementSync walk.
|
|
576
649
|
*/
|
|
577
|
-
createSettlement(options: { payments?: MollieMockPayment[]; refunds?: MollieMockRefund[]; chargebacks?: MollieMockChargeback[]; value?: string; settledAt?: Date
|
|
650
|
+
createSettlement(options: { payments?: MollieMockPayment[]; refunds?: MollieMockRefund[]; chargebacks?: MollieMockChargeback[]; value?: string; settledAt?: Date } = {}): MollieMockSettlement {
|
|
578
651
|
const settlement: MollieMockSettlement = {
|
|
579
652
|
id: this.createId('stl'),
|
|
580
653
|
reference: '1234567.' + (this.settlements.length + 1).toString().padStart(4, '0') + '.01',
|
|
@@ -582,8 +655,6 @@ export class MollieMocker {
|
|
|
582
655
|
amount: { currency: 'EUR', value: options.value ?? '0.00' },
|
|
583
656
|
createdAt: new Date().toISOString(),
|
|
584
657
|
settledAt: (options.settledAt ?? new Date()).toISOString(),
|
|
585
|
-
invoiceId: options.invoiceId ?? null,
|
|
586
|
-
periods: options.periods ?? {},
|
|
587
658
|
paymentIds: (options.payments ?? []).map(p => p.id),
|
|
588
659
|
refundIds: (options.refunds ?? []).map(r => r.id),
|
|
589
660
|
chargebackIds: (options.chargebacks ?? []).map(c => c.id),
|
|
@@ -617,7 +688,7 @@ export class MollieMocker {
|
|
|
617
688
|
const from = query.get('from');
|
|
618
689
|
const startIndex = from ? sorted.findIndex(s => s.id === from) : 0;
|
|
619
690
|
if (startIndex === -1) {
|
|
620
|
-
return [404,
|
|
691
|
+
return [404, this.#errorBody(404, 'Not Found', 'No settlement exists with token ' + from)];
|
|
621
692
|
}
|
|
622
693
|
|
|
623
694
|
const page = sorted.slice(startIndex, startIndex + limit);
|
|
@@ -637,12 +708,76 @@ export class MollieMocker {
|
|
|
637
708
|
amount: settlement.amount,
|
|
638
709
|
createdAt: settlement.createdAt,
|
|
639
710
|
settledAt: settlement.settledAt ?? null,
|
|
640
|
-
invoiceId: settlement.invoiceId,
|
|
641
|
-
periods: settlement.periods,
|
|
642
711
|
_links: { self: { href: 'https://api.mollie.com/v2/settlements/' + settlement.id, type: 'application/hal+json' } },
|
|
643
712
|
};
|
|
644
713
|
}
|
|
645
714
|
|
|
715
|
+
// ---- Balance transactions ---------------------------------------------
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Register the balance transaction of a payment, refund or chargeback, carrying the fee Mollie
|
|
719
|
+
* deducted for it. `fee` and `deductions` are positive euro values, stored negated like Mollie
|
|
720
|
+
* reports them. Pass only `deductions` for a transaction without the deductionDetails breakdown.
|
|
721
|
+
*/
|
|
722
|
+
createBalanceTransaction(options: {
|
|
723
|
+
type: 'payment' | 'refund' | 'chargeback';
|
|
724
|
+
entryId: string;
|
|
725
|
+
fee?: string;
|
|
726
|
+
deductions?: string;
|
|
727
|
+
createdAt?: Date;
|
|
728
|
+
}): MollieMockBalanceTransaction {
|
|
729
|
+
const contextKey = options.type === 'payment' ? 'paymentId' : options.type === 'refund' ? 'refundId' : 'chargebackId';
|
|
730
|
+
const deductions = options.deductions ?? options.fee;
|
|
731
|
+
|
|
732
|
+
const transaction: MollieMockBalanceTransaction = {
|
|
733
|
+
id: this.createId('baltr'),
|
|
734
|
+
type: options.type,
|
|
735
|
+
createdAt: (options.createdAt ?? new Date()).toISOString(),
|
|
736
|
+
deductions: deductions ? { currency: 'EUR', value: '-' + deductions } : null,
|
|
737
|
+
deductionDetails: options.fee ? { fees: { currency: 'EUR', value: '-' + options.fee } } : null,
|
|
738
|
+
context: { [contextKey]: options.entryId },
|
|
739
|
+
};
|
|
740
|
+
this.balanceTransactions.push(transaction);
|
|
741
|
+
return transaction;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Mollie lists balance transactions newest-first, paginated like the settlements list.
|
|
746
|
+
*/
|
|
747
|
+
#listBalanceTransactions(uri: string): [number, unknown] {
|
|
748
|
+
const query = new URLSearchParams(uri.split('?')[1] ?? '');
|
|
749
|
+
|
|
750
|
+
const sorted = this.balanceTransactions.slice().sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
751
|
+
|
|
752
|
+
const requestedLimit = parseInt(query.get('limit') ?? '250');
|
|
753
|
+
const limit = Math.min(requestedLimit, this.balanceTransactionsPageSize ?? requestedLimit);
|
|
754
|
+
|
|
755
|
+
const from = query.get('from');
|
|
756
|
+
const startIndex = from ? sorted.findIndex(t => t.id === from) : 0;
|
|
757
|
+
if (startIndex === -1) {
|
|
758
|
+
return [404, this.#errorBody(404, 'Not Found', 'No balance transaction exists with token ' + from)];
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const page = sorted.slice(startIndex, startIndex + limit);
|
|
762
|
+
const nextItem = sorted[startIndex + limit];
|
|
763
|
+
|
|
764
|
+
return this.#listResource('balance_transactions', page.map(t => this.#balanceTransactionResource(t)), {
|
|
765
|
+
next: nextItem ? 'https://api.mollie.com/v2/balances/primary/transactions?limit=' + requestedLimit + '&from=' + nextItem.id : null,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
#balanceTransactionResource(transaction: MollieMockBalanceTransaction) {
|
|
770
|
+
return {
|
|
771
|
+
resource: 'balance-transaction',
|
|
772
|
+
id: transaction.id,
|
|
773
|
+
type: transaction.type,
|
|
774
|
+
createdAt: transaction.createdAt,
|
|
775
|
+
deductions: transaction.deductions,
|
|
776
|
+
deductionDetails: transaction.deductionDetails,
|
|
777
|
+
context: transaction.context,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
646
781
|
// ---- List helper ------------------------------------------------------
|
|
647
782
|
|
|
648
783
|
#listResource(binderName: string, items: unknown[], options: { next?: string | null } = {}): [number, unknown] {
|
package/tests/init/initAdmin.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Token, UserFactory } from '@stamhoofd/models';
|
|
|
3
3
|
import type { AccessRight } from '@stamhoofd/structures';
|
|
4
4
|
import { PermissionLevel, PermissionRole, Permissions } from '@stamhoofd/structures';
|
|
5
5
|
import { initPermissionRole } from './initPermissionRole.js';
|
|
6
|
+
import { SessionService } from '../../src/services/SessionService.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* You cannot assign access rights directy to a user, but it can be done using roles. So when setting accessRights, a role wil be created and assigned to the user.
|
|
@@ -25,6 +26,6 @@ export async function initAdmin({ organization, permissions, accessRights }: { o
|
|
|
25
26
|
permissions,
|
|
26
27
|
}).create();
|
|
27
28
|
|
|
28
|
-
const adminToken = await
|
|
29
|
+
const adminToken = await SessionService.createSession(admin);
|
|
29
30
|
return { admin, adminToken };
|
|
30
31
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Token, UserFactory } from '@stamhoofd/models';
|
|
2
2
|
import { PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
3
|
+
import { SessionService } from '../../src/services/SessionService.js';
|
|
3
4
|
|
|
4
5
|
export async function initPlatformAdmin() {
|
|
5
6
|
const admin = await new UserFactory({
|
|
@@ -8,6 +9,6 @@ export async function initPlatformAdmin() {
|
|
|
8
9
|
}),
|
|
9
10
|
}).create();
|
|
10
11
|
|
|
11
|
-
const adminToken = await
|
|
12
|
+
const adminToken = await SessionService.createSession(admin);
|
|
12
13
|
return { admin, adminToken };
|
|
13
14
|
}
|