@develit-services/bank 5.2.1 → 5.2.2
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/base.d.cts +3 -3
- package/dist/base.d.mts +3 -3
- package/dist/base.d.ts +3 -3
- package/dist/export/workflows.cjs +117 -191
- package/dist/export/workflows.mjs +117 -191
- package/package.json +1 -1
package/dist/base.d.cts
CHANGED
|
@@ -2699,9 +2699,9 @@ declare const getBatchesInputSchema: z.ZodObject<{
|
|
|
2699
2699
|
limit: z.ZodNumber;
|
|
2700
2700
|
sort: z.ZodObject<{
|
|
2701
2701
|
column: z.ZodEnum<{
|
|
2702
|
+
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2702
2703
|
createdAt: "createdAt";
|
|
2703
2704
|
updatedAt: "updatedAt";
|
|
2704
|
-
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2705
2705
|
}>;
|
|
2706
2706
|
direction: z.ZodEnum<{
|
|
2707
2707
|
asc: "asc";
|
|
@@ -2825,9 +2825,9 @@ declare const getPaymentsInputSchema: z.ZodObject<{
|
|
|
2825
2825
|
limit: z.ZodNumber;
|
|
2826
2826
|
sort: z.ZodObject<{
|
|
2827
2827
|
column: z.ZodEnum<{
|
|
2828
|
+
amount: "amount";
|
|
2828
2829
|
createdAt: "createdAt";
|
|
2829
2830
|
updatedAt: "updatedAt";
|
|
2830
|
-
amount: "amount";
|
|
2831
2831
|
}>;
|
|
2832
2832
|
direction: z.ZodEnum<{
|
|
2833
2833
|
asc: "asc";
|
|
@@ -3875,9 +3875,9 @@ declare const getPaymentRequestsInputSchema: z.ZodObject<{
|
|
|
3875
3875
|
limit: z.ZodNumber;
|
|
3876
3876
|
sort: z.ZodObject<{
|
|
3877
3877
|
column: z.ZodEnum<{
|
|
3878
|
+
amount: "amount";
|
|
3878
3879
|
createdAt: "createdAt";
|
|
3879
3880
|
updatedAt: "updatedAt";
|
|
3880
|
-
amount: "amount";
|
|
3881
3881
|
}>;
|
|
3882
3882
|
direction: z.ZodEnum<{
|
|
3883
3883
|
asc: "asc";
|
package/dist/base.d.mts
CHANGED
|
@@ -2699,9 +2699,9 @@ declare const getBatchesInputSchema: z.ZodObject<{
|
|
|
2699
2699
|
limit: z.ZodNumber;
|
|
2700
2700
|
sort: z.ZodObject<{
|
|
2701
2701
|
column: z.ZodEnum<{
|
|
2702
|
+
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2702
2703
|
createdAt: "createdAt";
|
|
2703
2704
|
updatedAt: "updatedAt";
|
|
2704
|
-
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2705
2705
|
}>;
|
|
2706
2706
|
direction: z.ZodEnum<{
|
|
2707
2707
|
asc: "asc";
|
|
@@ -2825,9 +2825,9 @@ declare const getPaymentsInputSchema: z.ZodObject<{
|
|
|
2825
2825
|
limit: z.ZodNumber;
|
|
2826
2826
|
sort: z.ZodObject<{
|
|
2827
2827
|
column: z.ZodEnum<{
|
|
2828
|
+
amount: "amount";
|
|
2828
2829
|
createdAt: "createdAt";
|
|
2829
2830
|
updatedAt: "updatedAt";
|
|
2830
|
-
amount: "amount";
|
|
2831
2831
|
}>;
|
|
2832
2832
|
direction: z.ZodEnum<{
|
|
2833
2833
|
asc: "asc";
|
|
@@ -3875,9 +3875,9 @@ declare const getPaymentRequestsInputSchema: z.ZodObject<{
|
|
|
3875
3875
|
limit: z.ZodNumber;
|
|
3876
3876
|
sort: z.ZodObject<{
|
|
3877
3877
|
column: z.ZodEnum<{
|
|
3878
|
+
amount: "amount";
|
|
3878
3879
|
createdAt: "createdAt";
|
|
3879
3880
|
updatedAt: "updatedAt";
|
|
3880
|
-
amount: "amount";
|
|
3881
3881
|
}>;
|
|
3882
3882
|
direction: z.ZodEnum<{
|
|
3883
3883
|
asc: "asc";
|
package/dist/base.d.ts
CHANGED
|
@@ -2699,9 +2699,9 @@ declare const getBatchesInputSchema: z.ZodObject<{
|
|
|
2699
2699
|
limit: z.ZodNumber;
|
|
2700
2700
|
sort: z.ZodObject<{
|
|
2701
2701
|
column: z.ZodEnum<{
|
|
2702
|
+
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2702
2703
|
createdAt: "createdAt";
|
|
2703
2704
|
updatedAt: "updatedAt";
|
|
2704
|
-
batchPaymentInitiatedAt: "batchPaymentInitiatedAt";
|
|
2705
2705
|
}>;
|
|
2706
2706
|
direction: z.ZodEnum<{
|
|
2707
2707
|
asc: "asc";
|
|
@@ -2825,9 +2825,9 @@ declare const getPaymentsInputSchema: z.ZodObject<{
|
|
|
2825
2825
|
limit: z.ZodNumber;
|
|
2826
2826
|
sort: z.ZodObject<{
|
|
2827
2827
|
column: z.ZodEnum<{
|
|
2828
|
+
amount: "amount";
|
|
2828
2829
|
createdAt: "createdAt";
|
|
2829
2830
|
updatedAt: "updatedAt";
|
|
2830
|
-
amount: "amount";
|
|
2831
2831
|
}>;
|
|
2832
2832
|
direction: z.ZodEnum<{
|
|
2833
2833
|
asc: "asc";
|
|
@@ -3875,9 +3875,9 @@ declare const getPaymentRequestsInputSchema: z.ZodObject<{
|
|
|
3875
3875
|
limit: z.ZodNumber;
|
|
3876
3876
|
sort: z.ZodObject<{
|
|
3877
3877
|
column: z.ZodEnum<{
|
|
3878
|
+
amount: "amount";
|
|
3878
3879
|
createdAt: "createdAt";
|
|
3879
3880
|
updatedAt: "updatedAt";
|
|
3880
|
-
amount: "amount";
|
|
3881
3881
|
}>;
|
|
3882
3882
|
direction: z.ZodEnum<{
|
|
3883
3883
|
asc: "asc";
|
|
@@ -315,16 +315,12 @@ async function pushToQueue(queue, message) {
|
|
|
315
315
|
await queue.send(message, { contentType: "v8" });
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
contentType: "v8"
|
|
325
|
-
}))
|
|
326
|
-
);
|
|
327
|
-
}
|
|
318
|
+
await queue.sendBatch(
|
|
319
|
+
message.map((m) => ({
|
|
320
|
+
body: m,
|
|
321
|
+
contentType: "v8"
|
|
322
|
+
}))
|
|
323
|
+
);
|
|
328
324
|
}
|
|
329
325
|
class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
330
326
|
async run(event, step) {
|
|
@@ -350,13 +346,13 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
350
346
|
if (!account.lastSyncAt) {
|
|
351
347
|
throw new Error(`lastSyncedAt is not set for account: ${accountId}`);
|
|
352
348
|
}
|
|
353
|
-
await step.do(
|
|
354
|
-
"fetch
|
|
349
|
+
const payments = await step.do(
|
|
350
|
+
"fetch bank payments",
|
|
355
351
|
{
|
|
356
352
|
retries: { limit: 5, delay: "2 minutes", backoff: "exponential" },
|
|
357
|
-
timeout: "
|
|
353
|
+
timeout: "2 minutes"
|
|
358
354
|
},
|
|
359
|
-
async (
|
|
355
|
+
async () => {
|
|
360
356
|
try {
|
|
361
357
|
logger.info("payments.fetch.started", {
|
|
362
358
|
accountId,
|
|
@@ -379,192 +375,19 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
379
375
|
}
|
|
380
376
|
]
|
|
381
377
|
});
|
|
382
|
-
const
|
|
378
|
+
const result = await connector.getAllAccountPayments({
|
|
383
379
|
account,
|
|
384
380
|
filter: { dateFrom: account.lastSyncAt }
|
|
385
381
|
});
|
|
386
382
|
logger.info("payments.fetch.completed", {
|
|
387
383
|
accountId,
|
|
388
384
|
connectorKey: account.connectorKey,
|
|
389
|
-
paymentsCount:
|
|
390
|
-
});
|
|
391
|
-
const paymentsToProcess = payments.filter(
|
|
392
|
-
(p) => bank.isPaymentCompleted(p.parsed)
|
|
393
|
-
);
|
|
394
|
-
logger.info("payments.filtered.toProcess", {
|
|
395
|
-
accountId,
|
|
396
|
-
totalFetched: payments.length,
|
|
397
|
-
paymentsToProcess: paymentsToProcess.length,
|
|
398
|
-
sampleStatuses: payments.slice(0, 5).map((p) => ({
|
|
399
|
-
bankRefId: p.parsed.bankRefId,
|
|
400
|
-
status: p.parsed.status,
|
|
401
|
-
isCompleted: bank.isPaymentCompleted(p.parsed)
|
|
402
|
-
}))
|
|
403
|
-
});
|
|
404
|
-
const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
|
|
405
|
-
const paymentsToInsert = paymentsToProcess.filter(
|
|
406
|
-
(p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
|
|
407
|
-
);
|
|
408
|
-
logger.info("payments.filtered.toInsert", {
|
|
409
|
-
accountId,
|
|
410
|
-
paymentsToProcess: paymentsToProcess.length,
|
|
411
|
-
paymentsToInsert: paymentsToInsert.length,
|
|
412
|
-
lastSyncBankRefIdsCount: lastSyncBankRefIds.length,
|
|
413
|
-
sampleLastSyncBankRefIds: lastSyncBankRefIds.slice(0, 10),
|
|
414
|
-
sampleToInsert: paymentsToInsert.slice(0, 5).map((p) => p.parsed.bankRefId)
|
|
415
|
-
});
|
|
416
|
-
const eventsToEmit = [];
|
|
417
|
-
const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
|
|
418
|
-
const BANK_REF_ID_CHUNK_SIZE = 90;
|
|
419
|
-
const matchingRequests = [];
|
|
420
|
-
for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
|
|
421
|
-
const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
|
|
422
|
-
const rows = await db.select().from(bank.tables.paymentRequest).where(
|
|
423
|
-
drizzleOrm.and(
|
|
424
|
-
drizzleOrm.inArray(bank.tables.paymentRequest.bankRefId, chunkIds),
|
|
425
|
-
drizzleOrm.eq(bank.tables.paymentRequest.accountId, account.id),
|
|
426
|
-
drizzleOrm.eq(
|
|
427
|
-
bank.tables.paymentRequest.connectorKey,
|
|
428
|
-
account.connectorKey
|
|
429
|
-
)
|
|
430
|
-
)
|
|
431
|
-
);
|
|
432
|
-
matchingRequests.push(...rows);
|
|
433
|
-
}
|
|
434
|
-
const requestByBankRefId = Object.fromEntries(
|
|
435
|
-
matchingRequests.map((r) => [r.bankRefId, r])
|
|
436
|
-
);
|
|
437
|
-
const enrichedPayments = paymentsToInsert.map((p) => {
|
|
438
|
-
const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
|
|
439
|
-
if (!req) return p;
|
|
440
|
-
return {
|
|
441
|
-
...p,
|
|
442
|
-
parsed: {
|
|
443
|
-
...p.parsed,
|
|
444
|
-
// queue-bus: transaction matching (DBU doesn't echo these in statements)
|
|
445
|
-
vs: p.parsed.vs ?? req.vs,
|
|
446
|
-
ss: p.parsed.ss ?? req.ss,
|
|
447
|
-
ks: p.parsed.ks ?? req.ks,
|
|
448
|
-
message: p.parsed.message ?? req.message,
|
|
449
|
-
// queue-bus: party creation
|
|
450
|
-
creditor: {
|
|
451
|
-
...p.parsed.creditor,
|
|
452
|
-
holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
|
|
453
|
-
},
|
|
454
|
-
debtor: {
|
|
455
|
-
...p.parsed.debtor,
|
|
456
|
-
holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
|
|
457
|
-
}
|
|
458
|
-
// NOT enriched: chargeBearer, instructionPriority, refId, batchId,
|
|
459
|
-
// createdAt, address, swiftBic — no downstream consumer in payment table
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
});
|
|
463
|
-
const createCommands = enrichedPayments.map(
|
|
464
|
-
(p) => credentialsResolver.createPaymentCommand(db, { payment: p.parsed }).command
|
|
465
|
-
);
|
|
466
|
-
logger.info("payments.commands.created", {
|
|
467
|
-
accountId,
|
|
468
|
-
createCommandsCount: createCommands.length,
|
|
469
|
-
enrichedPaymentsCount: enrichedPayments.length
|
|
470
|
-
});
|
|
471
|
-
eventsToEmit.push(
|
|
472
|
-
...enrichedPayments.map((p) => ({
|
|
473
|
-
eventType: "BANK_PAYMENT",
|
|
474
|
-
eventSignal: "paymentFetched",
|
|
475
|
-
bankPayment: p.parsed,
|
|
476
|
-
metadata: {
|
|
477
|
-
correlationId: p.parsed.correlationId,
|
|
478
|
-
entityId: p.parsed.id,
|
|
479
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
480
|
-
}
|
|
481
|
-
}))
|
|
482
|
-
);
|
|
483
|
-
const lastSyncMetadata = {
|
|
484
|
-
payments: payments.length,
|
|
485
|
-
paymentsToProcess: paymentsToProcess.length,
|
|
486
|
-
paymentsInserted: paymentsToInsert.length,
|
|
487
|
-
lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
|
|
488
|
-
lastSyncPayments: lastSyncBankRefIds.length,
|
|
489
|
-
eventsEmitted: eventsToEmit.length,
|
|
490
|
-
iterationCount: getStepCount(ctx),
|
|
491
|
-
workflowStartedAt
|
|
492
|
-
};
|
|
493
|
-
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
494
|
-
accountId: account.id,
|
|
495
|
-
lastSyncAt: now,
|
|
496
|
-
lastSyncMetadata
|
|
497
|
-
}).command;
|
|
498
|
-
logger.info("payments.database.beforeBatch", {
|
|
499
|
-
accountId,
|
|
500
|
-
createCommandsCount: createCommands.length,
|
|
501
|
-
willUseBatch: createCommands.length > 0
|
|
385
|
+
paymentsCount: result.length
|
|
502
386
|
});
|
|
503
|
-
|
|
504
|
-
logger.info("payments.database.batchStart", {
|
|
505
|
-
accountId,
|
|
506
|
-
totalCommands: createCommands.length + 1,
|
|
507
|
-
paymentsToInsert: createCommands.length
|
|
508
|
-
});
|
|
509
|
-
const BATCH_CHUNK_SIZE = 90;
|
|
510
|
-
let totalInserted = 0;
|
|
511
|
-
for (let i = 0; i < createCommands.length; i += BATCH_CHUNK_SIZE) {
|
|
512
|
-
const chunkCommands = createCommands.slice(
|
|
513
|
-
i,
|
|
514
|
-
i + BATCH_CHUNK_SIZE
|
|
515
|
-
);
|
|
516
|
-
const isLastChunk = i + BATCH_CHUNK_SIZE >= createCommands.length;
|
|
517
|
-
const batchCommands = isLastChunk ? backendSdk.asNonEmpty([updateLastSyncCommand, ...chunkCommands]) : backendSdk.asNonEmpty(chunkCommands);
|
|
518
|
-
logger.info("payments.database.batchChunk", {
|
|
519
|
-
accountId,
|
|
520
|
-
chunkIndex: Math.floor(i / BATCH_CHUNK_SIZE),
|
|
521
|
-
chunkSize: chunkCommands.length,
|
|
522
|
-
isLastChunk
|
|
523
|
-
});
|
|
524
|
-
await db.batch(batchCommands);
|
|
525
|
-
totalInserted += chunkCommands.length;
|
|
526
|
-
}
|
|
527
|
-
logger.info("payments.database.batchComplete", {
|
|
528
|
-
accountId,
|
|
529
|
-
inserted: totalInserted,
|
|
530
|
-
chunks: Math.ceil(createCommands.length / BATCH_CHUNK_SIZE)
|
|
531
|
-
});
|
|
532
|
-
} else {
|
|
533
|
-
logger.info("payments.database.updateOnly", {
|
|
534
|
-
accountId,
|
|
535
|
-
reason: "no new payments to insert"
|
|
536
|
-
});
|
|
537
|
-
await updateLastSyncCommand.execute();
|
|
538
|
-
logger.info("payments.database.updateComplete", {
|
|
539
|
-
accountId
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
if (eventsToEmit.length) {
|
|
543
|
-
logger.info("payments.queue.sending", {
|
|
544
|
-
accountId,
|
|
545
|
-
eventsCount: eventsToEmit.length
|
|
546
|
-
});
|
|
547
|
-
await pushToQueue(
|
|
548
|
-
this.env.QUEUE_BUS_QUEUE,
|
|
549
|
-
eventsToEmit
|
|
550
|
-
);
|
|
551
|
-
logger.info("payments.queue.sent", {
|
|
552
|
-
accountId,
|
|
553
|
-
eventsCount: eventsToEmit.length
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
logger.info("payments.process.complete", {
|
|
557
|
-
accountId,
|
|
558
|
-
...lastSyncMetadata,
|
|
559
|
-
newLastSyncAt: now
|
|
560
|
-
});
|
|
561
|
-
return {
|
|
562
|
-
...lastSyncMetadata,
|
|
563
|
-
newLastSyncAt: now
|
|
564
|
-
};
|
|
387
|
+
return result;
|
|
565
388
|
} catch (err) {
|
|
566
389
|
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
567
|
-
logger.error("payments.
|
|
390
|
+
logger.error("payments.fetch.failed", {
|
|
568
391
|
accountId,
|
|
569
392
|
connectorKey: account.connectorKey,
|
|
570
393
|
error: message
|
|
@@ -573,6 +396,109 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
573
396
|
}
|
|
574
397
|
}
|
|
575
398
|
);
|
|
399
|
+
const paymentsToProcess = payments.filter(
|
|
400
|
+
(p) => bank.isPaymentCompleted(p.parsed)
|
|
401
|
+
);
|
|
402
|
+
const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
|
|
403
|
+
const paymentsToInsert = paymentsToProcess.filter(
|
|
404
|
+
(p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
|
|
405
|
+
);
|
|
406
|
+
await step.do(
|
|
407
|
+
"process new payments and update lastSyncAt",
|
|
408
|
+
async (ctx) => {
|
|
409
|
+
const eventsToEmit = [];
|
|
410
|
+
const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
|
|
411
|
+
const BANK_REF_ID_CHUNK_SIZE = 90;
|
|
412
|
+
const matchingRequests = [];
|
|
413
|
+
for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
|
|
414
|
+
const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
|
|
415
|
+
const rows = await db.select().from(bank.tables.paymentRequest).where(
|
|
416
|
+
drizzleOrm.and(
|
|
417
|
+
drizzleOrm.inArray(bank.tables.paymentRequest.bankRefId, chunkIds),
|
|
418
|
+
drizzleOrm.eq(bank.tables.paymentRequest.accountId, account.id),
|
|
419
|
+
drizzleOrm.eq(bank.tables.paymentRequest.connectorKey, account.connectorKey)
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
matchingRequests.push(...rows);
|
|
423
|
+
}
|
|
424
|
+
const requestByBankRefId = Object.fromEntries(
|
|
425
|
+
matchingRequests.map((r) => [r.bankRefId, r])
|
|
426
|
+
);
|
|
427
|
+
const enrichedPayments = paymentsToInsert.map((p) => {
|
|
428
|
+
const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
|
|
429
|
+
if (!req) return p;
|
|
430
|
+
return {
|
|
431
|
+
...p,
|
|
432
|
+
parsed: {
|
|
433
|
+
...p.parsed,
|
|
434
|
+
// queue-bus: transaction matching (DBU doesn't echo these in statements)
|
|
435
|
+
vs: p.parsed.vs ?? req.vs,
|
|
436
|
+
ss: p.parsed.ss ?? req.ss,
|
|
437
|
+
ks: p.parsed.ks ?? req.ks,
|
|
438
|
+
message: p.parsed.message ?? req.message,
|
|
439
|
+
// queue-bus: party creation
|
|
440
|
+
creditor: {
|
|
441
|
+
...p.parsed.creditor,
|
|
442
|
+
holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
|
|
443
|
+
},
|
|
444
|
+
debtor: {
|
|
445
|
+
...p.parsed.debtor,
|
|
446
|
+
holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
|
|
447
|
+
}
|
|
448
|
+
// NOT enriched: chargeBearer, instructionPriority, refId, batchId,
|
|
449
|
+
// createdAt, address, swiftBic — no downstream consumer in payment table
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
const createCommands = enrichedPayments.map(
|
|
454
|
+
(p) => credentialsResolver.createPaymentCommand(db, { payment: p.parsed }).command
|
|
455
|
+
);
|
|
456
|
+
eventsToEmit.push(
|
|
457
|
+
...enrichedPayments.map((p) => ({
|
|
458
|
+
eventType: "BANK_PAYMENT",
|
|
459
|
+
eventSignal: "paymentFetched",
|
|
460
|
+
bankPayment: p.parsed,
|
|
461
|
+
metadata: {
|
|
462
|
+
correlationId: p.parsed.correlationId,
|
|
463
|
+
entityId: p.parsed.id,
|
|
464
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
465
|
+
}
|
|
466
|
+
}))
|
|
467
|
+
);
|
|
468
|
+
const lastSyncMetadata = {
|
|
469
|
+
payments: payments.length,
|
|
470
|
+
paymentsToProcess: paymentsToProcess.length,
|
|
471
|
+
paymentsInserted: paymentsToInsert.length,
|
|
472
|
+
lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
|
|
473
|
+
lastSyncPayments: lastSyncBankRefIds.length,
|
|
474
|
+
eventsEmitted: eventsToEmit.length,
|
|
475
|
+
iterationCount: getStepCount(ctx),
|
|
476
|
+
workflowStartedAt
|
|
477
|
+
};
|
|
478
|
+
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
479
|
+
accountId: account.id,
|
|
480
|
+
lastSyncAt: now,
|
|
481
|
+
lastSyncMetadata
|
|
482
|
+
}).command;
|
|
483
|
+
if (createCommands.length) {
|
|
484
|
+
await db.batch(
|
|
485
|
+
backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
486
|
+
);
|
|
487
|
+
} else {
|
|
488
|
+
await updateLastSyncCommand;
|
|
489
|
+
}
|
|
490
|
+
if (eventsToEmit.length) {
|
|
491
|
+
await pushToQueue(
|
|
492
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
493
|
+
eventsToEmit
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
...lastSyncMetadata,
|
|
498
|
+
newLastSyncAt: now
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
);
|
|
576
502
|
await step.sleep(
|
|
577
503
|
"Sleep for next sync",
|
|
578
504
|
`${account.syncIntervalS} seconds`
|
|
@@ -313,16 +313,12 @@ async function pushToQueue(queue, message) {
|
|
|
313
313
|
await queue.send(message, { contentType: "v8" });
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
contentType: "v8"
|
|
323
|
-
}))
|
|
324
|
-
);
|
|
325
|
-
}
|
|
316
|
+
await queue.sendBatch(
|
|
317
|
+
message.map((m) => ({
|
|
318
|
+
body: m,
|
|
319
|
+
contentType: "v8"
|
|
320
|
+
}))
|
|
321
|
+
);
|
|
326
322
|
}
|
|
327
323
|
class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
328
324
|
async run(event, step) {
|
|
@@ -348,13 +344,13 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
348
344
|
if (!account.lastSyncAt) {
|
|
349
345
|
throw new Error(`lastSyncedAt is not set for account: ${accountId}`);
|
|
350
346
|
}
|
|
351
|
-
await step.do(
|
|
352
|
-
"fetch
|
|
347
|
+
const payments = await step.do(
|
|
348
|
+
"fetch bank payments",
|
|
353
349
|
{
|
|
354
350
|
retries: { limit: 5, delay: "2 minutes", backoff: "exponential" },
|
|
355
|
-
timeout: "
|
|
351
|
+
timeout: "2 minutes"
|
|
356
352
|
},
|
|
357
|
-
async (
|
|
353
|
+
async () => {
|
|
358
354
|
try {
|
|
359
355
|
logger.info("payments.fetch.started", {
|
|
360
356
|
accountId,
|
|
@@ -377,192 +373,19 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
377
373
|
}
|
|
378
374
|
]
|
|
379
375
|
});
|
|
380
|
-
const
|
|
376
|
+
const result = await connector.getAllAccountPayments({
|
|
381
377
|
account,
|
|
382
378
|
filter: { dateFrom: account.lastSyncAt }
|
|
383
379
|
});
|
|
384
380
|
logger.info("payments.fetch.completed", {
|
|
385
381
|
accountId,
|
|
386
382
|
connectorKey: account.connectorKey,
|
|
387
|
-
paymentsCount:
|
|
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
|
+
paymentsCount: result.length
|
|
500
384
|
});
|
|
501
|
-
|
|
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
|
+
return result;
|
|
563
386
|
} catch (err) {
|
|
564
387
|
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
565
|
-
logger.error("payments.
|
|
388
|
+
logger.error("payments.fetch.failed", {
|
|
566
389
|
accountId,
|
|
567
390
|
connectorKey: account.connectorKey,
|
|
568
391
|
error: message
|
|
@@ -571,6 +394,109 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
571
394
|
}
|
|
572
395
|
}
|
|
573
396
|
);
|
|
397
|
+
const paymentsToProcess = payments.filter(
|
|
398
|
+
(p) => isPaymentCompleted(p.parsed)
|
|
399
|
+
);
|
|
400
|
+
const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
|
|
401
|
+
const paymentsToInsert = paymentsToProcess.filter(
|
|
402
|
+
(p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
|
|
403
|
+
);
|
|
404
|
+
await step.do(
|
|
405
|
+
"process new payments and update lastSyncAt",
|
|
406
|
+
async (ctx) => {
|
|
407
|
+
const eventsToEmit = [];
|
|
408
|
+
const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
|
|
409
|
+
const BANK_REF_ID_CHUNK_SIZE = 90;
|
|
410
|
+
const matchingRequests = [];
|
|
411
|
+
for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
|
|
412
|
+
const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
|
|
413
|
+
const rows = await db.select().from(tables.paymentRequest).where(
|
|
414
|
+
and(
|
|
415
|
+
inArray(tables.paymentRequest.bankRefId, chunkIds),
|
|
416
|
+
eq(tables.paymentRequest.accountId, account.id),
|
|
417
|
+
eq(tables.paymentRequest.connectorKey, account.connectorKey)
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
matchingRequests.push(...rows);
|
|
421
|
+
}
|
|
422
|
+
const requestByBankRefId = Object.fromEntries(
|
|
423
|
+
matchingRequests.map((r) => [r.bankRefId, r])
|
|
424
|
+
);
|
|
425
|
+
const enrichedPayments = paymentsToInsert.map((p) => {
|
|
426
|
+
const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
|
|
427
|
+
if (!req) return p;
|
|
428
|
+
return {
|
|
429
|
+
...p,
|
|
430
|
+
parsed: {
|
|
431
|
+
...p.parsed,
|
|
432
|
+
// queue-bus: transaction matching (DBU doesn't echo these in statements)
|
|
433
|
+
vs: p.parsed.vs ?? req.vs,
|
|
434
|
+
ss: p.parsed.ss ?? req.ss,
|
|
435
|
+
ks: p.parsed.ks ?? req.ks,
|
|
436
|
+
message: p.parsed.message ?? req.message,
|
|
437
|
+
// queue-bus: party creation
|
|
438
|
+
creditor: {
|
|
439
|
+
...p.parsed.creditor,
|
|
440
|
+
holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
|
|
441
|
+
},
|
|
442
|
+
debtor: {
|
|
443
|
+
...p.parsed.debtor,
|
|
444
|
+
holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
|
|
445
|
+
}
|
|
446
|
+
// NOT enriched: chargeBearer, instructionPriority, refId, batchId,
|
|
447
|
+
// createdAt, address, swiftBic — no downstream consumer in payment table
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
const createCommands = enrichedPayments.map(
|
|
452
|
+
(p) => createPaymentCommand(db, { payment: p.parsed }).command
|
|
453
|
+
);
|
|
454
|
+
eventsToEmit.push(
|
|
455
|
+
...enrichedPayments.map((p) => ({
|
|
456
|
+
eventType: "BANK_PAYMENT",
|
|
457
|
+
eventSignal: "paymentFetched",
|
|
458
|
+
bankPayment: p.parsed,
|
|
459
|
+
metadata: {
|
|
460
|
+
correlationId: p.parsed.correlationId,
|
|
461
|
+
entityId: p.parsed.id,
|
|
462
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
463
|
+
}
|
|
464
|
+
}))
|
|
465
|
+
);
|
|
466
|
+
const lastSyncMetadata = {
|
|
467
|
+
payments: payments.length,
|
|
468
|
+
paymentsToProcess: paymentsToProcess.length,
|
|
469
|
+
paymentsInserted: paymentsToInsert.length,
|
|
470
|
+
lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
|
|
471
|
+
lastSyncPayments: lastSyncBankRefIds.length,
|
|
472
|
+
eventsEmitted: eventsToEmit.length,
|
|
473
|
+
iterationCount: getStepCount(ctx),
|
|
474
|
+
workflowStartedAt
|
|
475
|
+
};
|
|
476
|
+
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
477
|
+
accountId: account.id,
|
|
478
|
+
lastSyncAt: now,
|
|
479
|
+
lastSyncMetadata
|
|
480
|
+
}).command;
|
|
481
|
+
if (createCommands.length) {
|
|
482
|
+
await db.batch(
|
|
483
|
+
asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
484
|
+
);
|
|
485
|
+
} else {
|
|
486
|
+
await updateLastSyncCommand;
|
|
487
|
+
}
|
|
488
|
+
if (eventsToEmit.length) {
|
|
489
|
+
await pushToQueue(
|
|
490
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
491
|
+
eventsToEmit
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
...lastSyncMetadata,
|
|
496
|
+
newLastSyncAt: now
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
);
|
|
574
500
|
await step.sleep(
|
|
575
501
|
"Sleep for next sync",
|
|
576
502
|
`${account.syncIntervalS} seconds`
|