@stamhoofd/backend 1.0.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/.env.template.json +63 -0
- package/.eslintrc.js +61 -0
- package/README.md +40 -0
- package/index.ts +172 -0
- package/jest.config.js +11 -0
- package/migrations.ts +33 -0
- package/package.json +48 -0
- package/src/crons.ts +845 -0
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
- package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
- package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
- package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
- package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
- package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
- package/src/endpoints/auth/SignupEndpoint.ts +107 -0
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
- package/src/endpoints/global/files/UploadFile.ts +147 -0
- package/src/endpoints/global/files/UploadImage.ts +119 -0
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
- package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
- package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
- package/src/helpers/AddressValidator.test.ts +40 -0
- package/src/helpers/AddressValidator.ts +256 -0
- package/src/helpers/AdminPermissionChecker.ts +1031 -0
- package/src/helpers/AuthenticatedStructures.ts +158 -0
- package/src/helpers/BuckarooHelper.ts +279 -0
- package/src/helpers/CheckSettlements.ts +215 -0
- package/src/helpers/Context.ts +202 -0
- package/src/helpers/CookieHelper.ts +45 -0
- package/src/helpers/ForwardHandler.test.ts +216 -0
- package/src/helpers/ForwardHandler.ts +140 -0
- package/src/helpers/OpenIDConnectHelper.ts +284 -0
- package/src/helpers/StripeHelper.ts +293 -0
- package/src/helpers/StripePayoutChecker.ts +188 -0
- package/src/middleware/ContextMiddleware.ts +16 -0
- package/src/migrations/1646578856-validate-addresses.ts +60 -0
- package/src/seeds/0000000000-example.ts +13 -0
- package/src/seeds/1715028563-user-permissions.ts +52 -0
- package/tests/e2e/stock.test.ts +2120 -0
- package/tests/e2e/tickets.test.ts +926 -0
- package/tests/helpers/StripeMocker.ts +362 -0
- package/tests/helpers/TestServer.ts +21 -0
- package/tests/jest.global.setup.ts +29 -0
- package/tests/jest.setup.ts +59 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Document, Member } from '@stamhoofd/models';
|
|
5
|
+
import { MemberWithRegistrationsBlob, MembersBlob } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from '../../../helpers/Context';
|
|
8
|
+
import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
|
|
9
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>
|
|
13
|
+
type ResponseBody = MembersBlob
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Allow to add, patch and delete multiple members simultaneously, which is needed in order to sync relational data that is saved encrypted in multiple members (e.g. parents)
|
|
17
|
+
*/
|
|
18
|
+
export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
19
|
+
bodyDecoder = new PatchableArrayDecoder(MemberWithRegistrationsBlob as Decoder<MemberWithRegistrationsBlob>, MemberWithRegistrationsBlob.patchType() as Decoder<AutoEncoderPatchType<MemberWithRegistrationsBlob>>, StringDecoder)
|
|
20
|
+
|
|
21
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
22
|
+
if (request.method != "PATCH") {
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const params = Endpoint.parseParameters(request.url, "/members", {});
|
|
27
|
+
|
|
28
|
+
if (params) {
|
|
29
|
+
return [true, params as Params];
|
|
30
|
+
}
|
|
31
|
+
return [false];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
35
|
+
const organization = await Context.setUserOrganizationScope();
|
|
36
|
+
const {user} = await Context.authenticate()
|
|
37
|
+
|
|
38
|
+
// Process changes
|
|
39
|
+
const addedMembers: Member[] = []
|
|
40
|
+
for (const put of request.body.getPuts()) {
|
|
41
|
+
const struct = put.put
|
|
42
|
+
|
|
43
|
+
const member = new Member()
|
|
44
|
+
member.id = struct.id
|
|
45
|
+
member.organizationId = organization?.id ?? null
|
|
46
|
+
|
|
47
|
+
struct.details.cleanData()
|
|
48
|
+
member.details = struct.details
|
|
49
|
+
|
|
50
|
+
if (!struct.details) {
|
|
51
|
+
throw new SimpleError({
|
|
52
|
+
code: "invalid_data",
|
|
53
|
+
message: "No details provided",
|
|
54
|
+
human: "Opgelet! Je gebruikt een oudere versie van de inschrijvingspagina die niet langer wordt ondersteund. Herlaad de website grondig en wis je browser cache.",
|
|
55
|
+
field: "details"
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for duplicates and prevent creating a duplicate member by a user
|
|
60
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member);
|
|
61
|
+
if (duplicate) {
|
|
62
|
+
// Merge data
|
|
63
|
+
duplicate.details.merge(member.details)
|
|
64
|
+
await duplicate.save()
|
|
65
|
+
addedMembers.push(duplicate)
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await member.save()
|
|
70
|
+
addedMembers.push(member)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (addedMembers.length > 0) {
|
|
74
|
+
// Give access to created members
|
|
75
|
+
await Member.users.reverse("members").link(user, addedMembers)
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Modify members
|
|
80
|
+
const members = await Member.getMembersWithRegistrationForUser(user)
|
|
81
|
+
|
|
82
|
+
for (const member of addedMembers) {
|
|
83
|
+
const updatedMember = members.find(m => m.id === member.id);
|
|
84
|
+
if (updatedMember) {
|
|
85
|
+
// Make sure we also give access to other parents
|
|
86
|
+
await PatchOrganizationMembersEndpoint.updateManagers(updatedMember)
|
|
87
|
+
await Document.updateForMember(updatedMember.id)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (let struct of request.body.getPatches()) {
|
|
92
|
+
const member = members.find((m) => m.id == struct.id)
|
|
93
|
+
if (!member) {
|
|
94
|
+
throw new SimpleError({
|
|
95
|
+
code: "invalid_member",
|
|
96
|
+
message: "This member does not exist or you don't have permissions to modify this member",
|
|
97
|
+
human: "Je probeert een lid aan te passen die niet (meer) bestaat. Er ging ergens iets mis."
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
struct = await Context.auth.filterMemberPatch(member, struct)
|
|
101
|
+
|
|
102
|
+
if (struct.details) {
|
|
103
|
+
if (struct.details.isPut()) {
|
|
104
|
+
throw new SimpleError({
|
|
105
|
+
code: "not_allowed",
|
|
106
|
+
message: "Cannot override details",
|
|
107
|
+
human: "Er ging iets mis bij het aanpassen van de gegevens van dit lid. Probeer het later opnieuw en neem contact op als het probleem zich blijft voordoen.",
|
|
108
|
+
field: "details"
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
member.details.patchOrPut(struct.details)
|
|
112
|
+
member.details.cleanData()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!member.details) {
|
|
116
|
+
throw new SimpleError({
|
|
117
|
+
code: "invalid_data",
|
|
118
|
+
message: "No details provided",
|
|
119
|
+
human: "Opgelet! Je gebruikt een oudere versie van de inschrijvingspagina die niet langer wordt ondersteund. Herlaad de website grondig en wis je browser cache.",
|
|
120
|
+
field: "details"
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
await member.save();
|
|
124
|
+
await PatchOrganizationMembersEndpoint.updateManagers(member)
|
|
125
|
+
|
|
126
|
+
// Update documents
|
|
127
|
+
await Document.updateForMember(member.id)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new Response(
|
|
131
|
+
await AuthenticatedStructures.membersBlob(members)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { createMollieClient, PaymentMethod as molliePaymentMethod } from '@mollie/api-client';
|
|
2
|
+
import { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
3
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
4
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
5
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
+
import { I18n } from '@stamhoofd/backend-i18n';
|
|
7
|
+
import { Email } from '@stamhoofd/email';
|
|
8
|
+
import { BalanceItem, BalanceItemPayment, Group, Member, MolliePayment, MollieToken, PayconiqPayment, Payment, Platform, RateLimiter, Registration } from '@stamhoofd/models';
|
|
9
|
+
import { BalanceItemStatus, IDRegisterCheckout, MemberBalanceItem, MemberDetails, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PlatformFamily, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
|
|
10
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
11
|
+
|
|
12
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
13
|
+
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
14
|
+
import { Context } from '../../../helpers/Context';
|
|
15
|
+
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
16
|
+
import { ExchangePaymentEndpoint } from '../../organization/shared/ExchangePaymentEndpoint';
|
|
17
|
+
type Params = Record<string, never>;
|
|
18
|
+
type Query = undefined;
|
|
19
|
+
type Body = IDRegisterCheckout
|
|
20
|
+
type ResponseBody = RegisterResponse
|
|
21
|
+
|
|
22
|
+
export const demoLimiter = new RateLimiter({
|
|
23
|
+
limits: [
|
|
24
|
+
{
|
|
25
|
+
// Max 10 per hour
|
|
26
|
+
limit: 10,
|
|
27
|
+
duration: 60 * 1000 * 60
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
// Max 20 a day
|
|
31
|
+
limit: 20,
|
|
32
|
+
duration: 24 * 60 * 1000 * 60
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type RegistrationWithMemberAndGroup = Registration & { member: Member } & { group: Group }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Allow to add, patch and delete multiple members simultaneously, which is needed in order to sync relational data that is saved encrypted in multiple members (e.g. parents)
|
|
41
|
+
*/
|
|
42
|
+
export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
43
|
+
bodyDecoder = IDRegisterCheckout as Decoder<IDRegisterCheckout>
|
|
44
|
+
|
|
45
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
46
|
+
if (request.method != "POST") {
|
|
47
|
+
return [false];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const params = Endpoint.parseParameters(request.url, "/members/register", {});
|
|
51
|
+
|
|
52
|
+
if (params) {
|
|
53
|
+
if (request.getVersion() < 257) {
|
|
54
|
+
throw new SimpleError({
|
|
55
|
+
code: "not_supported",
|
|
56
|
+
message: "This version is no longer supported",
|
|
57
|
+
human: "Oops! Je gebruikt een oude versie van de applicatie om in te schrijven. Herlaad de website en verwijder indien nodig de cache van jouw browser."
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
return [true, params as Params];
|
|
61
|
+
}
|
|
62
|
+
return [false];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
66
|
+
const organization = await Context.setOrganizationScope();
|
|
67
|
+
const {user} = await Context.authenticate()
|
|
68
|
+
|
|
69
|
+
// For non paid organizations, limit amount of tests
|
|
70
|
+
if (!organization.meta.packages.isPaid) {
|
|
71
|
+
const limiter = demoLimiter
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
limiter.track(organization.id, 1);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
Email.sendInternal({
|
|
77
|
+
to: "hallo@stamhoofd.be",
|
|
78
|
+
subject: "[Limiet] Limiet bereikt voor aantal inschrijvingen",
|
|
79
|
+
text: "Beste, \nDe limiet werd bereikt voor het aantal inschrijvingen per dag. \nVereniging: "+organization.id+" ("+organization.name+")" + "\n\n" + e.message + "\n\nStamhoofd"
|
|
80
|
+
}, new I18n("nl", "BE"))
|
|
81
|
+
|
|
82
|
+
throw new SimpleError({
|
|
83
|
+
code: "too_many_emails_period",
|
|
84
|
+
message: "Too many e-mails limited",
|
|
85
|
+
human: "Oeps! Om spam te voorkomen limiteren we het aantal test inschrijvingen die je per uur of dag kan plaatsen. Probeer over een uur opnieuw of schakel over naar een betaald abonnement.",
|
|
86
|
+
field: "recipients"
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const members = await Member.getMembersWithRegistrationForUser(user)
|
|
92
|
+
const groups = await Group.getAll(organization.id, organization.periodId)
|
|
93
|
+
|
|
94
|
+
const blob = await AuthenticatedStructures.membersBlob(members, true)
|
|
95
|
+
const family = PlatformFamily.create(blob, {platform: await Platform.getSharedStruct(), contextOrganization: await organization.getStructure()})
|
|
96
|
+
const checkout = request.body.hydrate({family})
|
|
97
|
+
|
|
98
|
+
const registrations: RegistrationWithMemberAndGroup[] = []
|
|
99
|
+
const payRegistrations: {registration: RegistrationWithMemberAndGroup, item: RegisterItem}[] = []
|
|
100
|
+
|
|
101
|
+
if (checkout.cart.isEmpty) {
|
|
102
|
+
throw new SimpleError({
|
|
103
|
+
code: "empty_data",
|
|
104
|
+
message: "Oeps, jouw mandje is leeg. Voeg eerst inschrijvingen toe voor je verder gaat."
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Update occupancies
|
|
109
|
+
// TODO: might not be needed in the future (for performance)
|
|
110
|
+
for (const group of groups) {
|
|
111
|
+
if (request.body.cart.items.find(i => i.groupId == group.id)) {
|
|
112
|
+
await group.updateOccupancy()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Validate balance items (can only happen serverside)
|
|
117
|
+
const balanceItemIds = request.body.cart.balanceItems.map(i => i.item.id)
|
|
118
|
+
let memberBalanceItems: MemberBalanceItem[] = []
|
|
119
|
+
let balanceItems: BalanceItem[] = []
|
|
120
|
+
if (balanceItemIds.length > 0) {
|
|
121
|
+
balanceItems = await BalanceItem.where({ id: { sign:'IN', value: balanceItemIds }, organizationId: organization.id })
|
|
122
|
+
if (balanceItems.length != balanceItemIds.length) {
|
|
123
|
+
throw new SimpleError({
|
|
124
|
+
code: "invalid_data",
|
|
125
|
+
message: "Oeps, één of meerdere openstaande bedragen in jouw winkelmandje zijn aangepast. Herlaad de pagina en probeer opnieuw."
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
memberBalanceItems = await BalanceItem.getMemberStructure(balanceItems)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Validate the cart
|
|
132
|
+
checkout.validate({memberBalanceItems})
|
|
133
|
+
|
|
134
|
+
// Recalculate the price
|
|
135
|
+
checkout.updatePrices()
|
|
136
|
+
|
|
137
|
+
const totalPrice = checkout.totalPrice
|
|
138
|
+
|
|
139
|
+
if (totalPrice < 0) {
|
|
140
|
+
throw new SimpleError({
|
|
141
|
+
code: "empty_data",
|
|
142
|
+
message: "Oeps! De totaalprijs is negatief."
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const registrationMemberRelation = new ManyToOneRelation(Member, "member")
|
|
147
|
+
registrationMemberRelation.foreignKey = "memberId"
|
|
148
|
+
|
|
149
|
+
mainLoop: for (const item of checkout.cart.items) {
|
|
150
|
+
const member = members.find(m => m.id == item.memberId)
|
|
151
|
+
if (!member) {
|
|
152
|
+
throw new SimpleError({
|
|
153
|
+
code: "invalid_member",
|
|
154
|
+
message: "Het lid dat je probeert in te schrijven konden we niet meer terugvinden. Je herlaadt best even de pagina om opnieuw te proberen."
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const group = groups.find(g => g.id == item.groupId);
|
|
159
|
+
if (!group) {
|
|
160
|
+
throw new SimpleError({
|
|
161
|
+
code: "invalid_member",
|
|
162
|
+
message: "De groep waarin je een lid probeert in te schrijven lijkt niet meer te bestaan. Je herlaadt best even de pagina om opnieuw te proberen."
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check if this member is already registered in this group?
|
|
167
|
+
const existingRegistrations = await Registration.where({ memberId: member.id, groupId: item.groupId, cycle: group.cycle })
|
|
168
|
+
let registration: RegistrationWithMemberAndGroup | undefined = undefined;
|
|
169
|
+
|
|
170
|
+
for (const existingRegistration of existingRegistrations) {
|
|
171
|
+
registration = existingRegistration
|
|
172
|
+
.setRelation(registrationMemberRelation, member as Member)
|
|
173
|
+
.setRelation(Registration.group, group)
|
|
174
|
+
|
|
175
|
+
if (existingRegistration.waitingList && item.waitingList) {
|
|
176
|
+
// already on waiting list, no need to repeat it
|
|
177
|
+
// skip without error
|
|
178
|
+
registrations.push(registration)
|
|
179
|
+
continue mainLoop;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!existingRegistration.waitingList && existingRegistration.registeredAt !== null) {
|
|
183
|
+
// already registered, no need to put it on the waiting list or register (and pay) again
|
|
184
|
+
registrations.push(registration)
|
|
185
|
+
continue mainLoop;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!registration) {
|
|
190
|
+
registration = new Registration()
|
|
191
|
+
.setRelation(registrationMemberRelation, member as Member)
|
|
192
|
+
.setRelation(Registration.group, group)
|
|
193
|
+
registration.organizationId = organization.id
|
|
194
|
+
registration.periodId = group.periodId
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
registration.memberId = member.id
|
|
198
|
+
registration.groupId = group.id
|
|
199
|
+
registration.cycle = group.cycle
|
|
200
|
+
|
|
201
|
+
if (item.waitingList) {
|
|
202
|
+
registration.waitingList = true
|
|
203
|
+
registration.reservedUntil = null
|
|
204
|
+
await registration.save()
|
|
205
|
+
} else {
|
|
206
|
+
registration.waitingList = false
|
|
207
|
+
registration.canRegister = false
|
|
208
|
+
registration.price = item.calculatedPrice
|
|
209
|
+
payRegistrations.push({
|
|
210
|
+
registration,
|
|
211
|
+
item
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
registrations.push(registration)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Validate payment method
|
|
218
|
+
if (totalPrice > 0) {
|
|
219
|
+
const allowedPaymentMethods = organization.meta.registrationPaymentConfiguration.paymentMethods
|
|
220
|
+
|
|
221
|
+
if (!checkout.paymentMethod || !allowedPaymentMethods.includes(checkout.paymentMethod)) {
|
|
222
|
+
throw new SimpleError({
|
|
223
|
+
code: "invalid_payment_method",
|
|
224
|
+
message: "Oeps, je hebt geen geldige betaalmethode geselecteerd. Selecteer een betaalmethode en probeer opnieuw. Herlaad de pagina indien nodig."
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
checkout.paymentMethod = PaymentMethod.Unknown
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const payment = new Payment()
|
|
232
|
+
payment.userId = user.id
|
|
233
|
+
payment.organizationId = organization.id
|
|
234
|
+
payment.method = checkout.paymentMethod
|
|
235
|
+
payment.status = PaymentStatus.Created
|
|
236
|
+
payment.price = totalPrice
|
|
237
|
+
payment.freeContribution = checkout.freeContribution
|
|
238
|
+
|
|
239
|
+
if (payment.method == PaymentMethod.Transfer) {
|
|
240
|
+
// remark: we cannot add the lastnames, these will get added in the frontend when it is decrypted
|
|
241
|
+
payment.transferSettings = organization.mappedTransferSettings
|
|
242
|
+
|
|
243
|
+
if (!payment.transferSettings.iban) {
|
|
244
|
+
throw new SimpleError({
|
|
245
|
+
code: "no_iban",
|
|
246
|
+
message: "No IBAN",
|
|
247
|
+
human: "Er is geen rekeningnummer ingesteld voor overschrijvingen. Contacteer de beheerder."
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const m = [...payRegistrations.map(r => r.registration.member.details), ...memberBalanceItems.map(i => members.find(m => m.id === i.memberId)?.details).filter(n => n !== undefined)] as MemberDetails[]
|
|
252
|
+
payment.generateDescription(
|
|
253
|
+
organization,
|
|
254
|
+
Formatter.groupNamesByFamily(m),
|
|
255
|
+
{
|
|
256
|
+
name: Formatter.groupNamesByFamily(m),
|
|
257
|
+
naam: Formatter.groupNamesByFamily(m),
|
|
258
|
+
email: user.email
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
payment.paidAt = null
|
|
263
|
+
|
|
264
|
+
if (totalPrice == 0) {
|
|
265
|
+
payment.status = PaymentStatus.Succeeded
|
|
266
|
+
payment.method = PaymentMethod.Unknown
|
|
267
|
+
payment.paidAt = new Date()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Determine the payment provider
|
|
271
|
+
// Throws if invalid
|
|
272
|
+
const {provider, stripeAccount} = await organization.getPaymentProviderFor(payment.method, organization.privateMeta.registrationPaymentConfiguration)
|
|
273
|
+
payment.provider = provider
|
|
274
|
+
payment.stripeAccountId = stripeAccount?.id ?? null
|
|
275
|
+
|
|
276
|
+
await payment.save()
|
|
277
|
+
const items: BalanceItem[] = []
|
|
278
|
+
const itemPayments: (BalanceItemPayment & { balanceItem: BalanceItem })[] = []
|
|
279
|
+
|
|
280
|
+
// Save registrations and add extra data if needed
|
|
281
|
+
for (const bundle of payRegistrations) {
|
|
282
|
+
const registration = bundle.registration;
|
|
283
|
+
|
|
284
|
+
if (!registration.waitingList) {
|
|
285
|
+
// Replaced with balance items
|
|
286
|
+
// registration.paymentId = payment.id
|
|
287
|
+
|
|
288
|
+
registration.reservedUntil = null
|
|
289
|
+
|
|
290
|
+
if (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale || payment.status == PaymentStatus.Succeeded) {
|
|
291
|
+
await registration.markValid()
|
|
292
|
+
} else {
|
|
293
|
+
// Reserve registration for 30 minutes (if needed)
|
|
294
|
+
const group = groups.find(g => g.id === registration.groupId)
|
|
295
|
+
|
|
296
|
+
if (group && group.settings.maxMembers !== null) {
|
|
297
|
+
registration.reservedUntil = new Date(new Date().getTime() + 1000*60*30)
|
|
298
|
+
}
|
|
299
|
+
await registration.save()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
await registration.save()
|
|
304
|
+
|
|
305
|
+
// Create balance item
|
|
306
|
+
const balanceItem = new BalanceItem();
|
|
307
|
+
balanceItem.registrationId = registration.id;
|
|
308
|
+
balanceItem.price = bundle.item.calculatedPrice
|
|
309
|
+
balanceItem.description = `Inschrijving ${registration.group.settings.name}`
|
|
310
|
+
balanceItem.pricePaid = payment.status == PaymentStatus.Succeeded ? bundle.item.calculatedPrice : 0;
|
|
311
|
+
balanceItem.memberId = registration.memberId;
|
|
312
|
+
balanceItem.userId = user.id
|
|
313
|
+
balanceItem.organizationId = organization.id;
|
|
314
|
+
balanceItem.status = payment.status == PaymentStatus.Succeeded ? BalanceItemStatus.Paid : (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden);
|
|
315
|
+
await balanceItem.save();
|
|
316
|
+
|
|
317
|
+
// Create one balance item payment to pay it in one payment
|
|
318
|
+
const balanceItemPayment = new BalanceItemPayment()
|
|
319
|
+
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
320
|
+
balanceItemPayment.paymentId = payment.id;
|
|
321
|
+
balanceItemPayment.organizationId = organization.id;
|
|
322
|
+
balanceItemPayment.price = balanceItem.price;
|
|
323
|
+
await balanceItemPayment.save();
|
|
324
|
+
|
|
325
|
+
items.push(balanceItem)
|
|
326
|
+
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const oldestMember = members.slice().sort((a, b) => b.details.defaultAge - a.details.defaultAge)[0]
|
|
330
|
+
if (checkout.freeContribution) {
|
|
331
|
+
// Create balance item
|
|
332
|
+
const balanceItem = new BalanceItem();
|
|
333
|
+
balanceItem.price = checkout.freeContribution
|
|
334
|
+
balanceItem.description = `Vrije bijdrage`
|
|
335
|
+
balanceItem.pricePaid = payment.status == PaymentStatus.Succeeded ? balanceItem.price : 0;
|
|
336
|
+
balanceItem.userId = user.id
|
|
337
|
+
balanceItem.organizationId = organization.id;
|
|
338
|
+
|
|
339
|
+
// Connect this to the oldest member
|
|
340
|
+
|
|
341
|
+
if (oldestMember) {
|
|
342
|
+
balanceItem.memberId = oldestMember.id;
|
|
343
|
+
}
|
|
344
|
+
balanceItem.status = payment.status == PaymentStatus.Succeeded ? BalanceItemStatus.Paid : (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden);
|
|
345
|
+
await balanceItem.save();
|
|
346
|
+
|
|
347
|
+
// Create one balance item payment to pay it in one payment
|
|
348
|
+
const balanceItemPayment = new BalanceItemPayment()
|
|
349
|
+
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
350
|
+
balanceItemPayment.paymentId = payment.id;
|
|
351
|
+
balanceItemPayment.organizationId = organization.id;
|
|
352
|
+
balanceItemPayment.price = balanceItem.price;
|
|
353
|
+
await balanceItemPayment.save();
|
|
354
|
+
|
|
355
|
+
items.push(balanceItem)
|
|
356
|
+
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (checkout.administrationFee) {
|
|
360
|
+
// Create balance item
|
|
361
|
+
const balanceItem = new BalanceItem();
|
|
362
|
+
balanceItem.price = checkout.administrationFee
|
|
363
|
+
balanceItem.description = `Administratiekosten`
|
|
364
|
+
balanceItem.pricePaid = payment.status == PaymentStatus.Succeeded ? balanceItem.price : 0;
|
|
365
|
+
balanceItem.userId = user.id
|
|
366
|
+
balanceItem.organizationId = organization.id;
|
|
367
|
+
|
|
368
|
+
// Connect this to the oldest member
|
|
369
|
+
|
|
370
|
+
if (oldestMember) {
|
|
371
|
+
balanceItem.memberId = oldestMember.id;
|
|
372
|
+
}
|
|
373
|
+
balanceItem.status = payment.status == PaymentStatus.Succeeded ? BalanceItemStatus.Paid : (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden);
|
|
374
|
+
await balanceItem.save();
|
|
375
|
+
|
|
376
|
+
// Create one balance item payment to pay it in one payment
|
|
377
|
+
const balanceItemPayment = new BalanceItemPayment()
|
|
378
|
+
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
379
|
+
balanceItemPayment.paymentId = payment.id;
|
|
380
|
+
balanceItemPayment.organizationId = organization.id;
|
|
381
|
+
balanceItemPayment.price = balanceItem.price;
|
|
382
|
+
await balanceItemPayment.save();
|
|
383
|
+
|
|
384
|
+
items.push(balanceItem)
|
|
385
|
+
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Create a payment pending balance item
|
|
389
|
+
for (const item of checkout.cart.balanceItems) {
|
|
390
|
+
// Create one balance item payment to pay it in one payment
|
|
391
|
+
const balanceItemPayment = new BalanceItemPayment()
|
|
392
|
+
balanceItemPayment.balanceItemId = item.item.id;
|
|
393
|
+
balanceItemPayment.paymentId = payment.id;
|
|
394
|
+
balanceItemPayment.organizationId = organization.id;
|
|
395
|
+
balanceItemPayment.price = item.price;
|
|
396
|
+
await balanceItemPayment.save();
|
|
397
|
+
|
|
398
|
+
const balanceItem = balanceItems.find(i => i.id === item.item.id)
|
|
399
|
+
if (!balanceItem) {
|
|
400
|
+
throw new Error('Balance item not found')
|
|
401
|
+
}
|
|
402
|
+
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
403
|
+
}
|
|
404
|
+
items.push(...balanceItems)
|
|
405
|
+
await ExchangePaymentEndpoint.updateOutstanding(items, organization.id)
|
|
406
|
+
|
|
407
|
+
// Update occupancy
|
|
408
|
+
for (const group of groups) {
|
|
409
|
+
if (registrations.find(p => p.groupId === group.id)) {
|
|
410
|
+
await group.updateOccupancy()
|
|
411
|
+
await group.save()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Update balance items
|
|
416
|
+
if (payment.method == PaymentMethod.Transfer) {
|
|
417
|
+
// Send a small reminder email
|
|
418
|
+
try {
|
|
419
|
+
await Registration.sendTransferEmail(user, organization, payment)
|
|
420
|
+
} catch (e) {
|
|
421
|
+
console.error("Failed to send transfer email")
|
|
422
|
+
console.error(e)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let paymentUrl: string | null = null
|
|
427
|
+
const description = 'Inschrijving '+organization.name
|
|
428
|
+
if (payment.status != PaymentStatus.Succeeded) {
|
|
429
|
+
const redirectUrl = "https://"+organization.getHost()+'/payment?id='+encodeURIComponent(payment.id)
|
|
430
|
+
const cancelUrl = "https://"+organization.getHost()+'/payment?id='+encodeURIComponent(payment.id) + '&cancel=true'
|
|
431
|
+
const webhookUrl = 'https://'+organization.getApiHost()+"/v"+Version+"/payments/"+encodeURIComponent(payment.id)+"?exchange=true"
|
|
432
|
+
|
|
433
|
+
if (payment.provider === PaymentProvider.Stripe) {
|
|
434
|
+
const stripeResult = await StripeHelper.createPayment({
|
|
435
|
+
payment,
|
|
436
|
+
stripeAccount,
|
|
437
|
+
redirectUrl,
|
|
438
|
+
cancelUrl,
|
|
439
|
+
statementDescriptor: organization.name,
|
|
440
|
+
metadata: {
|
|
441
|
+
organization: organization.id,
|
|
442
|
+
user: user.id,
|
|
443
|
+
payment: payment.id
|
|
444
|
+
},
|
|
445
|
+
i18n: request.i18n,
|
|
446
|
+
lineItems: itemPayments,
|
|
447
|
+
organization,
|
|
448
|
+
customer: {
|
|
449
|
+
name: user.name ?? oldestMember?.details.name ?? 'Onbekend',
|
|
450
|
+
email: user.email,
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
paymentUrl = stripeResult.paymentUrl
|
|
454
|
+
} else if (payment.provider === PaymentProvider.Mollie) {
|
|
455
|
+
|
|
456
|
+
// Mollie payment
|
|
457
|
+
const token = await MollieToken.getTokenFor(organization.id)
|
|
458
|
+
if (!token) {
|
|
459
|
+
throw new SimpleError({
|
|
460
|
+
code: "",
|
|
461
|
+
message: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is onbeschikbaar"
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
const profileId = organization.privateMeta.mollieProfile?.id ?? await token.getProfileId(organization.getHost())
|
|
465
|
+
if (!profileId) {
|
|
466
|
+
throw new SimpleError({
|
|
467
|
+
code: "",
|
|
468
|
+
message: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is tijdelijk onbeschikbaar"
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
|
|
472
|
+
const molliePayment = await mollieClient.payments.create({
|
|
473
|
+
amount: {
|
|
474
|
+
currency: 'EUR',
|
|
475
|
+
value: (totalPrice / 100).toFixed(2)
|
|
476
|
+
},
|
|
477
|
+
method: payment.method == PaymentMethod.Bancontact ? molliePaymentMethod.bancontact : (payment.method == PaymentMethod.iDEAL ? molliePaymentMethod.ideal : molliePaymentMethod.creditcard),
|
|
478
|
+
testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment != 'production',
|
|
479
|
+
profileId,
|
|
480
|
+
description,
|
|
481
|
+
redirectUrl,
|
|
482
|
+
webhookUrl,
|
|
483
|
+
metadata: {
|
|
484
|
+
paymentId: payment.id,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
paymentUrl = molliePayment.getCheckoutUrl()
|
|
488
|
+
|
|
489
|
+
// Save payment
|
|
490
|
+
const dbPayment = new MolliePayment()
|
|
491
|
+
dbPayment.paymentId = payment.id
|
|
492
|
+
dbPayment.mollieId = molliePayment.id
|
|
493
|
+
await dbPayment.save();
|
|
494
|
+
} else if (payment.provider === PaymentProvider.Payconiq) {
|
|
495
|
+
paymentUrl = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, webhookUrl)
|
|
496
|
+
} else if (payment.provider == PaymentProvider.Buckaroo) {
|
|
497
|
+
// Increase request timeout because buckaroo is super slow (in development)
|
|
498
|
+
request.request.request?.setTimeout(60 * 1000)
|
|
499
|
+
const buckaroo = new BuckarooHelper(organization.privateMeta?.buckarooSettings?.key ?? "", organization.privateMeta?.buckarooSettings?.secret ?? "", organization.privateMeta.useTestPayments ?? STAMHOOFD.environment != 'production')
|
|
500
|
+
const ip = request.request.getIP()
|
|
501
|
+
paymentUrl = await buckaroo.createPayment(payment, ip, description, redirectUrl, webhookUrl)
|
|
502
|
+
await payment.save()
|
|
503
|
+
|
|
504
|
+
// TypeScript doesn't understand that the status can change and isn't a const....
|
|
505
|
+
if ((payment.status as any) === PaymentStatus.Failed) {
|
|
506
|
+
throw new SimpleError({
|
|
507
|
+
code: "payment_failed",
|
|
508
|
+
message: "Betaling via " + PaymentMethodHelper.getName(payment.method) + " is onbeschikbaar"
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return new Response(RegisterResponse.create({
|
|
515
|
+
payment: PaymentStruct.create(payment),
|
|
516
|
+
members: await AuthenticatedStructures.membersBlob(members),
|
|
517
|
+
registrations: registrations.map(r => Member.getRegistrationWithMemberStructure(r)),
|
|
518
|
+
paymentUrl
|
|
519
|
+
}));
|
|
520
|
+
}
|
|
521
|
+
}
|