@stamhoofd/backend 2.39.0 → 2.40.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/eslint.config.mjs +5 -0
- package/index.ts +81 -74
- package/jest.config.cjs +10 -0
- package/migrations.ts +16 -14
- package/package.json +11 -11
- package/src/crons/clear-excel-cache.test.ts +48 -50
- package/src/crons/clear-excel-cache.ts +18 -18
- package/src/crons/setup-steps.ts +2 -2
- package/src/crons.ts +325 -306
- package/src/decoders/StringArrayDecoder.ts +7 -7
- package/src/decoders/StringNullableDecoder.ts +1 -2
- package/src/email-recipient-loaders/members.ts +22 -22
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
- package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
- package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
- package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
- package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
- package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
- package/src/endpoints/auth/SignupEndpoint.ts +37 -36
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
- package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
- package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
- package/src/endpoints/global/files/GetFileCache.ts +13 -13
- package/src/endpoints/global/files/UploadFile.ts +51 -54
- package/src/endpoints/global/files/UploadImage.ts +53 -53
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
- package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
- package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
- package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
- package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
- package/src/excel-loaders/members.ts +102 -103
- package/src/excel-loaders/payments.ts +155 -156
- package/src/helpers/AddressValidator.test.ts +32 -32
- package/src/helpers/AddressValidator.ts +128 -122
- package/src/helpers/AdminPermissionChecker.ts +339 -236
- package/src/helpers/AuthenticatedStructures.ts +233 -134
- package/src/helpers/BuckarooHelper.ts +134 -134
- package/src/helpers/CheckSettlements.ts +94 -88
- package/src/helpers/Context.ts +87 -86
- package/src/helpers/CookieHelper.ts +23 -22
- package/src/helpers/EmailResumer.ts +10 -10
- package/src/helpers/FileCache.ts +62 -62
- package/src/helpers/ForwardHandler.test.ts +122 -124
- package/src/helpers/ForwardHandler.ts +76 -70
- package/src/helpers/MemberUserSyncer.ts +101 -96
- package/src/helpers/MembershipCharger.ts +69 -69
- package/src/helpers/MembershipHelper.ts +11 -12
- package/src/helpers/OpenIDConnectHelper.ts +85 -82
- package/src/helpers/PeriodHelper.ts +65 -70
- package/src/helpers/StripeHelper.ts +146 -137
- package/src/helpers/StripePayoutChecker.ts +51 -52
- package/src/helpers/ViesHelper.ts +46 -44
- package/src/helpers/fetchToAsyncIterator.ts +14 -14
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
- package/src/middleware/ContextMiddleware.ts +5 -5
- package/src/migrations/1646578856-validate-addresses.ts +6 -9
- package/src/seeds/0000000000-example.ts +3 -5
- package/src/seeds/1715028563-user-permissions.ts +16 -18
- package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
- package/src/seeds/1722344162-sync-member-users.ts +14 -15
- package/src/seeds/1722344162-update-membership.ts +6 -6
- package/src/seeds/1726055544-balance-item-paid.ts +4 -4
- package/src/seeds/1726055545-balance-item-pending.ts +4 -4
- package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
- package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
- package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
- package/src/seeds/1726847064-setup-steps.ts +16 -0
- package/src/sql-filters/balance-item-payments.ts +7 -7
- package/src/sql-filters/events.ts +14 -14
- package/src/sql-filters/members.ts +96 -96
- package/src/sql-filters/organizations.ts +139 -75
- package/src/sql-filters/payments.ts +28 -28
- package/src/sql-filters/registrations.ts +14 -14
- package/src/sql-sorters/events.ts +25 -25
- package/src/sql-sorters/members.ts +26 -26
- package/src/sql-sorters/organizations.ts +36 -36
- package/src/sql-sorters/payments.ts +26 -26
- package/tests/e2e/stock.test.ts +616 -621
- package/tests/e2e/tickets.test.ts +255 -260
- package/tests/helpers/StripeMocker.ts +177 -179
- package/tests/helpers/TestServer.ts +9 -9
- package/tests/jest.global.setup.ts +14 -13
- package/tests/jest.setup.ts +33 -32
- package/.eslintrc.js +0 -61
- package/jest.config.js +0 -11
- package/src/helpers/SetupStepsUpdater.ts +0 -359
- package/src/seeds/1724076679-setup-steps.ts +0 -16
|
@@ -6,15 +6,15 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
6
6
|
import Stripe from 'stripe';
|
|
7
7
|
|
|
8
8
|
export class StripeHelper {
|
|
9
|
-
static getInstance(accountId: string|null = null) {
|
|
10
|
-
return new Stripe(STAMHOOFD.STRIPE_SECRET_KEY, {apiVersion: '2024-06-20', typescript: true, maxNetworkRetries: 0, timeout: 10000, stripeAccount: accountId ?? undefined});
|
|
9
|
+
static getInstance(accountId: string | null = null) {
|
|
10
|
+
return new Stripe(STAMHOOFD.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20', typescript: true, maxNetworkRetries: 0, timeout: 10000, stripeAccount: accountId ?? undefined });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
static async saveChargeInfo(model: StripePaymentIntent|StripeCheckoutSession, charge: Stripe.Charge, payment: Payment) {
|
|
13
|
+
static async saveChargeInfo(model: StripePaymentIntent | StripeCheckoutSession, charge: Stripe.Charge, payment: Payment) {
|
|
14
14
|
try {
|
|
15
15
|
if (model.accountId) {
|
|
16
16
|
// This is a direct charge
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
if (charge.balance_transaction !== null && typeof charge.balance_transaction !== 'string') {
|
|
19
19
|
const fees = charge.balance_transaction.fee;
|
|
20
20
|
payment.transferFee = fees;
|
|
@@ -22,29 +22,30 @@ export class StripeHelper {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
if (charge.billing_details.name) {
|
|
25
|
-
payment.ibanName = charge.billing_details.name
|
|
25
|
+
payment.ibanName = charge.billing_details.name;
|
|
26
26
|
}
|
|
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 = 'xxxx ' + charge.payment_method_details.bancontact.iban_last4;
|
|
31
31
|
}
|
|
32
|
-
payment.ibanName = charge.payment_method_details.bancontact.verified_name
|
|
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 = 'xxxx ' + charge.payment_method_details.ideal.iban_last4;
|
|
37
37
|
}
|
|
38
|
-
payment.ibanName = charge.payment_method_details.ideal.verified_name
|
|
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 = 'xxxx ' + charge.payment_method_details.card.last4;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
await payment.save()
|
|
46
|
-
}
|
|
47
|
-
|
|
45
|
+
await payment.save();
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error('Failed processing charge', e);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -52,159 +53,162 @@ export class StripeHelper {
|
|
|
52
53
|
* Call when the charge is updated in Stripe, so we can save fee information in the payment
|
|
53
54
|
*/
|
|
54
55
|
static async updateChargeInfo(model: StripePaymentIntent) {
|
|
55
|
-
const stripe = this.getInstance(model.accountId)
|
|
56
|
+
const stripe = this.getInstance(model.accountId);
|
|
56
57
|
|
|
57
58
|
const intent = await stripe.paymentIntents.retrieve(model.stripeIntentId, {
|
|
58
|
-
expand: ['latest_charge.balance_transaction']
|
|
59
|
-
})
|
|
59
|
+
expand: ['latest_charge.balance_transaction'],
|
|
60
|
+
});
|
|
60
61
|
|
|
61
62
|
console.log(intent);
|
|
62
|
-
if (intent.status ===
|
|
63
|
+
if (intent.status === 'succeeded') {
|
|
63
64
|
if (intent.latest_charge !== null && typeof intent.latest_charge !== 'string') {
|
|
64
|
-
const payment = await Payment.getByID(model.paymentId)
|
|
65
|
+
const payment = await Payment.getByID(model.paymentId);
|
|
65
66
|
if (payment) {
|
|
66
|
-
await this.saveChargeInfo(model, intent.latest_charge, payment)
|
|
67
|
+
await this.saveChargeInfo(model, intent.latest_charge, payment);
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
static async getStatus(payment: Payment, cancel = false, testMode = false): Promise<PaymentStatus> {
|
|
73
|
-
if (testMode && !STAMHOOFD.STRIPE_SECRET_KEY.startsWith(
|
|
74
|
+
if (testMode && !STAMHOOFD.STRIPE_SECRET_KEY.startsWith('sk_test_')) {
|
|
74
75
|
// Do not query anything
|
|
75
|
-
return payment.status
|
|
76
|
+
return payment.status;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
const [model] = await StripePaymentIntent.where({paymentId: payment.id}, {limit: 1})
|
|
79
|
+
const [model] = await StripePaymentIntent.where({ paymentId: payment.id }, { limit: 1 });
|
|
79
80
|
|
|
80
81
|
if (!model) {
|
|
81
|
-
return await this.getStatusFromCheckoutSession(payment, cancel)
|
|
82
|
+
return await this.getStatusFromCheckoutSession(payment, cancel);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
const stripe = this.getInstance(model.accountId)
|
|
85
|
+
const stripe = this.getInstance(model.accountId);
|
|
85
86
|
|
|
86
87
|
let intent = await stripe.paymentIntents.retrieve(model.stripeIntentId, {
|
|
87
|
-
expand: ['latest_charge.balance_transaction']
|
|
88
|
-
})
|
|
88
|
+
expand: ['latest_charge.balance_transaction'],
|
|
89
|
+
});
|
|
89
90
|
console.log(intent);
|
|
90
|
-
if (intent.status ===
|
|
91
|
+
if (intent.status === 'succeeded') {
|
|
91
92
|
if (intent.latest_charge !== null && typeof intent.latest_charge !== 'string') {
|
|
92
|
-
await this.saveChargeInfo(model, intent.latest_charge, payment)
|
|
93
|
+
await this.saveChargeInfo(model, intent.latest_charge, payment);
|
|
93
94
|
}
|
|
94
|
-
return PaymentStatus.Succeeded
|
|
95
|
+
return PaymentStatus.Succeeded;
|
|
95
96
|
}
|
|
96
|
-
if (intent.status ===
|
|
97
|
+
if (intent.status === 'canceled' || intent.status === 'requires_payment_method') {
|
|
97
98
|
// For Bnaconctact/iDEAL the payment status is reverted to requires_payment_method when the user cancels the payment
|
|
98
99
|
// Don't ask me why...
|
|
99
|
-
return PaymentStatus.Failed
|
|
100
|
+
return PaymentStatus.Failed;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
if (cancel) {
|
|
103
104
|
try {
|
|
104
105
|
// Cancel the intent
|
|
105
|
-
console.log('Cancelling payment intent')
|
|
106
|
-
intent = await stripe.paymentIntents.cancel(model.stripeIntentId)
|
|
107
|
-
console.log('Cancelled payment intent', intent)
|
|
106
|
+
console.log('Cancelling payment intent');
|
|
107
|
+
intent = await stripe.paymentIntents.cancel(model.stripeIntentId);
|
|
108
|
+
console.log('Cancelled payment intent', intent);
|
|
108
109
|
|
|
109
|
-
if (intent.status ===
|
|
110
|
-
return PaymentStatus.Succeeded
|
|
110
|
+
if (intent.status === 'succeeded') {
|
|
111
|
+
return PaymentStatus.Succeeded;
|
|
111
112
|
}
|
|
112
|
-
if (intent.status ===
|
|
113
|
-
return PaymentStatus.Failed
|
|
113
|
+
if (intent.status === 'canceled' || intent.status === 'requires_payment_method') {
|
|
114
|
+
return PaymentStatus.Failed;
|
|
114
115
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.error('Error cancelling payment intent', e);
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
if (intent.status ===
|
|
121
|
-
return PaymentStatus.Pending
|
|
122
|
+
if (intent.status === 'processing') {
|
|
123
|
+
return PaymentStatus.Pending;
|
|
122
124
|
}
|
|
123
|
-
return PaymentStatus.Created
|
|
125
|
+
return PaymentStatus.Created;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
static async getStatusFromCheckoutSession(payment: Payment, cancel = false): Promise<PaymentStatus> {
|
|
127
|
-
const [model] = await StripeCheckoutSession.where({paymentId: payment.id}, {limit: 1})
|
|
129
|
+
const [model] = await StripeCheckoutSession.where({ paymentId: payment.id }, { limit: 1 });
|
|
128
130
|
|
|
129
131
|
if (!model) {
|
|
130
|
-
return PaymentStatus.Failed
|
|
132
|
+
return PaymentStatus.Failed;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
const stripe = this.getInstance(model.accountId)
|
|
135
|
+
const stripe = this.getInstance(model.accountId);
|
|
134
136
|
const session = await stripe.checkout.sessions.retrieve(model.stripeSessionId, {
|
|
135
|
-
expand: ['payment_intent.latest_charge.balance_transaction']
|
|
136
|
-
})
|
|
137
|
+
expand: ['payment_intent.latest_charge.balance_transaction'],
|
|
138
|
+
});
|
|
137
139
|
|
|
138
|
-
console.log(
|
|
140
|
+
console.log('session', session);
|
|
139
141
|
|
|
140
|
-
if (session.status ===
|
|
142
|
+
if (session.status === 'complete') {
|
|
141
143
|
// This is a direct charge
|
|
142
|
-
const payment_intent = session.payment_intent
|
|
144
|
+
const payment_intent = session.payment_intent;
|
|
143
145
|
if (payment_intent !== null && typeof payment_intent !== 'string') {
|
|
144
|
-
const charge = payment_intent.latest_charge
|
|
146
|
+
const charge = payment_intent.latest_charge;
|
|
145
147
|
if (charge !== null && typeof charge !== 'string') {
|
|
146
|
-
await this.saveChargeInfo(model, charge, payment)
|
|
148
|
+
await this.saveChargeInfo(model, charge, payment);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
return PaymentStatus.Succeeded
|
|
151
|
+
return PaymentStatus.Succeeded;
|
|
150
152
|
}
|
|
151
|
-
if (session.status ===
|
|
152
|
-
return PaymentStatus.Failed
|
|
153
|
+
if (session.status === 'expired') {
|
|
154
|
+
return PaymentStatus.Failed;
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
if (cancel) {
|
|
156
158
|
// Cancel the session
|
|
157
|
-
const session = await stripe.checkout.sessions.expire(model.stripeSessionId)
|
|
159
|
+
const session = await stripe.checkout.sessions.expire(model.stripeSessionId);
|
|
158
160
|
|
|
159
|
-
if (session.status ===
|
|
160
|
-
return PaymentStatus.Succeeded
|
|
161
|
+
if (session.status === 'complete') {
|
|
162
|
+
return PaymentStatus.Succeeded;
|
|
161
163
|
}
|
|
162
|
-
if (session.status ===
|
|
163
|
-
return PaymentStatus.Failed
|
|
164
|
+
if (session.status === 'expired') {
|
|
165
|
+
return PaymentStatus.Failed;
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
return PaymentStatus.Created
|
|
169
|
+
return PaymentStatus.Created;
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
static async createPayment(
|
|
171
|
-
{payment, stripeAccount, redirectUrl, cancelUrl, customer, statementDescriptor, i18n, metadata, organization, lineItems}: {
|
|
172
|
-
payment: Payment
|
|
173
|
-
stripeAccount: StripeAccount | null
|
|
174
|
-
redirectUrl: string
|
|
175
|
-
cancelUrl: string
|
|
173
|
+
{ payment, stripeAccount, redirectUrl, cancelUrl, customer, statementDescriptor, i18n, metadata, organization, lineItems }: {
|
|
174
|
+
payment: Payment;
|
|
175
|
+
stripeAccount: StripeAccount | null;
|
|
176
|
+
redirectUrl: string;
|
|
177
|
+
cancelUrl: string;
|
|
176
178
|
customer: {
|
|
177
|
-
name: string
|
|
178
|
-
email: string
|
|
179
|
-
}
|
|
180
|
-
statementDescriptor: string
|
|
181
|
-
i18n: I18n
|
|
182
|
-
metadata: {[key: string]: string}
|
|
183
|
-
organization: Organization
|
|
184
|
-
lineItems: (BalanceItemPayment & {balanceItem: BalanceItem})[]
|
|
185
|
-
}
|
|
186
|
-
): Promise<{paymentUrl: string}> {
|
|
179
|
+
name: string;
|
|
180
|
+
email: string;
|
|
181
|
+
};
|
|
182
|
+
statementDescriptor: string;
|
|
183
|
+
i18n: I18n;
|
|
184
|
+
metadata: { [key: string]: string };
|
|
185
|
+
organization: Organization;
|
|
186
|
+
lineItems: (BalanceItemPayment & { balanceItem: BalanceItem })[];
|
|
187
|
+
},
|
|
188
|
+
): Promise<{ paymentUrl: string }> {
|
|
187
189
|
if (!stripeAccount) {
|
|
188
190
|
throw new SimpleError({
|
|
189
|
-
code:
|
|
190
|
-
message:
|
|
191
|
-
})
|
|
191
|
+
code: '',
|
|
192
|
+
message: 'Betaling via ' + PaymentMethodHelper.getName(payment.method) + ' is onbeschikbaar',
|
|
193
|
+
});
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
const totalPrice = payment.price;
|
|
195
197
|
|
|
196
198
|
let fee = 0;
|
|
197
199
|
let directCharge = false;
|
|
198
|
-
const vat = calculateVATPercentage(organization.address, organization.meta.VATNumber)
|
|
200
|
+
const vat = calculateVATPercentage(organization.address, organization.meta.VATNumber);
|
|
199
201
|
function calculateFee(fixed: number, percentageTimes100: number) {
|
|
200
202
|
return Math.round(Math.round(fixed + Math.max(1, totalPrice * percentageTimes100 / 100 / 100)) * (100 + vat) / 100); // € 0,21 + 0,2%
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
if (payment.method === PaymentMethod.iDEAL) {
|
|
204
206
|
fee = calculateFee(21, 20); // € 0,21 + 0,2%
|
|
205
|
-
}
|
|
207
|
+
}
|
|
208
|
+
else if (payment.method === PaymentMethod.Bancontact) {
|
|
206
209
|
fee = calculateFee(24, 20); // € 0,24 + 0,2%
|
|
207
|
-
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
208
212
|
fee = calculateFee(25, 150); // € 0,25 + 1,5%
|
|
209
213
|
}
|
|
210
214
|
|
|
@@ -218,11 +222,11 @@ export class StripeHelper {
|
|
|
218
222
|
|
|
219
223
|
const fullMetadata = {
|
|
220
224
|
...(metadata ?? {}),
|
|
221
|
-
organizationVATNumber: organization.meta.VATNumber
|
|
222
|
-
}
|
|
225
|
+
organizationVATNumber: organization.meta.VATNumber,
|
|
226
|
+
};
|
|
223
227
|
|
|
224
|
-
const stripe = StripeHelper.getInstance(directCharge ? stripeAccount.accountId : null)
|
|
225
|
-
let paymentUrl: string
|
|
228
|
+
const stripe = StripeHelper.getInstance(directCharge ? stripeAccount.accountId : null);
|
|
229
|
+
let paymentUrl: string;
|
|
226
230
|
|
|
227
231
|
// Bancontact or iDEAL: use payment intends
|
|
228
232
|
if (payment.method === PaymentMethod.Bancontact || payment.method === PaymentMethod.iDEAL) {
|
|
@@ -230,9 +234,9 @@ export class StripeHelper {
|
|
|
230
234
|
type: payment.method.toLowerCase() as 'bancontact',
|
|
231
235
|
billing_details: {
|
|
232
236
|
name: customer.name && customer.name.length > 2 ? customer.name : 'Onbekend',
|
|
233
|
-
email: customer.email
|
|
237
|
+
email: customer.email,
|
|
234
238
|
},
|
|
235
|
-
})
|
|
239
|
+
});
|
|
236
240
|
|
|
237
241
|
const paymentIntent = await stripe.paymentIntents.create({
|
|
238
242
|
amount: totalPrice,
|
|
@@ -244,40 +248,43 @@ export class StripeHelper {
|
|
|
244
248
|
on_behalf_of: !directCharge ? stripeAccount.accountId : undefined,
|
|
245
249
|
confirm: true,
|
|
246
250
|
return_url: redirectUrl,
|
|
247
|
-
transfer_data: !directCharge
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
transfer_data: !directCharge
|
|
252
|
+
? {
|
|
253
|
+
destination: stripeAccount.accountId,
|
|
254
|
+
}
|
|
255
|
+
: undefined,
|
|
250
256
|
metadata: fullMetadata,
|
|
251
|
-
payment_method_options: {bancontact: {preferred_language: ['nl', 'fr', 'de', 'en'].includes(i18n.language) ? i18n.language as 'en' : 'nl'}},
|
|
257
|
+
payment_method_options: { bancontact: { preferred_language: ['nl', 'fr', 'de', 'en'].includes(i18n.language) ? i18n.language as 'en' : 'nl' } },
|
|
252
258
|
});
|
|
253
259
|
|
|
254
|
-
console.log(
|
|
255
|
-
const url = paymentIntent.next_action?.redirect_to_url?.url
|
|
260
|
+
console.log('Stripe payment intent', paymentIntent);
|
|
261
|
+
const url = paymentIntent.next_action?.redirect_to_url?.url;
|
|
256
262
|
|
|
257
263
|
if (paymentIntent.status !== 'requires_action' || !url) {
|
|
258
|
-
console.error(
|
|
264
|
+
console.error('Stripe payment intent status is not requires_action', paymentIntent);
|
|
259
265
|
throw new SimpleError({
|
|
260
|
-
code:
|
|
261
|
-
message:
|
|
262
|
-
})
|
|
266
|
+
code: 'invalid_status',
|
|
267
|
+
message: 'Betaling via ' + PaymentMethodHelper.getName(payment.method) + ' is onbeschikbaar',
|
|
268
|
+
});
|
|
263
269
|
}
|
|
264
270
|
|
|
265
|
-
paymentUrl = url
|
|
271
|
+
paymentUrl = url;
|
|
266
272
|
|
|
267
273
|
// Store in database
|
|
268
|
-
const paymentIntentModel = new StripePaymentIntent()
|
|
269
|
-
paymentIntentModel.paymentId = payment.id
|
|
270
|
-
paymentIntentModel.stripeIntentId = paymentIntent.id
|
|
271
|
-
paymentIntentModel.organizationId = organization.id
|
|
274
|
+
const paymentIntentModel = new StripePaymentIntent();
|
|
275
|
+
paymentIntentModel.paymentId = payment.id;
|
|
276
|
+
paymentIntentModel.stripeIntentId = paymentIntent.id;
|
|
277
|
+
paymentIntentModel.organizationId = organization.id;
|
|
272
278
|
|
|
273
279
|
if (directCharge) {
|
|
274
|
-
paymentIntentModel.accountId = stripeAccount.accountId
|
|
280
|
+
paymentIntentModel.accountId = stripeAccount.accountId;
|
|
275
281
|
}
|
|
276
|
-
await paymentIntentModel.save()
|
|
277
|
-
}
|
|
282
|
+
await paymentIntentModel.save();
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
278
285
|
// Build Stripe line items
|
|
279
|
-
const stripeLineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = []
|
|
280
|
-
let lineItemsPrice = 0
|
|
286
|
+
const stripeLineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [];
|
|
287
|
+
let lineItemsPrice = 0;
|
|
281
288
|
for (const item of lineItems) {
|
|
282
289
|
const stripeLineItem = {
|
|
283
290
|
price_data: {
|
|
@@ -288,19 +295,19 @@ export class StripeHelper {
|
|
|
288
295
|
},
|
|
289
296
|
},
|
|
290
297
|
quantity: 1,
|
|
291
|
-
}
|
|
292
|
-
stripeLineItems.push(stripeLineItem)
|
|
293
|
-
lineItemsPrice += item.price
|
|
298
|
+
};
|
|
299
|
+
stripeLineItems.push(stripeLineItem);
|
|
300
|
+
lineItemsPrice += item.price;
|
|
294
301
|
}
|
|
295
302
|
|
|
296
303
|
if (lineItemsPrice !== totalPrice) {
|
|
297
|
-
console.error('Total price of line items does not match total price of payment', lineItemsPrice, totalPrice, payment.id)
|
|
304
|
+
console.error('Total price of line items does not match total price of payment', lineItemsPrice, totalPrice, payment.id);
|
|
298
305
|
throw new SimpleError({
|
|
299
|
-
code:
|
|
300
|
-
message:
|
|
301
|
-
human:
|
|
302
|
-
statusCode: 500
|
|
303
|
-
})
|
|
306
|
+
code: 'invalid_price',
|
|
307
|
+
message: 'De totale prijs van de betaling komt niet overeen met de prijs van de items',
|
|
308
|
+
human: 'Er ging iets mis bij het aanmaken van de betaling. Probeer opnieuw of gebruik een andere betaalmethode.',
|
|
309
|
+
statusCode: 500,
|
|
310
|
+
});
|
|
304
311
|
}
|
|
305
312
|
|
|
306
313
|
// Use checkout flow
|
|
@@ -308,16 +315,18 @@ export class StripeHelper {
|
|
|
308
315
|
mode: 'payment',
|
|
309
316
|
success_url: redirectUrl,
|
|
310
317
|
cancel_url: cancelUrl,
|
|
311
|
-
payment_method_types: [
|
|
318
|
+
payment_method_types: ['card'],
|
|
312
319
|
line_items: stripeLineItems,
|
|
313
320
|
currency: 'eur',
|
|
314
321
|
locale: i18n.language as 'nl',
|
|
315
322
|
payment_intent_data: {
|
|
316
323
|
on_behalf_of: !directCharge ? stripeAccount.accountId : undefined,
|
|
317
324
|
application_fee_amount: fee ? fee : undefined,
|
|
318
|
-
transfer_data: !directCharge
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
transfer_data: !directCharge
|
|
326
|
+
? {
|
|
327
|
+
destination: stripeAccount.accountId,
|
|
328
|
+
}
|
|
329
|
+
: undefined,
|
|
321
330
|
metadata: fullMetadata,
|
|
322
331
|
statement_descriptor: Formatter.slug(statementDescriptor).substring(0, 22).toUpperCase(),
|
|
323
332
|
},
|
|
@@ -325,33 +334,33 @@ export class StripeHelper {
|
|
|
325
334
|
metadata: fullMetadata,
|
|
326
335
|
expires_at: Math.floor(Date.now() / 1000) + 30 * 60, // Expire in 30 minutes
|
|
327
336
|
});
|
|
328
|
-
console.log(
|
|
337
|
+
console.log('Stripe session', session);
|
|
329
338
|
|
|
330
339
|
if (!session.url) {
|
|
331
|
-
console.error(
|
|
340
|
+
console.error('Stripe session has no url', session);
|
|
332
341
|
throw new SimpleError({
|
|
333
|
-
code:
|
|
334
|
-
message:
|
|
335
|
-
})
|
|
342
|
+
code: 'invalid_status',
|
|
343
|
+
message: 'Betaling via ' + PaymentMethodHelper.getName(payment.method) + ' is onbeschikbaar',
|
|
344
|
+
});
|
|
336
345
|
}
|
|
337
|
-
paymentUrl = session.url
|
|
346
|
+
paymentUrl = session.url;
|
|
338
347
|
|
|
339
348
|
// Store in database
|
|
340
|
-
const paymentIntentModel = new StripeCheckoutSession()
|
|
341
|
-
paymentIntentModel.paymentId = payment.id
|
|
342
|
-
paymentIntentModel.stripeSessionId = session.id
|
|
343
|
-
paymentIntentModel.organizationId = organization.id
|
|
349
|
+
const paymentIntentModel = new StripeCheckoutSession();
|
|
350
|
+
paymentIntentModel.paymentId = payment.id;
|
|
351
|
+
paymentIntentModel.stripeSessionId = session.id;
|
|
352
|
+
paymentIntentModel.organizationId = organization.id;
|
|
344
353
|
|
|
345
354
|
if (directCharge) {
|
|
346
|
-
paymentIntentModel.accountId = stripeAccount.accountId
|
|
355
|
+
paymentIntentModel.accountId = stripeAccount.accountId;
|
|
347
356
|
}
|
|
348
|
-
await paymentIntentModel.save()
|
|
357
|
+
await paymentIntentModel.save();
|
|
349
358
|
}
|
|
350
359
|
|
|
351
|
-
await payment.save()
|
|
360
|
+
await payment.save();
|
|
352
361
|
|
|
353
362
|
return {
|
|
354
|
-
paymentUrl
|
|
355
|
-
}
|
|
363
|
+
paymentUrl,
|
|
364
|
+
};
|
|
356
365
|
}
|
|
357
366
|
}
|