@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
@@ -0,0 +1,160 @@
1
+ import { isSimpleError, isSimpleErrors } from '@simonbackx/simple-errors';
2
+ import { registerCron } from '@stamhoofd/crons';
3
+ import type { Invoice } from '@stamhoofd/models';
4
+ import { Organization, Payment, sendEmailTemplate } from '@stamhoofd/models';
5
+ import { EmailTemplateType, InvoiceStruct, PaymentStatus, Replacement } from '@stamhoofd/structures';
6
+ import { Formatter, Sorter } from '@stamhoofd/utility';
7
+ import { AuthenticatedStructures } from '../helpers/AuthenticatedStructures.js';
8
+ import { InvoiceService } from '../services/InvoiceService.js';
9
+ import { useSavedIterator } from './helpers/useSavedIterator.js';
10
+ import { isOutside } from './helpers/isOutside.js';
11
+
12
+ registerCron('invoices', invoices);
13
+
14
+ const { iterate, isHoursAgo } = useSavedIterator(() => {
15
+ return Organization.select();
16
+ }, { limit: 10, maxQueries: 5 });
17
+
18
+ const bootAt = new Date();
19
+
20
+ async function invoices() {
21
+ // Do not run within 5 hours after boot
22
+ if (bootAt.getTime() > new Date().getTime() - 1000 * 60 * 60 * 5 && STAMHOOFD.environment !== 'development') {
23
+ return;
24
+ }
25
+
26
+ if (isOutside('03:00', '10:00')) {
27
+ return;
28
+ }
29
+
30
+ if (!isHoursAgo(12)) {
31
+ return;
32
+ }
33
+
34
+ // Get the next x organization to send e-mails for
35
+ for await (const organization of iterate()) {
36
+ if (!organization.meta.invoicesEnabled) {
37
+ continue;
38
+ }
39
+
40
+ // Create all invoices for this organization
41
+ await createInvoicesFor(organization);
42
+ }
43
+ }
44
+
45
+ async function createInvoicesFor(organization: Organization) {
46
+ const seller = organization.meta.companies[0];
47
+ if (!seller) {
48
+ return;
49
+ }
50
+
51
+ // Belgian rules: allowed to invoice up to the 15th day of the next month. We extend it with one month to fix mistakes.
52
+ const today = Formatter.luxon();
53
+ const startDate = today.day <= 15 ? today.minus({ month: 2 }).startOf('month') : today.minus({ month: 1 }).startOf('month');
54
+
55
+ // Don't invoice below 1 euro - unless we reached the timeout date for invoices (end of month + 15 days - 3 days margin)
56
+ const invoiceLimit = STAMHOOFD.environment === 'development' ? 0 : 1_0000;
57
+ function getPaymentTimeoutDate(p: Payment) {
58
+ return Formatter.luxon(p.paidAt ?? p.createdAt).plus({ month: 1 }).set({ day: 15 - 3 }).startOf('day').toJSDate();
59
+ }
60
+
61
+ console.log('Fetching all payments between ' + Formatter.dateTime(startDate.toJSDate()) + ' and now for ' + organization.name);
62
+
63
+ const payments = await Payment.select()
64
+ .where('organizationId', organization.id)
65
+ .where('status', PaymentStatus.Succeeded)
66
+ .where('paidAt', '>=', startDate.toJSDate())
67
+ .where('invoiceId', null)
68
+ .where('customer', '!=', null)
69
+ .where('payingOrganizationId', '!=', null)
70
+ .where('price', '!=', 0)
71
+ .limit(1_000)
72
+ .orderBy('payingOrganizationId')
73
+ .fetch();
74
+
75
+ console.log('Invoicing ' + payments.length + ' payments');
76
+
77
+ // Group by VATNumber, company number or company name
78
+ const groups = new Map<string, Payment[]>();
79
+ for (const payment of payments) {
80
+ const blob = {
81
+ // Grouping by payingOrganizationId avoid privacy issues and data leaks
82
+ payingOrganizationId: payment.payingOrganizationId ?? null,
83
+ vatNumber: payment.customer?.company?.VATNumber,
84
+ companyNumber: payment.customer?.company?.companyNumber,
85
+ // Name and adress is ignored, because subject to changes
86
+ };
87
+ const id = JSON.stringify(blob);
88
+ const existing = groups.get(id);
89
+ if (existing) {
90
+ existing.push(payment);
91
+ } else {
92
+ groups.set(id, [payment]);
93
+ }
94
+ }
95
+
96
+ console.log('Invoicing ' + groups.size + ' customers');
97
+
98
+ const errors: string[] = [];
99
+ const invoices: Invoice[] = [];
100
+ let skipped = 0;
101
+
102
+ for (const [_, payments] of groups) {
103
+ // Group from last to newest (so we use the last customer details if the address changed during the month)
104
+ payments.sort((a, b) => Sorter.byDateValue(a.createdAt, b.createdAt));
105
+ const customer = payments[0].customer!.dynamicName;
106
+ try {
107
+ const generalStructs = await AuthenticatedStructures.paymentsGeneral(payments, false);
108
+
109
+ const invoice = InvoiceStruct.create({
110
+ seller,
111
+ customer: payments[0].customer!,
112
+ payments: generalStructs,
113
+ });
114
+ invoice.buildFromPayments();
115
+
116
+ if (invoice.totalWithVAT >= 0 && invoice.totalWithVAT < invoiceLimit) {
117
+ const first = new Date(Math.min(...payments.map(p => getPaymentTimeoutDate(p).getTime())));
118
+ if (first > new Date()) {
119
+ console.log('Delaying invoicing ' + customer + ' at ' + organization.id + ' until ' + Formatter.dateIso(first));
120
+ skipped += 1;
121
+ continue;
122
+ } else {
123
+ console.log('Invoiced low priced invoice, because of date ' + Formatter.dateIso(first) + ' being in the past');
124
+ }
125
+ }
126
+
127
+ const model = await InvoiceService.createFrom(organization, invoice);
128
+ invoices.push(model);
129
+ } catch (e) {
130
+ console.error(payments.map(p => p.id), e);
131
+
132
+ const prefix = customer + ' (' + payments.map(p => '<a href="' + Formatter.escapeHtml('https://' + organization.getDashboardHost() + '/boekhouding/betalingen/' + p.id) + '">' + Formatter.escapeHtml($t('%14a') + ' ' + p.id.substring(0, 8)) + '</a>').join(', ') + '): ';
133
+
134
+ if (isSimpleError(e) || isSimpleErrors(e)) {
135
+ errors.push(prefix + Formatter.escapeHtml(e.getHuman()));
136
+ } else {
137
+ errors.push(prefix + Formatter.escapeHtml($t('%1ED')));
138
+ }
139
+ }
140
+ }
141
+
142
+ console.log('Created ' + invoices.length + ' invoices with ' + errors.length + ' errors and skipped ' + skipped);
143
+
144
+ if (errors.length) {
145
+ await sendEmailTemplate(organization, {
146
+ template: {
147
+ type: EmailTemplateType.InvoiceGenerationErrors,
148
+ },
149
+ recipients: await organization.getAdminRecipients(),
150
+ type: 'transactional',
151
+ fromStamhoofd: true,
152
+ defaultReplacements: [
153
+ Replacement.create({
154
+ token: 'errors',
155
+ html: '<ul><li>' + errors.join('</li><li>') + '</li></ul>' + (skipped > 0 ? '<p>' + Formatter.escapeHtml(skipped === 1 ? $t('%1U4') : $t('%1Sp', { count: skipped })) + '</p>' : ''),
156
+ }),
157
+ ],
158
+ });
159
+ }
160
+ }
@@ -0,0 +1,50 @@
1
+ import { registerCron } from '@stamhoofd/crons';
2
+ import { Organization, Platform } from '@stamhoofd/models';
3
+ import { useSavedIterator } from './helpers/useSavedIterator.js';
4
+ import { STPackageService } from '../services/STPackageService.js';
5
+ import { BalanceItemStatus } from '@stamhoofd/structures';
6
+
7
+ // Charge manual service fees every night
8
+ registerCron('members-fees', chargeMembers);
9
+
10
+ const { iterate, isHoursAgo } = useSavedIterator(() => {
11
+ return Organization.select();
12
+ }, { limit: 10, maxQueries: 5 });
13
+
14
+ async function chargeMembers() {
15
+ if (STAMHOOFD.environment !== 'development' && (new Date().getHours() > 5 || new Date().getHours() < 2)) {
16
+ return;
17
+ }
18
+
19
+ if (!isHoursAgo(12)) {
20
+ return;
21
+ }
22
+
23
+ const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
24
+ if (!membershipOrganizationId) {
25
+ return;
26
+ }
27
+
28
+ const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
29
+
30
+ for await (const organization of iterate()) {
31
+ await chargeMemberFeesForOrganization(organization, membershipOrganization);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Charge the (increase in) per-member fees for every active package of an organization.
37
+ * For per-member packages this only charges the members that aren't paid/pending yet.
38
+ */
39
+ export async function chargeMemberFeesForOrganization(organization: Organization, sellingOrganization: Organization) {
40
+ const packages = await STPackageService.getActivePackages(organization.id);
41
+
42
+ for (const pack of packages) {
43
+ const item = await STPackageService.chargePackage(pack, sellingOrganization, organization.defaultCompanies[0]);
44
+ if (item) {
45
+ item.status = BalanceItemStatus.Due;
46
+ await item.save();
47
+ console.log('Charged', item.id);
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,86 @@
1
+ import { registerCron } from '@stamhoofd/crons';
2
+ import { MolliePayment, MollieToken, Organization, Payment } from '@stamhoofd/models';
3
+ import { MollieService } from '../services/MollieService.js';
4
+ import { PaymentService } from '../services/PaymentService.js';
5
+
6
+ registerCron('mollie-chargebacks', checkMollieChargebacks);
7
+
8
+ let lastRun: Date | null = null;
9
+ export async function checkMollieChargebacks() {
10
+ if (STAMHOOFD.environment !== 'development') {
11
+ if (lastRun && new Date().getTime() - lastRun.getTime() < 1000 * 60 * 60 * 24) {
12
+ return;
13
+ }
14
+ lastRun = new Date();
15
+ }
16
+ await doCheckMollieChargebacks(false);
17
+ }
18
+
19
+ export async function doCheckMollieChargebacks(checkAll = false) {
20
+ if (STAMHOOFD.environment !== 'development') {
21
+ console.log('Checking Mollie chargebacks');
22
+ }
23
+
24
+ // Loop all mollie tokens
25
+ for await (const token of MollieToken.select().limit(1).all({}, 'organizationId')) {
26
+ // todo
27
+ const sellingOrganization = await Organization.getByID(token.id);
28
+ if (sellingOrganization) {
29
+ const service = await MollieService.create({ sellingOrganization });
30
+ if (service) {
31
+ await checkMollieChargebacksFor(service, checkAll);
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ export async function checkMollieChargebacksFor(service: MollieService, checkAll = false) {
38
+ if (STAMHOOFD.environment !== 'development') {
39
+ console.log('Checking Mollie chargebacks for ' + service.sellingOrganization.name);
40
+ }
41
+
42
+ // Check last 3 days
43
+ const offset = new Date(Date.now() - 1000 * 60 * 60 * 24 * 4);
44
+
45
+ // due to a bug in mollie client code, testmode paramter is missing in the typescript definitions
46
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
47
+ for await (const chargeback of service.client.chargebacks.iterate({ sort: 'desc', testmode: service.testMode } as any)) {
48
+ if (!checkAll && new Date(chargeback.createdAt) < offset) {
49
+ break;
50
+ }
51
+
52
+ // Check if this chargeback has been handled
53
+ const existingChargeback = await MolliePayment.select().where('mollieId', chargeback.id).first(false);
54
+ if (existingChargeback) {
55
+ // We can break because all older chargebacks will also be handled
56
+ break;
57
+ }
58
+
59
+ if (chargeback.paymentId) {
60
+ const molliePayment = await MolliePayment.select().where('mollieId', chargeback.paymentId).first(false);
61
+ if (molliePayment) {
62
+ const payment = await Payment.getByID(molliePayment.paymentId);
63
+ if (payment) {
64
+ try {
65
+ const amount = Math.round(parseFloat(chargeback.amount.value) * 100) * 100;
66
+ const createdPayment = await PaymentService.registerChargeback(payment, amount, new Date(chargeback.createdAt));
67
+
68
+ // Link Mollie chargeback ID (so we can set settlement later in the settlements cron)
69
+ const molliePayment = new MolliePayment();
70
+ molliePayment.paymentId = createdPayment.id;
71
+ molliePayment.mollieId = chargeback.id;
72
+ await molliePayment.save();
73
+ } catch (e) {
74
+ console.error('Failed to register chargeback ' + chargeback.id, e);
75
+ }
76
+ }
77
+ } else {
78
+ console.error('Invalid chargeback payment id ' + chargeback.paymentId + ', not found');
79
+ }
80
+ }
81
+ }
82
+
83
+ if (STAMHOOFD.environment !== 'development') {
84
+ console.log('Done checking Mollie chargebacks for ' + service.sellingOrganization.name);
85
+ }
86
+ }
@@ -0,0 +1,152 @@
1
+ import { registerCron } from '@stamhoofd/crons';
2
+ import { BalanceItem, Organization, Payment, Platform } from '@stamhoofd/models';
3
+ import { SQL } from '@stamhoofd/sql';
4
+ import { BalanceItemStatus, BalanceItemType, PaymentMethod, PaymentStatus } from '@stamhoofd/structures';
5
+ import { Formatter } from '@stamhoofd/utility';
6
+ import { BalanceItemService } from '../services/BalanceItemService.js';
7
+ import { PaymentService } from '../services/PaymentService.js';
8
+ import { VATService } from '../services/VATService.js';
9
+
10
+ // Charge manual service fees every night
11
+ registerCron('service-fees', chargeServiceFees);
12
+
13
+ async function chargeServiceFees() {
14
+ if (STAMHOOFD.environment !== 'development' && (new Date().getHours() > 5 || new Date().getHours() < 2)) {
15
+ return;
16
+ }
17
+ const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
18
+ if (!membershipOrganizationId) {
19
+ return;
20
+ }
21
+
22
+ const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
23
+
24
+ // Succeeded payments
25
+ const payments = await Payment.select()
26
+ .where('status', PaymentStatus.Succeeded)
27
+ .where('serviceFeeManual', '>', 0)
28
+ .where('serviceFeeManualCharged', 0)
29
+ .where('createdAt', '<', STAMHOOFD.environment !== 'development' ? new Date() : Formatter.dateIso(new Date(Date.now() + 1000 * 60 * 60 * 24)))
30
+ .limit(200)
31
+ .orderBy('createdAt', 'ASC')
32
+ .fetch();
33
+
34
+ // Pending payments older than 14 days with method = Transfer or PointOfSale
35
+ const pendingPayments = await Payment.select()
36
+ .where('status', [PaymentStatus.Pending, PaymentStatus.Created])
37
+ .where('method', [PaymentMethod.Transfer, PaymentMethod.PointOfSale, PaymentMethod.Unknown])
38
+ .where('serviceFeeManual', '>', 0)
39
+ .where('serviceFeeManualCharged', 0)
40
+ .where('createdAt', '<', Formatter.dateIso(new Date(Date.now() - 1000 * 60 * 60 * 24 * 14)))
41
+ .limit(200)
42
+ .orderBy('createdAt', 'ASC')
43
+ .fetch();
44
+
45
+ const allPayments = [...payments, ...pendingPayments];
46
+
47
+ console.log('Found ' + allPayments.length + ' payments to charge service fees for');
48
+
49
+ const mappedToOrganizationId = new Map<string, Payment[]>();
50
+ for (const payment of allPayments) {
51
+ if (!payment.organizationId) {
52
+ console.warn('Payment without organizationId, skipping', payment.id);
53
+ continue;
54
+ }
55
+ if (!mappedToOrganizationId.has(payment.organizationId)) {
56
+ mappedToOrganizationId.set(payment.organizationId, []);
57
+ }
58
+ mappedToOrganizationId.get(payment.organizationId)!.push(payment);
59
+ }
60
+
61
+ for (const [organizationId, payments] of mappedToOrganizationId.entries()) {
62
+ const organization = await Organization.getByID(organizationId);
63
+ if (!organization) {
64
+ console.warn('[chargeServiceFees] Organization not found for ID', organizationId);
65
+ continue;
66
+ }
67
+
68
+ // Group payments by Formatter.dateNumber(p.createdAt)
69
+ const groupedPayments: Map<string, Payment[]> = new Map();
70
+ for (const payment of payments) {
71
+ const date = Formatter.dateNumber(payment.createdAt);
72
+ if (!groupedPayments.has(date)) {
73
+ groupedPayments.set(date, []);
74
+ }
75
+ groupedPayments.get(date)!.push(payment);
76
+ }
77
+
78
+ for (const [_, payments] of groupedPayments) {
79
+ const totalFees = payments.reduce((sum, payment) => sum + payment.serviceFeeManual - payment.serviceFeeManualCharged, 0);
80
+ const groupedByDate = new Map<string, Payment[]>();
81
+
82
+ for (const p of payments) {
83
+ const date = Formatter.dateNumber(p.createdAt);
84
+ const existing = groupedByDate.get(date);
85
+ if (existing) {
86
+ existing.push(p);
87
+ } else {
88
+ groupedByDate.set(date, [p]);
89
+ }
90
+ }
91
+
92
+ for (const [day, payments] of groupedByDate) {
93
+ const date = new Date(Math.min(...payments.map(p => p.createdAt.getTime())));
94
+ const startOfDay = Formatter.luxon(date).startOf('day').toJSDate();
95
+ const endOfDay = Formatter.luxon(date).endOf('day').toJSDate();
96
+
97
+ // Ms don't work when querying MySQL, they get lost
98
+ startOfDay.setMilliseconds(0);
99
+ endOfDay.setMilliseconds(0);
100
+
101
+ const item = new BalanceItem();
102
+ item.type = BalanceItemType.ServiceFee;
103
+ item.description = $t('%1cJ', { date: day });
104
+ item.payingOrganizationId = organization.id;
105
+ item.organizationId = membershipOrganizationId;
106
+ item.VATPercentage = 21;
107
+ item.VATExcempt = VATService.getVATExcempt({
108
+ company: organization.defaultCompanies[0] ?? null,
109
+ sellingOrganization: membershipOrganization,
110
+ type: 'services',
111
+ });
112
+ item.VATIncluded = false;
113
+ item.quantity = 1;
114
+ item.unitPrice = totalFees;
115
+ item.createdAt = new Date();
116
+ item.status = BalanceItemStatus.Due;
117
+ item.startDate = startOfDay;
118
+ item.endDate = endOfDay;
119
+
120
+ // Check if we find an existing one
121
+ const existing = await BalanceItem.select()
122
+ .where('type', BalanceItemType.ServiceFee)
123
+ .where('organizationId', membershipOrganizationId)
124
+ .where('VATPercentage', item.VATPercentage)
125
+ .where('VATIncluded', item.VATIncluded)
126
+ .where('VATExcempt', item.VATExcempt)
127
+ .where('payingOrganizationId', organization.id)
128
+ .where('status', BalanceItemStatus.Due)
129
+ .where('startDate', startOfDay)
130
+ .where('endDate', endOfDay)
131
+ .first(false);
132
+
133
+ if (existing) {
134
+ existing.unitPrice += totalFees;
135
+ await existing.save();
136
+ BalanceItemService.scheduleUpdate(existing);
137
+ } else {
138
+ await item.save();
139
+ BalanceItemService.scheduleUpdate(item);
140
+ }
141
+
142
+ const { changedRows } = await Payment.update()
143
+ .where('id', payments.map(p => p.id))
144
+ .set('serviceFeeManualCharged', SQL.column('serviceFeeManual'))
145
+ .update();
146
+ if (changedRows !== payments.length) {
147
+ console.error('Unexpected change in serviceFeeManualCharged. Expected ' + payments.length + ', updated only ' + changedRows);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,48 @@
1
+ import { registerCron } from '@stamhoofd/crons';
2
+ import { Formatter } from '@stamhoofd/utility';
3
+ import { StripeInvoicer } from '../helpers/StripeInvoicer.js';
4
+ import { Organization, Platform } from '@stamhoofd/models';
5
+
6
+ registerCron('stripe-invoices', createStripeInvoices);
7
+
8
+ let lastStripeInvoice: Date | null = null;
9
+
10
+ async function createStripeInvoices() {
11
+ if (STAMHOOFD.environment !== 'production') {
12
+ // return;
13
+ }
14
+
15
+ if (STAMHOOFD.userMode === 'platform') {
16
+ return;
17
+ }
18
+
19
+ if (STAMHOOFD.STRIPE_CONNECT_METHOD === 'standard') {
20
+ return;
21
+ }
22
+
23
+ // Wait for the next day before doing a new check
24
+ const today = new Date();
25
+ if (lastStripeInvoice && Formatter.dateIso(lastStripeInvoice) === Formatter.dateIso(today)) {
26
+ console.log('Stripe check done for this day');
27
+ return;
28
+ }
29
+ console.log('Creating Stripe Invoices...');
30
+
31
+ if (!STAMHOOFD.STRIPE_SECRET_KEY) {
32
+ console.log('No stripe key set');
33
+ return;
34
+ }
35
+
36
+ const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
37
+ if (!membershipOrganizationId) {
38
+ return;
39
+ }
40
+
41
+ const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
42
+
43
+ const invoicer = new StripeInvoicer({
44
+ secretKey: STAMHOOFD.STRIPE_SECRET_KEY,
45
+ });
46
+ await invoicer.generateAllInvoices(membershipOrganization);
47
+ lastStripeInvoice = new Date();
48
+ }
@@ -0,0 +1,176 @@
1
+ import { registerCron } from '@stamhoofd/crons';
2
+ import { BalanceItem, Organization, Payment, Platform } from '@stamhoofd/models';
3
+ import { SQL } from '@stamhoofd/sql';
4
+ import type { PaymentProvider } from '@stamhoofd/structures';
5
+ import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, getPaymentProviderName, PaymentMethod, PaymentStatus, TranslatedString } from '@stamhoofd/structures';
6
+ import { Formatter } from '@stamhoofd/utility';
7
+ import { BalanceItemService } from '../services/BalanceItemService.js';
8
+ import { PaymentService } from '../services/PaymentService.js';
9
+ import { VATService } from '../services/VATService.js';
10
+
11
+ // Charge manual service fees every night
12
+ registerCron('transfer-fees', chargeTransferFees);
13
+
14
+ async function chargeTransferFees() {
15
+ if (STAMHOOFD.environment !== 'development' && (new Date().getHours() > 5 || new Date().getHours() < 2)) {
16
+ return;
17
+ }
18
+ const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
19
+ if (!membershipOrganizationId) {
20
+ return;
21
+ }
22
+
23
+ const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
24
+
25
+ // Succeeded payments
26
+ const payments = await Payment.select()
27
+ .where('status', PaymentStatus.Succeeded)
28
+ .where('transferFeeManual', '>', 0)
29
+ .where('transferFeeManualCharged', 0)
30
+ .where('createdAt', '<', STAMHOOFD.environment !== 'development' ? new Date() : Formatter.dateIso(new Date(Date.now() + 1000 * 60 * 60 * 24)))
31
+ .limit(200)
32
+ .orderBy('createdAt', 'ASC')
33
+ .fetch();
34
+
35
+ // Pending payments older than 14 days with method = Transfer or PointOfSale
36
+ const pendingPayments = await Payment.select()
37
+ .where('status', [PaymentStatus.Pending, PaymentStatus.Created])
38
+ .where('method', [PaymentMethod.Transfer, PaymentMethod.PointOfSale, PaymentMethod.Unknown])
39
+ .where('transferFeeManual', '>', 0)
40
+ .where('transferFeeManualCharged', 0)
41
+ .where('createdAt', '<', Formatter.dateIso(new Date(Date.now() - 1000 * 60 * 60 * 24 * 14)))
42
+ .limit(200)
43
+ .orderBy('createdAt', 'ASC')
44
+ .fetch();
45
+
46
+ const allPayments = [...payments, ...pendingPayments];
47
+
48
+ console.log('Found ' + allPayments.length + ' payments to charge service fees for');
49
+
50
+ const mappedToOrganizationId = new Map<string, Payment[]>();
51
+ for (const payment of allPayments) {
52
+ if (!payment.organizationId) {
53
+ console.warn('Payment without organizationId, skipping', payment.id);
54
+ continue;
55
+ }
56
+ if (!mappedToOrganizationId.has(payment.organizationId)) {
57
+ mappedToOrganizationId.set(payment.organizationId, []);
58
+ }
59
+ mappedToOrganizationId.get(payment.organizationId)!.push(payment);
60
+ }
61
+
62
+ for (const [organizationId, payments] of mappedToOrganizationId.entries()) {
63
+ const organization = await Organization.getByID(organizationId);
64
+ if (!organization) {
65
+ console.warn('Organization not found for ID', organizationId);
66
+ continue;
67
+ }
68
+
69
+ // Group payments by Formatter.dateNumber(p.createdAt)
70
+ const groupedPayments: Map<string, Payment[]> = new Map();
71
+ for (const payment of payments) {
72
+ const date = Formatter.dateNumber(payment.createdAt);
73
+ if (!groupedPayments.has(date)) {
74
+ groupedPayments.set(date, []);
75
+ }
76
+ groupedPayments.get(date)!.push(payment);
77
+ }
78
+
79
+ for (const [_, payments] of groupedPayments) {
80
+ const totalFees = payments.reduce((sum, payment) => sum + payment.transferFeeManual - payment.transferFeeManualCharged, 0);
81
+ const groupedByDate = new Map<string, Payment[]>();
82
+
83
+ for (const p of payments) {
84
+ const date = Formatter.dateNumber(p.createdAt);
85
+ const existing = groupedByDate.get(date);
86
+ if (existing) {
87
+ existing.push(p);
88
+ } else {
89
+ groupedByDate.set(date, [p]);
90
+ }
91
+ }
92
+
93
+ for (const [day, payments] of groupedByDate) {
94
+ const groupedByProvider = new Map<PaymentProvider | null, Payment[]>();
95
+
96
+ for (const p of payments) {
97
+ const provider = p.provider;
98
+ const existing = groupedByProvider.get(provider);
99
+ if (existing) {
100
+ existing.push(p);
101
+ } else {
102
+ groupedByProvider.set(provider, [p]);
103
+ }
104
+ }
105
+
106
+ for (const [provider, payments] of groupedByProvider) {
107
+ const date = new Date(Math.min(...payments.map(p => p.createdAt.getTime())));
108
+ const startOfDay = Formatter.luxon(date).startOf('day').toJSDate();
109
+ const endOfDay = Formatter.luxon(date).endOf('day').toJSDate();
110
+
111
+ // Ms don't work when querying MySQL, they get lost
112
+ startOfDay.setMilliseconds(0);
113
+ endOfDay.setMilliseconds(0);
114
+
115
+ const item = new BalanceItem();
116
+ item.type = BalanceItemType.TransferFee;
117
+ item.description = $t('%1Yq', { date: day });
118
+
119
+ if (provider) {
120
+ item.relations.set(BalanceItemRelationType.PaymentProvider, BalanceItemRelation.create({
121
+ id: provider,
122
+ name: TranslatedString.create(getPaymentProviderName(provider)),
123
+ }));
124
+ }
125
+
126
+ item.payingOrganizationId = organization.id;
127
+ item.organizationId = membershipOrganizationId;
128
+ item.VATPercentage = 21;
129
+ item.VATExcempt = VATService.getVATExcempt({
130
+ company: organization.defaultCompanies[0] ?? null,
131
+ sellingOrganization: membershipOrganization,
132
+ type: 'services',
133
+ });
134
+ item.VATIncluded = false;
135
+ item.quantity = 1;
136
+ item.unitPrice = totalFees;
137
+ item.createdAt = new Date();
138
+ item.status = BalanceItemStatus.Due;
139
+ item.startDate = startOfDay;
140
+ item.endDate = endOfDay;
141
+
142
+ // Check if we find an existing one
143
+ const existingAll = await BalanceItem.select()
144
+ .where('type', BalanceItemType.TransferFee)
145
+ .where('organizationId', membershipOrganizationId)
146
+ .where('VATPercentage', item.VATPercentage)
147
+ .where('VATIncluded', item.VATIncluded)
148
+ .where('VATExcempt', item.VATExcempt)
149
+ .where('payingOrganizationId', organization.id)
150
+ .where('status', BalanceItemStatus.Due)
151
+ .where('startDate', startOfDay)
152
+ .where('endDate', endOfDay)
153
+ .fetch();
154
+ const existing = provider ? existingAll.find(e => e.relations.get(BalanceItemRelationType.PaymentProvider)?.id === provider) : existingAll.find(e => !e.relations.has(BalanceItemRelationType.PaymentProvider));
155
+
156
+ if (existing) {
157
+ existing.unitPrice += totalFees;
158
+ await existing.save();
159
+ BalanceItemService.scheduleUpdate(existing);
160
+ } else {
161
+ await item.save();
162
+ BalanceItemService.scheduleUpdate(item);
163
+ }
164
+
165
+ const { changedRows } = await Payment.update()
166
+ .where('id', payments.map(p => p.id))
167
+ .set('transferFeeManualCharged', SQL.column('transferFeeManual'))
168
+ .update();
169
+ if (changedRows !== payments.length) {
170
+ console.error('Unexpected change in transferFeeManualCharged. Expected ' + payments.length + ', updated only ' + changedRows);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }