@stamhoofd/backend 2.120.6 → 2.122.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 (434) hide show
  1. package/output-2.txt +1 -0
  2. package/output.txt +1 -0
  3. package/package.json +70 -53
  4. package/src/audit-logs/EventNotificationLogger.ts +40 -0
  5. package/src/audit-logs/ModelLogger.ts +19 -15
  6. package/src/audit-logs/OrganizationLogger.ts +1 -1
  7. package/src/audit-logs/RegistrationInvitationLogger.ts +46 -0
  8. package/src/audit-logs/init.ts +9 -1
  9. package/src/boot.ts +19 -18
  10. package/src/crons/amazon-ses.ts +13 -26
  11. package/src/crons/balance-emails.ts +3 -5
  12. package/src/crons/clearExcelCache.test.ts +1 -1
  13. package/src/crons/clearExcelCache.ts +1 -2
  14. package/src/crons/drip-emails.ts +42 -0
  15. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +4 -0
  16. package/src/crons/helpers/isOutside.ts +11 -0
  17. package/src/crons/helpers/useSavedIterator.ts +32 -0
  18. package/src/crons/index.ts +7 -0
  19. package/src/crons/invoices.ts +160 -0
  20. package/src/crons/members-fees.ts +50 -0
  21. package/src/crons/mollie-chargebacks.ts +86 -0
  22. package/src/crons/service-fees.ts +152 -0
  23. package/src/crons/stripe-invoices.ts +48 -0
  24. package/src/crons/transfer-fees.ts +176 -0
  25. package/src/crons/update-cached-balances.ts +4 -4
  26. package/src/crons.ts +54 -74
  27. package/src/debug.ts +4 -2
  28. package/src/email-recipient-loaders/documents.ts +1 -1
  29. package/src/email-recipient-loaders/members.ts +1 -1
  30. package/src/email-recipient-loaders/payments.ts +87 -46
  31. package/src/email-recipient-loaders/registrations.ts +1 -1
  32. package/src/email-replacements/getEmailReplacementsForPayment.ts +5 -9
  33. package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +1 -1
  34. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
  35. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +3 -5
  36. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +1 -1
  37. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
  38. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +13 -7
  39. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +4 -6
  40. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +10 -6
  41. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +39 -21
  42. package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +1 -1
  43. package/src/endpoints/auth/CreateAdminEndpoint.ts +23 -5
  44. package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -2
  45. package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
  46. package/src/endpoints/auth/DeleteUserEndpoint.ts +3 -3
  47. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  48. package/src/endpoints/auth/GetOtherUserEndpoint.ts +1 -1
  49. package/src/endpoints/auth/GetUserEndpoint.ts +1 -1
  50. package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +1 -1
  51. package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +1 -1
  52. package/src/endpoints/auth/PatchUserEndpoint.ts +5 -8
  53. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +1 -1
  54. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +1 -1
  55. package/src/endpoints/auth/SignupEndpoint.ts +2 -3
  56. package/src/endpoints/auth/VerifyEmailEndpoint.ts +20 -3
  57. package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +2 -1
  58. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +2 -4
  59. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +1 -1
  60. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +5 -7
  61. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +2 -2
  62. package/src/endpoints/global/email/CreateEmailEndpoint.ts +3 -4
  63. package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
  64. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +1 -2
  65. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +4 -6
  66. package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
  67. package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
  68. package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +1 -1
  69. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +6 -10
  70. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +4 -7
  71. package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -9
  72. package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +1 -1
  73. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +1 -1
  74. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +1 -1
  75. package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +2 -2
  76. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +1 -1
  77. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
  78. package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -3
  79. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +49 -4
  80. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +5 -8
  81. package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +12 -44
  82. package/src/endpoints/global/events/PatchEventsEndpoint.ts +128 -127
  83. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -9
  84. package/src/endpoints/global/files/GetFileCache.ts +3 -4
  85. package/src/endpoints/global/files/UploadFile.ts +3 -4
  86. package/src/endpoints/global/files/UploadImage.ts +3 -4
  87. package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +51 -0
  88. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +1 -1
  89. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +4 -2
  90. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -3
  91. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +1 -1
  92. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +2 -2
  93. package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -12
  94. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +195 -12
  95. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +16 -22
  96. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +6 -30
  97. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +1 -1
  98. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
  99. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +40 -8
  100. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +2 -2
  101. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -2
  102. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +4 -1
  103. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +4 -5
  104. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +9 -16
  105. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -1
  106. package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -1
  107. package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +1 -1
  108. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +32 -13
  109. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +31 -31
  110. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +18 -20
  111. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +1 -1
  112. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1 -1
  113. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +6 -10
  114. package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +35 -6
  115. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
  116. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +1 -1
  117. package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +1 -1
  118. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  119. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -3
  120. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +90 -6
  121. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +38 -33
  122. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
  123. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +494 -0
  124. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +213 -0
  125. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +404 -0
  126. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +167 -0
  127. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsCountEndpoint.ts +43 -0
  128. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +1 -1
  129. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +13 -5
  130. package/src/endpoints/global/sso/GetSSOEndpoint.ts +2 -3
  131. package/src/endpoints/global/sso/SetSSOEndpoint.ts +1 -1
  132. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
  133. package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +1 -2
  134. package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +2 -4
  135. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +2 -2
  136. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +1 -1
  137. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +3 -4
  138. package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +19 -6
  139. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
  140. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
  141. package/src/endpoints/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +4 -9
  142. package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
  143. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
  144. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +41 -18
  145. package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
  146. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
  147. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableInvoicesEndpoint.ts +68 -0
  148. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +12 -4
  149. package/src/endpoints/organization/dashboard/billing/GetRegisterCodeEndpoint.ts +66 -0
  150. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.test.ts +949 -0
  151. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +311 -0
  152. package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
  153. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
  154. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +1 -1
  155. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +1 -1
  156. package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
  157. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +1 -1
  158. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +10 -2
  159. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +2 -2
  160. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +2 -2
  161. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
  162. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +1 -1
  163. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +10 -8
  164. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +1 -1
  165. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +5 -6
  166. package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +1 -1
  167. package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +15 -4
  168. package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
  169. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +8 -8
  170. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +10 -5
  171. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +2 -2
  172. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +7 -7
  173. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +1 -1
  174. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +2 -2
  175. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +1 -1
  176. package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +1 -1
  177. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -2
  178. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +67 -20
  179. package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +2 -2
  180. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +5 -10
  181. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +1 -1
  182. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +1 -1
  183. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +3 -4
  184. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +24 -17
  185. package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +134 -0
  186. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -3
  187. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +1 -1
  188. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -1
  189. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  190. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +2 -3
  191. package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +1 -1
  192. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  193. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +32 -22
  194. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +4 -5
  195. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
  196. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -3
  197. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +2 -2
  198. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +1 -1
  199. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +2 -2
  200. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
  201. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +1 -1
  202. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +1 -1
  203. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +1 -1
  204. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +1 -1
  205. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +4 -6
  206. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +10 -15
  207. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +1 -1
  208. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +2 -2
  209. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
  210. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +1 -1
  211. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
  212. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +11 -2
  213. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +4 -6
  214. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +29 -8
  215. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +21 -28
  216. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
  217. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +2 -2
  218. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -3
  219. package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +95 -0
  220. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +15 -13
  221. package/src/endpoints/organization/shared/GetDocumentHtml.ts +1 -1
  222. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
  223. package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -5
  224. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +1 -1
  225. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -1
  226. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +14 -3
  227. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -2
  228. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +37 -16
  229. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +34 -2
  230. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -2
  231. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +74 -3
  232. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +28 -50
  233. package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -5
  234. package/src/endpoints/organization/webshops/WebshopAuthHelper.ts +36 -0
  235. package/src/endpoints/system/HealthEndpoint.ts +1 -1
  236. package/src/excel-loaders/balance-items.ts +1 -1
  237. package/src/excel-loaders/event-notifications.ts +1 -1
  238. package/src/excel-loaders/members.ts +1 -1
  239. package/src/excel-loaders/organizations.ts +3 -3
  240. package/src/excel-loaders/payments.ts +31 -1
  241. package/src/excel-loaders/platform-memberships.ts +13 -14
  242. package/src/excel-loaders/receivable-balances.ts +2 -2
  243. package/src/excel-loaders/registrations.ts +2 -3
  244. package/src/helpers/AddressValidator.ts +7 -14
  245. package/src/helpers/AdminPermissionChecker.ts +60 -65
  246. package/src/helpers/AuthenticatedStructures.ts +153 -51
  247. package/src/helpers/BalanceItemRelationsBackfiller.test.ts +174 -0
  248. package/src/helpers/BalanceItemRelationsBackfiller.ts +163 -0
  249. package/src/helpers/BuckarooHelper.ts +2 -4
  250. package/src/helpers/CheckSettlements.ts +12 -24
  251. package/src/helpers/Context.ts +15 -10
  252. package/src/helpers/CookieHelper.ts +1 -2
  253. package/src/helpers/EmailResumer.ts +1 -2
  254. package/src/helpers/FileCache.ts +2 -3
  255. package/src/helpers/FinancialSupportHelper.ts +21 -0
  256. package/src/helpers/FlagMomentCleanup.ts +1 -2
  257. package/src/helpers/ForwardHandler.ts +5 -10
  258. package/src/helpers/GlobalHelper.ts +3 -3
  259. package/src/helpers/LegacyBillingConverter.ts +787 -0
  260. package/src/helpers/MemberMerger.test.ts +782 -0
  261. package/src/helpers/MemberMerger.ts +600 -0
  262. package/src/helpers/MemberUserSyncer.ts +6 -12
  263. package/src/helpers/MembershipCharger.ts +2 -4
  264. package/src/helpers/PaymentCustomerResolver.test.ts +203 -0
  265. package/src/helpers/PaymentCustomerResolver.ts +144 -0
  266. package/src/helpers/PeriodHelper.ts +2 -3
  267. package/src/helpers/RecordAnswerHelper.test.ts +741 -0
  268. package/src/helpers/RecordAnswerHelper.ts +116 -0
  269. package/src/helpers/SeedTools.ts +106 -0
  270. package/src/helpers/ServiceFeeHelper.ts +125 -15
  271. package/src/helpers/SetupStepUpdater.ts +38 -32
  272. package/src/helpers/StamhoofdFilterAccessHelper.ts +52 -0
  273. package/src/helpers/StripeHelper.ts +15 -38
  274. package/src/helpers/StripeInvoicer.ts +419 -0
  275. package/src/helpers/StripePayoutChecker.ts +5 -10
  276. package/src/helpers/TagHelper.ts +7 -8
  277. package/src/helpers/TemporaryMemberAccess.ts +1 -2
  278. package/src/helpers/ThrottledQueue.ts +1 -2
  279. package/src/helpers/UitpasTokenRepository.ts +1 -2
  280. package/src/helpers/ViesHelper.ts +11 -11
  281. package/src/helpers/XlsxTransformerColumnHelper.ts +1 -1
  282. package/src/helpers/outstandingBalanceJoin.ts +2 -2
  283. package/src/helpers/updateMemberDetailsUitpasNumber.ts +3 -5
  284. package/src/migrate.ts +12 -5
  285. package/src/{seeds → migrations}/0000000004-single-organization.ts +10 -1
  286. package/src/migrations/0000000006-default-organization.ts +139 -0
  287. package/src/{seeds → migrations}/1715028563-user-permissions.ts +17 -27
  288. package/src/{seeds/1765896674-document-update-year.test.ts → migrations/1750090029-document-update-year.test.ts} +27 -26
  289. package/src/migrations/1750090029-document-update-year.ts +103 -0
  290. package/src/migrations/1750090030-records-configuration.ts +92 -0
  291. package/src/migrations/1750090031-records-configuration-webshops.ts +36 -0
  292. package/src/migrations/1752848540-national-numbers.ts +585 -0
  293. package/src/migrations/1752848550-records-configuration-group-filter.ts +1064 -0
  294. package/src/{seeds → migrations}/1752848561-groups-registration-periods.ts +95 -27
  295. package/src/{seeds → migrations}/1754560914-groups-prices.test.ts +1 -1
  296. package/src/{seeds → migrations}/1754560914-groups-prices.ts +9 -15
  297. package/src/{seeds → migrations}/1756293699-fill-previous-next-period-id.ts +1 -2
  298. package/src/migrations/1761665607-sync-member-users.ts +40 -0
  299. package/src/migrations/1779121239-default-invoice-email-template.sql +3 -0
  300. package/src/migrations/1779121240-invoice-email-template.sql +6 -0
  301. package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
  302. package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
  303. package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
  304. package/src/migrations/1780933598-document-groups.ts +88 -0
  305. package/src/migrations/1781692342-balance-item-relations.ts +13 -0
  306. package/src/migrations/1781857866-platform-admins.ts +103 -0
  307. package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
  308. package/src/seeds/0000000001-throw.ts +5 -0
  309. package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
  310. package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
  311. package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
  312. package/src/seeds/1773754928-force-save-members.ts +41 -8
  313. package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
  314. package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
  315. package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
  316. package/src/seeds/1780915002-update-orders.ts +39 -0
  317. package/src/seeds/1782047708-organization-company-address.ts +44 -0
  318. package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
  319. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
  320. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
  321. package/src/services/AuditLogService.test.ts +51 -0
  322. package/src/services/AuditLogService.ts +45 -0
  323. package/src/services/BalanceItemService.ts +148 -55
  324. package/src/services/CpuService.ts +3 -6
  325. package/src/services/DatabaseCollationService.test.ts +1 -2
  326. package/src/services/DocumentService.ts +1 -2
  327. package/src/services/EventNotificationService.ts +1 -1
  328. package/src/services/FileSignService.ts +2 -4
  329. package/src/services/InvoicePdfService.ts +286 -0
  330. package/src/services/InvoiceService.ts +330 -77
  331. package/src/services/InvoiceXMLService.ts +424 -0
  332. package/src/services/MemberNumberService.ts +6 -11
  333. package/src/services/MemberRecordStore.ts +8 -10
  334. package/src/services/MollieService.ts +530 -0
  335. package/src/services/PaymentMandateService.ts +214 -0
  336. package/src/services/PaymentReallocationService.test.ts +1 -1
  337. package/src/services/PaymentReallocationService.ts +7 -9
  338. package/src/services/PaymentService.ts +820 -328
  339. package/src/services/PlatformMembershipService.ts +5 -9
  340. package/src/services/ReferralService.ts +223 -0
  341. package/src/services/RegistrationService.ts +125 -54
  342. package/src/services/SSOService.test.ts +119 -0
  343. package/src/services/SSOService.ts +56 -49
  344. package/src/services/STPackageService.ts +97 -68
  345. package/src/services/UniqueMemberNumberService.ts +69 -0
  346. package/src/services/UniqueUserService.ts +3 -6
  347. package/src/services/VATService.ts +85 -0
  348. package/src/services/data/invoice.hbs.html +686 -0
  349. package/src/services/uitpas/PassholderEndpoints.ts +3 -6
  350. package/src/services/uitpas/UitpasService.ts +6 -10
  351. package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
  352. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
  353. package/src/services/uitpas/searchUitpasEvents.ts +1 -2
  354. package/src/sql-filters/audit-logs.ts +1 -1
  355. package/src/sql-filters/balance-item-payments.ts +1 -1
  356. package/src/sql-filters/balance-items.ts +1 -1
  357. package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
  358. package/src/sql-filters/document-templates.ts +1 -1
  359. package/src/sql-filters/documents.ts +1 -1
  360. package/src/sql-filters/email-recipients.ts +1 -1
  361. package/src/sql-filters/emails.ts +1 -1
  362. package/src/sql-filters/event-notifications.ts +1 -1
  363. package/src/sql-filters/events.ts +6 -1
  364. package/src/sql-filters/groups.ts +16 -1
  365. package/src/sql-filters/invoiced-balance-items.ts +1 -1
  366. package/src/sql-filters/invoices.ts +1 -1
  367. package/src/sql-filters/member-responsibility-records.ts +1 -1
  368. package/src/sql-filters/members.ts +1 -1
  369. package/src/sql-filters/orders.ts +4 -4
  370. package/src/sql-filters/organization-registration-periods.ts +1 -1
  371. package/src/sql-filters/organizations.ts +1 -1
  372. package/src/sql-filters/payments.ts +5 -0
  373. package/src/sql-filters/platform-memberships.ts +3 -3
  374. package/src/sql-filters/receivable-balances.ts +1 -1
  375. package/src/sql-filters/registration-invitations.ts +90 -0
  376. package/src/sql-filters/registration-periods.ts +1 -1
  377. package/src/sql-filters/registrations.ts +1 -1
  378. package/src/sql-filters/tickets.ts +1 -1
  379. package/src/sql-filters/users.ts +1 -1
  380. package/src/sql-sorters/groups.ts +23 -0
  381. package/src/sql-sorters/platform-memberships.ts +16 -16
  382. package/src/sql-sorters/registration-invitations.ts +36 -0
  383. package/tests/actions/patchOrganizationMember.ts +1 -1
  384. package/tests/actions/patchUserMember.ts +1 -1
  385. package/tests/assertions/assertBalances.ts +6 -12
  386. package/tests/e2e/api-rate-limits.test.ts +12 -24
  387. package/tests/e2e/bundle-discounts.test.ts +4 -1
  388. package/tests/e2e/charge-members.test.ts +13 -11
  389. package/tests/e2e/documents.test.ts +4 -1
  390. package/tests/e2e/register.test.ts +7 -9
  391. package/tests/e2e/stock.test.ts +4 -3
  392. package/tests/e2e/tickets.test.ts +1 -1
  393. package/tests/helpers/MollieMocker.ts +462 -0
  394. package/tests/helpers/PayconiqMocker.ts +138 -45
  395. package/tests/helpers/StripeMocker.ts +9 -6
  396. package/tests/helpers/index.ts +4 -0
  397. package/tests/init/initAdmin.ts +2 -2
  398. package/tests/init/initBundleDiscount.ts +1 -1
  399. package/tests/init/initMembershipOrganization.ts +13 -0
  400. package/tests/init/initPermissionRole.ts +3 -4
  401. package/tests/vitest.global.setup.ts +1 -1
  402. package/vitest.config.js +7 -0
  403. package/.nvmrc +0 -1
  404. package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
  405. package/src/helpers/GroupBuilder.ts +0 -418
  406. package/src/seeds/0000000000-example.ts +0 -11
  407. package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
  408. package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
  409. package/src/seeds/1726847064-setup-steps.ts +0 -16
  410. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
  411. package/src/seeds/1729253172-update-orders.ts +0 -21
  412. package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
  413. package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
  414. package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
  415. package/src/seeds/1740046783-update-membership.ts +0 -17
  416. package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
  417. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
  418. package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
  419. package/src/seeds/1750090030-records-configuration.ts +0 -26
  420. package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
  421. package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
  422. package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
  423. package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
  424. package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
  425. package/src/seeds/1756303697-update-email-counts.ts +0 -76
  426. package/src/seeds/1761665607-sync-member-users.ts +0 -59
  427. package/src/seeds/1765896674-document-update-year.ts +0 -66
  428. /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
  429. /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
  430. /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
  431. /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
  432. /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
  433. /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
  434. /package/src/{seeds → migrations}/data/default-email-templates.sql +0 -0
