@develit-services/bank 0.3.60 → 0.4.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.
Files changed (45) hide show
  1. package/dist/database/schema.cjs +9 -9
  2. package/dist/database/schema.d.cts +4 -5
  3. package/dist/database/schema.d.mts +4 -5
  4. package/dist/database/schema.d.ts +4 -5
  5. package/dist/database/schema.mjs +3 -3
  6. package/dist/export/worker.cjs +91 -85
  7. package/dist/export/worker.d.cts +23 -766
  8. package/dist/export/worker.d.mts +23 -766
  9. package/dist/export/worker.d.ts +23 -766
  10. package/dist/export/worker.mjs +72 -66
  11. package/dist/export/workflows.cjs +215 -211
  12. package/dist/export/workflows.d.cts +4 -4
  13. package/dist/export/workflows.d.mts +4 -4
  14. package/dist/export/workflows.d.ts +4 -4
  15. package/dist/export/workflows.mjs +210 -206
  16. package/dist/export/wrangler.d.cts +3 -1
  17. package/dist/export/wrangler.d.mts +3 -1
  18. package/dist/export/wrangler.d.ts +3 -1
  19. package/dist/shared/bank.B51e8oDT.mjs +104 -0
  20. package/dist/shared/{bank.Dh_H_5rC.d.cts → bank.BJvgLwyZ.d.cts} +2 -0
  21. package/dist/shared/{bank.Dh_H_5rC.d.mts → bank.BJvgLwyZ.d.mts} +2 -0
  22. package/dist/shared/{bank.Dh_H_5rC.d.ts → bank.BJvgLwyZ.d.ts} +2 -0
  23. package/dist/shared/bank.BUSlmr6r.mjs +13 -0
  24. package/dist/shared/{bank.CMbfx0u7.cjs → bank.BeIpkWR-.cjs} +3 -14
  25. package/dist/shared/bank.Bg3Pdwm4.cjs +44 -0
  26. package/dist/shared/bank.CDoYUKBx.d.cts +748 -0
  27. package/dist/shared/bank.CDoYUKBx.d.mts +748 -0
  28. package/dist/shared/bank.CDoYUKBx.d.ts +748 -0
  29. package/dist/shared/{bank.CNEv3Rac.cjs → bank.CfSZTfWS.cjs} +634 -179
  30. package/dist/shared/{bank.XJJV8p4b.d.cts → bank.CsJuzqZH.d.cts} +2401 -2117
  31. package/dist/shared/{bank.XJJV8p4b.d.mts → bank.CsJuzqZH.d.mts} +2401 -2117
  32. package/dist/shared/{bank.XJJV8p4b.d.ts → bank.CsJuzqZH.d.ts} +2401 -2117
  33. package/dist/shared/bank.DEvSNsEs.cjs +114 -0
  34. package/dist/shared/{bank.DgGcjhmM.mjs → bank.DinCwx0P.mjs} +627 -179
  35. package/dist/shared/bank.Dpx6PvkA.mjs +33 -0
  36. package/dist/shared/bank.c38V_FCq.cjs +15 -0
  37. package/dist/shared/{bank.BqGzJNC6.mjs → bank.qc8ALZwm.mjs} +4 -14
  38. package/dist/types.cjs +51 -33
  39. package/dist/types.d.cts +164 -39
  40. package/dist/types.d.mts +164 -39
  41. package/dist/types.d.ts +164 -39
  42. package/dist/types.mjs +4 -3
  43. package/package.json +7 -10
  44. package/dist/shared/bank.CXpc8168.cjs +0 -212
  45. package/dist/shared/bank.DSsVeR3F.mjs +0 -202
@@ -1,17 +1,18 @@
1
1
  import { uuidv4, first, bankAccountMetadataSchema, workflowInstanceStatusSchema, develitWorker, createInternalError, 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 } from '../shared/bank.DgGcjhmM.mjs';
4
+ import { t as tables, e as encrypt, i as importAesKey, b as getCredentialsByAccountId, u as upsertBatchCommand, g as getBatchByIdQuery, d as createPaymentCommand, a as getAccountByIdQuery } from '../shared/bank.B51e8oDT.mjs';
5
+ import { i as initiateConnector, t as toIncomingPayment, d as assignAccount, e as toBatchedPayment } from '../shared/bank.DinCwx0P.mjs';
5
6
  import { z } from 'zod';
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.BqGzJNC6.mjs';
7
+ 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.qc8ALZwm.mjs';
7
8
  import { CURRENCY_CODES } from '@develit-io/general-codes';
