@stamhoofd/backend 2.43.2 → 2.44.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 (29) hide show
  1. package/package.json +10 -10
  2. package/src/crons/{clear-excel-cache.test.ts → clearExcelCache.test.ts} +1 -1
  3. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +18 -0
  4. package/src/crons.ts +8 -2
  5. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +116 -0
  6. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +24 -20
  7. package/src/endpoints/global/events/PatchEventsEndpoint.ts +10 -0
  8. package/src/endpoints/global/registration/{GetUserDetailedBillingStatusEndpoint.ts → GetUserDetailedPayableBalanceEndpoint.ts} +10 -8
  9. package/src/endpoints/global/registration/{GetUserBillingStatusEndpoint.ts → GetUserPayableBalanceEndpoint.ts} +13 -11
  10. package/src/endpoints/organization/dashboard/billing/{GetOrganizationDetailedBillingStatusEndpoint.ts → GetOrganizationDetailedPayableBalanceEndpoint.ts} +12 -16
  11. package/src/endpoints/organization/dashboard/billing/{GetOrganizationBillingStatusEndpoint.ts → GetOrganizationPayableBalanceEndpoint.ts} +8 -6
  12. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +1 -0
  13. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +98 -0
  14. package/src/endpoints/organization/dashboard/{cached-outstanding-balance/GetCachedOutstandingBalanceCountEndpoint.ts → receivable-balances/GetReceivableBalancesCountEndpoint.ts} +4 -4
  15. package/src/endpoints/organization/dashboard/{cached-outstanding-balance/GetCachedOutstandingBalanceEndpoint.ts → receivable-balances/GetReceivableBalancesEndpoint.ts} +18 -14
  16. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +57 -0
  17. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +123 -13
  18. package/src/helpers/AdminPermissionChecker.ts +11 -2
  19. package/src/helpers/AuthenticatedStructures.ts +79 -18
  20. package/src/helpers/FlagMomentCleanup.ts +68 -0
  21. package/src/helpers/LimitedFilteredRequestHelper.ts +24 -0
  22. package/src/helpers/OrganizationCharger.ts +46 -0
  23. package/src/helpers/StripeHelper.ts +35 -10
  24. package/src/sql-filters/orders.ts +26 -0
  25. package/src/sql-filters/{cached-outstanding-balance.ts → receivable-balances.ts} +1 -1
  26. package/src/sql-sorters/orders.ts +47 -0
  27. package/src/sql-sorters/{cached-outstanding-balance.ts → receivable-balances.ts} +2 -2
  28. /package/src/crons/{clear-excel-cache.ts → clearExcelCache.ts} +0 -0
  29. /package/src/crons/{setup-steps.ts → updateSetupSteps.ts} +0 -0
