@stamhoofd/backend 2.121.0 → 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.
- package/output-2.txt +1 -0
- package/output.txt +1 -0
- package/package.json +69 -52
- package/src/audit-logs/EventNotificationLogger.ts +40 -0
- package/src/audit-logs/ModelLogger.ts +19 -15
- package/src/audit-logs/OrganizationLogger.ts +1 -1
- package/src/audit-logs/RegistrationInvitationLogger.ts +24 -24
- package/src/audit-logs/init.ts +8 -2
- package/src/boot.ts +19 -18
- package/src/crons/amazon-ses.ts +13 -26
- package/src/crons/balance-emails.ts +3 -5
- package/src/crons/clearExcelCache.test.ts +1 -1
- package/src/crons/clearExcelCache.ts +1 -2
- package/src/crons/drip-emails.ts +42 -0
- package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +4 -0
- package/src/crons/helpers/isOutside.ts +11 -0
- package/src/crons/helpers/useSavedIterator.ts +32 -0
- package/src/crons/index.ts +5 -0
- package/src/crons/invoices.ts +32 -38
- package/src/crons/members-fees.ts +50 -0
- package/src/crons/mollie-chargebacks.ts +14 -15
- package/src/crons/service-fees.ts +152 -0
- package/src/crons/stripe-invoices.ts +48 -0
- package/src/crons/transfer-fees.ts +176 -0
- package/src/crons/update-cached-balances.ts +4 -4
- package/src/crons.ts +7 -64
- package/src/debug.ts +4 -2
- package/src/email-recipient-loaders/documents.ts +1 -1
- package/src/email-recipient-loaders/members.ts +1 -1
- package/src/email-recipient-loaders/payments.ts +4 -6
- package/src/email-recipient-loaders/registrations.ts +1 -1
- package/src/email-replacements/getEmailReplacementsForPayment.ts +5 -9
- package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +1 -1
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +3 -5
- package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +13 -7
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +4 -6
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +10 -6
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +39 -21
- package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +23 -5
- package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -2
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/DeleteUserEndpoint.ts +3 -3
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +1 -1
- package/src/endpoints/auth/GetUserEndpoint.ts +1 -1
- package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +5 -8
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +1 -1
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +1 -1
- package/src/endpoints/auth/SignupEndpoint.ts +2 -3
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +20 -3
- package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +2 -1
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +2 -4
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +1 -1
- package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +5 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +2 -2
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +3 -4
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +1 -2
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +4 -6
- package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +1 -1
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +6 -10
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +4 -7
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -9
- package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +1 -1
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +1 -1
- package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +2 -2
- package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
- package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -3
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +49 -4
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +5 -8
- package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +12 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +128 -127
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -9
- package/src/endpoints/global/files/GetFileCache.ts +3 -4
- package/src/endpoints/global/files/UploadFile.ts +3 -4
- package/src/endpoints/global/files/UploadImage.ts +3 -4
- package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +1 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +1 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +4 -2
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -3
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +1 -1
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -12
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +195 -12
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +16 -22
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +6 -30
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +1 -1
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +40 -8
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +2 -2
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -2
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +4 -1
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +4 -5
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +9 -16
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -1
- package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +31 -31
- package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +18 -20
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1 -1
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +6 -10
- package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +35 -6
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +1 -1
- package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +90 -6
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +35 -34
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +62 -63
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +6 -9
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +51 -52
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +6 -7
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +1 -1
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +13 -5
- package/src/endpoints/global/sso/GetSSOEndpoint.ts +2 -3
- package/src/endpoints/global/sso/SetSSOEndpoint.ts +1 -1
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +1 -2
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +2 -4
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +7 -9
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +1 -5
- package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +11 -11
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableInvoicesEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +4 -4
- package/src/endpoints/organization/dashboard/billing/GetRegisterCodeEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.test.ts +949 -0
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +60 -57
- package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +20 -20
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +10 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +10 -8
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +5 -6
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +4 -5
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +5 -5
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +7 -7
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +50 -12
- package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +5 -10
- package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +21 -14
- package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +35 -28
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +19 -18
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +4 -5
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +4 -6
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +10 -15
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +11 -2
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +4 -6
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +22 -7
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +20 -27
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +95 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -2
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +1 -1
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
- package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -5
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +1 -1
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -1
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +14 -3
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -2
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +37 -16
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +34 -2
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +74 -3
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +16 -33
- package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -5
- package/src/endpoints/organization/webshops/WebshopAuthHelper.ts +36 -0
- package/src/endpoints/system/HealthEndpoint.ts +1 -1
- package/src/excel-loaders/balance-items.ts +1 -1
- package/src/excel-loaders/event-notifications.ts +1 -1
- package/src/excel-loaders/members.ts +1 -1
- package/src/excel-loaders/organizations.ts +3 -3
- package/src/excel-loaders/payments.ts +31 -1
- package/src/excel-loaders/platform-memberships.ts +13 -14
- package/src/excel-loaders/receivable-balances.ts +2 -2
- package/src/excel-loaders/registrations.ts +2 -3
- package/src/helpers/AddressValidator.ts +7 -14
- package/src/helpers/AdminPermissionChecker.ts +50 -63
- package/src/helpers/AuthenticatedStructures.ts +69 -55
- package/src/helpers/BalanceItemRelationsBackfiller.test.ts +174 -0
- package/src/helpers/BalanceItemRelationsBackfiller.ts +163 -0
- package/src/helpers/BuckarooHelper.ts +2 -4
- package/src/helpers/CheckSettlements.ts +12 -24
- package/src/helpers/Context.ts +15 -10
- package/src/helpers/CookieHelper.ts +1 -2
- package/src/helpers/EmailResumer.ts +1 -2
- package/src/helpers/FileCache.ts +2 -3
- package/src/helpers/FinancialSupportHelper.ts +2 -2
- package/src/helpers/FlagMomentCleanup.ts +1 -2
- package/src/helpers/ForwardHandler.ts +5 -10
- package/src/helpers/GlobalHelper.ts +3 -3
- package/src/helpers/LegacyBillingConverter.ts +787 -0
- package/src/helpers/MemberMerger.test.ts +782 -0
- package/src/helpers/MemberMerger.ts +600 -0
- package/src/helpers/MemberUserSyncer.ts +6 -12
- package/src/helpers/MembershipCharger.ts +2 -4
- package/src/helpers/PaymentCustomerResolver.test.ts +203 -0
- package/src/helpers/PaymentCustomerResolver.ts +144 -0
- package/src/helpers/PeriodHelper.ts +2 -3
- package/src/helpers/RecordAnswerHelper.test.ts +105 -110
- package/src/helpers/RecordAnswerHelper.ts +7 -7
- package/src/helpers/SeedTools.ts +106 -0
- package/src/helpers/ServiceFeeHelper.ts +125 -15
- package/src/helpers/SetupStepUpdater.ts +38 -32
- package/src/helpers/StamhoofdFilterAccessHelper.ts +52 -0
- package/src/helpers/StripeHelper.ts +14 -36
- package/src/helpers/StripeInvoicer.ts +419 -0
- package/src/helpers/StripePayoutChecker.ts +5 -10
- package/src/helpers/TagHelper.ts +7 -8
- package/src/helpers/TemporaryMemberAccess.ts +1 -2
- package/src/helpers/ThrottledQueue.ts +1 -2
- package/src/helpers/UitpasTokenRepository.ts +1 -2
- package/src/helpers/ViesHelper.ts +7 -11
- package/src/helpers/XlsxTransformerColumnHelper.ts +1 -1
- package/src/helpers/outstandingBalanceJoin.ts +2 -2
- package/src/helpers/updateMemberDetailsUitpasNumber.ts +3 -5
- package/src/migrate.ts +12 -5
- package/src/{seeds → migrations}/0000000004-single-organization.ts +10 -1
- package/src/migrations/0000000006-default-organization.ts +139 -0
- package/src/{seeds → migrations}/1715028563-user-permissions.ts +17 -27
- package/src/{seeds/1765896674-document-update-year.test.ts → migrations/1750090029-document-update-year.test.ts} +27 -26
- package/src/migrations/1750090029-document-update-year.ts +103 -0
- package/src/{seeds → migrations}/1750090030-records-configuration.ts +33 -32
- package/src/migrations/1750090031-records-configuration-webshops.ts +36 -0
- package/src/migrations/1752848540-national-numbers.ts +585 -0
- package/src/migrations/1752848550-records-configuration-group-filter.ts +1064 -0
- package/src/{seeds → migrations}/1752848561-groups-registration-periods.ts +70 -26
- package/src/{seeds → migrations}/1754560914-groups-prices.test.ts +1 -1
- package/src/{seeds → migrations}/1754560914-groups-prices.ts +9 -15
- package/src/{seeds → migrations}/1756293699-fill-previous-next-period-id.ts +1 -2
- package/src/migrations/1761665607-sync-member-users.ts +40 -0
- package/src/migrations/1779121240-invoice-email-template.sql +6 -0
- package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
- package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
- package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
- package/src/migrations/1780933598-document-groups.ts +88 -0
- package/src/migrations/1781692342-balance-item-relations.ts +13 -0
- package/src/migrations/1781857866-platform-admins.ts +103 -0
- package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
- package/src/seeds/0000000001-throw.ts +5 -0
- package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
- package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
- package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
- package/src/seeds/1773754928-force-save-members.ts +41 -8
- package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
- package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
- package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
- package/src/seeds/1780915002-update-orders.ts +39 -0
- package/src/seeds/1782047708-organization-company-address.ts +44 -0
- package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
- package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
- package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
- package/src/services/AuditLogService.test.ts +51 -0
- package/src/services/AuditLogService.ts +45 -0
- package/src/services/BalanceItemService.ts +141 -44
- package/src/services/CpuService.ts +3 -6
- package/src/services/DatabaseCollationService.test.ts +1 -2
- package/src/services/DocumentService.ts +1 -2
- package/src/services/EventNotificationService.ts +1 -1
- package/src/services/FileSignService.ts +2 -4
- package/src/services/InvoicePdfService.ts +286 -0
- package/src/services/InvoiceService.ts +204 -251
- package/src/services/InvoiceXMLService.ts +424 -0
- package/src/services/MemberNumberService.ts +6 -11
- package/src/services/MemberRecordStore.ts +8 -10
- package/src/services/MollieService.ts +114 -121
- package/src/services/PaymentMandateService.ts +45 -45
- package/src/services/PaymentReallocationService.test.ts +1 -1
- package/src/services/PaymentReallocationService.ts +7 -9
- package/src/services/PaymentService.ts +444 -308
- package/src/services/PlatformMembershipService.ts +4 -8
- package/src/services/ReferralService.ts +223 -0
- package/src/services/RegistrationService.ts +63 -53
- package/src/services/SSOService.test.ts +119 -0
- package/src/services/SSOService.ts +56 -49
- package/src/services/STPackageService.ts +97 -61
- package/src/services/UniqueMemberNumberService.ts +69 -0
- package/src/services/UniqueUserService.ts +3 -6
- package/src/services/VATService.ts +85 -0
- package/src/services/uitpas/PassholderEndpoints.ts +3 -6
- package/src/services/uitpas/UitpasService.ts +6 -10
- package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
- package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
- package/src/services/uitpas/searchUitpasEvents.ts +1 -2
- package/src/sql-filters/audit-logs.ts +1 -1
- package/src/sql-filters/balance-item-payments.ts +1 -1
- package/src/sql-filters/balance-items.ts +1 -1
- package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
- package/src/sql-filters/document-templates.ts +1 -1
- package/src/sql-filters/documents.ts +1 -1
- package/src/sql-filters/email-recipients.ts +1 -1
- package/src/sql-filters/emails.ts +1 -1
- package/src/sql-filters/event-notifications.ts +1 -1
- package/src/sql-filters/events.ts +6 -1
- package/src/sql-filters/groups.ts +5 -0
- package/src/sql-filters/invoiced-balance-items.ts +1 -1
- package/src/sql-filters/invoices.ts +1 -1
- package/src/sql-filters/member-responsibility-records.ts +1 -1
- package/src/sql-filters/members.ts +1 -1
- package/src/sql-filters/orders.ts +4 -4
- package/src/sql-filters/organization-registration-periods.ts +1 -1
- package/src/sql-filters/organizations.ts +1 -1
- package/src/sql-filters/platform-memberships.ts +3 -3
- package/src/sql-filters/receivable-balances.ts +1 -1
- package/src/sql-filters/registration-periods.ts +1 -1
- package/src/sql-filters/registrations.ts +1 -1
- package/src/sql-filters/tickets.ts +1 -1
- package/src/sql-filters/users.ts +1 -1
- package/src/sql-sorters/groups.ts +23 -0
- package/src/sql-sorters/platform-memberships.ts +16 -16
- package/src/sql-sorters/registration-invitations.ts +2 -2
- package/tests/actions/patchOrganizationMember.ts +1 -1
- package/tests/actions/patchUserMember.ts +1 -1
- package/tests/assertions/assertBalances.ts +6 -12
- package/tests/e2e/api-rate-limits.test.ts +12 -24
- package/tests/e2e/bundle-discounts.test.ts +4 -1
- package/tests/e2e/charge-members.test.ts +13 -11
- package/tests/e2e/documents.test.ts +4 -1
- package/tests/e2e/register.test.ts +7 -9
- package/tests/e2e/stock.test.ts +4 -3
- package/tests/e2e/tickets.test.ts +1 -1
- package/tests/helpers/MollieMocker.ts +462 -0
- package/tests/helpers/PayconiqMocker.ts +138 -45
- package/tests/helpers/StripeMocker.ts +9 -6
- package/tests/helpers/index.ts +4 -0
- package/tests/init/initAdmin.ts +2 -2
- package/tests/init/initBundleDiscount.ts +1 -1
- package/tests/init/initMembershipOrganization.ts +13 -0
- package/tests/init/initPermissionRole.ts +3 -4
- package/tests/vitest.global.setup.ts +1 -1
- package/vitest.config.js +6 -0
- package/.nvmrc +0 -1
- package/src/helpers/GroupBuilder.ts +0 -418
- package/src/seeds/0000000000-example.ts +0 -11
- package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
- package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
- package/src/seeds/1726847064-setup-steps.ts +0 -16
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
- package/src/seeds/1729253172-update-orders.ts +0 -21
- package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
- package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
- package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
- package/src/seeds/1740046783-update-membership.ts +0 -17
- package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
- package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
- package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
- package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
- package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
- package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
- package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
- package/src/seeds/1756303697-update-email-counts.ts +0 -76
- package/src/seeds/1761665607-sync-member-users.ts +0 -59
- package/src/seeds/1765896674-document-update-year.ts +0 -66
- /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
- /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
- /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
- /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
- /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
- /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
- /package/src/{seeds → migrations}/1779121239-default-invoice-email-template.sql +0 -0
- /package/src/{seeds → migrations}/data/default-email-templates.sql +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createMollieClient, PaymentStatus as MolliePaymentStatus } from '@mollie/api-client';
|
|
2
1
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
2
|
import type { Member, User } from '@stamhoofd/models';
|
|
4
|
-
import { BalanceItem } from '@stamhoofd/models';
|
|
5
|
-
import { BalanceItemPayment, Group, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, sendEmailTemplate } from '@stamhoofd/models';
|
|
3
|
+
import { BalanceItem, BalanceItemPayment, Group, Organization, PayconiqPayment, Payment, Platform, sendEmailTemplate, UsedRegisterCode } from '@stamhoofd/models';
|
|
6
4
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
7
5
|
import type { Checkoutable, PaymentConfiguration, PrivatePaymentConfiguration } from '@stamhoofd/structures';
|
|
8
|
-
import { AuditLogSource, BalanceItemPaymentDetailed, BalanceItemType, EmailTemplateType, Invoice, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, PaymentType, Recipient, VATExcemptReason, Version } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogSource, BalanceItemPaymentDetailed, BalanceItemType, EmailTemplateType, Invoice, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, PaymentType, Recipient, Replacement, VATExcemptReason, Version } from '@stamhoofd/structures';
|
|
7
|
+
import type { CreateMandateSettings } from '@stamhoofd/structures/checkout/CreateMandateSettings.js';
|
|
9
8
|
import type { PaymentMandate } from '@stamhoofd/structures/PaymentMandate.js';
|
|
10
9
|
import { PaymentMandateStatus, PaymentMandateType } from '@stamhoofd/structures/PaymentMandate.js';
|
|
11
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
@@ -20,10 +19,22 @@ import { BalanceItemPaymentService } from './BalanceItemPaymentService.js';
|
|
|
20
19
|
import { BalanceItemService } from './BalanceItemService.js';
|
|
21
20
|
import { MollieService } from './MollieService.js';
|
|
22
21
|
import { PaymentMandateService } from './PaymentMandateService.js';
|
|
23
|
-
import
|
|
22
|
+
import { ReferralService } from './ReferralService.js';
|
|
23
|
+
import { VATService } from './VATService.js';
|
|
24
|
+
import { STPackageService } from './STPackageService.js';
|
|
24
25
|
|
|
25
26
|
export class PaymentService {
|
|
26
|
-
static async
|
|
27
|
+
static async updateReversedPaymentsFor(payment: Payment) {
|
|
28
|
+
if (payment.reversingPaymentId) {
|
|
29
|
+
// Update refund amount of that payment
|
|
30
|
+
const reversingPayment = await Payment.getByID(payment.reversingPaymentId);
|
|
31
|
+
if (reversingPayment) {
|
|
32
|
+
await reversingPayment.updateRefundedAmount();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async handlePaymentStatusUpdate(payment: Payment, organization: Organization, status: PaymentStatus, paidAt?: Date) {
|
|
27
38
|
if (payment.status === status) {
|
|
28
39
|
return;
|
|
29
40
|
}
|
|
@@ -32,14 +43,19 @@ export class PaymentService {
|
|
|
32
43
|
throw new SimpleError({
|
|
33
44
|
code: 'cannot_fail',
|
|
34
45
|
message: 'A payment that has been invoiced cannot be marked as failed. Instead create a separate chargeback payment with a negative amount.',
|
|
35
|
-
human: $t('%1RI')
|
|
36
|
-
})
|
|
46
|
+
human: $t('%1RI'),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (organization.id !== payment.organizationId) {
|
|
51
|
+
throw new Error('Unexpected organization, expected ' + payment.organizationId + ', received ' + organization.id);
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
await AuditLogService.setContext({ fallbackUserId: payment.payingUserId, source: AuditLogSource.Payment, fallbackOrganizationId: payment.organizationId }, async () => {
|
|
40
55
|
if (status === PaymentStatus.Succeeded) {
|
|
56
|
+
console.log('Marking as succeeded', payment.id);
|
|
41
57
|
payment.status = PaymentStatus.Succeeded;
|
|
42
|
-
payment.paidAt = new Date();
|
|
58
|
+
payment.paidAt = paidAt ?? new Date();
|
|
43
59
|
await payment.save();
|
|
44
60
|
|
|
45
61
|
// Prevent concurrency issues
|
|
@@ -57,15 +73,30 @@ export class PaymentService {
|
|
|
57
73
|
|
|
58
74
|
// Flush caches so data is up to date in response
|
|
59
75
|
await BalanceItemService.flushCaches(organization.id);
|
|
76
|
+
|
|
77
|
+
await this.updateReversedPaymentsFor(payment);
|
|
60
78
|
});
|
|
61
79
|
|
|
80
|
+
if (payment.price >= 100_00 && payment.organizationId && payment.payingOrganizationId && payment.organizationId === ((await Platform.getShared()).membershipOrganizationId)) {
|
|
81
|
+
// We spend some money
|
|
82
|
+
const code = await UsedRegisterCode.getFor(payment.payingOrganizationId);
|
|
83
|
+
if (code && !code.balanceItemId) {
|
|
84
|
+
console.log('Rewarding code ' + code.id + ' for payment');
|
|
85
|
+
|
|
86
|
+
// Deze code werd nog niet beloond
|
|
87
|
+
await ReferralService.reward(code);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
// It is possible the mandate succeeds immediately, in which case we might
|
|
63
92
|
// need to save it as the default payment method
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
await this.saveMandateIfNeeded({
|
|
94
|
+
payment,
|
|
95
|
+
sellingOrganizationId: organization.id,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (payment.type === PaymentType.Chargeback) {
|
|
99
|
+
await this.handleChargebackCreated(payment, organization);
|
|
69
100
|
}
|
|
70
101
|
return;
|
|
71
102
|
}
|
|
@@ -93,11 +124,14 @@ export class PaymentService {
|
|
|
93
124
|
|
|
94
125
|
// Flush caches so data is up to date in response
|
|
95
126
|
await BalanceItemService.flushCaches(organization.id);
|
|
127
|
+
|
|
128
|
+
await this.updateReversedPaymentsFor(payment);
|
|
96
129
|
});
|
|
97
130
|
}
|
|
98
131
|
|
|
99
132
|
// Moved to failed
|
|
100
133
|
if (status === PaymentStatus.Failed) {
|
|
134
|
+
console.log('Marking as failed', payment.id);
|
|
101
135
|
await QueueHandler.schedule('balance-item-update/' + organization.id, async () => {
|
|
102
136
|
const balanceItemPayments = await BalanceItemPayment.balanceItem.load(
|
|
103
137
|
(await BalanceItemPayment.where({ paymentId: payment.id })).map(r => r.setRelation(BalanceItemPayment.payment, payment)),
|
|
@@ -112,6 +146,10 @@ export class PaymentService {
|
|
|
112
146
|
// Flush caches so data is up to date in response
|
|
113
147
|
await BalanceItemService.flushCaches(organization.id);
|
|
114
148
|
});
|
|
149
|
+
|
|
150
|
+
if (payment.type === PaymentType.Payment) {
|
|
151
|
+
await this.handlePaymentFailed(payment, organization);
|
|
152
|
+
}
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
// If OLD status was FAILED, we need to revert the actions
|
|
@@ -134,7 +172,84 @@ export class PaymentService {
|
|
|
134
172
|
});
|
|
135
173
|
}
|
|
136
174
|
|
|
137
|
-
static async
|
|
175
|
+
private static async handleChargebackCreated(payment: Payment, organization: Organization) {
|
|
176
|
+
if (!payment.payingOrganizationId) {
|
|
177
|
+
// For now only for B2B
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const payingOrganization = await Organization.getByID(payment.payingOrganizationId, true);
|
|
182
|
+
|
|
183
|
+
await sendEmailTemplate(organization, {
|
|
184
|
+
recipients: await payingOrganization.getFinanceAdminRecipients(),
|
|
185
|
+
defaultReplacements: [
|
|
186
|
+
Replacement.create({
|
|
187
|
+
token: 'payingOrganizationName',
|
|
188
|
+
value: payingOrganization.name,
|
|
189
|
+
}),
|
|
190
|
+
Replacement.create({
|
|
191
|
+
token: 'paymentUrl',
|
|
192
|
+
value: 'https://' + payingOrganization.getDashboardHost() + '/boekhouding/openstaand/' + organization.uri,
|
|
193
|
+
}),
|
|
194
|
+
],
|
|
195
|
+
template: {
|
|
196
|
+
type: EmailTemplateType.ChargebackPayingOrganization,
|
|
197
|
+
},
|
|
198
|
+
type: 'transactional',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await this.deactivatePackagesIfNeeded(payment, organization);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private static async handlePaymentFailed(payment: Payment, organization: Organization) {
|
|
205
|
+
if (!payment.payingOrganizationId) {
|
|
206
|
+
// For now only for B2B
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!payment.mandateId) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!payment.adminUserId && payment.method !== PaymentMethod.DirectDebit) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const payingOrganization = await Organization.getByID(payment.payingOrganizationId, true);
|
|
219
|
+
|
|
220
|
+
await sendEmailTemplate(organization, {
|
|
221
|
+
recipients: await payingOrganization.getFinanceAdminRecipients(),
|
|
222
|
+
defaultReplacements: [
|
|
223
|
+
Replacement.create({
|
|
224
|
+
token: 'payingOrganizationName',
|
|
225
|
+
value: payingOrganization.name,
|
|
226
|
+
}),
|
|
227
|
+
Replacement.create({
|
|
228
|
+
token: 'paymentUrl',
|
|
229
|
+
value: 'https://' + payingOrganization.getDashboardHost() + '/boekhouding/openstaand/' + organization.uri,
|
|
230
|
+
}),
|
|
231
|
+
],
|
|
232
|
+
template: {
|
|
233
|
+
type: EmailTemplateType.AutomaticChargeFailedPayingOrganization,
|
|
234
|
+
},
|
|
235
|
+
type: 'transactional',
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await this.deactivatePackagesIfNeeded(payment, organization);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static async deactivatePackagesIfNeeded(payment: Payment, organization: Organization) {
|
|
242
|
+
// If admin initiated mandate, then mark packages as failed only
|
|
243
|
+
if ((payment.adminUserId || payment.type === PaymentType.Chargeback) && payment.mandateId && STAMHOOFD.userMode === 'organization') {
|
|
244
|
+
const platform = await Platform.getShared();
|
|
245
|
+
const payingOrganizationId = payment.payingOrganizationId;
|
|
246
|
+
if (payingOrganizationId && organization.id === platform.membershipOrganizationId) {
|
|
247
|
+
await STPackageService.markFailedPayment(payingOrganizationId);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static async saveMandateIfNeeded({ payment, sellingOrganizationId }: { payment: Payment; sellingOrganizationId: string }) {
|
|
138
253
|
// Save as default
|
|
139
254
|
if (payment.createMandate && payment.createMandate.saveAsDefault) {
|
|
140
255
|
if (payment.mandateId && payment.status === PaymentStatus.Succeeded) {
|
|
@@ -142,9 +257,9 @@ export class PaymentService {
|
|
|
142
257
|
await PaymentMandateService.setDefaultMandate({
|
|
143
258
|
mandateId: payment.mandateId,
|
|
144
259
|
payingOrganizationId: payment.payingOrganizationId,
|
|
145
|
-
|
|
146
|
-
payingUserId: payment.payingUserId
|
|
147
|
-
})
|
|
260
|
+
sellingOrganizationId,
|
|
261
|
+
payingUserId: payment.payingUserId,
|
|
262
|
+
});
|
|
148
263
|
} catch (e) {
|
|
149
264
|
// Ignore as setting the payment status is more important
|
|
150
265
|
console.error(e);
|
|
@@ -165,6 +280,11 @@ export class PaymentService {
|
|
|
165
280
|
return;
|
|
166
281
|
}
|
|
167
282
|
|
|
283
|
+
if (org && payment.organizationId !== org.id && org.id !== payment.payingOrganizationId) {
|
|
284
|
+
console.error('Non-matching organization found for payment', payment.id, 'org', org.id);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
168
288
|
// Explicitly set userId to null, because all actions caused by a poll are not caused by the currently signed in user, but the paying user id
|
|
169
289
|
return await AuditLogService.setContext({ userId: payment.payingUserId ?? null, source: AuditLogSource.Payment }, async () => {
|
|
170
290
|
if (!payment.organizationId) {
|
|
@@ -194,29 +314,29 @@ export class PaymentService {
|
|
|
194
314
|
status = PaymentStatus.Failed;
|
|
195
315
|
}
|
|
196
316
|
|
|
317
|
+
console.log('Stripe setting status to', status);
|
|
197
318
|
await this.handlePaymentStatusUpdate(payment, organization, status);
|
|
198
|
-
}
|
|
199
|
-
catch (e) {
|
|
319
|
+
} catch (e) {
|
|
200
320
|
console.error('Payment check failed Stripe', payment.id, e);
|
|
201
321
|
if (this.isManualExpired(payment.status, payment)) {
|
|
202
322
|
console.error('Manually marking Stripe payment as expired', payment.id);
|
|
203
323
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
204
324
|
}
|
|
205
325
|
}
|
|
206
|
-
}
|
|
207
|
-
else if (payment.provider === PaymentProvider.Mollie) {
|
|
326
|
+
} else if (payment.provider === PaymentProvider.Mollie) {
|
|
208
327
|
try {
|
|
209
328
|
const mollieClient = await MollieService.create({
|
|
210
|
-
sellingOrganization: organization
|
|
211
|
-
});
|
|
329
|
+
sellingOrganization: organization,
|
|
330
|
+
});
|
|
212
331
|
if (mollieClient) {
|
|
213
|
-
let {status} = await mollieClient.getStatus(payment, cancel || this.shouldTryToCancel(payment.status, payment));
|
|
332
|
+
let { status } = await mollieClient.getStatus(payment, cancel || this.shouldTryToCancel(payment.status, payment));
|
|
214
333
|
|
|
215
334
|
if (this.isManualExpired(status, payment)) {
|
|
216
335
|
console.error('Manually marking Mollie payment as expired', payment.id);
|
|
217
336
|
status = PaymentStatus.Failed;
|
|
218
337
|
}
|
|
219
338
|
|
|
339
|
+
console.log('Mollie setting status to', status);
|
|
220
340
|
await this.handlePaymentStatusUpdate(payment, organization, status);
|
|
221
341
|
} else {
|
|
222
342
|
console.error('Missing Mollie Credentials for payment', payment.id);
|
|
@@ -225,7 +345,6 @@ export class PaymentService {
|
|
|
225
345
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
226
346
|
}
|
|
227
347
|
}
|
|
228
|
-
|
|
229
348
|
} catch (e) {
|
|
230
349
|
console.error('Payment check failed Mollie', payment.id, e);
|
|
231
350
|
if (this.isManualExpired(payment.status, payment)) {
|
|
@@ -233,9 +352,8 @@ export class PaymentService {
|
|
|
233
352
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
234
353
|
}
|
|
235
354
|
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const helper = new BuckarooHelper(organization.privateMeta.buckarooSettings?.key ?? '', organization.privateMeta.buckarooSettings?.secret ?? '', organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production');
|
|
355
|
+
} else if (payment.provider === PaymentProvider.Buckaroo) {
|
|
356
|
+
const helper = new BuckarooHelper(organization.privateMeta.buckarooSettings?.key ?? '', organization.privateMeta.buckarooSettings?.secret ?? '', testMode);
|
|
239
357
|
try {
|
|
240
358
|
let status = await helper.getStatus(payment);
|
|
241
359
|
|
|
@@ -244,17 +362,16 @@ export class PaymentService {
|
|
|
244
362
|
status = PaymentStatus.Failed;
|
|
245
363
|
}
|
|
246
364
|
|
|
365
|
+
console.log('Buckaroo setting status to', status);
|
|
247
366
|
await this.handlePaymentStatusUpdate(payment, organization, status);
|
|
248
|
-
}
|
|
249
|
-
catch (e) {
|
|
367
|
+
} catch (e) {
|
|
250
368
|
console.error('Payment check failed Buckaroo', payment.id, e);
|
|
251
369
|
if (this.isManualExpired(payment.status, payment)) {
|
|
252
370
|
console.error('Manually marking Buckaroo payment as expired', payment.id);
|
|
253
371
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
254
372
|
}
|
|
255
373
|
}
|
|
256
|
-
}
|
|
257
|
-
else if (payment.provider == PaymentProvider.Payconiq) {
|
|
374
|
+
} else if (payment.provider == PaymentProvider.Payconiq) {
|
|
258
375
|
// Check status
|
|
259
376
|
|
|
260
377
|
const payconiqPayments = await PayconiqPayment.where({ paymentId: payment.id }, { limit: 1 });
|
|
@@ -272,8 +389,7 @@ export class PaymentService {
|
|
|
272
389
|
console.error('Manually cancelling Payconiq payment', payment.id);
|
|
273
390
|
if (await payconiqPayment.cancel(organization)) {
|
|
274
391
|
status = PaymentStatus.Failed;
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
392
|
+
} else {
|
|
277
393
|
console.error('Failed to manually cancel payment');
|
|
278
394
|
}
|
|
279
395
|
}
|
|
@@ -284,8 +400,7 @@ export class PaymentService {
|
|
|
284
400
|
}
|
|
285
401
|
|
|
286
402
|
await this.handlePaymentStatusUpdate(payment, organization, status);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
403
|
+
} else {
|
|
289
404
|
console.warn('Payconiq payment is missing for organization ' + organization.id + ' while checking payment status...');
|
|
290
405
|
|
|
291
406
|
if (this.isManualExpired(payment.status, payment)) {
|
|
@@ -293,19 +408,17 @@ export class PaymentService {
|
|
|
293
408
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
294
409
|
}
|
|
295
410
|
}
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
411
|
+
} else {
|
|
298
412
|
console.error('Invalid payment provider', payment.provider, 'for payment', payment.id);
|
|
299
413
|
if (this.isManualExpired(payment.status, payment)) {
|
|
300
414
|
console.error('Manually marking unknown payment as expired', payment.id);
|
|
301
415
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
302
416
|
}
|
|
303
417
|
}
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
418
|
+
} else {
|
|
306
419
|
// Do a manual update if needed
|
|
307
420
|
if (payment.status === PaymentStatus.Succeeded) {
|
|
308
|
-
if (
|
|
421
|
+
if ((payment.provider === PaymentProvider.Mollie && STAMHOOFD.environment === 'development')) {
|
|
309
422
|
// Update the status
|
|
310
423
|
await StripeHelper.getStatus(payment, false, testMode);
|
|
311
424
|
}
|
|
@@ -370,7 +483,7 @@ export class PaymentService {
|
|
|
370
483
|
// should be subtracted, not added
|
|
371
484
|
const balanceItemsTotalPrice = payment.price - payment.roundingAmount;
|
|
372
485
|
|
|
373
|
-
const {roundingAmount, price} = this.round(balanceItemsTotalPrice);
|
|
486
|
+
const { roundingAmount, price } = this.round(balanceItemsTotalPrice);
|
|
374
487
|
payment.roundingAmount = roundingAmount;
|
|
375
488
|
payment.price = price;
|
|
376
489
|
}
|
|
@@ -382,7 +495,7 @@ export class PaymentService {
|
|
|
382
495
|
if (difference === 0) {
|
|
383
496
|
return {
|
|
384
497
|
price: amount,
|
|
385
|
-
roundingAmount: 0
|
|
498
|
+
roundingAmount: 0,
|
|
386
499
|
};
|
|
387
500
|
}
|
|
388
501
|
|
|
@@ -392,11 +505,11 @@ export class PaymentService {
|
|
|
392
505
|
|
|
393
506
|
return {
|
|
394
507
|
price: amount + difference,
|
|
395
|
-
roundingAmount: difference
|
|
396
|
-
}
|
|
508
|
+
roundingAmount: difference,
|
|
509
|
+
};
|
|
397
510
|
}
|
|
398
511
|
|
|
399
|
-
static calculateTotalPrice({ balanceItems, organization, members}: {
|
|
512
|
+
static calculateTotalPrice({ balanceItems, organization, members }: {
|
|
400
513
|
balanceItems: Map<BalanceItem, number>;
|
|
401
514
|
organization: Organization;
|
|
402
515
|
members?: Member[];
|
|
@@ -458,19 +571,19 @@ export class PaymentService {
|
|
|
458
571
|
});
|
|
459
572
|
}
|
|
460
573
|
|
|
461
|
-
return { hasNegative, price, roundingAmount, names }
|
|
574
|
+
return { hasNegative, price, roundingAmount, names };
|
|
462
575
|
}
|
|
463
576
|
|
|
464
|
-
static validateTotalPrice({ price, roundingAmount, checkout}: {
|
|
465
|
-
price: number
|
|
466
|
-
roundingAmount: number
|
|
577
|
+
static validateTotalPrice({ price, roundingAmount, checkout }: {
|
|
578
|
+
price: number; // already rounded
|
|
579
|
+
roundingAmount: number;
|
|
467
580
|
checkout: Pick<Checkoutable<never>, 'totalPrice'>;
|
|
468
581
|
}) {
|
|
469
582
|
// total price without rounding
|
|
470
|
-
const balanceItemsPrice = price - roundingAmount;
|
|
583
|
+
const balanceItemsPrice = price - roundingAmount;
|
|
471
584
|
|
|
472
585
|
// also accept rounding that might have happend in the frontend and that was correct
|
|
473
|
-
if (checkout.totalPrice !== null && checkout.totalPrice !== balanceItemsPrice && checkout.totalPrice !== price) {
|
|
586
|
+
if (checkout.totalPrice !== null && checkout.totalPrice !== balanceItemsPrice && checkout.totalPrice !== price) {
|
|
474
587
|
throw new SimpleError({
|
|
475
588
|
code: 'changed_price',
|
|
476
589
|
message: $t(`%vk`, { total: Formatter.price(price) }),
|
|
@@ -478,9 +591,7 @@ export class PaymentService {
|
|
|
478
591
|
}
|
|
479
592
|
}
|
|
480
593
|
|
|
481
|
-
static async validateCustomer({
|
|
482
|
-
price: number,
|
|
483
|
-
hasNegative: boolean,
|
|
594
|
+
static async validateCustomer({ user, checkout, payingOrganization }: {
|
|
484
595
|
user: User | null;
|
|
485
596
|
checkout: Pick<Checkoutable<never>, 'customer'>;
|
|
486
597
|
payingOrganization?: Organization | null;
|
|
@@ -490,84 +601,82 @@ export class PaymentService {
|
|
|
490
601
|
firstName: user?.firstName,
|
|
491
602
|
lastName: user?.lastName,
|
|
492
603
|
email: user?.email,
|
|
493
|
-
phone: checkout.customer?.phone
|
|
604
|
+
phone: checkout.customer?.phone,
|
|
494
605
|
});
|
|
495
606
|
|
|
496
607
|
// Use structured transfer description prefix
|
|
497
608
|
let prefix = '';
|
|
498
609
|
|
|
499
610
|
if (payingOrganization) {
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
611
|
+
if (!checkout.customer) {
|
|
612
|
+
throw new SimpleError({
|
|
613
|
+
code: 'missing_fields',
|
|
614
|
+
message: 'customer is required when paying as an organization',
|
|
615
|
+
human: $t(`%vz`),
|
|
616
|
+
});
|
|
617
|
+
}
|
|
508
618
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
619
|
+
if (!checkout.customer.company) {
|
|
620
|
+
throw new SimpleError({
|
|
621
|
+
code: 'missing_fields',
|
|
622
|
+
message: 'customer.company is required when paying as an organization',
|
|
623
|
+
human: $t(`%w0`),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
516
626
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
627
|
+
// Search company id
|
|
628
|
+
// this avoids needing to check the VAT number every time
|
|
629
|
+
const id = checkout.customer.company.id;
|
|
630
|
+
let foundCompany = payingOrganization.meta.companies.find(c => c.id === id);
|
|
521
631
|
|
|
522
|
-
|
|
632
|
+
if (!foundCompany) {
|
|
633
|
+
if (payingOrganization.meta.companies.length > 0) {
|
|
523
634
|
throw new SimpleError({
|
|
524
635
|
code: 'invalid_data',
|
|
525
|
-
message:
|
|
636
|
+
message: 'Cannot set company data. Please save the company data to the paying organization meta data before using it.',
|
|
637
|
+
human: $t(`%w1`),
|
|
526
638
|
});
|
|
527
639
|
}
|
|
528
640
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
});
|
|
534
|
-
}
|
|
641
|
+
// In case of no organizations, we ignore the id of the default, as this changes every time
|
|
642
|
+
// we'll only compare the contents
|
|
643
|
+
foundCompany = payingOrganization.defaultCompanies[0];
|
|
644
|
+
}
|
|
535
645
|
|
|
536
|
-
|
|
646
|
+
if (!checkout.customer.company.equals(foundCompany)) {
|
|
647
|
+
throw new SimpleError({
|
|
648
|
+
code: 'invalid_data',
|
|
649
|
+
message: 'Cannot change company data. Please save the company data to the paying organization meta data before using it.',
|
|
650
|
+
human: $t(`%w1`),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
537
653
|
|
|
538
|
-
|
|
654
|
+
customer.company = foundCompany;
|
|
539
655
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
else {
|
|
545
|
-
// Zero amount payment (without refunds) without specifying a company will just use the default company to link to the payment
|
|
546
|
-
// It doesn't really matter since the price is zero and we won't invoice it.
|
|
547
|
-
const company = this.getDefaultCompanyForOrganization(payingOrganization);
|
|
548
|
-
if (company) {
|
|
549
|
-
customer.company = company;
|
|
550
|
-
}
|
|
656
|
+
const orgNumber = parseInt(payingOrganization.uri);
|
|
657
|
+
|
|
658
|
+
if (orgNumber !== 0 && !isNaN(orgNumber)) {
|
|
659
|
+
prefix = orgNumber + '';
|
|
551
660
|
}
|
|
552
661
|
} else {
|
|
553
662
|
if (checkout.customer && checkout.customer.company) {
|
|
554
|
-
customer.company = checkout.customer.company.clone()
|
|
663
|
+
customer.company = checkout.customer.company.clone();
|
|
555
664
|
await ViesHelper.checkCompany(checkout.customer.company, customer.company);
|
|
556
665
|
}
|
|
557
666
|
}
|
|
558
667
|
|
|
559
|
-
if (
|
|
668
|
+
if (customer.company?.VATNumber !== checkout.customer?.company?.VATNumber) {
|
|
560
669
|
// Security check: because previous validation and generation might have used the VATNumber from the checkout
|
|
561
670
|
throw new SimpleError({
|
|
562
671
|
code: 'changed_VAT_number',
|
|
563
|
-
message: 'Unexpected VAT number change'
|
|
564
|
-
})
|
|
672
|
+
message: 'Unexpected VAT number change',
|
|
673
|
+
});
|
|
565
674
|
}
|
|
566
675
|
|
|
567
676
|
return { customer, prefix };
|
|
568
677
|
}
|
|
569
678
|
|
|
570
|
-
static async createProForma({ balanceItems, organization, user, members, checkout, payingOrganization}: {
|
|
679
|
+
static async createProForma({ balanceItems, organization, user, members, checkout, payingOrganization }: {
|
|
571
680
|
balanceItems: Map<BalanceItem, number>;
|
|
572
681
|
organization: Organization;
|
|
573
682
|
user: User;
|
|
@@ -576,12 +685,12 @@ export class PaymentService {
|
|
|
576
685
|
payingOrganization?: Organization | null;
|
|
577
686
|
}) {
|
|
578
687
|
// Calculate total price to pay
|
|
579
|
-
const { price,
|
|
580
|
-
PaymentService.validateTotalPrice({ price, roundingAmount, checkout })
|
|
688
|
+
const { price, roundingAmount } = PaymentService.calculateTotalPrice({ balanceItems, organization, members });
|
|
689
|
+
PaymentService.validateTotalPrice({ price, roundingAmount, checkout });
|
|
690
|
+
|
|
691
|
+
const { customer } = await PaymentService.validateCustomer({ user, checkout, payingOrganization });
|
|
692
|
+
const { seller } = PaymentService.validateVATRates({ customer, sellingOrganization: organization, balanceItems });
|
|
581
693
|
|
|
582
|
-
const { customer, } = await PaymentService.validateCustomer({ user, checkout, payingOrganization, price, hasNegative })
|
|
583
|
-
const { seller } = PaymentService.validateVATRates({ customer, organization, balanceItems });
|
|
584
|
-
|
|
585
694
|
// Create invoice instead from fictive PaymentGeneral
|
|
586
695
|
const fakePaymentGeneral = PaymentGeneral.create({
|
|
587
696
|
id: 'pro-forma',
|
|
@@ -592,15 +701,15 @@ export class PaymentService {
|
|
|
592
701
|
return BalanceItemPaymentDetailed.create({
|
|
593
702
|
id: 'pro-forma-' + balanceItem.id,
|
|
594
703
|
balanceItem: balanceItem.getStructure(),
|
|
595
|
-
price
|
|
596
|
-
})
|
|
597
|
-
})
|
|
598
|
-
})
|
|
704
|
+
price,
|
|
705
|
+
});
|
|
706
|
+
}),
|
|
707
|
+
});
|
|
599
708
|
|
|
600
709
|
let invoice: Invoice | null = null;
|
|
601
710
|
|
|
602
711
|
try {
|
|
603
|
-
|
|
712
|
+
invoice = Invoice.create({
|
|
604
713
|
seller,
|
|
605
714
|
customer,
|
|
606
715
|
payments: [fakePaymentGeneral],
|
|
@@ -617,46 +726,46 @@ export class PaymentService {
|
|
|
617
726
|
|
|
618
727
|
return {
|
|
619
728
|
invoice,
|
|
620
|
-
payment: fakePaymentGeneral
|
|
621
|
-
}
|
|
729
|
+
payment: fakePaymentGeneral,
|
|
730
|
+
};
|
|
622
731
|
}
|
|
623
732
|
|
|
624
|
-
static async registerChargeback(payment: Payment, amount: number) {
|
|
733
|
+
static async registerChargeback(payment: Payment, amount: number, date: Date) {
|
|
625
734
|
if (amount !== payment.price) {
|
|
626
735
|
// Creates issues to know what balance item was paid and what was not.
|
|
627
|
-
throw new Error('Cannot register chargeback with different amount than the payment for payment ' + payment.id)
|
|
736
|
+
throw new Error('Cannot register chargeback with different amount than the payment for payment ' + payment.id);
|
|
628
737
|
}
|
|
629
|
-
const balanceItemPayments = await BalanceItemPayment.select().where('paymentId', payment.id).fetch()
|
|
630
|
-
const items = await BalanceItem.getByIDs(...Formatter.uniqueArray(balanceItemPayments.map(b => b.balanceItemId)))
|
|
738
|
+
const balanceItemPayments = await BalanceItemPayment.select().where('paymentId', payment.id).fetch();
|
|
739
|
+
const items = await BalanceItem.getByIDs(...Formatter.uniqueArray(balanceItemPayments.map(b => b.balanceItemId)));
|
|
631
740
|
|
|
632
|
-
|
|
741
|
+
// Done validation
|
|
633
742
|
const chargeback = new Payment();
|
|
634
743
|
|
|
635
744
|
// Who will receive this money?
|
|
636
745
|
chargeback.organizationId = payment.organizationId!;
|
|
637
746
|
|
|
638
747
|
// Who paid
|
|
639
|
-
chargeback.payingUserId = payment.payingUserId
|
|
640
|
-
chargeback.payingOrganizationId = payment.payingOrganizationId
|
|
748
|
+
chargeback.payingUserId = payment.payingUserId;
|
|
749
|
+
chargeback.payingOrganizationId = payment.payingOrganizationId;
|
|
641
750
|
chargeback.customer = payment.customer;
|
|
642
751
|
|
|
643
|
-
chargeback.status = PaymentStatus.
|
|
644
|
-
chargeback.paidAt = new Date();
|
|
752
|
+
chargeback.status = PaymentStatus.Created;
|
|
645
753
|
chargeback.price = -payment.price;
|
|
646
754
|
chargeback.roundingAmount = -payment.roundingAmount;
|
|
647
755
|
chargeback.method = payment.method;
|
|
648
756
|
chargeback.type = PaymentType.Chargeback;
|
|
757
|
+
chargeback.mandateId = payment.mandateId; ;
|
|
758
|
+
chargeback.adminUserId = payment.adminUserId;
|
|
649
759
|
|
|
650
760
|
chargeback.provider = payment.provider;
|
|
651
|
-
chargeback.stripeAccountId = payment.stripeAccountId
|
|
652
|
-
|
|
761
|
+
chargeback.stripeAccountId = payment.stripeAccountId;
|
|
762
|
+
chargeback.reversingPaymentId = payment.id;
|
|
653
763
|
await chargeback.save();
|
|
654
764
|
|
|
655
|
-
|
|
656
765
|
for (const original of balanceItemPayments) {
|
|
657
766
|
// Create one balance item payment to pay it in one payment
|
|
658
767
|
const balanceItemPayment = new BalanceItemPayment();
|
|
659
|
-
balanceItemPayment.balanceItemId = original.balanceItemId
|
|
768
|
+
balanceItemPayment.balanceItemId = original.balanceItemId;
|
|
660
769
|
balanceItemPayment.paymentId = chargeback.id;
|
|
661
770
|
balanceItemPayment.organizationId = chargeback.organizationId;
|
|
662
771
|
balanceItemPayment.price = -original.price;
|
|
@@ -666,10 +775,19 @@ export class PaymentService {
|
|
|
666
775
|
// Update cached balance items pending amount (only created balance items, because those are involved in the payment)
|
|
667
776
|
await BalanceItemService.updatePaidAndPending(items);
|
|
668
777
|
|
|
778
|
+
const organization = await Organization.getByID(chargeback.organizationId, true);
|
|
779
|
+
|
|
780
|
+
try {
|
|
781
|
+
await this.handlePaymentStatusUpdate(chargeback, organization, PaymentStatus.Succeeded, date);
|
|
782
|
+
} catch (e) {
|
|
783
|
+
console.error('Failed to mark chargeback as succeeded', e);
|
|
784
|
+
await chargeback.delete();
|
|
785
|
+
throw e;
|
|
786
|
+
}
|
|
669
787
|
return chargeback;
|
|
670
788
|
}
|
|
671
789
|
|
|
672
|
-
static async createPayment({ balanceItems, organization, user, members, checkout, payingOrganization, serviceFeeType, createMandate, useMandate, paymentConfiguration, privatePaymentConfiguration}: {
|
|
790
|
+
static async createPayment({ balanceItems, organization, user, members, checkout, payingOrganization, serviceFeeType, createMandate, useMandate, paymentConfiguration, privatePaymentConfiguration, adminUserId }: {
|
|
673
791
|
balanceItems: Map<BalanceItem, number>;
|
|
674
792
|
organization: Organization;
|
|
675
793
|
user: User | null;
|
|
@@ -677,34 +795,35 @@ export class PaymentService {
|
|
|
677
795
|
checkout: Pick<Checkoutable<never>, 'paymentMethod' | 'totalPrice' | 'customer' | 'cancelUrl' | 'redirectUrl'>;
|
|
678
796
|
payingOrganization?: Organization | null;
|
|
679
797
|
serviceFeeType: 'webshop' | 'members' | 'tickets' | 'system';
|
|
680
|
-
createMandate: CreateMandateSettings | null
|
|
681
|
-
useMandate: PaymentMandate | null
|
|
682
|
-
paymentConfiguration: PaymentConfiguration
|
|
683
|
-
privatePaymentConfiguration: PrivatePaymentConfiguration
|
|
798
|
+
createMandate: CreateMandateSettings | null;
|
|
799
|
+
useMandate: PaymentMandate | null;
|
|
800
|
+
paymentConfiguration: PaymentConfiguration;
|
|
801
|
+
privatePaymentConfiguration: PrivatePaymentConfiguration;
|
|
802
|
+
adminUserId?: string | null;
|
|
684
803
|
}) {
|
|
685
804
|
if (balanceItems.size === 0) {
|
|
686
805
|
return null;
|
|
687
806
|
}
|
|
688
807
|
|
|
689
808
|
// Calculate total price to pay
|
|
690
|
-
const { price, roundingAmount, hasNegative, names } = this.calculateTotalPrice({ balanceItems, organization, members })
|
|
691
|
-
PaymentService.validateTotalPrice({ price, roundingAmount, checkout })
|
|
809
|
+
const { price, roundingAmount, hasNegative, names } = this.calculateTotalPrice({ balanceItems, organization, members });
|
|
810
|
+
PaymentService.validateTotalPrice({ price, roundingAmount, checkout });
|
|
692
811
|
|
|
693
|
-
const { customer, prefix } = await this.validateCustomer({ user, checkout, payingOrganization
|
|
694
|
-
this.validateVATRates({ customer, organization, balanceItems });
|
|
695
|
-
|
|
696
|
-
const {method, type, mandate} = await this.validatePaymentMethod({
|
|
812
|
+
const { customer, prefix } = await this.validateCustomer({ user, checkout, payingOrganization });
|
|
813
|
+
this.validateVATRates({ customer, sellingOrganization: organization, balanceItems });
|
|
814
|
+
|
|
815
|
+
const { method, type, mandate } = await this.validatePaymentMethod({
|
|
697
816
|
method: checkout.paymentMethod ?? PaymentMethod.Unknown,
|
|
698
817
|
mandate: useMandate,
|
|
699
818
|
createMandate: !!createMandate,
|
|
700
819
|
customer,
|
|
701
820
|
price,
|
|
702
821
|
hasNegative,
|
|
703
|
-
balanceItems,
|
|
822
|
+
balanceItems,
|
|
704
823
|
paymentConfiguration,
|
|
705
824
|
user,
|
|
706
825
|
payingOrganization: payingOrganization ?? null,
|
|
707
|
-
sellingOrganization: organization
|
|
826
|
+
sellingOrganization: organization,
|
|
708
827
|
});
|
|
709
828
|
|
|
710
829
|
// Check URL's set fro online payments
|
|
@@ -724,8 +843,8 @@ export class PaymentService {
|
|
|
724
843
|
throw new SimpleError({
|
|
725
844
|
code: 'cannot_create_mandate_for_provider',
|
|
726
845
|
message: 'Saving a payment method is not yet supported for this payment method',
|
|
727
|
-
human: $t('%1U0')
|
|
728
|
-
})
|
|
846
|
+
human: $t('%1U0'),
|
|
847
|
+
});
|
|
729
848
|
}
|
|
730
849
|
}
|
|
731
850
|
|
|
@@ -746,7 +865,9 @@ export class PaymentService {
|
|
|
746
865
|
payment.roundingAmount = roundingAmount;
|
|
747
866
|
payment.method = method;
|
|
748
867
|
payment.type = type;
|
|
749
|
-
payment.createMandate = createMandate
|
|
868
|
+
payment.createMandate = createMandate;
|
|
869
|
+
payment.mandateId = mandate?.id ?? null;
|
|
870
|
+
payment.adminUserId = adminUserId ?? null;
|
|
750
871
|
|
|
751
872
|
if (price === 0) {
|
|
752
873
|
payment.status = PaymentStatus.Succeeded;
|
|
@@ -755,7 +876,8 @@ export class PaymentService {
|
|
|
755
876
|
|
|
756
877
|
payment.provider = provider;
|
|
757
878
|
payment.stripeAccountId = stripeAccount?.id ?? null;
|
|
758
|
-
ServiceFeeHelper.setServiceFee(payment, organization, serviceFeeType, [...balanceItems.entries()].map(([_, p]) => p));
|
|
879
|
+
await ServiceFeeHelper.setServiceFee(payment, organization, serviceFeeType, [...balanceItems.entries()].map(([_, p]) => p));
|
|
880
|
+
await ServiceFeeHelper.setTransferFee({ payment, organization, stripeAccount });
|
|
759
881
|
|
|
760
882
|
// Add transfer description
|
|
761
883
|
if (payment.method === PaymentMethod.Transfer) {
|
|
@@ -810,13 +932,11 @@ export class PaymentService {
|
|
|
810
932
|
// Send a small reminder email
|
|
811
933
|
try {
|
|
812
934
|
await this.sendTransferEmail(customer, user?.id ?? null, organization, payment);
|
|
813
|
-
}
|
|
814
|
-
catch (e) {
|
|
935
|
+
} catch (e) {
|
|
815
936
|
console.error('Failed to send transfer email');
|
|
816
937
|
console.error(e);
|
|
817
938
|
}
|
|
818
|
-
}
|
|
819
|
-
else if (payment.method !== PaymentMethod.PointOfSale && payment.method !== PaymentMethod.Unknown) {
|
|
939
|
+
} else if (payment.method !== PaymentMethod.PointOfSale && payment.method !== PaymentMethod.Unknown) {
|
|
820
940
|
if ((!checkout.redirectUrl || !checkout.cancelUrl) && !mandate) {
|
|
821
941
|
throw new Error('Should have been caught earlier');
|
|
822
942
|
}
|
|
@@ -825,7 +945,7 @@ export class PaymentService {
|
|
|
825
945
|
_redirectUrl.searchParams.set('paymentId', payment.id);
|
|
826
946
|
_redirectUrl.searchParams.set('organizationId', payment.payingOrganizationId ?? organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
|
|
827
947
|
|
|
828
|
-
const _cancelUrl = new URL(checkout.cancelUrl?? ('https://' + STAMHOOFD.domains.dashboard));
|
|
948
|
+
const _cancelUrl = new URL(checkout.cancelUrl ?? ('https://' + STAMHOOFD.domains.dashboard));
|
|
829
949
|
_cancelUrl.searchParams.set('paymentId', payment.id);
|
|
830
950
|
_cancelUrl.searchParams.set('cancel', 'true');
|
|
831
951
|
_cancelUrl.searchParams.set('organizationId', payment.payingOrganizationId ?? organization.id); // makes sure the client uses the token associated with this organization when fetching payment polling status
|
|
@@ -834,18 +954,18 @@ export class PaymentService {
|
|
|
834
954
|
const cancelUrl = _cancelUrl.href;
|
|
835
955
|
|
|
836
956
|
const webhookUrl = 'https://' + organization.getApiHost() + '/v' + Version + '/payments/' + encodeURIComponent(payment.id) + '?exchange=true';
|
|
837
|
-
|
|
957
|
+
|
|
838
958
|
if (payment.provider === PaymentProvider.Stripe) {
|
|
839
959
|
if (createMandate || mandate) {
|
|
840
960
|
// Already checked, but for security
|
|
841
|
-
throw new Error('Unsupported')
|
|
961
|
+
throw new Error('Unsupported');
|
|
842
962
|
}
|
|
843
963
|
if (!customer.email) {
|
|
844
964
|
throw new SimpleError({
|
|
845
965
|
code: 'missing_email',
|
|
846
966
|
message: 'Email address is required for online payments via Stripe',
|
|
847
|
-
human: $t('%1SJ')
|
|
848
|
-
})
|
|
967
|
+
human: $t('%1SJ'),
|
|
968
|
+
});
|
|
849
969
|
}
|
|
850
970
|
const stripeResult = await StripeHelper.createPayment({
|
|
851
971
|
payment,
|
|
@@ -866,8 +986,7 @@ export class PaymentService {
|
|
|
866
986
|
},
|
|
867
987
|
});
|
|
868
988
|
paymentUrl = stripeResult.paymentUrl;
|
|
869
|
-
}
|
|
870
|
-
else if (payment.provider === PaymentProvider.Mollie) {
|
|
989
|
+
} else if (payment.provider === PaymentProvider.Mollie) {
|
|
871
990
|
const mollieResult = await MollieService.createPayment({
|
|
872
991
|
payment,
|
|
873
992
|
redirectUrl,
|
|
@@ -886,18 +1005,16 @@ export class PaymentService {
|
|
|
886
1005
|
mandate,
|
|
887
1006
|
});
|
|
888
1007
|
paymentUrl = mollieResult.paymentUrl;
|
|
889
|
-
}
|
|
890
|
-
else if (payment.provider === PaymentProvider.Payconiq) {
|
|
1008
|
+
} else if (payment.provider === PaymentProvider.Payconiq) {
|
|
891
1009
|
if (createMandate || mandate) {
|
|
892
1010
|
// Already checked, but for security
|
|
893
|
-
throw new Error('Unsupported')
|
|
1011
|
+
throw new Error('Unsupported');
|
|
894
1012
|
}
|
|
895
1013
|
({ paymentUrl, paymentQRCode } = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, webhookUrl));
|
|
896
|
-
}
|
|
897
|
-
else if (payment.provider === PaymentProvider.Buckaroo) {
|
|
1014
|
+
} else if (payment.provider === PaymentProvider.Buckaroo) {
|
|
898
1015
|
if (createMandate || mandate) {
|
|
899
1016
|
// Already checked, but for security
|
|
900
|
-
throw new Error('Unsupported')
|
|
1017
|
+
throw new Error('Unsupported');
|
|
901
1018
|
}
|
|
902
1019
|
|
|
903
1020
|
// Increase request timeout because buckaroo is super slow (in development)
|
|
@@ -916,20 +1033,18 @@ export class PaymentService {
|
|
|
916
1033
|
}
|
|
917
1034
|
}
|
|
918
1035
|
}
|
|
919
|
-
}
|
|
920
|
-
catch (e) {
|
|
1036
|
+
} catch (e) {
|
|
921
1037
|
await PaymentService.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
922
1038
|
throw e;
|
|
923
1039
|
}
|
|
924
1040
|
|
|
925
1041
|
// TypeScript thinks status cannot change to Failed, but it can.
|
|
926
|
-
if (payment.status === PaymentStatus.Succeeded || (payment.status as PaymentStatus) === PaymentStatus.Failed) {
|
|
1042
|
+
if (payment.status === PaymentStatus.Succeeded || (payment.status as PaymentStatus) === PaymentStatus.Failed) {
|
|
927
1043
|
// force update
|
|
928
|
-
const updateTo = payment.status
|
|
929
|
-
payment.status = PaymentStatus.Created
|
|
1044
|
+
const updateTo = payment.status;
|
|
1045
|
+
payment.status = PaymentStatus.Created;
|
|
930
1046
|
await PaymentService.handlePaymentStatusUpdate(payment, organization, updateTo);
|
|
931
|
-
}
|
|
932
|
-
else if (payment.method === PaymentMethod.Transfer || payment.method === PaymentMethod.PointOfSale || payment.method === PaymentMethod.Unknown || (payment.method === PaymentMethod.DirectDebit && mandate)) {
|
|
1047
|
+
} else if (payment.method === PaymentMethod.Transfer || payment.method === PaymentMethod.PointOfSale || payment.method === PaymentMethod.Unknown || (payment.method === PaymentMethod.DirectDebit && mandate)) {
|
|
933
1048
|
// Mark valid (not same as paid) if needed
|
|
934
1049
|
let hasBundleDiscount = false;
|
|
935
1050
|
for (const [balanceItem] of balanceItems) {
|
|
@@ -959,7 +1074,7 @@ export class PaymentService {
|
|
|
959
1074
|
static async sendTransferEmail(customer: PaymentCustomer, userId: string | null, organization: Organization, payment: Payment) {
|
|
960
1075
|
const email = customer.dynamicEmail;
|
|
961
1076
|
if (!email) {
|
|
962
|
-
console.warn('Skipped sending transfer email because of missing email address', payment.id)
|
|
1077
|
+
console.warn('Skipped sending transfer email because of missing email address', payment.id);
|
|
963
1078
|
return;
|
|
964
1079
|
}
|
|
965
1080
|
const paymentGeneral = await payment.getGeneralStructure();
|
|
@@ -994,37 +1109,110 @@ export class PaymentService {
|
|
|
994
1109
|
});
|
|
995
1110
|
}
|
|
996
1111
|
|
|
997
|
-
static async
|
|
1112
|
+
static async validateMandate({
|
|
1113
|
+
method,
|
|
1114
|
+
paymentConfiguration,
|
|
1115
|
+
mandate,
|
|
1116
|
+
payingOrganization,
|
|
1117
|
+
sellingOrganization,
|
|
1118
|
+
user,
|
|
1119
|
+
}: {
|
|
1120
|
+
method: PaymentMethod;
|
|
1121
|
+
paymentConfiguration: PaymentConfiguration;
|
|
1122
|
+
mandate: PaymentMandate;
|
|
1123
|
+
payingOrganization: Organization | null;
|
|
1124
|
+
sellingOrganization: Organization;
|
|
1125
|
+
user: User | null;
|
|
1126
|
+
}) {
|
|
1127
|
+
if (!paymentConfiguration.enableMandates) {
|
|
1128
|
+
throw new SimpleError({
|
|
1129
|
+
code: 'mandates_disabled',
|
|
1130
|
+
message: 'Cannot pay with mandate',
|
|
1131
|
+
human: $t('%1SK'),
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Validate mandate + update payment method based on the mandate
|
|
1136
|
+
if (method !== PaymentMethod.Unknown) {
|
|
1137
|
+
throw new SimpleError({
|
|
1138
|
+
code: 'invalid_data',
|
|
1139
|
+
message: 'Cannot combine setting mandate with method',
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const allMandates = await PaymentMandateService.getMandates({
|
|
1144
|
+
payingOrganization,
|
|
1145
|
+
sellingOrganization,
|
|
1146
|
+
user,
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
const existingMandate = allMandates.find(m => m.id === mandate.id && m.provider === mandate.provider);
|
|
1150
|
+
if (!existingMandate) {
|
|
1151
|
+
throw new SimpleError({
|
|
1152
|
+
code: 'not_found',
|
|
1153
|
+
message: 'Mandate not found',
|
|
1154
|
+
human: $t('%1TQ'),
|
|
1155
|
+
field: 'mandateId',
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (existingMandate.status !== PaymentMandateStatus.Valid) {
|
|
1160
|
+
throw new SimpleError({
|
|
1161
|
+
code: 'mandate_not_valid',
|
|
1162
|
+
message: 'Mandate not valid',
|
|
1163
|
+
human: $t('%1QA'),
|
|
1164
|
+
field: 'mandateId',
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
return existingMandate;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
static async validatePaymentMethod({
|
|
998
1171
|
method,
|
|
999
1172
|
customer,
|
|
1000
1173
|
price,
|
|
1001
1174
|
hasNegative,
|
|
1002
|
-
balanceItems,
|
|
1175
|
+
balanceItems,
|
|
1003
1176
|
paymentConfiguration,
|
|
1004
1177
|
mandate,
|
|
1005
1178
|
payingOrganization,
|
|
1006
1179
|
sellingOrganization,
|
|
1007
1180
|
user,
|
|
1008
|
-
createMandate
|
|
1009
|
-
}: {
|
|
1010
|
-
method: PaymentMethod
|
|
1011
|
-
customer: PaymentCustomer
|
|
1012
|
-
price: number
|
|
1013
|
-
hasNegative: boolean
|
|
1014
|
-
balanceItems: Map<BalanceItem, number>;
|
|
1015
|
-
paymentConfiguration: PaymentConfiguration
|
|
1016
|
-
mandate: PaymentMandate | null
|
|
1017
|
-
payingOrganization: Organization | null
|
|
1018
|
-
sellingOrganization: Organization
|
|
1019
|
-
user: User | null
|
|
1020
|
-
createMandate: boolean
|
|
1181
|
+
createMandate,
|
|
1182
|
+
}: {
|
|
1183
|
+
method: PaymentMethod;
|
|
1184
|
+
customer: PaymentCustomer;
|
|
1185
|
+
price: number;
|
|
1186
|
+
hasNegative: boolean;
|
|
1187
|
+
balanceItems: Map<BalanceItem, number>;
|
|
1188
|
+
paymentConfiguration: PaymentConfiguration;
|
|
1189
|
+
mandate: PaymentMandate | null;
|
|
1190
|
+
payingOrganization: Organization | null;
|
|
1191
|
+
sellingOrganization: Organization;
|
|
1192
|
+
user: User | null;
|
|
1193
|
+
createMandate: boolean;
|
|
1021
1194
|
}) {
|
|
1022
1195
|
if (price === 0) {
|
|
1023
1196
|
if (balanceItems.size === 0) {
|
|
1024
|
-
throw new Error('Empty payment')
|
|
1197
|
+
throw new Error('Empty payment');
|
|
1025
1198
|
}
|
|
1026
1199
|
|
|
1027
|
-
|
|
1200
|
+
// We still need to validate the mandate, because
|
|
1201
|
+
// we still allow setting a new default payment method
|
|
1202
|
+
// for zero payments
|
|
1203
|
+
// - e.g. activating a new package with an existing mandate
|
|
1204
|
+
const existingMandate = mandate
|
|
1205
|
+
? await this.validateMandate({
|
|
1206
|
+
method,
|
|
1207
|
+
mandate,
|
|
1208
|
+
paymentConfiguration,
|
|
1209
|
+
payingOrganization,
|
|
1210
|
+
sellingOrganization,
|
|
1211
|
+
user,
|
|
1212
|
+
})
|
|
1213
|
+
: null;
|
|
1214
|
+
|
|
1215
|
+
if (createMandate && !existingMandate) {
|
|
1028
1216
|
throw new SimpleError({
|
|
1029
1217
|
code: 'cannot_create_mandate_without_payment',
|
|
1030
1218
|
message: 'Cannot create saved payment method without payment of a small amount',
|
|
@@ -1037,88 +1225,56 @@ export class PaymentService {
|
|
|
1037
1225
|
return {
|
|
1038
1226
|
method: PaymentMethod.Unknown,
|
|
1039
1227
|
type: PaymentType.Reallocation,
|
|
1040
|
-
mandate:
|
|
1041
|
-
}
|
|
1228
|
+
mandate: existingMandate,
|
|
1229
|
+
};
|
|
1042
1230
|
}
|
|
1043
1231
|
|
|
1044
1232
|
return {
|
|
1045
1233
|
// Free purchase
|
|
1046
1234
|
method: PaymentMethod.Unknown,
|
|
1047
1235
|
type: PaymentType.Payment,
|
|
1048
|
-
mandate:
|
|
1049
|
-
}
|
|
1236
|
+
mandate: existingMandate,
|
|
1237
|
+
};
|
|
1050
1238
|
}
|
|
1051
1239
|
|
|
1052
1240
|
if (mandate) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
human: $t('%1SK')
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// Validate mandate + update payment method based on the mandate
|
|
1062
|
-
if (method !== PaymentMethod.Unknown) {
|
|
1063
|
-
throw new SimpleError({
|
|
1064
|
-
code: 'invalid_data',
|
|
1065
|
-
message: 'Cannot combine setting mandate with method'
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const allMandates = await PaymentMandateService.getMandates({
|
|
1241
|
+
const existingMandate = await this.validateMandate({
|
|
1242
|
+
method,
|
|
1243
|
+
mandate,
|
|
1244
|
+
paymentConfiguration,
|
|
1070
1245
|
payingOrganization,
|
|
1071
1246
|
sellingOrganization,
|
|
1072
1247
|
user,
|
|
1073
1248
|
});
|
|
1074
1249
|
|
|
1075
|
-
const existingMandate = allMandates.find(m => m.id === mandate.id && m.provider === mandate.provider);
|
|
1076
|
-
if (!existingMandate) {
|
|
1077
|
-
throw new SimpleError({
|
|
1078
|
-
code: 'not_found',
|
|
1079
|
-
message: 'Mandate not found',
|
|
1080
|
-
human: $t('%1TQ'),
|
|
1081
|
-
field: 'mandateId'
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
if (existingMandate.status !== PaymentMandateStatus.Valid) {
|
|
1086
|
-
throw new SimpleError({
|
|
1087
|
-
code: 'mandate_not_valid',
|
|
1088
|
-
message: 'Mandate not valid',
|
|
1089
|
-
human: $t('%1QA'),
|
|
1090
|
-
field: 'mandateId'
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
1250
|
switch (existingMandate.type) {
|
|
1095
1251
|
case PaymentMandateType.CreditCard:
|
|
1096
1252
|
return {
|
|
1097
1253
|
mandate: existingMandate,
|
|
1098
1254
|
method: PaymentMethod.CreditCard,
|
|
1099
|
-
type: PaymentType.Payment
|
|
1100
|
-
}
|
|
1255
|
+
type: PaymentType.Payment,
|
|
1256
|
+
};
|
|
1101
1257
|
case PaymentMandateType.DirectDebit:
|
|
1102
1258
|
return {
|
|
1103
1259
|
mandate: existingMandate,
|
|
1104
1260
|
method: PaymentMethod.DirectDebit,
|
|
1105
|
-
type: PaymentType.Payment
|
|
1106
|
-
}
|
|
1261
|
+
type: PaymentType.Payment,
|
|
1262
|
+
};
|
|
1107
1263
|
}
|
|
1108
1264
|
|
|
1109
|
-
throw new Error('Unsupported mandate type')
|
|
1265
|
+
throw new Error('Unsupported mandate type');
|
|
1110
1266
|
}
|
|
1111
1267
|
|
|
1112
1268
|
if (createMandate) {
|
|
1113
1269
|
if (!paymentConfiguration.enableMandates) {
|
|
1114
|
-
|
|
1270
|
+
throw new SimpleError({
|
|
1115
1271
|
code: 'mandates_disabled',
|
|
1116
1272
|
message: 'Cannot pay with mandate',
|
|
1117
|
-
human: $t('%1R1')
|
|
1273
|
+
human: $t('%1R1'),
|
|
1118
1274
|
});
|
|
1119
1275
|
}
|
|
1120
1276
|
}
|
|
1121
|
-
|
|
1277
|
+
|
|
1122
1278
|
if (method === PaymentMethod.Unknown) {
|
|
1123
1279
|
throw new SimpleError({
|
|
1124
1280
|
code: 'invalid_data',
|
|
@@ -1130,7 +1286,7 @@ export class PaymentService {
|
|
|
1130
1286
|
const allowedPaymentMethods = paymentConfiguration.getAvailablePaymentMethods({
|
|
1131
1287
|
amount: price,
|
|
1132
1288
|
customer: customer,
|
|
1133
|
-
forMandate: createMandate
|
|
1289
|
+
forMandate: createMandate,
|
|
1134
1290
|
});
|
|
1135
1291
|
|
|
1136
1292
|
if (!allowedPaymentMethods.includes(method)) {
|
|
@@ -1143,41 +1299,64 @@ export class PaymentService {
|
|
|
1143
1299
|
return {
|
|
1144
1300
|
method,
|
|
1145
1301
|
type: PaymentType.Payment,
|
|
1146
|
-
mandate: null
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
static getDefaultCompanyForOrganization(sellingOrganization: Organization) {
|
|
1151
|
-
return sellingOrganization.meta.companies[0];
|
|
1302
|
+
mandate: null,
|
|
1303
|
+
};
|
|
1152
1304
|
}
|
|
1153
1305
|
|
|
1154
|
-
static
|
|
1306
|
+
static validateVATRates({ customer, sellingOrganization, balanceItems }: { customer: PaymentCustomer; sellingOrganization: Organization; balanceItems: Map<BalanceItem, number> }) {
|
|
1155
1307
|
// Validate VAT rates for this customer
|
|
1156
|
-
const seller =
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1308
|
+
const seller = VATService.getDefaultCompanyForOrganization(sellingOrganization);
|
|
1309
|
+
const excempt = VATService.isVATExcempt({
|
|
1310
|
+
company: customer.company,
|
|
1311
|
+
sellingOrganization,
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
// Reverse charged vat applicable?
|
|
1315
|
+
if (excempt) {
|
|
1316
|
+
// Check VAT Exempt is set on each an every balance item with a non-zero price
|
|
1317
|
+
for (const [item] of balanceItems) {
|
|
1318
|
+
if (item.VATExcempt !== VATExcemptReason.IntraCommunityServices && item.VATExcempt !== VATExcemptReason.IntraCommunityGoods) {
|
|
1319
|
+
throw new SimpleError({
|
|
1320
|
+
code: 'VAT_error',
|
|
1321
|
+
message: 'Intra community VAT reverse charge not supported for this purchase',
|
|
1322
|
+
human: $t('%1LI'),
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// We also need to know the VAT rate exactly to be sure the VAT is removed from the purchase
|
|
1327
|
+
// If VAT is not included, we don't need to know the VAT percentage until the payment is invoiced
|
|
1328
|
+
if (item.VATPercentage === null && item.VATIncluded) {
|
|
1329
|
+
throw new SimpleError({
|
|
1330
|
+
code: 'VAT_error',
|
|
1331
|
+
message: 'Intra community VAT reverse charge is not supported for this purchase because of missing VAT rates',
|
|
1332
|
+
human: $t('%1LJ'),
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1166
1335
|
}
|
|
1336
|
+
} else {
|
|
1337
|
+
// Fine to just not have setup VAT rates yet if the price is guaranteed to include VAT
|
|
1338
|
+
for (const [item] of balanceItems) {
|
|
1339
|
+
if (item.VATExcempt === VATExcemptReason.IntraCommunityGoods || item.VATExcempt === VATExcemptReason.IntraCommunityServices) {
|
|
1340
|
+
throw new SimpleError({
|
|
1341
|
+
code: 'VAT_error',
|
|
1342
|
+
message: 'Unexpected reverse charge applied',
|
|
1343
|
+
human: $t('%1LK'),
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1167
1346
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1347
|
+
if (seller && seller.VATNumber) {
|
|
1348
|
+
if (!item.VATIncluded && item.VATPercentage === null) {
|
|
1349
|
+
throw new SimpleError({
|
|
1350
|
+
code: 'VAT_error',
|
|
1351
|
+
message: 'Missing VAT percentage',
|
|
1352
|
+
human: $t('%1LL'),
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1171
1356
|
}
|
|
1172
1357
|
}
|
|
1173
1358
|
|
|
1174
|
-
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
static validateVATRates({ customer, organization, balanceItems }: { customer: PaymentCustomer; organization: Organization; balanceItems: Map<BalanceItem, number> }) {
|
|
1178
|
-
// Validate VAT rates for this customer
|
|
1179
|
-
const seller = this.getDefaultCompanyForOrganization(organization)
|
|
1180
|
-
if (seller && seller.VATNumber && seller.address && customer.company) {
|
|
1359
|
+
/* if (seller && seller.VATNumber && seller.address && customer.company) {
|
|
1181
1360
|
// B2B validation
|
|
1182
1361
|
if (!customer.company.address) {
|
|
1183
1362
|
throw new SimpleError({
|
|
@@ -1188,56 +1367,13 @@ export class PaymentService {
|
|
|
1188
1367
|
});
|
|
1189
1368
|
}
|
|
1190
1369
|
|
|
1191
|
-
// Reverse charged vat applicable?
|
|
1192
|
-
if (customer.company.address.country !== seller.address.country && customer.company.VATNumber) {
|
|
1193
|
-
// Check VAT Exempt is set on each an every balance item with a non-zero price
|
|
1194
|
-
for (const [item] of balanceItems) {
|
|
1195
|
-
if (item.VATExcempt !== VATExcemptReason.IntraCommunity) {
|
|
1196
|
-
throw new SimpleError({
|
|
1197
|
-
code: 'VAT_error',
|
|
1198
|
-
message: 'Intra community VAT reverse charge not supported for this purchase',
|
|
1199
|
-
human: $t('%1LI'),
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// We also need to know the VAT rate exactly to be sure the VAT is removed from the purchase
|
|
1204
|
-
// If VAT is not included, we don't need to know the VAT percentage until the payment is invoiced
|
|
1205
|
-
if (item.VATPercentage === null && item.VATIncluded) {
|
|
1206
|
-
throw new SimpleError({
|
|
1207
|
-
code: 'VAT_error',
|
|
1208
|
-
message: 'Intra community VAT reverse charge is not supported for this purchase because of missing VAT rates',
|
|
1209
|
-
human: $t('%1LJ'),
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
else {
|
|
1215
|
-
// Fine to just not have setup VAT rates yet if the price is guaranteed to include VAT
|
|
1216
|
-
for (const [item] of balanceItems) {
|
|
1217
|
-
if (item.VATExcempt === VATExcemptReason.IntraCommunity) {
|
|
1218
|
-
throw new SimpleError({
|
|
1219
|
-
code: 'VAT_error',
|
|
1220
|
-
message: 'Unexpected reverse charge applied',
|
|
1221
|
-
human: $t('%1LK'),
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
if (!item.VATIncluded && item.VATPercentage === null) {
|
|
1226
|
-
throw new SimpleError({
|
|
1227
|
-
code: 'VAT_error',
|
|
1228
|
-
message: 'Missing VAT percentage',
|
|
1229
|
-
human: $t('%1LL'),
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
1370
|
}
|
|
1235
1371
|
else {
|
|
1236
1372
|
// B2C / C2B / C2C
|
|
1237
1373
|
|
|
1238
1374
|
// You cannot buy balance items with VAT if you didn't set up a VAT number.
|
|
1239
1375
|
for (const [item] of balanceItems) {
|
|
1240
|
-
if (item.VATExcempt === VATExcemptReason.
|
|
1376
|
+
if (item.VATExcempt === VATExcemptReason.IntraCommunityGoods || item.VATExcempt === VATExcemptReason.IntraCommunityServices) {
|
|
1241
1377
|
throw new SimpleError({
|
|
1242
1378
|
code: 'VAT_error',
|
|
1243
1379
|
message: 'Unexpected reverse charge applied',
|
|
@@ -1255,8 +1391,8 @@ export class PaymentService {
|
|
|
1255
1391
|
}
|
|
1256
1392
|
}
|
|
1257
1393
|
}
|
|
1258
|
-
}
|
|
1394
|
+
} */
|
|
1259
1395
|
|
|
1260
|
-
return {seller}
|
|
1396
|
+
return { seller };
|
|
1261
1397
|
}
|
|
1262
1398
|
};
|