@stamhoofd/backend 2.105.0 → 2.106.1
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 +10 -10
- package/src/crons.ts +39 -5
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +953 -47
- package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +142 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +1 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +163 -8
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.test.ts +108 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -1
- package/src/helpers/AdminPermissionChecker.ts +30 -6
- package/src/helpers/AuthenticatedStructures.ts +2 -2
- package/src/helpers/MemberUserSyncer.test.ts +400 -1
- package/src/helpers/MemberUserSyncer.ts +15 -10
- package/src/helpers/ServiceFeeHelper.ts +63 -0
- package/src/helpers/StripeHelper.ts +7 -4
- package/src/helpers/StripePayoutChecker.ts +1 -1
- package/src/seeds/0000000001-development-user.ts +2 -2
- package/src/seeds/0000000004-single-organization.ts +60 -0
- package/src/seeds/1754560914-groups-prices.test.ts +3023 -0
- package/src/seeds/1754560914-groups-prices.ts +408 -0
- package/src/seeds/{1722344162-sync-member-users.ts → 1761665607-sync-member-users.ts} +1 -1
- package/src/sql-filters/members.ts +1 -1
- package/tests/init/initAdmin.ts +19 -5
- package/tests/init/initPermissionRole.ts +14 -4
- package/tests/init/initPlatformRecordCategory.ts +8 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Organization, Payment } from '@stamhoofd/models';
|
|
2
|
+
import { calculateVATPercentage, PaymentProvider, STPackageType } from '@stamhoofd/structures';
|
|
3
|
+
|
|
4
|
+
export class ServiceFeeHelper {
|
|
5
|
+
static setServiceFee(payment: Payment, organization: Organization, type: 'webshop' | 'members' | 'tickets', itemPrices: number[]): void {
|
|
6
|
+
let vat = 0;
|
|
7
|
+
|
|
8
|
+
// Calculate item count
|
|
9
|
+
if (payment.provider === PaymentProvider.Stripe && payment.stripeAccountId) {
|
|
10
|
+
// Don't chagne
|
|
11
|
+
vat = calculateVATPercentage(organization.address, organization.meta.VATNumber);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// don't charge VAT (we'll add the VAT to the invoice)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function calculateFee(price: number, minimumFee: number | null, maximumFee: number | null, fixed: number, percentageTimes100: number) {
|
|
18
|
+
if (price === 0 && !minimumFee) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
let fee = Math.round(fixed + Math.max(1, price * percentageTimes100 / 100 / 100));
|
|
22
|
+
if (minimumFee !== null && fee < minimumFee) {
|
|
23
|
+
fee = minimumFee;
|
|
24
|
+
}
|
|
25
|
+
if (maximumFee !== null && fee > maximumFee) {
|
|
26
|
+
fee = maximumFee;
|
|
27
|
+
}
|
|
28
|
+
return Math.round(
|
|
29
|
+
fee * (100 + vat) / 100,
|
|
30
|
+
); // € 0,21 + 0,2%
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let serviceFee = 0;
|
|
34
|
+
|
|
35
|
+
// Calculate service fee if organization has a package with service fees
|
|
36
|
+
if (organization) {
|
|
37
|
+
const packageStatus = type === 'webshop' || type === 'tickets' ? organization.meta.packages.packages.get(STPackageType.Webshops) : organization.meta.packages.packages.get(STPackageType.Members);
|
|
38
|
+
const fees = packageStatus?.activeServiceFees;
|
|
39
|
+
|
|
40
|
+
console.log('Service fee settings for payment', payment.id, type, 'are', fees);
|
|
41
|
+
|
|
42
|
+
if (fees && (fees.fixed > 0 || fees.percentage > 0)) {
|
|
43
|
+
serviceFee = itemPrices.reduce((total, price) => {
|
|
44
|
+
return total + calculateFee(
|
|
45
|
+
price,
|
|
46
|
+
fees.minimum,
|
|
47
|
+
fees.maximum,
|
|
48
|
+
fees.fixed,
|
|
49
|
+
fees.percentage,
|
|
50
|
+
);
|
|
51
|
+
}, 0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('Service fee for payment', payment.id, type, 'is', serviceFee);
|
|
56
|
+
if (payment.provider === PaymentProvider.Stripe && payment.stripeAccountId) {
|
|
57
|
+
payment.serviceFeePayout = serviceFee;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
payment.serviceFeeManual = serviceFee;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -28,7 +28,7 @@ export class StripeHelper {
|
|
|
28
28
|
|
|
29
29
|
if (charge.balance_transaction !== null && typeof charge.balance_transaction !== 'string') {
|
|
30
30
|
const fees = charge.balance_transaction.fee;
|
|
31
|
-
payment.transferFee = fees;
|
|
31
|
+
payment.transferFee = fees - payment.serviceFeePayout;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -223,7 +223,7 @@ export class StripeHelper {
|
|
|
223
223
|
|
|
224
224
|
if (totalPrice < 50) {
|
|
225
225
|
throw new SimpleError({
|
|
226
|
-
code: '
|
|
226
|
+
code: 'minimum_amount',
|
|
227
227
|
message: 'The minimum amount for an online payment is € 0,50',
|
|
228
228
|
human: $t(`dae9058f-0aa7-4fcb-9f1d-fc918c65784b`),
|
|
229
229
|
});
|
|
@@ -253,10 +253,13 @@ export class StripeHelper {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
payment.transferFee = fee;
|
|
256
|
+
const serviceFee = payment.serviceFeePayout;
|
|
256
257
|
|
|
257
258
|
const fullMetadata = {
|
|
258
259
|
...(metadata ?? {}),
|
|
259
260
|
organizationVATNumber: organization.meta.VATNumber,
|
|
261
|
+
transactionFee: fee,
|
|
262
|
+
serviceFee: serviceFee,
|
|
260
263
|
};
|
|
261
264
|
|
|
262
265
|
const stripe = StripeHelper.getInstance(directCharge ? stripeAccount.accountId : null);
|
|
@@ -286,7 +289,7 @@ export class StripeHelper {
|
|
|
286
289
|
payment_method: paymentMethod.id,
|
|
287
290
|
payment_method_types: [payment.method.toLowerCase()],
|
|
288
291
|
statement_descriptor: Formatter.slug(statementDescriptor).substring(0, 22).toUpperCase(),
|
|
289
|
-
application_fee_amount: fee
|
|
292
|
+
application_fee_amount: fee + serviceFee,
|
|
290
293
|
on_behalf_of: !directCharge ? stripeAccount.accountId : undefined,
|
|
291
294
|
confirm: true,
|
|
292
295
|
return_url: redirectUrl,
|
|
@@ -363,7 +366,7 @@ export class StripeHelper {
|
|
|
363
366
|
locale: i18n.language as 'nl',
|
|
364
367
|
payment_intent_data: {
|
|
365
368
|
on_behalf_of: !directCharge ? stripeAccount.accountId : undefined,
|
|
366
|
-
application_fee_amount: fee
|
|
369
|
+
application_fee_amount: fee + serviceFee,
|
|
367
370
|
transfer_data: !directCharge
|
|
368
371
|
? {
|
|
369
372
|
destination: stripeAccount.accountId,
|
|
@@ -179,7 +179,7 @@ export class StripePayoutChecker {
|
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
payment.settlement = settlement;
|
|
182
|
-
payment.transferFee = totalFees;
|
|
182
|
+
payment.transferFee = totalFees - payment.serviceFeePayout;
|
|
183
183
|
|
|
184
184
|
// Force an updatedAt timestamp of the related order
|
|
185
185
|
// Mark order as 'updated', or the frontend won't pull in the updates
|
|
@@ -9,8 +9,8 @@ export default new Migration(async () => {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// Check if total users is 0
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
12
|
+
const hasUser = await User.select().where('id', '!=', '1').first(false);
|
|
13
|
+
if (hasUser) {
|
|
14
14
|
console.log('Skipped, users already exist');
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { Organization, RegistrationPeriod } from '@stamhoofd/models';
|
|
3
|
+
import { Address, Country } from '@stamhoofd/structures';
|
|
4
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
|
+
|
|
6
|
+
export default new Migration(async () => {
|
|
7
|
+
if (!STAMHOOFD.singleOrganization) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let organization = await Organization.getByID(STAMHOOFD.singleOrganization);
|
|
12
|
+
if (organization) {
|
|
13
|
+
console.log('Single organization already created.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const registrationPeriod = (await RegistrationPeriod.select().first(false)) ?? new RegistrationPeriod();
|
|
18
|
+
if (!registrationPeriod.id) {
|
|
19
|
+
const s = Formatter.luxon();
|
|
20
|
+
const startDate = s.set({
|
|
21
|
+
day: 1,
|
|
22
|
+
month: 9,
|
|
23
|
+
hour: 0,
|
|
24
|
+
minute: 0,
|
|
25
|
+
second: 0,
|
|
26
|
+
millisecond: 0,
|
|
27
|
+
});
|
|
28
|
+
const endDate = s.set({
|
|
29
|
+
year: startDate.year + 1,
|
|
30
|
+
day: 31,
|
|
31
|
+
month: 8,
|
|
32
|
+
hour: 23,
|
|
33
|
+
minute: 59,
|
|
34
|
+
second: 59,
|
|
35
|
+
millisecond: 999,
|
|
36
|
+
});
|
|
37
|
+
registrationPeriod.startDate = startDate.toJSDate();
|
|
38
|
+
registrationPeriod.endDate = endDate.toJSDate();
|
|
39
|
+
}
|
|
40
|
+
await registrationPeriod.save();
|
|
41
|
+
|
|
42
|
+
console.log('Creating single organization...');
|
|
43
|
+
organization = new Organization();
|
|
44
|
+
organization.id = STAMHOOFD.singleOrganization;
|
|
45
|
+
organization.name = STAMHOOFD.platformName;
|
|
46
|
+
organization.uri = Formatter.slug(STAMHOOFD.platformName);
|
|
47
|
+
organization.periodId = registrationPeriod.id;
|
|
48
|
+
organization.address = Address.create({
|
|
49
|
+
street: 'Demo',
|
|
50
|
+
number: '1',
|
|
51
|
+
postalCode: '9000',
|
|
52
|
+
city: 'Gent',
|
|
53
|
+
country: STAMHOOFD.fixedCountry ?? Country.Belgium,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await organization.save();
|
|
57
|
+
|
|
58
|
+
// Do something here
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
});
|