@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.
Files changed (28) hide show
  1. package/package.json +10 -10
  2. package/src/crons.ts +39 -5
  3. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +953 -47
  4. package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
  5. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +142 -0
  6. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +1 -1
  7. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +163 -8
  8. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
  9. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.test.ts +108 -0
  10. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +40 -0
  11. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
  12. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -1
  13. package/src/helpers/AdminPermissionChecker.ts +30 -6
  14. package/src/helpers/AuthenticatedStructures.ts +2 -2
  15. package/src/helpers/MemberUserSyncer.test.ts +400 -1
  16. package/src/helpers/MemberUserSyncer.ts +15 -10
  17. package/src/helpers/ServiceFeeHelper.ts +63 -0
  18. package/src/helpers/StripeHelper.ts +7 -4
  19. package/src/helpers/StripePayoutChecker.ts +1 -1
  20. package/src/seeds/0000000001-development-user.ts +2 -2
  21. package/src/seeds/0000000004-single-organization.ts +60 -0
  22. package/src/seeds/1754560914-groups-prices.test.ts +3023 -0
  23. package/src/seeds/1754560914-groups-prices.ts +408 -0
  24. package/src/seeds/{1722344162-sync-member-users.ts → 1761665607-sync-member-users.ts} +1 -1
  25. package/src/sql-filters/members.ts +1 -1
  26. package/tests/init/initAdmin.ts +19 -5
  27. package/tests/init/initPermissionRole.ts +14 -4
  28. 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: 'minmum_amount',
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 ? fee : undefined,
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 ? fee : undefined,
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 totalUsers = await User.select().count();
13
- if (totalUsers > 0) {
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
+ });