@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,308 @@
|
|
|
1
|
+
import type { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { BalanceItem, Organization, Platform, STPackage } from '@stamhoofd/models';
|
|
6
|
+
import { BalanceItemStatus, BalanceItemType, CheckoutResponse, OrganizationPackagesStatus, OrganizationCheckout, STPackageStruct } from '@stamhoofd/structures';
|
|
7
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
8
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
10
|
+
import { STPackageService } from '../../../../services/STPackageService.js';
|
|
11
|
+
import { CreateMandateSettings } from '@stamhoofd/structures/checkout/CreateMandateSettings.js';
|
|
12
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService.js';
|
|
13
|
+
|
|
14
|
+
type Params = { sellingOrganizationId: string };
|
|
15
|
+
type Query = undefined;
|
|
16
|
+
type Body = OrganizationCheckout;
|
|
17
|
+
type ResponseBody = CheckoutResponse;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* B2B billing (outstanding amount) and activating packages
|
|
21
|
+
*/
|
|
22
|
+
export class OrganizationCheckoutEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
23
|
+
bodyDecoder = OrganizationCheckout as Decoder<Body>;
|
|
24
|
+
|
|
25
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
26
|
+
if (request.method !== 'POST') {
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const params = Endpoint.parseParameters(request.url, '/billing/@sellingOrganizationId/checkout', {sellingOrganizationId: String});
|
|
31
|
+
|
|
32
|
+
if (params) {
|
|
33
|
+
return [true, params as Params];
|
|
34
|
+
}
|
|
35
|
+
return [false];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
39
|
+
const organization = await Context.setOrganizationScope();
|
|
40
|
+
const { user } = await Context.authenticate();
|
|
41
|
+
|
|
42
|
+
// If the user has permission, we'll also search if he has access to the organization's key
|
|
43
|
+
if (!await Context.auth.canActivatePackages(organization.id)) {
|
|
44
|
+
throw Context.auth.error();
|
|
45
|
+
}
|
|
46
|
+
const checkout = request.body;
|
|
47
|
+
|
|
48
|
+
// Validate company
|
|
49
|
+
if (checkout.customer && checkout.customer.company) {
|
|
50
|
+
// Search company id
|
|
51
|
+
// this avoids needing to check the VAT number every time
|
|
52
|
+
const id = checkout.customer.company.id;
|
|
53
|
+
const foundCompany = organization.meta.companies.find(c => c.id === id);
|
|
54
|
+
|
|
55
|
+
if (!foundCompany) {
|
|
56
|
+
throw new SimpleError({
|
|
57
|
+
code: 'invalid_data',
|
|
58
|
+
message: $t(`%w1`),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const id = request.params.sellingOrganizationId;
|
|
64
|
+
if (!id) {
|
|
65
|
+
throw new SimpleError({
|
|
66
|
+
code: 'unavailable',
|
|
67
|
+
message: 'This is temporarily unavailable',
|
|
68
|
+
human: $t('%1Rz')
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sellingOrganization = await Organization.getByID(id);
|
|
73
|
+
if (!sellingOrganization || !sellingOrganization.active) {
|
|
74
|
+
throw new SimpleError({
|
|
75
|
+
statusCode: 404,
|
|
76
|
+
code: 'not_found',
|
|
77
|
+
message: 'Selling organization not found',
|
|
78
|
+
human: $t('%1R5'),
|
|
79
|
+
field: 'sellingOrganization'
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (sellingOrganization.id === organization.id) {
|
|
84
|
+
throw new SimpleError({
|
|
85
|
+
code: 'unavailable',
|
|
86
|
+
message: 'Cannot purchase from yourself',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const packages: STPackageStruct[] = [];
|
|
91
|
+
const balanceItems: Map<BalanceItem, number> = new Map();
|
|
92
|
+
const models: STPackage[] = [];
|
|
93
|
+
|
|
94
|
+
const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
|
|
95
|
+
|
|
96
|
+
if (STAMHOOFD.userMode === 'organization' && sellingOrganization.id === membershipOrganizationId) {
|
|
97
|
+
const currentPackages = await STPackageService.getActivePackages(organization.id);
|
|
98
|
+
const packageStatus = OrganizationPackagesStatus.create({
|
|
99
|
+
packages: currentPackages.map(p => STPackageStruct.create(p)),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
packages.push(...checkout.purchases.getPackages(packageStatus));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create the real models for each package
|
|
106
|
+
// calculate the price for these packages and create a hidden balance item
|
|
107
|
+
for (const pack of packages) {
|
|
108
|
+
const model = new STPackage();
|
|
109
|
+
model.id = pack.id;
|
|
110
|
+
model.meta = pack.meta;
|
|
111
|
+
model.validUntil = pack.validUntil;
|
|
112
|
+
model.removeAt = pack.removeAt;
|
|
113
|
+
|
|
114
|
+
// Not yet valid / active (ignored until valid)
|
|
115
|
+
model.validAt = null;
|
|
116
|
+
model.organizationId = organization.id;
|
|
117
|
+
|
|
118
|
+
if (request.body.proForma) {
|
|
119
|
+
// Security addition
|
|
120
|
+
model.disableSave();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const balanceItem = await STPackageService.chargePackage(model, undefined, checkout.customer ?? undefined);
|
|
124
|
+
if (balanceItem) {
|
|
125
|
+
balanceItem.VATExcempt = PaymentService.getVATExcempt({
|
|
126
|
+
customer: checkout.customer,
|
|
127
|
+
sellingOrganization
|
|
128
|
+
});
|
|
129
|
+
balanceItems.set(balanceItem, balanceItem.priceWithVAT);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!request.body.proForma) {
|
|
133
|
+
await model.save();
|
|
134
|
+
await balanceItem?.save();
|
|
135
|
+
} else {
|
|
136
|
+
// Security addition
|
|
137
|
+
if (balanceItem && balanceItem.id) {
|
|
138
|
+
throw new Error('Unexpected balance item save')
|
|
139
|
+
}
|
|
140
|
+
balanceItem?.disableSave()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
models.push(model);
|
|
144
|
+
|
|
145
|
+
if (model.meta.requiresMandate) {
|
|
146
|
+
if (checkout.proForma) {
|
|
147
|
+
if (!checkout.createMandate) {
|
|
148
|
+
checkout.createMandate = CreateMandateSettings.create({saveAsDefault: true})
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// setting checkout.mandate is not enough - we also need to set it as default.
|
|
152
|
+
|
|
153
|
+
if (!checkout.createMandate) {
|
|
154
|
+
throw new SimpleError({
|
|
155
|
+
code: '',
|
|
156
|
+
message: $t('%1Rh')
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!checkout.createMandate.saveAsDefault) {
|
|
161
|
+
throw new SimpleError({
|
|
162
|
+
code: '',
|
|
163
|
+
message: $t('%1Qp')
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Add pending items (balance items in request)
|
|
171
|
+
// Update balances before we start
|
|
172
|
+
await BalanceItemService.flushCaches(organization.id);
|
|
173
|
+
|
|
174
|
+
// Validate balance items (can only happen serverside)
|
|
175
|
+
const balanceItemIds = [...request.body.balances.keys()]
|
|
176
|
+
let balanceItemsModels: BalanceItem[] = [];
|
|
177
|
+
if (balanceItemIds.length > 0) {
|
|
178
|
+
balanceItemsModels = await BalanceItem.select().where('id', balanceItemIds).andWhere('organizationId', sellingOrganization.id).limit(balanceItemIds.length).fetch();
|
|
179
|
+
|
|
180
|
+
if (balanceItemsModels.length !== balanceItemIds.length) {
|
|
181
|
+
throw new SimpleError({
|
|
182
|
+
code: 'balance_changed',
|
|
183
|
+
message: $t(`%vg`),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const [id, amount] of request.body.balances) {
|
|
188
|
+
if (amount === 0) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const item = balanceItemsModels.find(b => b.id === id);
|
|
193
|
+
if (!item) {
|
|
194
|
+
throw new SimpleError({
|
|
195
|
+
code: 'balance_changed',
|
|
196
|
+
message: $t(`%vg`),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (item.priceOpen === 0) {
|
|
201
|
+
throw new SimpleError({
|
|
202
|
+
code: 'balance_changed',
|
|
203
|
+
message: $t(`%vg`),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (item.priceOpen > 0 && amount < 0 || amount > item.priceOpen) {
|
|
208
|
+
throw new SimpleError({
|
|
209
|
+
code: 'balance_changed',
|
|
210
|
+
message: $t(`%vg`),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (item.priceOpen < 0 && amount > 0 || amount < item.priceOpen) {
|
|
215
|
+
throw new SimpleError({
|
|
216
|
+
code: 'balance_changed',
|
|
217
|
+
message: $t(`%vg`),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
balanceItems.set(item, amount)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If still zero payment
|
|
225
|
+
const { price: totalPrice, roundingAmount } = PaymentService.calculateTotalPrice({
|
|
226
|
+
balanceItems,
|
|
227
|
+
organization: sellingOrganization
|
|
228
|
+
})
|
|
229
|
+
const minimumAmount = 2_00;
|
|
230
|
+
|
|
231
|
+
if (totalPrice < minimumAmount && checkout.createMandate && !checkout.mandate) {
|
|
232
|
+
const item = new BalanceItem();
|
|
233
|
+
item.type = BalanceItemType.AdministrationFee;
|
|
234
|
+
item.description = $t('%1Q4')
|
|
235
|
+
item.payingOrganizationId = organization.id;
|
|
236
|
+
item.organizationId = sellingOrganization.id;
|
|
237
|
+
item.VATPercentage = 21;
|
|
238
|
+
item.VATExcempt = PaymentService.getVATExcempt({
|
|
239
|
+
customer: checkout.customer,
|
|
240
|
+
sellingOrganization
|
|
241
|
+
});
|
|
242
|
+
item.VATIncluded = !item.VATExcempt; // Makes sure price with VAT always matches unitPrice
|
|
243
|
+
item.quantity = 1;
|
|
244
|
+
item.unitPrice = minimumAmount - (totalPrice - roundingAmount);
|
|
245
|
+
item.createdAt = new Date();
|
|
246
|
+
item.status = BalanceItemStatus.Hidden;
|
|
247
|
+
|
|
248
|
+
if (!request.body.proForma) {
|
|
249
|
+
await item.save();
|
|
250
|
+
} else {
|
|
251
|
+
item.disableSave()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
balanceItems.set(item, item.priceWithVAT);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (checkout.proForma) {
|
|
258
|
+
const result = await PaymentService.createProForma({
|
|
259
|
+
balanceItems,
|
|
260
|
+
checkout,
|
|
261
|
+
user,
|
|
262
|
+
organization: sellingOrganization,
|
|
263
|
+
payingOrganization: organization,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return new Response(CheckoutResponse.create({
|
|
267
|
+
payment: result.payment,
|
|
268
|
+
invoice: result.invoice
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = await PaymentService.createPayment({
|
|
273
|
+
balanceItems,
|
|
274
|
+
checkout,
|
|
275
|
+
user,
|
|
276
|
+
organization: sellingOrganization,
|
|
277
|
+
payingOrganization: organization,
|
|
278
|
+
serviceFeeType: 'system',
|
|
279
|
+
createMandate: checkout.createMandate,
|
|
280
|
+
useMandate: checkout.mandate,
|
|
281
|
+
paymentConfiguration: sellingOrganization.meta.registrationPaymentConfiguration,
|
|
282
|
+
privatePaymentConfiguration: sellingOrganization.privateMeta.registrationPaymentConfiguration
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (!result) {
|
|
286
|
+
// No payment needed
|
|
287
|
+
throw new SimpleError({
|
|
288
|
+
code: 'missing_data',
|
|
289
|
+
message: 'Checkout was empty',
|
|
290
|
+
human: $t('%1L2'),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const pack of models) {
|
|
295
|
+
await pack.save();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const { payment, paymentUrl, paymentQRCode } = result;
|
|
299
|
+
|
|
300
|
+
return new Response(CheckoutResponse.create({
|
|
301
|
+
payment: payment ? await AuthenticatedStructures.paymentGeneral(payment) : null,
|
|
302
|
+
paymentUrl,
|
|
303
|
+
paymentQRCode,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
setPayment
|
|
308
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
5
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
+
import { Organization } from '@stamhoofd/models';
|
|
7
|
+
import { PaymentMandate } from '@stamhoofd/structures/PaymentMandate.js';
|
|
8
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { PaymentMandateService } from '../../../../services/PaymentMandateService.js';
|
|
10
|
+
|
|
11
|
+
type Params = { sellingOrganizationId: string };
|
|
12
|
+
type Query = undefined;
|
|
13
|
+
type Body = PatchableArrayAutoEncoder<PaymentMandate>;
|
|
14
|
+
type ResponseBody = PaymentMandate[]
|
|
15
|
+
|
|
16
|
+
export class DeleteOrganizationMandateEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
17
|
+
bodyDecoder = new PatchableArrayDecoder(
|
|
18
|
+
PaymentMandate as Decoder<PaymentMandate>,
|
|
19
|
+
PaymentMandate.patchType() as Decoder<AutoEncoderPatchType<PaymentMandate>>,
|
|
20
|
+
StringDecoder
|
|
21
|
+
) as Decoder<PatchableArrayAutoEncoder<PaymentMandate>>;
|
|
22
|
+
|
|
23
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
24
|
+
if (request.method !== 'PATCH') {
|
|
25
|
+
return [false];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const params = Endpoint.parseParameters(request.url, '/billing/@sellingOrganizationId/mandates', {sellingOrganizationId: String});
|
|
29
|
+
|
|
30
|
+
if (params) {
|
|
31
|
+
return [true, params as Params];
|
|
32
|
+
}
|
|
33
|
+
return [false];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
37
|
+
const payingOrganization = await Context.setOrganizationScope();
|
|
38
|
+
const { user } = await Context.authenticate();
|
|
39
|
+
|
|
40
|
+
const id = request.params.sellingOrganizationId;
|
|
41
|
+
if (!id) {
|
|
42
|
+
throw new SimpleError({
|
|
43
|
+
code: 'unavailable',
|
|
44
|
+
message: 'This is temporarily unavailable',
|
|
45
|
+
human: $t('%1Rz')
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sellingOrganization = await Organization.getByID(id);
|
|
50
|
+
if (!sellingOrganization || !sellingOrganization.active) {
|
|
51
|
+
throw new SimpleError({
|
|
52
|
+
statusCode: 404,
|
|
53
|
+
code: 'not_found',
|
|
54
|
+
message: 'Selling organization not found',
|
|
55
|
+
human: $t('%1R5'),
|
|
56
|
+
field: 'sellingOrganization'
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const mandates = await PaymentMandateService.getMandates({
|
|
61
|
+
sellingOrganization,
|
|
62
|
+
user,
|
|
63
|
+
payingOrganization
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
for (const patch of request.body.getPatches()) {
|
|
67
|
+
const mandate = mandates.find(m => m.id === patch.id);
|
|
68
|
+
if (!mandate) {
|
|
69
|
+
throw new SimpleError({
|
|
70
|
+
code: 'not_found',
|
|
71
|
+
message: 'This payment mandate is not found',
|
|
72
|
+
human: $t('%1R8')
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (patch.isDefault === true) {
|
|
77
|
+
await PaymentMandateService.setDefaultMandate({
|
|
78
|
+
mandateId: mandate.id,
|
|
79
|
+
sellingOrganization,
|
|
80
|
+
payingOrganizationId: payingOrganization,
|
|
81
|
+
payingUserId: null
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const updatedMandates = await PaymentMandateService.getMandates({
|
|
87
|
+
sellingOrganization,
|
|
88
|
+
user,
|
|
89
|
+
payingOrganization
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return new Response(PaymentMandateService.groupByMandate(updatedMandates).mandates);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -11,6 +11,7 @@ import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStruct
|
|
|
11
11
|
import { Context } from '../../../../helpers/Context.js';
|
|
12
12
|
import { invoiceFilterCompilers } from '../../../../sql-filters/invoices.js';
|
|
13
13
|
import { invoiceSorters } from '../../../../sql-sorters/invoices.js';
|
|
14
|
+
import { InvoiceService } from '../../../../services/InvoiceService.js';
|
|
14
15
|
|
|
15
16
|
type Params = Record<string, never>;
|
|
16
17
|
type Query = LimitedFilteredRequest;
|
|
@@ -186,6 +187,12 @@ export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
for (const invoice of invoices) {
|
|
191
|
+
if (invoice.number && STAMHOOFD.environment === 'development') {
|
|
192
|
+
await InvoiceService.generatePdf(invoice)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
189
196
|
return new PaginatedResponse<InvoiceStruct[], LimitedFilteredRequest>({
|
|
190
197
|
results: await AuthenticatedStructures.invoices(invoices),
|
|
191
198
|
next,
|
|
@@ -6,6 +6,7 @@ import { CheckMollieResponse, PermissionLevel } from '@stamhoofd/structures';
|
|
|
6
6
|
|
|
7
7
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
8
8
|
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { MollieService } from '../../../../services/MollieService.js';
|
|
9
10
|
|
|
10
11
|
type Params = Record<string, never>;
|
|
11
12
|
type Body = undefined;
|
|
@@ -36,9 +37,9 @@ export class CheckMollieEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
36
37
|
throw Context.auth.error();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const service = await MollieService.create({sellingOrganization: organization })
|
|
40
41
|
|
|
41
|
-
if (!
|
|
42
|
+
if (!service) {
|
|
42
43
|
organization.privateMeta.mollieOnboarding = null;
|
|
43
44
|
organization.privateMeta.mollieProfile = null;
|
|
44
45
|
await organization.save();
|
|
@@ -52,9 +53,9 @@ export class CheckMollieEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
52
53
|
profiles: [],
|
|
53
54
|
}));
|
|
54
55
|
}
|
|
55
|
-
const profiles = await
|
|
56
|
+
const profiles = await service.getProfiles();
|
|
56
57
|
|
|
57
|
-
const status = await
|
|
58
|
+
const status = await service.getOnboardingStatus();
|
|
58
59
|
organization.privateMeta.mollieOnboarding = status;
|
|
59
60
|
|
|
60
61
|
// Check profile is still valid
|
|
@@ -9,6 +9,7 @@ import { PermissionLevel } from '@stamhoofd/structures';
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
10
10
|
import { checkMollieSettlementsFor } from '../../../../helpers/CheckSettlements.js';
|
|
11
11
|
import { Context } from '../../../../helpers/Context.js';
|
|
12
|
+
import { MollieService } from '../../../../services/MollieService.js';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
14
15
|
|
|
@@ -47,9 +48,13 @@ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const mollieToken = await MollieToken.create(organization, request.body.code);
|
|
51
|
+
const service = await MollieService.create({sellingOrganization: organization})
|
|
52
|
+
if (service) {
|
|
53
|
+
await service.setupOnboarding()
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
// Check settlements after linking (shouldn't block)
|
|
56
|
+
checkMollieSettlementsFor(mollieToken.accessToken, true).catch(console.error);
|
|
57
|
+
}
|
|
53
58
|
|
|
54
59
|
return new Response(await AuthenticatedStructures.organization(organization));
|
|
55
60
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder} from '@simonbackx/simple-encoding';
|
|
1
|
+
import type { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { cloneObject, isPatchableArray, isPatchMap, ObjectData, PatchableArray, PatchMap, patchObject } from '@simonbackx/simple-encoding';
|
|
3
|
-
import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
|
|
3
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
5
5
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
6
6
|
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
7
|
-
import type { Company, PermissionsResourceType, ResourcePermissions} from '@stamhoofd/structures';
|
|
7
|
+
import type { Company, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
|
|
8
8
|
import { BuckarooSettings, MemberResponsibility, OrganizationMetaData, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, UitpasClientCredentialsStatus } from '@stamhoofd/structures';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
10
|
|
|
@@ -12,6 +12,7 @@ import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStruct
|
|
|
12
12
|
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper.js';
|
|
13
13
|
import { Context } from '../../../../helpers/Context.js';
|
|
14
14
|
import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer.js';
|
|
15
|
+
import { RecordAnswerHelper } from '../../../../helpers/RecordAnswerHelper.js';
|
|
15
16
|
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater.js';
|
|
16
17
|
import { TagHelper } from '../../../../helpers/TagHelper.js';
|
|
17
18
|
import { ViesHelper } from '../../../../helpers/ViesHelper.js';
|
|
@@ -158,6 +159,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
158
159
|
organization.privateMeta.privateKey = request.body.privateMeta.privateKey ?? organization.privateMeta.privateKey;
|
|
159
160
|
organization.privateMeta.featureFlags = patchObject(organization.privateMeta.featureFlags, request.body.privateMeta.featureFlags);
|
|
160
161
|
organization.privateMeta.balanceNotificationSettings = patchObject(organization.privateMeta.balanceNotificationSettings, request.body.privateMeta.balanceNotificationSettings);
|
|
162
|
+
organization.privateMeta.invoiceSettings = patchObject(organization.privateMeta.invoiceSettings, request.body.privateMeta.invoiceSettings);
|
|
161
163
|
organization.privateMeta.recordAnswers = request.body.privateMeta.recordAnswers.applyTo(organization.privateMeta.recordAnswers);
|
|
162
164
|
|
|
163
165
|
if (request.body.privateMeta.responsibilities || request.body.privateMeta.roles) {
|
|
@@ -251,16 +253,23 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
251
253
|
}
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
// Allow admin patches (permissions only atm). No put atm
|
|
255
|
-
if (request.body.admins) {
|
|
256
|
-
throw new Error('Temporary removed to keep code cleaner. Please use different endpoints.');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
256
|
if (request.body.meta) {
|
|
260
257
|
if (request.body.meta.companies) {
|
|
261
258
|
await this.validateCompanies(organization, request.body.meta.companies);
|
|
262
259
|
shouldUpdateSetupSteps = true;
|
|
263
260
|
}
|
|
261
|
+
|
|
262
|
+
if (request.body.meta.recordsConfiguration?.recordCategories) {
|
|
263
|
+
RecordAnswerHelper.throwIfPatchOrPutIsInvalid(organization.meta.recordsConfiguration.recordCategories, request.body.meta.recordsConfiguration.recordCategories);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (request.body.meta.financialSupport) {
|
|
267
|
+
if (STAMHOOFD.userMode === 'platform') {
|
|
268
|
+
// can only set financial support settings on platform if userMode platform
|
|
269
|
+
request.body.meta.financialSupport = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
264
273
|
const savedPackages = organization.meta.packages;
|
|
265
274
|
organization.meta.patchOrPut(request.body.meta);
|
|
266
275
|
organization.meta.packages = savedPackages;
|
|
@@ -166,7 +166,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
166
166
|
// Check total price
|
|
167
167
|
const totalPrice = balanceItemPayments.reduce((total, item) => total + item.price, 0);
|
|
168
168
|
payment.price = totalPrice;
|
|
169
|
-
PaymentService.
|
|
169
|
+
PaymentService.roundPayment(payment);
|
|
170
170
|
|
|
171
171
|
switch (payment.type) {
|
|
172
172
|
case PaymentType.Payment: {
|
|
@@ -301,8 +301,8 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
301
301
|
payment.paidAt = patch.paidAt;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
if (patch.customer) {
|
|
305
|
-
|
|
304
|
+
if (patch.customer !== undefined) {
|
|
305
|
+
payment.customer = patchObject(payment.customer, patch.customer, { getDefaultValue: () => PaymentCustomer.create({}) });
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
const payingOrganizationId = patch.payingOrganizationId ?? patch.payingOrganization?.id ?? null;
|