@develit-services/bank 5.0.1 → 5.2.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,8 +1,8 @@
1
1
  import { first, uuidv4, asNonEmpty } from '@develit-io/backend-sdk';
2
- import { G as tables, H as relations, v as toBatchedPaymentFromPaymentRequest, z as toPreparedPayment, J as initiateConnector, m as isPaymentCompleted } from '../shared/bank.CZ8MQDPa.mjs';
2
+ import { G as tables, H as relations, v as toBatchedPaymentFromPaymentRequest, z as toPreparedPayment, J as initiateConnector, m as isPaymentCompleted } from '../shared/bank.Bkxo76q4.mjs';
3
3
  import { i as isBatchAuthorized, b as isBatchFailed, d as isBatchProcessing } from '../shared/bank.XqSw509X.mjs';
4
4
  import { eq, and, inArray } from 'drizzle-orm';
5
- import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, e as updatePaymentRequestStatusCommand, f as createPaymentCommand } from '../shared/bank.BdTj54NO.mjs';
5
+ import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, e as updatePaymentRequestStatusCommand, f as createPaymentCommand } from '../shared/bank.BRD2WfnT.mjs';
6
6
  import { WorkflowEntrypoint } from 'cloudflare:workers';
7
7
  import { NonRetryableError } from 'cloudflare:workflows';
8
8
  import { drizzle } from 'drizzle-orm/d1';
@@ -304,6 +304,7 @@ function createWorkflowLogger(instanceId) {
304
304
  }
305
305
 
306
306
  function getStepCount(ctx) {
307
+ if (!ctx) return 0;
307
308
  return ctx.step?.count ?? 0;
308
309
  }
309
310
 
@@ -312,12 +313,16 @@ async function pushToQueue(queue, message) {
312
313
  await queue.send(message, { contentType: "v8" });
313
314
  return;
314
315
  }
315
- await queue.sendBatch(
316
- message.map((m) => ({
317
- body: m,
318
- contentType: "v8"
319
- }))
320
- );
316
+ const QUEUE_BATCH_SIZE = 100;
317
+ for (let i = 0; i < message.length; i += QUEUE_BATCH_SIZE) {
318
+ const chunk = message.slice(i, i + QUEUE_BATCH_SIZE);
319
+ await queue.sendBatch(
320
+ chunk.map((m) => ({
321
+ body: m,
322
+ contentType: "v8"
323
+ }))
324
+ );
325
+ }
321
326
  }
