@stamhoofd/backend 2.90.3 → 2.91.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.
Files changed (35) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/ModelLogger.ts +0 -1
  3. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +14 -0
  4. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -0
  5. package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -3
  6. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +6 -4
  7. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -7
  8. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +112 -105
  9. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  10. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +5 -5
  11. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -1
  12. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +1 -1
  13. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
  14. package/src/helpers/AdminPermissionChecker.ts +0 -5
  15. package/src/helpers/FlagMomentCleanup.ts +13 -1
  16. package/src/helpers/GroupedThrottledQueue.ts +5 -3
  17. package/src/helpers/PeriodHelper.ts +10 -137
  18. package/src/helpers/SetupStepUpdater.ts +54 -7
  19. package/src/helpers/UitpasTokenRepository.ts +3 -3
  20. package/src/seeds/1750090030-records-configuration.ts +5 -1
  21. package/src/services/BalanceItemService.ts +12 -7
  22. package/src/services/DocumentService.ts +0 -1
  23. package/src/services/RegistrationService.ts +30 -1
  24. package/src/services/uitpas/UitpasService.ts +1 -1
  25. package/src/services/uitpas/cancelTicketSales.ts +1 -1
  26. package/src/services/uitpas/checkPermissionsFor.ts +9 -9
  27. package/src/services/uitpas/checkUitpasNumbers.ts +2 -2
  28. package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
  29. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
  30. package/src/services/uitpas/registerTicketSales.ts +4 -4
  31. package/src/services/uitpas/searchUitpasEvents.ts +3 -3
  32. package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
  33. package/src/sql-filters/members.ts +1 -1
  34. package/src/sql-filters/organizations.ts +52 -0
  35. package/tests/e2e/register.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.90.3",
3
+ "version": "2.91.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -45,14 +45,14 @@
45
45
  "@simonbackx/simple-encoding": "2.22.0",
46
46
  "@simonbackx/simple-endpoints": "1.20.1",
47
47
  "@simonbackx/simple-logging": "^1.0.1",
48
- "@stamhoofd/backend-i18n": "2.90.3",
49
- "@stamhoofd/backend-middleware": "2.90.3",
50
- "@stamhoofd/email": "2.90.3",
51
- "@stamhoofd/models": "2.90.3",
52
- "@stamhoofd/queues": "2.90.3",
53
- "@stamhoofd/sql": "2.90.3",
54
- "@stamhoofd/structures": "2.90.3",
55
- "@stamhoofd/utility": "2.90.3",
48
+ "@stamhoofd/backend-i18n": "2.91.0",
49
+ "@stamhoofd/backend-middleware": "2.91.0",
50
+ "@stamhoofd/email": "2.91.0",
51
+ "@stamhoofd/models": "2.91.0",
52
+ "@stamhoofd/queues": "2.91.0",
53
+ "@stamhoofd/sql": "2.91.0",
54
+ "@stamhoofd/structures": "2.91.0",
55
+ "@stamhoofd/utility": "2.91.0",
56
56
  "archiver": "^7.0.1",
57
57
  "axios": "^1.8.2",
58
58
  "cookie": "^0.7.0",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "81248f4f4d578fb67e29c48c55ddb1a1beb12313"
73
+ "gitHead": "37ff6e89f07320c0736d32c9b911c8c1c35dc421"
74
74
  }
@@ -193,7 +193,6 @@ export class ModelLogger<ModelType extends typeof Model, M extends InstanceType<
193
193
 
194
194
  if (log.patchList.length === 0 && !log.description) {
195
195
  // No changes or all skipped
196
- console.log('No changes after secundary filtering');
197
196
  return false;
198
197
  }
199
198
  }
@@ -1,5 +1,6 @@
1
1
  import { registerCron } from '@stamhoofd/crons';
2
2
  import { FlagMomentCleanup } from '../helpers/FlagMomentCleanup';
3
+ import { Platform, RegistrationPeriod } from '@stamhoofd/models';
3
4
 
4
5
  // Only delete responsibilities when the server is running during a month change.
5
6
  // Chances are almost zero that we reboot during a month change
@@ -19,6 +20,19 @@ export async function endFunctionsOfUsersWithoutRegistration() {
19
20
  return;
20
21
  }
