@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.
Files changed (198) hide show
  1. package/eslint.config.mjs +5 -0
  2. package/index.ts +81 -74
  3. package/jest.config.cjs +10 -0
  4. package/migrations.ts +16 -14
  5. package/package.json +11 -11
  6. package/src/crons/clear-excel-cache.test.ts +48 -50
  7. package/src/crons/clear-excel-cache.ts +18 -18
  8. package/src/crons/setup-steps.ts +2 -2
  9. package/src/crons.ts +325 -306
  10. package/src/decoders/StringArrayDecoder.ts +7 -7
  11. package/src/decoders/StringNullableDecoder.ts +1 -2
  12. package/src/email-recipient-loaders/members.ts +22 -22
  13. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
  14. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
  15. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
  16. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
  17. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
  18. package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
  19. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
  20. package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
  21. package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
  22. package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
  23. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
  24. package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
  25. package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
  26. package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
  27. package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
  28. package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
  29. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
  30. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
  31. package/src/endpoints/auth/SignupEndpoint.ts +37 -36
  32. package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
  33. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
  34. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
  35. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
  36. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
  37. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
  38. package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
  39. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
  40. package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
  41. package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
  42. package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
  43. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
  44. package/src/endpoints/global/files/GetFileCache.ts +13 -13
  45. package/src/endpoints/global/files/UploadFile.ts +51 -54
  46. package/src/endpoints/global/files/UploadImage.ts +53 -53
  47. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
  48. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
  49. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
  50. package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
  51. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
  52. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
  53. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
  54. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
  55. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
  56. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
  57. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
  58. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
  59. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
  60. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
  61. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
  62. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
  63. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
  64. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
  65. package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
  66. package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
  67. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
  68. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
  69. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
  70. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
  71. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
  72. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
  73. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
  74. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
  75. package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
  76. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
  77. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
  78. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
  79. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
  80. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
  81. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
  82. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
  83. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
  84. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
  85. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
  86. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
  87. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
  88. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
  89. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
  90. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
  91. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
  92. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
  93. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
  94. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
  95. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
  96. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
  97. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
  98. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
  99. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
  100. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
  101. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
  102. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
  103. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
  104. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
  105. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
  106. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
  107. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
  108. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
  109. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
  110. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
  111. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
  112. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
  113. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
  114. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
  115. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
  116. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
  117. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
  118. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
  119. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
  120. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
  121. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
  122. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
  123. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
  124. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
  125. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
  126. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
  127. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
  128. package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
  129. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
  130. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
  131. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
  132. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
  133. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
  134. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
  135. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
  136. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
  137. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
  138. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
  139. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
  140. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
  141. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
  142. package/src/excel-loaders/members.ts +102 -103
  143. package/src/excel-loaders/payments.ts +155 -156
  144. package/src/helpers/AddressValidator.test.ts +32 -32
  145. package/src/helpers/AddressValidator.ts +128 -122
  146. package/src/helpers/AdminPermissionChecker.ts +339 -236
  147. package/src/helpers/AuthenticatedStructures.ts +233 -134
  148. package/src/helpers/BuckarooHelper.ts +134 -134
  149. package/src/helpers/CheckSettlements.ts +94 -88
  150. package/src/helpers/Context.ts +87 -86
  151. package/src/helpers/CookieHelper.ts +23 -22
  152. package/src/helpers/EmailResumer.ts +10 -10
  153. package/src/helpers/FileCache.ts +62 -62
  154. package/src/helpers/ForwardHandler.test.ts +122 -124
  155. package/src/helpers/ForwardHandler.ts +76 -70
  156. package/src/helpers/MemberUserSyncer.ts +101 -96
  157. package/src/helpers/MembershipCharger.ts +69 -69
  158. package/src/helpers/MembershipHelper.ts +11 -12
  159. package/src/helpers/OpenIDConnectHelper.ts +85 -82
  160. package/src/helpers/PeriodHelper.ts +65 -70
  161. package/src/helpers/StripeHelper.ts +146 -137
  162. package/src/helpers/StripePayoutChecker.ts +51 -52
  163. package/src/helpers/ViesHelper.ts +46 -44
  164. package/src/helpers/fetchToAsyncIterator.ts +14 -14
  165. package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
  166. package/src/middleware/ContextMiddleware.ts +5 -5
  167. package/src/migrations/1646578856-validate-addresses.ts +6 -9
  168. package/src/seeds/0000000000-example.ts +3 -5
  169. package/src/seeds/1715028563-user-permissions.ts +16 -18
  170. package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
  171. package/src/seeds/1722344162-sync-member-users.ts +14 -15
  172. package/src/seeds/1722344162-update-membership.ts +6 -6
  173. package/src/seeds/1726055544-balance-item-paid.ts +4 -4
  174. package/src/seeds/1726055545-balance-item-pending.ts +4 -4
  175. package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
  176. package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
  177. package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
  178. package/src/seeds/1726847064-setup-steps.ts +16 -0
  179. package/src/sql-filters/balance-item-payments.ts +7 -7
  180. package/src/sql-filters/events.ts +14 -14
  181. package/src/sql-filters/members.ts +96 -96
  182. package/src/sql-filters/organizations.ts +139 -75
  183. package/src/sql-filters/payments.ts +28 -28
  184. package/src/sql-filters/registrations.ts +14 -14
  185. package/src/sql-sorters/events.ts +25 -25
  186. package/src/sql-sorters/members.ts +26 -26
  187. package/src/sql-sorters/organizations.ts +36 -36
  188. package/src/sql-sorters/payments.ts +26 -26
  189. package/tests/e2e/stock.test.ts +616 -621
  190. package/tests/e2e/tickets.test.ts +255 -260
  191. package/tests/helpers/StripeMocker.ts +177 -179
  192. package/tests/helpers/TestServer.ts +9 -9
  193. package/tests/jest.global.setup.ts +14 -13
  194. package/tests/jest.setup.ts +33 -32
  195. package/.eslintrc.js +0 -61
  196. package/jest.config.js +0 -11
  197. package/src/helpers/SetupStepsUpdater.ts +0 -359
  198. 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 = "xxxx " + charge.payment_method_details.bancontact.iban_last4
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 = "xxxx " + charge.payment_method_details.ideal.iban_last4
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 = "xxxx " + charge.payment_method_details.card.last4
42
+ payment.iban = 'xxxx ' + charge.payment_method_details.card.last4;
43
43
  }
