@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
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { PeppolEndointId } from '@stamhoofd/structures';
|
|
2
|
-
import { STExpect } from '@stamhoofd/test-utils';
|
|
3
|
-
import nock from 'nock';
|
|
4
|
-
import { PeppolDirectoryService } from './PeppolDirectoryService.js';
|
|
5
|
-
|
|
6
|
-
const DIRECTORY_HOST = 'https://directory.peppol.eu';
|
|
7
|
-
const DIRECTORY_PATH = '/search/1.0/json';
|
|
8
|
-
|
|
9
|
-
describe('PeppolDirectoryService', () => {
|
|
10
|
-
/**
|
|
11
|
-
* Mocks the PEPPOL directory search endpoint for a given participant query.
|
|
12
|
-
* Returns the nock scope so tests can assert whether it was actually called.
|
|
13
|
-
*/
|
|
14
|
-
function mockDirectory(participant: string, response: unknown, statusCode = 200) {
|
|
15
|
-
return nock(DIRECTORY_HOST)
|
|
16
|
-
.get(DIRECTORY_PATH)
|
|
17
|
-
.query({ participant })
|
|
18
|
-
.reply(statusCode, response as nock.Body);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A directory response that contains a single match for the given participant value.
|
|
23
|
-
*/
|
|
24
|
-
function matchResponse(value: string) {
|
|
25
|
-
return {
|
|
26
|
-
version: '1.0',
|
|
27
|
-
'total-result-count': 1,
|
|
28
|
-
matches: [
|
|
29
|
-
{
|
|
30
|
-
participantID: { scheme: 'iso6523-actorid-upis', value },
|
|
31
|
-
entities: [{ name: [{ name: 'Demo Company' }], countryCode: 'BE' }],
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
nock.cleanAll();
|
|
39
|
-
nock.disableNetConnect();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('resolves when the participant is registered in the directory', async () => {
|
|
43
|
-
const scope = mockDirectory('iso6523-actorid-upis::0208:0123456789', matchResponse('0208:0123456789'));
|
|
44
|
-
|
|
45
|
-
const endpointId = PeppolEndointId.create({ schemeID: '0208', id: '0123456789' });
|
|
46
|
-
await expect(PeppolDirectoryService.validate(endpointId)).resolves.toBeUndefined();
|
|
47
|
-
|
|
48
|
-
expect(scope.isDone()).toBe(true);
|
|
49
|
-
// The registered entity name from the directory is stored on the endpoint id.
|
|
50
|
-
expect(endpointId.entityName).toBe('Demo Company');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('overwrites a pre-existing entityName with the value from the directory', async () => {
|
|
54
|
-
mockDirectory('iso6523-actorid-upis::0208:0123456789', matchResponse('0208:0123456789'));
|
|
55
|
-
|
|
56
|
-
const endpointId = PeppolEndointId.create({ schemeID: '0208', id: '0123456789', entityName: 'Client supplied name' });
|
|
57
|
-
await PeppolDirectoryService.validate(endpointId);
|
|
58
|
-
|
|
59
|
-
expect(endpointId.entityName).toBe('Demo Company');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('sets entityName to null when the directory match has no entity name', async () => {
|
|
63
|
-
mockDirectory('iso6523-actorid-upis::0208:0123456789', {
|
|
64
|
-
'total-result-count': 1,
|
|
65
|
-
matches: [{ participantID: { scheme: 'iso6523-actorid-upis', value: '0208:0123456789' }, entities: [] }],
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const endpointId = PeppolEndointId.create({ schemeID: '0208', id: '0123456789', entityName: 'stale' });
|
|
69
|
-
await PeppolDirectoryService.validate(endpointId);
|
|
70
|
-
|
|
71
|
-
expect(endpointId.entityName).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('trims whitespace around the scheme and id before querying', async () => {
|
|
75
|
-
const scope = mockDirectory('iso6523-actorid-upis::0088:5412345000013', matchResponse('0088:5412345000013'));
|
|
76
|
-
|
|
77
|
-
await expect(
|
|
78
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: ' 0088 ', id: ' 5412345000013 ' })),
|
|
79
|
-
).resolves.toBeUndefined();
|
|
80
|
-
|
|
81
|
-
expect(scope.isDone()).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('rejects an unsupported scheme without contacting the directory', async () => {
|
|
85
|
-
const scope = mockDirectory('iso6523-actorid-upis::9999:123', matchResponse('9999:123'));
|
|
86
|
-
|
|
87
|
-
await expect(
|
|
88
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '9999', id: '123' })),
|
|
89
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'invalid_field', field: 'customPeppolEndpointId' }));
|
|
90
|
-
|
|
91
|
-
expect(scope.isDone()).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('rejects an empty id without contacting the directory', async () => {
|
|
95
|
-
const scope = mockDirectory('iso6523-actorid-upis::0208:', matchResponse('0208:'));
|
|
96
|
-
|
|
97
|
-
await expect(
|
|
98
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '0208', id: ' ' })),
|
|
99
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'invalid_field', field: 'customPeppolEndpointId' }));
|
|
100
|
-
|
|
101
|
-
expect(scope.isDone()).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('rejects when the participant is not found in the directory', async () => {
|
|
105
|
-
mockDirectory('iso6523-actorid-upis::0060:123456789', {
|
|
106
|
-
version: '1.0',
|
|
107
|
-
'total-result-count': 0,
|
|
108
|
-
matches: [],
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
await expect(
|
|
112
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '0060', id: '123456789' })),
|
|
113
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'invalid_field', field: 'customPeppolEndpointId' }));
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('rejects when the directory only returns a different participant', async () => {
|
|
117
|
-
mockDirectory('iso6523-actorid-upis::0208:0123456789', matchResponse('0208:9999999999'));
|
|
118
|
-
|
|
119
|
-
await expect(
|
|
120
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '0208', id: '0123456789' })),
|
|
121
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'invalid_field', field: 'customPeppolEndpointId' }));
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('throws service_unavailable when the directory errors out', async () => {
|
|
125
|
-
mockDirectory('iso6523-actorid-upis::0208:0123456789', { message: 'Internal Server Error' }, 500);
|
|
126
|
-
|
|
127
|
-
await expect(
|
|
128
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '0208', id: '0123456789' })),
|
|
129
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'service_unavailable', field: 'customPeppolEndpointId' }));
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('throws service_unavailable when the directory returns an unexpected response', async () => {
|
|
133
|
-
mockDirectory('iso6523-actorid-upis::0208:0123456789', { unexpected: true });
|
|
134
|
-
|
|
135
|
-
await expect(
|
|
136
|
-
PeppolDirectoryService.validate(PeppolEndointId.create({ schemeID: '0208', id: '0123456789' })),
|
|
137
|
-
).rejects.toThrow(STExpect.simpleError({ code: 'service_unavailable', field: 'customPeppolEndpointId' }));
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
-
import type { PeppolEndointId } from '@stamhoofd/structures';
|
|
3
|
-
import { PeppolScheme } from '@stamhoofd/structures';
|
|
4
|
-
import axios from 'axios';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The PEPPOL schemes (ISO 6523 ICD codes) we currently support for a custom
|
|
8
|
-
* PEPPOL endpoint id. The scheme identifier is the numeric code used in the
|
|
9
|
-
* PEPPOL participant identifier (e.g. `iso6523-actorid-upis::0208:...`).
|
|
10
|
-
*/
|
|
11
|
-
export const supportedPeppolSchemes: string[] = Object.values(PeppolScheme);
|
|
12
|
-
|
|
13
|
-
export class PeppolDirectoryServiceStatic {
|
|
14
|
-
/**
|
|
15
|
-
* Validates a custom PEPPOL endpoint id: the scheme must be supported and the
|
|
16
|
-
* participant must be registered in the PEPPOL directory. Throws a SimpleError
|
|
17
|
-
* (with field `customPeppolEndpointId`) when the id is invalid or unknown.
|
|
18
|
-
*/
|
|
19
|
-
async validate(endpointId: PeppolEndointId): Promise<void> {
|
|
20
|
-
const schemeID = endpointId.schemeID.trim();
|
|
21
|
-
const id = endpointId.id.trim();
|
|
22
|
-
|
|
23
|
-
if (!supportedPeppolSchemes.includes(schemeID)) {
|
|
24
|
-
throw new SimpleError({
|
|
25
|
-
code: 'invalid_field',
|
|
26
|
-
message: 'Unsupported PEPPOL scheme ' + schemeID,
|
|
27
|
-
human: $t('%ZcX'),
|
|
28
|
-
field: 'customPeppolEndpointId',
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (id.length === 0) {
|
|
33
|
-
throw new SimpleError({
|
|
34
|
-
code: 'invalid_field',
|
|
35
|
-
message: 'Missing PEPPOL endpoint id',
|
|
36
|
-
human: $t('%ZcY'),
|
|
37
|
-
field: 'customPeppolEndpointId',
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const participant = `iso6523-actorid-upis::${schemeID}:${id}`;
|
|
42
|
-
|
|
43
|
-
let data: any;
|
|
44
|
-
try {
|
|
45
|
-
const response = await axios.request({
|
|
46
|
-
method: 'GET',
|
|
47
|
-
url: 'https://directory.peppol.eu/search/1.0/json',
|
|
48
|
-
params: { participant },
|
|
49
|
-
});
|
|
50
|
-
data = response.data;
|
|
51
|
-
} catch (e) {
|
|
52
|
-
// The directory is unavailable: we can't validate the id right now.
|
|
53
|
-
console.error('PEPPOL directory error', e);
|
|
54
|
-
throw new SimpleError({
|
|
55
|
-
code: 'service_unavailable',
|
|
56
|
-
message: 'PEPPOL directory unavailable',
|
|
57
|
-
human: $t('%Zcf'),
|
|
58
|
-
field: 'customPeppolEndpointId',
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeof data !== 'object' || data === null || !Array.isArray(data.matches)) {
|
|
63
|
-
throw new SimpleError({
|
|
64
|
-
code: 'service_unavailable',
|
|
65
|
-
message: 'Invalid response from PEPPOL directory',
|
|
66
|
-
human: $t('%Zcf'),
|
|
67
|
-
field: 'customPeppolEndpointId',
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log('PEPPOL lookup', participant, data);
|
|
72
|
-
|
|
73
|
-
const expected = `${schemeID}:${id}`.toLowerCase();
|
|
74
|
-
const match = data.matches.find((m: any) => {
|
|
75
|
-
return typeof m?.participantID?.value === 'string'
|
|
76
|
-
&& m.participantID.value.toLowerCase() === expected;
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (!match) {
|
|
80
|
-
throw new SimpleError({
|
|
81
|
-
code: 'invalid_field',
|
|
82
|
-
message: 'PEPPOL participant not found in directory: ' + participant,
|
|
83
|
-
human: $t('%Zcr', { id: `${schemeID}:${id}` }),
|
|
84
|
-
field: 'customPeppolEndpointId',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Store the registered entity name from the directory. This is server-controlled
|
|
89
|
-
// and overwrites any value the client may have sent (see ViesHelper.checkCompany).
|
|
90
|
-
endpointId.entityName = this.extractEntityName(match);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Best-effort extraction of the registered entity name from a PEPPOL directory match.
|
|
95
|
-
* The directory returns one or more entities, each with a list of localized names.
|
|
96
|
-
*/
|
|
97
|
-
private extractEntityName(match: any): string | null {
|
|
98
|
-
const entities = Array.isArray(match?.entities) ? match.entities : [];
|
|
99
|
-
for (const entity of entities) {
|
|
100
|
-
const names = Array.isArray(entity?.name) ? entity.name : [];
|
|
101
|
-
for (const n of names) {
|
|
102
|
-
if (typeof n?.name === 'string' && n.name.trim().length > 0) {
|
|
103
|
-
return n.name.trim();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export const PeppolDirectoryService = new PeppolDirectoryServiceStatic();
|