@develit-services/bank 5.7.0 → 5.8.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.
- package/dist/base.cjs +222 -18
- package/dist/base.d.cts +14 -7
- package/dist/base.d.mts +14 -7
- package/dist/base.d.ts +14 -7
- package/dist/base.mjs +222 -18
- 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/workflows.cjs +204 -41
- package/dist/export/workflows.d.cts +1 -0
- package/dist/export/workflows.d.mts +1 -0
- package/dist/export/workflows.d.ts +1 -0
- package/dist/export/workflows.mjs +205 -42
- package/dist/service.d.cts +3 -0
- package/dist/service.d.mts +3 -0
- package/dist/service.d.ts +3 -0
- package/dist/shared/{bank.0zYbe2ZB.d.ts → bank.BOeuHdjy.d.ts} +1 -1
- package/dist/shared/{bank.DI_Q2OtU.mjs → bank.BVXtqHnq.mjs} +47 -9
- package/dist/shared/{bank.B1Xa5U2u.cjs → bank.CQjuudum.cjs} +47 -9
- package/dist/shared/{bank.DmZly4Hz.d.cts → bank.CefGTNH1.d.cts} +6 -4
- package/dist/shared/{bank.DmZly4Hz.d.mts → bank.CefGTNH1.d.mts} +6 -4
- package/dist/shared/{bank.DmZly4Hz.d.ts → bank.CefGTNH1.d.ts} +6 -4
- package/dist/shared/{bank.sVKKxDLm.d.mts → bank.CtIkqQG9.d.cts} +1 -1
- package/dist/shared/{bank.BcAwr2OG.cjs → bank.Cy7xa566.cjs} +46 -1
- package/dist/shared/{bank.BTsBHRsn.mjs → bank.D6gBgL07.mjs} +43 -2
- package/dist/shared/{bank.Dyt5JZy1.d.cts → bank.oNDZ44du.d.mts} +1 -1
- package/dist/types.cjs +1 -1
- package/dist/types.d.cts +12 -5
- package/dist/types.d.mts +12 -5
- package/dist/types.d.ts +12 -5
- package/dist/types.mjs +1 -1
- package/package.json +1 -1
|
@@ -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, m as isPaymentCompleted } from '../shared/bank.
|
|
2
|
+
import { G as tables, H as relations, v as toBatchedPaymentFromPaymentRequest, z as toPreparedPayment, m as isPaymentCompleted } from '../shared/bank.BVXtqHnq.mjs';
|
|
3
3
|
import { i as isBatchAuthorized, b as isBatchFailed, d as isBatchProcessing } from '../shared/bank.XqSw509X.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, i as initiateConnector, e as updatePaymentRequestStatusCommand, f as createPaymentCommand } from '../shared/bank.
|
|
4
|
+
import { sql, and, eq, inArray } from 'drizzle-orm';
|
|
5
|
+
import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, i as initiateConnector, e as updatePaymentRequestStatusCommand, f as isSupersededBy, h as createPaymentCommand, r as resolveIterationBudget } from '../shared/bank.D6gBgL07.mjs';
|
|
6
6
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
7
7
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
8
8
|
import { drizzle } from 'drizzle-orm/d1';
|
|
@@ -18,12 +18,17 @@ import 'drizzle-orm/zod';
|
|
|
18
18
|
const updateAccountLastSyncCommand = (db, {
|
|
19
19
|
lastSyncAt,
|
|
20
20
|
accountId,
|
|
21
|
-
lastSyncMetadata
|
|
21
|
+
lastSyncMetadata,
|
|
22
|
+
seenInstanceId
|
|
22
23
|
}) => {
|
|
24
|
+
const markerUnchanged = seenInstanceId === void 0 ? void 0 : (
|
|
25
|
+
// IS instead of = so a null marker compares equal to null.
|
|
26
|
+
sql`json_extract(${tables.account.lastSyncMetadata}, '$.instanceId') IS ${seenInstanceId}`
|
|
27
|
+
);
|
|
23
28
|
const command = db.update(tables.account).set({
|
|
24
29
|
lastSyncAt,
|
|
25
30
|
lastSyncMetadata
|
|
26
|
-
}).where(eq(tables.account.id, accountId)).returning();
|
|
31
|
+
}).where(and(eq(tables.account.id, accountId), markerUnchanged)).returning();
|
|
27
32
|
return {
|
|
28
33
|
command
|
|
29
34
|
};
|
|
@@ -338,6 +343,72 @@ function getStepCount(ctx) {
|
|
|
338
343
|
return ctx.step?.count ?? 0;
|
|
339
344
|
}
|
|
340
345
|
|
|
346
|
+
const MAX_CATCH_UP_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
347
|
+
function resolveSyncWindow(lastSyncAt, nowMs, maxWindowMs) {
|
|
348
|
+
const openEnded = {
|
|
349
|
+
dateFrom: lastSyncAt,
|
|
350
|
+
dateTo: new Date(Math.max(nowMs, lastSyncAt.getTime())),
|
|
351
|
+
isCatchUp: false
|
|
352
|
+
};
|
|
353
|
+
if (!Number.isFinite(maxWindowMs) || maxWindowMs <= 0) return openEnded;
|
|
354
|
+
const clippedToMs = lastSyncAt.getTime() + maxWindowMs;
|
|
355
|
+
if (clippedToMs >= nowMs) return openEnded;
|
|
356
|
+
return {
|
|
357
|
+
dateFrom: lastSyncAt,
|
|
358
|
+
dateTo: new Date(clippedToMs),
|
|
359
|
+
isCatchUp: true
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const STEP_RESULT_BYTE_CEILING = 800 * 1024;
|
|
364
|
+
class StepResultTooLargeError extends NonRetryableError {
|
|
365
|
+
}
|
|
366
|
+
function guardStepResultSize(value, ceilingBytes = STEP_RESULT_BYTE_CEILING) {
|
|
367
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
368
|
+
if (bytes > ceilingBytes) {
|
|
369
|
+
const count = Array.isArray(value) ? value.length : 1;
|
|
370
|
+
throw new StepResultTooLargeError(
|
|
371
|
+
`Fetched ${count} payments (${bytes} bytes), over the ${ceilingBytes} byte step result ceiling`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function rethrowFetchError(err) {
|
|
376
|
+
if (err instanceof NonRetryableError) throw err;
|
|
377
|
+
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
378
|
+
throw new Error(message);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const MIN_CATCH_UP_WINDOW_MS = 6 * 60 * 60 * 1e3;
|
|
382
|
+
async function fetchBoundedByWindow({
|
|
383
|
+
window,
|
|
384
|
+
fetchWindow,
|
|
385
|
+
guardResult,
|
|
386
|
+
minWindowMs,
|
|
387
|
+
onNarrowed
|
|
388
|
+
}) {
|
|
389
|
+
let current = window;
|
|
390
|
+
while (true) {
|
|
391
|
+
const result = await fetchWindow(current);
|
|
392
|
+
try {
|
|
393
|
+
guardResult(result);
|
|
394
|
+
return { result, window: current };
|
|
395
|
+
} catch (tooLarge) {
|
|
396
|
+
if (!(tooLarge instanceof StepResultTooLargeError)) throw tooLarge;
|
|
397
|
+
const halvedSpanMs = Math.floor(
|
|
398
|
+
(current.dateTo.getTime() - current.dateFrom.getTime()) / 2
|
|
399
|
+
);
|
|
400
|
+
if (halvedSpanMs < minWindowMs) throw tooLarge;
|
|
401
|
+
current = {
|
|
402
|
+
dateFrom: current.dateFrom,
|
|
403
|
+
dateTo: new Date(current.dateFrom.getTime() + halvedSpanMs),
|
|
404
|
+
// Stops short of the original target, so another pass is needed.
|
|
405
|
+
isCatchUp: true
|
|
406
|
+
};
|
|
407
|
+
onNarrowed?.(current);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
341
412
|
async function pushToQueue(queue, message) {
|
|
342
413
|
if (!Array.isArray(message)) {
|
|
343
414
|
await queue.send(message, { contentType: "v8" });
|
|
@@ -352,7 +423,8 @@ async function pushToQueue(queue, message) {
|
|
|
352
423
|
}
|
|
353
424
|
class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
354
425
|
async run(event, step) {
|
|
355
|
-
const { accountId } = event.payload;
|
|
426
|
+
const { accountId, maxIterations } = event.payload;
|
|
427
|
+
const iterationBudget = resolveIterationBudget(maxIterations);
|
|
356
428
|
const db = drizzle(this.env.BANK_D1, { schema: tables, relations });
|
|
357
429
|
const logger = createWorkflowLogger(event.instanceId);
|
|
358
430
|
if (!accountId) {
|
|
@@ -362,8 +434,7 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
362
434
|
"capture workflow start time",
|
|
363
435
|
async () => Date.now()
|
|
364
436
|
);
|
|
365
|
-
|
|
366
|
-
const now = /* @__PURE__ */ new Date();
|
|
437
|
+
for (let iteration = 0; iteration < iterationBudget; iteration++) {
|
|
367
438
|
const account = await step.do("load account", async () => {
|
|
368
439
|
const account2 = await getAccountByIdQuery(db, { accountId });
|
|
369
440
|
if (!account2) {
|
|
@@ -374,7 +445,38 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
374
445
|
if (!account.lastSyncAt) {
|
|
375
446
|
throw new Error(`lastSyncedAt is not set for account: ${accountId}`);
|
|
376
447
|
}
|
|
377
|
-
|
|
448
|
+
if (isSupersededBy(
|
|
449
|
+
event.instanceId,
|
|
450
|
+
account.lastSyncMetadata?.instanceId,
|
|
451
|
+
accountId
|
|
452
|
+
)) {
|
|
453
|
+
logger.info("sync.superseded", {
|
|
454
|
+
accountId,
|
|
455
|
+
instanceId: event.instanceId,
|
|
456
|
+
supersededBy: account.lastSyncMetadata?.instanceId,
|
|
457
|
+
iteration
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const lastSyncAtMs = account.lastSyncAt.getTime();
|
|
462
|
+
const windowMs = await step.do("resolve sync window", async () => {
|
|
463
|
+
const window = resolveSyncWindow(
|
|
464
|
+
new Date(lastSyncAtMs),
|
|
465
|
+
Date.now(),
|
|
466
|
+
MAX_CATCH_UP_WINDOW_MS
|
|
467
|
+
);
|
|
468
|
+
return {
|
|
469
|
+
dateFromMs: window.dateFrom.getTime(),
|
|
470
|
+
dateToMs: window.dateTo.getTime(),
|
|
471
|
+
isCatchUp: window.isCatchUp
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
const syncWindow = {
|
|
475
|
+
dateFrom: new Date(windowMs.dateFromMs),
|
|
476
|
+
dateTo: new Date(windowMs.dateToMs),
|
|
477
|
+
isCatchUp: windowMs.isCatchUp
|
|
478
|
+
};
|
|
479
|
+
const fetched = await step.do(
|
|
378
480
|
"fetch bank payments",
|
|
379
481
|
{
|
|
380
482
|
retries: { limit: 5, delay: "2 minutes", backoff: "exponential" },
|
|
@@ -403,42 +505,88 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
403
505
|
}
|
|
404
506
|
]
|
|
405
507
|
});
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
508
|
+
const fetchWindow = async (window) => {
|
|
509
|
+
const result = await connector.getAllAccountPayments({
|
|
510
|
+
account,
|
|
511
|
+
filter: {
|
|
512
|
+
dateFrom: window.dateFrom,
|
|
513
|
+
dateTo: window.dateTo
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
const identifiable = result.filter(
|
|
517
|
+
(p) => p.parsed.bankRefId?.trim()
|
|
518
|
+
);
|
|
519
|
+
const skipped = result.length - identifiable.length;
|
|
520
|
+
if (skipped > 0) {
|
|
521
|
+
logger.warn("payments.fetch.skipped_blank_bank_ref", {
|
|
522
|
+
accountId,
|
|
523
|
+
connectorKey: account.connectorKey,
|
|
524
|
+
skipped
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return Promise.all(
|
|
528
|
+
identifiable.map(async (p) => ({
|
|
529
|
+
parsed: {
|
|
530
|
+
...p.parsed,
|
|
531
|
+
id: await deriveV4PaymentId(p.parsed)
|
|
532
|
+
}
|
|
533
|
+
}))
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
let bounded;
|
|
537
|
+
try {
|
|
538
|
+
bounded = await fetchBoundedByWindow({
|
|
539
|
+
window: syncWindow,
|
|
540
|
+
fetchWindow,
|
|
541
|
+
guardResult: guardStepResultSize,
|
|
542
|
+
minWindowMs: MIN_CATCH_UP_WINDOW_MS,
|
|
543
|
+
onNarrowed: (narrowed) => logger.warn("payments.fetch.window-narrowed", {
|
|
544
|
+
accountId,
|
|
545
|
+
connectorKey: account.connectorKey,
|
|
546
|
+
dateFrom: narrowed.dateFrom.toISOString(),
|
|
547
|
+
dateTo: narrowed.dateTo.toISOString()
|
|
548
|
+
})
|
|
549
|
+
});
|
|
550
|
+
} catch (err) {
|
|
551
|
+
if (err instanceof StepResultTooLargeError) {
|
|
552
|
+
logger.error("payments.fetch.result-too-large", {
|
|
553
|
+
accountId,
|
|
554
|
+
connectorKey: account.connectorKey,
|
|
555
|
+
dateFrom: syncWindow.dateFrom.toISOString(),
|
|
556
|
+
dateTo: syncWindow.dateTo.toISOString()
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
throw err;
|
|
560
|
+
}
|
|
410
561
|
logger.info("payments.fetch.completed", {
|
|
411
562
|
accountId,
|
|
412
563
|
connectorKey: account.connectorKey,
|
|
413
|
-
paymentsCount: result.length
|
|
564
|
+
paymentsCount: bounded.result.length
|
|
414
565
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
skipped
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
return Promise.all(
|
|
427
|
-
identifiable.map(async (p) => ({
|
|
428
|
-
parsed: { ...p.parsed, id: await deriveV4PaymentId(p.parsed) }
|
|
429
|
-
}))
|
|
430
|
-
);
|
|
566
|
+
return {
|
|
567
|
+
payments: bounded.result,
|
|
568
|
+
windowMs: {
|
|
569
|
+
dateFromMs: bounded.window.dateFrom.getTime(),
|
|
570
|
+
dateToMs: bounded.window.dateTo.getTime(),
|
|
571
|
+
isCatchUp: bounded.window.isCatchUp
|
|
572
|
+
}
|
|
573
|
+
};
|
|
431
574
|
} catch (err) {
|
|
432
|
-
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
433
575
|
logger.error("payments.fetch.failed", {
|
|
434
576
|
accountId,
|
|
435
577
|
connectorKey: account.connectorKey,
|
|
436
|
-
error: message
|
|
578
|
+
error: err instanceof Error ? err.message : String(err)
|
|
437
579
|
});
|
|
438
|
-
|
|
580
|
+
rethrowFetchError(err);
|
|
439
581
|
}
|
|
440
582
|
}
|
|
441
583
|
);
|
|
584
|
+
const payments = fetched.payments;
|
|
585
|
+
const effectiveWindow = {
|
|
586
|
+
dateFrom: new Date(fetched.windowMs.dateFromMs),
|
|
587
|
+
dateTo: new Date(fetched.windowMs.dateToMs),
|
|
588
|
+
isCatchUp: fetched.windowMs.isCatchUp
|
|
589
|
+
};
|
|
442
590
|
const paymentsToProcess = payments.filter(
|
|
443
591
|
(p) => isPaymentCompleted(p.parsed)
|
|
444
592
|
);
|
|
@@ -513,6 +661,7 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
513
661
|
}))
|
|
514
662
|
);
|
|
515
663
|
const lastSyncMetadata = {
|
|
664
|
+
instanceId: event.instanceId,
|
|
516
665
|
payments: payments.length,
|
|
517
666
|
paymentsToProcess: paymentsToProcess.length,
|
|
518
667
|
paymentsInserted: paymentsToInsert.length,
|
|
@@ -529,15 +678,27 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
529
678
|
};
|
|
530
679
|
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
531
680
|
accountId: account.id,
|
|
532
|
-
|
|
533
|
-
|
|
681
|
+
// End of the window just fetched, not `now`: while catching up the
|
|
682
|
+
// window stops short of now, and advancing past it would skip every
|
|
683
|
+
// payment in the gap.
|
|
684
|
+
lastSyncAt: effectiveWindow.dateTo,
|
|
685
|
+
lastSyncMetadata,
|
|
686
|
+
seenInstanceId: account.lastSyncMetadata?.instanceId ?? null
|
|
534
687
|
}).command;
|
|
688
|
+
let syncStateRows;
|
|
535
689
|
if (createCommands.length) {
|
|
536
|
-
await db.batch(
|
|
690
|
+
const [updateResult] = await db.batch(
|
|
537
691
|
asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
538
692
|
);
|
|
693
|
+
syncStateRows = updateResult;
|
|
539
694
|
} else {
|
|
540
|
-
await updateLastSyncCommand;
|
|
695
|
+
syncStateRows = await updateLastSyncCommand;
|
|
696
|
+
}
|
|
697
|
+
if (syncStateRows.length === 0) {
|
|
698
|
+
logger.info("sync.write-superseded", {
|
|
699
|
+
accountId,
|
|
700
|
+
instanceId: event.instanceId
|
|
701
|
+
});
|
|
541
702
|
}
|
|
542
703
|
if (eventsToEmit.length) {
|
|
543
704
|
await pushToQueue(
|
|
@@ -547,14 +708,16 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
547
708
|
}
|
|
548
709
|
return {
|
|
549
710
|
...lastSyncMetadata,
|
|
550
|
-
newLastSyncAt:
|
|
711
|
+
newLastSyncAt: effectiveWindow.dateTo
|
|
551
712
|
};
|
|
552
713
|
}
|
|
553
714
|
);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
715
|
+
if (!effectiveWindow.isCatchUp) {
|
|
716
|
+
await step.sleep(
|
|
717
|
+
"Sleep for next sync",
|
|
718
|
+
`${account.syncIntervalS} seconds`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
558
721
|
}
|
|
559
722
|
}
|
|
560
723
|
}
|
package/dist/service.d.cts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.mts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as CurrencyCode,
|
|
1
|
+
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.CefGTNH1.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
type ReferenceType = `${'VS' | 'SS' | 'KS'}:${number}`;
|
|
@@ -803,6 +803,31 @@ const calculateCzechIban = (accountNumber, bankCode) => {
|
|
|
803
803
|
return `CZ${checkDigits}${basicIban}`;
|
|
804
804
|
};
|
|
805
805
|
|
|
806
|
+
const unsupported = (priority, rail, supported) => {
|
|
807
|
+
throw createInternalError(null, {
|
|
808
|
+
message: `instructionPriority=${priority} is not supported on the ${rail} rail (supported: ${supported}).`,
|
|
809
|
+
code: "UNSUPPORTED_INSTRUCTION_PRIORITY",
|
|
810
|
+
status: 422
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
const toFinbricksPriority = (priority) => {
|
|
814
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
815
|
+
if (priority === "INST") return "INST";
|
|
816
|
+
return unsupported(priority, "Finbricks SEPA/domestic", "NORM, INST");
|
|
817
|
+
};
|
|
818
|
+
const toFinbricksForeignPriority = (priority) => {
|
|
819
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
820
|
+
return unsupported(priority, "Finbricks foreign (SWIFT)", "NORM");
|
|
821
|
+
};
|
|
822
|
+
const toErstePriority = (priority) => {
|
|
823
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
824
|
+
if (priority === "HIGH") return "HIGH";
|
|
825
|
+
return unsupported(priority, "Erste ISO 20022", "NORM, HIGH");
|
|
826
|
+
};
|
|
827
|
+
const assertDbuPriority = (priority) => {
|
|
828
|
+
if (priority === "HIGH") unsupported(priority, "DBU domestic", "NORM, INST");
|
|
829
|
+
};
|
|
830
|
+
|
|
806
831
|
function autoVariableSymbol(paymentId) {
|
|
807
832
|
let hash = 0;
|
|
808
833
|
for (let i = 0; i < paymentId.length; i++) {
|
|
@@ -1090,7 +1115,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1090
1115
|
description: this.PROVIDER === "CSOB" ? composeWithUltimateCreditor(p.message, p.creditor, {
|
|
1091
1116
|
stripDiacritics: p.currency !== "CZK"
|
|
1092
1117
|
}) : p.message,
|
|
1093
|
-
instructionPriority: p.instructionPriority
|
|
1118
|
+
instructionPriority: toFinbricksPriority(p.instructionPriority)
|
|
1094
1119
|
}))
|
|
1095
1120
|
}
|
|
1096
1121
|
})
|
|
@@ -1114,6 +1139,15 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1114
1139
|
initiateForeignBatchImpl(_args) {
|
|
1115
1140
|
throw new Error("Finbricks: Foreign batch not implemented");
|
|
1116
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* endToEndIdentification for the SEPA rail. Optional per the Finbricks spec —
|
|
1144
|
+
* return `undefined` to omit the field entirely, which some executing banks
|
|
1145
|
+
* need because they overwrite the remittance text with it (see
|
|
1146
|
+
* `CreditasConnector`).
|
|
1147
|
+
*/
|
|
1148
|
+
buildSepaEndToEndId(payment) {
|
|
1149
|
+
return this.buildEndToEndId(payment);
|
|
1150
|
+
}
|
|
1117
1151
|
async initiateForeignPayment(payment) {
|
|
1118
1152
|
const debtorAccount = this.connectedAccounts.find(
|
|
1119
1153
|
(acc) => acc.iban === payment.debtorIban
|
|
@@ -1171,6 +1205,9 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1171
1205
|
},
|
|
1172
1206
|
...creditorAgent && { creditorAgent },
|
|
1173
1207
|
...unstructured && { remittanceInformation: { unstructured } },
|
|
1208
|
+
instructionPriority: toFinbricksForeignPriority(
|
|
1209
|
+
payment.instructionPriority
|
|
1210
|
+
),
|
|
1174
1211
|
callbackUrl: `${this.finbricks.REDIRECT_URI}?type=paymentRequest&paymentRequestId=${payment.id}`
|
|
1175
1212
|
}
|
|
1176
1213
|
})
|
|
@@ -1218,6 +1255,10 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1218
1255
|
0,
|
|
1219
1256
|
140
|
|
1220
1257
|
);
|
|
1258
|
+
const endToEndIdentification = this.buildSepaEndToEndId({
|
|
1259
|
+
...payment,
|
|
1260
|
+
id: payment.id
|
|
1261
|
+
});
|
|
1221
1262
|
const bankRefId = uuidv4();
|
|
1222
1263
|
const [response, error] = await useResult(
|
|
1223
1264
|
this.finbricks.request({
|
|
@@ -1231,10 +1272,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1231
1272
|
},
|
|
1232
1273
|
paymentIdentification: {
|
|
1233
1274
|
merchantTransactionId: bankRefId,
|
|
1234
|
-
endToEndIdentification
|
|
1235
|
-
...payment,
|
|
1236
|
-
id: payment.id
|
|
1237
|
-
})
|
|
1275
|
+
...endToEndIdentification && { endToEndIdentification }
|
|
1238
1276
|
},
|
|
1239
1277
|
amount: payment.amount,
|
|
1240
1278
|
debtor: {
|
|
@@ -1249,8 +1287,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1249
1287
|
...creditorAddress && { postalAddress: creditorAddress }
|
|
1250
1288
|
},
|
|
1251
1289
|
remittanceInformation: { unstructured },
|
|
1252
|
-
|
|
1253
|
-
instructionPriority: payment.instructionPriority === "INST" ? "INST" : "NORM",
|
|
1290
|
+
instructionPriority: toFinbricksPriority(payment.instructionPriority),
|
|
1254
1291
|
callbackUrl: `${this.finbricks.REDIRECT_URI}?type=paymentRequest&paymentRequestId=${payment.id}`
|
|
1255
1292
|
}
|
|
1256
1293
|
})
|
|
@@ -1303,7 +1340,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1303
1340
|
// TEMP: clientId disabled per client request — do not send.
|
|
1304
1341
|
// clientId,
|
|
1305
1342
|
paymentProvider: this.PROVIDER,
|
|
1306
|
-
instructionPriority: payment.instructionPriority
|
|
1343
|
+
instructionPriority: toFinbricksPriority(payment.instructionPriority)
|
|
1307
1344
|
}
|
|
1308
1345
|
})
|
|
1309
1346
|
);
|
|
@@ -1825,6 +1862,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1825
1862
|
};
|
|
1826
1863
|
}
|
|
1827
1864
|
async initiateDomesticPayment(payment) {
|
|
1865
|
+
assertDbuPriority(payment.instructionPriority);
|
|
1828
1866
|
if (payment.instructionPriority === "INST") {
|
|
1829
1867
|
return this.initiateInstantPayment(payment);
|
|
1830
1868
|
}
|
|
@@ -2172,7 +2210,7 @@ class ErsteConnector extends IBankConnector {
|
|
|
2172
2210
|
instructionIdentification: payment.id
|
|
2173
2211
|
},
|
|
2174
2212
|
paymentTypeInformation: {
|
|
2175
|
-
instructionPriority: payment.instructionPriority
|
|
2213
|
+
instructionPriority: toErstePriority(payment.instructionPriority)
|
|
2176
2214
|
},
|
|
2177
2215
|
amount: {
|
|
2178
2216
|
instructedAmount: { value: payment.amount, currency: payment.currency }
|
|
@@ -805,6 +805,31 @@ const calculateCzechIban = (accountNumber, bankCode) => {
|
|
|
805
805
|
return `CZ${checkDigits}${basicIban}`;
|
|
806
806
|
};
|
|
807
807
|
|
|
808
|
+
const unsupported = (priority, rail, supported) => {
|
|
809
|
+
throw backendSdk.createInternalError(null, {
|
|
810
|
+
message: `instructionPriority=${priority} is not supported on the ${rail} rail (supported: ${supported}).`,
|
|
811
|
+
code: "UNSUPPORTED_INSTRUCTION_PRIORITY",
|
|
812
|
+
status: 422
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
const toFinbricksPriority = (priority) => {
|
|
816
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
817
|
+
if (priority === "INST") return "INST";
|
|
818
|
+
return unsupported(priority, "Finbricks SEPA/domestic", "NORM, INST");
|
|
819
|
+
};
|
|
820
|
+
const toFinbricksForeignPriority = (priority) => {
|
|
821
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
822
|
+
return unsupported(priority, "Finbricks foreign (SWIFT)", "NORM");
|
|
823
|
+
};
|
|
824
|
+
const toErstePriority = (priority) => {
|
|
825
|
+
if (!priority || priority === "NORM") return "NORM";
|
|
826
|
+
if (priority === "HIGH") return "HIGH";
|
|
827
|
+
return unsupported(priority, "Erste ISO 20022", "NORM, HIGH");
|
|
828
|
+
};
|
|
829
|
+
const assertDbuPriority = (priority) => {
|
|
830
|
+
if (priority === "HIGH") unsupported(priority, "DBU domestic", "NORM, INST");
|
|
831
|
+
};
|
|
832
|
+
|
|
808
833
|
function autoVariableSymbol(paymentId) {
|
|
809
834
|
let hash = 0;
|
|
810
835
|
for (let i = 0; i < paymentId.length; i++) {
|
|
@@ -1092,7 +1117,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1092
1117
|
description: this.PROVIDER === "CSOB" ? composeWithUltimateCreditor(p.message, p.creditor, {
|
|
1093
1118
|
stripDiacritics: p.currency !== "CZK"
|
|
1094
1119
|
}) : p.message,
|
|
1095
|
-
instructionPriority: p.instructionPriority
|
|
1120
|
+
instructionPriority: toFinbricksPriority(p.instructionPriority)
|
|
1096
1121
|
}))
|
|
1097
1122
|
}
|
|
1098
1123
|
})
|
|
@@ -1116,6 +1141,15 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1116
1141
|
initiateForeignBatchImpl(_args) {
|
|
1117
1142
|
throw new Error("Finbricks: Foreign batch not implemented");
|
|
1118
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* endToEndIdentification for the SEPA rail. Optional per the Finbricks spec —
|
|
1146
|
+
* return `undefined` to omit the field entirely, which some executing banks
|
|
1147
|
+
* need because they overwrite the remittance text with it (see
|
|
1148
|
+
* `CreditasConnector`).
|
|
1149
|
+
*/
|
|
1150
|
+
buildSepaEndToEndId(payment) {
|
|
1151
|
+
return this.buildEndToEndId(payment);
|
|
1152
|
+
}
|
|
1119
1153
|
async initiateForeignPayment(payment) {
|
|
1120
1154
|
const debtorAccount = this.connectedAccounts.find(
|
|
1121
1155
|
(acc) => acc.iban === payment.debtorIban
|
|
@@ -1173,6 +1207,9 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1173
1207
|
},
|
|
1174
1208
|
...creditorAgent && { creditorAgent },
|
|
1175
1209
|
...unstructured && { remittanceInformation: { unstructured } },
|
|
1210
|
+
instructionPriority: toFinbricksForeignPriority(
|
|
1211
|
+
payment.instructionPriority
|
|
1212
|
+
),
|
|
1176
1213
|
callbackUrl: `${this.finbricks.REDIRECT_URI}?type=paymentRequest&paymentRequestId=${payment.id}`
|
|
1177
1214
|
}
|
|
1178
1215
|
})
|
|
@@ -1220,6 +1257,10 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1220
1257
|
0,
|
|
1221
1258
|
140
|
|
1222
1259
|
);
|
|
1260
|
+
const endToEndIdentification = this.buildSepaEndToEndId({
|
|
1261
|
+
...payment,
|
|
1262
|
+
id: payment.id
|
|
1263
|
+
});
|
|
1223
1264
|
const bankRefId = backendSdk.uuidv4();
|
|
1224
1265
|
const [response, error] = await backendSdk.useResult(
|
|
1225
1266
|
this.finbricks.request({
|
|
@@ -1233,10 +1274,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1233
1274
|
},
|
|
1234
1275
|
paymentIdentification: {
|
|
1235
1276
|
merchantTransactionId: bankRefId,
|
|
1236
|
-
endToEndIdentification
|
|
1237
|
-
...payment,
|
|
1238
|
-
id: payment.id
|
|
1239
|
-
})
|
|
1277
|
+
...endToEndIdentification && { endToEndIdentification }
|
|
1240
1278
|
},
|
|
1241
1279
|
amount: payment.amount,
|
|
1242
1280
|
debtor: {
|
|
@@ -1251,8 +1289,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1251
1289
|
...creditorAddress && { postalAddress: creditorAddress }
|
|
1252
1290
|
},
|
|
1253
1291
|
remittanceInformation: { unstructured },
|
|
1254
|
-
|
|
1255
|
-
instructionPriority: payment.instructionPriority === "INST" ? "INST" : "NORM",
|
|
1292
|
+
instructionPriority: toFinbricksPriority(payment.instructionPriority),
|
|
1256
1293
|
callbackUrl: `${this.finbricks.REDIRECT_URI}?type=paymentRequest&paymentRequestId=${payment.id}`
|
|
1257
1294
|
}
|
|
1258
1295
|
})
|
|
@@ -1305,7 +1342,7 @@ class FinbricksConnector extends IBankConnector {
|
|
|
1305
1342
|
// TEMP: clientId disabled per client request — do not send.
|
|
1306
1343
|
// clientId,
|
|
1307
1344
|
paymentProvider: this.PROVIDER,
|
|
1308
|
-
instructionPriority: payment.instructionPriority
|
|
1345
|
+
instructionPriority: toFinbricksPriority(payment.instructionPriority)
|
|
1309
1346
|
}
|
|
1310
1347
|
})
|
|
1311
1348
|
);
|
|
@@ -1827,6 +1864,7 @@ class DbuConnector extends IBankConnector {
|
|
|
1827
1864
|
};
|
|
1828
1865
|
}
|
|
1829
1866
|
async initiateDomesticPayment(payment) {
|
|
1867
|
+
assertDbuPriority(payment.instructionPriority);
|
|
1830
1868
|
if (payment.instructionPriority === "INST") {
|
|
1831
1869
|
return this.initiateInstantPayment(payment);
|
|
1832
1870
|
}
|
|
@@ -2174,7 +2212,7 @@ class ErsteConnector extends IBankConnector {
|
|
|
2174
2212
|
instructionIdentification: payment.id
|
|
2175
2213
|
},
|
|
2176
2214
|
paymentTypeInformation: {
|
|
2177
|
-
instructionPriority: payment.instructionPriority
|
|
2215
|
+
instructionPriority: toErstePriority(payment.instructionPriority)
|
|
2178
2216
|
},
|
|
2179
2217
|
amount: {
|
|
2180
2218
|
instructedAmount: { value: payment.amount, currency: payment.currency }
|
|
@@ -27,6 +27,8 @@ interface LastSyncMetadata {
|
|
|
27
27
|
eventsEmitted: number;
|
|
28
28
|
iterationCount?: number;
|
|
29
29
|
workflowStartedAt?: number;
|
|
30
|
+
/** Which instance wrote this sync — lets an older sibling detect it was superseded. */
|
|
31
|
+
instanceId?: string;
|
|
30
32
|
}
|
|
31
33
|
type ConnectorConfig = Record<string, string | number | boolean | null>;
|
|
32
34
|
|
|
@@ -3606,7 +3608,7 @@ declare const payment: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
3606
3608
|
name: string;
|
|
3607
3609
|
tableName: "payment";
|
|
3608
3610
|
dataType: "string enum";
|
|
3609
|
-
data: "BOOKED" | "REJECTED" | "
|
|
3611
|
+
data: "BOOKED" | "REJECTED" | "PROCESSING" | "PENDING" | "CANCELLED" | "SCHEDULED" | "HOLD" | "INFO";
|
|
3610
3612
|
driverParam: string;
|
|
3611
3613
|
notNull: true;
|
|
3612
3614
|
hasDefault: false;
|
|
@@ -4110,7 +4112,7 @@ declare const paymentRequest: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
4110
4112
|
name: string;
|
|
4111
4113
|
tableName: "payment_request";
|
|
4112
4114
|
dataType: "string enum";
|
|
4113
|
-
data: "
|
|
4115
|
+
data: "OPENED" | "AUTHORIZED" | "COMPLETED" | "BOOKED" | "SETTLED" | "REJECTED" | "CLOSED";
|
|
4114
4116
|
driverParam: string;
|
|
4115
4117
|
notNull: true;
|
|
4116
4118
|
hasDefault: false;
|
|
@@ -5391,5 +5393,5 @@ type ParsedBankPayment = {
|
|
|
5391
5393
|
*/
|
|
5392
5394
|
type BatchPayment = Omit<PaymentInsertType, 'bankRefId'>;
|
|
5393
5395
|
|
|
5394
|
-
export { IBankConnector as I, BASE_TERMINAL_STATUSES as
|
|
5395
|
-
export type {
|
|
5396
|
+
export { CREDENTIALS_TYPES as $, IBankConnector as I, BASE_TERMINAL_STATUSES as N, BATCH_MODES as O, BATCH_STATUSES as Q, CHARGE_BEARERS as Y, CONNECTOR_KEYS as Z, COUNTRY_CODES as _, INSTRUCTION_PRIORITIES as a4, PAYMENT_DIRECTIONS as a6, PAYMENT_REQUEST_STATUSES as a7, PAYMENT_STATUSES as a8, PAYMENT_TYPES as a9, isProcessedStatus as aA, isTerminalStatus as aB, account as aC, accountCredentials as aD, batch as aE, ott as aF, payment as aG, paymentRequest as aH, TOKEN_TYPES as aj, accountCredentialsInsertSchema as al, accountCredentialsSelectSchema as am, accountCredentialsUpdateSchema as an, accountInsertSchema as ao, accountSelectSchema as ap, accountUpdateSchema as aq, hasPaymentAccountAssigned as ar, isBatchAuthorized as as, isBatchCompleted as at, isBatchFailed as au, isBatchInitiated as av, isBatchProcessing as aw, isBatchReadyToSign as ax, isPaymentCompleted as ay, isPendingStatus as az, tables as t, ACCOUNT_STATUSES as y };
|
|
5397
|
+
export type { AccountSelectType as A, BatchSelectType as B, ConnectorConfig as C, AccountCredentialsSelectType as D, EndToEndIdPayment as E, AccountCredentialsUpdateType as F, AccountPatchType as G, HandleAuthorizationCallbackInput as H, AccountStatus as J, AccountUpdateType as K, LastSyncMetadata as L, AuthorizedBatch as M, PaymentRequestSelectType as P, BankAccountWithLastSync as R, BankCode as S, BatchInsertType as T, BatchLifecycle as U, BatchMode as V, BatchPayment as W, BatchStatus as X, PaymentSelectType as a, ChargeBearer as a0, CompletedBatch as a1, CountryCode as a2, CredentialsType as a3, InstructionPriority as a5, PaymentDirection as aa, PaymentFailedInsertType as ab, PaymentInsertType as ac, PaymentLifecycle as ad, PaymentPreparedInsertType as ae, PaymentStatus as af, ProcessingBatch as ag, ReadyToSignBatch as ah, ResolvedCredentials as ai, TokenType as ak, ConnectorKey as b, ConfigEnvironmentBank as c, PaymentType as d, CurrencyCode as e, HandleAuthorizationCallbackOutput as f, ConnectedAccount as g, CredentialsResolver as h, AccountCredentialsInsertType as i, AccountInsertType as j, BatchedPayment as k, InitiatedBatch as l, IncomingPayment as m, InitiatedPayment as n, ParsedBankPayment as o, PaymentRequestStatus as p, AuthorizationCallbackResult as q, BatchMetadata as r, Currency as s, AccountAssignedPayment as u, PreparedPayment as v, CompletedPayment as w, PaymentRequestInsertType as x, AccountCredentialsPatchType as z };
|