@@ -1,8 +1,8 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
2
  import type { I18n } from '@stamhoofd/backend-i18n';
3
- import type { BalanceItem, BalanceItemPayment, Organization, StripeAccount} from '@stamhoofd/models';
3
+ import type { Organization, StripeAccount } from '@stamhoofd/models';
4
4
  import { Payment, StripeCheckoutSession, StripePaymentIntent } from '@stamhoofd/models';
5
- import { calculateVATPercentage, PaymentMethod, PaymentMethodHelper, PaymentStatus } from '@stamhoofd/structures';
5
+ import { PaymentMethod, PaymentMethodHelper, PaymentStatus } from '@stamhoofd/structures';
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
  import Stripe from 'stripe';
8
8
  import { passthroughFetch } from './passthroughFetch.js';
@@ -68,15 +68,13 @@ export class StripeHelper {
68
68
  if (charge.payment_method_details.sepa_debit.last4) {
69
69
  if (charge.payment_method_details.sepa_debit.country === 'BE') {
70
70
  payment.iban = charge.payment_method_details.sepa_debit.country + '•• ' + charge.payment_method_details.sepa_debit.bank_code + '• •••• ' + charge.payment_method_details.sepa_debit.last4;
71
- }
72
- else {
71
+ } else {
73
72
  payment.iban = '•••• ' + charge.payment_method_details.sepa_debit.last4;
74
73
  }
75
74
  }
76
75
  }
