@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.
Files changed (150) hide show
  1. package/.env.template.json +63 -0
  2. package/.eslintrc.js +61 -0
  3. package/README.md +40 -0
  4. package/index.ts +172 -0
  5. package/jest.config.js +11 -0
  6. package/migrations.ts +33 -0
  7. package/package.json +48 -0
  8. package/src/crons.ts +845 -0
  9. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
  10. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
  11. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
  12. package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
  13. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
  14. package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
  15. package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
  16. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
  17. package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
  18. package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
  19. package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
  20. package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
  21. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
  22. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
  23. package/src/endpoints/auth/SignupEndpoint.ts +107 -0
  24. package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
  25. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
  26. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
  27. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
  28. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
  29. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
  30. package/src/endpoints/global/files/UploadFile.ts +147 -0
  31. package/src/endpoints/global/files/UploadImage.ts +119 -0
  32. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
  33. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
  34. package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
  35. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
  36. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
  37. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
  38. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
  39. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
  40. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
  41. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
  42. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
  43. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
  44. package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
  45. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
  46. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
  47. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
  48. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
  49. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
  50. package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
  51. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
  52. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
  53. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
  54. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
  55. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
  56. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
  57. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
  58. package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
  59. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
  60. package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
  61. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
  62. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
  63. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
  64. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
  65. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
  66. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
  67. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
  68. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
  69. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
  70. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
  71. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
  72. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
  73. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
  74. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
  75. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
  76. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
  77. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
  78. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
  79. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
  80. package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
  81. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
  82. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
  83. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
  84. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
  85. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
  86. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
  87. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
  88. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
  89. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
  90. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
  91. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
  92. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
  93. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
  94. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
  95. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
  96. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
  97. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
  98. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
  99. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
  100. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
  101. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
  102. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
  103. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
  104. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
  105. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
  106. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
  107. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
  108. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
  109. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
  110. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
  111. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
  112. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
  113. package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
  114. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
  115. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
  116. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
  117. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
  118. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
  119. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
  120. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
  121. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
  122. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
  123. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
  124. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
  125. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
  126. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
  127. package/src/helpers/AddressValidator.test.ts +40 -0
  128. package/src/helpers/AddressValidator.ts +256 -0
  129. package/src/helpers/AdminPermissionChecker.ts +1031 -0
  130. package/src/helpers/AuthenticatedStructures.ts +158 -0
  131. package/src/helpers/BuckarooHelper.ts +279 -0
  132. package/src/helpers/CheckSettlements.ts +215 -0
  133. package/src/helpers/Context.ts +202 -0
  134. package/src/helpers/CookieHelper.ts +45 -0
  135. package/src/helpers/ForwardHandler.test.ts +216 -0
  136. package/src/helpers/ForwardHandler.ts +140 -0
  137. package/src/helpers/OpenIDConnectHelper.ts +284 -0
  138. package/src/helpers/StripeHelper.ts +293 -0
  139. package/src/helpers/StripePayoutChecker.ts +188 -0
  140. package/src/middleware/ContextMiddleware.ts +16 -0
  141. package/src/migrations/1646578856-validate-addresses.ts +60 -0
  142. package/src/seeds/0000000000-example.ts +13 -0
  143. package/src/seeds/1715028563-user-permissions.ts +52 -0
  144. package/tests/e2e/stock.test.ts +2120 -0
  145. package/tests/e2e/tickets.test.ts +926 -0
  146. package/tests/helpers/StripeMocker.ts +362 -0
  147. package/tests/helpers/TestServer.ts +21 -0
  148. package/tests/jest.global.setup.ts +29 -0
  149. package/tests/jest.setup.ts +59 -0
  150. package/tsconfig.json +42 -0
