@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,12 +1,13 @@
1
- import type { Decoder} from '@simonbackx/simple-encoding';
1
+ import type { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { AutoEncoder, field, StringDecoder } from '@simonbackx/simple-encoding';
3
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
3
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
4
4
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
- import { Order, Ticket } from '@stamhoofd/models';
6
+ import { Order, Ticket, Webshop } from '@stamhoofd/models';
7
7
  import { TicketOrder, TicketPublic } from '@stamhoofd/structures';
8
8
 
9
9
  import { Context } from '../../../helpers/Context.js';
10
+ import { WebshopAuthHelper } from './WebshopAuthHelper.js';
10
11
  type Params = { id: string };
11
12
 
12
13
  class Query extends AutoEncoder {
@@ -45,6 +46,14 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
45
46
 
46
47
  async handle(request: DecodedRequest<Params, Query, Body>) {
47
48
  const organization = await Context.setOrganizationScope({ willAuthenticate: false });
49
+ const webshop = await Webshop.getByID(request.params.id);
50
+ if (!webshop || webshop.organizationId !== organization.id) {
51
+ throw new SimpleError({
52
+ code: 'not_found',
53
+ message: 'Webshop not found',
54
+ human: $t(`%FX`),
55
+ });
56
+ }
48
57
 
49
58
  if (request.query.secret) {
50
59
  const [ticket] = await Ticket.where({
@@ -61,17 +70,20 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
61
70
  });
62
71
  }
63
72
 
73
+ const order = await Order.getByID(ticket.orderId);
74
+ if (!order || order.webshopId !== request.params.id || order.organizationId !== organization.id) {
75
+ console.error('Error: missing order ' + ticket.orderId + ' for ticket ' + ticket.id);
76
+ throw new SimpleError({
77
+ code: 'not_found',
78
+ message: 'Ticket not found',
79
+ human: $t(`%FZ`),
80
+ });
81
+ }
82
+
83
+ await WebshopAuthHelper.checkOrderAccess(webshop, order);
84
+
64
85
  if (!request.query.orderId) {
65
86
  // Include item data
66
- const order = await Order.getByID(ticket.orderId);
67
- if (!order || order.webshopId !== request.params.id) {
68
- console.error('Error: missing order ' + ticket.orderId + ' for ticket ' + ticket.id);
69
- throw new SimpleError({
70
- code: 'not_found',
71
- message: 'Ticket not found',
72
- human: $t(`%FZ`),
73
- });
74
- }
75
87
 
76
88
  if (ticket.itemId) {
77
89
  const item = order.data.cart.items.find(i => i.id === ticket.itemId);
@@ -91,8 +103,7 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
91
103
  items: [item],
92
104
  }),
93
105
  ]);
94
- }
95
- else {
106
+ } else {
96
107
  return new Response([
97
108
  TicketPublic.create({
98
109
  ...ticket,
@@ -104,8 +115,7 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
104
115
  return new Response([
105
116
  TicketOrder.create(ticket),
106
117
  ]);
107
- }
108
- else {
118
+ } else {
109
119
  if (!request.query.orderId) {
110
120
  throw new SimpleError({
111
121
  code: 'not_found',
@@ -119,6 +129,17 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
119
129
  organizationId: organization.id,
120
130
  deletedAt: null,
121
131
  });
132
+
133
+ const order = await Order.getByID(request.query.orderId);
134
+ if (!order || order.webshopId !== request.params.id || order.organizationId !== organization.id) {
135
+ throw new SimpleError({
136
+ code: 'not_found',
137
+ message: 'Ticket not found',
138
+ human: $t(`%FZ`),
139
+ });
140
+ }
141
+
142
+ await WebshopAuthHelper.checkOrderAccess(webshop, order);
122
143
  return new Response(
123
144
  tickets.map(ticket => TicketOrder.create(ticket)),
124
145
  );
@@ -1,8 +1,8 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory, WebshopFactory } from '@stamhoofd/models';
3
- import { PermissionLevel, Permissions } from '@stamhoofd/structures';
3
+ import { PermissionLevel, Permissions, WebshopAuthType, WebshopMetaData } from '@stamhoofd/structures';
4
4
 
5
- import { TestUtils } from '@stamhoofd/test-utils';
5
+ import { STExpect, TestUtils } from '@stamhoofd/test-utils';
6
6
  import { testServer } from '../../../../tests/helpers/TestServer.js';
7
7
  import { GetWebshopEndpoint } from './GetWebshopEndpoint.js';
8
8
 
@@ -43,6 +43,38 @@ describe('Endpoint.GetWebshop', () => {
43
43
  expect((response.body as any).privateMeta).toBeUndefined();
44
44
  });
45
45
 
46
+ test('Reject anonymous access for required-auth webshop', async () => {
47
+ const organization = await new OrganizationFactory({}).create();
48
+ const webshop = await new WebshopFactory({
49
+ organizationId: organization.id,
50
+ meta: WebshopMetaData.create({
51
+ authType: WebshopAuthType.Required,
52
+ }),
53
+ }).create();
54
+
55
+ const r = Request.buildJson('GET', '/webshop/' + webshop.id, organization.getApiHost());
56
+
57
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(STExpect.errorWithCode('not_authenticated'));
58
+ });
59
+
60
+ test('Allow signed in user access for required-auth webshop', async () => {
61
+ const organization = await new OrganizationFactory({}).create();
62
+ const user = await new UserFactory({ organization }).create();
63
+ const token = await Token.createToken(user);
64
+ const webshop = await new WebshopFactory({
65
+ organizationId: organization.id,
66
+ meta: WebshopMetaData.create({
67
+ authType: WebshopAuthType.Required,
68
+ }),
69
+ }).create();
70
+
71
+ const r = Request.buildJson('GET', '/webshop/' + webshop.id, organization.getApiHost());
72
+ r.headers.authorization = 'Bearer ' + token.accessToken;
73
+
74
+ const response = await testServer.test(endpoint, r);
75
+ expect(response.body.id).toEqual(webshop.id);
76
+ });
77
+
46
78
  test('Get webshop as admin', async () => {
47
79
  const organization = await new OrganizationFactory({}).create();
48
80
  const user = await new UserFactory({
@@ -1,4 +1,4 @@
1
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
1
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
2
2
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Webshop } from '@stamhoofd/models';
@@ -6,6 +6,7 @@ import type { PrivateWebshop, Webshop as WebshopStruct } from '@stamhoofd/struct
6
6
 
7
7
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
8
8
  import { Context } from '../../../helpers/Context.js';
9
+ import { WebshopAuthHelper } from './WebshopAuthHelper.js';
9
10
 
10
11
  type Params = { id: string };
11
12
  type Query = undefined;
@@ -28,7 +29,6 @@ export class GetWebshopEndpoint extends Endpoint<Params, Query, Body, ResponseBo
28
29
 
29
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
30
31
  await Context.setOptionalOrganizationScope(); // editing webshop from the admin panel -> no scope
31
- await Context.optionalAuthenticate();
32
32
 
33
33
  const webshop = await Webshop.getByID(request.params.id);
34
34
  if (!webshop) {
@@ -39,6 +39,8 @@ export class GetWebshopEndpoint extends Endpoint<Params, Query, Body, ResponseBo
39
39
  });
40
40
  }
41
41
 
42
+ await WebshopAuthHelper.optionalAuthenticateForWebshop(webshop);
43
+
42
44
  return new Response(
43
45
  await AuthenticatedStructures.webshop(webshop),
44
46
  );
@@ -1,15 +1,17 @@
1
1
  import type { Response } from '@simonbackx/simple-endpoints';
2
2
  import { Request } from '@simonbackx/simple-endpoints';
3
- import type { Organization, StripeAccount, User} from '@stamhoofd/models';
3
+ import type { Organization, StripeAccount, User } from '@stamhoofd/models';
4
4
  import { OrganizationFactory, Token, UserFactory, Webshop, WebshopFactory } from '@stamhoofd/models';
5
- import type { OrderResponse} from '@stamhoofd/structures';
6
- import { Address, Cart, CartItem, CartItemOption, Customer, Option, OptionMenu, OrderData, PaymentConfiguration, PaymentMethod, PermissionLevel, Permissions, PrivatePaymentConfiguration, Product, ProductPrice, ProductType, SeatingPlan, SeatingPlanRow, SeatingPlanSeat, SeatingPlanSection, TransferSettings, WebshopDeliveryMethod, WebshopMetaData, WebshopOnSiteMethod, WebshopPrivateMetaData, WebshopTakeoutMethod, WebshopTimeSlot } from '@stamhoofd/structures';
5
+ import type { OrderResponse } from '@stamhoofd/structures';
6
+ import { Address, Cart, CartItem, CartItemOption, Customer, Option, OptionMenu, OrderData, PaymentConfiguration, PaymentMethod, PermissionLevel, Permissions, PrivatePaymentConfiguration, Product, ProductPrice, ProductType, SeatingPlan, SeatingPlanRow, SeatingPlanSeat, SeatingPlanSection, TransferSettings, WebshopAuthType, WebshopDeliveryMethod, WebshopMetaData, WebshopOnSiteMethod, WebshopPrivateMetaData, WebshopTakeoutMethod, WebshopTimeSlot } from '@stamhoofd/structures';
7
+ import { STExpect } from '@stamhoofd/test-utils';
7
8
  import { Country } from '@stamhoofd/types/Country';
8
9
  import sinon from 'sinon';
9
10
 
10
11
  import { StripeMocker } from '../../../../tests/helpers/StripeMocker.js';
11
12
  import { testServer } from '../../../../tests/helpers/TestServer.js';
12
13
  import { PatchWebshopOrdersEndpoint } from '../dashboard/webshops/PatchWebshopOrdersEndpoint.js';
14
+ import { GetOrderEndpoint } from './GetOrderEndpoint.js';
13
15
  import { PlaceOrderEndpoint } from './PlaceOrderEndpoint.js';
14
16
 
15
17
  const address = Address.create({
@@ -31,6 +33,7 @@ describe('Endpoint.PlaceOrderEndpoint', () => {
31
33
  // Test endpoint
32
34
  const endpoint = new PlaceOrderEndpoint();
33
35
  const patchWebshopOrdersEndpoint = new PatchWebshopOrdersEndpoint();
36
+ const getOrderEndpoint = new GetOrderEndpoint();
34
37
 
35
38
  let organization: Organization;
36
39
  let webshop: Webshop;
@@ -328,6 +331,31 @@ describe('Endpoint.PlaceOrderEndpoint', () => {
328
331
  });
329
332
 
330
333
  describe('User authentication', () => {
334
+ test('Required-auth webshop rejects anonymous orders', async () => {
335
+ webshop.meta.authType = WebshopAuthType.Required;
336
+ await webshop.save();
337
+
338
+ const orderData = OrderData.create({
339
+ paymentMethod: PaymentMethod.Unknown,
340
+ checkoutMethod: onSiteMethod,
341
+ timeSlot: slot4,
342
+ cart: Cart.create({
343
+ items: [
344
+ CartItem.create({
345
+ product,
346
+ productPrice: freeProductPrice,
347
+ amount: 1,
348
+ }),
349
+ ],
350
+ }),
351
+ customer,
352
+ });
353
+
354
+ const r = Request.buildJson('POST', `/webshop/${webshop.id}/order`, organization.getApiHost(), orderData);
355
+
356
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(STExpect.errorWithCode('not_authenticated'));
357
+ });
358
+
331
359
  test('Placing an order with a signed in user overrides the order customer', async () => {
332
360
  const user = await new UserFactory({ organization, firstName: 'User', lastName: 'Tester' }).create();
333
361
  const token = await Token.createToken(user);
@@ -370,6 +398,49 @@ describe('Endpoint.PlaceOrderEndpoint', () => {
370
398
  expect(order.data.customer.lastName).toEqual(user.lastName);
371
399
  });
372
400
 
401
+ test('Required-auth webshop order can only be read by owner', async () => {
402
+ webshop.meta.authType = WebshopAuthType.Required;
403
+ await webshop.save();
404
+
405
+ const user = await new UserFactory({ organization, firstName: 'User', lastName: 'Tester' }).create();
406
+ const token = await Token.createToken(user);
407
+ const otherUser = await new UserFactory({ organization }).create();
408
+ const otherToken = await Token.createToken(otherUser);
409
+
410
+ const orderData = OrderData.create({
411
+ paymentMethod: PaymentMethod.Unknown,
412
+ checkoutMethod: onSiteMethod,
413
+ timeSlot: slot4,
414
+ cart: Cart.create({
415
+ items: [
416
+ CartItem.create({
417
+ product,
418
+ productPrice: freeProductPrice,
419
+ amount: 1,
420
+ }),
421
+ ],
422
+ }),
423
+ customer,
424
+ });
425
+
426
+ const placeRequest = Request.buildJson('POST', `/webshop/${webshop.id}/order`, organization.getApiHost(), orderData);
427
+ placeRequest.headers.authorization = 'Bearer ' + token.accessToken;
428
+ const placeResponse = await testServer.test(endpoint, placeRequest);
429
+ const order = placeResponse.body.order;
430
+
431
+ const anonymousRequest = Request.buildJson('GET', `/webshop/${webshop.id}/order/${order.id}`, organization.getApiHost());
432
+ await expect(testServer.test(getOrderEndpoint, anonymousRequest)).rejects.toThrow(STExpect.errorWithCode('not_authenticated'));
433
+
434
+ const otherUserRequest = Request.buildJson('GET', `/webshop/${webshop.id}/order/${order.id}`, organization.getApiHost());
435
+ otherUserRequest.headers.authorization = 'Bearer ' + otherToken.accessToken;
436
+ await expect(testServer.test(getOrderEndpoint, otherUserRequest)).rejects.toThrow(STExpect.errorWithCode('not_found'));
437
+
438
+ const ownerRequest = Request.buildJson('GET', `/webshop/${webshop.id}/order/${order.id}`, organization.getApiHost());
439
+ ownerRequest.headers.authorization = 'Bearer ' + token.accessToken;
440
+ const ownerResponse = await testServer.test(getOrderEndpoint, ownerRequest);
441
+ expect(ownerResponse.body.id).toEqual(order.id);
442
+ });
443
+
373
444
  /**
374
445
  * This checks if the authenticated context is passed when multiple orders are executed in a queue
375
446
  */
@@ -1,21 +1,23 @@
1
- import { createMollieClient, PaymentMethod as molliePaymentMethod } from '@mollie/api-client';
1
+ import { PaymentMethod as molliePaymentMethod } from '@mollie/api-client';
2
2
  import type { Decoder } from '@simonbackx/simple-encoding';
3
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
3
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
4
4
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
6
  import { Email } from '@stamhoofd/email';
7
- import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
7
+ import { BalanceItem, BalanceItemPayment, MolliePayment, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
8
8
  import { QueueHandler } from '@stamhoofd/queues';
9
- import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderData, OrderResponse, Order as OrderStruct, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, TranslatedString, Version, WebshopAuthType, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
9
+ import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderData, OrderResponse, Order as OrderStruct, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, TranslatedString, Version, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
10
10
  import { Formatter } from '@stamhoofd/utility';
11
11
 
12
12
  import { BuckarooHelper } from '../../../helpers/BuckarooHelper.js';
13
13
  import { Context } from '../../../helpers/Context.js';
14
+ import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
14
15
  import { StripeHelper } from '../../../helpers/StripeHelper.js';
15
16
  import { AuditLogService } from '../../../services/AuditLogService.js';
16
- import { UitpasService } from '../../../services/uitpas/UitpasService.js';
17
- import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
17
+ import { MollieService } from '../../../services/MollieService.js';
18
18
  import { PaymentService } from '../../../services/PaymentService.js';
19
+ import { UitpasService } from '../../../services/uitpas/UitpasService.js';
20
+ import { WebshopAuthHelper } from './WebshopAuthHelper.js';
19
21
 
20
22
  type Params = { id: string };
21
23
  type Query = undefined;
@@ -58,7 +60,6 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
58
60
 
59
61
  async handle(request: DecodedRequest<Params, Query, Body>) {
60
62
  const organization = await Context.setOrganizationScope();
61
- await Context.optionalAuthenticate();
62
63
 
63
64
  // Read + validate + update stock in one go, to prevent race conditions
64
65
  const { webshop, order } = await QueueHandler.schedule('webshop-stock/' + request.params.id, async () => {
@@ -73,15 +74,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
73
74
 
74
75
  // const organization = (await Organization.getByID(webshopWithoutOrganization.organizationId))!
75
76
  const webshop = webshopWithoutOrganization.setRelation(Webshop.organization, organization);
76
-
77
- if (webshop.meta.authType === WebshopAuthType.Required && !Context.user) {
78
- throw new SimpleError({
79
- code: 'not_authenticated',
80
- message: 'Not authenticated',
81
- human: $t(`%w5`),
82
- statusCode: 401,
83
- });
84
- }
77
+ await WebshopAuthHelper.optionalAuthenticateForWebshop(webshop);
85
78
 
86
79
  // For non paid organizations, the limit is 10
87
80
  if (!organization.meta.packages.isPaid && STAMHOOFD.environment !== 'test') {
@@ -89,8 +82,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
89
82
 
90
83
  try {
91
84
  limiter.track(organization.id, 1);
92
- }
93
- catch (e) {
85
+ } catch (e) {
94
86
  Email.sendWebmaster({
95
87
  subject: '[Limiet] Limiet bereikt voor aantal bestellingen',
96
88
  text: 'Beste, \nDe limiet werd bereikt voor het aantal bestellingen per dag. \nVereniging: ' + organization.id + ' (' + organization.name + ')' + '\n\n' + e.message + '\n\nStamhoofd',
@@ -173,14 +165,13 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
173
165
  // Mark this order as paid
174
166
  await order.markPaid(null, organization, webshop);
175
167
  await order.save();
176
- }
177
- else {
168
+ } else {
178
169
  const payment = new Payment();
179
170
  payment.organizationId = organization.id;
180
171
  payment.method = request.body.paymentMethod;
181
172
  payment.status = PaymentStatus.Created;
182
173
  payment.price = totalPrice;
183
- PaymentService.round(payment);
174
+ PaymentService.roundPayment(payment);
184
175
  totalPrice = payment.price;
185
176
  payment.paidAt = null;
186
177
  payment.customer = PaymentCustomer.create({
@@ -191,16 +182,16 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
191
182
 
192
183
  // Determine the payment provider
193
184
  // Throws if invalid
194
- const { provider, stripeAccount } = await organization.getPaymentProviderFor(payment.method, webshop.privateMeta.paymentConfiguration);
185
+ const { provider, stripeAccount } = await organization.getPaymentProviderFor(payment.method, null, webshop.privateMeta.paymentConfiguration);
195
186
  payment.provider = provider;
196
187
  payment.stripeAccountId = stripeAccount?.id ?? null;
197
- ServiceFeeHelper.setServiceFee(
188
+ await ServiceFeeHelper.setServiceFee(
198
189
  payment,
199
190
  organization,
200
191
  webshop.meta.ticketType === WebshopTicketType.None ? 'webshop' : 'tickets',
201
192
  order.data.cart.items.flatMap(i => i.calculatedPrices.map(p => p.discountedPrice)),
202
193
  );
203
-
194
+ await ServiceFeeHelper.setTransferFee({ payment, organization, stripeAccount });
204
195
  await payment.save();
205
196
 
206
197
  // Deprecated field
@@ -210,8 +201,6 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
210
201
  // Save order to get the id
211
202
  await order.save();
212
203
 
213
- const balanceItemPayments: (BalanceItemPayment & { balanceItem: BalanceItem })[] = [];
214
-
215
204
  // Create balance item
216
205
  const balanceItem = new BalanceItem();
217
206
  balanceItem.type = BalanceItemType.Order;
@@ -239,7 +228,6 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
239
228
  balanceItemPayment.organizationId = organization.id;
240
229
  balanceItemPayment.price = balanceItem.price;
241
230
  await balanceItemPayment.save();
242
- balanceItemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem));
243
231
 
244
232
  let paymentUrl: string | null = null;
245
233
  let paymentQRCode: string | null = null;
@@ -255,8 +243,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
255
243
  balanceItem.status = BalanceItemStatus.Due;
256
244
  await balanceItem.save();
257
245
  await payment.save();
258
- }
259
- else if (payment.method === PaymentMethod.PointOfSale) {
246
+ } else if (payment.method === PaymentMethod.PointOfSale) {
260
247
  // Not really paid, but needed to create the tickets if needed
261
248
  await order.markPaid(payment, organization, webshop);
262
249
 
@@ -267,8 +254,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
267
254
  balanceItem.status = BalanceItemStatus.Due;
268
255
  await balanceItem.save();
269
256
  await payment.save();
270
- }
271
- else {
257
+ } else {
272
258
  const cancelUrl = 'https://' + webshop.getHost() + '/payment?id=' + encodeURIComponent(payment.id) + '&cancel=true';
273
259
  const redirectUrl = 'https://' + webshop.getHost() + '/payment?id=' + encodeURIComponent(payment.id);
274
260
  const exchangeUrl = 'https://' + organization.getApiHost() + '/v' + Version + '/payments/' + encodeURIComponent(payment.id) + '?exchange=true';
@@ -287,7 +273,6 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
287
273
  payment: payment.id,
288
274
  },
289
275
  i18n: request.i18n,
290
- lineItems: balanceItemPayments,
291
276
  organization,
292
277
  customer: {
293
278
  name: order.data.customer.name,
@@ -295,31 +280,28 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
295
280
  },
296
281
  });
297
282
  paymentUrl = stripeResult.paymentUrl;
298
- }
299
- else if (payment.provider === PaymentProvider.Mollie) {
283
+ } else if (payment.provider === PaymentProvider.Mollie) {
300
284
  // Mollie payment
301
- const token = await MollieToken.getTokenFor(webshop.organizationId);
302
- if (!token) {
285
+ const mollieService = await MollieService.create({ sellingOrganization: organization });
286
+ if (!mollieService) {
303
287
  throw new SimpleError({
304
288
  code: '',
305
289
  message: $t(`%w3`, { method: PaymentMethodHelper.getName(payment.method) }),
306
290
  });
307
291
  }
308
- const profileId = organization.privateMeta.mollieProfile?.id ?? await token.getProfileId(webshop.getHost());
292
+ const profileId = await mollieService.getProfileId(webshop.getHost());
309
293
  if (!profileId) {
310
294
  throw new SimpleError({
311
295
  code: '',
312
296
  message: $t(`%w4`, { method: PaymentMethodHelper.getName(payment.method) }),
313
297
  });
314
298
  }
315
- const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
316
- const locale = Context.i18n.locale.replace('-', '_');
317
- const molliePayment = await mollieClient.payments.create({
299
+ const molliePayment = await mollieService.client.payments.create({
318
300
  amount: {
319
301
  currency: 'EUR',
320
302
  value: (totalPrice / 10000).toFixed(2), // from 4 decimals to 0 decimals
321
303
  },
322
- method: payment.method == PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method == PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
304
+ method: payment.method === PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method === PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
323
305
  testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production',
324
306
  profileId,
325
307
  description,
@@ -331,7 +313,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
331
313
  webshop: webshop.id,
332
314
  payment: payment.id,
333
315
  },
334
- locale: ['en_US', 'en_GB', 'nl_NL', 'nl_BE', 'fr_FR', 'fr_BE', 'de_DE', 'de_AT', 'de_CH', 'es_ES', 'ca_ES', 'pt_PT', 'it_IT', 'nb_NO', 'sv_SE', 'fi_FI', 'da_DK', 'is_IS', 'hu_HU', 'pl_PL', 'lv_LV', 'lt_LT'].includes(locale) ? (locale as any) : null,
316
+ locale: mollieService.locale,
335
317
  });
336
318
  console.log(molliePayment);
337
319
  paymentUrl = molliePayment.getCheckoutUrl();
@@ -341,11 +323,9 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
341
323
  dbPayment.paymentId = payment.id;
342
324
  dbPayment.mollieId = molliePayment.id;
343
325
  await dbPayment.save();
344
- }
345
- else if (payment.provider == PaymentProvider.Payconiq) {
326
+ } else if (payment.provider == PaymentProvider.Payconiq) {
346
327
  ({ paymentUrl, paymentQRCode } = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, exchangeUrl));
347
- }
348
- else if (payment.provider == PaymentProvider.Buckaroo) {
328
+ } else if (payment.provider == PaymentProvider.Buckaroo) {
349
329
  // Increase request timeout because buckaroo is super slow
350
330
  request.request.request?.setTimeout(60 * 1000);
351
331
  const buckaroo = new BuckarooHelper(organization.privateMeta?.buckarooSettings?.key ?? '', organization.privateMeta?.buckarooSettings?.secret ?? '', organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production');
@@ -362,8 +342,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
362
342
  }),
363
343
  });
364
344
  }
365
- }
366
- else {
345
+ } else {
367
346
  throw new Error('Unknown payment provider');
368
347
  }
369
348
  }
@@ -374,8 +353,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
374
353
  order: OrderStruct.create({ ...order, payment: PaymentStruct.create(payment) }),
375
354
  }));
376
355
  }
377
- }
378
- catch (e) {
356
+ } catch (e) {
379
357
  // Mark order as failed to release stock
380
358
  if (order) {
381
359
  await order.deleteOrderBecauseOfCreationError();
@@ -1,4 +1,4 @@
1
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
1
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
2
2
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { UitpasPriceCheckRequest, UitpasPriceCheckResponse } from '@stamhoofd/structures';
@@ -47,8 +47,7 @@ export class RetrieveUitpasSocialTariffPricesEndpoint extends Endpoint<Params, Q
47
47
  prices: [reducedPrice], // Convert to cents
48
48
  });
49
49
  return new Response(uitpasPriceCheckResponse);
50
- }
51
- else {
50
+ } else {
52
51
  // OFFICIAL FLOW with an UiTPAS number
53
52
  // request should include a reduced price (estimate by the frontend)
54
53
  const organization = await Context.setOrganizationScope({ willAuthenticate: false });
@@ -58,8 +57,7 @@ export class RetrieveUitpasSocialTariffPricesEndpoint extends Endpoint<Params, Q
58
57
  });
59
58
  return new Response(uitpasPriceCheckResponse);
60
59
  }
61
- }
62
- else {
60
+ } else {
63
61
  // NON-OFFICIAL FLOW
64
62
  // request should include UiTPAS-numbers, reduced price AND base price
65
63
  if (request.body.reducedPrice === null) {
@@ -0,0 +1,36 @@
1
+ import { SimpleError } from '@simonbackx/simple-errors';
2
+ import type { Order, Webshop } from '@stamhoofd/models';
3
+ import { WebshopAuthType } from '@stamhoofd/structures';
4
+
5
+ import { Context } from '../../../helpers/Context.js';
6
+
7
+ export class WebshopAuthHelper {
8
+ static async optionalAuthenticateForWebshop(webshop: Webshop) {
9
+ await Context.optionalAuthenticate();
10
+
11
+ if (webshop.meta.authType === WebshopAuthType.Required && !Context.user) {
12
+ throw new SimpleError({
13
+ code: 'not_authenticated',
14
+ message: 'Not authenticated',
15
+ human: $t(`%w5`),
16
+ statusCode: 401,
17
+ });
18
+ }
19
+ }
20
+
21
+ static async checkOrderAccess(webshop: Webshop, order: Order) {
22
+ await this.optionalAuthenticateForWebshop(webshop);
23
+
24
+ if (webshop.meta.authType !== WebshopAuthType.Required) {
25
+ return;
26
+ }
27
+
28
+ if (order.userId !== Context.user?.id) {
29
+ throw new SimpleError({
30
+ code: 'not_found',
31
+ message: 'Order not found',
32
+ human: $t(`%FY`),
33
+ });
34
+ }
35
+ }
36
+ }
@@ -1,5 +1,5 @@
1
1
  import { ArrayDecoder, AutoEncoder, field, StringDecoder } from '@simonbackx/simple-encoding';
2
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
2
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
3
3
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
5
  import { CpuService } from '../../services/CpuService.js';
@@ -1,7 +1,7 @@
1
1
  import type { XlsxTransformerColumn } from '@stamhoofd/excel-writer';
2
2
  import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
3
3
  import { BalanceItem } from '@stamhoofd/models';
4
- import type { BalanceItemWithPayments} from '@stamhoofd/structures';
4
+ import type { BalanceItemWithPayments } from '@stamhoofd/structures';
5
5
  import { BalanceItemRelationType, ExcelExportType, getBalanceItemRelationTypeName, getBalanceItemTypeName, PaginatedResponse, PaymentStatus } from '@stamhoofd/structures';
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
  import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
@@ -1,6 +1,6 @@
1
1
  import type { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
2
2
  import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
3
- import type { EventNotification, LimitedFilteredRequest} from '@stamhoofd/structures';
3
+ import type { EventNotification, LimitedFilteredRequest } from '@stamhoofd/structures';
4
4
  import { EventNotificationStatus, EventNotificationStatusHelper, ExcelExportType, Platform as PlatformStruct } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { GetEventNotificationsEndpoint } from '../endpoints/global/events/GetEventNotificationsEndpoint.js';
@@ -1,7 +1,7 @@
1
1
  import type { XlsxTransformerColumn, XlsxTransformerSheet } from '@stamhoofd/excel-writer';
2
2
  import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
3
3
  import { Platform } from '@stamhoofd/models';
4
- import type { LimitedFilteredRequest, PlatformMember} from '@stamhoofd/structures';
4
+ import type { LimitedFilteredRequest, PlatformMember } from '@stamhoofd/structures';
5
5
  import { ExcelExportType, Gender, GroupType, MembershipStatus, PlatformFamily, Platform as PlatformStruct, UnencodeablePaginatedResponse } from '@stamhoofd/structures';
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
  import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
@@ -240,9 +240,9 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
240
240
  const organizationIds = organizations.results.map(o => o.id);
241
241
  const responsibilities = organizationIds.length
242
242
  ? await MemberResponsibilityRecord.select()
243
- .where('organizationId', organizationIds)
244
- .where('endDate', null)
245
- .fetch()
243
+ .where('organizationId', organizationIds)
244
+ .where('endDate', null)
245
+ .fetch()
246
246
  : [];
247
247
 
248
248
  // Load groups and members