77
76
  await payment.save();
78
- }
79
- catch (e) {
77
+ } catch (e) {
80
78
  console.error('Failed processing charge', e);
81
79
  }
82
80
  }
@@ -145,8 +143,7 @@ export class StripeHelper {
145
143
  if (intent.status === 'canceled' || intent.status === 'requires_payment_method') {
146
144
  return PaymentStatus.Failed;
147
145
  }
148
- }
149
- catch (e) {
146
+ } catch (e) {
150
147
  console.error('Error cancelling payment intent', e);
151
148
  }
152
149
  }
@@ -161,6 +158,10 @@ export class StripeHelper {
161
158
  const [model] = await StripeCheckoutSession.where({ paymentId: payment.id }, { limit: 1 });
162
159
 
163
160
  if (!model) {
161
+ if (payment.createdAt.getTime() > Date.now() - 1000 * 10) {
162
+ // Possibly missing model data because the payment has only been created just yet
163
+ return payment.status;
164
+ }
164
165
  return PaymentStatus.Failed;
165
166
  }
166
167
 
@@ -207,7 +208,7 @@ export class StripeHelper {
207
208
  }
208
209
 
209
210
  static async createPayment(
210
- { payment, stripeAccount, redirectUrl, cancelUrl, customer, statementDescriptor, i18n, metadata, organization, lineItems }: {
211
+ { payment, stripeAccount, redirectUrl, cancelUrl, customer, statementDescriptor, i18n, metadata, organization }: {
211
212
  payment: Payment;
212
213
  stripeAccount: StripeAccount | null;
213
214
  redirectUrl: string;
@@ -218,9 +219,8 @@ export class StripeHelper {
218
219
  };
219
220
  statementDescriptor: string;
220
221
  i18n: I18n;
221
- metadata: { [key: string]: string };
222
+ metadata: { [key: string]: string | undefined };
222
223
  organization: Organization;
223
- lineItems: (BalanceItemPayment & { balanceItem: BalanceItem })[];
224
224
  },
225
225
  ): Promise<{ paymentUrl: string }> {
226
226
  if (!stripeAccount) {
@@ -240,36 +240,14 @@ export class StripeHelper {
240
240
  });
241
241
  }
242
242
 
243
- let fee = 0;
244
- let directCharge = false;
245
- const vat = calculateVATPercentage(organization.address, organization.meta.VATNumber);
246
- function calculateFee(fixed: number, percentageTimes100: number) {
247
- return Math.round(Math.round(fixed + Math.max(1, totalPrice * percentageTimes100 / 100 / 100)) * (100 + vat) / 100); // € 0,21 + 0,2%
248
- }
249
-
250
- if (payment.method === PaymentMethod.iDEAL) {
251
- fee = calculateFee(21, 20); // € 0,21 + 0,2%
252
- }
253
- else if (payment.method === PaymentMethod.Bancontact) {
254
- fee = calculateFee(24, 20); // € 0,24 + 0,2%
255
- }
256
- else {
257
- fee = calculateFee(25, 150); // € 0,25 + 1,5%
258
- }
259
-
260
- if (stripeAccount.meta.type === 'standard') {
261
- // Submerchant is charged by Stripe for the fees directly
262
- fee = 0;
263
- directCharge = true;
264
- }
265
-
266
- payment.transferFee = fee * 100; // Convert back to 4 decimal places for storage
243
+ const directCharge = (stripeAccount.meta.type === 'standard');
244
+ const fee = directCharge ? 0 : Math.round(payment.transferFee / 100);
267
245
  const serviceFee = Math.round(payment.serviceFeePayout / 100);
268
246
 
269
247
  const fullMetadata = {
270
248
  ...(metadata ?? {}),
271
249
  organizationVATNumber: organization.meta.VATNumber,
272
- transactionFee: fee,
250
+ transferFee: fee,
273
251
  serviceFee: serviceFee, // For historic reasons, this is stored in cents
274
252
  };
275
253
 
@@ -336,8 +314,7 @@ export class StripeHelper {
336
314
  paymentIntentModel.accountId = stripeAccount.accountId;
337
315
  }
338
316
  await paymentIntentModel.save();
339
- }
340
- else {
317
+ } else {
341
318
  // Use checkout flow
342
319
  const data: Stripe.Checkout.SessionCreateParams = {
343
320
  mode: 'payment',
@@ -0,0 +1,419 @@
1
+ import { SimpleError } from '@simonbackx/simple-errors';
2
+ import { Email } from '@stamhoofd/email';
3
+ import { BalanceItem, BalanceItemPayment, Invoice, Organization, Payment, StripeAccount, User } from '@stamhoofd/models';
4
+ import { QueueHandler } from '@stamhoofd/queues';
5
+ import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, getPaymentProviderName, InvoiceStruct, PaymentCustomer, PaymentMethod, PaymentProvider, PaymentStatus, PaymentType, TranslatedString } from '@stamhoofd/structures';
6
+ import { Formatter } from '@stamhoofd/utility';
7
+ import Stripe from 'stripe';
8
+ import { InvoiceService } from '../services/InvoiceService.js';
9
+ import { PaymentService } from '../services/PaymentService.js';
10
+ import { AuthenticatedStructures } from './AuthenticatedStructures.js';
11
+ import { VATService } from '../services/VATService.js';
12
+
13
+ export class ApplicationFeeDetails {
14
+ transferFee = 0;
15
+ serviceFee = 0;
16
+ count = 0;
17
+ minimumDate: Date | null = null;
18
+ maximumDate: Date | null = null;
19
+
20
+ constructor(details: { count?: number; transferFee: number; serviceFee: number; minimumDate: Date | null; maximumDate: Date | null }) {
21
+ this.count = details.count ?? 0;
22
+ this.transferFee = details.transferFee;
23
+ this.serviceFee = details.serviceFee;
24
+ this.minimumDate = details.minimumDate;
25
+ this.maximumDate = details.maximumDate;
26
+ }
27
+
28
+ get amount() {
29
+ return this.transferFee + this.serviceFee;
30
+ }
31
+
32
+ static fromStripe(transaction: Stripe.BalanceTransaction) {
33
+ const source = transaction.source as Stripe.ApplicationFee;
34
+ const metadata = (source.originating_transaction as Stripe.Charge).metadata;
35
+
36
+ const serviceFeeStr = metadata.serviceFee as unknown;
37
+ if (serviceFeeStr === undefined || typeof serviceFeeStr !== 'string') {
38
+ throw new Error('Missing serviceFee metadata');
39
+ }
40
+
41
+ const parsed = parseInt(serviceFeeStr);
42
+ if (isNaN(parsed) || !isFinite(parsed)) {
43
+ throw new Error('Invalid serviceFee metadata');
44
+ }
45
+ const serviceFee = parsed * 100; // in cents
46
+ const transferFee = transaction.amount * 100 - serviceFee;
47
+
48
+ return new ApplicationFeeDetails({
49
+ count: 1,
50
+ serviceFee,
51
+ transferFee,
52
+ minimumDate: new Date(transaction.created * 1000),
53
+ maximumDate: new Date(transaction.created * 1000),
54
+ });
55
+ }
56
+
57
+ add(fee: Stripe.BalanceTransaction) {
58
+ this.combine(ApplicationFeeDetails.fromStripe(fee));
59
+ }
60
+
61
+ remove(fee: Stripe.BalanceTransaction) {
62
+ this.combine(ApplicationFeeDetails.fromStripe(fee));
63
+ }
64
+
65
+ combine(other: ApplicationFeeDetails) {
66
+ this.serviceFee += other.serviceFee;
67
+ this.transferFee += other.transferFee;
68
+ this.count += other.count;
69
+ if (other.minimumDate && (this.minimumDate === null || other.minimumDate < this.minimumDate)) {
70
+ this.minimumDate = other.minimumDate;
71
+ }
72
+ if (other.maximumDate && (this.maximumDate === null || other.maximumDate > this.maximumDate)) {
73
+ this.maximumDate = other.maximumDate;
74
+ }
75
+ }
76
+ }
77
+
78
+ class StripeReport {
79
+ readonly applicationFeesPerAccount = new Map<string, ApplicationFeeDetails>();
80
+
81
+ add(balanceTransaction: Stripe.BalanceTransaction) {
82
+ if (balanceTransaction.type === 'application_fee' || balanceTransaction.type === 'application_fee_refund') {
83
+ const source = balanceTransaction.source as Stripe.ApplicationFee;
84
+ const account = typeof source.account === 'string' ? source.account : source.account.id;
85
+
86
+ if (account) {
87
+ // We don't included refunded fees because these are test payments from test account
88
+ const currentAmount = this.applicationFeesPerAccount.get(account);
89
+ if (currentAmount === undefined) {
90
+ this.applicationFeesPerAccount.set(account, ApplicationFeeDetails.fromStripe(balanceTransaction));
91
+ } else {
92
+ currentAmount.add(balanceTransaction);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ combine(report: StripeReport) {
99
+ for (const [account, amount] of report.applicationFeesPerAccount.entries()) {
100
+ const currentAmount = this.applicationFeesPerAccount.get(account);
101
+ if (currentAmount === undefined) {
102
+ this.applicationFeesPerAccount.set(account, amount);
103
+ } else {
104
+ currentAmount.combine(amount);
105
+ }
106
+ }
107
+ return this;
108
+ }
109
+ }
110
+
111
+ export class StripeReportInvoicer {
112
+ private readonly report: StripeReport;
113
+ start: Date;
114
+ end: Date;
115
+
116
+ constructor(report: StripeReport, start: Date, end: Date) {
117
+ this.report = report;
118
+ this.start = start;
119
+ this.end = end;
120
+ }
121
+
122
+ async generateInvoices(sellingOrganization: Organization, reference: string) {
123
+ const invoices: Invoice[] = [];
124
+ for (const [account, details] of this.report.applicationFeesPerAccount) {
125
+ try {
126
+ const invoice = await this.generateInvoice(sellingOrganization, reference, account, details);
127
+ if (invoice) {
128
+ invoices.push(invoice);
129
+ }
130
+ } catch (e) {
131
+ console.error(e);
132
+ // Send email notification of this error
133
+ Email.sendWebmaster({
134
+ subject: 'Aanmaken Stripe facturen voor ' + account + ' - ' + reference + ' mislukt',
135
+ html: 'Aanmaken Stripe facturen voor ' + account + ' - ' + reference + ' mislukt. <br><br> ' + e.toString(),
136
+ });
137
+ }
138
+ }
139
+ return invoices;
140
+ }
141
+
142
+ static async hasInvoice(sellingOrganization: Organization, reference: string) {
143
+ return !!await Invoice.select().where('organizationId', sellingOrganization.id).where('reference', reference).where('number', '!=', null).first(false);
144
+ }
145
+
146
+ async generateInvoice(sellingOrganization: Organization, reference: string, accountId: string, applicationFee: ApplicationFeeDetails) {
147
+ if (applicationFee.amount === 0) {
148
+ return;
149
+ }
150
+
151
+ const seller = sellingOrganization.meta.companies[0];
152
+ if (!seller) {
153
+ return;
154
+ }
155
+
156
+ // Search for the organization in Stamhoofd
157
+ const stripeAccount = await StripeAccount.select().where('accountId', accountId).first(false);
158
+ if (!stripeAccount) {
159
+ if (STAMHOOFD.environment === 'test') {
160
+ console.error('No organization found for Stripe account ' + accountId);
161
+ return;
162
+ }
163
+ throw new SimpleError({
164
+ code: 'stripe_account_not_found',
165
+ message: 'No organization found for Stripe account ' + accountId,
166
+ });
167
+ }
168
+ const organization = await Organization.getByID(stripeAccount.organizationId);
169
+
170
+ if (!organization) {
171
+ throw new SimpleError({
172
+ code: 'organization_not_found',
173
+ message: 'No organization found for Stripe account ' + accountId,
174
+ });
175
+ }
176
+
177
+ const existingInvoices = await Invoice.select()
178
+ .where('organizationId', sellingOrganization.id)
179
+ .where('payingOrganizationId', organization.id)
180
+ .where('reference', reference)
181
+ .where('number', '!=', null)
182
+ .fetch();
183
+
184
+ for (const i of existingInvoices) {
185
+ if (!i.stripeAccountId && i.totalWithVAT === applicationFee.amount) {
186
+ i.stripeAccountId = accountId;
187
+
188
+ console.log('Set stripe account id for invoice ' + i.number + ' to ' + accountId);
189
+ await i.save();
190
+ }
191
+
192
+ if (!i.stripeAccountId) {
193
+ throw new SimpleError({
194
+ code: 'invoice_already_exists_with_different_amount',
195
+ message: 'Invoice without account id already exists with different amount ' + organization.id + ' expected ' + applicationFee.amount + ' got ' + i.totalWithVAT + ' in invoice ' + i.number,
196
+ });
197
+ }
198
+
199
+ if (i.stripeAccountId === accountId) {
200
+ if (i.totalWithVAT !== applicationFee.amount) {
201
+ throw new SimpleError({
202
+ code: 'invoice_already_exists_with_different_amount',
203
+ message: 'Invoice already exists with different amount ' + organization.id + ' expected ' + applicationFee.amount + ' got ' + i.totalWithVAT + ' in invoice ' + i.number,
204
+ });
205
+ }
206
+
207
+ // Already invoiced
208
+ console.warn('Tried to invoice an already invoiced.');
209
+ return;
210
+ }
211
+ }
212
+
213
+ const customer = PaymentCustomer.create({
214
+ company: sellingOrganization.defaultCompanies[0],
215
+ });
216
+
217
+ const balanceItems: BalanceItem[] = [];
218
+
219
+ if (applicationFee.serviceFee !== 0) {
220
+ const item = new BalanceItem();
221
+ item.type = BalanceItemType.ServiceFee;
222
+ item.description = $t('%1Wd', {
223
+ startDate: Formatter.startDate(this.start, false, true),
224
+ endDate: Formatter.endDate(this.end, false, true),
225
+ });
226
+ item.relations.set(BalanceItemRelationType.PaymentProvider, BalanceItemRelation.create({
227
+ id: PaymentProvider.Stripe,
228
+ name: TranslatedString.create(getPaymentProviderName(PaymentProvider.Stripe)),
229
+ }));
230
+ item.payingOrganizationId = organization.id;
231
+ item.organizationId = sellingOrganization.id;
232
+ item.VATPercentage = 21;
233
+ item.VATExcempt = VATService.getVATExcempt({
234
+ company: organization.defaultCompanies[0] ?? null,
235
+ sellingOrganization,
236
+ type: 'services',
237
+ });
238
+ item.VATIncluded = true;
239
+ item.quantity = 1;
240
+ item.unitPrice = applicationFee.serviceFee;
241
+ item.createdAt = new Date();
242
+ item.status = BalanceItemStatus.Hidden;
243
+ item.startDate = this.start;
244
+ item.endDate = this.end;
245
+ await item.save();
246
+ balanceItems.push(item);
247
+ }
248
+
249
+ if (applicationFee.transferFee !== 0) {
250
+ const item = new BalanceItem();
251
+ item.type = BalanceItemType.TransferFee;
252
+ item.description = $t('%1Xx', {
253
+ startDate: Formatter.startDate(this.start, false, true),
254
+ endDate: Formatter.endDate(this.end, false, true),
255
+ });
256
+ item.relations.set(BalanceItemRelationType.PaymentProvider, BalanceItemRelation.create({
257
+ id: PaymentProvider.Stripe,
258
+ name: TranslatedString.create(getPaymentProviderName(PaymentProvider.Stripe)),
259
+ }));
260
+ item.payingOrganizationId = organization.id;
261
+ item.organizationId = sellingOrganization.id;
262
+ item.VATPercentage = 21;
263
+ item.VATExcempt = VATService.getVATExcempt({
264
+ company: organization.defaultCompanies[0] ?? null,
265
+ sellingOrganization,
266
+ type: 'services',
267
+ });
268
+ item.VATIncluded = true;
269
+ item.quantity = 1;
270
+ item.unitPrice = applicationFee.transferFee;
271
+ item.createdAt = new Date();
272
+ item.status = BalanceItemStatus.Hidden;
273
+ item.startDate = this.start;
274
+ item.endDate = this.end;
275
+ await item.save();
276
+ balanceItems.push(item);
277
+ }
278
+
279
+ const systemUser = await User.getSystem();
280
+
281
+ // Done validation
282
+ const payment = new Payment();
283
+ payment.adminUserId = systemUser.id;
284
+
285
+ // Who will receive this money?
286
+ payment.organizationId = sellingOrganization.id;
287
+
288
+ // Who paid
289
+ payment.payingOrganizationId = organization.id;
290
+ payment.customer = customer;
291
+
292
+ payment.status = PaymentStatus.Pending;
293
+ payment.price = applicationFee.amount;
294
+ payment.roundingAmount = 0;
295
+ payment.method = PaymentMethod.Unknown;
296
+ payment.type = PaymentType.Payment;
297
+ payment.createMandate = null;
298
+
299
+ payment.provider = PaymentProvider.Stripe;
300
+ payment.stripeAccountId = stripeAccount.id;
301
+ await payment.save();
302
+
303
+ for (const balanceItem of balanceItems) {
304
+ // Create one balance item payment to pay it in one payment
305
+ const balanceItemPayment = new BalanceItemPayment();
306
+ balanceItemPayment.balanceItemId = balanceItem.id;
307
+ balanceItemPayment.paymentId = payment.id;
308
+ balanceItemPayment.organizationId = organization.id;
309
+ balanceItemPayment.price = balanceItem.priceWithVAT;
310
+ await balanceItemPayment.save();
311
+ }
312
+
313
+ // Create invoice (before payment, to prevent auto-invoicing)
314
+ const generalStructs = await AuthenticatedStructures.paymentsGeneral([payment], false);
315
+
316
+ const invoiceStruct = InvoiceStruct.create({
317
+ seller,
318
+ customer: payment.customer,
319
+ payments: generalStructs,
320
+ });
321
+ invoiceStruct.buildFromPayments();
322
+
323
+ invoiceStruct.reference = reference;
324
+ invoiceStruct.stripeAccountId = accountId;
325
+
326
+ await PaymentService.handlePaymentStatusUpdate(payment, sellingOrganization, PaymentStatus.Succeeded, this.end);
327
+ const invoice = await InvoiceService.createFrom(sellingOrganization, invoiceStruct);
328
+
329
+ return invoice;
330
+ }
331
+ }
332
+
333
+ export class StripeInvoicer {
334
+ private stripe: Stripe;
335
+
336
+ constructor({ secretKey }: { secretKey: string }) {
337
+ this.stripe = new Stripe(secretKey, { apiVersion: '2024-06-20', typescript: true, maxNetworkRetries: 1, timeout: 10000 });
338
+ }
339
+
340
+ static getMonthUnixStartEnd(date: Date) {
341
+ const start = Math.floor((new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0).getTime()) / 1000);
342
+ const end = Math.ceil((new Date(date.getFullYear(), date.getMonth() + 1, 1, 0, 0, 0, 0).getTime() - 1000) / 1000);
343
+ return { start, end };
344
+ }
345
+
346
+ // Loops all months until all invoices are generated
347
+ async generateAllInvoices(sellingOrganization: Organization, options?: { force?: boolean; start?: Date }) {
348
+ const startMonth = options?.start ?? new Date(2026, 0, 1);
349
+ const stopAt = new Date(Date.now() + (STAMHOOFD.environment === 'production' ? (-60 * 60 * 24 * 1000) : (60 * 60 * 24 * 1000 * 31))); // One day margin before creating invoices
350
+ let currentMonth = new Date(startMonth);
351
+
352
+ while (true) {
353
+ const { start, end } = StripeInvoicer.getMonthUnixStartEnd(currentMonth);
354
+
355
+ if (end >= stopAt.getTime() / 1000) {
356
+ // Stop
357
+ break;
358
+ }
359
+ await this.generateInvoices(sellingOrganization, currentMonth, options);
360
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
361
+ }
362
+ }
363
+
364
+ async generateInvoices(sellingOrganization: Organization, month: Date, options?: { force?: boolean }) {
365
+ const { start, end } = StripeInvoicer.getMonthUnixStartEnd(month);
366
+ const reference = 'stripe-fees-' + Formatter.dateIso(new Date(start * 1000));
367
+
368
+ try {
369
+ await QueueHandler.schedule(reference, async () => {
370
+ if (!options?.force && await StripeReportInvoicer.hasInvoice(sellingOrganization, reference)) {
371
+ console.log('Already invoiced month', reference);
372
+ return;
373
+ }
374
+
375
+ console.log('Generating invoices for ', reference, start, end);
376
+ const reportFees = await this.fetchBalanceItems({
377
+ created: {
378
+ gte: start,
379
+ lte: end,
380
+ },
381
+ type: 'application_fee',
382
+ expand: ['data.source', 'data.source.originating_transaction'],
383
+ });
384
+ const reportRefund = await this.fetchBalanceItems({
385
+ created: {
386
+ gte: start,
387
+ lte: end,
388
+ },
389
+ type: 'application_fee_refund',
390
+ expand: ['data.source', 'data.source.originating_transaction'],
391
+ });
392
+
393
+ const report = reportFees.combine(reportRefund);
394
+ const invoicer = new StripeReportInvoicer(report, new Date(start * 1000), new Date(end * 1000));
395
+ await invoicer.generateInvoices(sellingOrganization, reference);
396
+ });
397
+ } catch (e) {
398
+ console.error(e);
399
+
400
+ // Send email notification of this error
401
+ Email.sendWebmaster({
402
+ subject: 'Aanmaken Stripe facturen voor ' + Formatter.dateNumber(month) + ' mislukt',
403
+ html: 'Aanmaken Stripe facturen voor ' + Formatter.dateNumber(month) + ' mislukt. <br><br> ' + e.toString(),
404
+ });
405
+ }
406
+ }
407
+
408
+ private async fetchBalanceItems(options: Stripe.BalanceTransactionListParams) {
409
+ // For the given payout, fetch all balance items
410
+ const params = { ...options };
411
+ const report = new StripeReport();
412
+
413
+ for await (const balanceItem of this.stripe.balanceTransactions.list(params)) {
414
+ report.add(balanceItem);
415
+ }
416
+
417
+ return report;
418
+ }
419
+ }
@@ -53,14 +53,12 @@ export class StripePayoutChecker {
53
53
  // Get all payments for this payout
54
54
  try {
55
55
  await this.fetchBalanceItems(payout);
56
- }
57
- catch (e) {
56
+ } catch (e) {
58
57
  console.error('Error for payout ' + payout.id);
59
58
  console.error(e);
60
59
  }
61
60
  }
62
- }
63
- catch (e) {
61
+ } catch (e) {
64
62
  console.error(e);
65
63
  }
66
64
  }
@@ -119,8 +117,7 @@ export class StripePayoutChecker {
119
117
  if (stripePayments.length === 1) {
120
118
  paymentId = stripePayments[0].paymentId;
121
119
  console.log('Found missing payment metadata for payment intent ', originatingTransaction.payment_intent, paymentId);
122
- }
123
- else {
120
+ } else {
124
121
  // Probably a card payment
125
122
  // Search for the checkout session
126
123
  const checkoutSession = await this.stripePlatform.checkout.sessions.list({
@@ -138,12 +135,10 @@ export class StripePayoutChecker {
138
135
  if (stripeCheckoutSessions.length === 1) {
139
136
  paymentId = stripeCheckoutSessions[0].paymentId;
140
137
  console.log('Found missing payment metadata for payment intent ', originatingTransaction.payment_intent, paymentId);
141
- }
142
- else {
138
+ } else {
143
139
  console.log('No payment found for checkout session ' + session.id);
144
140
  }
145
- }
146
- else {
141
+ } else {
147
142
  console.log('No Stripe Checkout Sessions found for payment intent ' + paymentIntentId);
148
143
  }
149
144
  }
@@ -1,21 +1,22 @@
1
1
  import { Organization, Platform } from '@stamhoofd/models';
2
2
  import { QueueHandler } from '@stamhoofd/queues';
3
- import type { OrganizationTag} from '@stamhoofd/structures';
3
+ import type { OrganizationTag } from '@stamhoofd/structures';
4
4
  import { AuditLogSource, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
5
5
  import { AuditLogService } from '../services/AuditLogService.js';
6
6
 
7
7
  export class TagHelper extends SharedTagHelper {
8
8
  static async updateOrganizations() {
9
9
  const queueId = 'update-tags-on-organizations';
10
- QueueHandler.cancel(queueId);
10
+ QueueHandler.abort(queueId);
11
11
 
12
12
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
13
- await QueueHandler.schedule(queueId, async () => {
13
+ await QueueHandler.schedule(queueId, async ({ abort }) => {
14
14
  const sharedPlatform = await Platform.getShared();
15
15
 
16
16
  const tagCounts = new Map<string, number>();
17
17
 
18
18
  for await (const organization of Organization.select().all()) {
19
+ abort.throwIfAborted();
19
20
  organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, sharedPlatform.config.tags);
20
21
 
21
22
  for (const tag of organization.meta.tags) {
@@ -26,11 +27,9 @@ export class TagHelper extends SharedTagHelper {
26
27
 
27
28
  // Reload platform to avoid race conditions
28
29
  const platform = await Platform.getForEditing();
29
- for (const [tag, count] of tagCounts.entries()) {
30
- const tagObject = platform.config.tags.find(t => t.id === tag);
31
- if (tagObject) {
32
- tagObject.organizationCount = count;
33
- }
30
+ for (const tagObject of platform.config.tags) {
31
+ abort.throwIfAborted();
32
+ tagObject.organizationCount = tagCounts.get(tagObject.id) ?? 0;
34
33
  }
35
34
  await platform.save();
36
35
  });
@@ -33,8 +33,7 @@ export function deleteExpiredTemporaryMemberAccess() {
33
33
 
34
34
  if (filtered.length === 0) {
35
35
  userMemberTemporaryAccessCache.delete(userId);
36
- }
37
- else {
36
+ } else {
38
37
  userMemberTemporaryAccessCache.set(userId, filtered);
39
38
  }
40
39
  }
@@ -83,8 +83,7 @@ export class ThrottledQueue<T> {
83
83
  const batch = batchSize < items.length ? items.slice(i, i + batchSize) : items;
84
84
  try {
85
85
  await this.handler(batch);
86
- }
87
- catch (error) {
86
+ } catch (error) {
88
87
  console.error('Error processing batch in ThrottledQueue:', error);
89
88
  }
90
89
  }
@@ -51,8 +51,7 @@ export class UitpasTokenRepository {
51
51
  let repo = new UitpasTokenRepository(model);
52
52
  try {
53
53
  await repo.getNewAccessToken();
54
- }
55
- catch (e) {
54
+ } catch (e) {
56
55
  return false; // not valid
57
56
  }
58
57
  // valid -> store