@@ -0,0 +1,210 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from "@stamhoofd/structures";
3
+
4
+ import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
5
+ import { Context } from "../../../../helpers/Context";
6
+ import { Group, OrganizationRegistrationPeriod, RegistrationPeriod } from "@stamhoofd/models";
7
+ import { SimpleError } from "@simonbackx/simple-errors";
8
+
9
+ type Params = Record<string, never>;
10
+ type Query = undefined;
11
+ type Body = PatchableArrayAutoEncoder<OrganizationRegistrationPeriodStruct>
12
+ type ResponseBody = OrganizationRegistrationPeriodStruct[]
13
+
14
+ /**
15
+ * One endpoint to create, patch and delete members and their registrations and payments
16
+ */
17
+
18
+ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
19
+ bodyDecoder = new PatchableArrayDecoder(OrganizationRegistrationPeriodStruct as Decoder<OrganizationRegistrationPeriodStruct>, OrganizationRegistrationPeriodStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationRegistrationPeriodStruct>>, 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, "/organization/registration-periods", {});
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.setOrganizationScope();
36
+ const {user} = await Context.authenticate()
37
+
38
+ if (!await Context.auth.hasFullAccess(organization.id)) {
39
+ throw Context.auth.error()
40
+ }
41
+
42
+ const structs: OrganizationRegistrationPeriodStruct[] = [];
43
+
44
+ for (const patch of request.body.getPatches()) {
45
+ const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
46
+ if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
47
+ throw new SimpleError({
48
+ code: "not_found",
49
+ message: "Period not found",
50
+ statusCode: 404
51
+ })
52
+ }
53
+ let deleteUnreachable = false
54
+ const allowedIds: string[] = []
55
+
56
+ if (await Context.auth.hasFullAccess(organization.id)) {
57
+ if (patch.settings) {
58
+ organizationPeriod.settings.patchOrPut(patch.settings);
59
+ }
60
+ } else {
61
+ /// Only allow editing category group ids
62
+ if (patch.settings) {
63
+ // Only allow adding groups if we have create permissions in a given category group
64
+ if (patch.settings.categories && !Array.isArray(patch.settings.categories)) {
65
+ for (const pp of patch.settings.categories.getPatches()) {
66
+ const category = organizationPeriod.settings.categories.find(c => c.id === pp.id)
67
+ if (!category) {
68
+ // Fail silently
69
+ continue
70
+ }
71
+
72
+ if (!await Context.auth.canCreateGroupInCategory(organization.id, category)) {
73
+ throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen in deze categorie')
74
+ }
75
+
76
+ // Only process puts
77
+ const ids = pp.groupIds.getPuts().map(p => p.put)
78
+ allowedIds.push(...ids)
79
+ category.groupIds.push(...ids)
80
+ }
81
+
82
+ if (allowedIds.length > 0) {
83
+ deleteUnreachable = true
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ await organizationPeriod.save();
90
+
91
+ // Check changes to groups
92
+ const deleteGroups = patch.groups.getDeletes()
93
+ if (deleteGroups.length > 0) {
94
+ for (const id of deleteGroups) {
95
+ const model = await Group.getByID(id)
96
+ if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
97
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen')
98
+ }
99
+
100
+ model.deletedAt = new Date()
101
+ await model.save()
102
+ deleteUnreachable = true
103
+ }
104
+ }
105
+
106
+ for (const groupPut of patch.groups.getPuts()) {
107
+ if (!await Context.auth.hasFullAccess(organization.id) && !allowedIds.includes(groupPut.put.id)) {
108
+ throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen')
109
+ }
110
+
111
+ const struct = groupPut.put
112
+ const model = new Group()
113
+ model.id = struct.id
114
+ model.organizationId = organization.id
115
+ model.periodId = organizationPeriod.periodId
116
+ model.settings = struct.settings
117
+ model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
118
+ model.status = struct.status
119
+
120
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
121
+ // Create a temporary permission role for this user
122
+ const organizationPermissions = user.permissions?.organizationPermissions?.get(organization.id)
123
+ if (!organizationPermissions) {
124
+ throw new Error('Unexpected missing permissions')
125
+ }
126
+ const resourcePermissions = ResourcePermissions.create({
127
+ resourceName: model.settings.name,
128
+ level: PermissionLevel.Full
129
+ })
130
+ const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions)
131
+ user.permissions!.organizationPermissions.set(organization.id, organizationPermissions.patch(patch))
132
+ console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({version: Version}))
133
+ await user.save()
134
+ }
135
+
136
+ // Check if current user has permissions to this new group -> else fail with error
137
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
138
+ throw new SimpleError({
139
+ code: "missing_permissions",
140
+ message: "You cannot restrict your own permissions",
141
+ human: "Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep"
142
+ })
143
+ continue;
144
+ }
145
+
146
+ await model.updateOccupancy()
147
+ await model.save();
148
+ }
149
+
150
+ for (const struct of patch.groups.getPatches()) {
151
+ const model = await Group.getByID(struct.id)
152
+
153
+ if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
154
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen')
155
+ }
156
+
157
+ if (struct.settings) {
158
+ model.settings.patchOrPut(struct.settings)
159
+ }
160
+
161
+ if (struct.status) {
162
+ model.status = struct.status
163
+ }
164
+
165
+ if (struct.privateSettings) {
166
+ model.privateSettings.patchOrPut(struct.privateSettings)
167
+
168
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
169
+ throw new SimpleError({
170
+ code: "missing_permissions",
171
+ message: "You cannot restrict your own permissions",
172
+ human: "Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen."
173
+ })
174
+ }
175
+ }
176
+
177
+ if (struct.cycle !== undefined) {
178
+ model.cycle = struct.cycle
179
+ }
180
+
181
+ if (struct.deletedAt !== undefined) {
182
+ model.deletedAt = struct.deletedAt
183
+ }
184
+
185
+ await model.updateOccupancy()
186
+ await model.save();
187
+ }
188
+
189
+ const period = await RegistrationPeriod.getByID(organizationPeriod.periodId);
190
+ const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
191
+
192
+ if (deleteUnreachable) {
193
+ // Delete unreachable categories first
194
+ await organization.cleanCategories(groups);
195
+ await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
196
+ }
197
+
198
+ if (period) {
199
+
200
+ structs.push(organizationPeriod.getStructure(period, groups));
201
+ }
202
+
203
+ }
204
+
205
+ return new Response(
206
+ structs
207
+ );
208
+ }
209
+
210
+ }
@@ -0,0 +1,93 @@
1
+
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
+ import { StripeAccount } from '@stamhoofd/models';
5
+ import { PermissionLevel, StripeAccount as StripeAccountStruct } from "@stamhoofd/structures";
6
+ import Stripe from 'stripe';
7
+
8
+ import { Context } from '../../../../helpers/Context';
9
+ import { StripeHelper } from '../../../../helpers/StripeHelper';
10
+ type Params = Record<string, never>;
11
+ type Body = undefined;
12
+ type Query = undefined
13
+ type ResponseBody = StripeAccountStruct
14
+
15
+ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
16
+ protected doesMatch(request: Request): [true, Params] | [false] {
17
+ if (request.method != "POST") {
18
+ return [false];
19
+ }
20
+
21
+ const params = Endpoint.parseParameters(request.url, "/stripe/connect", {});
22
+
23
+ if (params) {
24
+ return [true, params as Params];
25
+ }
26
+
27
+ return [false];
28
+ }
29
+
30
+ async handle(_: DecodedRequest<Params, Query, Body>) {
31
+ const organization = await Context.setOrganizationScope();
32
+ await Context.authenticate()
33
+
34
+ // Fast throw first (more in depth checking for patches later)
35
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Full)) {
36
+ throw Context.auth.error()
37
+ }
38
+
39
+ const models = await StripeAccount.where({ organizationId: organization.id, status: "active" })
40
+
41
+ const canCreateMultipleStripeAccounts = models.every(a => (a.meta.charges_enabled && a.meta.payouts_enabled) || (a.meta.details_submitted))
42
+ if (models.length > 0 && !canCreateMultipleStripeAccounts) {
43
+ throw new SimpleError({
44
+ code: "already_connected",
45
+ message: "Je hebt al een Stripe account gekoppeld"
46
+ })
47
+ }
48
+
49
+ const type = 'express'
50
+
51
+ let expressData: Stripe.AccountCreateParams = {
52
+ country: organization.address.country,
53
+ // Problem: we cannot set company or business_type, because then it defaults the structure of the company to one that requires a company number
54
+ capabilities: {
55
+ card_payments: { requested: true },
56
+ transfers: { requested: true },
57
+ bancontact_payments: { requested: true },
58
+ ideal_payments: { requested: true },
59
+ },
60
+ settings: {
61
+ payouts: {
62
+ schedule: {
63
+ delay_days: 'minimum',
64
+ interval: 'weekly',
65
+ weekly_anchor: 'monday',
66
+ }
67
+ }
68
+ }
69
+ };
70
+
71
+ if (type !== 'express') {
72
+ expressData = {};
73
+ }
74
+
75
+ // Create a new Stripe account
76
+ const stripe = StripeHelper.getInstance()
77
+ const account = await stripe.accounts.create({
78
+ type,
79
+ ...expressData
80
+ });
81
+
82
+ // Save the Stripe account in the database
83
+ const model = new StripeAccount();
84
+ model.organizationId = organization.id;
85
+ model.accountId = account.id;
86
+ model.setMetaFromStripeAccount(account)
87
+ await model.save();
88
+
89
+ // Return information about the Stripe Account
90
+
91
+ return new Response(StripeAccountStruct.create(model));
92
+ }
93
+ }
@@ -0,0 +1,59 @@
1
+
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
+ import { StripeAccount } from '@stamhoofd/models';
5
+ import { PermissionLevel } from '@stamhoofd/structures';
6
+
7
+ import { Context } from '../../../../helpers/Context';
8
+
9
+ type Params = { id: string };
10
+ type Body = undefined;
11
+ type Query = undefined
12
+ type ResponseBody = undefined
13
+
14
+ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
+ protected doesMatch(request: Request): [true, Params] | [false] {
16
+ if (request.method != "DELETE") {
17
+ return [false];
18
+ }
19
+
20
+ const params = Endpoint.parseParameters(request.url, "/stripe/accounts/@id", {id: String});
21
+
22
+ if (params) {
23
+ return [true, params as Params];
24
+ }
25
+
26
+ return [false];
27
+ }
28
+
29
+ async handle(request: DecodedRequest<Params, Query, Body>) {
30
+ const organization = await Context.setOrganizationScope();
31
+ await Context.authenticate()
32
+
33
+ // Fast throw first (more in depth checking for patches later)
34
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Full)) {
35
+ throw Context.auth.error()
36
+ }
37
+
38
+ // Search account in database
39
+ const model = await StripeAccount.getByID(request.params.id)
40
+ if (!model || model.organizationId != organization.id || model.status !== "active") {
41
+ throw Context.auth.notFoundOrNoAccess("Account niet gevonden")
42
+ }
43
+
44
+ // For now we don't delete them in Stripe because this causes issues with data access
45
+ // const stripe = StripeHelper.getInstance()
46
+ //
47
+ // try {
48
+ // await stripe.accounts.del(model.accountId);
49
+ // } catch (e) {
50
+ // console.error('Tried deleting account but failed', e)
51
+ // }
52
+
53
+ // If that succeeded
54
+ model.status = "deleted"
55
+ await model.save()
56
+
57
+ return new Response(undefined);
58
+ }
59
+ }
@@ -0,0 +1,78 @@
1
+
2
+ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
3
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
+ import { SimpleError } from '@simonbackx/simple-errors';
5
+ import { StripeAccount } from '@stamhoofd/models';
6
+ import { PermissionLevel } from '@stamhoofd/structures';
7
+
8
+ import { Context } from '../../../../helpers/Context';
9
+ import { StripeHelper } from '../../../../helpers/StripeHelper';
10
+
11
+ type Params = Record<string, never>;
12
+ class Body extends AutoEncoder {
13
+ /**
14
+ * The account id (internal id, not the stripe id)
15
+ */
16
+ @field({ decoder: StringDecoder })
17
+ accountId: string
18
+
19
+ @field({ decoder: StringDecoder })
20
+ returnUrl: string
21
+
22
+ @field({ decoder: StringDecoder })
23
+ refreshUrl: string
24
+ }
25
+
26
+ type Query = undefined
27
+ class ResponseBody extends AutoEncoder {
28
+ @field({ decoder: StringDecoder })
29
+ url: string
30
+ }
31
+
32
+ export class GetStripeAccountLinkEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
33
+ bodyDecoder = Body as Decoder<Body>
34
+
35
+ protected doesMatch(request: Request): [true, Params] | [false] {
36
+ if (request.method != "POST") {
37
+ return [false];
38
+ }
39
+
40
+ const params = Endpoint.parseParameters(request.url, "/stripe/account-link", {});
41
+
42
+ if (params) {
43
+ return [true, params as Params];
44
+ }
45
+
46
+ return [false];
47
+ }
48
+
49
+ async handle(request: DecodedRequest<Params, Query, Body>) {
50
+ const organization = await Context.setOrganizationScope();
51
+ await Context.authenticate()
52
+
53
+ // Fast throw first (more in depth checking for patches later)
54
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Full)) {
55
+ throw Context.auth.error()
56
+ }
57
+
58
+ // Search account in database
59
+ const model = await StripeAccount.getByID(request.body.accountId)
60
+ if (!model || model.organizationId != organization.id || model.status !== 'active') {
61
+ throw Context.auth.notFoundOrNoAccess("Account niet gevonden")
62
+ }
63
+
64
+ // Get account
65
+ const stripe = StripeHelper.getInstance()
66
+ const accountLink = await stripe.accountLinks.create({
67
+ account: model.accountId,
68
+ refresh_url: request.body.refreshUrl,
69
+ return_url: request.body.returnUrl,
70
+ type: 'account_onboarding',
71
+ collect: model.meta.type === 'express' ? 'eventually_due' : undefined, // Collect all at the start
72
+ });
73
+
74
+ return new Response(ResponseBody.create({
75
+ url: accountLink.url
76
+ }));
77
+ }
78
+ }
@@ -0,0 +1,40 @@
1
+
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { StripeAccount } from '@stamhoofd/models';
4
+ import { PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
5
+
6
+ import { Context } from '../../../../helpers/Context';
7
+
8
+ type Params = Record<string, never>;
9
+ type Body = undefined;
10
+ type Query = undefined
11
+ type ResponseBody = StripeAccountStruct[]
12
+
13
+ export class GetStripeAccountLinkEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
+ protected doesMatch(request: Request): [true, Params] | [false] {
15
+ if (request.method != "GET") {
16
+ return [false];
17
+ }
18
+
19
+ const params = Endpoint.parseParameters(request.url, "/stripe/accounts", {});
20
+
21
+ if (params) {
22
+ return [true, params as Params];
23
+ }
24
+
25
+ return [false];
26
+ }
27
+
28
+ async handle(_: DecodedRequest<Params, Query, Body>) {
29
+ const organization = await Context.setOrganizationScope();
30
+ await Context.authenticate()
31
+
32
+ // Fast throw first (more in depth checking for patches later)
33
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Read)) {
34
+ throw Context.auth.error()
35
+ }
36
+
37
+ const models = await StripeAccount.where({ organizationId: organization.id, status: 'active' })
38
+ return new Response(models.map(m => StripeAccountStruct.create(m)));
39
+ }
40
+ }
@@ -0,0 +1,69 @@
1
+
2
+ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
3
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
+ import { SimpleError } from '@simonbackx/simple-errors';
5
+ import { StripeAccount, Token } from '@stamhoofd/models';
6
+ import { PermissionLevel } from '@stamhoofd/structures';
7
+
8
+ import { Context } from '../../../../helpers/Context';
9
+ import { StripeHelper } from '../../../../helpers/StripeHelper';
10
+
11
+ type Params = Record<string, never>;
12
+ class Body extends AutoEncoder {
13
+ /**
14
+ * The account id (internal id, not the stripe id)
15
+ */
16
+ @field({ decoder: StringDecoder })
17
+ accountId: string
18
+ }
19
+
20
+ type Query = undefined
21
+ class ResponseBody extends AutoEncoder {
22
+ @field({ decoder: StringDecoder })
23
+ url: string
24
+ }
25
+
26
+ export class GetStripeLoginLinkEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
27
+ bodyDecoder = Body as Decoder<Body>
28
+
29
+ protected doesMatch(request: Request): [true, Params] | [false] {
30
+ if (request.method != "POST") {
31
+ return [false];
32
+ }
33
+
34
+ const params = Endpoint.parseParameters(request.url, "/stripe/login-link", {});
35
+
36
+ if (params) {
37
+ return [true, params as Params];
38
+ }
39
+
40
+ return [false];
41
+ }
42
+
43
+ async handle(request: DecodedRequest<Params, Query, Body>) {
44
+ const organization = await Context.setOrganizationScope();
45
+ await Context.authenticate()
46
+
47
+ // Fast throw first (more in depth checking for patches later)
48
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Full)) {
49
+ throw Context.auth.error()
50
+ }
51
+
52
+ // Search account in database
53
+ const model = await StripeAccount.getByID(request.body.accountId)
54
+ if (!model || model.organizationId != organization.id || model.status !== 'active') {
55
+ throw new SimpleError({
56
+ code: "not_found",
57
+ message: "Account niet gevonden",
58
+ statusCode: 400
59
+ })
60
+ }
61
+
62
+ const stripe = StripeHelper.getInstance()
63
+ const accountLink = await stripe.accounts.createLoginLink(model.accountId);
64
+
65
+ return new Response(ResponseBody.create({
66
+ url: accountLink.url
67
+ }));
68
+ }
69
+ }
@@ -0,0 +1,52 @@
1
+
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { StripeAccount } from '@stamhoofd/models';
4
+ import { PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
5
+
6
+ import { Context } from '../../../../helpers/Context';
7
+ import { StripeHelper } from '../../../../helpers/StripeHelper';
8
+
9
+ type Params = { id: string };
10
+ type Body = undefined;
11
+ type Query = undefined
12
+ type ResponseBody = StripeAccountStruct
13
+
14
+ export class UpdateStripeAccountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
+ protected doesMatch(request: Request): [true, Params] | [false] {
16
+ if (request.method != "POST") {
17
+ return [false];
18
+ }
19
+
20
+ const params = Endpoint.parseParameters(request.url, "/stripe/accounts/@id", {id: String});
21
+
22
+ if (params) {
23
+ return [true, params as Params];
24
+ }
25
+
26
+ return [false];
27
+ }
28
+
29
+ async handle(request: DecodedRequest<Params, Query, Body>) {
30
+ const organization = await Context.setOrganizationScope();
31
+ await Context.authenticate()
32
+
33
+ // Fast throw first (more in depth checking for patches later)
34
+ if (!await Context.auth.canManagePaymentAccounts(organization.id, PermissionLevel.Read)) {
35
+ throw Context.auth.error()
36
+ }
37
+
38
+ // Search account in database
39
+ const model = await StripeAccount.getByID(request.params.id)
40
+ if (!model || model.organizationId != organization.id) {
41
+ throw Context.auth.notFoundOrNoAccess("Account niet gevonden")
42
+ }
43
+
44
+ // Get account
45
+ const stripe = StripeHelper.getInstance()
46
+ const account = await stripe.accounts.retrieve(model.accountId);
47
+ model.setMetaFromStripeAccount(account)
48
+ await model.save()
49
+
50
+ return new Response(StripeAccountStruct.create(model));
51
+ }
52
+ }
@@ -0,0 +1,73 @@
1
+ import { Decoder } from '@simonbackx/simple-encoding';
2
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
+ import { SimpleError } from "@simonbackx/simple-errors";
4
+ import { Token, User } from '@stamhoofd/models';
5
+ import { ApiUser, ApiUserWithToken, UserPermissions } from "@stamhoofd/structures";
6
+
7
+ import { Context } from '../../../../helpers/Context';
8
+ type Params = Record<string, never>;
9
+ type Query = undefined;
10
+ type Body = ApiUser
11
+ type ResponseBody = ApiUser
12
+
13
+ export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
+ bodyDecoder = ApiUser as Decoder<ApiUser>
15
+
16
+ protected doesMatch(request: Request): [true, Params] | [false] {
17
+ if (request.method != "POST") {
18
+ return [false];
19
+ }
20
+
21
+ const params = Endpoint.parseParameters(request.url, "/api-keys", {});
22
+
23
+ if (params) {
24
+ return [true, params as Params];
25
+ }
26
+ return [false];
27
+ }
28
+
29
+ async handle(request: DecodedRequest<Params, Query, Body>) {
30
+ const organization = await Context.setOrganizationScope();
31
+ await Context.authenticate()
32
+
33
+ // Fast throw first (more in depth checking for patches later)
34
+ if (!await Context.auth.canManageAdmins(organization.id)) {
35
+ throw Context.auth.error()
36
+ }
37
+
38
+ const admin = new User();
39
+ admin.organizationId = organization.id;
40
+ admin.firstName = request.body.name;
41
+ admin.lastName = null
42
+ admin.email = 'creating.api'
43
+ admin.verified = true
44
+
45
+ if (!admin.isApiUser) {
46
+ throw new Error('Unexpectedly created normal user while trying to create API-user')
47
+ }
48
+
49
+ // Merge permissions
50
+ if (!request.body.permissions) {
51
+ throw new SimpleError({
52
+ code: 'missing_field',
53
+ message: 'When creating API-users, you are required to specify permissions in the request',
54
+ field: 'permissions'
55
+ })
56
+ }
57
+
58
+ admin.permissions = UserPermissions.limitedAdd(null, request.body.permissions, organization.id)
59
+ await admin.save();
60
+
61
+ // Set id
62
+ admin.email = admin.id + '.api'
63
+ await admin.save();
64
+
65
+ const createdToken = await Token.createApiToken(admin);
66
+
67
+ return new Response(ApiUserWithToken.create({
68
+ ...(await Token.getAPIUserWithToken(admin)),
69
+ token: createdToken.accessToken,
70
+ expiresAt: createdToken.accessTokenValidUntil
71
+ }));
72
+ }
73
+ }