@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.
Files changed (32) hide show
  1. package/dist/database/schema.cjs +1 -1
  2. package/dist/database/schema.d.cts +1 -1
  3. package/dist/database/schema.d.mts +1 -1
  4. package/dist/database/schema.d.ts +1 -1
  5. package/dist/database/schema.mjs +1 -1
  6. package/dist/export/worker.cjs +583 -146
  7. package/dist/export/worker.d.cts +251 -39
  8. package/dist/export/worker.d.mts +251 -39
  9. package/dist/export/worker.d.ts +251 -39
  10. package/dist/export/worker.mjs +584 -147
  11. package/dist/export/workflows.cjs +132 -16
  12. package/dist/export/workflows.mjs +133 -17
  13. package/dist/shared/{bank.C-T1FQxg.cjs → bank.62VzK9Aj.cjs} +1 -1
  14. package/dist/shared/{bank.SQ4Mmr8u.cjs → bank.BS7fFjGA.cjs} +34 -4
  15. package/dist/shared/{bank.B6U8sUZn.d.mts → bank.BYRq3yJf.d.ts} +17 -7
  16. package/dist/shared/{bank.C4VOdIx1.mjs → bank.C0UN6luZ.mjs} +34 -4
  17. package/dist/shared/{bank.DDHrdFgy.mjs → bank.CA5ytXxp.mjs} +1 -1
  18. package/dist/shared/{bank.BoMDujsl.d.ts → bank.CO89tR9U.d.cts} +17 -7
  19. package/dist/shared/{bank.BliD3oCT.d.ts → bank.Cns5ss41.d.cts} +47 -10
  20. package/dist/shared/{bank.BliD3oCT.d.cts → bank.Cns5ss41.d.mts} +47 -10
  21. package/dist/shared/{bank.BliD3oCT.d.mts → bank.Cns5ss41.d.ts} +47 -10
  22. package/dist/shared/{bank.lbzMqyr3.d.cts → bank.CreoSb2d.d.mts} +17 -7
  23. package/dist/shared/{bank.DRrBrAdI.mjs → bank.D1jqaHaF.mjs} +91 -31
  24. package/dist/shared/{bank.Cpy9PULF.mjs → bank.DEmzZGZW.mjs} +31 -5
  25. package/dist/shared/{bank.BOnP9p9Y.cjs → bank.Dm8GHThw.cjs} +31 -5
  26. package/dist/shared/{bank.CQURey1E.cjs → bank.DwyCCyd0.cjs} +90 -31
  27. package/dist/types.cjs +3 -4
  28. package/dist/types.d.cts +8 -16
  29. package/dist/types.d.mts +8 -16
  30. package/dist/types.d.ts +8 -16
  31. package/dist/types.mjs +3 -3
  32. 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.C4VOdIx1.mjs';
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
- //TODO(kleinpetr): use real correlationId once available
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 || tx.entryDetails.transactionDetails?.references?.endToEndIdentification
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 + "1215";
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: payment.creditor.number,
1491
+ accountNumberCredit: creditor.number,
1429
1492
  idAccountDebit: this.resolveIdAccountDebit(payment),
1430
- bankCodeCredit: payment.creditor.bankCode,
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: payment.debtor.number,
1464
- accountNumberCredit: payment.creditor.number,
1465
- bankCodeDebit: payment.debtor.bankCode,
1466
- bankCodeCredit: payment.creditor.bankCode,
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
- streetName: null,
1485
- buildingNumber: null,
1486
- city: null,
1487
- zip: null,
1488
- countryCode: payment.debtor.countryCode || "CZ"
1489
- },
1490
- creditorAddress: {
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, batchTransform as e, toBatchedPaymentFromPaymentRequest as f, toCompletedPayment as g, toIncomingPayment as h, toPaymentRequestInsert as i, toPreparedPayment as j, initiateConnector as k, mapFinbricksTransactionStatus as m, signFinbricksJws as s, toBatchedPayment as t, useFinbricksFetch as u };
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 { eq } from 'drizzle-orm';
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.DDHrdFgy.mjs';
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(eq(tables.paymentRequest.id, id)).returning()
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(eq(tables.paymentRequest.batchId, batchId));
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('./bank.SQ4Mmr8u.cjs');
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.C-T1FQxg.cjs');
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(drizzleOrm.eq(tables.paymentRequest.id, id)).returning()
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(drizzleOrm.eq(tables.paymentRequest.batchId, batchId));
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.SQ4Mmr8u.cjs');
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
- //TODO(kleinpetr): use real correlationId once available
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 || tx.entryDetails.transactionDetails?.references?.endToEndIdentification
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 + "1215";
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: payment.creditor.number,
1493
+ accountNumberCredit: creditor.number,
1431
1494
  idAccountDebit: this.resolveIdAccountDebit(payment),
1432
- bankCodeCredit: payment.creditor.bankCode,
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: payment.debtor.number,
1466
- accountNumberCredit: payment.creditor.number,
1467
- bankCodeDebit: payment.debtor.bankCode,
1468
- bankCodeCredit: payment.creditor.bankCode,
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
- streetName: null,
1487
- buildingNumber: null,
1488
- city: null,
1489
- zip: null,
1490
- countryCode: payment.debtor.countryCode || "CZ"
1491
- },
1492
- creditorAddress: {
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.CQURey1E.cjs');
4
- const paymentRequest_schema = require('./shared/bank.SQ4Mmr8u.cjs');
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;