@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.
Files changed (37) hide show
  1. package/dist/database/schema.cjs +4 -4
  2. package/dist/database/schema.d.cts +1 -1
  3. package/dist/database/schema.d.mts +1 -1
  4. package/dist/database/schema.d.ts +1 -1
  5. package/dist/database/schema.mjs +4 -4
  6. package/dist/export/worker.cjs +166 -183
  7. package/dist/export/worker.d.cts +43 -22
  8. package/dist/export/worker.d.mts +43 -22
  9. package/dist/export/worker.d.ts +43 -22
  10. package/dist/export/worker.mjs +126 -143
  11. package/dist/export/workflows.cjs +214 -18
  12. package/dist/export/workflows.mjs +207 -12
  13. package/dist/export/wrangler.d.cts +1 -2
  14. package/dist/export/wrangler.d.mts +1 -2
  15. package/dist/export/wrangler.d.ts +1 -2
  16. package/dist/shared/{bank.YiArmBW2.mjs → bank.B1Gpn3ht.mjs} +12 -174
  17. package/dist/shared/{bank.xrXNjWCo.d.cts → bank.BEL1HIxZ.d.cts} +21 -1
  18. package/dist/shared/{bank.xrXNjWCo.d.mts → bank.BEL1HIxZ.d.mts} +21 -1
  19. package/dist/shared/{bank.xrXNjWCo.d.ts → bank.BEL1HIxZ.d.ts} +21 -1
  20. package/dist/shared/bank.BUEmFxS8.mjs +174 -0
  21. package/dist/shared/{bank.CLF9wee9.cjs → bank.CPYfE-Ei.cjs} +12 -199
  22. package/dist/shared/bank.CpwLFudl.cjs +198 -0
  23. package/dist/shared/bank.D-3fzX63.mjs +170 -0
  24. package/dist/shared/bank.D0a-MZon.cjs +184 -0
  25. package/dist/shared/{bank.BchnXQDL.d.cts → bank.Dh_H_5rC.d.cts} +0 -1
  26. package/dist/shared/{bank.BchnXQDL.d.mts → bank.Dh_H_5rC.d.mts} +0 -1
  27. package/dist/shared/{bank.BchnXQDL.d.ts → bank.Dh_H_5rC.d.ts} +0 -1
  28. package/dist/types.cjs +14 -14
  29. package/dist/types.d.cts +9 -9
  30. package/dist/types.d.mts +9 -9
  31. package/dist/types.d.ts +9 -9
  32. package/dist/types.mjs +5 -5
  33. package/package.json +1 -1
  34. package/dist/shared/bank.CNtiZSem.mjs +0 -9
  35. package/dist/shared/bank.CUU0Daxj.mjs +0 -391
  36. package/dist/shared/bank.ChffLgyT.cjs +0 -401
  37. package/dist/shared/bank.DfE0M5H3.cjs +0 -11
@@ -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, c as INSTRUCTION_PRIORITIES, C as CHARGE_BEARERS, P as PAYMENT_TYPES, g as CONNECTOR_KEYS, B as BATCH_STATUSES, d as PAYMENT_STATUSES, e as PAYMENT_DIRECTIONS, i as accountInsertSchema, M as MockConnector, F as FinbricksConnector, E as ErsteConnector } from '../shared/bank.YiArmBW2.mjs';
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 { e as encrypt, P as PROCESS_BATCH_WORKFLOW_EVENTS, i as importAesKey, a as getCredentialsByAccountId, b as initiateConnector, u as upsertBatchCommand, c as createPaymentCommand, g as getAccountByIdQuery } from '../shared/bank.CUU0Daxj.mjs';
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 'node:crypto';
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
- withAuth
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
- const accounts = await this._getAccounts();
429
- const accountsForConnector = accounts.filter(
430
- (acc) => acc.connectorKey === connectorKey && acc.status !== "DISABLED"
431
- );
432
- const encryptionKey = await importAesKey(this.env.ENCRYPTION_KEY);
433
- const accountsWithCredentials = await Promise.all(
434
- accountsForConnector.map(async (acc) => {
435
- const credentials = await getCredentialsByAccountId(
436
- this.db,
437
- encryptionKey,
438
- {
439
- accountId: acc.id
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
- if (!credentials) {
443
- throw createInternalError(null, {
444
- message: `No credentials found for account ${acc.id}`,
445
- code: "DB-B-001",
446
- status: 404
447
- });
448
- }
449
- return {
450
- currency: acc.currency,
451
- iban: acc.iban,
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: accountsWithCredentials,
450
+ connectedAccounts,
461
451
  env: this.env
462
452
  });
463
- if (!withAuth) return;
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 updateBatchStatuses() {
565
- const pendingBatches = await getAllPendingBatchesQuery(this.db);
566
- const uniqueConnectors = /* @__PURE__ */ new Set();
567
- const accounts = await this._getAccounts();
568
- for (const batch of pendingBatches) {
569
- const connectorKey = accounts.find(
570
- (acc) => acc.id === batch.accountId
571
- ).connectorKey;
572
- if (!uniqueConnectors.has(connectorKey)) {
573
- uniqueConnectors.add(connectorKey);
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 finalBatches = [];
577
- for (const connectorKey of uniqueConnectors) {
578
- const accs = accounts.filter((acc) => acc.connectorKey === connectorKey);
579
- const updatedBatches = await Promise.all(
580
- accs.map(async (account) => {
581
- const batch = pendingBatches.find((acc) => acc.id === account.id);
582
- const newStatus = await this.bankConnector.getBatchStatus({
583
- batchId: batch.id
584
- });
585
- return {
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: newStatus
588
- };
589
- })
590
- );
591
- finalBatches.push(...updatedBatches);
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 updateBatchesCommands = finalBatches.map((batch) => {
594
- const { command } = upsertBatchCommand(this.db, {
595
- batch: {
596
- ...batch
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
- return command;
600
- });
601
- await this.db.batch([
602
- updateBatchesCommands[0],
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
- console.log("Batch update completed");
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({ connectorKey, withAuth: false });
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 this.bankConnector.getAuthUri({ ott });
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 this.bankConnector.authorizeAccount({
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 database_schema = require('../shared/bank.CLF9wee9.cjs');
6
+ const drizzle = require('../shared/bank.CPYfE-Ei.cjs');
7
7
  const backendSdk = require('@develit-io/backend-sdk');
8
- const processBatch_workflow = require('../shared/bank.ChffLgyT.cjs');
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('jose');
13
- require('@develit-io/general-codes');
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('../shared/bank.DfE0M5H3.cjs');
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(database_schema.tables.account).set({
24
+ const command = db.update(drizzle.tables.account).set({
25
25
  lastSyncAt,
26
26
  lastSyncMetadata
27
- }).where(drizzleOrm.eq(database_schema.tables.account.id, accountId)).returning();
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: database_schema.tables });
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 processBatch_workflow.getAccountByIdQuery(db, { accountId });
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 processBatch_workflow.importAesKey(this.env.ENCRYPTION_KEY);
67
- const credentials = await processBatch_workflow.getCredentialsByAccountId(
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 = processBatch_workflow.initiateConnector({
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) => processBatch_workflow.createPaymentCommand(db, { payment: p.parsed }).command
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
- exports.BankProcessBatch = processBatch_workflow.BankProcessBatch;
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;