@stamhoofd/backend 2.9.0 → 2.14.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/package.json +10 -2
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +6 -18
- package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -9
- package/src/endpoints/global/members/GetMembersEndpoint.ts +15 -31
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +52 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +54 -2
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +294 -134
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/payments/legacy/GetPaymentsEndpoint.ts +170 -0
- package/src/helpers/AdminPermissionChecker.ts +88 -68
- package/src/helpers/MemberUserSyncer.ts +8 -2
- package/src/helpers/ViesHelper.ts +151 -0
- package/.env.json +0 -65
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -35,6 +35,14 @@
|
|
|
35
35
|
"@simonbackx/simple-database": "1.24.0",
|
|
36
36
|
"@simonbackx/simple-endpoints": "1.13.0",
|
|
37
37
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
38
|
+
"@stamhoofd/backend-i18n": "^2.13.0",
|
|
39
|
+
"@stamhoofd/backend-middleware": "^2.13.0",
|
|
40
|
+
"@stamhoofd/email": "^2.13.0",
|
|
41
|
+
"@stamhoofd/models": "^2.13.0",
|
|
42
|
+
"@stamhoofd/queues": "^2.13.0",
|
|
43
|
+
"@stamhoofd/sql": "^2.13.0",
|
|
44
|
+
"@stamhoofd/structures": "^2.14.0",
|
|
45
|
+
"@stamhoofd/utility": "^2.13.0",
|
|
38
46
|
"aws-sdk": "^2.885.0",
|
|
39
47
|
"axios": "1.6.8",
|
|
40
48
|
"cookie": "^0.5.0",
|
|
@@ -50,5 +58,5 @@
|
|
|
50
58
|
"postmark": "4.0.2",
|
|
51
59
|
"stripe": "^16.6.0"
|
|
52
60
|
},
|
|
53
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "93374a0a3799ee54f387d4cff70c7cf70afc0ac8"
|
|
54
62
|
}
|
|
@@ -30,33 +30,23 @@ export const filterCompilers: SQLFilterDefinitions = {
|
|
|
30
30
|
),
|
|
31
31
|
city: createSQLExpressionFilterCompiler(
|
|
32
32
|
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.city'),
|
|
33
|
-
|
|
34
|
-
true,
|
|
35
|
-
false
|
|
33
|
+
{isJSONValue: true}
|
|
36
34
|
),
|
|
37
35
|
country: createSQLExpressionFilterCompiler(
|
|
38
36
|
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.country'),
|
|
39
|
-
|
|
40
|
-
true,
|
|
41
|
-
false
|
|
37
|
+
{isJSONValue: true}
|
|
42
38
|
),
|
|
43
39
|
umbrellaOrganization: createSQLExpressionFilterCompiler(
|
|
44
40
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.umbrellaOrganization'),
|
|
45
|
-
|
|
46
|
-
true,
|
|
47
|
-
false
|
|
41
|
+
{isJSONValue: true}
|
|
48
42
|
),
|
|
49
43
|
type: createSQLExpressionFilterCompiler(
|
|
50
44
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.type'),
|
|
51
|
-
|
|
52
|
-
true,
|
|
53
|
-
false
|
|
45
|
+
{isJSONValue: true}
|
|
54
46
|
),
|
|
55
47
|
tags: createSQLExpressionFilterCompiler(
|
|
56
48
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.tags'),
|
|
57
|
-
|
|
58
|
-
true,
|
|
59
|
-
true
|
|
49
|
+
{isJSONValue: true, isJSONObject: true}
|
|
60
50
|
),
|
|
61
51
|
packages: createSQLRelationFilterCompiler(
|
|
62
52
|
SQL.select().from(
|
|
@@ -103,9 +93,7 @@ export const filterCompilers: SQLFilterDefinitions = {
|
|
|
103
93
|
...baseSQLFilterCompilers,
|
|
104
94
|
"type": createSQLExpressionFilterCompiler(
|
|
105
95
|
SQL.jsonValue(SQL.column('meta'), '$.value.type'),
|
|
106
|
-
|
|
107
|
-
true,
|
|
108
|
-
false
|
|
96
|
+
{isJSONValue: true}
|
|
109
97
|
)
|
|
110
98
|
}
|
|
111
99
|
),
|
|
@@ -24,21 +24,15 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
24
24
|
endDate: createSQLColumnFilterCompiler('endDate'),
|
|
25
25
|
groupIds: createSQLExpressionFilterCompiler(
|
|
26
26
|
SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
|
|
27
|
-
|
|
28
|
-
true,
|
|
29
|
-
true
|
|
27
|
+
{isJSONValue: true, isJSONObject: true}
|
|
30
28
|
),
|
|
31
29
|
defaultAgeGroupIds: createSQLExpressionFilterCompiler(
|
|
32
30
|
SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
|
|
33
|
-
|
|
34
|
-
true,
|
|
35
|
-
true
|
|
31
|
+
{isJSONValue: true, isJSONObject: true}
|
|
36
32
|
),
|
|
37
33
|
organizationTagIds: createSQLExpressionFilterCompiler(
|
|
38
34
|
SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
|
|
39
|
-
|
|
40
|
-
true,
|
|
41
|
-
true
|
|
35
|
+
{isJSONValue: true, isJSONObject: true}
|
|
42
36
|
)
|
|
43
37
|
}
|
|
44
38
|
|
|
@@ -75,25 +75,12 @@ Email.recipientLoaders.set(EmailRecipientFilterType.MemberUnverified, {
|
|
|
75
75
|
|
|
76
76
|
const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
77
77
|
...baseSQLFilterCompilers,
|
|
78
|
-
"price": createSQLColumnFilterCompiler('price'),
|
|
78
|
+
"price": createSQLColumnFilterCompiler('price', {nullable: true}),
|
|
79
79
|
"pricePaid": createSQLColumnFilterCompiler('pricePaid'),
|
|
80
|
-
"waitingList": createSQLColumnFilterCompiler('waitingList'),
|
|
81
80
|
"canRegister": createSQLColumnFilterCompiler('canRegister'),
|
|
82
|
-
"cycle": createSQLColumnFilterCompiler('cycle'),
|
|
83
|
-
|
|
84
|
-
"cycleOffset": createSQLExpressionFilterCompiler({
|
|
85
|
-
getSQL(options) {
|
|
86
|
-
return joinSQLQuery([
|
|
87
|
-
SQL.column('groups', 'cycle').getSQL(options),
|
|
88
|
-
' - ',
|
|
89
|
-
SQL.column('registrations', 'cycle').getSQL(options)
|
|
90
|
-
])
|
|
91
|
-
},
|
|
92
|
-
}),
|
|
93
|
-
|
|
94
81
|
"organizationId": createSQLColumnFilterCompiler('organizationId'),
|
|
95
82
|
"groupId": createSQLColumnFilterCompiler('groupId'),
|
|
96
|
-
"registeredAt": createSQLColumnFilterCompiler('registeredAt'),
|
|
83
|
+
"registeredAt": createSQLColumnFilterCompiler('registeredAt', {nullable: true}),
|
|
97
84
|
"periodId": createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
|
|
98
85
|
|
|
99
86
|
"group": createSQLFilterNamespace({
|
|
@@ -105,7 +92,7 @@ const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
|
105
92
|
status: createSQLExpressionFilterCompiler(
|
|
106
93
|
SQL.column('groups', 'status')
|
|
107
94
|
),
|
|
108
|
-
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
|
|
95
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), {nullable: true}),
|
|
109
96
|
})
|
|
110
97
|
}
|
|
111
98
|
|
|
@@ -120,20 +107,21 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
120
107
|
)
|
|
121
108
|
),
|
|
122
109
|
age: createSQLExpressionFilterCompiler(
|
|
123
|
-
new SQLAge(SQL.column('birthDay'))
|
|
110
|
+
new SQLAge(SQL.column('birthDay')),
|
|
111
|
+
{nullable: true}
|
|
124
112
|
),
|
|
125
113
|
gender: createSQLExpressionFilterCompiler(
|
|
126
114
|
SQL.jsonValue(SQL.column('details'), '$.value.gender'),
|
|
127
|
-
|
|
128
|
-
true,
|
|
129
|
-
false
|
|
115
|
+
{isJSONValue: true}
|
|
130
116
|
),
|
|
131
|
-
birthDay: createSQLColumnFilterCompiler('birthDay',
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
birthDay: createSQLColumnFilterCompiler('birthDay', {
|
|
118
|
+
normalizeValue: (d) => {
|
|
119
|
+
if (typeof d === 'number') {
|
|
120
|
+
const date = new Date(d)
|
|
121
|
+
return Formatter.dateIso(date);
|
|
122
|
+
}
|
|
123
|
+
return d;
|
|
135
124
|
}
|
|
136
|
-
return d;
|
|
137
125
|
}),
|
|
138
126
|
organizationName: createSQLExpressionFilterCompiler(
|
|
139
127
|
SQL.column('organizations', 'name')
|
|
@@ -141,16 +129,12 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
141
129
|
|
|
142
130
|
email: createSQLExpressionFilterCompiler(
|
|
143
131
|
SQL.jsonValue(SQL.column('details'), '$.value.email'),
|
|
144
|
-
|
|
145
|
-
true,
|
|
146
|
-
false
|
|
132
|
+
{isJSONValue: true}
|
|
147
133
|
),
|
|
148
134
|
|
|
149
135
|
parentEmail: createSQLExpressionFilterCompiler(
|
|
150
136
|
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
|
|
151
|
-
|
|
152
|
-
true,
|
|
153
|
-
true
|
|
137
|
+
{isJSONValue: true, isJSONObject: true}
|
|
154
138
|
),
|
|
155
139
|
|
|
156
140
|
registrations: createSQLRelationFilterCompiler(
|
|
@@ -6,7 +6,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
6
6
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
7
7
|
import { Email } from '@stamhoofd/email';
|
|
8
8
|
import { BalanceItem, BalanceItemPayment, Group, Member, MemberWithRegistrations, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, Platform, RateLimiter, Registration, User } from '@stamhoofd/models';
|
|
9
|
-
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, IDRegisterCheckout,
|
|
9
|
+
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
|
|
10
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
11
11
|
|
|
12
12
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -678,11 +678,61 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
const payment = new Payment()
|
|
681
|
-
payment.userId = user.id
|
|
682
681
|
|
|
683
682
|
// Who will receive this money?
|
|
684
683
|
payment.organizationId = organization.id
|
|
685
684
|
|
|
685
|
+
// Who paid
|
|
686
|
+
payment.payingUserId = user.id
|
|
687
|
+
payment.payingOrganizationId = checkout.asOrganizationId ?? null
|
|
688
|
+
|
|
689
|
+
// Fill in customer:
|
|
690
|
+
payment.customer = PaymentCustomer.create({
|
|
691
|
+
firstName: user.firstName,
|
|
692
|
+
lastName: user.lastName,
|
|
693
|
+
email: user.email,
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
if (checkout.asOrganizationId) {
|
|
697
|
+
if (!checkout.customer) {
|
|
698
|
+
throw new SimpleError({
|
|
699
|
+
code: "missing_fields",
|
|
700
|
+
message: "customer is required when paying as an organization",
|
|
701
|
+
human: "Vul je facturatiegegevens in om verder te gaan."
|
|
702
|
+
})
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (!checkout.customer.company) {
|
|
706
|
+
throw new SimpleError({
|
|
707
|
+
code: "missing_fields",
|
|
708
|
+
message: "customer.company is required when paying as an organization",
|
|
709
|
+
human: "Als je een betaling uitvoert in naam van je vereniging, is het noodzakelijk om facturatiegegevens met bedrijfsgegevens in te vullen."
|
|
710
|
+
})
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const payingOrganization = await Organization.getByID(checkout.asOrganizationId);
|
|
714
|
+
if (!payingOrganization) {
|
|
715
|
+
throw new SimpleError({
|
|
716
|
+
code: "invalid_data",
|
|
717
|
+
message: "Oeps, de organisatie waarvoor je probeert te betalen lijkt niet meer te bestaan. Herlaad de pagina en probeer opnieuw."
|
|
718
|
+
})
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Search company id
|
|
722
|
+
// this avoids needing to check the VAT number every time
|
|
723
|
+
const id = checkout.customer.company.id
|
|
724
|
+
const foundCompany = payingOrganization.meta.companies.find(c => c.id === id)
|
|
725
|
+
|
|
726
|
+
if (!foundCompany) {
|
|
727
|
+
throw new SimpleError({
|
|
728
|
+
code: "invalid_data",
|
|
729
|
+
message: "Oeps, de facturatiegegevens die je probeerde te selecteren lijken niet meer te bestaan. Herlaad de pagina en probeer opnieuw."
|
|
730
|
+
})
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
payment.customer.company = foundCompany
|
|
734
|
+
}
|
|
735
|
+
|
|
686
736
|
payment.method = checkout.paymentMethod
|
|
687
737
|
payment.status = PaymentStatus.Created
|
|
688
738
|
payment.price = totalPrice
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, Decoder, ObjectData, patchObject } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
5
|
-
import { BuckarooSettings, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel } from "@stamhoofd/structures";
|
|
5
|
+
import { BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel } from "@stamhoofd/structures";
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
9
|
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
11
|
+
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
11
12
|
|
|
12
13
|
type Params = Record<string, never>;
|
|
13
14
|
type Query = undefined;
|
|
@@ -208,6 +209,10 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
if (request.body.meta) {
|
|
212
|
+
if (request.body.meta.companies) {
|
|
213
|
+
await this.validateCompanies(organization, request.body.meta.companies)
|
|
214
|
+
}
|
|
215
|
+
|
|
211
216
|
const savedPackages = organization.meta.packages
|
|
212
217
|
organization.meta.patchOrPut(request.body.meta)
|
|
213
218
|
organization.meta.packages = savedPackages
|
|
@@ -366,5 +371,52 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
366
371
|
errors.throwIfNotEmpty()
|
|
367
372
|
return new Response(await AuthenticatedStructures.organization(organization));
|
|
368
373
|
}
|
|
374
|
+
|
|
375
|
+
async validateCompanies(organization: Organization, companies: PatchableArrayAutoEncoder<Company>|Company[]) {
|
|
376
|
+
if (isPatchableArray(companies)) {
|
|
377
|
+
for (const patch of companies.getPatches()) {
|
|
378
|
+
// Changed VAT number
|
|
379
|
+
const original = organization.meta.companies.find(c => c.id === patch.id)
|
|
380
|
+
|
|
381
|
+
if (!original) {
|
|
382
|
+
throw new Error('Could not find company')
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Changed VAT number
|
|
386
|
+
const prepatched = original.patch(patch)
|
|
387
|
+
await ViesHelper.checkCompany(prepatched, patch)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let c = 0;
|
|
391
|
+
for (const {put} of companies.getPuts()) {
|
|
392
|
+
c++;
|
|
393
|
+
|
|
394
|
+
if ((organization.meta.companies.length + c) > 5) {
|
|
395
|
+
throw new SimpleError({
|
|
396
|
+
code: "invalid_field",
|
|
397
|
+
message: "Too many companies",
|
|
398
|
+
human: "Je kan maximaal 5 bedrijven toevoegen",
|
|
399
|
+
field: "companies"
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
await ViesHelper.checkCompany(put, put)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
} else {
|
|
407
|
+
if (companies.length > 5) {
|
|
408
|
+
throw new SimpleError({
|
|
409
|
+
code: "invalid_field",
|
|
410
|
+
message: "Too many companies",
|
|
411
|
+
human: "Je kan maximaal 5 bedrijven toevoegen",
|
|
412
|
+
field: "companies"
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const company of companies) {
|
|
417
|
+
await ViesHelper.checkCompany(company, company)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
369
421
|
}
|
|
370
422
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { Context } from '../../../../helpers/Context';
|
|
6
|
+
import { GetPaymentsEndpoint } from './GetPaymentsEndpoint';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = CountFilteredRequest;
|
|
10
|
+
type Body = undefined;
|
|
11
|
+
type ResponseBody = CountResponse;
|
|
12
|
+
|
|
13
|
+
export class GetPaymentsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "GET") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/payments/count", {});
|
|
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
|
+
await Context.setOrganizationScope();
|
|
31
|
+
await Context.authenticate()
|
|
32
|
+
const query = await GetPaymentsEndpoint.buildQuery(request.query)
|
|
33
|
+
|
|
34
|
+
const count = await query
|
|
35
|
+
.count();
|
|
36
|
+
|
|
37
|
+
return new Response(
|
|
38
|
+
CountResponse.create({
|
|
39
|
+
count
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|