@develit-services/bank 0.0.3 → 0.0.4
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/dist/@types.cjs +29 -0
- package/dist/@types.d.cts +445 -0
- package/dist/@types.d.mts +4 -4
- package/dist/@types.d.ts +4 -4
- package/dist/database/schema.cjs +12 -0
- package/dist/database/schema.d.cts +4 -0
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/export/worker.cjs +1224 -0
- package/dist/export/worker.d.cts +243 -0
- package/dist/export/worker.d.mts +7 -7
- package/dist/export/worker.d.ts +10 -8
- package/dist/export/wrangler.cjs +131 -0
- package/dist/export/wrangler.d.cts +59 -0
- package/dist/shared/bank.BU2_AKRG.cjs +724 -0
- package/dist/shared/{bank.DqT4zUJn.d.mts → bank.C28GtA5s.d.cts} +1 -1
- package/dist/shared/{bank.dxpKOn1t.d.mts → bank.CBNQZ5Pd.d.cts} +6 -6
- package/dist/shared/{bank.dxpKOn1t.d.ts → bank.CBNQZ5Pd.d.mts} +6 -6
- package/dist/shared/bank.CBNQZ5Pd.d.ts +785 -0
- package/dist/shared/bank.CrAQe4PH.cjs +72 -0
- package/dist/shared/bank.DxGqqFhD.d.cts +36 -0
- package/dist/shared/bank.LCrZu9Ic.d.mts +98 -0
- package/dist/shared/{bank.B72_bFlr.d.ts → bank.atHmEWmc.d.ts} +1 -1
- package/package.json +9 -5
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
|
+
const database_schema = require('./bank.CrAQe4PH.cjs');
|
|
5
|
+
const drizzleOrm = require('drizzle-orm');
|
|
6
|
+
const dateFns = require('date-fns');
|
|
7
|
+
const jose = require('jose');
|
|
8
|
+
const generalCodes = require('@develit-io/general-codes');
|
|
9
|
+
const zod = require('zod');
|
|
10
|
+
|
|
11
|
+
const tables = database_schema.schema;
|
|
12
|
+
|
|
13
|
+
const PAYMENT_TYPES = ["SEPA", "SWIFT", "IFSC", "DOMESTIC"];
|
|
14
|
+
const PAYMENT_STATUSES = [
|
|
15
|
+
"PREPARED",
|
|
16
|
+
"INITIALIZED",
|
|
17
|
+
"FAILED",
|
|
18
|
+
"PENDING",
|
|
19
|
+
"COMPLETED",
|
|
20
|
+
"CREATED"
|
|
21
|
+
];
|
|
22
|
+
const PAYMENT_DIRECTIONS = ["INCOMING", "OUTGOING"];
|
|
23
|
+
const BATCH_STATUSES = [
|
|
24
|
+
"OPEN",
|
|
25
|
+
"WAITING_FOR_PROCESSING",
|
|
26
|
+
"PROCESSING",
|
|
27
|
+
"READY_TO_SIGN",
|
|
28
|
+
"PREPARED",
|
|
29
|
+
"COMPLETED",
|
|
30
|
+
"FAILED"
|
|
31
|
+
];
|
|
32
|
+
const COUNTRY_CODES = generalCodes.COUNTRY_CODES_2;
|
|
33
|
+
|
|
34
|
+
class IBankConnector {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isDeposit = (payment, creditorIban) => {
|
|
38
|
+
return payment.creditorIban === creditorIban;
|
|
39
|
+
};
|
|
40
|
+
const getPaymentDirection = (payment, iban) => {
|
|
41
|
+
if (isDeposit(payment, iban)) return "INCOMING";
|
|
42
|
+
return "OUTGOING";
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const mapReferencesToPayment = (reference) => {
|
|
46
|
+
const symbols = {
|
|
47
|
+
vs: void 0,
|
|
48
|
+
ss: void 0,
|
|
49
|
+
ks: void 0
|
|
50
|
+
};
|
|
51
|
+
if (!reference) return symbols;
|
|
52
|
+
if (Array.isArray(reference) && reference.length > 0) {
|
|
53
|
+
symbols.vs = reference.find((ref) => ref.includes("VS")) || void 0;
|
|
54
|
+
symbols.ss = reference.find((ref) => ref.includes("SS")) || void 0;
|
|
55
|
+
symbols.ks = reference.find((ref) => ref.includes("KS")) || void 0;
|
|
56
|
+
}
|
|
57
|
+
if (typeof reference === "string") {
|
|
58
|
+
const vsMatch = reference.match(/VS[:\s]*(\d+)/i);
|
|
59
|
+
const ssMatch = reference.match(/SS[:\s]*(\d+)/i);
|
|
60
|
+
const ksMatch = reference.match(/KS[:\s]*(\d+)/i);
|
|
61
|
+
if (vsMatch) symbols.vs = vsMatch[1];
|
|
62
|
+
if (ssMatch) symbols.ss = ssMatch[1];
|
|
63
|
+
if (ksMatch) symbols.ks = ksMatch[1];
|
|
64
|
+
}
|
|
65
|
+
return symbols;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
class ErsteConnector extends IBankConnector {
|
|
69
|
+
constructor(config) {
|
|
70
|
+
super();
|
|
71
|
+
this.connectorKey = "ERSTE";
|
|
72
|
+
this.accessToken = null;
|
|
73
|
+
this.API_KEY = config.API_KEY;
|
|
74
|
+
this.CLIENT_ID = config.CLIENT_ID;
|
|
75
|
+
this.CLIENT_SECRET = config.CLIENT_SECRET;
|
|
76
|
+
this.REDIRECT_URI = config.REDIRECT_URI;
|
|
77
|
+
this.AUTH_URI = config.AUTH_URI;
|
|
78
|
+
this.PAYMENTS_URI = config.PAYMENTS_URI;
|
|
79
|
+
this.ACCOUNTS_URI = config.ACCOUNTS_URI;
|
|
80
|
+
}
|
|
81
|
+
static {
|
|
82
|
+
this.FETCH_INTERVAL = 60 * 60 * 5;
|
|
83
|
+
}
|
|
84
|
+
get adminCodeCreationURI() {
|
|
85
|
+
const params = new URLSearchParams({
|
|
86
|
+
redirect_uri: `${this.REDIRECT_URI}`,
|
|
87
|
+
client_id: this.CLIENT_ID,
|
|
88
|
+
response_type: "code",
|
|
89
|
+
access_type: "offline",
|
|
90
|
+
state: "csas-auth",
|
|
91
|
+
prompt: "consent"
|
|
92
|
+
});
|
|
93
|
+
return `${this.AUTH_URI}/auth?${params.toString()}`;
|
|
94
|
+
}
|
|
95
|
+
async authenticate({ token, refreshToken }) {
|
|
96
|
+
const grantType = refreshToken ? "refresh_token" : "authorization_code";
|
|
97
|
+
let bodyParams = {
|
|
98
|
+
client_id: this.CLIENT_ID,
|
|
99
|
+
client_secret: this.CLIENT_SECRET,
|
|
100
|
+
grant_type: grantType
|
|
101
|
+
};
|
|
102
|
+
if (token) {
|
|
103
|
+
bodyParams = {
|
|
104
|
+
...bodyParams,
|
|
105
|
+
code: token,
|
|
106
|
+
redirect_uri: this.REDIRECT_URI
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (refreshToken) bodyParams.refresh_token = refreshToken;
|
|
110
|
+
const body = new URLSearchParams(bodyParams);
|
|
111
|
+
const result = await fetch(`${this.AUTH_URI}/token`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
body: body.toString(),
|
|
114
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
115
|
+
});
|
|
116
|
+
if (result.status !== 200) {
|
|
117
|
+
throw backendSdk.createInternalError(null, {
|
|
118
|
+
message: await result.text()
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const json = await result.json();
|
|
122
|
+
this.accessToken = json.access_token;
|
|
123
|
+
return json.refresh_token || "";
|
|
124
|
+
}
|
|
125
|
+
async preparePayment(payment) {
|
|
126
|
+
if (!this.accessToken) {
|
|
127
|
+
throw backendSdk.createInternalError(null, {
|
|
128
|
+
message: "Internal authentication failed",
|
|
129
|
+
status: 500
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const paymentBody = {
|
|
133
|
+
paymentIdentification: {
|
|
134
|
+
endToEndIdentification: payment.id,
|
|
135
|
+
instructionIdentification: payment.id
|
|
136
|
+
},
|
|
137
|
+
paymentTypeInformation: { instructionPriority: "NORM" },
|
|
138
|
+
amount: {
|
|
139
|
+
instructedAmount: { value: payment.amount, currency: payment.currency }
|
|
140
|
+
},
|
|
141
|
+
debtorAccount: { identification: { iban: payment.debtorIban } },
|
|
142
|
+
creditorAccount: { identification: { iban: payment.creditorIban } },
|
|
143
|
+
creditor: {
|
|
144
|
+
name: payment.creditorHolderName
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const [data, error] = await backendSdk.useResult(
|
|
148
|
+
fetch(`${this.PAYMENTS_URI}/my/payments`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"WEB-API-key": this.API_KEY,
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify(paymentBody)
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
if (error || !data) {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
const erstePayment = await data.json();
|
|
162
|
+
return {
|
|
163
|
+
...payment,
|
|
164
|
+
id: backendSdk.uuidv4(),
|
|
165
|
+
bankRefId: erstePayment.signInfo?.signId,
|
|
166
|
+
refId: payment.refId,
|
|
167
|
+
direction: "OUTGOING",
|
|
168
|
+
paymentType: "DOMESTIC",
|
|
169
|
+
status: "PREPARED",
|
|
170
|
+
initiatedAt: /* @__PURE__ */ new Date()
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async initiateBatchFromPayments({
|
|
174
|
+
payments
|
|
175
|
+
}) {
|
|
176
|
+
const allRefIds = payments.map((payment) => payment.bankRefId);
|
|
177
|
+
const batchId = backendSdk.uuidv4();
|
|
178
|
+
if (allRefIds.length === 0) {
|
|
179
|
+
throw backendSdk.createInternalError(null, {
|
|
180
|
+
message: "No valid payments to include in batch",
|
|
181
|
+
status: 400
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const [signInfo, signError] = await backendSdk.useResult(
|
|
185
|
+
this.sendBatchPaymentForAuthorization(allRefIds)
|
|
186
|
+
);
|
|
187
|
+
if (signError) {
|
|
188
|
+
throw backendSdk.createInternalError(signError);
|
|
189
|
+
}
|
|
190
|
+
const [authUri, authUriError] = await backendSdk.useResult(
|
|
191
|
+
this.getBatchAuthorizationURI(signInfo)
|
|
192
|
+
);
|
|
193
|
+
if (authUriError) {
|
|
194
|
+
throw backendSdk.createInternalError(authUriError);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
id: batchId,
|
|
198
|
+
authorizationUrls: [authUri || ""],
|
|
199
|
+
payments: payments.map((payment) => ({
|
|
200
|
+
...payment,
|
|
201
|
+
status: "INITIALIZED"
|
|
202
|
+
})),
|
|
203
|
+
metadata: signInfo
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
initiateSinglePayment() {
|
|
207
|
+
throw new Error("Method not implemented.");
|
|
208
|
+
}
|
|
209
|
+
async sendBatchPaymentForAuthorization(paymentIds) {
|
|
210
|
+
const [data, error] = await backendSdk.useResult(
|
|
211
|
+
fetch(`${this.PAYMENTS_URI}/my/batchpayments`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
216
|
+
"WEB-API-key": this.CLIENT_SECRET
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
exchangeIdentification: "658576010faf0a23dc",
|
|
220
|
+
payments: paymentIds
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
);
|
|
224
|
+
if (error) throw error;
|
|
225
|
+
const batch = await data.json();
|
|
226
|
+
return {
|
|
227
|
+
signHash: batch.signInfo.hash,
|
|
228
|
+
signId: batch.signInfo.signId
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async getBatchAuthorizationURI({
|
|
232
|
+
signId,
|
|
233
|
+
signHash
|
|
234
|
+
}) {
|
|
235
|
+
const [data, error] = await backendSdk.useResult(
|
|
236
|
+
fetch(
|
|
237
|
+
`${this.PAYMENTS_URI}/my/batchpayments/federate/sign/${signId}/hash/${signHash}`,
|
|
238
|
+
{
|
|
239
|
+
method: "GET",
|
|
240
|
+
headers: {
|
|
241
|
+
"Callback-Uri": `${this.REDIRECT_URI}/payment-authorization-complete`,
|
|
242
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
243
|
+
"WEB-API-key": this.CLIENT_SECRET
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
if (error) {
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
return (await data.json()).signingUrl;
|
|
252
|
+
}
|
|
253
|
+
async getAllAccountPayments({
|
|
254
|
+
account,
|
|
255
|
+
lastSync
|
|
256
|
+
}) {
|
|
257
|
+
const erstePayments = [];
|
|
258
|
+
const dateFormat = "yyyy-MM-dd";
|
|
259
|
+
const fromDate = dateFns.format(lastSync.lastSyncedAt, dateFormat);
|
|
260
|
+
const toDate = dateFns.format(/* @__PURE__ */ new Date(), dateFormat);
|
|
261
|
+
let page = 0;
|
|
262
|
+
const pageSize = 200;
|
|
263
|
+
const pageCount = 1;
|
|
264
|
+
while (page < pageCount) {
|
|
265
|
+
const response = await fetch(
|
|
266
|
+
`${this.ACCOUNTS_URI}/my/accounts/${account.id}/transactions?fromDate=${fromDate}&toDate=${toDate}&size=${pageSize}&page=${page}&sort=bookingDate&order=desc`,
|
|
267
|
+
{
|
|
268
|
+
headers: {
|
|
269
|
+
"WEB-API-key": this.API_KEY,
|
|
270
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
const data = await response.json();
|
|
275
|
+
erstePayments.push(...data.transactions);
|
|
276
|
+
page += 1;
|
|
277
|
+
}
|
|
278
|
+
const payments = erstePayments.map((payment) => {
|
|
279
|
+
const reference = payment.entryDetails.transactionDetails.remittanceInformation?.structured.creditorReferenceInformation?.reference || [];
|
|
280
|
+
const symbols = mapReferencesToPayment(reference);
|
|
281
|
+
const paymentInsert = {
|
|
282
|
+
id: backendSdk.uuidv4(),
|
|
283
|
+
bankRefId: payment.entryReference,
|
|
284
|
+
amount: payment.amount.value,
|
|
285
|
+
currency: payment.amount.currency.code,
|
|
286
|
+
debtorHolderName: payment.entryDetails.transactionDetails.relatedParties.debtor?.name,
|
|
287
|
+
debtorIban: payment.entryDetails.transactionDetails.relatedParties.debtorAccount?.identification.iban,
|
|
288
|
+
debtorAccountNumberWithBankCode: payment.entryDetails.transactionDetails.relatedParties.debtorAccount?.identification.other?.identification,
|
|
289
|
+
creditorHolderName: payment.entryDetails.transactionDetails.relatedParties.creditor?.name || "",
|
|
290
|
+
creditorIban: payment.entryDetails.transactionDetails.relatedParties.creditorAccount?.identification.iban,
|
|
291
|
+
creditorAccountNumberWithBankCode: payment.entryDetails.transactionDetails.relatedParties.creditorAccount?.identification.other?.identification,
|
|
292
|
+
paymentType: "DOMESTIC",
|
|
293
|
+
direction: "INCOMING",
|
|
294
|
+
message: payment.entryDetails.transactionDetails.remittanceInformation?.unstructured,
|
|
295
|
+
vs: symbols.vs,
|
|
296
|
+
ss: symbols.ss,
|
|
297
|
+
ks: symbols.ks,
|
|
298
|
+
processedAt: dateFns.parseISO(payment.bookingDate.date),
|
|
299
|
+
status: "COMPLETED"
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
...paymentInsert,
|
|
303
|
+
direction: getPaymentDirection(
|
|
304
|
+
paymentInsert,
|
|
305
|
+
account.identification.iban
|
|
306
|
+
)
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
return payments;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const mapFinbricksStatus = (status) => {
|
|
314
|
+
switch (status) {
|
|
315
|
+
case "BOOK":
|
|
316
|
+
return "COMPLETED";
|
|
317
|
+
case "PDNG":
|
|
318
|
+
return "PENDING";
|
|
319
|
+
case "CANCL":
|
|
320
|
+
return "FAILED";
|
|
321
|
+
case "RJCT":
|
|
322
|
+
return "FAILED";
|
|
323
|
+
case "SCHDL":
|
|
324
|
+
return "PENDING";
|
|
325
|
+
case "HOLD":
|
|
326
|
+
return "PENDING";
|
|
327
|
+
case "INFO":
|
|
328
|
+
return "PENDING";
|
|
329
|
+
default:
|
|
330
|
+
return "PENDING";
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
class FinbricksConnector extends IBankConnector {
|
|
335
|
+
constructor(PROVIDER, { BASE_URI, MERCHANT_ID, PRIVATE_KEY_PEM }) {
|
|
336
|
+
super();
|
|
337
|
+
this.connectorKey = "FINBRICKS";
|
|
338
|
+
this.apiDictionary = /* @__PURE__ */ new Map([
|
|
339
|
+
["single-payment-domestic", "/transaction/platform/init"],
|
|
340
|
+
["batch-payment-domestic", "/transaction/platform/batchPayment/init"],
|
|
341
|
+
["get-accounts", "/account/list"],
|
|
342
|
+
["get-account-transactions", "/account/transactions"],
|
|
343
|
+
["authenticate-client", "/v2/auth/authenticate"],
|
|
344
|
+
["expiration", "/auth/token"]
|
|
345
|
+
]);
|
|
346
|
+
this.PROVIDER = PROVIDER;
|
|
347
|
+
this.BASE_URI = BASE_URI;
|
|
348
|
+
this.MERCHANT_ID = MERCHANT_ID;
|
|
349
|
+
this.PRIVATE_KEY_PEM = PRIVATE_KEY_PEM;
|
|
350
|
+
}
|
|
351
|
+
static {
|
|
352
|
+
this.FETCH_INTERVAL = 60 * 60 * 5;
|
|
353
|
+
}
|
|
354
|
+
async signFinbricksJws({ jwsData }) {
|
|
355
|
+
const privateKey = await jose.importPKCS8(this.PRIVATE_KEY_PEM, "RS256");
|
|
356
|
+
const payload = JSON.stringify(jwsData);
|
|
357
|
+
const jws = await new jose.SignJWT(JSON.parse(payload)).setProtectedHeader({
|
|
358
|
+
alg: "RS256",
|
|
359
|
+
kid: this.MERCHANT_ID,
|
|
360
|
+
typ: "JWT"
|
|
361
|
+
}).sign(privateKey);
|
|
362
|
+
return jws;
|
|
363
|
+
}
|
|
364
|
+
async authenticate({ token }) {
|
|
365
|
+
this.lastValidClientId = token;
|
|
366
|
+
return this.lastValidClientId;
|
|
367
|
+
}
|
|
368
|
+
async isClientIdExpired(clientId) {
|
|
369
|
+
const query = new URLSearchParams({
|
|
370
|
+
merchantId: this.MERCHANT_ID,
|
|
371
|
+
provider: this.PROVIDER,
|
|
372
|
+
clientId
|
|
373
|
+
});
|
|
374
|
+
const uri = this.apiDictionary.get("expiration");
|
|
375
|
+
const fullUri = `${uri}?${query.toString()}`;
|
|
376
|
+
const signature = await this.signFinbricksJws({
|
|
377
|
+
jwsData: {
|
|
378
|
+
body: "",
|
|
379
|
+
uri: fullUri
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const [data, error] = await backendSdk.useResult(
|
|
383
|
+
fetch(`${this.BASE_URI}${fullUri}`, {
|
|
384
|
+
method: "POST",
|
|
385
|
+
headers: {
|
|
386
|
+
"JWS-Signature": signature,
|
|
387
|
+
"Content-Type": "application/json"
|
|
388
|
+
},
|
|
389
|
+
body: ""
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
if (error) {
|
|
393
|
+
const body = await data?.json();
|
|
394
|
+
throw backendSdk.createInternalError(error, {
|
|
395
|
+
message: body?.message || "Finbricks API error"
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (!data?.ok) {
|
|
399
|
+
const body = await data.json();
|
|
400
|
+
throw backendSdk.createInternalError(null, {
|
|
401
|
+
message: body.message || "Finbricks API error",
|
|
402
|
+
code: "FINBRICKS_API_ERROR",
|
|
403
|
+
status: data?.status || 500
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const tokens = await data.json();
|
|
407
|
+
return !!tokens.find((token) => token.clientId === clientId);
|
|
408
|
+
}
|
|
409
|
+
async preparePayment(payment) {
|
|
410
|
+
const bankRefId = backendSdk.uuidv4();
|
|
411
|
+
return {
|
|
412
|
+
...payment,
|
|
413
|
+
id: backendSdk.uuidv4(),
|
|
414
|
+
bankRefId,
|
|
415
|
+
direction: "OUTGOING",
|
|
416
|
+
paymentType: "DOMESTIC",
|
|
417
|
+
status: "PREPARED",
|
|
418
|
+
initiatedAt: /* @__PURE__ */ new Date()
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
async initiateBatchFromPayments({
|
|
422
|
+
payments
|
|
423
|
+
}) {
|
|
424
|
+
const uri = this.apiDictionary.get("batch-payment-domestic");
|
|
425
|
+
const batchId = backendSdk.uuidv4();
|
|
426
|
+
const batchBody = {
|
|
427
|
+
batchPaymentIdentification: {
|
|
428
|
+
merchantId: this.MERCHANT_ID,
|
|
429
|
+
debtorAccountIban: payments[0].debtorIban,
|
|
430
|
+
clientId: this.lastValidClientId,
|
|
431
|
+
callbackUrl: "https://example.com/callback",
|
|
432
|
+
merchantBatchId: batchId
|
|
433
|
+
},
|
|
434
|
+
payments: payments.map((payment) => ({
|
|
435
|
+
merchantTransactionId: payment.bankRefId,
|
|
436
|
+
creditorAccountIban: payment.creditorIban,
|
|
437
|
+
amount: payment.amount
|
|
438
|
+
}))
|
|
439
|
+
};
|
|
440
|
+
const payload = JSON.stringify(batchBody);
|
|
441
|
+
const jwsData = {
|
|
442
|
+
uri,
|
|
443
|
+
body: payload
|
|
444
|
+
};
|
|
445
|
+
const signature = await this.signFinbricksJws({
|
|
446
|
+
jwsData
|
|
447
|
+
});
|
|
448
|
+
const [data, error] = await backendSdk.useResult(
|
|
449
|
+
fetch(`${this.BASE_URI}${uri}`, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: {
|
|
452
|
+
"JWS-Signature": signature,
|
|
453
|
+
"Content-Type": "application/json"
|
|
454
|
+
},
|
|
455
|
+
body: payload
|
|
456
|
+
})
|
|
457
|
+
);
|
|
458
|
+
const body = await data?.json();
|
|
459
|
+
if (error) {
|
|
460
|
+
throw backendSdk.createInternalError(error, {
|
|
461
|
+
message: body?.message || "Finbricks API error"
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (!data?.ok) {
|
|
465
|
+
console.log("FINBRICKSBODY", body);
|
|
466
|
+
throw backendSdk.createInternalError(null, {
|
|
467
|
+
message: body?.message || "Finbricks API error",
|
|
468
|
+
code: "FINBRICKS_API_ERROR",
|
|
469
|
+
status: data?.status || 500
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const { redirectUrl } = body;
|
|
473
|
+
return {
|
|
474
|
+
id: batchId,
|
|
475
|
+
authorizationUrls: [redirectUrl],
|
|
476
|
+
payments: payments.map((payment) => ({
|
|
477
|
+
...payment,
|
|
478
|
+
status: "INITIALIZED"
|
|
479
|
+
}))
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
async initiateSinglePayment(payment) {
|
|
483
|
+
const uri = this.apiDictionary.get("single-payment-domestic");
|
|
484
|
+
const paymentBody = {
|
|
485
|
+
merchantId: this.MERCHANT_ID,
|
|
486
|
+
merchantTransactionId: payment.bankRefId,
|
|
487
|
+
totalPrice: payment.amount,
|
|
488
|
+
creditorAccountIban: payment.creditorIban,
|
|
489
|
+
debtorAccountIban: payment.debtorIban,
|
|
490
|
+
creditorName: payment.creditorHolderName,
|
|
491
|
+
description: payment.message || "",
|
|
492
|
+
variableSymbol: payment.vs || "",
|
|
493
|
+
callbackUrl: "example.com",
|
|
494
|
+
paymentProvider: this.PROVIDER
|
|
495
|
+
};
|
|
496
|
+
const signature = await this.signFinbricksJws({
|
|
497
|
+
jwsData: {
|
|
498
|
+
body: JSON.stringify(paymentBody),
|
|
499
|
+
uri
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
const [data, error] = await backendSdk.useResult(
|
|
503
|
+
fetch(`${this.BASE_URI}${uri}`, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: {
|
|
506
|
+
"JWS-Signature": signature,
|
|
507
|
+
"Content-Type": "application/json"
|
|
508
|
+
},
|
|
509
|
+
body: JSON.stringify(paymentBody)
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
const body = await data?.json();
|
|
513
|
+
if (error) {
|
|
514
|
+
throw backendSdk.createInternalError(error, {
|
|
515
|
+
message: body?.message || "Finbricks API error"
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (!data?.ok) {
|
|
519
|
+
console.log("FINBRICKSBODY", body);
|
|
520
|
+
throw backendSdk.createInternalError(null, {
|
|
521
|
+
message: body?.message || "Finbricks API error",
|
|
522
|
+
code: "FINBRICKS_API_ERROR",
|
|
523
|
+
status: data?.status || 500
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const redirectUrl = (await data.json()).redirectUrl;
|
|
527
|
+
return {
|
|
528
|
+
authorizationUrl: redirectUrl,
|
|
529
|
+
payment: { ...payment, status: "INITIALIZED" }
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
async getAllAccountPayments({
|
|
533
|
+
account,
|
|
534
|
+
lastSync
|
|
535
|
+
}) {
|
|
536
|
+
const uri = this.apiDictionary.get("get-account-transactions");
|
|
537
|
+
const dateFormat = "yyyy-MM-dd";
|
|
538
|
+
let cursor = null;
|
|
539
|
+
let allPayments = [];
|
|
540
|
+
const dateFrom = dateFns.format(lastSync.lastSyncedAt, dateFormat);
|
|
541
|
+
const dateTo = dateFns.format(/* @__PURE__ */ new Date(), dateFormat);
|
|
542
|
+
do {
|
|
543
|
+
const query = new URLSearchParams({
|
|
544
|
+
merchantId: this.MERCHANT_ID,
|
|
545
|
+
clientId: this.lastValidClientId,
|
|
546
|
+
paymentProvider: this.PROVIDER,
|
|
547
|
+
bankAccountId: account.identification.number,
|
|
548
|
+
dateFrom,
|
|
549
|
+
dateTo,
|
|
550
|
+
currency: account.currency,
|
|
551
|
+
size: "20"
|
|
552
|
+
});
|
|
553
|
+
console.log("query", query);
|
|
554
|
+
if (cursor) query.set("cursor", cursor);
|
|
555
|
+
const fullUri = `${uri}?${query.toString()}`;
|
|
556
|
+
const jwsData = {
|
|
557
|
+
uri: fullUri,
|
|
558
|
+
body: ""
|
|
559
|
+
};
|
|
560
|
+
const signature = await this.signFinbricksJws({ jwsData });
|
|
561
|
+
console.log("signature ready", signature);
|
|
562
|
+
const [data, error] = await backendSdk.useResult(
|
|
563
|
+
fetch(`${this.BASE_URI}${fullUri}`, {
|
|
564
|
+
method: "GET",
|
|
565
|
+
headers: {
|
|
566
|
+
"JWS-Signature": signature,
|
|
567
|
+
"Content-Type": "application/json"
|
|
568
|
+
}
|
|
569
|
+
})
|
|
570
|
+
);
|
|
571
|
+
if (error) {
|
|
572
|
+
const body = await data?.json();
|
|
573
|
+
throw backendSdk.createInternalError(error, {
|
|
574
|
+
message: body?.message || "Finbricks API error"
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
if (!data?.ok) {
|
|
578
|
+
const body = await data.json();
|
|
579
|
+
throw backendSdk.createInternalError(null, {
|
|
580
|
+
message: body.message || "Finbricks API error",
|
|
581
|
+
code: "FINBRICKS_API_ERROR",
|
|
582
|
+
status: data?.status || 500
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const { transactions, links } = await data.json();
|
|
586
|
+
cursor = links?.[0]?.value || null;
|
|
587
|
+
allPayments = [
|
|
588
|
+
...allPayments,
|
|
589
|
+
...transactions.map((tx) => {
|
|
590
|
+
const isIncoming = tx.creditDebitIndicator === "CRDT";
|
|
591
|
+
const relatedParties = tx.entryDetails?.transactionDetails?.relatedParties;
|
|
592
|
+
const paymentInsert = {
|
|
593
|
+
id: backendSdk.uuidv4(),
|
|
594
|
+
bankRefId: tx.entryReference || tx.fbxReference,
|
|
595
|
+
amount: tx.amount?.value || 0,
|
|
596
|
+
currency: tx.amount?.currency || "CZK",
|
|
597
|
+
debtorHolderName: isIncoming ? relatedParties?.debtor?.name || "Unknown" : "Unknown",
|
|
598
|
+
debtorIban: isIncoming ? relatedParties?.debtorAccount?.identification?.iban || account.identification.iban : account.identification.iban,
|
|
599
|
+
debtorAccountNumberWithBankCode: isIncoming ? relatedParties?.debtorAccount?.identification?.other?.identification || `${account.identification.number}/${account.identification.bankCode}` : `${account.identification.number}/${account.identification.bankCode}`,
|
|
600
|
+
creditorHolderName: isIncoming ? "Unknown" : relatedParties?.creditor?.name || "Unknown",
|
|
601
|
+
creditorIban: isIncoming ? account.identification.iban : relatedParties?.creditorAccount?.identification?.iban || account.identification.iban,
|
|
602
|
+
creditorAccountNumberWithBankCode: isIncoming ? `${account.identification.number}/${account.identification.bankCode}` : relatedParties?.creditorAccount?.identification?.other?.identification || `${account.identification.number}/${account.identification.bankCode}`,
|
|
603
|
+
paymentType: "DOMESTIC",
|
|
604
|
+
direction: isIncoming ? "INCOMING" : "OUTGOING",
|
|
605
|
+
message: tx.entryDetails?.transactionDetails?.remittanceInformation?.unstructured || tx.entryDetails?.transactionDetails?.additionalRemittanceInformation || null,
|
|
606
|
+
...mapReferencesToPayment(
|
|
607
|
+
tx.entryDetails?.transactionDetails?.remittanceInformation?.structured?.creditorReferenceInformation?.reference || tx.entryDetails?.transactionDetails?.references?.endToEndIdentification
|
|
608
|
+
),
|
|
609
|
+
processedAt: new Date(tx.bookingDate.date),
|
|
610
|
+
status: "COMPLETED"
|
|
611
|
+
};
|
|
612
|
+
return {
|
|
613
|
+
...paymentInsert,
|
|
614
|
+
direction: getPaymentDirection(
|
|
615
|
+
paymentInsert,
|
|
616
|
+
account.identification.iban
|
|
617
|
+
),
|
|
618
|
+
status: mapFinbricksStatus(tx.status)
|
|
619
|
+
};
|
|
620
|
+
})
|
|
621
|
+
];
|
|
622
|
+
} while (cursor != null);
|
|
623
|
+
return allPayments;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
class MockConnector extends IBankConnector {
|
|
628
|
+
constructor() {
|
|
629
|
+
super(...arguments);
|
|
630
|
+
this.connectorKey = "MOCK";
|
|
631
|
+
}
|
|
632
|
+
static {
|
|
633
|
+
this.FETCH_INTERVAL = 60 * 60 * 5;
|
|
634
|
+
}
|
|
635
|
+
async authenticate() {
|
|
636
|
+
return "";
|
|
637
|
+
}
|
|
638
|
+
async preparePayment(payment) {
|
|
639
|
+
const bankRefId = backendSdk.uuidv4();
|
|
640
|
+
return {
|
|
641
|
+
...payment,
|
|
642
|
+
id: backendSdk.uuidv4(),
|
|
643
|
+
bankRefId,
|
|
644
|
+
direction: "OUTGOING",
|
|
645
|
+
paymentType: "DOMESTIC",
|
|
646
|
+
status: "PREPARED",
|
|
647
|
+
initiatedAt: /* @__PURE__ */ new Date()
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
async initiateSinglePayment(payment) {
|
|
651
|
+
return {
|
|
652
|
+
payment: {
|
|
653
|
+
...payment,
|
|
654
|
+
status: "INITIALIZED"
|
|
655
|
+
},
|
|
656
|
+
authorizationUrl: "http://mock.com"
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async initiateBatchFromPayments({
|
|
660
|
+
payments
|
|
661
|
+
}) {
|
|
662
|
+
return {
|
|
663
|
+
id: backendSdk.uuidv4(),
|
|
664
|
+
authorizationUrls: ["http://mock.com"],
|
|
665
|
+
payments: payments.map((payment) => ({
|
|
666
|
+
...payment,
|
|
667
|
+
status: "INITIALIZED"
|
|
668
|
+
}))
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async getAllAccountPayments({
|
|
672
|
+
db
|
|
673
|
+
}) {
|
|
674
|
+
const payments = await db.select().from(tables.payment).where(
|
|
675
|
+
drizzleOrm.and(
|
|
676
|
+
drizzleOrm.eq(tables.payment.status, "INITIALIZED"),
|
|
677
|
+
drizzleOrm.eq(tables.payment.direction, "OUTGOING")
|
|
678
|
+
)
|
|
679
|
+
);
|
|
680
|
+
return payments.map((payment) => ({
|
|
681
|
+
...payment,
|
|
682
|
+
status: "COMPLETED"
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
class MockCobsConnector extends FinbricksConnector {
|
|
688
|
+
constructor(config) {
|
|
689
|
+
super("MOCK_COBS", config);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const paymentInsertTypeZod = zod.z.object({
|
|
694
|
+
refId: zod.z.string(),
|
|
695
|
+
bankRefId: zod.z.string(),
|
|
696
|
+
amount: zod.z.number(),
|
|
697
|
+
direction: zod.z.enum(PAYMENT_DIRECTIONS),
|
|
698
|
+
paymentType: zod.z.enum(PAYMENT_TYPES),
|
|
699
|
+
currency: zod.z.enum(generalCodes.CURRENCY_CODES),
|
|
700
|
+
status: zod.z.enum(PAYMENT_STATUSES),
|
|
701
|
+
batchId: zod.z.string(),
|
|
702
|
+
bankPaymentInitiatedAt: zod.z.coerce.date(),
|
|
703
|
+
bankPaymentProcessedAt: zod.z.coerce.date(),
|
|
704
|
+
bankingVs: zod.z.string().max(10),
|
|
705
|
+
message: zod.z.string(),
|
|
706
|
+
creditorIban: zod.z.string().max(34),
|
|
707
|
+
creditorHolderName: zod.z.string(),
|
|
708
|
+
debtorIban: zod.z.string().max(34),
|
|
709
|
+
debtorHolderName: zod.z.string().nullable()
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
exports.BATCH_STATUSES = BATCH_STATUSES;
|
|
713
|
+
exports.COUNTRY_CODES = COUNTRY_CODES;
|
|
714
|
+
exports.ErsteConnector = ErsteConnector;
|
|
715
|
+
exports.FinbricksConnector = FinbricksConnector;
|
|
716
|
+
exports.IBankConnector = IBankConnector;
|
|
717
|
+
exports.MockCobsConnector = MockCobsConnector;
|
|
718
|
+
exports.MockConnector = MockConnector;
|
|
719
|
+
exports.PAYMENT_DIRECTIONS = PAYMENT_DIRECTIONS;
|
|
720
|
+
exports.PAYMENT_STATUSES = PAYMENT_STATUSES;
|
|
721
|
+
exports.PAYMENT_TYPES = PAYMENT_TYPES;
|
|
722
|
+
exports.getPaymentDirection = getPaymentDirection;
|
|
723
|
+
exports.paymentInsertTypeZod = paymentInsertTypeZod;
|
|
724
|
+
exports.tables = tables;
|