@develit-services/bank 0.3.43 → 0.3.45
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.cjs +4 -4
- 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/database/schema.mjs +4 -4
- package/dist/export/worker.cjs +166 -183
- package/dist/export/worker.d.cts +43 -22
- package/dist/export/worker.d.mts +43 -22
- package/dist/export/worker.d.ts +43 -22
- package/dist/export/worker.mjs +126 -143
- package/dist/export/workflows.cjs +214 -18
- package/dist/export/workflows.mjs +207 -12
- package/dist/export/wrangler.d.cts +1 -2
- package/dist/export/wrangler.d.mts +1 -2
- package/dist/export/wrangler.d.ts +1 -2
- package/dist/shared/{bank.YiArmBW2.mjs → bank.B1Gpn3ht.mjs} +12 -174
- package/dist/shared/{bank.xrXNjWCo.d.cts → bank.BEL1HIxZ.d.cts} +21 -1
- package/dist/shared/{bank.xrXNjWCo.d.mts → bank.BEL1HIxZ.d.mts} +21 -1
- package/dist/shared/{bank.xrXNjWCo.d.ts → bank.BEL1HIxZ.d.ts} +21 -1
- package/dist/shared/bank.BUEmFxS8.mjs +174 -0
- package/dist/shared/{bank.CLF9wee9.cjs → bank.CPYfE-Ei.cjs} +12 -199
- package/dist/shared/bank.CpwLFudl.cjs +198 -0
- package/dist/shared/bank.D-3fzX63.mjs +170 -0
- package/dist/shared/bank.D0a-MZon.cjs +184 -0
- package/dist/shared/{bank.BchnXQDL.d.cts → bank.Dh_H_5rC.d.cts} +0 -1
- package/dist/shared/{bank.BchnXQDL.d.mts → bank.Dh_H_5rC.d.mts} +0 -1
- package/dist/shared/{bank.BchnXQDL.d.ts → bank.Dh_H_5rC.d.ts} +0 -1
- package/dist/types.cjs +14 -14
- package/dist/types.d.cts +9 -9
- package/dist/types.d.mts +9 -9
- package/dist/types.d.ts +9 -9
- package/dist/types.mjs +5 -5
- package/package.json +1 -1
- package/dist/shared/bank.CNtiZSem.mjs +0 -9
- package/dist/shared/bank.CUU0Daxj.mjs +0 -391
- package/dist/shared/bank.ChffLgyT.cjs +0 -401
- package/dist/shared/bank.DfE0M5H3.cjs +0 -11
package/dist/export/worker.mjs
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { uuidv4, bankAccountMetadataSchema, workflowInstanceStatusSchema, develitWorker, createInternalError, first, action, service } from '@develit-io/backend-sdk';
|
|
2
2
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
3
3
|
import { drizzle } from 'drizzle-orm/d1';
|
|
4
|
-
import { t as tables
|
|
5
|
-
import 'jose';
|
|
6
|
-
import { CURRENCY_CODES } from '@develit-io/general-codes';
|
|
4
|
+
import { t as tables } from '../shared/bank.B1Gpn3ht.mjs';
|
|
7
5
|
import { z } from 'zod';
|
|
8
|
-
import {
|
|
6
|
+
import { I as INSTRUCTION_PRIORITIES, C as CHARGE_BEARERS, P as PAYMENT_TYPES, d as CONNECTOR_KEYS, B as BATCH_STATUSES, a as PAYMENT_STATUSES, b as PAYMENT_DIRECTIONS, f as accountInsertSchema } from '../shared/bank.D-3fzX63.mjs';
|
|
7
|
+
import { CURRENCY_CODES } from '@develit-io/general-codes';
|
|
8
|
+
import 'date-fns';
|
|
9
9
|
import { eq, inArray, and, sql, asc, desc, gte, lte } from 'drizzle-orm';
|
|
10
|
+
import 'jose';
|
|
11
|
+
import 'node:crypto';
|
|
12
|
+
import { f as encrypt, i as importAesKey, a as getCredentialsByAccountId, b as initiateConnector, u as upsertBatchCommand, d as getBatchByIdQuery, c as createPaymentCommand, g as getAccountByIdQuery } from '../shared/bank.BUEmFxS8.mjs';
|
|
10
13
|
import 'drizzle-orm/sqlite-core';
|
|
11
|
-
import 'date-fns';
|
|
12
|
-
import 'drizzle-zod';
|
|
13
14
|
import 'drizzle-orm/relations';
|
|
14
|
-
import '
|
|
15
|
-
import 'cloudflare:workflows';
|
|
16
|
-
import '../shared/bank.CNtiZSem.mjs';
|
|
15
|
+
import 'drizzle-zod';
|
|
17
16
|
|
|
18
17
|
const upsertAccountCommand = (db, { account }) => {
|
|
19
18
|
const id = account.id || uuidv4();
|
|
@@ -313,22 +312,6 @@ z.object({
|
|
|
313
312
|
details: workflowInstanceStatusSchema
|
|
314
313
|
});
|
|
315
314
|
|
|
316
|
-
const batchAuthorizedEventPayloadSchema = z.object({
|
|
317
|
-
authorized: z.boolean()
|
|
318
|
-
});
|
|
319
|
-
const processBatchEventInputSchema = z.discriminatedUnion("eventType", [
|
|
320
|
-
z.object({
|
|
321
|
-
batchId: z.uuid(),
|
|
322
|
-
eventType: z.literal(PROCESS_BATCH_WORKFLOW_EVENTS.batchAuthorized),
|
|
323
|
-
payload: batchAuthorizedEventPayloadSchema
|
|
324
|
-
})
|
|
325
|
-
// Future event types can be added here as new objects in the array
|
|
326
|
-
]);
|
|
327
|
-
z.object({
|
|
328
|
-
success: z.boolean(),
|
|
329
|
-
message: z.string()
|
|
330
|
-
});
|
|
331
|
-
|
|
332
315
|
const ALLOWED_PAYMENT_FILTERS = {
|
|
333
316
|
ACCOUNT_ID: "filterPaymentAccountId",
|
|
334
317
|
AMOUNT: "filterPaymentAmount",
|
|
@@ -414,9 +397,13 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
414
397
|
async _getAccounts() {
|
|
415
398
|
return await getAllAccountsQuery(this.db);
|
|
416
399
|
}
|
|
400
|
+
async _getConnectedAccounts() {
|
|
401
|
+
const accounts = await this._getAccounts();
|
|
402
|
+
return accounts.filter((acc) => acc.status !== "DISABLED");
|
|
403
|
+
}
|
|
417
404
|
async _initiateBankConnector({
|
|
418
405
|
connectorKey,
|
|
419
|
-
|
|
406
|
+
skipAccounts
|
|
420
407
|
}) {
|
|
421
408
|
if (!this.allowedProviders.includes(connectorKey)) {
|
|
422
409
|
throw createInternalError(null, {
|
|
@@ -425,55 +412,45 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
425
412
|
status: 400
|
|
426
413
|
});
|
|
427
414
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
415
|
+
let connectedAccounts = [];
|
|
416
|
+
if (!skipAccounts) {
|
|
417
|
+
const encryptionKey = await importAesKey(this.env.ENCRYPTION_KEY);
|
|
418
|
+
const accounts = await this._getConnectedAccounts();
|
|
419
|
+
const accountsForConnector = accounts.filter(
|
|
420
|
+
(acc) => acc.connectorKey === connectorKey
|
|
421
|
+
);
|
|
422
|
+
connectedAccounts = await Promise.all(
|
|
423
|
+
accountsForConnector.map(async (acc) => {
|
|
424
|
+
const credentials = await getCredentialsByAccountId(
|
|
425
|
+
this.db,
|
|
426
|
+
encryptionKey,
|
|
427
|
+
{
|
|
428
|
+
accountId: acc.id
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
if (!credentials) {
|
|
432
|
+
throw createInternalError(null, {
|
|
433
|
+
message: `No credentials found for account ${acc.id}`,
|
|
434
|
+
code: "DB-B-001",
|
|
435
|
+
status: 404
|
|
436
|
+
});
|
|
440
437
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
token: credentials.value,
|
|
453
|
-
id: acc.id,
|
|
454
|
-
connectorKey: acc.connectorKey
|
|
455
|
-
};
|
|
456
|
-
})
|
|
457
|
-
);
|
|
458
|
-
this.bankConnector = initiateConnector({
|
|
438
|
+
return {
|
|
439
|
+
currency: acc.currency,
|
|
440
|
+
iban: acc.iban,
|
|
441
|
+
token: credentials.value,
|
|
442
|
+
id: acc.id,
|
|
443
|
+
connectorKey: acc.connectorKey
|
|
444
|
+
};
|
|
445
|
+
})
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const connector = initiateConnector({
|
|
459
449
|
bank: connectorKey,
|
|
460
|
-
connectedAccounts
|
|
450
|
+
connectedAccounts,
|
|
461
451
|
env: this.env
|
|
462
452
|
});
|
|
463
|
-
|
|
464
|
-
await this.bankConnector.authenticate();
|
|
465
|
-
}
|
|
466
|
-
_accountFetchInterval(connectorKey) {
|
|
467
|
-
switch (connectorKey) {
|
|
468
|
-
case "ERSTE":
|
|
469
|
-
return ErsteConnector.FETCH_INTERVAL;
|
|
470
|
-
case "FINBRICKS":
|
|
471
|
-
return FinbricksConnector.FETCH_INTERVAL;
|
|
472
|
-
case "MOCK":
|
|
473
|
-
return MockConnector.FETCH_INTERVAL;
|
|
474
|
-
default:
|
|
475
|
-
return 0;
|
|
476
|
-
}
|
|
453
|
+
return connector;
|
|
477
454
|
}
|
|
478
455
|
async getPayments(input) {
|
|
479
456
|
return this.handleAction(
|
|
@@ -561,65 +538,85 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
561
538
|
await this.updateBatchStatuses();
|
|
562
539
|
}
|
|
563
540
|
}
|
|
564
|
-
async
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
541
|
+
async _resolveSingleBatch(batch, connector) {
|
|
542
|
+
const previousStatus = batch.status;
|
|
543
|
+
const currentStatus = await connector.getBatchStatus({
|
|
544
|
+
batchId: batch.id
|
|
545
|
+
});
|
|
546
|
+
let statusChanged = false;
|
|
547
|
+
if (previousStatus !== currentStatus) {
|
|
548
|
+
await upsertBatchCommand(this.db, {
|
|
549
|
+
batch: {
|
|
550
|
+
...batch,
|
|
551
|
+
status: currentStatus
|
|
552
|
+
}
|
|
553
|
+
}).command.execute();
|
|
554
|
+
statusChanged = true;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
batchId: batch.id,
|
|
558
|
+
previousStatus,
|
|
559
|
+
currentStatus,
|
|
560
|
+
statusChanged
|
|
561
|
+
// eventSent,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
async updateBatchStatuses(input) {
|
|
565
|
+
let pendingBatches;
|
|
566
|
+
if (input?.batchId) {
|
|
567
|
+
const batch = await getBatchByIdQuery(this.db, { batchId: input.batchId });
|
|
568
|
+
if (!batch) {
|
|
569
|
+
throw createInternalError(null, {
|
|
570
|
+
message: `Batch not found: ${input.batchId}`,
|
|
571
|
+
code: "DB-B-007",
|
|
572
|
+
status: 404
|
|
573
|
+
});
|
|
574
574
|
}
|
|
575
|
+
pendingBatches = [batch];
|
|
576
|
+
} else {
|
|
577
|
+
pendingBatches = await getAllPendingBatchesQuery(this.db);
|
|
575
578
|
}
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
579
|
+
const accounts = await this._getConnectedAccounts();
|
|
580
|
+
console.log(`Processing ${pendingBatches.length} pending batche(s)`);
|
|
581
|
+
const batchesByConnector = /* @__PURE__ */ new Map();
|
|
582
|
+
for (const batch of pendingBatches) {
|
|
583
|
+
const account = accounts.find((acc) => acc.id === batch.accountId);
|
|
584
|
+
if (!account) {
|
|
585
|
+
this.logError({
|
|
586
|
+
message: `Account not found for batch ${batch.id}, skipping`
|
|
587
|
+
});
|
|
588
|
+
await upsertBatchCommand(this.db, {
|
|
589
|
+
batch: {
|
|
586
590
|
...batch,
|
|
587
|
-
status:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
591
|
+
status: "FAILED",
|
|
592
|
+
statusReason: "ACCOUNT_NOT_FOUND"
|
|
593
|
+
}
|
|
594
|
+
}).command.execute();
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const batches = batchesByConnector.get(account.connectorKey) || [];
|
|
598
|
+
batches.push(batch);
|
|
599
|
+
batchesByConnector.set(account.connectorKey, batches);
|
|
592
600
|
}
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
601
|
+
const allResults = [];
|
|
602
|
+
for (const [connectorKey, batches] of batchesByConnector) {
|
|
603
|
+
console.log(
|
|
604
|
+
`Initializing ${connectorKey} connector for ${batches.length} batches`
|
|
605
|
+
);
|
|
606
|
+
const connector = await this._initiateBankConnector({
|
|
607
|
+
connectorKey
|
|
598
608
|
});
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
...updateBatchesCommands.slice(1)
|
|
604
|
-
]);
|
|
605
|
-
for (const batch of finalBatches) {
|
|
606
|
-
try {
|
|
607
|
-
const instance = await this.env.PROCESS_BATCH_WORKFLOW.get(batch.id);
|
|
608
|
-
if (batch.status === "SIGNED" || batch.status === "SIGNATURE_FAILED") {
|
|
609
|
-
instance.sendEvent({
|
|
610
|
-
type: PROCESS_BATCH_WORKFLOW_EVENTS.batchAuthorized,
|
|
611
|
-
payload: {
|
|
612
|
-
authorized: batch.status === "SIGNED"
|
|
613
|
-
}
|
|
614
|
-
});
|
|
609
|
+
for (const batch of batches) {
|
|
610
|
+
const result = await this._resolveSingleBatch(batch, connector);
|
|
611
|
+
if (result) {
|
|
612
|
+
allResults.push(result);
|
|
615
613
|
}
|
|
616
|
-
} catch (_) {
|
|
617
|
-
console.log(
|
|
618
|
-
`No workflow instance found for batch ${batch.id}, skipping.`
|
|
619
|
-
);
|
|
620
614
|
}
|
|
621
615
|
}
|
|
622
|
-
|
|
616
|
+
const changedCount = allResults.filter((r) => r.statusChanged).length;
|
|
617
|
+
console.log(
|
|
618
|
+
`Batch update completed: ${changedCount} status change(s) detected.`
|
|
619
|
+
);
|
|
623
620
|
}
|
|
624
621
|
async addPaymentsToBatch({
|
|
625
622
|
paymentsToBatch
|
|
@@ -762,23 +759,6 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
762
759
|
}
|
|
763
760
|
);
|
|
764
761
|
}
|
|
765
|
-
async processBatchEvent(input) {
|
|
766
|
-
return this.handleAction(
|
|
767
|
-
{ data: input, schema: processBatchEventInputSchema },
|
|
768
|
-
{ successMessage: "Event sent to batch processing workflow" },
|
|
769
|
-
async ({ batchId, eventType, payload }) => {
|
|
770
|
-
const instance = await this.env.PROCESS_BATCH_WORKFLOW.get(batchId);
|
|
771
|
-
await instance.sendEvent({
|
|
772
|
-
type: eventType,
|
|
773
|
-
payload
|
|
774
|
-
});
|
|
775
|
-
return {
|
|
776
|
-
success: true,
|
|
777
|
-
message: `Event '${eventType}' sent to workflow instance ${batchId}`
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
762
|
async queue(b) {
|
|
783
763
|
await this.handleAction(
|
|
784
764
|
null,
|
|
@@ -797,7 +777,10 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
797
777
|
{ data: input, schema: getAuthUriInputSchema },
|
|
798
778
|
{ successMessage: "Auth URI obtained." },
|
|
799
779
|
async ({ connectorKey }) => {
|
|
800
|
-
await this._initiateBankConnector({
|
|
780
|
+
const connector = await this._initiateBankConnector({
|
|
781
|
+
connectorKey,
|
|
782
|
+
skipAccounts: true
|
|
783
|
+
});
|
|
801
784
|
const ott = uuidv4();
|
|
802
785
|
const { command: createOneTimeToken } = createOneTimeTokenCommand(
|
|
803
786
|
this.db,
|
|
@@ -808,7 +791,7 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
808
791
|
}
|
|
809
792
|
);
|
|
810
793
|
await createOneTimeToken.execute();
|
|
811
|
-
const uri = await
|
|
794
|
+
const uri = await connector.getAuthUri({ ott });
|
|
812
795
|
return { uri };
|
|
813
796
|
}
|
|
814
797
|
);
|
|
@@ -835,10 +818,10 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
835
818
|
status: 400
|
|
836
819
|
});
|
|
837
820
|
}
|
|
838
|
-
await this._initiateBankConnector({
|
|
821
|
+
const connector = await this._initiateBankConnector({
|
|
839
822
|
connectorKey: ottRow.refId
|
|
840
823
|
});
|
|
841
|
-
const { credentials, accounts } = await
|
|
824
|
+
const { credentials, accounts } = await connector.authorizeAccount({
|
|
842
825
|
urlParams
|
|
843
826
|
});
|
|
844
827
|
console.log("Bank connector initiated");
|
|
@@ -3,34 +3,34 @@
|
|
|
3
3
|
const cloudflare_workers = require('cloudflare:workers');
|
|
4
4
|
const cloudflare_workflows = require('cloudflare:workflows');
|
|
5
5
|
const d1 = require('drizzle-orm/d1');
|
|
6
|
-
const
|
|
6
|
+
const drizzle = require('../shared/bank.CPYfE-Ei.cjs');
|
|
7
7
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
8
|
-
const
|
|
8
|
+
const encryption = require('../shared/bank.D0a-MZon.cjs');
|
|
9
9
|
const drizzleOrm = require('drizzle-orm');
|
|
10
|
-
require('drizzle-orm/sqlite-core');
|
|
11
10
|
require('date-fns');
|
|
12
|
-
require('
|
|
13
|
-
require('
|
|
14
|
-
require('drizzle-zod');
|
|
11
|
+
require('../shared/bank.CpwLFudl.cjs');
|
|
12
|
+
require('drizzle-orm/sqlite-core');
|
|
15
13
|
require('drizzle-orm/relations');
|
|
14
|
+
require('@develit-io/general-codes');
|
|
15
|
+
require('jose');
|
|
16
16
|
require('node:crypto');
|
|
17
|
-
require('
|
|
17
|
+
require('drizzle-zod');
|
|
18
18
|
|
|
19
19
|
const updateAccountLastSyncCommand = (db, {
|
|
20
20
|
lastSyncAt,
|
|
21
21
|
accountId,
|
|
22
22
|
lastSyncMetadata
|
|
23
23
|
}) => {
|
|
24
|
-
const command = db.update(
|
|
24
|
+
const command = db.update(drizzle.tables.account).set({
|
|
25
25
|
lastSyncAt,
|
|
26
26
|
lastSyncMetadata
|
|
27
|
-
}).where(drizzleOrm.eq(
|
|
27
|
+
}).where(drizzleOrm.eq(drizzle.tables.account.id, accountId)).returning();
|
|
28
28
|
return {
|
|
29
29
|
command
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
function pushToQueue(queue, message) {
|
|
33
|
+
function pushToQueue$1(queue, message) {
|
|
34
34
|
if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
|
|
35
35
|
return queue.sendBatch(
|
|
36
36
|
message.map((m) => ({
|
|
@@ -42,11 +42,11 @@ function pushToQueue(queue, message) {
|
|
|
42
42
|
class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
43
43
|
async run(event, step) {
|
|
44
44
|
const { accountId } = event.payload;
|
|
45
|
-
const db = d1.drizzle(this.env.BANK_D1, { schema:
|
|
45
|
+
const db = d1.drizzle(this.env.BANK_D1, { schema: drizzle.tables });
|
|
46
46
|
while (true) {
|
|
47
47
|
const now = /* @__PURE__ */ new Date();
|
|
48
48
|
const account = await step.do("load account", async () => {
|
|
49
|
-
const account2 = await
|
|
49
|
+
const account2 = await encryption.getAccountByIdQuery(db, { accountId });
|
|
50
50
|
if (!account2) {
|
|
51
51
|
throw new cloudflare_workflows.NonRetryableError(`Bank account not found: ${accountId}`);
|
|
52
52
|
}
|
|
@@ -63,8 +63,8 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
63
63
|
timeout: "30 seconds"
|
|
64
64
|
},
|
|
65
65
|
async () => {
|
|
66
|
-
const encryptionKey = await
|
|
67
|
-
const credentials = await
|
|
66
|
+
const encryptionKey = await encryption.importAesKey(this.env.ENCRYPTION_KEY);
|
|
67
|
+
const credentials = await encryption.getCredentialsByAccountId(
|
|
68
68
|
db,
|
|
69
69
|
encryptionKey,
|
|
70
70
|
{ accountId }
|
|
@@ -79,7 +79,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
79
79
|
`Credentials have expired for account: ${accountId}`
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
|
-
const connector =
|
|
82
|
+
const connector = encryption.initiateConnector({
|
|
83
83
|
env: this.env,
|
|
84
84
|
bank: account.connectorKey,
|
|
85
85
|
connectedAccounts: [
|
|
@@ -111,7 +111,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
111
111
|
async () => {
|
|
112
112
|
const eventsToEmit = [];
|
|
113
113
|
const createCommands = paymentsToInsert.map(
|
|
114
|
-
(p) =>
|
|
114
|
+
(p) => encryption.createPaymentCommand(db, { payment: p.parsed }).command
|
|
115
115
|
);
|
|
116
116
|
eventsToEmit.push(
|
|
117
117
|
...paymentsToInsert.map((p) => ({
|
|
@@ -144,7 +144,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
144
144
|
await db.batch(
|
|
145
145
|
backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
146
146
|
);
|
|
147
|
-
await pushToQueue(
|
|
147
|
+
await pushToQueue$1(
|
|
148
148
|
this.env.QUEUE_BUS_QUEUE,
|
|
149
149
|
eventsToEmit
|
|
150
150
|
);
|
|
@@ -164,5 +164,201 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
function pushToQueue(queue, message) {
|
|
168
|
+
if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
|
|
169
|
+
return queue.sendBatch(
|
|
170
|
+
message.map((m) => ({
|
|
171
|
+
body: m,
|
|
172
|
+
contentType: "v8"
|
|
173
|
+
}))
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
class BankProcessBatch extends cloudflare_workers.WorkflowEntrypoint {
|
|
177
|
+
async run(event, step) {
|
|
178
|
+
const { batchId } = event.payload;
|
|
179
|
+
const db = d1.drizzle(this.env.BANK_D1, { schema: drizzle.tables });
|
|
180
|
+
const batch = await step.do("load batch", async () => {
|
|
181
|
+
const batch2 = await encryption.getBatchByIdQuery(db, { batchId });
|
|
182
|
+
if (!batch2) {
|
|
183
|
+
throw new cloudflare_workflows.NonRetryableError(`Batch not found`);
|
|
184
|
+
}
|
|
185
|
+
if (batch2.status === "SIGNED")
|
|
186
|
+
throw new cloudflare_workflows.NonRetryableError(`Batch already signed`);
|
|
187
|
+
return batch2;
|
|
188
|
+
});
|
|
189
|
+
await step.do("lock batch", async () => {
|
|
190
|
+
const paymentsChecksum = encryption.checksum(batch.payments);
|
|
191
|
+
if (batch.paymentsChecksum && batch.paymentsChecksum !== paymentsChecksum)
|
|
192
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
193
|
+
`Batch payments have been modified externally, manual review needed`
|
|
194
|
+
);
|
|
195
|
+
if (batch.status === "FAILED")
|
|
196
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
197
|
+
`Batch is in FAILED status and cannot be processed, manual review needed`
|
|
198
|
+
);
|
|
199
|
+
if (batch.status === "OPEN" || batch.status === "FULL") {
|
|
200
|
+
const updatedBatch = await encryption.upsertBatchCommand(db, {
|
|
201
|
+
batch: {
|
|
202
|
+
...batch,
|
|
203
|
+
status: "PROCESSING",
|
|
204
|
+
paymentsChecksum
|
|
205
|
+
}
|
|
206
|
+
}).command.execute().then(backendSdk.first);
|
|
207
|
+
return { status: updatedBatch.status };
|
|
208
|
+
}
|
|
209
|
+
return { status: batch.status };
|
|
210
|
+
});
|
|
211
|
+
const preparedPayments = await step.do(
|
|
212
|
+
"prepare payments",
|
|
213
|
+
{
|
|
214
|
+
retries: { limit: 5, delay: 2e3, backoff: "constant" },
|
|
215
|
+
timeout: "30 seconds"
|
|
216
|
+
},
|
|
217
|
+
async () => {
|
|
218
|
+
const account = await encryption.getAccountByIdQuery(db, {
|
|
219
|
+
accountId: batch.accountId
|
|
220
|
+
});
|
|
221
|
+
if (!account) {
|
|
222
|
+
throw new cloudflare_workflows.NonRetryableError(`Account not found: ${batch.accountId}`);
|
|
223
|
+
}
|
|
224
|
+
const encryptionKey = await encryption.importAesKey(this.env.ENCRYPTION_KEY);
|
|
225
|
+
const credentials = await encryption.getCredentialsByAccountId(db, encryptionKey, {
|
|
226
|
+
accountId: account.id
|
|
227
|
+
});
|
|
228
|
+
if (!credentials) {
|
|
229
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
230
|
+
`No credentials found for account: ${account.id}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (credentials.expiresAt < /* @__PURE__ */ new Date()) {
|
|
234
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
235
|
+
`Credentials have expired for account: ${account.id}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const connector = encryption.initiateConnector({
|
|
239
|
+
env: this.env,
|
|
240
|
+
bank: account.connectorKey,
|
|
241
|
+
connectedAccounts: [
|
|
242
|
+
{
|
|
243
|
+
...account,
|
|
244
|
+
iban: account.iban,
|
|
245
|
+
token: credentials.value
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
const preparedPayments2 = [];
|
|
250
|
+
for (const payment of batch.payments) {
|
|
251
|
+
const prepared = await connector.preparePayment(payment);
|
|
252
|
+
if (prepared) {
|
|
253
|
+
preparedPayments2.push(prepared);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return preparedPayments2;
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
const batchResult = await step.do(
|
|
260
|
+
"initiate batch from payments",
|
|
261
|
+
{
|
|
262
|
+
retries: { limit: 5, delay: 2e3, backoff: "constant" },
|
|
263
|
+
timeout: "30 seconds"
|
|
264
|
+
},
|
|
265
|
+
async () => {
|
|
266
|
+
if (batch.batchPaymentInitiatedAt) {
|
|
267
|
+
return {
|
|
268
|
+
authorizationUrls: batch.authorizationUrls,
|
|
269
|
+
initializedPayments: preparedPayments.map((p) => ({
|
|
270
|
+
...p,
|
|
271
|
+
status: "INITIALIZED"
|
|
272
|
+
})),
|
|
273
|
+
metadata: batch.metadata
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const account = await encryption.getAccountByIdQuery(db, {
|
|
277
|
+
accountId: batch.accountId
|
|
278
|
+
});
|
|
279
|
+
if (!account) {
|
|
280
|
+
throw new cloudflare_workflows.NonRetryableError(`Account not found: ${batch.accountId}`);
|
|
281
|
+
}
|
|
282
|
+
const encryptionKey = await encryption.importAesKey(this.env.ENCRYPTION_KEY);
|
|
283
|
+
const credentials = await encryption.getCredentialsByAccountId(db, encryptionKey, {
|
|
284
|
+
accountId: account.id
|
|
285
|
+
});
|
|
286
|
+
if (!credentials) {
|
|
287
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
288
|
+
`No credentials found for account: ${account.id}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (credentials.expiresAt < /* @__PURE__ */ new Date()) {
|
|
292
|
+
throw new cloudflare_workflows.NonRetryableError(
|
|
293
|
+
`Credentials have expired for account: ${account.id}`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const connector = encryption.initiateConnector({
|
|
297
|
+
env: this.env,
|
|
298
|
+
bank: account.connectorKey,
|
|
299
|
+
connectedAccounts: [
|
|
300
|
+
{
|
|
301
|
+
...account,
|
|
302
|
+
iban: account.iban,
|
|
303
|
+
token: credentials.value
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
});
|
|
307
|
+
const result = await connector.initiateBatchFromPayments({
|
|
308
|
+
batchId: batch.id,
|
|
309
|
+
payments: preparedPayments
|
|
310
|
+
});
|
|
311
|
+
const {
|
|
312
|
+
authorizationUrls,
|
|
313
|
+
payments: initializedPayments,
|
|
314
|
+
metadata
|
|
315
|
+
} = result;
|
|
316
|
+
if (!authorizationUrls || authorizationUrls.length === 0) {
|
|
317
|
+
throw new Error("Failed to retrieve authorization URLs");
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
authorizationUrls,
|
|
321
|
+
initializedPayments,
|
|
322
|
+
metadata
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
await step.do("update batch and payments to initialized", async () => {
|
|
327
|
+
const upsertBatch = encryption.upsertBatchCommand(db, {
|
|
328
|
+
batch: {
|
|
329
|
+
...batch,
|
|
330
|
+
authorizationUrls: batchResult.authorizationUrls,
|
|
331
|
+
metadata: batchResult.metadata,
|
|
332
|
+
status: "READY_TO_SIGN",
|
|
333
|
+
batchPaymentInitiatedAt: /* @__PURE__ */ new Date()
|
|
334
|
+
}
|
|
335
|
+
}).command;
|
|
336
|
+
await upsertBatch.execute();
|
|
337
|
+
return {
|
|
338
|
+
status: "READY_TO_SIGN",
|
|
339
|
+
paymentsInitialized: batchResult.initializedPayments.length
|
|
340
|
+
};
|
|
341
|
+
});
|
|
342
|
+
await step.do("enqueue authorization email", async () => {
|
|
343
|
+
await pushToQueue(this.env.NOTIFICATIONS_QUEUE, {
|
|
344
|
+
type: "email",
|
|
345
|
+
payload: {
|
|
346
|
+
email: {
|
|
347
|
+
to: [this.env.BANK_AUTH_RECIPIENT],
|
|
348
|
+
subject: "Payment Authorization",
|
|
349
|
+
text: Array.isArray(batchResult.authorizationUrls) ? batchResult.authorizationUrls[0] || "No Authorization URL" : batchResult.authorizationUrls || "No Authorization URL"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
metadata: {
|
|
353
|
+
initiator: {
|
|
354
|
+
service: "bank"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return { emailSent: true };
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
exports.BankProcessBatch = BankProcessBatch;
|
|
168
364
|
exports.BankSyncAccountPayments = BankSyncAccountPayments;
|