8
9
  import 'date-fns';
9
- import { eq, sql, inArray, and, asc, desc, gte, lte } from 'drizzle-orm';
10
10
  import 'jose';
11
+ import { eq, sql, inArray, and, asc, desc, gte, lte } from 'drizzle-orm';
11
12
  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.DSsVeR3F.mjs';
13
- import 'drizzle-orm/sqlite-core';
13
+ import '../shared/bank.BUSlmr6r.mjs';
14
14
  import 'drizzle-orm/relations';
15
+ import 'drizzle-orm/sqlite-core';
15
16
  import 'drizzle-zod';
16
17
 
17
18
  const upsertAccountCommand = (db, { account }) => {
@@ -234,8 +235,8 @@ const getOttQuery = async (db, { ott }) => {
234
235
  return await db.select().from(tables.ott).where(eq(tables.ott.oneTimeToken, ott)).get();
235
236
  };
236
237
 
237
- const seperateSupportedPayments = (mappedPayments, accounts) => {
238
- const [supportedPayments, unsupportedPayments] = mappedPayments.reduce(
238
+ const seperateSupportedPayments = (incomingPayments, accounts) => {
239
+ const [supportedPayments, unsupportedPayments] = incomingPayments.reduce(
239
240
  ([valid, invalid], payment) => {
240
241
  const isValid = accounts.some(
241
242
  (acc) => acc.iban === payment.debtorIban && acc.currency === payment.currency
@@ -270,7 +271,8 @@ const sendPaymentInputSchema = z.object({
270
271
  message: z.string().optional(),
271
272
  creditor: bankAccountMetadataSchema,
272
273
  debtor: bankAccountMetadataSchema,
273
- purpose: z.string().optional()
274
+ purpose: z.string().optional(),
275
+ sendAsSinglePayment: z.boolean().optional()
274
276
  });
275
277
 
276
278
  const getAuthUriInputSchema = z.object({
@@ -673,32 +675,9 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
673
675
  }) {
674
676
  return this.handleAction(null, {}, async () => {
675
677
  this.logInput({ paymentsToBatch });
676
- const mappedPayments = paymentsToBatch.map(
677
- (payment) => {
678
- return {
679
- id: uuidv4(),
680
- correlationId: payment.correlationId,
681
- refId: payment.refId,
682
- amount: payment.amount,
683
- direction: "OUTGOING",
684
- paymentType: payment.paymentType,
685
- currency: payment.currency,
686
- status: "CREATED",
687
- initiatedAt: /* @__PURE__ */ new Date(),
688
- processedAt: null,
689
- vs: payment.vs || null,
690
- ss: payment.ss || null,
691
- ks: payment.ks || null,
692
- message: payment.message || null,
693
- creditor: payment.creditor,
694
- creditorIban: payment.creditor.iban || null,
695
- debtor: payment.debtor,
696
- debtorIban: payment.debtor.iban || null
697
- };
698
- }
699
- );
678
+ const incomingPayments = paymentsToBatch.map(toIncomingPayment);
700
679
  const accounts = await this._getAccounts();
701
- const { supportedPayments, unsupportedPayments } = seperateSupportedPayments(mappedPayments, accounts);
680
+ const { supportedPayments, unsupportedPayments } = seperateSupportedPayments(incomingPayments, accounts);
702
681
  if (unsupportedPayments.length > 0) {
703
682
  this.logError({ unsupportedPayments });
704
683
  await this.pushToQueue(
@@ -720,45 +699,72 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
720
699
  );
721
700
  }
722
701
  for (const acc of accounts) {
723
- const newPayments = supportedPayments.filter(
702
+ const accountAssignedPayments = supportedPayments.filter(
724
703
  (payment) => payment.debtorIban === acc.iban && payment.currency === acc.currency
725
- ).map((payment) => ({
726
- ...payment,
727
- connectorKey: acc.connectorKey,
728
- accountId: acc.id
729
- }));
730
- if (newPayments.length === 0) {
704
+ ).map((payment) => assignAccount(payment, acc));
705
+ if (accountAssignedPayments.length === 0) {
731
706
  continue;
732
707
  }
733
708
  this.log({
734
- message: `\u{1F4B0} Processing ${newPayments.length} payments for account (${acc.iban}, ${acc.currency})`
735
- });
736
- const openBatches = await getAccountOpenBatchesQuery(this.db, {
737
- accountId: acc.id
709
+ message: `\u{1F4B0} Processing ${accountAssignedPayments.length} payments for account (${acc.iban}, ${acc.currency})`
738
710
  });
739
- const availableBatch = openBatches.find(
740
- (batch) => batch.payments.length < acc.batchSizeLimit
711
+ const singlePayments = accountAssignedPayments.filter(
712
+ (p) => p.sendAsSinglePayment === true
741
713
  );
742
- this.log({
743
- message: availableBatch ? `\u{1F504} Found existing OPEN batches for account ${acc.id}, merging ${availableBatch.payments.length} existing + ${newPayments.length} new payments` : `\u2728 Creating new batch for account ${acc.id} with ${newPayments.length} payments`
744
- });
745
- const { command } = upsertBatchCommand(this.db, {
746
- batch: availableBatch ? {
747
- ...availableBatch,
748
- payments: [...availableBatch.payments, ...newPayments]
749
- } : {
750
- id: uuidv4(),
751
- authorizationUrls: [],
752
- accountId: acc.id,
753
- payments: newPayments,
754
- status: "OPEN"
755
- }
756
- });
757
- const upsertedBatch = await command.execute();
758
- this.log({
759
- message: `\u2705 Batch upserted successfully for account ${acc.id}`
760
- });
761
- return upsertedBatch;
714
+ const regularPayments = accountAssignedPayments.filter(
715
+ (p) => p.sendAsSinglePayment !== true
716
+ );
717
+ for (const singlePayment of singlePayments) {
718
+ const batchedPayment = toBatchedPayment(singlePayment);
719
+ const { command } = upsertBatchCommand(this.db, {
720
+ batch: {
721
+ id: uuidv4(),
722
+ authorizationUrls: [],
723
+ accountId: acc.id,
724
+ payments: [batchedPayment],
725
+ status: "OPEN",
726
+ metadata: {
727
+ sizeLimit: 1
728
+ }
729
+ }
730
+ });
731
+ await command.execute();
732
+ this.log({
733
+ message: `\u2728 Created single payment batch for account ${acc.id}`
734
+ });
735
+ }
736
+ if (regularPayments.length > 0) {
737
+ const openBatches = await getAccountOpenBatchesQuery(this.db, {
738
+ accountId: acc.id
739
+ });
740
+ const availableBatch = openBatches.find(
741
+ (batch) => batch.payments.length < (batch.metadata?.sizeLimit ?? acc.batchSizeLimit)
742
+ );
743
+ this.log({
744
+ message: availableBatch ? `\u{1F504} Found existing OPEN batch for account ${acc.id}, merging ${availableBatch.payments.length} existing + ${regularPayments.length} new payments` : `\u2728 Creating new batch for account ${acc.id} with ${regularPayments.length} payments`
745
+ });
746
+ const batchedPayments = regularPayments.map(toBatchedPayment);
747
+ const { command } = upsertBatchCommand(this.db, {
748
+ batch: availableBatch ? {
749
+ ...availableBatch,
750
+ payments: [...availableBatch.payments, ...batchedPayments]
751
+ } : {
752
+ id: uuidv4(),
753
+ authorizationUrls: [],
754
+ accountId: acc.id,
755
+ payments: batchedPayments,
756
+ status: "OPEN",
757
+ metadata: {
758
+ sizeLimit: acc.batchSizeLimit
759
+ }
760
+ }
761
+ });
762
+ const upsertedBatch = await command.execute();
763
+ this.log({
764
+ message: `\u2705 Batch upserted successfully for account ${acc.id}`
765
+ });
766
+ return upsertedBatch;
767
+ }
762
768
  }
763
769
  });
764
770
  }
@@ -1,16 +1,18 @@
1
1
  'use strict';
2
2
 
3
+ const backendSdk = require('@develit-io/backend-sdk');
3
4
  const cloudflare_workers = require('cloudflare:workers');
4
5
  const cloudflare_workflows = require('cloudflare:workflows');
5
6
  const d1 = require('drizzle-orm/d1');
6
- const drizzle = require('../shared/bank.CNEv3Rac.cjs');
7
- const backendSdk = require('@develit-io/backend-sdk');
8
- const encryption = require('../shared/bank.CXpc8168.cjs');
7
+ const drizzle = require('../shared/bank.DEvSNsEs.cjs');
8
+ const batchLifecycle = require('../shared/bank.Bg3Pdwm4.cjs');
9
+ const mock_connector = require('../shared/bank.CfSZTfWS.cjs');
9
10
  const drizzleOrm = require('drizzle-orm');
10
- require('date-fns');
11
- require('../shared/bank.CMbfx0u7.cjs');
12
- require('drizzle-orm/sqlite-core');
11
+ require('../shared/bank.c38V_FCq.cjs');
12
+ require('../shared/bank.BeIpkWR-.cjs');
13
13
  require('drizzle-orm/relations');
14
+ require('drizzle-orm/sqlite-core');
15
+ require('date-fns');
14
16
  require('@develit-io/general-codes');
15
17
  require('jose');
16
18
  require('node:crypto');
@@ -39,6 +41,206 @@ function pushToQueue$1(queue, message) {
39
41
  }))
40
42
  );
41
43
  }
44
+ class BankProcessBatch extends cloudflare_workers.WorkflowEntrypoint {
45
+ async run(event, step) {
46
+ const { batchId } = event.payload;
47
+ const db = d1.drizzle(this.env.BANK_D1, { schema: drizzle.tables });
48
+ const batch = await step.do("load batch", async () => {
49
+ const batch2 = await drizzle.getBatchByIdQuery(db, { batchId });
50
+ if (!batch2) {
51
+ throw new cloudflare_workflows.NonRetryableError(`Batch not found`);
52
+ }
53
+ if (batchLifecycle.isBatchSigned(batch2))
54
+ throw new cloudflare_workflows.NonRetryableError(`Batch already signed`);
55
+ return batch2;
56
+ });
57
+ await step.do("lock batch", async () => {
58
+ const paymentsChecksum = drizzle.checksum(batch.payments);
59
+ if (batch.paymentsChecksum && batch.paymentsChecksum !== paymentsChecksum)
60
+ throw new cloudflare_workflows.NonRetryableError(
61
+ `Batch payments have been modified externally, manual review needed`
62
+ );
63
+ if (batchLifecycle.isBatchFailed(batch))
64
+ throw new cloudflare_workflows.NonRetryableError(
65
+ `Batch is in FAILED status and cannot be processed, manual review needed`
66
+ );
67
+ if (batchLifecycle.isBatchOpen(batch)) {
68
+ const updatedBatch = await drizzle.upsertBatchCommand(db, {
69
+ batch: {
70
+ ...batch,
71
+ status: "PROCESSING",
72
+ paymentsChecksum
73
+ }
74
+ }).command.execute().then(backendSdk.first);
75
+ return { status: updatedBatch.status };
76
+ }
77
+ return { status: batch.status };
78
+ });
79
+ const batchResult = await step.do(
80
+ "initiate batch with payments",
81
+ {
82
+ retries: { limit: 5, delay: 2e3, backoff: "constant" },
83
+ timeout: "30 seconds"
84
+ },
85
+ async () => {
86
+ if (batch.batchPaymentInitiatedAt) {
87
+ return {
88
+ authorizationUrls: batch.authorizationUrls,
89
+ preparedPayments: batch.payments.map(
90
+ (p) => mock_connector.toPreparedPayment(p, void 0, batch.batchPaymentInitiatedAt)
91
+ ),
92
+ metadata: batch.metadata
93
+ };
94
+ }
95
+ const account = await drizzle.getAccountByIdQuery(db, {
96
+ accountId: batch.accountId
97
+ });
98
+ if (!account) {
99
+ throw new cloudflare_workflows.NonRetryableError(`Account not found: ${batch.accountId}`);
100
+ }
101
+ const encryptionKey = await drizzle.importAesKey(this.env.ENCRYPTION_KEY);
102
+ const credentials = await drizzle.getCredentialsByAccountId(db, encryptionKey, {
103
+ accountId: account.id
104
+ });
105
+ if (!credentials) {
106
+ throw new cloudflare_workflows.NonRetryableError(
107
+ `No credentials found for account: ${account.id}`
108
+ );
109
+ }
110
+ if (credentials.expiresAt < /* @__PURE__ */ new Date()) {
111
+ throw new cloudflare_workflows.NonRetryableError(
112
+ `Credentials have expired for account: ${account.id}`
113
+ );
114
+ }
115
+ const connector = mock_connector.initiateConnector({
116
+ env: this.env,
117
+ bank: account.connectorKey,
118
+ connectedAccounts: [
119
+ {
120
+ ...account,
121
+ iban: account.iban,
122
+ token: credentials.value
123
+ }
124
+ ]
125
+ });
126
+ const result = await connector.initiateBatch({
127
+ batchId: batch.id,
128
+ payments: batch.payments
129
+ });
130
+ const {
131
+ authorizationUrls,
132
+ payments: preparedPayments,
133
+ metadata
134
+ } = result;
135
+ if (!authorizationUrls || authorizationUrls.length === 0) {
136
+ throw new Error("Failed to retrieve authorization URLs");
137
+ }
138
+ return {
139
+ authorizationUrls,
140
+ preparedPayments,
141
+ metadata
142
+ };
143
+ }
144
+ );
145
+ await step.do("update batch to ready to sign", async () => {
146
+ const upsertBatch = drizzle.upsertBatchCommand(db, {
147
+ batch: {
148
+ ...batch,
149
+ authorizationUrls: batchResult.authorizationUrls,
150
+ metadata: batchResult.metadata,
151
+ status: "READY_TO_SIGN",
152
+ batchPaymentInitiatedAt: /* @__PURE__ */ new Date()
153
+ }
154
+ }).command;
155
+ await upsertBatch.execute();
156
+ return {
157
+ status: "READY_TO_SIGN",
158
+ paymentsPrepared: batchResult.preparedPayments.length
159
+ };
160
+ });
161
+ const isMockConnector = batchResult.preparedPayments.some(
162
+ (p) => p.connectorKey === "MOCK"
163
+ );
164
+ if (isMockConnector) {
165
+ await step.do(
166
+ "mock: create completed payments and update batch",
167
+ async () => {
168
+ const processedAt = /* @__PURE__ */ new Date();
169
+ const completedPayments = batchResult.preparedPayments.map(
170
+ (p) => mock_connector.toCompletedPayment(p, "COMPLETED", `MOCK-${backendSdk.uuidv4()}`, processedAt)
171
+ );
172
+ const createCommands = completedPayments.map(
173
+ (payment) => drizzle.createPaymentCommand(db, { payment }).command
174
+ );
175
+ const updateBatchCommand = drizzle.upsertBatchCommand(db, {
176
+ batch: {
177
+ ...batch,
178
+ status: "SIGNED",
179
+ authorizationUrls: batchResult.authorizationUrls,
180
+ metadata: batchResult.metadata,
181
+ batchPaymentInitiatedAt: /* @__PURE__ */ new Date()
182
+ }
183
+ }).command;
184
+ if (createCommands.length > 0) {
185
+ await db.batch([updateBatchCommand, ...createCommands]);
186
+ } else {
187
+ await updateBatchCommand.execute();
188
+ }
189
+ const eventsToEmit = completedPayments.map(
190
+ (payment) => ({
191
+ eventType: "BANK_PAYMENT",
192
+ eventSignal: "paymentCompleted",
193
+ bankPayment: payment,
194
+ metadata: {
195
+ correlationId: backendSdk.uuidv4(),
196
+ entityId: payment.id,
197
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
198
+ }
199
+ })
200
+ );
201
+ await pushToQueue$1(
202
+ this.env.QUEUE_BUS_QUEUE,
203
+ eventsToEmit
204
+ );
205
+ return {
206
+ paymentsCreated: completedPayments.length,
207
+ eventsEmitted: completedPayments.length
208
+ };
209
+ }
210
+ );
211
+ }
212
+ if (!isMockConnector) {
213
+ await step.do("enqueue authorization email", async () => {
214
+ await pushToQueue$1(this.env.NOTIFICATIONS_QUEUE, {
215
+ type: "email",
216
+ payload: {
217
+ email: {
218
+ to: [this.env.BANK_AUTH_RECIPIENT],
219
+ subject: "Payment Authorization",
220
+ text: Array.isArray(batchResult.authorizationUrls) ? batchResult.authorizationUrls[0] || "No Authorization URL" : batchResult.authorizationUrls || "No Authorization URL"
221
+ }
222
+ },
223
+ metadata: {
224
+ initiator: {
225
+ service: "bank"
226
+ }
227
+ }
228
+ });
229
+ return { emailSent: true };
230
+ });
231
+ }
232
+ }
233
+ }
234
+
235
+ function pushToQueue(queue, message) {
236
+ if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
237
+ return queue.sendBatch(
238
+ message.map((m) => ({
239
+ body: m,
240
+ contentType: "v8"
241
+ }))
242
+ );
243
+ }
42
244
  class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
43
245
  async run(event, step) {
44
246
  const { accountId } = event.payload;
@@ -46,7 +248,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
46
248
  while (true) {
47
249
  const now = /* @__PURE__ */ new Date();
48
250
  const account = await step.do("load account", async () => {
49
- const account2 = await encryption.getAccountByIdQuery(db, { accountId });
251
+ const account2 = await drizzle.getAccountByIdQuery(db, { accountId });
50
252
  if (!account2) {
51
253
  throw new cloudflare_workflows.NonRetryableError(`Bank account not found: ${accountId}`);
52
254
  }
@@ -63,8 +265,8 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
63
265
  timeout: "30 seconds"
64
266
  },
65
267
  async () => {
66
- const encryptionKey = await encryption.importAesKey(this.env.ENCRYPTION_KEY);
67
- const credentials = await encryption.getCredentialsByAccountId(
268
+ const encryptionKey = await drizzle.importAesKey(this.env.ENCRYPTION_KEY);
269
+ const credentials = await drizzle.getCredentialsByAccountId(
68
270
  db,
69
271
  encryptionKey,
70
272
  { accountId }
@@ -79,7 +281,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
79
281
  `Credentials have expired for account: ${accountId}`
80
282
  );
81
283
  }
82
- const connector = encryption.initiateConnector({
284
+ const connector = mock_connector.initiateConnector({
83
285
  env: this.env,
84
286
  bank: account.connectorKey,
85
287
  connectedAccounts: [
@@ -91,15 +293,13 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
91
293
  ]
92
294
  });
93
295
  return await connector.getAllAccountPayments({
94
- environment: this.env.ENVIRONMENT,
95
- db,
96
296
  account,
97
297
  filter: { dateFrom: account.lastSyncAt }
98
298
  });
99
299
  }
100
300
  );
101
301
  const paymentsToProcess = payments.filter(
102
- (p) => p.parsed.status === "COMPLETED" || p.parsed.status === "FAILED"
302
+ (p) => batchLifecycle.isPaymentCompleted(p.parsed)
103
303
  );
104
304
  if (paymentsToProcess.length) {
105
305
  const lastSyncBankRefIds = account.lastSyncMetadata?.lastSyncBankRefIds || [];
@@ -111,7 +311,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
111
311
  async () => {
112
312
  const eventsToEmit = [];
113
313
  const createCommands = paymentsToInsert.map(
114
- (p) => encryption.createPaymentCommand(db, { payment: p.parsed }).command
314
+ (p) => drizzle.createPaymentCommand(db, { payment: p.parsed }).command
115
315
  );
116
316
  eventsToEmit.push(
117
317
  ...paymentsToInsert.map((p) => ({
@@ -144,7 +344,7 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
144
344
  await db.batch(
145
345
  backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
146
346
  );
147
- await pushToQueue$1(
347
+ await pushToQueue(
148
348
  this.env.QUEUE_BUS_QUEUE,
149
349
  eventsToEmit
150
350
  );
@@ -164,201 +364,5 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
164
364
  }
165
365
  }
166
366
 
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") {
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
367
  exports.BankProcessBatch = BankProcessBatch;
364
368
  exports.BankSyncAccountPayments = BankSyncAccountPayments;