21
22
 
23
+ // Check if the current period is active for more than 2 months
24
+ const platform = await Platform.getShared();
25
+ const period = await RegistrationPeriod.getByID(platform.periodId);
26
+ if (!period) {
27
+ console.warn('No active registration period found, skipping cleanup.');
28
+ return;
29
+ }
30
+
31
+ if (period.startDate > new Date(Date.now() - 1000 * 60 * 60 * 24 * 55)) {
32
+ console.warn('Current registration period is less than 2 months old, skipping cleanup.');
33
+ return;
34
+ }
35
+
22
36
  await FlagMomentCleanup.endFunctionsOfUsersWithoutRegistration();
23
37
  lastCleanupYear = currentYear;
24
38
  lastCleanupMonth = currentMonth;
@@ -94,6 +94,8 @@ export class GetOrganizationsEndpoint extends Endpoint<Params, Query, Body, Resp
94
94
  query.limit(q.limit);
95
95
  }
96
96
 
97
+ console.log('GetOrganizationsEndpoint query', query.getSQL());
98
+
97
99
  return query;
98
100
  }
99
101
 
@@ -1,10 +1,9 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
- import { Email, EmailTemplate, RateLimiter } from '@stamhoofd/models';
4
- import { EmailPreview, EmailStatus, Email as EmailStruct, Version, EmailTemplate as EmailTemplateStruct } from '@stamhoofd/structures';
3
+ import { Email, RateLimiter } from '@stamhoofd/models';
4
+ import { EmailPreview, EmailStatus, Email as EmailStruct, EmailTemplate as EmailTemplateStruct } from '@stamhoofd/structures';
5
5
 
6
6
  import { Context } from '../../../helpers/Context';
7
- import { SQL } from '@stamhoofd/sql';
8
7
 
9
8
  type Params = Record<string, never>;
10
9
  type Query = undefined;
@@ -3,7 +3,7 @@ import { AutoEncoderPatchType, ConvertArrayToPatchableArray, Decoder, isEmptyPat
3
3
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
5
  import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
6
- import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel } from '@stamhoofd/structures';
6
+ import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel, SetupStepType } from '@stamhoofd/structures';
7
7
  import { Formatter } from '@stamhoofd/utility';
8
8
 
9
9
  import { Email } from '@stamhoofd/email';
@@ -317,7 +317,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
317
317
  throw new SimpleError({
318
318
  code: 'invalid_field',
319
319
  message: 'Invalid organization',
320
- human: Context.i18n.$t('d41cdbe3-57e3-4a2e-83bc-cb9e65c9c840'),
320
+ human: platformResponsibility ? $t('ec6a555e-6bb1-4b5f-b17e-38eaa8a478b5') : $t('d41cdbe3-57e3-4a2e-83bc-cb9e65c9c840'),
321
321
  });
322
322
  }
323
323
 
@@ -688,7 +688,9 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
688
688
  }
689
689
 
690
690
  if (shouldUpdateSetupSteps && organization) {
691
- SetupStepUpdater.updateForOrganization(organization).catch(console.error);
691
+ SetupStepUpdater.updateForOrganization(organization, { types: [
692
+ SetupStepType.Responsibilities,
693
+ ] }).catch(console.error);
692
694
  }
693
695
 
694
696
  return new Response(
@@ -957,7 +959,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
957
959
  throw new SimpleError({
958
960
  code: 'known_member_missing_rights',
959
961
  message: 'Creating known member without sufficient access rights',
960
- human: $t(`{member} is al gekend in ons systeem, maar jouw e-mailadres niet. Om toegang te krijgen heb je de beveiligingscode nodig.`, { member: member.details.firstName }),
962
+ human: $t(`510807a1-d4c7-45fa-9e3b-ddc8764d3f6e`, { member: member.details.firstName }),
961
963
  statusCode: 400,
962
964
  });
963
965
  }
@@ -90,6 +90,10 @@ export class PatchPlatformEndpoint extends Endpoint<
90
90
  throw Context.auth.error();
91
91
  }
92
92
 
93
+ if (request.body.config.organizationLevelRecordsConfiguration) {
94
+ shouldUpdateSetupSteps = true;
95
+ }
96
+
93
97
  const newConfig = request.body.config;