@@ -27,19 +27,29 @@ export class StripeHelper {
27
27
 
28
28
  if (charge.payment_method_details?.bancontact) {
29
29
  if (charge.payment_method_details.bancontact.iban_last4) {
30
- payment.iban = 'xxxx ' + charge.payment_method_details.bancontact.iban_last4;
30
+ payment.iban = '•••• ' + charge.payment_method_details.bancontact.iban_last4;
31
31
  }
32
32
  payment.ibanName = charge.payment_method_details.bancontact.verified_name;
33
33
  }
34
34
  if (charge.payment_method_details?.ideal) {
35
35
  if (charge.payment_method_details.ideal.iban_last4) {
36
- payment.iban = 'xxxx ' + charge.payment_method_details.ideal.iban_last4;
36
+ payment.iban = '•••• ' + charge.payment_method_details.ideal.iban_last4;
37
37
  }
38
38
  payment.ibanName = charge.payment_method_details.ideal.verified_name;
39
39
  }
40
40
  if (charge.payment_method_details?.card) {
41
41
  if (charge.payment_method_details.card.last4) {
42
- payment.iban = 'xxxx ' + charge.payment_method_details.card.last4;
42
+ payment.iban = '•••• ' + charge.payment_method_details.card.last4;
43
+ }
44
+ }
45
+ if (charge.payment_method_details?.sepa_debit) {
46
+ if (charge.payment_method_details.sepa_debit.last4) {
47
+ if (charge.payment_method_details.sepa_debit.country === 'BE') {
48
+ payment.iban = charge.payment_method_details.sepa_debit.country + '•• ' + charge.payment_method_details.sepa_debit.bank_code + '• •••• ' + charge.payment_method_details.sepa_debit.last4;
49
+ }
50
+ else {
51
+ payment.iban = '•••• ' + charge.payment_method_details.sepa_debit.last4;
52
+ }
43
53
  }
44
54
  }
45
55
  await payment.save();
@@ -139,7 +149,7 @@ export class StripeHelper {
139
149
 
140
150
  console.log('session', session);
141
151
 
142
- if (session.status === 'complete') {
152
+ if (session.payment_status === 'paid') {
143
153
  // This is a direct charge
144
154
  const payment_intent = session.payment_intent;
145
155
  if (payment_intent !== null && typeof payment_intent !== 'string') {
@@ -158,7 +168,7 @@ export class StripeHelper {
158
168
  // Cancel the session
159
169
  const session = await stripe.checkout.sessions.expire(model.stripeSessionId);
160
170
 
161
- if (session.status === 'complete') {
171
+ if (session.payment_status === 'paid') {
162
172
  return PaymentStatus.Succeeded;
163
173
  }
164
174
  if (session.status === 'expired') {
@@ -166,6 +176,11 @@ export class StripeHelper {
166
176
  }
167
177
  }
168
178
 
179
+ if (session.status === 'complete') {
180
+ // Small difference to detect if the payment is almost done
181
+ return PaymentStatus.Pending;
182
+ }
183
+
169
184
  return PaymentStatus.Created;
170
185
  }
171
186
 
@@ -231,10 +246,18 @@ export class StripeHelper {
231
246
  // Bancontact or iDEAL: use payment intends
232
247
  if (payment.method === PaymentMethod.Bancontact || payment.method === PaymentMethod.iDEAL) {
233
248
  const paymentMethod = await stripe.paymentMethods.create({
234
- type: payment.method.toLowerCase() as 'bancontact',
249
+ type: payment.method.toLowerCase() as 'bancontact' | 'ideal',
235
250
  billing_details: {
236
- name: customer.name && customer.name.length > 2 ? customer.name : 'Onbekend',
237
- email: customer.email,
251
+ name: payment.customer?.dynamicName || (customer.name.length > 2 ? customer.name : 'Onbekend'),
252
+ email: payment.customer?.dynamicEmail || customer.email,
253
+ address: payment.customer?.company?.address
254
+ ? {
255
+ city: payment.customer.company.address.city,
256
+ country: payment.customer.company.address.country,
257
+ line1: payment.customer.company.address.street + ' ' + payment.customer.company.address.number,
258
+ postal_code: payment.customer.company.address.postalCode,
259
+ }
260
+ : undefined,
238
261
  },
239
262
  });
240
263
 
@@ -315,7 +338,7 @@ export class StripeHelper {
315
338
  mode: 'payment',
316
339
  success_url: redirectUrl,
317
340
  cancel_url: cancelUrl,
318
- payment_method_types: ['card'],
341
+ payment_method_types: payment.method === PaymentMethod.DirectDebit ? ['sepa_debit'] : ['card'],
319
342
  line_items: stripeLineItems,
320
343
  currency: 'eur',
321
344
  locale: i18n.language as 'nl',
@@ -329,8 +352,10 @@ export class StripeHelper {
329
352
  : undefined,
330
353
  metadata: fullMetadata,
331
354
  statement_descriptor: Formatter.slug(statementDescriptor).substring(0, 22).toUpperCase(),
355
+
332
356
  },
333
- customer_email: customer.email,
357
+ customer_email: payment.customer?.dynamicEmail || customer.email,
358
+ customer_creation: 'if_required',
334
359
  metadata: fullMetadata,
335
360
  expires_at: Math.floor(Date.now() / 1000) + 30 * 60, // Expire in 30 minutes
336
361
  });
@@ -0,0 +1,26 @@
1
+ import { baseSQLFilterCompilers, createSQLColumnFilterCompiler, SQLFilterDefinitions } from '@stamhoofd/sql';
2
+
3
+ export const orderFilterCompilers: SQLFilterDefinitions = {
4
+ ...baseSQLFilterCompilers,
5
+ id: createSQLColumnFilterCompiler('id'),
6
+ organizationId: createSQLColumnFilterCompiler('organizationId'),
7
+ number: createSQLColumnFilterCompiler('number'),
8
+ // 'startDate': createSQLColumnFilterCompiler('startDate'),
9
+ // 'endDate': createSQLColumnFilterCompiler('endDate'),
10
+ // 'groupIds': createSQLExpressionFilterCompiler(
11
+ // SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
12
+ // { isJSONValue: true, isJSONObject: true },
13
+ // ),
14
+ // 'defaultAgeGroupIds': createSQLExpressionFilterCompiler(
15
+ // SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
16
+ // { isJSONValue: true, isJSONObject: true },
17
+ // ),
18
+ // 'organizationTagIds': createSQLExpressionFilterCompiler(
19
+ // SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
20
+ // { isJSONValue: true, isJSONObject: true },
21
+ // ),
22
+ // 'meta.visible': createSQLExpressionFilterCompiler(
23
+ // SQL.jsonValue(SQL.column('meta'), '$.value.visible'),
24
+ // { isJSONValue: true, type: SQLValueType.JSONBoolean },
25
+ // ),
26
+ };
@@ -3,7 +3,7 @@ import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterComp
3
3
  /**
4
4
  * Defines how to filter cached balance items in the database from StamhoofdFilter objects
5
5
  */
6
- export const cachedOutstandingBalanceFilterCompilers: SQLFilterDefinitions = {
6
+ export const receivableBalanceFilterCompilers: SQLFilterDefinitions = {
7
7
  ...baseSQLFilterCompilers,
8
8
  id: createSQLColumnFilterCompiler('id'),
9
9
  organizationId: createSQLColumnFilterCompiler('organizationId'),
@@ -0,0 +1,47 @@
1
+ import { Order } from '@stamhoofd/models';
2
+ import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
3
+ import { Formatter } from '@stamhoofd/utility';
4
+
5
+ export const orderSorters: SQLSortDefinitions<Order> = {
6
+ // WARNING! TEST NEW SORTERS THOROUGHLY!
7
+ // Try to avoid creating sorters on fields that er not 1:1 with the database, that often causes pagination issues if not thought through
8
+ // An example: sorting on 'name' is not a good idea, because it is a concatenation of two fields.
9
+ // You might be tempted to use ORDER BY firstName, lastName, but that will not work as expected and it needs to be ORDER BY CONCAT(firstName, ' ', lastName)
10
+ // Why? Because ORDER BY firstName, lastName produces a different order dan ORDER BY CONCAT(firstName, ' ', lastName) if there are multiple people with spaces in the first name
11
+ // And that again causes issues with pagination because the next query will append a filter of name > 'John Doe' - causing duplicate and/or skipped results
12
+ // What if you need mapping? simply map the sorters in the frontend: name -> firstname, lastname, age -> birthDay, etc.
13
+
14
+ number: {
15
+ getValue(a) {
16
+ return a.number;
17
+ },
18
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
19
+ return new SQLOrderBy({
20
+ column: SQL.column('number'),
21
+ direction,
22
+ });
23
+ },
24
+ },
25
+ id: {
26
+ getValue(a) {
27
+ return a.id;
28
+ },
29
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
30
+ return new SQLOrderBy({
31
+ column: SQL.column('id'),
32
+ direction,
33
+ });
34
+ },
35
+ },
36
+ createdAt: {
37
+ getValue(a) {
38
+ return Formatter.dateTimeIso(a.createdAt);
39
+ },
40
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
41
+ return new SQLOrderBy({
42
+ column: SQL.column('createdAt'),
43
+ direction,
44
+ });
45
+ },
46
+ },
47
+ };
@@ -1,7 +1,7 @@
1
- import { CachedOutstandingBalance } from '@stamhoofd/models';
1
+ import { CachedBalance } from '@stamhoofd/models';
2
2
  import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
3
3
 
4
- export const cachedOutstandingBalanceSorters: SQLSortDefinitions<CachedOutstandingBalance> = {
4
+ export const receivableBalanceSorters: SQLSortDefinitions<CachedBalance> = {
5
5
  // WARNING! TEST NEW SORTERS THOROUGHLY!
6
6
  // Try to avoid creating sorters on fields that er not 1:1 with the database, that often causes pagination issues if not thought through
7
7
  // An example: sorting on 'name' is not a good idea, because it is a concatenation of two fields.