@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
|
@@ -1,54 +1,161 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { Payment } from '@stamhoofd/models';
|
|
6
|
+
import { SQL, SQLCast, SQLConcat, SQLFilterDefinitions, SQLJsonUnquote, SQLOrderBy, SQLOrderByDirection, SQLScalar, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
|
|
7
|
+
import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
|
|
8
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
9
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Context } from "../../../../helpers/Context";
|
|
9
|
-
import { StringNullableDecoder } from "../../../../decoders/StringNullableDecoder";
|
|
10
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
11
|
+
import { Context } from '../../../../helpers/Context';
|
|
10
12
|
|
|
11
13
|
type Params = Record<string, never>;
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
+
type Query = LimitedFilteredRequest;
|
|
15
|
+
type Body = undefined;
|
|
16
|
+
type ResponseBody = PaginatedResponse<PaymentGeneral[], LimitedFilteredRequest>
|
|
14
17
|
|
|
18
|
+
const balanceItemPaymentsCompilers: SQLFilterDefinitions = {
|
|
19
|
+
...baseSQLFilterCompilers,
|
|
20
|
+
"id": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'id')),
|
|
21
|
+
"price": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'price')),
|
|
22
|
+
|
|
23
|
+
"balanceItem": createSQLFilterNamespace({
|
|
24
|
+
...baseSQLFilterCompilers,
|
|
25
|
+
id: createSQLColumnFilterCompiler(SQL.column('balance_items', 'id')),
|
|
26
|
+
description: createSQLColumnFilterCompiler(SQL.column('balance_items', 'description')),
|
|
27
|
+
})
|
|
28
|
+
}
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const filterCompilers: SQLFilterDefinitions = {
|
|
31
|
+
...baseSQLFilterCompilers,
|
|
32
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
33
|
+
method: createSQLColumnFilterCompiler('method'),
|
|
34
|
+
status: createSQLColumnFilterCompiler('status'),
|
|
35
|
+
organizationId: createSQLColumnFilterCompiler('organizationId'),
|
|
36
|
+
createdAt: createSQLColumnFilterCompiler('createdAt'),
|
|
37
|
+
updatedAt: createSQLColumnFilterCompiler('updatedAt'),
|
|
38
|
+
paidAt: createSQLColumnFilterCompiler('paidAt', {nullable: true}),
|
|
39
|
+
price: createSQLColumnFilterCompiler('price'),
|
|
40
|
+
provider: createSQLColumnFilterCompiler('provider', {nullable: true}),
|
|
41
|
+
customer: createSQLFilterNamespace({
|
|
42
|
+
...baseSQLFilterCompilers,
|
|
43
|
+
email: createSQLExpressionFilterCompiler(
|
|
44
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.email'),
|
|
45
|
+
{isJSONValue: true}
|
|
46
|
+
),
|
|
47
|
+
firstName: createSQLExpressionFilterCompiler(
|
|
48
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.firstName'),
|
|
49
|
+
{isJSONValue: true}
|
|
50
|
+
),
|
|
51
|
+
lastName: createSQLExpressionFilterCompiler(
|
|
52
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.lastName'),
|
|
53
|
+
{isJSONValue: true}
|
|
54
|
+
),
|
|
55
|
+
name: createSQLExpressionFilterCompiler(
|
|
56
|
+
new SQLCast(
|
|
57
|
+
new SQLConcat(
|
|
58
|
+
new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.firstName')),
|
|
59
|
+
new SQLScalar(' '),
|
|
60
|
+
new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.lastName')),
|
|
61
|
+
),
|
|
62
|
+
'CHAR'
|
|
63
|
+
)
|
|
64
|
+
),
|
|
65
|
+
company: createSQLFilterNamespace({
|
|
66
|
+
name: createSQLExpressionFilterCompiler(
|
|
67
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.name'),
|
|
68
|
+
{isJSONValue: true}
|
|
69
|
+
),
|
|
70
|
+
VATNumber: createSQLExpressionFilterCompiler(
|
|
71
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.VATNumber'),
|
|
72
|
+
{isJSONValue: true}
|
|
73
|
+
),
|
|
74
|
+
companyNumber: createSQLExpressionFilterCompiler(
|
|
75
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.companyNumber'),
|
|
76
|
+
{isJSONValue: true}
|
|
77
|
+
),
|
|
78
|
+
administrationEmail: createSQLExpressionFilterCompiler(
|
|
79
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.administrationEmail'),
|
|
80
|
+
{isJSONValue: true}
|
|
81
|
+
),
|
|
82
|
+
})
|
|
83
|
+
}),
|
|
84
|
+
balanceItemPayments: createSQLRelationFilterCompiler(
|
|
85
|
+
SQL.select()
|
|
86
|
+
.from(
|
|
87
|
+
SQL.table('balance_item_payments')
|
|
88
|
+
).join(
|
|
89
|
+
SQL.join(
|
|
90
|
+
SQL.table('balance_items')
|
|
91
|
+
).where(
|
|
92
|
+
SQL.column('balance_items', 'id'),
|
|
93
|
+
SQL.column('balance_item_payments', 'balanceItemId')
|
|
94
|
+
)
|
|
95
|
+
).where(
|
|
96
|
+
SQL.column('paymentId'),
|
|
97
|
+
SQL.column('payments', 'id')
|
|
98
|
+
),
|
|
99
|
+
balanceItemPaymentsCompilers
|
|
100
|
+
),
|
|
101
|
+
}
|
|
38
102
|
|
|
39
|
-
|
|
40
|
-
|
|
103
|
+
const sorters: SQLSortDefinitions<Payment> = {
|
|
104
|
+
'id': {
|
|
105
|
+
getValue(a) {
|
|
106
|
+
return a.id
|
|
107
|
+
},
|
|
108
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
109
|
+
return new SQLOrderBy({
|
|
110
|
+
column: SQL.column('id'),
|
|
111
|
+
direction
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
'createdAt': {
|
|
116
|
+
getValue(a) {
|
|
117
|
+
return Formatter.dateTimeIso(a.createdAt, 'UTC')
|
|
118
|
+
},
|
|
119
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
120
|
+
return new SQLOrderBy({
|
|
121
|
+
column: SQL.column('createdAt'),
|
|
122
|
+
direction
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
'paidAt': {
|
|
127
|
+
getValue(a) {
|
|
128
|
+
return a.paidAt !== null ? Formatter.dateTimeIso(a.paidAt, 'UTC') : null
|
|
129
|
+
},
|
|
130
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
131
|
+
return new SQLOrderBy({
|
|
132
|
+
column: SQL.column('paidAt'),
|
|
133
|
+
direction
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
'price': {
|
|
138
|
+
getValue(a) {
|
|
139
|
+
return a.price
|
|
140
|
+
},
|
|
141
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
142
|
+
return new SQLOrderBy({
|
|
143
|
+
column: SQL.column('price'),
|
|
144
|
+
direction
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
41
148
|
}
|
|
42
149
|
|
|
43
150
|
export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
44
|
-
|
|
151
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>
|
|
45
152
|
|
|
46
153
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
47
154
|
if (request.method != "GET") {
|
|
48
155
|
return [false];
|
|
49
156
|
}
|
|
50
157
|
|
|
51
|
-
const params = Endpoint.parseParameters(request.url, "/
|
|
158
|
+
const params = Endpoint.parseParameters(request.url, "/payments", {});
|
|
52
159
|
|
|
53
160
|
if (params) {
|
|
54
161
|
return [true, params as Params];
|
|
@@ -56,115 +163,168 @@ export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
56
163
|
return [false];
|
|
57
164
|
}
|
|
58
165
|
|
|
59
|
-
async
|
|
60
|
-
const organization =
|
|
61
|
-
|
|
166
|
+
static async buildQuery(q: CountFilteredRequest|LimitedFilteredRequest) {
|
|
167
|
+
const organization = Context.organization
|
|
168
|
+
let scopeFilter: StamhoofdFilter|undefined = undefined;
|
|
169
|
+
|
|
170
|
+
if (!organization) {
|
|
171
|
+
throw Context.auth.error()
|
|
172
|
+
}
|
|
62
173
|
|
|
63
174
|
if (!await Context.auth.canManagePayments(organization.id)) {
|
|
64
175
|
throw Context.auth.error()
|
|
65
|
-
}
|
|
176
|
+
}
|
|
66
177
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
178
|
+
scopeFilter = {
|
|
179
|
+
organizationId: organization.id
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const query = SQL
|
|
183
|
+
.select()
|
|
184
|
+
.from(
|
|
185
|
+
SQL.table('payments')
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (scopeFilter) {
|
|
189
|
+
query.where(compileToSQLFilter(scopeFilter, filterCompilers))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (q.filter) {
|
|
193
|
+
query.where(compileToSQLFilter(q.filter, filterCompilers))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (q.search) {
|
|
197
|
+
// todo
|
|
71
198
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
199
|
+
let searchFilter: StamhoofdFilter|null = null
|
|
200
|
+
searchFilter = {
|
|
201
|
+
$or: [
|
|
202
|
+
{
|
|
203
|
+
customer: {
|
|
204
|
+
name: {
|
|
205
|
+
$contains: q.search
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
customer: {
|
|
211
|
+
company: {
|
|
212
|
+
name: {
|
|
213
|
+
$contains: q.search
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
balanceItemPayments: {
|
|
220
|
+
$elemMatch: {
|
|
221
|
+
balanceItem: {
|
|
222
|
+
description: {
|
|
223
|
+
$contains: q.search
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (q.search.includes('@')) {
|
|
233
|
+
searchFilter = {
|
|
234
|
+
$or: [
|
|
235
|
+
{
|
|
236
|
+
customer: {
|
|
237
|
+
email: {
|
|
238
|
+
$contains: q.search
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
customer: {
|
|
244
|
+
company: {
|
|
245
|
+
administrationEmail: {
|
|
246
|
+
$contains: q.search
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
]
|
|
96
252
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}]
|
|
103
|
-
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (searchFilter) {
|
|
256
|
+
query.where(compileToSQLFilter(searchFilter, filterCompilers))
|
|
257
|
+
}
|
|
104
258
|
}
|
|
105
259
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
sign: query.afterId ? '>' : '>=',
|
|
110
|
-
value: paidSince
|
|
111
|
-
}, {
|
|
112
|
-
sign: '<=',
|
|
113
|
-
value: query.paidBefore
|
|
114
|
-
}] : {
|
|
115
|
-
sign: query.afterId ? '>' : '>=',
|
|
116
|
-
value: paidSince
|
|
117
|
-
},
|
|
118
|
-
method: {
|
|
119
|
-
sign: 'IN',
|
|
120
|
-
value: query.methods ?? [PaymentMethod.Transfer]
|
|
121
|
-
},
|
|
122
|
-
provider: {
|
|
123
|
-
sign: 'IN',
|
|
124
|
-
value: query.providers ?? [null]
|
|
260
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
261
|
+
if (q.pageFilter) {
|
|
262
|
+
query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
|
|
125
263
|
}
|
|
126
|
-
}, {
|
|
127
|
-
limit: query.limit ? (query.limit - payments.length) : undefined,
|
|
128
|
-
sort: [{
|
|
129
|
-
column: "paidAt",
|
|
130
|
-
direction: "ASC"
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
column: "id",
|
|
134
|
-
direction: "ASC"
|
|
135
|
-
}]
|
|
136
|
-
}));
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!query.paidSince && !query.methods && !query.providers) {
|
|
140
|
-
// Default behaviour is to return all not-paid transfer payments that are not yet paid
|
|
141
|
-
|
|
142
|
-
payments.push(...
|
|
143
|
-
await Payment.where({
|
|
144
|
-
organizationId: organization.id,
|
|
145
|
-
paidAt: null,
|
|
146
|
-
method: PaymentMethod.Transfer,
|
|
147
|
-
status: {
|
|
148
|
-
sign: '!=',
|
|
149
|
-
value: PaymentStatus.Failed
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
264
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
organizationId: organization.id,
|
|
157
|
-
paidAt: null,
|
|
158
|
-
updatedAt: {
|
|
159
|
-
sign: '>',
|
|
160
|
-
value: new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
161
|
-
},
|
|
162
|
-
method: PaymentMethod.Transfer,
|
|
163
|
-
status: PaymentStatus.Failed
|
|
164
|
-
})
|
|
165
|
-
);
|
|
265
|
+
query.orderBy(compileToSQLSorter(q.sort, sorters))
|
|
266
|
+
query.limit(q.limit)
|
|
166
267
|
}
|
|
268
|
+
|
|
269
|
+
return query
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
273
|
+
const query = await this.buildQuery(requestQuery)
|
|
274
|
+
const data = await query.fetch()
|
|
275
|
+
|
|
276
|
+
const payments = Payment.fromRows(data, 'payments')
|
|
277
|
+
|
|
278
|
+
let next: LimitedFilteredRequest|undefined;
|
|
167
279
|
|
|
168
|
-
|
|
280
|
+
if (payments.length >= requestQuery.limit) {
|
|
281
|
+
const lastObject = payments[payments.length - 1];
|
|
282
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
283
|
+
|
|
284
|
+
next = new LimitedFilteredRequest({
|
|
285
|
+
filter: requestQuery.filter,
|
|
286
|
+
pageFilter: nextFilter,
|
|
287
|
+
sort: requestQuery.sort,
|
|
288
|
+
limit: requestQuery.limit,
|
|
289
|
+
search: requestQuery.search
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
293
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
294
|
+
next = undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return new PaginatedResponse<PaymentGeneral[], LimitedFilteredRequest>({
|
|
299
|
+
results: await AuthenticatedStructures.paymentsGeneral(payments, false),
|
|
300
|
+
next
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
305
|
+
await Context.setOrganizationScope();
|
|
306
|
+
await Context.authenticate()
|
|
307
|
+
|
|
308
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
309
|
+
|
|
310
|
+
if (request.query.limit > maxLimit) {
|
|
311
|
+
throw new SimpleError({
|
|
312
|
+
code: 'invalid_field',
|
|
313
|
+
field: 'limit',
|
|
314
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (request.query.limit < 1) {
|
|
319
|
+
throw new SimpleError({
|
|
320
|
+
code: 'invalid_field',
|
|
321
|
+
field: 'limit',
|
|
322
|
+
message: 'Limit can not be less than 1'
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return new Response(
|
|
327
|
+
await GetPaymentsEndpoint.buildData(request.query)
|
|
328
|
+
);
|
|
169
329
|
}
|
|
170
330
|
}
|
|
@@ -169,7 +169,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
169
169
|
})
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
if (patch.method || patch.paidAt !== undefined) {
|
|
172
|
+
if (patch.method || patch.paidAt !== undefined || patch.status !== undefined) {
|
|
173
173
|
if (payment.method && ![PaymentMethod.Unknown, PaymentMethod.Transfer, PaymentMethod.PointOfSale].includes(payment.method)) {
|
|
174
174
|
throw new SimpleError({
|
|
175
175
|
code: "invalid_field",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { AutoEncoder, DateDecoder, EnumDecoder, field, IntegerDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Organization, Payment } from "@stamhoofd/models";
|
|
4
|
+
import { PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { StringArrayDecoder } from "../../../../../decoders/StringArrayDecoder";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../../../helpers/AuthenticatedStructures";
|
|
8
|
+
import { Context } from "../../../../../helpers/Context";
|
|
9
|
+
import { StringNullableDecoder } from "../../../../../decoders/StringNullableDecoder";
|
|
10
|
+
|
|
11
|
+
type Params = Record<string, never>;
|
|
12
|
+
type Body = undefined
|
|
13
|
+
type ResponseBody = PaymentGeneral[]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Query extends AutoEncoder {
|
|
17
|
+
/**
|
|
18
|
+
* Usage in combination with paidSince is special!
|
|
19
|
+
*/
|
|
20
|
+
@field({ decoder: StringDecoder, optional: true })
|
|
21
|
+
afterId?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Return all payments that were paid after (and including) this date.
|
|
25
|
+
* Only returns orders **equal** to this date if afterId is not provided or if the id of those payments is also higher.
|
|
26
|
+
*/
|
|
27
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
28
|
+
paidSince?: Date
|
|
29
|
+
|
|
30
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
31
|
+
paidBefore?: Date
|
|
32
|
+
|
|
33
|
+
@field({ decoder: IntegerDecoder, optional: true })
|
|
34
|
+
limit?: number
|
|
35
|
+
|
|
36
|
+
@field({ decoder: new StringArrayDecoder(new EnumDecoder(PaymentMethod)), optional: true })
|
|
37
|
+
methods?: PaymentMethod[]
|
|
38
|
+
|
|
39
|
+
@field({ decoder: new StringArrayDecoder(new StringNullableDecoder(new EnumDecoder(PaymentProvider))), optional: true })
|
|
40
|
+
providers?: (PaymentProvider|null)[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
44
|
+
protected queryDecoder = Query;
|
|
45
|
+
|
|
46
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
47
|
+
if (request.method != "GET") {
|
|
48
|
+
return [false];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const params = Endpoint.parseParameters(request.url, "/organization/payments", {});
|
|
52
|
+
|
|
53
|
+
if (params) {
|
|
54
|
+
return [true, params as Params];
|
|
55
|
+
}
|
|
56
|
+
return [false];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
60
|
+
const organization = await Context.setOrganizationScope();
|
|
61
|
+
await Context.authenticate()
|
|
62
|
+
|
|
63
|
+
if (!await Context.auth.canManagePayments(organization.id)) {
|
|
64
|
+
throw Context.auth.error()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Response(
|
|
68
|
+
(await this.getPayments(organization, request.query))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getPayments(organization: Organization, query: Query) {
|
|
73
|
+
const paidSince = query.paidSince ?? new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
74
|
+
paidSince.setMilliseconds(0)
|
|
75
|
+
const payments: Payment[] = []
|
|
76
|
+
|
|
77
|
+
if (query.afterId) {
|
|
78
|
+
// First return all payments with id > afterId and paidAt == paidSince
|
|
79
|
+
payments.push(...await Payment.where({
|
|
80
|
+
organizationId: organization.id,
|
|
81
|
+
paidAt: {
|
|
82
|
+
sign: '=',
|
|
83
|
+
value: paidSince
|
|
84
|
+
},
|
|
85
|
+
id: {
|
|
86
|
+
sign: '>',
|
|
87
|
+
value: query.afterId ?? ""
|
|
88
|
+
},
|
|
89
|
+
method: {
|
|
90
|
+
sign: 'IN',
|
|
91
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
92
|
+
},
|
|
93
|
+
provider: {
|
|
94
|
+
sign: 'IN',
|
|
95
|
+
value: query.providers ?? [null]
|
|
96
|
+
}
|
|
97
|
+
}, {
|
|
98
|
+
limit: query.limit ?? undefined,
|
|
99
|
+
sort: [{
|
|
100
|
+
column: "id",
|
|
101
|
+
direction: "ASC"
|
|
102
|
+
}]
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
payments.push(...await Payment.where({
|
|
107
|
+
organizationId: organization.id,
|
|
108
|
+
paidAt: query.paidBefore ? [{
|
|
109
|
+
sign: query.afterId ? '>' : '>=',
|
|
110
|
+
value: paidSince
|
|
111
|
+
}, {
|
|
112
|
+
sign: '<=',
|
|
113
|
+
value: query.paidBefore
|
|
114
|
+
}] : {
|
|
115
|
+
sign: query.afterId ? '>' : '>=',
|
|
116
|
+
value: paidSince
|
|
117
|
+
},
|
|
118
|
+
method: {
|
|
119
|
+
sign: 'IN',
|
|
120
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
121
|
+
},
|
|
122
|
+
provider: {
|
|
123
|
+
sign: 'IN',
|
|
124
|
+
value: query.providers ?? [null]
|
|
125
|
+
}
|
|
126
|
+
}, {
|
|
127
|
+
limit: query.limit ? (query.limit - payments.length) : undefined,
|
|
128
|
+
sort: [{
|
|
129
|
+
column: "paidAt",
|
|
130
|
+
direction: "ASC"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
column: "id",
|
|
134
|
+
direction: "ASC"
|
|
135
|
+
}]
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (!query.paidSince && !query.methods && !query.providers) {
|
|
140
|
+
// Default behaviour is to return all not-paid transfer payments that are not yet paid
|
|
141
|
+
|
|
142
|
+
payments.push(...
|
|
143
|
+
await Payment.where({
|
|
144
|
+
organizationId: organization.id,
|
|
145
|
+
paidAt: null,
|
|
146
|
+
method: PaymentMethod.Transfer,
|
|
147
|
+
status: {
|
|
148
|
+
sign: '!=',
|
|
149
|
+
value: PaymentStatus.Failed
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
payments.push(...
|
|
155
|
+
await Payment.where({
|
|
156
|
+
organizationId: organization.id,
|
|
157
|
+
paidAt: null,
|
|
158
|
+
updatedAt: {
|
|
159
|
+
sign: '>',
|
|
160
|
+
value: new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
161
|
+
},
|
|
162
|
+
method: PaymentMethod.Transfer,
|
|
163
|
+
status: PaymentStatus.Failed
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return await AuthenticatedStructures.paymentsGeneral(payments, true)
|
|
169
|
+
}
|
|
170
|
+
}
|