@develit-services/bank 5.4.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import { uuidv4, createInternalError, useResult, structuredAddressSchema } from '@develit-io/backend-sdk';
2
2
  import { format, parseISO } from 'date-fns';
3
+ import { s as schema, h as account, i as accountCredentials, o as ott } from './bank.BzDNLxB_.mjs';
4
+ import { defineRelations, and, not, inArray, isNull } from 'drizzle-orm';
3
5
  import { importPKCS8, SignJWT } from 'jose';
4
6
  import { z } from 'zod';
5
7
  import { CURRENCY_CODES } from '@develit-io/general-codes';
6
- import { s as schema, h as account, i as accountCredentials, o as ott } from './bank.BzDNLxB_.mjs';
7
- import { defineRelations, and, not, inArray, isNull } from 'drizzle-orm';
8
8
  import { createInsertSchema, createUpdateSchema, createSelectSchema } from 'drizzle-orm/zod';
9
9
  import 'node:crypto';
10
10
 
@@ -750,6 +750,49 @@ const mapFinbricksTransactionToPayment = (tx, account) => {
750
750
  return base;
751
751
  };
752
752
 
753
+ function mod97(string) {
754
+ let checksum = string.slice(0, 2);
755
+ let fragment = "";
756
+ for (let offset = 2; offset < string.length; offset += 7) {
757
+ fragment = checksum + string.substring(offset, offset + 7);
758
+ checksum = (parseInt(fragment, 10) % 97).toString();
759
+ }
760
+ return parseInt(checksum, 10);
761
+ }
762
+ const parseCzechIban = (iban) => {
763
+ const stripped = iban.replace(/\s/g, "").toUpperCase();
764
+ if (!stripped.startsWith("CZ") || stripped.length !== 24) {
765
+ throw new Error(`Invalid Czech IBAN: ${iban}`);
766
+ }
767
+ const bankCode = stripped.slice(4, 8);
768
+ const prefix = stripped.slice(8, 14).replace(/^0+/, "");
769
+ const main = stripped.slice(14, 24).replace(/^0+/, "");
770
+ const accountNumber = prefix ? `${prefix}-${main}` : main;
771
+ return { bankCode, accountNumber };
772
+ };
773
+ const calculateCzechIban = (accountNumber, bankCode) => {
774
+ const paddedBankCode = bankCode.padStart(4, "0");
775
+ let prefix = "";
776
+ let mainAccount = accountNumber;
777
+ if (accountNumber.includes("-")) {
778
+ const parts = accountNumber.split("-");
779
+ if (parts.length !== 2) {
780
+ throw new Error(
781
+ `Invalid account number format: expected "prefix-main" or "main", got "${accountNumber}"`
782
+ );
783
+ }
784
+ prefix = parts[0];
785
+ mainAccount = parts[1];
786
+ }
787
+ const paddedPrefix = prefix.padStart(6, "0");
788
+ const paddedAccount = mainAccount.padStart(10, "0");
789
+ const basicIban = paddedBankCode + paddedPrefix + paddedAccount;
790
+ const rearranged = basicIban + "123500";
791
+ const remainder = mod97(rearranged);
792
+ const checkDigits = (98 - remainder).toString().padStart(2, "0");
793
+ return `CZ${checkDigits}${basicIban}`;
794
+ };
795
+
753
796
  function autoVariableSymbol(paymentId) {
754
797
  let hash = 0;
755
798
  for (let i = 0; i < paymentId.length; i++) {
@@ -772,6 +815,20 @@ function resolveAccountIdentification(account, currency) {
772
815
  message: "Account has neither IBAN nor account number \u2014 cannot identify for foreign payment"
773
816
  });
774
817
  }
