@stamhoofd/backend 2.120.5 → 2.121.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 +12 -12
- package/src/audit-logs/RegistrationInvitationLogger.ts +46 -0
- package/src/audit-logs/init.ts +2 -0
- package/src/crons/index.ts +2 -0
- package/src/crons/invoices.ts +166 -0
- package/src/crons/mollie-chargebacks.ts +87 -0
- package/src/crons.ts +47 -10
- package/src/email-recipient-loaders/payments.ts +84 -41
- package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +51 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +22 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +4 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +495 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +216 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +405 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +168 -0
- package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +15 -0
- package/src/endpoints/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +42 -19
- package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +11 -3
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +308 -0
- package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +7 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +5 -4
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +7 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +17 -8
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +127 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +13 -4
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +7 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +1 -1
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -11
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +14 -19
- package/src/helpers/AdminPermissionChecker.ts +11 -3
- package/src/helpers/AuthenticatedStructures.ts +94 -6
- package/src/helpers/FinancialSupportHelper.ts +21 -0
- package/src/helpers/RecordAnswerHelper.test.ts +746 -0
- package/src/helpers/RecordAnswerHelper.ts +116 -0
- package/src/helpers/StripeHelper.ts +2 -3
- package/src/helpers/ViesHelper.ts +7 -3
- package/src/seeds/1750090030-records-configuration.ts +68 -3
- package/src/seeds/1752848561-groups-registration-periods.ts +26 -2
- package/src/seeds/1779121239-default-invoice-email-template.sql +3 -0
- package/src/services/BalanceItemService.ts +12 -16
- package/src/services/InvoiceService.ts +372 -72
- package/src/services/MollieService.ts +537 -0
- package/src/services/PaymentMandateService.ts +214 -0
- package/src/services/PaymentService.ts +578 -222
- package/src/services/PlatformMembershipService.ts +1 -1
- package/src/services/RegistrationService.ts +66 -5
- package/src/services/STPackageService.ts +0 -7
- package/src/services/data/invoice.hbs.html +686 -0
- package/src/sql-filters/groups.ts +11 -1
- package/src/sql-filters/payments.ts +5 -0
- package/src/sql-filters/registration-invitations.ts +90 -0
- package/src/sql-sorters/registration-invitations.ts +36 -0
- package/vitest.config.js +1 -0
- package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { User } from '@stamhoofd/models';
|
|
2
|
+
import { Organization } from '@stamhoofd/models';
|
|
3
|
+
import { Platform } from '@stamhoofd/models';
|
|
4
|
+
import { PaymentMandateStatus } from '@stamhoofd/structures/PaymentMandate.js';
|
|
5
|
+
import type {PaymentMandate} from '@stamhoofd/structures/PaymentMandate.js';
|
|
6
|
+
import { MollieService } from './MollieService.js';
|
|
7
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
8
|
+
import { Context } from '../helpers/Context.js';
|
|
9
|
+
import { PaymentProvider } from '@stamhoofd/structures';
|
|
10
|
+
|
|
11
|
+
export class PaymentMandateService {
|
|
12
|
+
static async getMandates({ sellingOrganization, user, payingOrganization }: {
|
|
13
|
+
sellingOrganization: Organization,
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Mandates for B2B payments
|
|
17
|
+
*/
|
|
18
|
+
payingOrganization: Organization | null,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Not yet supported, but in the future you'll be able to save mandates for a certain user.
|
|
22
|
+
* Only for B2C payments
|
|
23
|
+
*/
|
|
24
|
+
user: User | null,
|
|
25
|
+
}): Promise<PaymentMandate[]> {
|
|
26
|
+
if (!payingOrganization) {
|
|
27
|
+
// Not yet supportedd
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const mollieService = await MollieService.create({sellingOrganization});
|
|
32
|
+
if (!mollieService) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (sellingOrganization.id !== (await Platform.getShared()).membershipOrganizationId) {
|
|
37
|
+
// Not yet supported
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return await mollieService.getMandates({payingOrganization, user});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async deleteMandate({ mandateId, sellingOrganization, user, payingOrganization }: {
|
|
45
|
+
mandateId: string,
|
|
46
|
+
|
|
47
|
+
sellingOrganization: Organization,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Mandates for B2B payments
|
|
51
|
+
*/
|
|
52
|
+
payingOrganization: Organization | null,
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Not yet supported, but in the future you'll be able to save mandates for a certain user.
|
|
56
|
+
* Only for B2C payments
|
|
57
|
+
*/
|
|
58
|
+
user: User | null,
|
|
59
|
+
}) {
|
|
60
|
+
const mandates = await PaymentMandateService.getMandates({
|
|
61
|
+
sellingOrganization,
|
|
62
|
+
user,
|
|
63
|
+
payingOrganization
|
|
64
|
+
});
|
|
65
|
+
const {grouped} = this.groupByMandate(mandates);
|
|
66
|
+
const match = mandates.find(m => m.id === mandateId);
|
|
67
|
+
|
|
68
|
+
if (!match) {
|
|
69
|
+
// Not allowed or does not exist
|
|
70
|
+
throw new SimpleError({
|
|
71
|
+
code: 'not_allowed',
|
|
72
|
+
message: 'Mandate not found',
|
|
73
|
+
human: $t('%1Q1')
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (match.isDefault) {
|
|
78
|
+
if (!Context.optionalAuth?.hasPlatformFullAccess()) {
|
|
79
|
+
throw new SimpleError({
|
|
80
|
+
code: 'not_allowed',
|
|
81
|
+
message: 'You cannot delete the default mandate',
|
|
82
|
+
human: $t('%1Qu')
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (grouped.size <= 1) {
|
|
88
|
+
if (!Context.optionalAuth?.hasPlatformFullAccess()) {
|
|
89
|
+
throw new SimpleError({
|
|
90
|
+
code: 'not_allowed',
|
|
91
|
+
message: 'You cannot delete the last mandate',
|
|
92
|
+
human: $t('%1Q7')
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Delete all that have the same card linked to it
|
|
98
|
+
const deleteId = match.identifier;
|
|
99
|
+
for (const mandate of mandates) {
|
|
100
|
+
if (mandate.identifier === deleteId) {
|
|
101
|
+
// delete (todo)
|
|
102
|
+
if (mandate.provider === PaymentProvider.Mollie) {
|
|
103
|
+
const mollieService = await MollieService.create({sellingOrganization});
|
|
104
|
+
if (!mollieService) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await mollieService.deleteMandate({
|
|
110
|
+
mandateId: mandate.id,
|
|
111
|
+
payingOrganization,
|
|
112
|
+
user
|
|
113
|
+
})
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error('Failed to delete Mollie mandate', mandateId, e)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static async setDefaultMandate({ mandateId, sellingOrganization, payingUserId, payingOrganizationId }: {
|
|
123
|
+
mandateId: string,
|
|
124
|
+
|
|
125
|
+
sellingOrganization: Organization,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Mandates for B2B payments
|
|
129
|
+
*/
|
|
130
|
+
payingOrganizationId: Organization | string | null,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Not yet supported, but in the future you'll be able to save mandates for a certain user.
|
|
134
|
+
* Only for B2C payments
|
|
135
|
+
*/
|
|
136
|
+
payingUserId: string | null,
|
|
137
|
+
}) {
|
|
138
|
+
try {
|
|
139
|
+
if (!payingOrganizationId) {
|
|
140
|
+
// Not supported yet
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (sellingOrganization.id !== (await Platform.getShared()).membershipOrganizationId) {
|
|
145
|
+
// Not yet supported
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Set as default
|
|
150
|
+
if (payingOrganizationId) {
|
|
151
|
+
const payingOrganization = typeof payingOrganizationId === 'string' ? await Organization.getByID(payingOrganizationId) : payingOrganizationId;
|
|
152
|
+
if (payingOrganization) {
|
|
153
|
+
console.log('Saving ' + mandateId + ' as default mandate for organization ' + payingOrganization.id + ' ' + payingOrganization.name)
|
|
154
|
+
payingOrganization.serverMeta.mollieMandateId = mandateId
|
|
155
|
+
await payingOrganization.save()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (e) {
|
|
159
|
+
console.error('Failed to save default mandate for mandate ' + mandateId + ' in organization ' + (typeof payingOrganizationId === 'string' ? payingOrganizationId : payingOrganizationId?.id), {cause: e})
|
|
160
|
+
|
|
161
|
+
throw new SimpleError({
|
|
162
|
+
code: 'failed',
|
|
163
|
+
message: 'Failed to update default mandate',
|
|
164
|
+
human: $t('%1S4')
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The same mandates should be bundled and only the most recent active one should be returned
|
|
171
|
+
*/
|
|
172
|
+
static groupByMandate(base: PaymentMandate[]) {
|
|
173
|
+
base.sort((a, b) => {
|
|
174
|
+
if (a.status === PaymentMandateStatus.Valid && b.status !== PaymentMandateStatus.Valid ) return -1;
|
|
175
|
+
if (a.status !== PaymentMandateStatus.Valid && b.status === PaymentMandateStatus.Valid ) return 1;
|
|
176
|
+
if (a.isDefault && !b.isDefault) return -1;
|
|
177
|
+
if (!a.isDefault && b.isDefault) return 1;
|
|
178
|
+
return b.createdAt.getTime() - a.createdAt.getTime();
|
|
179
|
+
});
|
|
180
|
+
const found = new Map<string, PaymentMandate[]>();
|
|
181
|
+
|
|
182
|
+
const cleaned: PaymentMandate[] = [];
|
|
183
|
+
|
|
184
|
+
for (const mandate of base) {
|
|
185
|
+
const existing = found.get(mandate.identifier || '');
|
|
186
|
+
|
|
187
|
+
if (existing) {
|
|
188
|
+
if (mandate.isDefault) {
|
|
189
|
+
// Make sure first is also marked as default
|
|
190
|
+
existing[0].isDefault = true;
|
|
191
|
+
}
|
|
192
|
+
existing.push(mandate)
|
|
193
|
+
continue; // Skip duplicates
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
found.set(mandate.identifier || '', [mandate]);
|
|
197
|
+
cleaned.push(mandate);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Restort as isDefault might have changed
|
|
201
|
+
cleaned.sort((a, b) => {
|
|
202
|
+
if (a.status === PaymentMandateStatus.Valid && b.status !== PaymentMandateStatus.Valid ) return -1;
|
|
203
|
+
if (a.status !== PaymentMandateStatus.Valid && b.status === PaymentMandateStatus.Valid ) return 1;
|
|
204
|
+
if (a.isDefault && !b.isDefault) return -1;
|
|
205
|
+
if (!a.isDefault && b.isDefault) return 1;
|
|
206
|
+
return b.createdAt.getTime() - a.createdAt.getTime();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
mandates: cleaned.filter(b => b.status === PaymentMandateStatus.Valid || b.isDefault),
|
|
211
|
+
grouped: found
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|