@develit-services/bank 4.1.0 → 4.2.1
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/database/schema.d.cts +1 -1
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/export/worker.cjs +217 -17
- package/dist/export/worker.d.cts +3 -2
- package/dist/export/worker.d.mts +3 -2
- package/dist/export/worker.d.ts +3 -2
- package/dist/export/worker.mjs +217 -17
- package/dist/export/workflows.cjs +102 -87
- package/dist/export/workflows.mjs +102 -87
- package/dist/export/wrangler.d.cts +2 -1
- package/dist/export/wrangler.d.mts +2 -1
- package/dist/export/wrangler.d.ts +2 -1
- package/dist/shared/{bank.C0JeMUxQ.d.cts → bank.BCqBwSKC.d.cts} +22 -3
- package/dist/shared/{bank.C0JeMUxQ.d.mts → bank.BCqBwSKC.d.mts} +22 -3
- package/dist/shared/{bank.C0JeMUxQ.d.ts → bank.BCqBwSKC.d.ts} +22 -3
- package/dist/shared/{bank.BScD3GXI.mjs → bank.BELDXSDV.mjs} +1 -1
- package/dist/shared/{bank.BxZARqNE.mjs → bank.BOMobxtA.mjs} +45 -18
- package/dist/shared/{bank.DflRiCrT.d.ts → bank.BmX_IG66.d.ts} +1 -1
- package/dist/shared/{bank.CjPpPiJF.d.mts → bank.BnCJmT6k.d.mts} +1 -1
- package/dist/shared/{bank.DNFep60v.cjs → bank.CioJeFzf.cjs} +1 -1
- package/dist/shared/{bank.xrNekjj9.cjs → bank.DiJmJkDt.cjs} +44 -16
- package/dist/shared/{bank.BydmpvCs.d.ts → bank.Dq24vYU7.d.cts} +1 -0
- package/dist/shared/{bank.BydmpvCs.d.cts → bank.Dq24vYU7.d.mts} +1 -0
- package/dist/shared/{bank.BydmpvCs.d.mts → bank.Dq24vYU7.d.ts} +1 -0
- package/dist/shared/{bank.CwB3cDIG.d.cts → bank.mHFTrKBv.d.cts} +1 -1
- package/dist/types.cjs +2 -1
- package/dist/types.d.cts +5 -5
- package/dist/types.d.mts +5 -5
- package/dist/types.d.ts +5 -5
- package/dist/types.mjs +1 -1
- package/package.json +1 -1
package/dist/export/worker.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bankAccountMetadataSchema, structuredAddressSchema, uuidv4, first, buildMultiFilterConditions as buildMultiFilterConditions$1, workflowInstanceStatusSchema, develitWorker, createInternalError, action, service } from '@develit-io/backend-sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { G as tables, g as accountInsertSchema, H as relations, J as initiateConnector, o as isProcessedStatus, p as isTerminalStatus, L as getNonTerminalPaymentRequestsQuery, x as toIncomingPayment, j as assignAccount, u as toBatchedPayment, y as toPaymentRequestInsert, a as FinbricksClient, F as FINBRICKS_ENDPOINTS } from '../shared/bank.BOMobxtA.mjs';
|
|
3
3
|
import { eq, sql, and, like, asc, desc, inArray, gte, lte, isNull, count } from 'drizzle-orm';
|
|
4
4
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
5
5
|
import { drizzle } from 'drizzle-orm/d1';
|
|
@@ -9,10 +9,69 @@ import { I as INSTRUCTION_PRIORITIES, C as CHARGE_BEARERS, g as PAYMENT_TYPES, b
|
|
|
9
9
|
import { CURRENCY_CODES } from '@develit-io/general-codes';
|
|
10
10
|
import 'date-fns';
|
|
11
11
|
import 'node:crypto';
|
|
12
|
-
import { h as encrypt, d as createCredentialsResolver, e as updatePaymentRequestStatusCommand, a as getPaymentRequestsByBatchIdQuery, g as getBatchByIdQuery, u as upsertBatchCommand, i as importAesKey, f as createPaymentCommand, b as getAccountByIdQuery } from '../shared/bank.
|
|
12
|
+
import { h as encrypt, d as createCredentialsResolver, e as updatePaymentRequestStatusCommand, a as getPaymentRequestsByBatchIdQuery, g as getBatchByIdQuery, u as upsertBatchCommand, i as importAesKey, f as createPaymentCommand, b as getAccountByIdQuery } from '../shared/bank.BELDXSDV.mjs';
|
|
13
13
|
import 'drizzle-orm/zod';
|
|
14
14
|
import 'drizzle-orm/sqlite-core';
|
|
15
15
|
|
|
16
|
+
const SYNC_WORKFLOW_HEARTBEAT_CRON = "0 */8 * * *";
|
|
17
|
+
const DEAD_STATUSES = /* @__PURE__ */ new Set([
|
|
18
|
+
"complete",
|
|
19
|
+
"terminated",
|
|
20
|
+
"errored",
|
|
21
|
+
"unknown"
|
|
22
|
+
]);
|
|
23
|
+
async function heartbeatSyncWorkflows({
|
|
24
|
+
entities,
|
|
25
|
+
resetAfterIterations,
|
|
26
|
+
getInstance,
|
|
27
|
+
createInstance,
|
|
28
|
+
logger
|
|
29
|
+
}) {
|
|
30
|
+
await Promise.all(
|
|
31
|
+
entities.map(async ({ id, iterationCount }) => {
|
|
32
|
+
try {
|
|
33
|
+
const instance = await getInstance(id);
|
|
34
|
+
const { status } = await instance.status();
|
|
35
|
+
if (DEAD_STATUSES.has(status)) {
|
|
36
|
+
await instance.restart();
|
|
37
|
+
logger.info("sync-workflow.heartbeat.zombie-recovery", {
|
|
38
|
+
id,
|
|
39
|
+
previousStatus: status
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (iterationCount == null) {
|
|
44
|
+
logger.warn("sync-workflow.heartbeat.iteration-count-missing", {
|
|
45
|
+
id,
|
|
46
|
+
status
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (iterationCount >= resetAfterIterations) {
|
|
51
|
+
await instance.restart();
|
|
52
|
+
logger.info("sync-workflow.heartbeat.scheduled-reset", {
|
|
53
|
+
id,
|
|
54
|
+
iterationCount
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
try {
|
|
59
|
+
await createInstance(id);
|
|
60
|
+
logger.info("sync-workflow.heartbeat.created-missing-instance", {
|
|
61
|
+
id
|
|
62
|
+
});
|
|
63
|
+
} catch (createErr) {
|
|
64
|
+
logger.error("sync-workflow.heartbeat.failed", {
|
|
65
|
+
id,
|
|
66
|
+
error: err instanceof Error ? err.message : String(err),
|
|
67
|
+
createError: createErr instanceof Error ? createErr.message : String(createErr)
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
16
75
|
const sendPaymentInputSchema = z.object({
|
|
17
76
|
correlationId: z.string().min(1),
|
|
18
77
|
refId: z.string().optional(),
|
|
@@ -755,6 +814,7 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
755
814
|
const byConnector = Map.groupBy(allPRs, (pr) => pr.connectorKey);
|
|
756
815
|
let processed = 0;
|
|
757
816
|
let statusChanged = 0;
|
|
817
|
+
let skippedNoBankRefId = 0;
|
|
758
818
|
const eventsToEmit = [];
|
|
759
819
|
for (const [connectorKey, requests] of byConnector) {
|
|
760
820
|
let connector;
|
|
@@ -767,10 +827,30 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
767
827
|
continue;
|
|
768
828
|
}
|
|
769
829
|
for (const pr of requests) {
|
|
830
|
+
if (!pr.bankRefId && pr.connectorKey === "DBU") {
|
|
831
|
+
skippedNoBankRefId++;
|
|
832
|
+
console.warn(
|
|
833
|
+
"[_resolvePaymentRequestStatuses] Skipping DBU PR without bankRefId",
|
|
834
|
+
{
|
|
835
|
+
prId: pr.id,
|
|
836
|
+
status: pr.status,
|
|
837
|
+
connectorKey: pr.connectorKey,
|
|
838
|
+
createdAt: pr.createdAt
|
|
839
|
+
}
|
|
840
|
+
);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
770
843
|
try {
|
|
771
844
|
const paymentId = pr.bankRefId ?? pr.id;
|
|
772
845
|
const newStatus = await connector.getPaymentStatus({ paymentId });
|
|
773
846
|
if (newStatus !== pr.status) {
|
|
847
|
+
console.log("[_resolvePaymentRequestStatuses] Status changed", {
|
|
848
|
+
prId: pr.id,
|
|
849
|
+
connectorKey: pr.connectorKey,
|
|
850
|
+
bankRefId: pr.bankRefId,
|
|
851
|
+
oldStatus: pr.status,
|
|
852
|
+
newStatus
|
|
853
|
+
});
|
|
774
854
|
await updatePaymentRequestStatusCommand(this.db, {
|
|
775
855
|
id: pr.id,
|
|
776
856
|
status: newStatus,
|
|
@@ -783,17 +863,34 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
783
863
|
}
|
|
784
864
|
processed++;
|
|
785
865
|
} catch (err) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
866
|
+
console.error(
|
|
867
|
+
"[_resolvePaymentRequestStatuses] Failed to get payment status",
|
|
868
|
+
{
|
|
869
|
+
prId: pr.id,
|
|
870
|
+
connectorKey: pr.connectorKey,
|
|
871
|
+
status: pr.status,
|
|
872
|
+
bankRefId: pr.bankRefId,
|
|
873
|
+
error: err instanceof Error ? err.message : String(err)
|
|
874
|
+
}
|
|
875
|
+
);
|
|
789
876
|
}
|
|
790
877
|
}
|
|
791
878
|
}
|
|
792
879
|
if (eventsToEmit.length > 0) {
|
|
793
|
-
|
|
794
|
-
this.
|
|
795
|
-
|
|
796
|
-
|
|
880
|
+
try {
|
|
881
|
+
await this.pushToQueue(
|
|
882
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
883
|
+
eventsToEmit
|
|
884
|
+
);
|
|
885
|
+
} catch (err) {
|
|
886
|
+
console.error(
|
|
887
|
+
"[_resolvePaymentRequestStatuses] Failed to push events to queue",
|
|
888
|
+
{
|
|
889
|
+
eventCount: eventsToEmit.length,
|
|
890
|
+
error: err instanceof Error ? err.message : String(err)
|
|
891
|
+
}
|
|
892
|
+
);
|
|
893
|
+
}
|
|
797
894
|
}
|
|
798
895
|
const affectedBatchIds = [
|
|
799
896
|
...new Set(
|
|
@@ -801,7 +898,32 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
801
898
|
)
|
|
802
899
|
];
|
|
803
900
|
for (const batchId of affectedBatchIds) {
|
|
804
|
-
|
|
901
|
+
try {
|
|
902
|
+
await this._deriveBatchStatus(batchId);
|
|
903
|
+
} catch (err) {
|
|
904
|
+
console.error(
|
|
905
|
+
"[_resolvePaymentRequestStatuses] Failed to derive batch status",
|
|
906
|
+
{
|
|
907
|
+
batchId,
|
|
908
|
+
error: err instanceof Error ? err.message : String(err)
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
console.log("[_resolvePaymentRequestStatuses] Summary", {
|
|
914
|
+
total: allPRs.length,
|
|
915
|
+
processed,
|
|
916
|
+
statusChanged,
|
|
917
|
+
skippedNoBankRefId
|
|
918
|
+
});
|
|
919
|
+
if (skippedNoBankRefId > 5) {
|
|
920
|
+
console.warn(
|
|
921
|
+
"[_resolvePaymentRequestStatuses] HIGH NUMBER of PRs without bankRefId",
|
|
922
|
+
{
|
|
923
|
+
count: skippedNoBankRefId,
|
|
924
|
+
note: "These PRs may be orphaned - payment sent but bankRefId not saved"
|
|
925
|
+
}
|
|
926
|
+
);
|
|
805
927
|
}
|
|
806
928
|
return { processed, statusChanged };
|
|
807
929
|
}
|
|
@@ -816,8 +938,9 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
816
938
|
if (!batch || batch.status === "COMPLETED" || batch.status === "FAILED")
|
|
817
939
|
return;
|
|
818
940
|
if (allPRs.length === 0) return;
|
|
941
|
+
const connectorKey = allPRs[0]?.connectorKey;
|
|
819
942
|
const allTerminal = allPRs.every(
|
|
820
|
-
(pr) => isTerminalStatus(pr.status)
|
|
943
|
+
(pr) => isTerminalStatus(pr.status, connectorKey)
|
|
821
944
|
);
|
|
822
945
|
const allAuthorizedOrHigher = allPRs.every((pr) => pr.status !== "OPENED");
|
|
823
946
|
if (allTerminal) {
|
|
@@ -838,16 +961,49 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
838
961
|
null,
|
|
839
962
|
{ successMessage: "Payment request statuses updated" },
|
|
840
963
|
async () => {
|
|
964
|
+
const startTime = Date.now();
|
|
965
|
+
console.log("[updatePaymentRequestStatuses] Starting", {
|
|
966
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
967
|
+
});
|
|
841
968
|
const nonTerminalPRs = await getNonTerminalPaymentRequestsQuery(this.db);
|
|
842
969
|
if (nonTerminalPRs.length === 0) {
|
|
970
|
+
console.log(
|
|
971
|
+
"[updatePaymentRequestStatuses] No non-terminal PRs found",
|
|
972
|
+
{
|
|
973
|
+
duration: `${Date.now() - startTime}ms`
|
|
974
|
+
}
|
|
975
|
+
);
|
|
843
976
|
return { processed: 0, statusChanged: 0 };
|
|
844
977
|
}
|
|
978
|
+
const byConnector = nonTerminalPRs.reduce(
|
|
979
|
+
(acc, pr) => {
|
|
980
|
+
const key = pr.connectorKey || "unknown";
|
|
981
|
+
acc[key] = (acc[key] || 0) + 1;
|
|
982
|
+
return acc;
|
|
983
|
+
},
|
|
984
|
+
{}
|
|
985
|
+
);
|
|
986
|
+
console.log("[updatePaymentRequestStatuses] By connector", byConnector);
|
|
845
987
|
const now = Date.now();
|
|
846
988
|
const pollableIds = [];
|
|
847
989
|
for (const pr of nonTerminalPRs) {
|
|
848
990
|
const status = pr.status;
|
|
991
|
+
if (isTerminalStatus(status, pr.connectorKey)) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
849
994
|
if (status === "OPENED" || status === "AUTHORIZED") {
|
|
850
995
|
if (pr.createdAt != null && now - pr.createdAt.getTime() > this.POLLING_TIMEOUT_MS) {
|
|
996
|
+
console.warn(
|
|
997
|
+
"[updatePaymentRequestStatuses] Closing PR due to timeout",
|
|
998
|
+
{
|
|
999
|
+
prId: pr.id,
|
|
1000
|
+
status: pr.status,
|
|
1001
|
+
connectorKey: pr.connectorKey,
|
|
1002
|
+
createdAt: pr.createdAt,
|
|
1003
|
+
bankRefId: pr.bankRefId,
|
|
1004
|
+
age: `${Math.floor((now - pr.createdAt.getTime()) / (24 * 60 * 60 * 1e3))} days`
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
851
1007
|
await updatePaymentRequestStatusCommand(this.db, {
|
|
852
1008
|
id: pr.id,
|
|
853
1009
|
status: "CLOSED",
|
|
@@ -857,14 +1013,20 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
857
1013
|
continue;
|
|
858
1014
|
}
|
|
859
1015
|
pollableIds.push(pr.id);
|
|
860
|
-
} else if (status === "COMPLETED" || status === "BOOKED") {
|
|
861
|
-
if (pr.processedAt != null && now - pr.processedAt.getTime() > this.COMPLETED_POLLING_WINDOW_MS) {
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
864
|
-
pollableIds.push(pr.id);
|
|
865
1016
|
}
|
|
866
1017
|
}
|
|
867
|
-
|
|
1018
|
+
const result = await this._resolvePaymentRequestStatuses(pollableIds);
|
|
1019
|
+
const duration = Date.now() - startTime;
|
|
1020
|
+
console.log("[updatePaymentRequestStatuses] Completed", {
|
|
1021
|
+
duration: `${duration}ms`,
|
|
1022
|
+
metrics: {
|
|
1023
|
+
total: nonTerminalPRs.length,
|
|
1024
|
+
polled: pollableIds.length,
|
|
1025
|
+
processed: result.processed,
|
|
1026
|
+
statusChanged: result.statusChanged
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
return result;
|
|
868
1030
|
}
|
|
869
1031
|
);
|
|
870
1032
|
}
|
|
@@ -872,8 +1034,46 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
872
1034
|
if (controller.cron === this.env.CRON_PAYMENT_STATUSES) {
|
|
873
1035
|
console.log("Scheduled CRON payment request statuses");
|
|
874
1036
|
await this.updatePaymentRequestStatuses();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (controller.cron === SYNC_WORKFLOW_HEARTBEAT_CRON) {
|
|
1040
|
+
console.log("Scheduled CRON sync workflow heartbeat");
|
|
1041
|
+
await this.heartbeatSyncWorkflows();
|
|
875
1042
|
}
|
|
876
1043
|
}
|
|
1044
|
+
async heartbeatSyncWorkflows() {
|
|
1045
|
+
const accounts = await this.db.select({
|
|
1046
|
+
id: tables.account.id,
|
|
1047
|
+
lastSyncMetadata: tables.account.lastSyncMetadata
|
|
1048
|
+
}).from(tables.account).where(eq(tables.account.status, "AUTHORIZED")).all();
|
|
1049
|
+
const resetAfterIterations = Number(
|
|
1050
|
+
this.env.SYNC_WORKFLOW_RESET_AFTER_ITERATIONS
|
|
1051
|
+
);
|
|
1052
|
+
if (!Number.isFinite(resetAfterIterations) || resetAfterIterations <= 0) {
|
|
1053
|
+
console.error(
|
|
1054
|
+
"Invalid SYNC_WORKFLOW_RESET_AFTER_ITERATIONS, skipping heartbeat",
|
|
1055
|
+
{ value: this.env.SYNC_WORKFLOW_RESET_AFTER_ITERATIONS }
|
|
1056
|
+
);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
await heartbeatSyncWorkflows({
|
|
1060
|
+
entities: accounts.map((a) => ({
|
|
1061
|
+
id: a.id,
|
|
1062
|
+
iterationCount: a.lastSyncMetadata?.iterationCount
|
|
1063
|
+
})),
|
|
1064
|
+
resetAfterIterations,
|
|
1065
|
+
getInstance: (id) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(id),
|
|
1066
|
+
createInstance: (id) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create({
|
|
1067
|
+
id,
|
|
1068
|
+
params: { accountId: id }
|
|
1069
|
+
}),
|
|
1070
|
+
logger: {
|
|
1071
|
+
info: (event, data) => console.log(JSON.stringify({ level: "info", event, ...data })),
|
|
1072
|
+
warn: (event, data) => console.warn(JSON.stringify({ level: "warn", event, ...data })),
|
|
1073
|
+
error: (event, data) => console.error(JSON.stringify({ level: "error", event, ...data }))
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
877
1077
|
async handleAuthorizationCallback(input) {
|
|
878
1078
|
return this.handleAction(
|
|
879
1079
|
{ data: input, schema: handleAuthorizationCallbackInputSchema },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
|
-
const ott_zod = require('../shared/bank.
|
|
4
|
+
const ott_zod = require('../shared/bank.DiJmJkDt.cjs');
|
|
5
5
|
const batchLifecycle = require('../shared/bank.NF8bZBy0.cjs');
|
|
6
6
|
const drizzleOrm = require('drizzle-orm');
|
|
7
|
-
const credentialsResolver = require('../shared/bank.
|
|
7
|
+
const credentialsResolver = require('../shared/bank.CioJeFzf.cjs');
|
|
8
8
|
const cloudflare_workers = require('cloudflare:workers');
|
|
9
9
|
const cloudflare_workflows = require('cloudflare:workflows');
|
|
10
10
|
const d1 = require('drizzle-orm/d1');
|
|
@@ -305,6 +305,10 @@ function createWorkflowLogger(instanceId) {
|
|
|
305
305
|
};
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
function getStepCount(ctx) {
|
|
309
|
+
return ctx.step?.count ?? 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
308
312
|
async function pushToQueue(queue, message) {
|
|
309
313
|
if (!Array.isArray(message)) {
|
|
310
314
|
await queue.send(message, { contentType: "v8" });
|
|
@@ -325,6 +329,10 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
325
329
|
if (!accountId) {
|
|
326
330
|
throw new cloudflare_workflows.NonRetryableError(`Haven't obtained accountId to load.`);
|
|
327
331
|
}
|
|
332
|
+
const workflowStartedAt = await step.do(
|
|
333
|
+
"capture workflow start time",
|
|
334
|
+
async () => Date.now()
|
|
335
|
+
);
|
|
328
336
|
while (true) {
|
|
329
337
|
const now = /* @__PURE__ */ new Date();
|
|
330
338
|
const account = await step.do("load account", async () => {
|
|
@@ -390,99 +398,106 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
390
398
|
const paymentsToProcess = payments.filter(
|
|
391
399
|
(p) => ott_zod.isPaymentCompleted(p.parsed)
|
|
392
400
|
);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
|
|
402
|
+
const paymentsToInsert = paymentsToProcess.filter(
|
|
403
|
+
(p) => !lastSyncBankRefIds.includes(p.parsed.bankRefId)
|
|
404
|
+
);
|
|
405
|
+
await step.do(
|
|
406
|
+
"process new payments and update lastSyncAt",
|
|
407
|
+
async (ctx) => {
|
|
408
|
+
const eventsToEmit = [];
|
|
409
|
+
const bankRefIds = paymentsToInsert.map((p) => p.parsed.bankRefId).filter((id) => id != null);
|
|
410
|
+
const BANK_REF_ID_CHUNK_SIZE = 90;
|
|
411
|
+
const matchingRequests = [];
|
|
412
|
+
for (let i = 0; i < bankRefIds.length; i += BANK_REF_ID_CHUNK_SIZE) {
|
|
413
|
+
const chunkIds = bankRefIds.slice(i, i + BANK_REF_ID_CHUNK_SIZE);
|
|
414
|
+
const rows = await db.select().from(ott_zod.tables.paymentRequest).where(
|
|
404
415
|
drizzleOrm.and(
|
|
405
|
-
drizzleOrm.inArray(ott_zod.tables.paymentRequest.bankRefId,
|
|
416
|
+
drizzleOrm.inArray(ott_zod.tables.paymentRequest.bankRefId, chunkIds),
|
|
406
417
|
drizzleOrm.eq(ott_zod.tables.paymentRequest.accountId, account.id),
|
|
407
|
-
drizzleOrm.eq(
|
|
408
|
-
ott_zod.tables.paymentRequest.connectorKey,
|
|
409
|
-
account.connectorKey
|
|
410
|
-
)
|
|
418
|
+
drizzleOrm.eq(ott_zod.tables.paymentRequest.connectorKey, account.connectorKey)
|
|
411
419
|
)
|
|
412
|
-
) : [];
|
|
413
|
-
const requestByBankRefId = Object.fromEntries(
|
|
414
|
-
matchingRequests.map((r) => [r.bankRefId, r])
|
|
415
420
|
);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
421
|
+
matchingRequests.push(...rows);
|
|
422
|
+
}
|
|
423
|
+
const requestByBankRefId = Object.fromEntries(
|
|
424
|
+
matchingRequests.map((r) => [r.bankRefId, r])
|
|
425
|
+
);
|
|
426
|
+
const enrichedPayments = paymentsToInsert.map((p) => {
|
|
427
|
+
const req = p.parsed.bankRefId && requestByBankRefId[p.parsed.bankRefId] || null;
|
|
428
|
+
if (!req) return p;
|
|
429
|
+
return {
|
|
430
|
+
...p,
|
|
431
|
+
parsed: {
|
|
432
|
+
...p.parsed,
|
|
433
|
+
// queue-bus: transaction matching (DBU doesn't echo these in statements)
|
|
434
|
+
vs: p.parsed.vs ?? req.vs,
|
|
435
|
+
ss: p.parsed.ss ?? req.ss,
|
|
436
|
+
ks: p.parsed.ks ?? req.ks,
|
|
437
|
+
message: p.parsed.message ?? req.message,
|
|
438
|
+
// queue-bus: party creation
|
|
439
|
+
creditor: {
|
|
440
|
+
...p.parsed.creditor,
|
|
441
|
+
holderName: p.parsed.creditor?.holderName ?? req.creditor?.holderName
|
|
442
|
+
},
|
|
443
|
+
debtor: {
|
|
444
|
+
...p.parsed.debtor,
|
|
445
|
+
holderName: p.parsed.debtor?.holderName ?? req.debtor?.holderName
|
|
439
446
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
447
|
+
// NOT enriched: chargeBearer, instructionPriority, refId, batchId,
|
|
448
|
+
// createdAt, address, swiftBic — no downstream consumer in payment table
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
const createCommands = enrichedPayments.map(
|
|
453
|
+
(p) => credentialsResolver.createPaymentCommand(db, { payment: p.parsed }).command
|
|
454
|
+
);
|
|
455
|
+
eventsToEmit.push(
|
|
456
|
+
...enrichedPayments.map((p) => ({
|
|
457
|
+
eventType: "BANK_PAYMENT",
|
|
458
|
+
eventSignal: "paymentFetched",
|
|
459
|
+
bankPayment: p.parsed,
|
|
460
|
+
metadata: {
|
|
461
|
+
correlationId: p.parsed.correlationId,
|
|
462
|
+
entityId: p.parsed.id,
|
|
463
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
464
|
+
}
|
|
465
|
+
}))
|
|
466
|
+
);
|
|
467
|
+
const lastSyncMetadata = {
|
|
468
|
+
payments: payments.length,
|
|
469
|
+
paymentsToProcess: paymentsToProcess.length,
|
|
470
|
+
paymentsInserted: paymentsToInsert.length,
|
|
471
|
+
lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
|
|
472
|
+
lastSyncPayments: lastSyncBankRefIds.length,
|
|
473
|
+
eventsEmitted: eventsToEmit.length,
|
|
474
|
+
iterationCount: getStepCount(ctx),
|
|
475
|
+
workflowStartedAt
|
|
476
|
+
};
|
|
477
|
+
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
478
|
+
accountId: account.id,
|
|
479
|
+
lastSyncAt: now,
|
|
480
|
+
lastSyncMetadata
|
|
481
|
+
}).command;
|
|
482
|
+
if (createCommands.length) {
|
|
483
|
+
await db.batch(
|
|
484
|
+
backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
444
485
|
);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
entityId: p.parsed.id,
|
|
453
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
454
|
-
}
|
|
455
|
-
}))
|
|
486
|
+
} else {
|
|
487
|
+
await updateLastSyncCommand;
|
|
488
|
+
}
|
|
489
|
+
if (eventsToEmit.length) {
|
|
490
|
+
await pushToQueue(
|
|
491
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
492
|
+
eventsToEmit
|
|
456
493
|
);
|
|
457
|
-
const lastSyncMetadata = {
|
|
458
|
-
payments: payments.length,
|
|
459
|
-
paymentsToProcess: paymentsToProcess.length,
|
|
460
|
-
paymentsInserted: paymentsToInsert.length,
|
|
461
|
-
lastSyncBankRefIds: paymentsToProcess.filter((p) => p.parsed.status === "BOOKED").map((p) => p.parsed.bankRefId),
|
|
462
|
-
lastSyncPayments: lastSyncBankRefIds.length,
|
|
463
|
-
eventsEmitted: eventsToEmit.length
|
|
464
|
-
};
|
|
465
|
-
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
466
|
-
accountId: account.id,
|
|
467
|
-
lastSyncAt: now,
|
|
468
|
-
lastSyncMetadata
|
|
469
|
-
}).command;
|
|
470
|
-
if (eventsToEmit.length) {
|
|
471
|
-
await db.batch(
|
|
472
|
-
backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
473
|
-
);
|
|
474
|
-
await pushToQueue(
|
|
475
|
-
this.env.QUEUE_BUS_QUEUE,
|
|
476
|
-
eventsToEmit
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
return {
|
|
480
|
-
...lastSyncMetadata,
|
|
481
|
-
newLastSyncAt: now
|
|
482
|
-
};
|
|
483
494
|
}
|
|
484
|
-
|
|
485
|
-
|
|
495
|
+
return {
|
|
496
|
+
...lastSyncMetadata,
|
|
497
|
+
newLastSyncAt: now
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
);
|
|
486
501
|
await step.sleep(
|
|
487
502
|
"Sleep for next sync",
|
|
488
503
|
`${account.syncIntervalS} seconds`
|