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