@stamhoofd/backend 2.39.0 → 2.40.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/eslint.config.mjs +5 -0
- package/index.ts +81 -74
- package/jest.config.cjs +10 -0
- package/migrations.ts +16 -14
- package/package.json +11 -11
- package/src/crons/clear-excel-cache.test.ts +48 -50
- package/src/crons/clear-excel-cache.ts +18 -18
- package/src/crons/setup-steps.ts +2 -2
- package/src/crons.ts +325 -306
- package/src/decoders/StringArrayDecoder.ts +7 -7
- package/src/decoders/StringNullableDecoder.ts +1 -2
- package/src/email-recipient-loaders/members.ts +22 -22
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
- package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
- package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
- package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
- package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
- package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
- package/src/endpoints/auth/SignupEndpoint.ts +37 -36
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
- package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
- package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
- package/src/endpoints/global/files/GetFileCache.ts +13 -13
- package/src/endpoints/global/files/UploadFile.ts +51 -54
- package/src/endpoints/global/files/UploadImage.ts +53 -53
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
- package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
- package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
- package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
- package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
- package/src/excel-loaders/members.ts +102 -103
- package/src/excel-loaders/payments.ts +155 -156
- package/src/helpers/AddressValidator.test.ts +32 -32
- package/src/helpers/AddressValidator.ts +128 -122
- package/src/helpers/AdminPermissionChecker.ts +339 -236
- package/src/helpers/AuthenticatedStructures.ts +233 -134
- package/src/helpers/BuckarooHelper.ts +134 -134
- package/src/helpers/CheckSettlements.ts +94 -88
- package/src/helpers/Context.ts +87 -86
- package/src/helpers/CookieHelper.ts +23 -22
- package/src/helpers/EmailResumer.ts +10 -10
- package/src/helpers/FileCache.ts +62 -62
- package/src/helpers/ForwardHandler.test.ts +122 -124
- package/src/helpers/ForwardHandler.ts +76 -70
- package/src/helpers/MemberUserSyncer.ts +101 -96
- package/src/helpers/MembershipCharger.ts +69 -69
- package/src/helpers/MembershipHelper.ts +11 -12
- package/src/helpers/OpenIDConnectHelper.ts +85 -82
- package/src/helpers/PeriodHelper.ts +65 -70
- package/src/helpers/StripeHelper.ts +146 -137
- package/src/helpers/StripePayoutChecker.ts +51 -52
- package/src/helpers/ViesHelper.ts +46 -44
- package/src/helpers/fetchToAsyncIterator.ts +14 -14
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
- package/src/middleware/ContextMiddleware.ts +5 -5
- package/src/migrations/1646578856-validate-addresses.ts +6 -9
- package/src/seeds/0000000000-example.ts +3 -5
- package/src/seeds/1715028563-user-permissions.ts +16 -18
- package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
- package/src/seeds/1722344162-sync-member-users.ts +14 -15
- package/src/seeds/1722344162-update-membership.ts +6 -6
- package/src/seeds/1726055544-balance-item-paid.ts +4 -4
- package/src/seeds/1726055545-balance-item-pending.ts +4 -4
- package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
- package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
- package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
- package/src/seeds/1726847064-setup-steps.ts +16 -0
- package/src/sql-filters/balance-item-payments.ts +7 -7
- package/src/sql-filters/events.ts +14 -14
- package/src/sql-filters/members.ts +96 -96
- package/src/sql-filters/organizations.ts +139 -75
- package/src/sql-filters/payments.ts +28 -28
- package/src/sql-filters/registrations.ts +14 -14
- package/src/sql-sorters/events.ts +25 -25
- package/src/sql-sorters/members.ts +26 -26
- package/src/sql-sorters/organizations.ts +36 -36
- package/src/sql-sorters/payments.ts +26 -26
- package/tests/e2e/stock.test.ts +616 -621
- package/tests/e2e/tickets.test.ts +255 -260
- package/tests/helpers/StripeMocker.ts +177 -179
- package/tests/helpers/TestServer.ts +9 -9
- package/tests/jest.global.setup.ts +14 -13
- package/tests/jest.setup.ts +33 -32
- package/.eslintrc.js +0 -61
- package/jest.config.js +0 -11
- package/src/helpers/SetupStepsUpdater.ts +0 -359
- package/src/seeds/1724076679-setup-steps.ts +0 -16
|
@@ -1,38 +1,37 @@
|
|
|
1
|
-
import {Order, Payment, StripeCheckoutSession, StripePaymentIntent} from
|
|
2
|
-
import { Settlement } from
|
|
3
|
-
import Stripe from
|
|
1
|
+
import { Order, Payment, StripeCheckoutSession, StripePaymentIntent } from '@stamhoofd/models';
|
|
2
|
+
import { Settlement } from '@stamhoofd/structures';
|
|
3
|
+
import Stripe from 'stripe';
|
|
4
4
|
|
|
5
5
|
export class StripePayoutChecker {
|
|
6
6
|
private stripe: Stripe;
|
|
7
7
|
private stripePlatform: Stripe;
|
|
8
8
|
|
|
9
|
-
constructor({secretKey, stripeAccount}: { secretKey: string
|
|
9
|
+
constructor({ secretKey, stripeAccount }: { secretKey: string; stripeAccount?: string }) {
|
|
10
10
|
this.stripe = new Stripe(
|
|
11
11
|
secretKey, {
|
|
12
|
-
apiVersion: '2024-06-20',
|
|
13
|
-
typescript: true,
|
|
14
|
-
maxNetworkRetries: 1,
|
|
12
|
+
apiVersion: '2024-06-20',
|
|
13
|
+
typescript: true,
|
|
14
|
+
maxNetworkRetries: 1,
|
|
15
15
|
timeout: 10000,
|
|
16
|
-
stripeAccount
|
|
17
|
-
|
|
16
|
+
stripeAccount,
|
|
17
|
+
});
|
|
18
18
|
|
|
19
19
|
this.stripePlatform = new Stripe(
|
|
20
20
|
secretKey, {
|
|
21
|
-
apiVersion: '2024-06-20',
|
|
22
|
-
typescript: true,
|
|
23
|
-
maxNetworkRetries: 1,
|
|
24
|
-
timeout: 10000
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
apiVersion: '2024-06-20',
|
|
22
|
+
typescript: true,
|
|
23
|
+
maxNetworkRetries: 1,
|
|
24
|
+
timeout: 10000,
|
|
25
|
+
});
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async checkSettlements(checkAll = false) {
|
|
30
|
-
|
|
31
|
-
const d = new Date()
|
|
32
|
-
d.setDate(d.getDate() - 17)
|
|
29
|
+
// Check last 2 weeks + 3 day margin, unless we check them all
|
|
30
|
+
const d = new Date();
|
|
31
|
+
d.setDate(d.getDate() - 17);
|
|
33
32
|
|
|
34
33
|
if (checkAll) {
|
|
35
|
-
d.setFullYear(2022, 11, 1)
|
|
34
|
+
d.setFullYear(2022, 11, 1);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
// Loop all payouts
|
|
@@ -41,26 +40,26 @@ export class StripePayoutChecker {
|
|
|
41
40
|
for await (const payout of this.stripe.payouts.list({
|
|
42
41
|
status: 'paid',
|
|
43
42
|
arrival_date: {
|
|
44
|
-
gte: Math.floor(d.getTime() / 1000)
|
|
45
|
-
}
|
|
43
|
+
gte: Math.floor(d.getTime() / 1000),
|
|
44
|
+
},
|
|
46
45
|
})) {
|
|
47
46
|
// Get all payments for this payout
|
|
48
47
|
await this.fetchBalanceItems(payout);
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.error(e)
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
console.error(e);
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
private async fetchBalanceItems(payout: Stripe.Payout) {
|
|
57
56
|
// For the given payout, fetch all balance items
|
|
58
57
|
const params = {
|
|
59
|
-
payout: payout.id,
|
|
58
|
+
payout: payout.id,
|
|
60
59
|
// Via the Application Fee object, we can get the original payment metadata
|
|
61
60
|
expand: ['data.source', 'data.source.application_fee', 'data.source.application_fee.originating_transaction'],
|
|
62
61
|
// TODO: ALSO DO CARDS! (type: 'charge')
|
|
63
|
-
//type: 'payment'
|
|
62
|
+
// type: 'payment'
|
|
64
63
|
};
|
|
65
64
|
|
|
66
65
|
for await (const balanceItem of this.stripe.balanceTransactions.list(params)) {
|
|
@@ -79,7 +78,7 @@ export class StripePayoutChecker {
|
|
|
79
78
|
return;
|
|
80
79
|
}
|
|
81
80
|
if (balanceItem.source.object !== 'charge') {
|
|
82
|
-
console.log(
|
|
81
|
+
console.log('No payment id set for charge ' + balanceItem.source.id);
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
84
|
|
|
@@ -99,67 +98,68 @@ export class StripePayoutChecker {
|
|
|
99
98
|
// Try to look it up by payment intent id
|
|
100
99
|
|
|
101
100
|
if (originatingTransaction.payment_intent) {
|
|
102
|
-
const paymentIntentId = typeof originatingTransaction.payment_intent === 'string' ? originatingTransaction.payment_intent : originatingTransaction.payment_intent.id
|
|
101
|
+
const paymentIntentId = typeof originatingTransaction.payment_intent === 'string' ? originatingTransaction.payment_intent : originatingTransaction.payment_intent.id;
|
|
103
102
|
const stripePayments = await StripePaymentIntent.where({
|
|
104
|
-
stripeIntentId: paymentIntentId
|
|
105
|
-
}, {limit: 1});
|
|
103
|
+
stripeIntentId: paymentIntentId,
|
|
104
|
+
}, { limit: 1 });
|
|
106
105
|
|
|
107
106
|
if (stripePayments.length === 1) {
|
|
108
107
|
paymentId = stripePayments[0].paymentId;
|
|
109
|
-
console.log(
|
|
110
|
-
}
|
|
108
|
+
console.log('Found missing payment metadata for payment intent ', originatingTransaction.payment_intent, paymentId);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
111
|
// Probably a card payment
|
|
112
112
|
// Search for the checkout session
|
|
113
113
|
const checkoutSession = await this.stripePlatform.checkout.sessions.list({
|
|
114
|
-
payment_intent: paymentIntentId
|
|
115
|
-
})
|
|
114
|
+
payment_intent: paymentIntentId,
|
|
115
|
+
});
|
|
116
116
|
if (checkoutSession.data.length === 1) {
|
|
117
117
|
const session = checkoutSession.data[0];
|
|
118
|
-
console.log(
|
|
118
|
+
console.log('Found checkout session for payment intent ', paymentIntentId, session);
|
|
119
119
|
|
|
120
120
|
// Search
|
|
121
121
|
const stripeCheckoutSessions = await StripeCheckoutSession.where({
|
|
122
|
-
stripeSessionId: session.id
|
|
123
|
-
}, {limit: 1});
|
|
122
|
+
stripeSessionId: session.id,
|
|
123
|
+
}, { limit: 1 });
|
|
124
124
|
|
|
125
125
|
if (stripeCheckoutSessions.length === 1) {
|
|
126
126
|
paymentId = stripeCheckoutSessions[0].paymentId;
|
|
127
|
-
console.log(
|
|
128
|
-
}
|
|
129
|
-
|
|
127
|
+
console.log('Found missing payment metadata for payment intent ', originatingTransaction.payment_intent, paymentId);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log('No payment found for checkout session ' + session.id);
|
|
130
131
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log('No Stripe Checkout Sessions found for payment intent ' + paymentIntentId);
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if (!paymentId) {
|
|
144
|
-
console.log(balanceItem)
|
|
145
|
-
console.log(
|
|
144
|
+
console.log(balanceItem);
|
|
145
|
+
console.log('No payment id set for charge ' + balanceItem.source.id);
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const applicationFee = balanceItem.source.application_fee_amount;
|
|
150
|
-
const otherFees = balanceItem.fee
|
|
150
|
+
const otherFees = balanceItem.fee;
|
|
151
151
|
const totalFees = otherFees + (applicationFee ?? 0);
|
|
152
152
|
|
|
153
153
|
// Cool, we can store this in the database now.
|
|
154
154
|
|
|
155
155
|
const payment = await Payment.getByID(paymentId);
|
|
156
156
|
if (!payment) {
|
|
157
|
-
console.log(
|
|
157
|
+
console.log('Invalid payment id set for charge ' + balanceItem.source.id + ': ' + paymentId);
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
if (payment.price !== balanceItem.amount) {
|
|
162
|
-
console.log(
|
|
162
|
+
console.log('Amount mismatch for payment ' + payment.id + ': ' + payment.price + ' !== ' + balanceItem.amount);
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -169,7 +169,7 @@ export class StripePayoutChecker {
|
|
|
169
169
|
settledAt: new Date(payout.arrival_date * 1000),
|
|
170
170
|
amount: payout.amount,
|
|
171
171
|
// Set only if application fee is witheld
|
|
172
|
-
fee: totalFees
|
|
172
|
+
fee: totalFees,
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
payment.settlement = settlement;
|
|
@@ -177,7 +177,7 @@ export class StripePayoutChecker {
|
|
|
177
177
|
|
|
178
178
|
// Force an updatedAt timestamp of the related order
|
|
179
179
|
// Mark order as 'updated', or the frontend won't pull in the updates
|
|
180
|
-
const order = await Order.getForPayment(null, payment.id)
|
|
180
|
+
const order = await Order.getForPayment(null, payment.id);
|
|
181
181
|
if (order) {
|
|
182
182
|
order.updatedAt = new Date();
|
|
183
183
|
order.forceSaveProperty('updatedAt');
|
|
@@ -186,5 +186,4 @@ export class StripePayoutChecker {
|
|
|
186
186
|
|
|
187
187
|
await payment.save();
|
|
188
188
|
}
|
|
189
|
-
|
|
190
189
|
}
|
|
@@ -7,26 +7,25 @@ import * as jsvat from 'jsvat-next'; // has no default export, so we need the wi
|
|
|
7
7
|
export class ViesHelperStatic {
|
|
8
8
|
testMode = false;
|
|
9
9
|
|
|
10
|
-
async request(method:
|
|
10
|
+
async request(method: 'GET' | 'POST', url: string, content: any) {
|
|
11
|
+
const json = content ? JSON.stringify(content) : '';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.log("[VIES REQUEST]", method, url, content ? "\n [VIES REQUEST] " : undefined, json)
|
|
13
|
+
console.log('[VIES REQUEST]', method, url, content ? '\n [VIES REQUEST] ' : undefined, json);
|
|
15
14
|
|
|
16
15
|
const response = await axios.request({
|
|
17
16
|
method,
|
|
18
17
|
url,
|
|
19
18
|
headers: {
|
|
20
|
-
'Content-Type': json.length > 0 ? 'application/json' :
|
|
19
|
+
'Content-Type': json.length > 0 ? 'application/json' : 'text/plain',
|
|
21
20
|
},
|
|
22
|
-
data: json
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
console.log(
|
|
26
|
-
return response.data
|
|
21
|
+
data: json,
|
|
22
|
+
|
|
23
|
+
});
|
|
24
|
+
console.log('[VIES RESPONSE]', method, url, '\n[VIES RESPONSE]', JSON.stringify(response.data));
|
|
25
|
+
return response.data;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
async checkCompany(company: Company, patch: AutoEncoderPatchType<Company
|
|
28
|
+
async checkCompany(company: Company, patch: AutoEncoderPatchType<Company> | Company) {
|
|
30
29
|
if (!company.address) {
|
|
31
30
|
// Not allowed to set
|
|
32
31
|
patch.companyNumber = null;
|
|
@@ -36,32 +35,33 @@ export class ViesHelperStatic {
|
|
|
36
35
|
|
|
37
36
|
if (company.VATNumber !== null) {
|
|
38
37
|
// Changed VAT number
|
|
39
|
-
patch.VATNumber = await ViesHelper.checkVATNumber(company.address.country, company.VATNumber)
|
|
38
|
+
patch.VATNumber = await ViesHelper.checkVATNumber(company.address.country, company.VATNumber);
|
|
40
39
|
|
|
41
40
|
if (company.address.country === Country.Belgium) {
|
|
42
|
-
patch.companyNumber = company.VATNumber
|
|
41
|
+
patch.companyNumber = company.VATNumber;
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
if (company.companyNumber) {
|
|
47
46
|
if (company.VATNumber !== null && company.address.country === Country.Belgium) {
|
|
48
47
|
// Already validated
|
|
49
|
-
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
50
|
// Need to validate
|
|
51
|
-
const result = await ViesHelper.checkCompanyNumber(company.address.country, company.companyNumber)
|
|
52
|
-
patch.companyNumber = result.companyNumber
|
|
51
|
+
const result = await ViesHelper.checkCompanyNumber(company.address.country, company.companyNumber);
|
|
52
|
+
patch.companyNumber = result.companyNumber;
|
|
53
53
|
if (result.VATNumber !== undefined) {
|
|
54
|
-
patch.VATNumber = result.VATNumber
|
|
54
|
+
patch.VATNumber = result.VATNumber;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async checkCompanyNumber(country: Country, companyNumber: string): Promise<{companyNumber: string|null
|
|
60
|
+
async checkCompanyNumber(country: Country, companyNumber: string): Promise<{ companyNumber: string | null; VATNumber?: string | null }> {
|
|
61
61
|
if (country !== Country.Belgium) {
|
|
62
62
|
// Not supported
|
|
63
63
|
return {
|
|
64
|
-
companyNumber
|
|
64
|
+
companyNumber,
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -71,10 +71,10 @@ export class ViesHelperStatic {
|
|
|
71
71
|
|
|
72
72
|
if (!result.isValid) {
|
|
73
73
|
throw new SimpleError({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
74
|
+
code: 'invalid_field',
|
|
75
|
+
message: 'Ongeldig ondernemingsnummer: ' + companyNumber,
|
|
76
|
+
field: 'companyNumber',
|
|
77
|
+
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// If this is a valid VAT number, we can assume it's a valid company number
|
|
@@ -84,12 +84,14 @@ export class ViesHelperStatic {
|
|
|
84
84
|
// this is a VAT number, not a company number
|
|
85
85
|
return {
|
|
86
86
|
companyNumber: corrected,
|
|
87
|
-
VATNumber: corrected
|
|
88
|
-
}
|
|
89
|
-
}
|
|
87
|
+
VATNumber: corrected,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
90
91
|
if (isSimpleError(e) || isSimpleErrors(e)) {
|
|
91
92
|
// Ignore: normal that it is not a valid VAT number
|
|
92
|
-
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
93
95
|
// Other errors should be thrown
|
|
94
96
|
throw e;
|
|
95
97
|
}
|
|
@@ -99,7 +101,7 @@ export class ViesHelperStatic {
|
|
|
99
101
|
companyNumber: result.value ?? companyNumber,
|
|
100
102
|
|
|
101
103
|
// VATNumber should always be set to null if it is not a valid VAT number
|
|
102
|
-
VATNumber: null
|
|
104
|
+
VATNumber: null,
|
|
103
105
|
};
|
|
104
106
|
}
|
|
105
107
|
|
|
@@ -108,34 +110,35 @@ export class ViesHelperStatic {
|
|
|
108
110
|
|
|
109
111
|
if (!result.isValid) {
|
|
110
112
|
throw new SimpleError({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
}
|
|
113
|
+
code: 'invalid_field',
|
|
114
|
+
message: 'Ongeldig BTW-nummer: ' + vatNumber,
|
|
115
|
+
field: 'VATNumber',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
116
118
|
|
|
117
119
|
const formatted = result.value ?? vatNumber;
|
|
118
120
|
|
|
119
121
|
try {
|
|
120
|
-
const cleaned = formatted.substring(2).replace(/(
|
|
121
|
-
const response = await this.request(
|
|
122
|
+
const cleaned = formatted.substring(2).replace(/(\.-\s)+/g, '');
|
|
123
|
+
const response = await this.request('POST', 'https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number', {
|
|
122
124
|
countryCode: country,
|
|
123
|
-
vatNumber: cleaned
|
|
125
|
+
vatNumber: cleaned,
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
if (typeof response !== 'object' || response === null || typeof response.valid !== 'boolean') {
|
|
127
129
|
// APi error
|
|
128
|
-
throw new Error(
|
|
130
|
+
throw new Error('Invalid response from VIES');
|
|
129
131
|
}
|
|
130
|
-
|
|
132
|
+
|
|
131
133
|
if (!response.valid) {
|
|
132
134
|
throw new SimpleError({
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
135
|
+
code: 'invalid_field',
|
|
136
|
+
message: 'Het opgegeven BTW-nummer is ongeldig of niet BTW-plichtig: ' + formatted,
|
|
137
|
+
field: 'VATNumber',
|
|
138
|
+
});
|
|
137
139
|
}
|
|
138
|
-
}
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
139
142
|
if (isSimpleError(e) || isSimpleErrors(e)) {
|
|
140
143
|
throw e;
|
|
141
144
|
}
|
|
@@ -145,7 +148,6 @@ export class ViesHelperStatic {
|
|
|
145
148
|
|
|
146
149
|
return formatted;
|
|
147
150
|
}
|
|
148
|
-
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
export const ViesHelper = new ViesHelperStatic();
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { IPaginatedResponse, LimitedFilteredRequest } from
|
|
1
|
+
import { IPaginatedResponse, LimitedFilteredRequest } from '@stamhoofd/structures';
|
|
2
2
|
|
|
3
3
|
export function fetchToAsyncIterator<T>(
|
|
4
|
-
initialFilter: LimitedFilteredRequest,
|
|
4
|
+
initialFilter: LimitedFilteredRequest,
|
|
5
5
|
loader: {
|
|
6
|
-
fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T, LimitedFilteredRequest
|
|
7
|
-
}
|
|
6
|
+
fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T, LimitedFilteredRequest>>;
|
|
7
|
+
},
|
|
8
8
|
): AsyncIterable<T> {
|
|
9
9
|
return {
|
|
10
10
|
[Symbol.asyncIterator]: function () {
|
|
11
|
-
let request: LimitedFilteredRequest|null = initialFilter
|
|
11
|
+
let request: LimitedFilteredRequest | null = initialFilter;
|
|
12
12
|
|
|
13
13
|
return {
|
|
14
14
|
async next(): Promise<IteratorResult<T, undefined>> {
|
|
15
15
|
if (!request) {
|
|
16
16
|
return {
|
|
17
17
|
done: true,
|
|
18
|
-
value: undefined
|
|
19
|
-
}
|
|
18
|
+
value: undefined,
|
|
19
|
+
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const response = await loader.fetch(request);
|
|
23
23
|
request = response.next ?? null;
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
return {
|
|
26
26
|
done: false,
|
|
27
|
-
value: response.results
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
27
|
+
value: response.results,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
33
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { XlsxTransformerColumn } from
|
|
2
|
-
import { Address, CountryHelper, MemberWithRegistrationsBlob, Parent, ParentTypeHelper } from
|
|
1
|
+
import { XlsxTransformerColumn } from '@stamhoofd/excel-writer';
|
|
2
|
+
import { Address, CountryHelper, MemberWithRegistrationsBlob, Parent, ParentTypeHelper } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
4
|
export class XlsxTransformerColumnHelper {
|
|
5
5
|
static formatBoolean(value: boolean | undefined | null): string {
|
|
6
|
-
if(value === true) {
|
|
7
|
-
return
|
|
6
|
+
if (value === true) {
|
|
7
|
+
return 'Ja';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
if(value === false) {
|
|
11
|
-
return 'Nee'
|
|
10
|
+
if (value === false) {
|
|
11
|
+
return 'Nee';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
return '';
|
|
@@ -18,13 +18,13 @@ export class XlsxTransformerColumnHelper {
|
|
|
18
18
|
return [
|
|
19
19
|
...this.createColumnsForParent(0),
|
|
20
20
|
...this.createColumnsForParent(1),
|
|
21
|
-
]
|
|
21
|
+
];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
static createColumnsForAddresses<T>({limit, getAddresses, matchIdStart, identifier}: {limit: number
|
|
24
|
+
static createColumnsForAddresses<T>({ limit, getAddresses, matchIdStart, identifier }: { limit: number; getAddresses: (object: T) => Address[]; matchIdStart: string; identifier: string }): XlsxTransformerColumn<unknown>[] {
|
|
25
25
|
const result: XlsxTransformerColumn<unknown>[] = [];
|
|
26
26
|
|
|
27
|
-
for(let i = 0; i <= limit; i++) {
|
|
27
|
+
for (let i = 0; i <= limit; i++) {
|
|
28
28
|
const column = this.createAddressColumns({
|
|
29
29
|
matchId: `${matchIdStart}.${i}`,
|
|
30
30
|
getAddress: (object: T) => getAddresses(object)[i],
|
|
@@ -38,13 +38,13 @@ export class XlsxTransformerColumnHelper {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
static createColumnsForParent(parentIndex: number): XlsxTransformerColumn<unknown>[] {
|
|
41
|
-
const getParent = (member: MemberWithRegistrationsBlob): Parent | null | undefined => member.details.parents[parentIndex];
|
|
41
|
+
const getParent = (member: MemberWithRegistrationsBlob): Parent | null | undefined => member.details.parents[parentIndex];
|
|
42
42
|
|
|
43
43
|
const parentNumber = parentIndex + 1;
|
|
44
44
|
|
|
45
|
-
const identifier = `Ouder ${parentNumber}
|
|
45
|
+
const identifier = `Ouder ${parentNumber}`;
|
|
46
46
|
const getId = (value: string) => `parent.${parentIndex}.${value}`;
|
|
47
|
-
const getName = (value: string) => `${identifier} - ${value}
|
|
47
|
+
const getName = (value: string) => `${identifier} - ${value}`;
|
|
48
48
|
|
|
49
49
|
return [
|
|
50
50
|
{
|
|
@@ -55,61 +55,61 @@ export class XlsxTransformerColumnHelper {
|
|
|
55
55
|
const parent = getParent(member);
|
|
56
56
|
|
|
57
57
|
return {
|
|
58
|
-
value: parent ? ParentTypeHelper.getName(parent.type) : ''
|
|
59
|
-
}
|
|
60
|
-
}
|
|
58
|
+
value: parent ? ParentTypeHelper.getName(parent.type) : '',
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
id: getId('firstName'),
|
|
64
64
|
name: getName('Voornaam'),
|
|
65
65
|
width: 20,
|
|
66
66
|
getValue: (member: MemberWithRegistrationsBlob) => ({
|
|
67
|
-
value: getParent(member)?.firstName ?? ''
|
|
68
|
-
})
|
|
67
|
+
value: getParent(member)?.firstName ?? '',
|
|
68
|
+
}),
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
id: getId('lastName'),
|
|
72
72
|
name: getName('Achternaam'),
|
|
73
73
|
width: 20,
|
|
74
74
|
getValue: (member: MemberWithRegistrationsBlob) => ({
|
|
75
|
-
value: getParent(member)?.lastName ?? ''
|
|
76
|
-
})
|
|
75
|
+
value: getParent(member)?.lastName ?? '',
|
|
76
|
+
}),
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
id: getId('phone'),
|
|
80
80
|
name: getName('Telefoonnummer'),
|
|
81
81
|
width: 20,
|
|
82
82
|
getValue: (member: MemberWithRegistrationsBlob) => ({
|
|
83
|
-
value: getParent(member)?.phone ?? ''
|
|
84
|
-
})
|
|
83
|
+
value: getParent(member)?.phone ?? '',
|
|
84
|
+
}),
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
id: getId('email'),
|
|
88
88
|
name: getName('E-mailadres'),
|
|
89
89
|
width: 20,
|
|
90
90
|
getValue: (member: MemberWithRegistrationsBlob) => ({
|
|
91
|
-
value: getParent(member)?.email ?? ''
|
|
92
|
-
})
|
|
91
|
+
value: getParent(member)?.email ?? '',
|
|
92
|
+
}),
|
|
93
93
|
},
|
|
94
94
|
XlsxTransformerColumnHelper.createAddressColumns<MemberWithRegistrationsBlob>({
|
|
95
95
|
matchId: getId('address'),
|
|
96
|
-
getAddress:
|
|
97
|
-
identifier: getName('Adres')
|
|
96
|
+
getAddress: member => getParent(member)?.address,
|
|
97
|
+
identifier: getName('Adres'),
|
|
98
98
|
}),
|
|
99
|
-
]
|
|
99
|
+
];
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
static createAddressColumns<T>({matchId, identifier, getAddress}
|
|
102
|
+
static createAddressColumns<T>({ matchId, identifier, getAddress }: { matchId: string; identifier?: string; getAddress: (object: T) => Address | null | undefined }): XlsxTransformerColumn<T> {
|
|
103
103
|
const getId = (value: string) => matchId + '.' + value;
|
|
104
104
|
const identifierText = identifier ? `${identifier} - ` : '';
|
|
105
105
|
const getName = (value: string) => {
|
|
106
|
-
const name
|
|
106
|
+
const name = `${identifierText}${value}`;
|
|
107
107
|
return name[0].toUpperCase() + name.slice(1);
|
|
108
108
|
};
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
return {
|
|
111
111
|
match: (id) => {
|
|
112
|
-
if(id === matchId) {
|
|
112
|
+
if (id === matchId) {
|
|
113
113
|
return [
|
|
114
114
|
{
|
|
115
115
|
id: getId('street'),
|
|
@@ -118,9 +118,9 @@ export class XlsxTransformerColumnHelper {
|
|
|
118
118
|
getValue: (object: T) => {
|
|
119
119
|
const address = getAddress(object);
|
|
120
120
|
return {
|
|
121
|
-
value: address?.street || ''
|
|
122
|
-
}
|
|
123
|
-
}
|
|
121
|
+
value: address?.street || '',
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
id: getId('number'),
|
|
@@ -129,9 +129,9 @@ export class XlsxTransformerColumnHelper {
|
|
|
129
129
|
getValue: (object: T) => {
|
|
130
130
|
const address = getAddress(object);
|
|
131
131
|
return {
|
|
132
|
-
value: address?.number || ''
|
|
133
|
-
}
|
|
134
|
-
}
|
|
132
|
+
value: address?.number || '',
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
id: getId('postalCode'),
|
|
@@ -140,9 +140,9 @@ export class XlsxTransformerColumnHelper {
|
|
|
140
140
|
getValue: (object: T) => {
|
|
141
141
|
const address = getAddress(object);
|
|
142
142
|
return {
|
|
143
|
-
value: address?.postalCode || ''
|
|
144
|
-
}
|
|
145
|
-
}
|
|
143
|
+
value: address?.postalCode || '',
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
id: getId('city'),
|
|
@@ -151,9 +151,9 @@ export class XlsxTransformerColumnHelper {
|
|
|
151
151
|
getValue: (object: T) => {
|
|
152
152
|
const address = getAddress(object);
|
|
153
153
|
return {
|
|
154
|
-
value: address?.city || ''
|
|
155
|
-
}
|
|
156
|
-
}
|
|
154
|
+
value: address?.city || '',
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
id: getId('country'),
|
|
@@ -163,15 +163,13 @@ export class XlsxTransformerColumnHelper {
|
|
|
163
163
|
const address = getAddress(object);
|
|
164
164
|
const country = address?.country;
|
|
165
165
|
return {
|
|
166
|
-
value: country ? CountryHelper.getName(country) : ''
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
]
|
|
166
|
+
value: country ? CountryHelper.getName(country) : '',
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
171
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
177
175
|
}
|