94
98
 
95
99
  // Update config
@@ -100,13 +104,11 @@ export class PatchPlatformEndpoint extends Endpoint<
100
104
  platform.config = patchObject(platform.config, newConfig);
101
105
  const currentConfig: PlatformConfig = platform.config;
102
106
 
103
- if (shouldCheckSteps) {
104
- shouldUpdateSetupSteps = this.shouldUpdateSetupSteps(
105
- currentConfig,
106
- newConfig,
107
- oldConfig,
108
- );
109
- }
107
+ shouldUpdateSetupSteps = shouldUpdateSetupSteps || this.shouldUpdateSetupSteps(
108
+ currentConfig,
109
+ newConfig,
110
+ oldConfig,
111
+ );
110
112
  }
111
113
  else {
112
114
  platform.config = patchObject(platform.config, newConfig);
@@ -14,6 +14,7 @@ import { Context } from '../../../helpers/Context';
14
14
  import { StripeHelper } from '../../../helpers/StripeHelper';
15
15
  import { BalanceItemService } from '../../../services/BalanceItemService';
16
16
  import { RegistrationService } from '../../../services/RegistrationService';
17
+ import { PaymentService } from '../../../services/PaymentService';
17
18
  type Params = Record<string, never>;
18
19
  type Query = undefined;
19
20
  type Body = IDRegisterCheckout;
@@ -1047,120 +1048,126 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
1047
1048
 
1048
1049
  let paymentUrl: string | null = null;
1049
1050
 
1050
- // Update balance items
1051
- if (payment.method === PaymentMethod.Transfer) {
1052
- // Send a small reminder email
1053
- try {
1054
- await Registration.sendTransferEmail(user, organization, payment);
1055
- }
1056
- catch (e) {
1057
- console.error('Failed to send transfer email');
1058
- console.error(e);
1059
- }
1060
- }
1061
- else if (payment.method !== PaymentMethod.PointOfSale && payment.method !== PaymentMethod.Unknown) {
1062
- if (!checkout.redirectUrl || !checkout.cancelUrl) {
1063
- throw new Error('Should have been caught earlier');
1051
+ try {
1052
+ // Update balance items
1053
+ if (payment.method === PaymentMethod.Transfer) {
1054
+ // Send a small reminder email
1055
+ try {
1056
+ await Registration.sendTransferEmail(user, organization, payment);
1057
+ }
1058
+ catch (e) {
1059
+ console.error('Failed to send transfer email');
1060
+ console.error(e);
1061
+ }
1064
1062
  }
1063
+ else if (payment.method !== PaymentMethod.PointOfSale && payment.method !== PaymentMethod.Unknown) {
1064
+ if (!checkout.redirectUrl || !checkout.cancelUrl) {
1065
+ throw new Error('Should have been caught earlier');
1066
+ }
1065
1067
 
1066
- const _redirectUrl = new URL(checkout.redirectUrl);
1067
- _redirectUrl.searchParams.set('paymentId', payment.id);
1068
- _redirectUrl.searchParams.set('organizationId', organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
1069
-
1070
- const _cancelUrl = new URL(checkout.cancelUrl);
1071
- _cancelUrl.searchParams.set('paymentId', payment.id);
1072
- _cancelUrl.searchParams.set('cancel', 'true');
1073
- _cancelUrl.searchParams.set('organizationId', organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
1074
-
1075
- const redirectUrl = _redirectUrl.href;
1076
- const cancelUrl = _cancelUrl.href;
1077
-
1078
- const webhookUrl = 'https://' + organization.getApiHost() + '/v' + Version + '/payments/' + encodeURIComponent(payment.id) + '?exchange=true';
1079
-
1080
- if (payment.provider === PaymentProvider.Stripe) {
1081
- const stripeResult = await StripeHelper.createPayment({
1082
- payment,
1083
- stripeAccount,
1084
- redirectUrl,
1085
- cancelUrl,
1086
- statementDescriptor: organization.name,
1087
- metadata: {
1088
- organization: organization.id,
1089
- user: user.id,
1090
- payment: payment.id,
1091
- },
1092
- i18n: Context.i18n,
1093
- lineItems: balanceItemPayments,
1094
- organization,
1095
- customer: {
1096
- name: user.name ?? payMembers[0]?.details.name ?? $t(`bd1e59c8-3d4c-4097-ab35-0ce7b20d0e50`),
1097
- email: user.email,
1098
- },
1099
- });
1100
- paymentUrl = stripeResult.paymentUrl;
1101
- }
1102
- else if (payment.provider === PaymentProvider.Mollie) {
1103
- // Mollie payment
1104
- const token = await MollieToken.getTokenFor(organization.id);
1105
- if (!token) {
1106
- throw new SimpleError({
1107
- code: '',
1108
- message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
1068
+ const _redirectUrl = new URL(checkout.redirectUrl);
1069
+ _redirectUrl.searchParams.set('paymentId', payment.id);
1070
+ _redirectUrl.searchParams.set('organizationId', organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
1071
+
1072
+ const _cancelUrl = new URL(checkout.cancelUrl);
1073
+ _cancelUrl.searchParams.set('paymentId', payment.id);
1074
+ _cancelUrl.searchParams.set('cancel', 'true');
1075
+ _cancelUrl.searchParams.set('organizationId', organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
1076
+
1077
+ const redirectUrl = _redirectUrl.href;
1078
+ const cancelUrl = _cancelUrl.href;
1079
+
1080
+ const webhookUrl = 'https://' + organization.getApiHost() + '/v' + Version + '/payments/' + encodeURIComponent(payment.id) + '?exchange=true';
1081
+
1082
+ if (payment.provider === PaymentProvider.Stripe) {
1083
+ const stripeResult = await StripeHelper.createPayment({
1084
+ payment,
1085
+ stripeAccount,
1086
+ redirectUrl,
1087
+ cancelUrl,
1088
+ statementDescriptor: organization.name,
1089
+ metadata: {
1090
+ organization: organization.id,
1091
+ user: user.id,
1092
+ payment: payment.id,
1093
+ },
1094
+ i18n: Context.i18n,
1095
+ lineItems: balanceItemPayments,
1096
+ organization,
1097
+ customer: {
1098
+ name: user.name ?? payMembers[0]?.details.name ?? $t(`bd1e59c8-3d4c-4097-ab35-0ce7b20d0e50`),
1099
+ email: user.email,
1100
+ },
1109
1101
  });
1102
+ paymentUrl = stripeResult.paymentUrl;
1110
1103
  }
1111
- const profileId = organization.privateMeta.mollieProfile?.id ?? await token.getProfileId(organization.getHost());
1112
- if (!profileId) {
1113
- throw new SimpleError({
1114
- code: '',
1115
- message: $t(`5574469f-8eee-47fe-9fb6-1b097142ac75`, { method: PaymentMethodHelper.getName(payment.method) }),
1104
+ else if (payment.provider === PaymentProvider.Mollie) {
1105
+ // Mollie payment
1106
+ const token = await MollieToken.getTokenFor(organization.id);
1107
+ if (!token) {
1108
+ throw new SimpleError({
1109
+ code: '',
1110
+ message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
1111
+ });
1112
+ }
1113
+ const profileId = organization.privateMeta.mollieProfile?.id ?? await token.getProfileId(organization.getHost());
1114
+ if (!profileId) {
1115
+ throw new SimpleError({
1116
+ code: '',
1117
+ message: $t(`5574469f-8eee-47fe-9fb6-1b097142ac75`, { method: PaymentMethodHelper.getName(payment.method) }),
1118
+ });
1119
+ }
1120
+ const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
1121
+ const locale = Context.i18n.locale.replace('-', '_');
1122
+ const molliePayment = await mollieClient.payments.create({
1123
+ amount: {
1124
+ currency: 'EUR',
1125
+ value: (totalPrice / 100).toFixed(2),
1126
+ },
1127
+ method: payment.method == PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method == PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
1128
+ testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production',
1129
+ profileId,
1130
+ description,
1131
+ redirectUrl,
1132
+ webhookUrl,
1133
+ metadata: {
1134
+ paymentId: payment.id,
1135
+ },
1136
+ locale: ['en_US', 'en_GB', 'nl_NL', 'nl_BE', 'fr_FR', 'fr_BE', 'de_DE', 'de_AT', 'de_CH', 'es_ES', 'ca_ES', 'pt_PT', 'it_IT', 'nb_NO', 'sv_SE', 'fi_FI', 'da_DK', 'is_IS', 'hu_HU', 'pl_PL', 'lv_LV', 'lt_LT'].includes(locale) ? (locale as any) : null,
1116
1137
  });
1117
- }
1118
- const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
1119
- const locale = Context.i18n.locale.replace('-', '_');
1120
- const molliePayment = await mollieClient.payments.create({
1121
- amount: {
1122
- currency: 'EUR',
1123
- value: (totalPrice / 100).toFixed(2),
1124
- },
1125
- method: payment.method == PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method == PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
1126
- testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production',
1127
- profileId,
1128
- description,
1129
- redirectUrl,
1130
- webhookUrl,
1131
- metadata: {
1132
- paymentId: payment.id,
1133
- },
1134
- locale: ['en_US', 'en_GB', 'nl_NL', 'nl_BE', 'fr_FR', 'fr_BE', 'de_DE', 'de_AT', 'de_CH', 'es_ES', 'ca_ES', 'pt_PT', 'it_IT', 'nb_NO', 'sv_SE', 'fi_FI', 'da_DK', 'is_IS', 'hu_HU', 'pl_PL', 'lv_LV', 'lt_LT'].includes(locale) ? (locale as any) : null,
1135
- });
1136
- paymentUrl = molliePayment.getCheckoutUrl();
1138
+ paymentUrl = molliePayment.getCheckoutUrl();
1137
1139
 
1138
- // Save payment
1139
- const dbPayment = new MolliePayment();
1140
- dbPayment.paymentId = payment.id;
1141
- dbPayment.mollieId = molliePayment.id;
1142
- await dbPayment.save();
1143
- }
1144
- else if (payment.provider === PaymentProvider.Payconiq) {
1145
- paymentUrl = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, webhookUrl);
1146
- }
1147
- else if (payment.provider == PaymentProvider.Buckaroo) {
1148
- // Increase request timeout because buckaroo is super slow (in development)
1149
- Context.request.request?.setTimeout(60 * 1000);
1150
- const buckaroo = new BuckarooHelper(organization.privateMeta?.buckarooSettings?.key ?? '', organization.privateMeta?.buckarooSettings?.secret ?? '', organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production');
1151
- const ip = Context.request.getIP();
1152
- paymentUrl = await buckaroo.createPayment(payment, ip, description, redirectUrl, webhookUrl);
1153
- await payment.save();
1154
-
1155
- // TypeScript doesn't understand that the status can change and isn't a const....
1156
- if ((payment.status as any) === PaymentStatus.Failed) {
1157
- throw new SimpleError({
1158
- code: 'payment_failed',
1159
- message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
1160
- });
1140
+ // Save payment
1141
+ const dbPayment = new MolliePayment();
1142
+ dbPayment.paymentId = payment.id;
1143
+ dbPayment.mollieId = molliePayment.id;
1144
+ await dbPayment.save();
1145
+ }
1146
+ else if (payment.provider === PaymentProvider.Payconiq) {
1147
+ paymentUrl = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, webhookUrl);
1148
+ }
1149
+ else if (payment.provider == PaymentProvider.Buckaroo) {
1150
+ // Increase request timeout because buckaroo is super slow (in development)
1151
+ Context.request.request?.setTimeout(60 * 1000);
1152
+ const buckaroo = new BuckarooHelper(organization.privateMeta?.buckarooSettings?.key ?? '', organization.privateMeta?.buckarooSettings?.secret ?? '', organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production');
1153
+ const ip = Context.request.getIP();
1154
+ paymentUrl = await buckaroo.createPayment(payment, ip, description, redirectUrl, webhookUrl);
1155
+ await payment.save();
1156
+
1157
+ // TypeScript doesn't understand that the status can change and isn't a const....
1158
+ if ((payment.status as any) === PaymentStatus.Failed) {
1159
+ throw new SimpleError({
1160
+ code: 'payment_failed',
1161
+ message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
1162
+ });
1163
+ }
1161
1164
  }
1162
1165
  }
1163
1166
  }
1167
+ catch (e) {
1168
+ await PaymentService.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
1169
+ throw e;
1170
+ }
1164
1171
 
1165
1172
  return {
1166
1173
  payment,
@@ -303,7 +303,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
303
303
  throw new SimpleError({
304
304
  code: 'invalid_field',
305
305
  message: 'You cannot set the uitpasClientCredentialsStatus manually',
306
- human: $t('Je kan de status van de UiTPAS-credentials niet handmatig instellen'),
306
+ human: $t('d8937ba8-6689-4c76-9841-d5a00c99074b'),
307
307
  });
308
308
  }
309
309
 
@@ -51,7 +51,7 @@ export class SetUitpasClientCredentialsEndpoint extends Endpoint<Params, Query,
51
51
  throw new SimpleError({
52
52
  message: 'You must provide a client id',
53
53
  code: 'missing_client_id',
54
- human: $t('Je moet een client id opgeven.'),
54
+ human: $t('9b9ec483-63b8-4696-ade6-0eb18f9008e6'),
55
55
  field: 'clientId',
56
56
  });
57
57
  }
@@ -59,7 +59,7 @@ export class SetUitpasClientCredentialsEndpoint extends Endpoint<Params, Query,
59
59
  throw new SimpleError({
60
60
  message: 'You must provide a client secret',
61
61
  code: 'missing_client_secret',
62
- human: $t('Je moet een client secret opgeven.'),
62
+ human: $t('58de00fb-3b0a-45a6-9214-7d11b4175779'),
63
63
  field: 'clientSecret',
64
64
  });
65
65
  }
@@ -67,7 +67,7 @@ export class SetUitpasClientCredentialsEndpoint extends Endpoint<Params, Query,
67
67
  throw new SimpleError({
68
68
  message: 'You cannot use the placeholder client secret for a different client id',
69
69
  code: 'invalid_client_secret',
70
- human: $t('Je kan niet enkel de client id wijzigen. Geef ook de client secret in.'),
70
+ human: $t('bbc79280-7ae2-4b8d-a900-2d7cbb552428'),
71
71
  field: 'clientSecret',
72
72
  });
73
73
  }
@@ -77,7 +77,7 @@ export class SetUitpasClientCredentialsEndpoint extends Endpoint<Params, Query,
77
77
  throw new SimpleError({
78
78
  message: 'This organization does not have a uitpas organizer id set',
79
79
  code: 'missing_uitpas_organizer_id',
80
- human: $t('Stel eerst een UiTPAS-organisator in.'),
80
+ human: $t('80fcde9c-c8c7-4fe1-b9a6-51684a23d850'),
81
81
  });
82
82
  }
83
83
 
@@ -88,7 +88,7 @@ export class SetUitpasClientCredentialsEndpoint extends Endpoint<Params, Query,
88
88
  throw new SimpleError({
89
89
  message: 'The provided client credentials are not valid',
90
90
  code: 'invalid_client_credentials',
91
- human: $t('De opgegeven client credentials zijn niet geldig.'),
91
+ human: $t('42bbd5c0-8789-4ca1-b667-1c9ecf4d0190'),
92
92
  });
93
93
  }
94
94
  }
@@ -1,5 +1,5 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
- import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
2
+ import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, SetupStepType, Version } from '@stamhoofd/structures';
3
3
 
4
4
  import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
@@ -41,7 +41,7 @@ export class SearchUitpasEventsEndpoint extends Endpoint<Params, Query, Body, Re
41
41
  throw new SimpleError({
42
42
  code: 'no_uitpas_organizer_id',
43
43
  message: `No UiTPAS organizer ID set for organization`,
44
- human: $t(`Deze organisatie heeft nog geen UiTPAS-organisatie ingesteld. Stel dit in via de algemene instellingen.`),
44
+ human: $t(`aaf56535-c13b-4f92-9ba4-7309cae3e078`),
45
45
  });
46
46
  }
47
47
  const uitpasOrganizersResponse = await UitpasService.searchUitpasEvents(organization.id, organization.meta.uitpasOrganizerId, request.query.text);
@@ -179,7 +179,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
179
179
  throw new SimpleError({
180
180
  code: 'uitpas_social_tariff_price_mismatch',
181
181
  message: 'UiTPAS wrong number of prices retruned',
182
- human: $t('Het kansentarief voor sommige UiTPAS-nummers kon niet worden opgehaald.'),
182
+ human: $t('2d1983fa-2224-422f-9ea0-fdae77cb4914'),
183
183
  field: 'cart.items.uitpasNumbers',
184
184
  });
185
185
  }
@@ -188,7 +188,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
188
188
  throw new SimpleError({
189
189
  code: 'uitpas_social_tariff_price_mismatch',
190
190
  message: 'UiTPAS social tariff have a different price',
191
- human: $t('Het kansentarief voor deze UiTPAS is {correctPrice} in plaats van {orderPrice}.', { correctPrice: Formatter.price(reducedPrices[i].price), orderPrice: Formatter.price(expectedReducedPrices[i].price) }),
191
+ human: $t('2f4b9572-4b9c-42e0-91f1-b0984624d225', { correctPrice: Formatter.price(reducedPrices[i].price), orderPrice: Formatter.price(expectedReducedPrices[i].price) }),
192
192
  field: 'uitpasNumbers.' + i.toString(),
193
193
  });
194
194
  }
@@ -379,11 +379,6 @@ export class AdminPermissionChecker {
379
379
  return false;
380
380
  }
381
381
 
382
- // Check permissions aren't scoped to a specific organization, and they mismatch
383
- if (!this.checkScope(registration.organizationId)) {
384
- return false;
385
- }
386
-
387
382
  const organizationPermissions = await this.getOrganizationPermissions(registration.organizationId);
388
383
 
389
384
  if (!organizationPermissions) {
@@ -26,7 +26,9 @@ export class FlagMomentCleanup {
26
26
  }
27
27
 
28
28
  static async getActiveMemberResponsibilityRecordsForOrganizationWithoutRegistrationInCurrentPeriod() {
29
- const currentPeriodId = (await Platform.getShared()).periodId;
29
+ const platform = await Platform.getShared();
30
+ const currentPeriodId = platform.periodId;
31
+ const platformResponsibilityIds = platform.config.responsibilities.map(r => r.id);
30
32
 
31
33
  return await MemberResponsibilityRecord.select()
32
34
  .whereNot('organizationId', null)
@@ -62,6 +64,16 @@ export class FlagMomentCleanup {
62
64
  ).where(
63
65
  SQL.column(Group.table, 'type'),
64
66
  GroupType.Membership,
67
+ ).where(
68
+ SQL.where(
69
+ SQL.column(Group.table, 'defaultAgeGroupId'),
70
+ '!=',
71
+ null,
72
+ ).or(
73
+ SQL.column(MemberResponsibilityRecord.table, 'responsibilityId'),
74
+ '!=',
75
+ platformResponsibilityIds,
76
+ ),
65
77
  ).where(
66
78
  SQL.column(Group.table, 'deletedAt'),
67
79
  null,
@@ -15,13 +15,15 @@ export class GroupedThrottledQueue<T> {
15
15
  * In milliseconds.
16
16
  */
17
17
  maxDelay: number | null = 10_000;
18
+ itemDelay: number | null = null;
18
19
 
19
20
  constructor(
20
21
  handler: (group: string, items: T[]) => Promise<void> | void,
21
- options: { maxDelay?: number | null } = {},
22
+ options: { maxDelay?: number | null; itemDelay?: number | null } = {},
22
23
  ) {
23
24
  this.handler = handler;
24
- this.maxDelay = options.maxDelay ?? 10_000;
25
+ this.maxDelay = options.maxDelay !== null ? (options.maxDelay ?? 10_000) : null;
26
+ this.itemDelay = options.itemDelay ?? null;
25
27
  }
26
28
 
27
29
  addItems(group: string, items: T[]): void {
@@ -32,7 +34,7 @@ export class GroupedThrottledQueue<T> {
32
34
  return;
33
35
  }
34
36
  const newQueue = new ThrottledQueue<T>(items => this.handler(group, items), {
35
- maxDelay: null,
37
+ maxDelay: this.itemDelay,
36
38
  emptyHandler: () => {
37
39
  this.queues.delete(group);
38
40
  },