44
44
  }
45
- await payment.save()
46
- } catch (e) {
47
- console.error('Failed processing charge', e)
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 === "succeeded") {
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("sk_test_")) {
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 === "succeeded") {
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 === "canceled" || intent.status === "requires_payment_method") {
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 === "succeeded") {
110
- return PaymentStatus.Succeeded
110
+ if (intent.status === 'succeeded') {
111
+ return PaymentStatus.Succeeded;
111
112
  }
112
- if (intent.status === "canceled" || intent.status === "requires_payment_method") {
113
- return PaymentStatus.Failed
113
+ if (intent.status === 'canceled' || intent.status === 'requires_payment_method') {
114
+ return PaymentStatus.Failed;
114
115
  }
115
- } catch (e) {
116
- console.error('Error cancelling payment intent', e)
116
+ }
117
+ catch (e) {
118
+ console.error('Error cancelling payment intent', e);
117
119
  }
118
120
  }
119
121
 
120
- if (intent.status === "processing") {
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("session", session);
140
+ console.log('session', session);
139
141
 
140
- if (session.status === "complete") {
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 === "expired") {
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 === "complete") {
160
- return PaymentStatus.Succeeded
161
+ if (session.status === 'complete') {
162
+ return PaymentStatus.Succeeded;
161
163
  }
162
- if (session.status === "expired") {
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: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is onbeschikbaar"
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
- } else if (payment.method === PaymentMethod.Bancontact) {
207
+ }
208
+ else if (payment.method === PaymentMethod.Bancontact) {
206
209
  fee = calculateFee(24, 20); // € 0,24 + 0,2%
207
- } else {
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
- destination: stripeAccount.accountId,
249
- } : undefined,
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("Stripe payment intent", paymentIntent)
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("Stripe payment intent status is not requires_action", paymentIntent)
264
+ console.error('Stripe payment intent status is not requires_action', paymentIntent);
259
265
  throw new SimpleError({
260
- code: "invalid_status",
261
- message: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is onbeschikbaar"
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
- } else {
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: "invalid_price",
300
- message: "De totale prijs van de betaling komt niet overeen met de prijs van de items",
301
- human: "Er ging iets mis bij het aanmaken van de betaling. Probeer opnieuw of gebruik een andere betaalmethode.",
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: ["card"],
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
- destination: stripeAccount.accountId,
320
- } : undefined,
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("Stripe session", session)
337
+ console.log('Stripe session', session);
329
338
 
330
339
  if (!session.url) {
331
- console.error("Stripe session has no url", session)
340
+ console.error('Stripe session has no url', session);
332
341
  throw new SimpleError({
333
- code: "invalid_status",
334
- message: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is onbeschikbaar"
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
  }