322
327
  class BankSyncAccountPayments extends WorkflowEntrypoint {
323
328
  async run(event, step) {
@@ -343,13 +348,13 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
343
348
  if (!account.lastSyncAt) {
344
349
  throw new Error(`lastSyncedAt is not set for account: ${accountId}`);
345
350
  }
346
- const payments = await step.do(
347
- "fetch bank payments",
351
+ await step.do(
352
+ "fetch and process payments",
348
353
  {
349
354
  retries: { limit: 5, delay: "2 minutes", backoff: "exponential" },
350
- timeout: "2 minutes"
355
+ timeout: "5 minutes"
351
356
  },
352
- async () => {
357
+ async (ctx) => {
353
358
  try {
354
359
  logger.info("payments.fetch.started", {
355
360
  accountId,
@@ -372,19 +377,192 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
372
377
  }
373
378
  ]
374
379
  });
375
- const result = await connector.getAllAccountPayments({
380
+ const payments = await connector.getAllAccountPayments({
376
381
  account,
377
382
  filter: { dateFrom: account.lastSyncAt }
378
383
  });
379
384
  logger.info("payments.fetch.completed", {
380
385
  accountId,
381
386
  connectorKey: account.connectorKey,
382
- paymentsCount: result.length
387
+ paymentsCount: payments.length
388
+ });
389
+ const paymentsToProcess = payments.filter(
390
+ (p) => isPaymentCompleted(p.parsed)
391
+ );
392
+ logger.info("payments.filtered.toProcess", {
393
+ accountId,
394
+ totalFetched: payments.length,
395
+ paymentsToProcess: paymentsToProcess.length,
396
+ sampleStatuses: payments.slice(0, 5).map((p) => ({
397
+ bankRefId: p.parsed.bankRefId,
398
+ status: p.parsed.status,
399
+ isCompleted: isPaymentCompleted(p.parsed)
400
+ }))
401
+ });
402
+ const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
403
+ const paymentsToInsert = paymentsToProcess.filter(
404
+ (p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
405
+ );
406
+ logger.info("payments.filtered.toInsert", {
407
+ accountId,
408
+ paymentsToProcess: paymentsToProcess.length,
409
+ paymentsToInsert: paymentsToInsert.length,
410
+ lastSyncBankRefIdsCount: lastSyncBankRefIds.length,
411
+ sampleLastSyncBankRefIds: lastSyncBankRefIds.slice(0, 10),
412
+ sampleToInsert: paymentsToInsert.slice(0, 5).map((p) => p.parsed.bankRefId)
413
+ });
414
+ const eventsToEmit = [];
415
+ const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
416
+ const BANK_REF_ID_CHUNK_SIZE = 90;
417
+ const matchingRequests = [];
418
+ for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
419
+ const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
420
+ const rows = await db.select().from(tables.paymentRequest).where(
421
+ and(
422
+ inArray(tables.paymentRequest.bankRefId, chunkIds),
423
+ eq(tables.paymentRequest.accountId, account.id),
424
+ eq(
425
+ tables.paymentRequest.connectorKey,
426
+ account.connectorKey
427
+ )
428
+ )
429
+ );
430
+ matchingRequests.push(...rows);
431
+ }
432
+ const requestByBankRefId = Object.fromEntries(
433
+ matchingRequests.map((r) => [r.bankRefId, r])
434
+ );
435
+ const enrichedPayments = paymentsToInsert.map((p) => {
436
+ const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
437
+ if (!req) return p;
438
+ return {
439
+ ...p,
440
+ parsed: {
441
+ ...p.parsed,
442
+ // queue-bus: transaction matching (DBU doesn't echo these in statements)
443
+ vs: p.parsed.vs ?? req.vs,
444
+ ss: p.parsed.ss ?? req.ss,
445
+ ks: p.parsed.ks ?? req.ks,
446
+ message: p.parsed.message ?? req.message,
447
+ // queue-bus: party creation
448
+ creditor: {
449
+ ...p.parsed.creditor,
450
+ holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
451
+ },
452
+ debtor: {
453
+ ...p.parsed.debtor,
454
+ holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
455
+ }
456
+ // NOT enriched: chargeBearer, instructionPriority, refId, batchId,
457
+ // createdAt, address, swiftBic — no downstream consumer in payment table
458
+ }
459
+ };
460
+ });
461
+ const createCommands = enrichedPayments.map(
462
+ (p) => createPaymentCommand(db, { payment: p.parsed }).command
463
+ );
464
+ logger.info("payments.commands.created", {
465
+ accountId,
466
+ createCommandsCount: createCommands.length,
467
+ enrichedPaymentsCount: enrichedPayments.length
468
+ });
469
+ eventsToEmit.push(
470
+ ...enrichedPayments.map((p) => ({
471
+ eventType: "BANK_PAYMENT",
472
+ eventSignal: "paymentFetched",
473
+ bankPayment: p.parsed,
474
+ metadata: {
475
+ correlationId: p.parsed.correlationId,
476
+ entityId: p.parsed.id,
477
+ timestamp: /* @__PURE__ */ new Date()
478
+ }
479
+ }))
480
+ );
481
+ const lastSyncMetadata = {
482
+ payments: payments.length,
483
+ paymentsToProcess: paymentsToProcess.length,
484
+ paymentsInserted: paymentsToInsert.length,
485
+ lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
486
+ lastSyncPayments: lastSyncBankRefIds.length,
487
+ eventsEmitted: eventsToEmit.length,
488
+ iterationCount: getStepCount(ctx),
489
+ workflowStartedAt
490
+ };
491
+ const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
492
+ accountId: account.id,
493
+ lastSyncAt: now,
494
+ lastSyncMetadata
495
+ }).command;
496
+ logger.info("payments.database.beforeBatch", {
497
+ accountId,
498
+ createCommandsCount: createCommands.length,
499
+ willUseBatch: createCommands.length > 0
383
500
  });
384
- return result;
501
+ if (createCommands.length) {
502
+ logger.info("payments.database.batchStart", {
503
+ accountId,
504
+ totalCommands: createCommands.length + 1,
505
+ paymentsToInsert: createCommands.length
506
+ });
507
+ const BATCH_CHUNK_SIZE = 90;
508
+ let totalInserted = 0;
509
+ for (let i = 0; i < createCommands.length; i += BATCH_CHUNK_SIZE) {
510
+ const chunkCommands = createCommands.slice(
511
+ i,
512
+ i + BATCH_CHUNK_SIZE
513
+ );
514
+ const isLastChunk = i + BATCH_CHUNK_SIZE >= createCommands.length;
515
+ const batchCommands = isLastChunk ? asNonEmpty([updateLastSyncCommand, ...chunkCommands]) : asNonEmpty(chunkCommands);
516
+ logger.info("payments.database.batchChunk", {
517
+ accountId,
518
+ chunkIndex: Math.floor(i / BATCH_CHUNK_SIZE),
519
+ chunkSize: chunkCommands.length,
520
+ isLastChunk
521
+ });
522
+ await db.batch(batchCommands);
523
+ totalInserted += chunkCommands.length;
524
+ }
525
+ logger.info("payments.database.batchComplete", {
526
+ accountId,
527
+ inserted: totalInserted,
528
+ chunks: Math.ceil(createCommands.length / BATCH_CHUNK_SIZE)
529
+ });
530
+ } else {
531
+ logger.info("payments.database.updateOnly", {
532
+ accountId,
533
+ reason: "no new payments to insert"
534
+ });
535
+ await updateLastSyncCommand.execute();
536
+ logger.info("payments.database.updateComplete", {
537
+ accountId
538
+ });
539
+ }
540
+ if (eventsToEmit.length) {
541
+ logger.info("payments.queue.sending", {
542
+ accountId,
543
+ eventsCount: eventsToEmit.length
544
+ });
545
+ await pushToQueue(
546
+ this.env.QUEUE_BUS_QUEUE,
547
+ eventsToEmit
548
+ );
549
+ logger.info("payments.queue.sent", {
550
+ accountId,
551
+ eventsCount: eventsToEmit.length
552
+ });
553
+ }
554
+ logger.info("payments.process.complete", {
555
+ accountId,
556
+ ...lastSyncMetadata,
557
+ newLastSyncAt: now
558
+ });
559
+ return {
560
+ ...lastSyncMetadata,
561
+ newLastSyncAt: now
562
+ };
385
563
  } catch (err) {
386
564
  const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
387
- logger.error("payments.fetch.failed", {
565
+ logger.error("payments.process.failed", {
388
566
  accountId,
389
567
  connectorKey: account.connectorKey,
390
568
  error: message
@@ -393,109 +571,6 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
393
571
  }
394
572
  }
395
573
  );
396
- const paymentsToProcess = payments.filter(
397
- (p) => isPaymentCompleted(p.parsed)
398
- );
399
- const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
400
- const paymentsToInsert = paymentsToProcess.filter(
401
- (p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
402
- );
403
- await step.do(
404
- "process new payments and update lastSyncAt",
405
- async (ctx) => {
406
- const eventsToEmit = [];
407
- const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
408
- const BANK_REF_ID_CHUNK_SIZE = 90;
409
- const matchingRequests = [];
410
- for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
411
- const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
412
- const rows = await db.select().from(tables.paymentRequest).where(
413
- and(
414
- inArray(tables.paymentRequest.bankRefId, chunkIds),
415
- eq(tables.paymentRequest.accountId, account.id),
416
- eq(tables.paymentRequest.connectorKey, account.connectorKey)
417
- )
418
- );
419
- matchingRequests.push(...rows);
420
- }
421
- const requestByBankRefId = Object.fromEntries(
422
- matchingRequests.map((r) => [r.bankRefId, r])
423
- );
424
- const enrichedPayments = paymentsToInsert.map((p) => {
425
- const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
426
- if (!req) return p;
427
- return {
428
- ...p,
429
- parsed: {
430
- ...p.parsed,
431
- // queue-bus: transaction matching (DBU doesn't echo these in statements)
432
- vs: p.parsed.vs ?? req.vs,
433
- ss: p.parsed.ss ?? req.ss,
434
- ks: p.parsed.ks ?? req.ks,
435
- message: p.parsed.message ?? req.message,
436
- // queue-bus: party creation
437
- creditor: {
438
- ...p.parsed.creditor,
439
- holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
440
- },
441
- debtor: {
442
- ...p.parsed.debtor,
443
- holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
444
- }
445
- // NOT enriched: chargeBearer, instructionPriority, refId, batchId,
446
- // createdAt, address, swiftBic — no downstream consumer in payment table
447
- }
448
- };
449
- });
450
- const createCommands = enrichedPayments.map(
451
- (p) => createPaymentCommand(db, { payment: p.parsed }).command
452
- );
453
- eventsToEmit.push(
454
- ...enrichedPayments.map((p) => ({
455
- eventType: "BANK_PAYMENT",
456
- eventSignal: "paymentFetched",
457
- bankPayment: p.parsed,
458
- metadata: {
459
- correlationId: p.parsed.correlationId,
460
- entityId: p.parsed.id,
461
- timestamp: /* @__PURE__ */ new Date()
462
- }
463
- }))
464
- );
465
- const lastSyncMetadata = {
466
- payments: payments.length,
467
- paymentsToProcess: paymentsToProcess.length,
468
- paymentsInserted: paymentsToInsert.length,
469
- lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
470
- lastSyncPayments: lastSyncBankRefIds.length,
471
- eventsEmitted: eventsToEmit.length,
472
- iterationCount: getStepCount(ctx),
473
- workflowStartedAt
474
- };
475
- const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
476
- accountId: account.id,
477
- lastSyncAt: now,
478
- lastSyncMetadata
479
- }).command;
480
- if (createCommands.length) {
481
- await db.batch(
482
- asNonEmpty([updateLastSyncCommand, ...createCommands])
483
- );
484
- } else {
485
- await updateLastSyncCommand;
486
- }
487
- if (eventsToEmit.length) {
488
- await pushToQueue(
489
- this.env.QUEUE_BUS_QUEUE,
490
- eventsToEmit
491
- );
492
- }
493
- return {
494
- ...lastSyncMetadata,
495
- newLastSyncAt: now
496
- };
497
- }
498
- );
499
574
  await step.sleep(
500
575
  "Sleep for next sync",
501
576
  `${account.syncIntervalS} seconds`
@@ -1,5 +1,5 @@
1
1
  import { sql, and, eq, isNull } from 'drizzle-orm';
2
- import { G as tables } from './bank.CZ8MQDPa.mjs';
2
+ import { G as tables } from './bank.Bkxo76q4.mjs';
3
3
  import { uuidv4 } from '@develit-io/backend-sdk';
4
4
  import './bank.BzDNLxB_.mjs';
5
5
  import 'date-fns';
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const drizzleOrm = require('drizzle-orm');
4
- const ott_zod = require('./bank.BSX82jhx.cjs');
4
+ const bank = require('./bank.BkctD4hR.cjs');
5
5
  const backendSdk = require('@develit-io/backend-sdk');
6
6
  require('./bank.9Yw4KHyl.cjs');
7
7
  require('date-fns');
@@ -11,16 +11,16 @@ const node_crypto = require('node:crypto');
11
11
 
12
12
  const createPaymentCommand = (db, { payment }) => {
13
13
  return {
14
- command: db.insert(ott_zod.tables.payment).values({
14
+ command: db.insert(bank.tables.payment).values({
15
15
  ...payment,
16
16
  creditorIban: payment.creditor.iban,
17
17
  debtorIban: payment.debtor.iban
18
18
  }).onConflictDoUpdate({
19
19
  // Unique index: (connector_key, account_id, bank_ref_id)
20
20
  target: [
21
- ott_zod.tables.payment.connectorKey,
22
- ott_zod.tables.payment.accountId,
23
- ott_zod.tables.payment.bankRefId
21
+ bank.tables.payment.connectorKey,
22
+ bank.tables.payment.accountId,
23
+ bank.tables.payment.bankRefId
24
24
  ],
25
25
  set: {
26
26
  status: drizzleOrm.sql`excluded.status`,
@@ -38,11 +38,11 @@ const createPaymentCommand = (db, { payment }) => {
38
38
 
39
39
  const upsertBatchCommand = (db, { batch }) => {
40
40
  const id = batch.id || backendSdk.uuidv4();
41
- const command = db.insert(ott_zod.tables.batch).values({
41
+ const command = db.insert(bank.tables.batch).values({
42
42
  ...batch,
43
43
  id
44
44
  }).onConflictDoUpdate({
45
- target: ott_zod.tables.batch.id,
45
+ target: bank.tables.batch.id,
46
46
  set: {
47
47
  ...batch
48
48
  }
@@ -56,25 +56,25 @@ const upsertBatchCommand = (db, { batch }) => {
56
56
  const updatePaymentRequestStatusCommand = (db, values) => {
57
57
  const { id, ...set } = values;
58
58
  return {
59
- command: db.update(ott_zod.tables.paymentRequest).set(set).where(
59
+ command: db.update(bank.tables.paymentRequest).set(set).where(
60
60
  drizzleOrm.and(
61
- drizzleOrm.eq(ott_zod.tables.paymentRequest.id, id),
62
- drizzleOrm.isNull(ott_zod.tables.paymentRequest.deletedAt)
61
+ drizzleOrm.eq(bank.tables.paymentRequest.id, id),
62
+ drizzleOrm.isNull(bank.tables.paymentRequest.deletedAt)
63
63
  )
64
64
  ).returning()
65
65
  };
66
66
  };
67
67
 
68
68
  const getAccountByIdQuery = async (db, { accountId }) => {
69
- return await db.select().from(ott_zod.tables.account).where(drizzleOrm.eq(ott_zod.tables.account.id, accountId)).get();
69
+ return await db.select().from(bank.tables.account).where(drizzleOrm.eq(bank.tables.account.id, accountId)).get();
70
70
  };
71
71
 
72
72
  const getBatchByIdQuery = async (db, { batchId }) => {
73
- return await db.select().from(ott_zod.tables.batch).where(drizzleOrm.eq(ott_zod.tables.batch.id, batchId)).get();
73
+ return await db.select().from(bank.tables.batch).where(drizzleOrm.eq(bank.tables.batch.id, batchId)).get();
74
74
  };
75
75
 
76
76
  const getCredentialsByAccountId = async (db, encryptionKey, { accountId }) => {
77
- const cred = await db.select().from(ott_zod.tables.accountCredentials).where(drizzleOrm.eq(ott_zod.tables.accountCredentials.accountId, accountId)).get();
77
+ const cred = await db.select().from(bank.tables.accountCredentials).where(drizzleOrm.eq(bank.tables.accountCredentials.accountId, accountId)).get();
78
78
  return cred ? {
79
79
  ...cred,
80
80
  value: await decrypt(cred.value, encryptionKey)
@@ -82,10 +82,10 @@ const getCredentialsByAccountId = async (db, encryptionKey, { accountId }) => {
82
82
  };
83
83
 
84
84
  const getPaymentRequestsByBatchIdQuery = async (db, { batchId }) => {
85
- return await db.select().from(ott_zod.tables.paymentRequest).where(
85
+ return await db.select().from(bank.tables.paymentRequest).where(
86
86
  drizzleOrm.and(
87
- drizzleOrm.eq(ott_zod.tables.paymentRequest.batchId, batchId),
88
- drizzleOrm.isNull(ott_zod.tables.paymentRequest.deletedAt)
87
+ drizzleOrm.eq(bank.tables.paymentRequest.batchId, batchId),
88
+ drizzleOrm.isNull(bank.tables.paymentRequest.deletedAt)
89
89
  )
90
90
  );
91
91
  };