@develit-services/bank 0.8.6 → 0.8.7
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/database/schema.cjs +1 -1
- package/dist/database/schema.d.cts +1 -1
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/database/schema.mjs +1 -1
- package/dist/export/worker.cjs +583 -146
- package/dist/export/worker.d.cts +251 -39
- package/dist/export/worker.d.mts +251 -39
- package/dist/export/worker.d.ts +251 -39
- package/dist/export/worker.mjs +584 -147
- package/dist/export/workflows.cjs +132 -16
- package/dist/export/workflows.mjs +133 -17
- package/dist/shared/{bank.C-T1FQxg.cjs → bank.62VzK9Aj.cjs} +1 -1
- package/dist/shared/{bank.SQ4Mmr8u.cjs → bank.BS7fFjGA.cjs} +34 -4
- package/dist/shared/{bank.B6U8sUZn.d.mts → bank.BYRq3yJf.d.ts} +17 -7
- package/dist/shared/{bank.C4VOdIx1.mjs → bank.C0UN6luZ.mjs} +34 -4
- package/dist/shared/{bank.DDHrdFgy.mjs → bank.CA5ytXxp.mjs} +1 -1
- package/dist/shared/{bank.BoMDujsl.d.ts → bank.CO89tR9U.d.cts} +17 -7
- package/dist/shared/{bank.BliD3oCT.d.ts → bank.Cns5ss41.d.cts} +47 -10
- package/dist/shared/{bank.BliD3oCT.d.cts → bank.Cns5ss41.d.mts} +47 -10
- package/dist/shared/{bank.BliD3oCT.d.mts → bank.Cns5ss41.d.ts} +47 -10
- package/dist/shared/{bank.lbzMqyr3.d.cts → bank.CreoSb2d.d.mts} +17 -7
- package/dist/shared/{bank.DRrBrAdI.mjs → bank.D1jqaHaF.mjs} +91 -31
- package/dist/shared/{bank.Cpy9PULF.mjs → bank.DEmzZGZW.mjs} +31 -5
- package/dist/shared/{bank.BOnP9p9Y.cjs → bank.Dm8GHThw.cjs} +31 -5
- package/dist/shared/{bank.CQURey1E.cjs → bank.DwyCCyd0.cjs} +90 -31
- package/dist/types.cjs +3 -4
- package/dist/types.d.cts +8 -16
- package/dist/types.d.mts +8 -16
- package/dist/types.d.ts +8 -16
- package/dist/types.mjs +3 -3
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { uuidv4, createInternalError, useResult } from '@develit-io/backend-sdk';
|
|
2
2
|
import { format, parseISO } from 'date-fns';
|
|
3
3
|
import { CURRENCY_CODES } from '@develit-io/general-codes';
|
|
4
|
-
import './bank.
|
|
4
|
+
import './bank.C0UN6luZ.mjs';
|
|
5
5
|
import 'drizzle-orm';
|
|
6
|
+
import 'drizzle-orm/sqlite-core';
|
|
6
7
|
import { importPKCS8, SignJWT } from 'jose';
|
|
7
8
|
import 'node:crypto';
|
|
8
9
|
|
|
@@ -60,9 +61,6 @@ function toCompletedPayment(payment, status, bankRefId, processedAt) {
|
|
|
60
61
|
processedAt: processedAt ?? /* @__PURE__ */ new Date()
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
|
-
function batchTransform(payments, transformer) {
|
|
64
|
-
return payments.map(transformer);
|
|
65
|
-
}
|
|
66
64
|
function toPaymentRequestInsert(payment, batchId) {
|
|
67
65
|
return {
|
|
68
66
|
id: payment.id,
|
|
@@ -84,7 +82,8 @@ function toPaymentRequestInsert(payment, batchId) {
|
|
|
84
82
|
creditor: payment.creditor,
|
|
85
83
|
creditorIban: payment.creditorIban ?? null,
|
|
86
84
|
debtor: payment.debtor,
|
|
87
|
-
debtorIban: payment.debtorIban ?? null
|
|
85
|
+
debtorIban: payment.debtorIban ?? null,
|
|
86
|
+
sendAsSinglePayment: payment.sendAsSinglePayment ?? null
|
|
88
87
|
};
|
|
89
88
|
}
|
|
90
89
|
function toBatchedPaymentFromPaymentRequest(sp) {
|
|
@@ -117,6 +116,9 @@ function toBatchedPaymentFromPaymentRequest(sp) {
|
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
class IBankConnector {
|
|
119
|
+
supportsPaymentType(paymentType) {
|
|
120
|
+
return paymentType === "DOMESTIC";
|
|
121
|
+
}
|
|
120
122
|
// ── Deprecated methods (backward compatibility) ────────────────────
|
|
121
123
|
/**
|
|
122
124
|
* @deprecated Use initiateDomesticBatch, initiateSEPABatch, or initiateForeignBatch instead.
|
|
@@ -392,10 +394,13 @@ const mapReferencesToPayment = (reference) => {
|
|
|
392
394
|
const mapFinbricksTransactionToPayment = (tx, account) => {
|
|
393
395
|
const isIncoming = tx.creditDebitIndicator === "CRDT";
|
|
394
396
|
const related = tx.entryDetails?.transactionDetails?.relatedParties;
|
|
397
|
+
const endToEndId = tx.entryDetails.transactionDetails?.references?.endToEndIdentification;
|
|
398
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
399
|
+
const paymentRequestId = endToEndId && uuidRegex.test(endToEndId) ? endToEndId : null;
|
|
395
400
|
const base = {
|
|
396
401
|
id: uuidv4(),
|
|
397
402
|
correlationId: uuidv4(),
|
|
398
|
-
|
|
403
|
+
paymentRequestId,
|
|
399
404
|
connectorKey: account.connectorKey,
|
|
400
405
|
accountId: account.id,
|
|
401
406
|
bankRefId: tx.fbxReference,
|
|
@@ -406,7 +411,7 @@ const mapFinbricksTransactionToPayment = (tx, account) => {
|
|
|
406
411
|
message: tx.entryDetails.transactionDetails?.remittanceInformation?.unstructured || tx.entryDetails.transactionDetails?.additionalRemittanceInformation || tx.entryDetails.transactionDetails?.additionalTransactionInformation || null,
|
|
407
412
|
processedAt: new Date(tx.bookingDate.date),
|
|
408
413
|
...mapReferencesToPayment(
|
|
409
|
-
tx.entryDetails.transactionDetails?.remittanceInformation?.structured?.creditorReferenceInformation?.reference ||
|
|
414
|
+
tx.entryDetails.transactionDetails?.remittanceInformation?.structured?.creditorReferenceInformation?.reference || (!paymentRequestId ? endToEndId : void 0)
|
|
410
415
|
),
|
|
411
416
|
creditor: {
|
|
412
417
|
holderName: related?.creditorAccount?.name || related?.creditor?.name || "Unknown",
|
|
@@ -455,6 +460,9 @@ class FinbricksConnector extends IBankConnector {
|
|
|
455
460
|
this.connectedAccounts = connectedAccounts;
|
|
456
461
|
this.resolveCredentials = resolveCredentials;
|
|
457
462
|
}
|
|
463
|
+
supportsPaymentType(paymentType) {
|
|
464
|
+
return paymentType === "DOMESTIC" || paymentType === "SEPA" || paymentType === "SWIFT";
|
|
465
|
+
}
|
|
458
466
|
async getClientId(accountId) {
|
|
459
467
|
if (!this.resolveCredentials) {
|
|
460
468
|
throw createInternalError(null, {
|
|
@@ -1145,24 +1153,64 @@ function mod97(string) {
|
|
|
1145
1153
|
}
|
|
1146
1154
|
return parseInt(checksum, 10);
|
|
1147
1155
|
}
|
|
1156
|
+
const parseCzechIban = (iban) => {
|
|
1157
|
+
const stripped = iban.replace(/\s/g, "").toUpperCase();
|
|
1158
|
+
if (!stripped.startsWith("CZ") || stripped.length !== 24) {
|
|
1159
|
+
throw new Error(`Invalid Czech IBAN: ${iban}`);
|
|
1160
|
+
}
|
|
1161
|
+
const bankCode = stripped.slice(4, 8);
|
|
1162
|
+
const prefix = stripped.slice(8, 14).replace(/^0+/, "");
|
|
1163
|
+
const main = stripped.slice(14, 24).replace(/^0+/, "");
|
|
1164
|
+
const accountNumber = prefix ? `${prefix}-${main}` : main;
|
|
1165
|
+
return { bankCode, accountNumber };
|
|
1166
|
+
};
|
|
1148
1167
|
const calculateCzechIban = (accountNumber, bankCode) => {
|
|
1149
1168
|
const paddedBankCode = bankCode.padStart(4, "0");
|
|
1150
1169
|
let prefix = "";
|
|
1151
1170
|
let mainAccount = accountNumber;
|
|
1152
1171
|
if (accountNumber.includes("-")) {
|
|
1153
1172
|
const parts = accountNumber.split("-");
|
|
1173
|
+
if (parts.length !== 2) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`Invalid account number format: expected "prefix-main" or "main", got "${accountNumber}"`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1154
1178
|
prefix = parts[0];
|
|
1155
1179
|
mainAccount = parts[1];
|
|
1156
1180
|
}
|
|
1157
1181
|
const paddedPrefix = prefix.padStart(6, "0");
|
|
1158
1182
|
const paddedAccount = mainAccount.padStart(10, "0");
|
|
1159
1183
|
const basicIban = paddedBankCode + paddedPrefix + paddedAccount;
|
|
1160
|
-
const rearranged = basicIban + "
|
|
1184
|
+
const rearranged = basicIban + "123500";
|
|
1161
1185
|
const remainder = mod97(rearranged);
|
|
1162
1186
|
const checkDigits = (98 - remainder).toString().padStart(2, "0");
|
|
1163
1187
|
return `CZ${checkDigits}${basicIban}`;
|
|
1164
1188
|
};
|
|
1165
1189
|
|
|
1190
|
+
function parseFlatAddress(addr, fallbackCountry) {
|
|
1191
|
+
if (!addr) {
|
|
1192
|
+
return {
|
|
1193
|
+
streetName: null,
|
|
1194
|
+
buildingNumber: null,
|
|
1195
|
+
city: null,
|
|
1196
|
+
zip: null,
|
|
1197
|
+
countryCode: fallbackCountry
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const parts = addr.split(",").map((p) => p.trim());
|
|
1201
|
+
const streetPart = parts[0];
|
|
1202
|
+
const cityPart = parts[1];
|
|
1203
|
+
const countryCode = parts[2]?.trim() || fallbackCountry;
|
|
1204
|
+
const streetMatch = streetPart?.match(/^(.+?)\s+(\d+\w*)$/);
|
|
1205
|
+
const cityMatch = cityPart?.match(/^(\d[\d ]*?)\s+(.+)$/);
|
|
1206
|
+
return {
|
|
1207
|
+
streetName: streetMatch?.[1] ?? streetPart ?? null,
|
|
1208
|
+
buildingNumber: streetMatch?.[2] ?? null,
|
|
1209
|
+
city: cityMatch?.[2] ?? cityPart ?? null,
|
|
1210
|
+
zip: cityMatch?.[1]?.replace(/\s/g, "") ?? null,
|
|
1211
|
+
countryCode
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1166
1214
|
class DbuConnector extends IBankConnector {
|
|
1167
1215
|
constructor({
|
|
1168
1216
|
BASE_URL,
|
|
@@ -1258,6 +1306,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1258
1306
|
const parsed = {
|
|
1259
1307
|
id: uuidv4(),
|
|
1260
1308
|
correlationId: uuidv4(),
|
|
1309
|
+
paymentRequestId: transaction.uniqueExternalId ?? null,
|
|
1261
1310
|
connectorKey: "DBU",
|
|
1262
1311
|
accountId: account.id,
|
|
1263
1312
|
bankRefId: transaction.idRequiredTransaction.toString(),
|
|
@@ -1423,11 +1472,25 @@ class DbuConnector extends IBankConnector {
|
|
|
1423
1472
|
}
|
|
1424
1473
|
return Number(account.bankRefId);
|
|
1425
1474
|
}
|
|
1475
|
+
resolveAccountDetails(account) {
|
|
1476
|
+
if (account.number && account.bankCode) {
|
|
1477
|
+
return { number: account.number, bankCode: account.bankCode };
|
|
1478
|
+
}
|
|
1479
|
+
if (account.iban) {
|
|
1480
|
+
const { bankCode, accountNumber } = parseCzechIban(account.iban);
|
|
1481
|
+
return { number: accountNumber, bankCode };
|
|
1482
|
+
}
|
|
1483
|
+
throw createInternalError(null, {
|
|
1484
|
+
message: "DBU: account has neither number+bankCode nor iban",
|
|
1485
|
+
code: "DBU_MISSING_ACCOUNT_DETAILS"
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1426
1488
|
async initiateInstantPayment(payment) {
|
|
1489
|
+
const creditor = this.resolveAccountDetails(payment.creditor);
|
|
1427
1490
|
const body = {
|
|
1428
|
-
accountNumberCredit:
|
|
1491
|
+
accountNumberCredit: creditor.number,
|
|
1429
1492
|
idAccountDebit: this.resolveIdAccountDebit(payment),
|
|
1430
|
-
bankCodeCredit:
|
|
1493
|
+
bankCodeCredit: creditor.bankCode,
|
|
1431
1494
|
amount: payment.amount,
|
|
1432
1495
|
currencyCode: "CZK",
|
|
1433
1496
|
channelCode: "WWW",
|
|
@@ -1459,11 +1522,13 @@ class DbuConnector extends IBankConnector {
|
|
|
1459
1522
|
if (payment.instructionPriority === "INST") {
|
|
1460
1523
|
return this.initiateInstantPayment(payment);
|
|
1461
1524
|
}
|
|
1525
|
+
const debtor = this.resolveAccountDetails(payment.debtor);
|
|
1526
|
+
const creditor = this.resolveAccountDetails(payment.creditor);
|
|
1462
1527
|
const body = {
|
|
1463
|
-
accountNumberDebit:
|
|
1464
|
-
accountNumberCredit:
|
|
1465
|
-
bankCodeDebit:
|
|
1466
|
-
bankCodeCredit:
|
|
1528
|
+
accountNumberDebit: debtor.number,
|
|
1529
|
+
accountNumberCredit: creditor.number,
|
|
1530
|
+
bankCodeDebit: debtor.bankCode,
|
|
1531
|
+
bankCodeCredit: creditor.bankCode,
|
|
1467
1532
|
amount: payment.amount,
|
|
1468
1533
|
currencyCode: "CZK",
|
|
1469
1534
|
actionTypeCode: "TRANSFER",
|
|
@@ -1480,20 +1545,14 @@ class DbuConnector extends IBankConnector {
|
|
|
1480
1545
|
constSymbol: payment.ks,
|
|
1481
1546
|
uniqueExternalId: payment.id,
|
|
1482
1547
|
with4EyeApproval: "Y",
|
|
1483
|
-
debtorAddress:
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
streetName: null,
|
|
1492
|
-
buildingNumber: null,
|
|
1493
|
-
city: null,
|
|
1494
|
-
zip: null,
|
|
1495
|
-
countryCode: payment.creditor.countryCode || "CZ"
|
|
1496
|
-
}
|
|
1548
|
+
debtorAddress: parseFlatAddress(
|
|
1549
|
+
payment.debtor.address,
|
|
1550
|
+
payment.debtor.countryCode || "CZ"
|
|
1551
|
+
),
|
|
1552
|
+
creditorAddress: parseFlatAddress(
|
|
1553
|
+
payment.creditor.address,
|
|
1554
|
+
payment.creditor.countryCode || "CZ"
|
|
1555
|
+
)
|
|
1497
1556
|
};
|
|
1498
1557
|
const response = await this.makeRequest(
|
|
1499
1558
|
"/required-transactions",
|
|
@@ -1547,7 +1606,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1547
1606
|
const limit = 10;
|
|
1548
1607
|
let hasMoreData = true;
|
|
1549
1608
|
let pageNumber = 1;
|
|
1550
|
-
const baseRequestId = uuidv4();
|
|
1609
|
+
const baseRequestId = uuidv4().replace(/-/g, "");
|
|
1551
1610
|
while (hasMoreData) {
|
|
1552
1611
|
const response = await this.makeRequest(
|
|
1553
1612
|
"/required-transactions",
|
|
@@ -1573,7 +1632,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1573
1632
|
}
|
|
1574
1633
|
for (const transaction of response.transactions.transaction) {
|
|
1575
1634
|
const status = transaction.status.trim();
|
|
1576
|
-
if (status === "0" || status === "50") continue;
|
|
1635
|
+
if (status === "0" || status === "50" || status === "51") continue;
|
|
1577
1636
|
try {
|
|
1578
1637
|
const payment = this.mapDbuTransactionToPayment(
|
|
1579
1638
|
transaction,
|
|
@@ -1886,6 +1945,7 @@ class ErsteConnector extends IBankConnector {
|
|
|
1886
1945
|
const paymentInsert = {
|
|
1887
1946
|
id: uuidv4(),
|
|
1888
1947
|
correlationId: uuidv4(),
|
|
1948
|
+
paymentRequestId: payment.entryDetails.transactionDetails.references.endToEndIdentification ?? null,
|
|
1889
1949
|
connectorKey: "ERSTE",
|
|
1890
1950
|
accountId: account.id,
|
|
1891
1951
|
bankRefId: payment.entryReference,
|
|
@@ -2027,4 +2087,4 @@ class MockConnector extends IBankConnector {
|
|
|
2027
2087
|
}
|
|
2028
2088
|
}
|
|
2029
2089
|
|
|
2030
|
-
export { CsobConnector as C, DbuConnector as D, ErsteConnector as E, FINBRICKS_ENDPOINTS as F, IBankConnector as I, KBConnector as K, MockCobsConnector as M, FinbricksClient as a, FinbricksConnector as b, MockConnector as c, assignAccount as d,
|
|
2090
|
+
export { CsobConnector as C, DbuConnector as D, ErsteConnector as E, FINBRICKS_ENDPOINTS as F, IBankConnector as I, KBConnector as K, MockCobsConnector as M, FinbricksClient as a, FinbricksConnector as b, MockConnector as c, assignAccount as d, toBatchedPaymentFromPaymentRequest as e, toCompletedPayment as f, toIncomingPayment as g, toPaymentRequestInsert as h, toPreparedPayment as i, initiateConnector as j, mapFinbricksTransactionStatus as m, signFinbricksJws as s, toBatchedPayment as t, useFinbricksFetch as u };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { sql, and, eq, isNull } from 'drizzle-orm';
|
|
1
2
|
import { uuidv4 } from '@develit-io/backend-sdk';
|
|
2
|
-
import
|
|
3
|
-
import './bank.C4VOdIx1.mjs';
|
|
3
|
+
import './bank.C0UN6luZ.mjs';
|
|
4
4
|
import 'date-fns';
|
|
5
5
|
import 'jose';
|
|
6
6
|
import '@develit-io/general-codes';
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
|
-
import { s as schema } from './bank.
|
|
8
|
+
import { s as schema } from './bank.CA5ytXxp.mjs';
|
|
9
|
+
import 'drizzle-orm/sqlite-core';
|
|
9
10
|
|
|
10
11
|
const createPaymentCommand = (db, { payment }) => {
|
|
11
12
|
return {
|
|
@@ -13,6 +14,21 @@ const createPaymentCommand = (db, { payment }) => {
|
|
|
13
14
|
...payment,
|
|
14
15
|
creditorIban: payment.creditor.iban,
|
|
15
16
|
debtorIban: payment.debtor.iban
|
|
17
|
+
}).onConflictDoUpdate({
|
|
18
|
+
// Unique index: (connector_key, bank_ref_id)
|
|
19
|
+
target: [tables.payment.connectorKey, tables.payment.bankRefId],
|
|
20
|
+
set: {
|
|
21
|
+
status: sql`excluded.status`,
|
|
22
|
+
statusReason: sql`excluded.status_reason`,
|
|
23
|
+
processedAt: sql`excluded.processed_at`,
|
|
24
|
+
updatedAt: sql`excluded.updated_at`,
|
|
25
|
+
// Keep existing paymentRequestId if already set, otherwise use new value
|
|
26
|
+
paymentRequestId: sql`coalesce(payment.payment_request_id, excluded.payment_request_id)`,
|
|
27
|
+
// Keep existing refId if already set, otherwise use enriched value
|
|
28
|
+
refId: sql`coalesce(payment.ref_id, excluded.ref_id)`,
|
|
29
|
+
// Keep existing batchId if already set, otherwise use enriched value
|
|
30
|
+
batchId: sql`coalesce(payment.bank_execution_batch_id, excluded.bank_execution_batch_id)`
|
|
31
|
+
}
|
|
16
32
|
}).returning()
|
|
17
33
|
};
|
|
18
34
|
};
|
|
@@ -37,7 +53,12 @@ const upsertBatchCommand = (db, { batch }) => {
|
|
|
37
53
|
const updatePaymentRequestStatusCommand = (db, values) => {
|
|
38
54
|
const { id, ...set } = values;
|
|
39
55
|
return {
|
|
40
|
-
command: db.update(tables.paymentRequest).set(set).where(
|
|
56
|
+
command: db.update(tables.paymentRequest).set(set).where(
|
|
57
|
+
and(
|
|
58
|
+
eq(tables.paymentRequest.id, id),
|
|
59
|
+
isNull(tables.paymentRequest.deletedAt)
|
|
60
|
+
)
|
|
61
|
+
).returning()
|
|
41
62
|
};
|
|
42
63
|
};
|
|
43
64
|
|
|
@@ -58,7 +79,12 @@ const getCredentialsByAccountId = async (db, encryptionKey, { accountId }) => {
|
|
|
58
79
|
};
|
|
59
80
|
|
|
60
81
|
const getPaymentRequestsByBatchIdQuery = async (db, { batchId }) => {
|
|
61
|
-
return await db.select().from(tables.paymentRequest).where(
|
|
82
|
+
return await db.select().from(tables.paymentRequest).where(
|
|
83
|
+
and(
|
|
84
|
+
eq(tables.paymentRequest.batchId, batchId),
|
|
85
|
+
isNull(tables.paymentRequest.deletedAt)
|
|
86
|
+
)
|
|
87
|
+
);
|
|
62
88
|
};
|
|
63
89
|
|
|
64
90
|
async function importAesKey(base64Key) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
3
|
const drizzleOrm = require('drizzle-orm');
|
|
5
|
-
require('
|
|
4
|
+
const backendSdk = require('@develit-io/backend-sdk');
|
|
5
|
+
require('./bank.BS7fFjGA.cjs');
|
|
6
6
|
require('date-fns');
|
|
7
7
|
require('jose');
|
|
8
8
|
require('@develit-io/general-codes');
|
|
9
9
|
const node_crypto = require('node:crypto');
|
|
10
|
-
const database_schema = require('./bank.
|
|
10
|
+
const database_schema = require('./bank.62VzK9Aj.cjs');
|
|
11
|
+
require('drizzle-orm/sqlite-core');
|
|
11
12
|
|
|
12
13
|
const createPaymentCommand = (db, { payment }) => {
|
|
13
14
|
return {
|
|
@@ -15,6 +16,21 @@ const createPaymentCommand = (db, { payment }) => {
|
|
|
15
16
|
...payment,
|
|
16
17
|
creditorIban: payment.creditor.iban,
|
|
17
18
|
debtorIban: payment.debtor.iban
|
|
19
|
+
}).onConflictDoUpdate({
|
|
20
|
+
// Unique index: (connector_key, bank_ref_id)
|
|
21
|
+
target: [tables.payment.connectorKey, tables.payment.bankRefId],
|
|
22
|
+
set: {
|
|
23
|
+
status: drizzleOrm.sql`excluded.status`,
|
|
24
|
+
statusReason: drizzleOrm.sql`excluded.status_reason`,
|
|
25
|
+
processedAt: drizzleOrm.sql`excluded.processed_at`,
|
|
26
|
+
updatedAt: drizzleOrm.sql`excluded.updated_at`,
|
|
27
|
+
// Keep existing paymentRequestId if already set, otherwise use new value
|
|
28
|
+
paymentRequestId: drizzleOrm.sql`coalesce(payment.payment_request_id, excluded.payment_request_id)`,
|
|
29
|
+
// Keep existing refId if already set, otherwise use enriched value
|
|
30
|
+
refId: drizzleOrm.sql`coalesce(payment.ref_id, excluded.ref_id)`,
|
|
31
|
+
// Keep existing batchId if already set, otherwise use enriched value
|
|
32
|
+
batchId: drizzleOrm.sql`coalesce(payment.bank_execution_batch_id, excluded.bank_execution_batch_id)`
|
|
33
|
+
}
|
|
18
34
|
}).returning()
|
|
19
35
|
};
|
|
20
36
|
};
|
|
@@ -39,7 +55,12 @@ const upsertBatchCommand = (db, { batch }) => {
|
|
|
39
55
|
const updatePaymentRequestStatusCommand = (db, values) => {
|
|
40
56
|
const { id, ...set } = values;
|
|
41
57
|
return {
|
|
42
|
-
command: db.update(tables.paymentRequest).set(set).where(
|
|
58
|
+
command: db.update(tables.paymentRequest).set(set).where(
|
|
59
|
+
drizzleOrm.and(
|
|
60
|
+
drizzleOrm.eq(tables.paymentRequest.id, id),
|
|
61
|
+
drizzleOrm.isNull(tables.paymentRequest.deletedAt)
|
|
62
|
+
)
|
|
63
|
+
).returning()
|
|
43
64
|
};
|
|
44
65
|
};
|
|
45
66
|
|
|
@@ -60,7 +81,12 @@ const getCredentialsByAccountId = async (db, encryptionKey, { accountId }) => {
|
|
|
60
81
|
};
|
|
61
82
|
|
|
62
83
|
const getPaymentRequestsByBatchIdQuery = async (db, { batchId }) => {
|
|
63
|
-
return await db.select().from(tables.paymentRequest).where(
|
|
84
|
+
return await db.select().from(tables.paymentRequest).where(
|
|
85
|
+
drizzleOrm.and(
|
|
86
|
+
drizzleOrm.eq(tables.paymentRequest.batchId, batchId),
|
|
87
|
+
drizzleOrm.isNull(tables.paymentRequest.deletedAt)
|
|
88
|
+
)
|
|
89
|
+
);
|
|
64
90
|
};
|
|
65
91
|
|
|
66
92
|
async function importAesKey(base64Key) {
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
4
|
const dateFns = require('date-fns');
|
|
5
5
|
const generalCodes = require('@develit-io/general-codes');
|
|
6
|
-
require('./bank.
|
|
6
|
+
require('./bank.BS7fFjGA.cjs');
|
|
7
7
|
require('drizzle-orm');
|
|
8
|
+
require('drizzle-orm/sqlite-core');
|
|
8
9
|
const jose = require('jose');
|
|
9
10
|
require('node:crypto');
|
|
10
11
|
|
|
@@ -62,9 +63,6 @@ function toCompletedPayment(payment, status, bankRefId, processedAt) {
|
|
|
62
63
|
processedAt: processedAt ?? /* @__PURE__ */ new Date()
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
|
-
function batchTransform(payments, transformer) {
|
|
66
|
-
return payments.map(transformer);
|
|
67
|
-
}
|
|
68
66
|
function toPaymentRequestInsert(payment, batchId) {
|
|
69
67
|
return {
|
|
70
68
|
id: payment.id,
|
|
@@ -86,7 +84,8 @@ function toPaymentRequestInsert(payment, batchId) {
|
|
|
86
84
|
creditor: payment.creditor,
|
|
87
85
|
creditorIban: payment.creditorIban ?? null,
|
|
88
86
|
debtor: payment.debtor,
|
|
89
|
-
debtorIban: payment.debtorIban ?? null
|
|
87
|
+
debtorIban: payment.debtorIban ?? null,
|
|
88
|
+
sendAsSinglePayment: payment.sendAsSinglePayment ?? null
|
|
90
89
|
};
|
|
91
90
|
}
|
|
92
91
|
function toBatchedPaymentFromPaymentRequest(sp) {
|
|
@@ -119,6 +118,9 @@ function toBatchedPaymentFromPaymentRequest(sp) {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
class IBankConnector {
|
|
121
|
+
supportsPaymentType(paymentType) {
|
|
122
|
+
return paymentType === "DOMESTIC";
|
|
123
|
+
}
|
|
122
124
|
// ── Deprecated methods (backward compatibility) ────────────────────
|
|
123
125
|
/**
|
|
124
126
|
* @deprecated Use initiateDomesticBatch, initiateSEPABatch, or initiateForeignBatch instead.
|
|
@@ -394,10 +396,13 @@ const mapReferencesToPayment = (reference) => {
|
|
|
394
396
|
const mapFinbricksTransactionToPayment = (tx, account) => {
|
|
395
397
|
const isIncoming = tx.creditDebitIndicator === "CRDT";
|
|
396
398
|
const related = tx.entryDetails?.transactionDetails?.relatedParties;
|
|
399
|
+
const endToEndId = tx.entryDetails.transactionDetails?.references?.endToEndIdentification;
|
|
400
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
401
|
+
const paymentRequestId = endToEndId && uuidRegex.test(endToEndId) ? endToEndId : null;
|
|
397
402
|
const base = {
|
|
398
403
|
id: backendSdk.uuidv4(),
|
|
399
404
|
correlationId: backendSdk.uuidv4(),
|
|
400
|
-
|
|
405
|
+
paymentRequestId,
|
|
401
406
|
connectorKey: account.connectorKey,
|
|
402
407
|
accountId: account.id,
|
|
403
408
|
bankRefId: tx.fbxReference,
|
|
@@ -408,7 +413,7 @@ const mapFinbricksTransactionToPayment = (tx, account) => {
|
|
|
408
413
|
message: tx.entryDetails.transactionDetails?.remittanceInformation?.unstructured || tx.entryDetails.transactionDetails?.additionalRemittanceInformation || tx.entryDetails.transactionDetails?.additionalTransactionInformation || null,
|
|
409
414
|
processedAt: new Date(tx.bookingDate.date),
|
|
410
415
|
...mapReferencesToPayment(
|
|
411
|
-
tx.entryDetails.transactionDetails?.remittanceInformation?.structured?.creditorReferenceInformation?.reference ||
|
|
416
|
+
tx.entryDetails.transactionDetails?.remittanceInformation?.structured?.creditorReferenceInformation?.reference || (!paymentRequestId ? endToEndId : void 0)
|
|
412
417
|
),
|
|
413
418
|
creditor: {
|
|
414
419
|
holderName: related?.creditorAccount?.name || related?.creditor?.name || "Unknown",
|
|
@@ -457,6 +462,9 @@ class FinbricksConnector extends IBankConnector {
|
|
|
457
462
|
this.connectedAccounts = connectedAccounts;
|
|
458
463
|
this.resolveCredentials = resolveCredentials;
|
|
459
464
|
}
|
|
465
|
+
supportsPaymentType(paymentType) {
|
|
466
|
+
return paymentType === "DOMESTIC" || paymentType === "SEPA" || paymentType === "SWIFT";
|
|
467
|
+
}
|
|
460
468
|
async getClientId(accountId) {
|
|
461
469
|
if (!this.resolveCredentials) {
|
|
462
470
|
throw backendSdk.createInternalError(null, {
|
|
@@ -1147,24 +1155,64 @@ function mod97(string) {
|
|
|
1147
1155
|
}
|
|
1148
1156
|
return parseInt(checksum, 10);
|
|
1149
1157
|
}
|
|
1158
|
+
const parseCzechIban = (iban) => {
|
|
1159
|
+
const stripped = iban.replace(/\s/g, "").toUpperCase();
|
|
1160
|
+
if (!stripped.startsWith("CZ") || stripped.length !== 24) {
|
|
1161
|
+
throw new Error(`Invalid Czech IBAN: ${iban}`);
|
|
1162
|
+
}
|
|
1163
|
+
const bankCode = stripped.slice(4, 8);
|
|
1164
|
+
const prefix = stripped.slice(8, 14).replace(/^0+/, "");
|
|
1165
|
+
const main = stripped.slice(14, 24).replace(/^0+/, "");
|
|
1166
|
+
const accountNumber = prefix ? `${prefix}-${main}` : main;
|
|
1167
|
+
return { bankCode, accountNumber };
|
|
1168
|
+
};
|
|
1150
1169
|
const calculateCzechIban = (accountNumber, bankCode) => {
|
|
1151
1170
|
const paddedBankCode = bankCode.padStart(4, "0");
|
|
1152
1171
|
let prefix = "";
|
|
1153
1172
|
let mainAccount = accountNumber;
|
|
1154
1173
|
if (accountNumber.includes("-")) {
|
|
1155
1174
|
const parts = accountNumber.split("-");
|
|
1175
|
+
if (parts.length !== 2) {
|
|
1176
|
+
throw new Error(
|
|
1177
|
+
`Invalid account number format: expected "prefix-main" or "main", got "${accountNumber}"`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1156
1180
|
prefix = parts[0];
|
|
1157
1181
|
mainAccount = parts[1];
|
|
1158
1182
|
}
|
|
1159
1183
|
const paddedPrefix = prefix.padStart(6, "0");
|
|
1160
1184
|
const paddedAccount = mainAccount.padStart(10, "0");
|
|
1161
1185
|
const basicIban = paddedBankCode + paddedPrefix + paddedAccount;
|
|
1162
|
-
const rearranged = basicIban + "
|
|
1186
|
+
const rearranged = basicIban + "123500";
|
|
1163
1187
|
const remainder = mod97(rearranged);
|
|
1164
1188
|
const checkDigits = (98 - remainder).toString().padStart(2, "0");
|
|
1165
1189
|
return `CZ${checkDigits}${basicIban}`;
|
|
1166
1190
|
};
|
|
1167
1191
|
|
|
1192
|
+
function parseFlatAddress(addr, fallbackCountry) {
|
|
1193
|
+
if (!addr) {
|
|
1194
|
+
return {
|
|
1195
|
+
streetName: null,
|
|
1196
|
+
buildingNumber: null,
|
|
1197
|
+
city: null,
|
|
1198
|
+
zip: null,
|
|
1199
|
+
countryCode: fallbackCountry
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
const parts = addr.split(",").map((p) => p.trim());
|
|
1203
|
+
const streetPart = parts[0];
|
|
1204
|
+
const cityPart = parts[1];
|
|
1205
|
+
const countryCode = parts[2]?.trim() || fallbackCountry;
|
|
1206
|
+
const streetMatch = streetPart?.match(/^(.+?)\s+(\d+\w*)$/);
|
|
1207
|
+
const cityMatch = cityPart?.match(/^(\d[\d ]*?)\s+(.+)$/);
|
|
1208
|
+
return {
|
|
1209
|
+
streetName: streetMatch?.[1] ?? streetPart ?? null,
|
|
1210
|
+
buildingNumber: streetMatch?.[2] ?? null,
|
|
1211
|
+
city: cityMatch?.[2] ?? cityPart ?? null,
|
|
1212
|
+
zip: cityMatch?.[1]?.replace(/\s/g, "") ?? null,
|
|
1213
|
+
countryCode
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1168
1216
|
class DbuConnector extends IBankConnector {
|
|
1169
1217
|
constructor({
|
|
1170
1218
|
BASE_URL,
|
|
@@ -1260,6 +1308,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1260
1308
|
const parsed = {
|
|
1261
1309
|
id: backendSdk.uuidv4(),
|
|
1262
1310
|
correlationId: backendSdk.uuidv4(),
|
|
1311
|
+
paymentRequestId: transaction.uniqueExternalId ?? null,
|
|
1263
1312
|
connectorKey: "DBU",
|
|
1264
1313
|
accountId: account.id,
|
|
1265
1314
|
bankRefId: transaction.idRequiredTransaction.toString(),
|
|
@@ -1425,11 +1474,25 @@ class DbuConnector extends IBankConnector {
|
|
|
1425
1474
|
}
|
|
1426
1475
|
return Number(account.bankRefId);
|
|
1427
1476
|
}
|
|
1477
|
+
resolveAccountDetails(account) {
|
|
1478
|
+
if (account.number && account.bankCode) {
|
|
1479
|
+
return { number: account.number, bankCode: account.bankCode };
|
|
1480
|
+
}
|
|
1481
|
+
if (account.iban) {
|
|
1482
|
+
const { bankCode, accountNumber } = parseCzechIban(account.iban);
|
|
1483
|
+
return { number: accountNumber, bankCode };
|
|
1484
|
+
}
|
|
1485
|
+
throw backendSdk.createInternalError(null, {
|
|
1486
|
+
message: "DBU: account has neither number+bankCode nor iban",
|
|
1487
|
+
code: "DBU_MISSING_ACCOUNT_DETAILS"
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1428
1490
|
async initiateInstantPayment(payment) {
|
|
1491
|
+
const creditor = this.resolveAccountDetails(payment.creditor);
|
|
1429
1492
|
const body = {
|
|
1430
|
-
accountNumberCredit:
|
|
1493
|
+
accountNumberCredit: creditor.number,
|
|
1431
1494
|
idAccountDebit: this.resolveIdAccountDebit(payment),
|
|
1432
|
-
bankCodeCredit:
|
|
1495
|
+
bankCodeCredit: creditor.bankCode,
|
|
1433
1496
|
amount: payment.amount,
|
|
1434
1497
|
currencyCode: "CZK",
|
|
1435
1498
|
channelCode: "WWW",
|
|
@@ -1461,11 +1524,13 @@ class DbuConnector extends IBankConnector {
|
|
|
1461
1524
|
if (payment.instructionPriority === "INST") {
|
|
1462
1525
|
return this.initiateInstantPayment(payment);
|
|
1463
1526
|
}
|
|
1527
|
+
const debtor = this.resolveAccountDetails(payment.debtor);
|
|
1528
|
+
const creditor = this.resolveAccountDetails(payment.creditor);
|
|
1464
1529
|
const body = {
|
|
1465
|
-
accountNumberDebit:
|
|
1466
|
-
accountNumberCredit:
|
|
1467
|
-
bankCodeDebit:
|
|
1468
|
-
bankCodeCredit:
|
|
1530
|
+
accountNumberDebit: debtor.number,
|
|
1531
|
+
accountNumberCredit: creditor.number,
|
|
1532
|
+
bankCodeDebit: debtor.bankCode,
|
|
1533
|
+
bankCodeCredit: creditor.bankCode,
|
|
1469
1534
|
amount: payment.amount,
|
|
1470
1535
|
currencyCode: "CZK",
|
|
1471
1536
|
actionTypeCode: "TRANSFER",
|
|
@@ -1482,20 +1547,14 @@ class DbuConnector extends IBankConnector {
|
|
|
1482
1547
|
constSymbol: payment.ks,
|
|
1483
1548
|
uniqueExternalId: payment.id,
|
|
1484
1549
|
with4EyeApproval: "Y",
|
|
1485
|
-
debtorAddress:
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
streetName: null,
|
|
1494
|
-
buildingNumber: null,
|
|
1495
|
-
city: null,
|
|
1496
|
-
zip: null,
|
|
1497
|
-
countryCode: payment.creditor.countryCode || "CZ"
|
|
1498
|
-
}
|
|
1550
|
+
debtorAddress: parseFlatAddress(
|
|
1551
|
+
payment.debtor.address,
|
|
1552
|
+
payment.debtor.countryCode || "CZ"
|
|
1553
|
+
),
|
|
1554
|
+
creditorAddress: parseFlatAddress(
|
|
1555
|
+
payment.creditor.address,
|
|
1556
|
+
payment.creditor.countryCode || "CZ"
|
|
1557
|
+
)
|
|
1499
1558
|
};
|
|
1500
1559
|
const response = await this.makeRequest(
|
|
1501
1560
|
"/required-transactions",
|
|
@@ -1549,7 +1608,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1549
1608
|
const limit = 10;
|
|
1550
1609
|
let hasMoreData = true;
|
|
1551
1610
|
let pageNumber = 1;
|
|
1552
|
-
const baseRequestId = backendSdk.uuidv4();
|
|
1611
|
+
const baseRequestId = backendSdk.uuidv4().replace(/-/g, "");
|
|
1553
1612
|
while (hasMoreData) {
|
|
1554
1613
|
const response = await this.makeRequest(
|
|
1555
1614
|
"/required-transactions",
|
|
@@ -1575,7 +1634,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1575
1634
|
}
|
|
1576
1635
|
for (const transaction of response.transactions.transaction) {
|
|
1577
1636
|
const status = transaction.status.trim();
|
|
1578
|
-
if (status === "0" || status === "50") continue;
|
|
1637
|
+
if (status === "0" || status === "50" || status === "51") continue;
|
|
1579
1638
|
try {
|
|
1580
1639
|
const payment = this.mapDbuTransactionToPayment(
|
|
1581
1640
|
transaction,
|
|
@@ -1888,6 +1947,7 @@ class ErsteConnector extends IBankConnector {
|
|
|
1888
1947
|
const paymentInsert = {
|
|
1889
1948
|
id: backendSdk.uuidv4(),
|
|
1890
1949
|
correlationId: backendSdk.uuidv4(),
|
|
1950
|
+
paymentRequestId: payment.entryDetails.transactionDetails.references.endToEndIdentification ?? null,
|
|
1891
1951
|
connectorKey: "ERSTE",
|
|
1892
1952
|
accountId: account.id,
|
|
1893
1953
|
bankRefId: payment.entryReference,
|
|
@@ -2040,7 +2100,6 @@ exports.KBConnector = KBConnector;
|
|
|
2040
2100
|
exports.MockCobsConnector = MockCobsConnector;
|
|
2041
2101
|
exports.MockConnector = MockConnector;
|
|
2042
2102
|
exports.assignAccount = assignAccount;
|
|
2043
|
-
exports.batchTransform = batchTransform;
|
|
2044
2103
|
exports.initiateConnector = initiateConnector;
|
|
2045
2104
|
exports.mapFinbricksTransactionStatus = mapFinbricksTransactionStatus;
|
|
2046
2105
|
exports.signFinbricksJws = signFinbricksJws;
|
package/dist/types.cjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const mock_connector = require('./shared/bank.
|
|
4
|
-
const paymentRequest_schema = require('./shared/bank.
|
|
3
|
+
const mock_connector = require('./shared/bank.DwyCCyd0.cjs');
|
|
4
|
+
const paymentRequest_schema = require('./shared/bank.BS7fFjGA.cjs');
|
|
5
5
|
const batchLifecycle = require('./shared/bank.Bg3Pdwm4.cjs');
|
|
6
6
|
const generalCodes = require('@develit-io/general-codes');
|
|
7
7
|
require('@develit-io/backend-sdk');
|
|
8
8
|
require('date-fns');
|
|
9
9
|
require('drizzle-orm');
|
|
10
|
+
require('drizzle-orm/sqlite-core');
|
|
10
11
|
require('jose');
|
|
11
12
|
require('node:crypto');
|
|
12
13
|
require('drizzle-orm/relations');
|
|
13
|
-
require('drizzle-orm/sqlite-core');
|
|
14
14
|
require('drizzle-zod');
|
|
15
15
|
|
|
16
16
|
|
|
@@ -26,7 +26,6 @@ exports.KBConnector = mock_connector.KBConnector;
|
|
|
26
26
|
exports.MockCobsConnector = mock_connector.MockCobsConnector;
|
|
27
27
|
exports.MockConnector = mock_connector.MockConnector;
|
|
28
28
|
exports.assignAccount = mock_connector.assignAccount;
|
|
29
|
-
exports.batchTransform = mock_connector.batchTransform;
|
|
30
29
|
exports.signFinbricksJws = mock_connector.signFinbricksJws;
|
|
31
30
|
exports.toBatchedPayment = mock_connector.toBatchedPayment;
|
|
32
31
|
exports.toBatchedPaymentFromPaymentRequest = mock_connector.toBatchedPaymentFromPaymentRequest;
|