@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.
- package/package.json +10 -10
- package/src/crons/{clear-excel-cache.test.ts → clearExcelCache.test.ts} +1 -1
- package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +18 -0
- package/src/crons.ts +8 -2
- package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +116 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +24 -20
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +10 -0
- package/src/endpoints/global/registration/{GetUserDetailedBillingStatusEndpoint.ts → GetUserDetailedPayableBalanceEndpoint.ts} +10 -8
- package/src/endpoints/global/registration/{GetUserBillingStatusEndpoint.ts → GetUserPayableBalanceEndpoint.ts} +13 -11
- package/src/endpoints/organization/dashboard/billing/{GetOrganizationDetailedBillingStatusEndpoint.ts → GetOrganizationDetailedPayableBalanceEndpoint.ts} +12 -16
- package/src/endpoints/organization/dashboard/billing/{GetOrganizationBillingStatusEndpoint.ts → GetOrganizationPayableBalanceEndpoint.ts} +8 -6
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +98 -0
- package/src/endpoints/organization/dashboard/{cached-outstanding-balance/GetCachedOutstandingBalanceCountEndpoint.ts → receivable-balances/GetReceivableBalancesCountEndpoint.ts} +4 -4
- package/src/endpoints/organization/dashboard/{cached-outstanding-balance/GetCachedOutstandingBalanceEndpoint.ts → receivable-balances/GetReceivableBalancesEndpoint.ts} +18 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +57 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +123 -13
- package/src/helpers/AdminPermissionChecker.ts +11 -2
- package/src/helpers/AuthenticatedStructures.ts +79 -18
- package/src/helpers/FlagMomentCleanup.ts +68 -0
- package/src/helpers/LimitedFilteredRequestHelper.ts +24 -0
- package/src/helpers/OrganizationCharger.ts +46 -0
- package/src/helpers/StripeHelper.ts +35 -10
- package/src/sql-filters/orders.ts +26 -0
- package/src/sql-filters/{cached-outstanding-balance.ts → receivable-balances.ts} +1 -1
- package/src/sql-sorters/orders.ts +47 -0
- package/src/sql-sorters/{cached-outstanding-balance.ts → receivable-balances.ts} +2 -2
- /package/src/crons/{clear-excel-cache.ts → clearExcelCache.ts} +0 -0
- /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 = '
|
|
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 = '
|
|
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 = '
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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 {
|
|
1
|
+
import { CachedBalance } from '@stamhoofd/models';
|
|
2
2
|
import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
3
3
|
|
|
4
|
-
export const
|
|
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.
|
|
File without changes
|
|
File without changes
|