818
+ function resolveCzechDomesticIban(account) {
819
+ if (!account.number || !account.bankCode) {
820
+ return account.iban;
821
+ }
822
+ const derived = calculateCzechIban(account.number, account.bankCode);
823
+ if (account.iban && account.iban !== derived) {
824
+ throw createInternalError(null, {
825
+ status: 422,
826
+ code: "VALID-B-017",
827
+ message: `Czech account ${account.number}/${account.bankCode} derives IBAN ${derived}, which conflicts with the stored IBAN ${account.iban}. Refusing to guess which is correct.`
828
+ });
829
+ }
830
+ return derived;
831
+ }
775
832
  function mapToFinbricksAddress(addr) {
776
833
  if (!addr) return void 0;
777
834
  return {
@@ -995,14 +1052,14 @@ class FinbricksConnector extends IBankConnector {
995
1052
  body: {
996
1053
  batchPaymentIdentification: {
997
1054
  merchantId: this.finbricks.MERCHANT_ID,
998
- debtorAccountIban: payments[0].debtor.iban,
1055
+ debtorAccountIban: resolveCzechDomesticIban(payments[0].debtor),
999
1056
  clientId,
1000
1057
  callbackUrl: `${this.finbricks.REDIRECT_URI}?type=batch&batchId=${batchId}`,
1001
1058
  merchantBatchId: batchId
1002
1059
  },
1003
1060
  payments: payments.map((p) => ({
1004
1061
  merchantTransactionId: p.id,
1005
- creditorAccountIban: p.creditor.iban,
1062
+ creditorAccountIban: resolveCzechDomesticIban(p.creditor),
1006
1063
  amount: p.amount,
1007
1064
  variableSymbol: p.vs ?? (p.ss ? void 0 : autoVariableSymbol(p.id)),
1008
1065
  specificSymbol: p.ss,
@@ -1047,7 +1104,7 @@ class FinbricksConnector extends IBankConnector {
1047
1104
  payment.currency
1048
1105
  );
1049
1106
  const creditorBic = payment.creditor.swiftBic ?? payment.creditor.bicCor;
1050
- const creditorAgent = creditorIdentification.type === "BBAN" && creditorBic ? {
1107
+ const creditorAgent = creditorBic ? {
1051
1108
  financialInstitutionIdentification: { bic: creditorBic }
1052
1109
  } : void 0;
1053
1110
  const reference = buildPaymentReference({
@@ -1183,6 +1240,10 @@ class FinbricksConnector extends IBankConnector {
1183
1240
  };
1184
1241
  }
1185
1242
  async initiateDomesticPayment(payment) {
1243
+ const debtorAccount = this.connectedAccounts.find(
1244
+ (acc) => acc.iban === payment.debtor.iban
1245
+ );
1246
+ const clientId = await this.getClientId(debtorAccount.id);
1186
1247
  const [response, error] = await useResult(
1187
1248
  this.finbricks.request({
1188
1249
  endpoint: FINBRICKS_ENDPOINTS.TRANSACTION_INIT,
@@ -1191,8 +1252,8 @@ class FinbricksConnector extends IBankConnector {
1191
1252
  merchantId: this.finbricks.MERCHANT_ID,
1192
1253
  merchantTransactionId: payment.id,
1193
1254
  totalPrice: payment.amount,
1194
- debtorAccountIban: payment.debtor.iban,
1195
- creditorAccountIban: payment.creditor.iban,
1255
+ debtorAccountIban: resolveCzechDomesticIban(payment.debtor),
1256
+ creditorAccountIban: resolveCzechDomesticIban(payment.creditor),
1196
1257
  creditorName: payment.creditor.holderName,
1197
1258
  description: this.PROVIDER === "CSOB" ? composeWithUltimateCreditor(payment.message, payment.creditor, {
1198
1259
  stripDiacritics: payment.currency !== "CZK"
@@ -1201,6 +1262,7 @@ class FinbricksConnector extends IBankConnector {
1201
1262
  specificSymbol: payment.ss,
1202
1263
  constantSymbol: payment.ks,
1203
1264
  callbackUrl: `${this.finbricks.REDIRECT_URI}?type=paymentRequest&paymentRequestId=${payment.id}`,
1265
+ clientId,
1204
1266
  paymentProvider: this.PROVIDER,
1205
1267
  instructionPriority: payment.instructionPriority ?? "NORM"
1206
1268
  }
@@ -2407,74 +2469,6 @@ const ottInsertSchema = createInsertSchema(ott);
2407
2469
  const ottUpdateSchema = createUpdateSchema(ott);
2408
2470
  const ottSelectSchema = createSelectSchema(ott);
2409
2471
 
2410
- class AirBankConnector extends FinbricksConnector {
2411
- constructor(config) {
2412
- super("AIRBANK", config);
2413
- }
2414
- supportsBatch() {
2415
- return false;
2416
- }
2417
- }
2418
-
2419
- class CreditasConnector extends FinbricksConnector {
2420
- constructor(config) {
2421
- super("CREDITAS", config);
2422
- }
2423
- /**
2424
- * Creditas bank doesn't support batch payments at all.
2425
- */
2426
- supportsBatch() {
2427
- return false;
2428
- }
2429
- }
2430
-
2431
- class CSASConnector extends FinbricksConnector {
2432
- constructor(config) {
2433
- super("CSAS", config);
2434
- }
2435
- supportsBatch(paymentType) {
2436
- return paymentType === "DOMESTIC";
2437
- }
2438
- }
2439
-
2440
- class FioConnector extends FinbricksConnector {
2441
- constructor(config) {
2442
- super("FIO", config);
2443
- }
2444
- /**
2445
- * FIO supports batch only for DOMESTIC (CZK) payments.
2446
- * SEPA and SWIFT batch support is planned by Finbricks.
2447
- */
2448
- supportsBatch(paymentType) {
2449
- return paymentType === "DOMESTIC";
2450
- }
2451
- }
2452
-
2453
- class MonetaConnector extends FinbricksConnector {
2454
- constructor(config) {
2455
- super("MONETA", config);
2456
- }
2457
- /**
2458
- * MONETA supports batch only for DOMESTIC (CZK) payments.
2459
- * SEPA and SWIFT batch support is planned by Finbricks.
2460
- */
2461
- supportsBatch(paymentType) {
2462
- return paymentType === "DOMESTIC";
2463
- }
2464
- /**
2465
- * MONETA rejects all special characters in endToEndIdentification.
2466
- * Boundaries between segments are unambiguous without a separator —
2467
- * `VS`/`SS`/`KS` prefixes are alphabetic and values are always numeric,
2468
- * so the parsing regex `VS[:\s]*(\d+)` extracts each value correctly.
2469
- *
2470
- * Format: `VS1234SS5678KS0308` (max 30 chars).
2471
- * Confirmed by Finbricks support (2026-04-23).
2472
- */
2473
- buildEndToEndId(payment) {
2474
- return buildEndToEndId(payment, { separator: "" });
2475
- }
2476
- }
2477
-
2478
2472
  const isDeposit = (payment, creditorIban) => {
2479
2473
  return payment.creditor.iban === creditorIban;
2480
2474
  };
@@ -2483,177 +2477,4 @@ const getPaymentDirection = (payment, iban) => {
2483
2477
  return "OUTGOING";
2484
2478
  };
2485
2479
 
2486
- const initiateConnector = async ({
2487
- bank,
2488
- env,
2489
- connectedAccounts,
2490
- resolveCredentials
2491
- }) => {
2492
- switch (bank) {
2493
- case "ERSTE":
2494
- return new ErsteConnector({
2495
- API_KEY: (await env.SECRETS_STORE.get({
2496
- secretName: "BANK_SERVICE_ERSTE_API_KEY"
2497
- })).data?.secretValue || "",
2498
- CLIENT_ID: (await env.SECRETS_STORE.get({
2499
- secretName: "BANK_SERVICE_ERSTE_CLIENT_ID"
2500
- })).data?.secretValue || "",
2501
- CLIENT_SECRET: (await env.SECRETS_STORE.get({
2502
- secretName: "BANK_SERVICE_ERSTE_CLIENT_SECRET"
2503
- })).data?.secretValue || "",
2504
- REDIRECT_URI: env.REDIRECT_URI,
2505
- AUTH_URI: env.ERSTE_AUTH_URI,
2506
- PAYMENTS_URI: env.ERSTE_PAYMENTS_URI,
2507
- ACCOUNTS_URI: env.ERSTE_ACCOUNTS_URI,
2508
- connectedAccounts,
2509
- resolveCredentials
2510
- });
2511
- case "CREDITAS":
2512
- return new CreditasConnector({
2513
- BASE_URI: env.FINBRICKS_BASE_URI,
2514
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2515
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2516
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2517
- })).data?.secretValue || "",
2518
- REDIRECT_URI: env.REDIRECT_URI,
2519
- connectedAccounts,
2520
- resolveCredentials
2521
- });
2522
- case "MOCK_COBS":
2523
- return new MockCobsConnector({
2524
- BASE_URI: env.FINBRICKS_BASE_URI,
2525
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2526
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2527
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2528
- })).data?.secretValue || "",
2529
- REDIRECT_URI: env.REDIRECT_URI,
2530
- connectedAccounts,
2531
- resolveCredentials
2532
- });
2533
- case "FIO":
2534
- return new FioConnector({
2535
- BASE_URI: env.FINBRICKS_BASE_URI,
2536
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2537
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2538
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2539
- })).data?.secretValue || "",
2540
- REDIRECT_URI: env.REDIRECT_URI,
2541
- connectedAccounts,
2542
- resolveCredentials
2543
- });
2544
- case "MONETA":
2545
- return new MonetaConnector({
2546
- BASE_URI: env.FINBRICKS_BASE_URI,
2547
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2548
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2549
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2550
- })).data?.secretValue || "",
2551
- REDIRECT_URI: env.REDIRECT_URI,
2552
- connectedAccounts,
2553
- resolveCredentials
2554
- });
2555
- case "AIRBANK":
2556
- return new AirBankConnector({
2557
- BASE_URI: env.FINBRICKS_BASE_URI,
2558
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2559
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2560
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2561
- })).data?.secretValue || "",
2562
- REDIRECT_URI: env.REDIRECT_URI,
2563
- connectedAccounts,
2564
- resolveCredentials
2565
- });
2566
- case "CSAS":
2567
- return new CSASConnector({
2568
- BASE_URI: env.FINBRICKS_BASE_URI,
2569
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2570
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2571
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2572
- })).data?.secretValue || "",
2573
- REDIRECT_URI: env.REDIRECT_URI,
2574
- connectedAccounts,
2575
- resolveCredentials
2576
- });
2577
- case "KB":
2578
- return new KBConnector({
2579
- BASE_URI: env.FINBRICKS_BASE_URI,
2580
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2581
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2582
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2583
- })).data?.secretValue || "",
2584
- REDIRECT_URI: env.REDIRECT_URI,
2585
- connectedAccounts,
2586
- resolveCredentials
2587
- });
2588
- case "CSOB":
2589
- return new CsobConnector({
2590
- BASE_URI: env.FINBRICKS_BASE_URI,
2591
- MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
2592
- PRIVATE_KEY_PEM: (await env.SECRETS_STORE.get({
2593
- secretName: "BANK_SERVICE_FINBRICKS_PRIVATE_KEY_PEM"
2594
- })).data?.secretValue || "",
2595
- REDIRECT_URI: env.REDIRECT_URI,
2596
- connectedAccounts,
2597
- resolveCredentials
2598
- });
2599
- case "DBU":
2600
- return new DbuConnector({
2601
- BASE_URL: env.DBUCS_BASE_URI,
2602
- USERNAME: env.DBUCS_USERNAME,
2603
- APPLICATION_CODE: env.DBUCS_APPLICATION_CODE,
2604
- KV: env.BANK_KV,
2605
- API: env.DBU_CBS_BACKOFFICE_DEV,
2606
- REDIRECT_URI: env.REDIRECT_URI,
2607
- TX_AUTH_URI: env.DBUCS_TX_AUTH_URI,
2608
- connectedAccounts
2609
- });
2610
- default:
2611
- const mockConnector = new MockConnector();
2612
- mockConnector.connectedAccounts = connectedAccounts;
2613
- return mockConnector;
2614
- }
2615
- };
2616
- function mod97(string) {
2617
- let checksum = string.slice(0, 2);
2618
- let fragment = "";
2619
- for (let offset = 2; offset < string.length; offset += 7) {
2620
- fragment = checksum + string.substring(offset, offset + 7);
2621
- checksum = (parseInt(fragment, 10) % 97).toString();
2622
- }
2623
- return parseInt(checksum, 10);
2624
- }
2625
- const parseCzechIban = (iban) => {
2626
- const stripped = iban.replace(/\s/g, "").toUpperCase();
2627
- if (!stripped.startsWith("CZ") || stripped.length !== 24) {
2628
- throw new Error(`Invalid Czech IBAN: ${iban}`);
2629
- }
2630
- const bankCode = stripped.slice(4, 8);
2631
- const prefix = stripped.slice(8, 14).replace(/^0+/, "");
2632
- const main = stripped.slice(14, 24).replace(/^0+/, "");
2633
- const accountNumber = prefix ? `${prefix}-${main}` : main;
2634
- return { bankCode, accountNumber };
2635
- };
2636
- const calculateCzechIban = (accountNumber, bankCode) => {
2637
- const paddedBankCode = bankCode.padStart(4, "0");
2638
- let prefix = "";
2639
- let mainAccount = accountNumber;
2640
- if (accountNumber.includes("-")) {
2641
- const parts = accountNumber.split("-");
2642
- if (parts.length !== 2) {
2643
- throw new Error(
2644
- `Invalid account number format: expected "prefix-main" or "main", got "${accountNumber}"`
2645
- );
2646
- }
2647
- prefix = parts[0];
2648
- mainAccount = parts[1];
2649
- }
2650
- const paddedPrefix = prefix.padStart(6, "0");
2651
- const paddedAccount = mainAccount.padStart(10, "0");
2652
- const basicIban = paddedBankCode + paddedPrefix + paddedAccount;
2653
- const rearranged = basicIban + "123500";
2654
- const remainder = mod97(rearranged);
2655
- const checkDigits = (98 - remainder).toString().padStart(2, "0");
2656
- return `CZ${checkDigits}${basicIban}`;
2657
- };
2658
-
2659
- export { useFinbricksFetch as A, BASE_TERMINAL_STATUSES as B, CsobConnector as C, DbuConnector as D, ErsteConnector as E, FINBRICKS_ENDPOINTS as F, tables as G, relations as H, IBankConnector as I, initiateConnector as J, KBConnector as K, getNonTerminalPaymentRequestsQuery as L, MockCobsConnector as M, calculateCzechIban as N, FinbricksClient as a, FinbricksConnector as b, MockConnector as c, accountCredentialsInsertSchema as d, accountCredentialsSelectSchema as e, accountCredentialsUpdateSchema as f, accountInsertSchema as g, accountSelectSchema as h, accountUpdateSchema as i, assignAccount as j, dbuAccountConfigSchema as k, hasPaymentAccountAssigned as l, isPaymentCompleted as m, isPendingStatus as n, isProcessedStatus as o, isTerminalStatus as p, ottInsertSchema as q, ottSelectSchema as r, ottUpdateSchema as s, signFinbricksJws as t, toBatchedPayment as u, toBatchedPaymentFromPaymentRequest as v, toCompletedPayment as w, toIncomingPayment as x, toPaymentRequestInsert as y, toPreparedPayment as z };
2480
+ export { useFinbricksFetch as A, BASE_TERMINAL_STATUSES as B, CsobConnector as C, DbuConnector as D, ErsteConnector as E, FINBRICKS_ENDPOINTS as F, tables as G, relations as H, IBankConnector as I, buildEndToEndId as J, KBConnector as K, getNonTerminalPaymentRequestsQuery as L, MockCobsConnector as M, calculateCzechIban as N, FinbricksClient as a, FinbricksConnector as b, MockConnector as c, accountCredentialsInsertSchema as d, accountCredentialsSelectSchema as e, accountCredentialsUpdateSchema as f, accountInsertSchema as g, accountSelectSchema as h, accountUpdateSchema as i, assignAccount as j, dbuAccountConfigSchema as k, hasPaymentAccountAssigned as l, isPaymentCompleted as m, isPendingStatus as n, isProcessedStatus as o, isTerminalStatus as p, ottInsertSchema as q, ottSelectSchema as r, ottUpdateSchema as s, signFinbricksJws as t, toBatchedPayment as u, toBatchedPaymentFromPaymentRequest as v, toCompletedPayment as w, toIncomingPayment as x, toPaymentRequestInsert as y, toPreparedPayment as z };