@develit-services/bank 0.0.3 → 0.0.5

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.
@@ -0,0 +1,1224 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const backendSdk = require('@develit-io/backend-sdk');
6
+ const db = require('../shared/bank.BU2_AKRG.cjs');
7
+ const cloudflare_workers = require('cloudflare:workers');
8
+ const d1 = require('drizzle-orm/d1');
9
+ const zod = require('zod');
10
+ const generalCodes = require('@develit-io/general-codes');
11
+ const drizzleOrm = require('drizzle-orm');
12
+ const superjson = require('superjson');
13
+ require('../shared/bank.CrAQe4PH.cjs');
14
+ require('drizzle-orm/sqlite-core');
15
+ require('drizzle-orm/relations');
16
+ require('date-fns');
17
+ require('jose');
18
+
19
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
20
+
21
+ const superjson__default = /*#__PURE__*/_interopDefaultCompat(superjson);
22
+
23
+ const sendPaymentInputSchema = zod.z.object({
24
+ refId: zod.z.string(),
25
+ amount: zod.z.number().positive(),
26
+ paymentType: zod.z.enum(db.PAYMENT_TYPES),
27
+ currency: zod.z.enum(generalCodes.CURRENCY_CODES),
28
+ vs: zod.z.string().nullable().optional(),
29
+ ss: zod.z.string().nullable().optional(),
30
+ ks: zod.z.string().nullable().optional(),
31
+ message: zod.z.string().nullable().optional(),
32
+ creditorHolderName: zod.z.string(),
33
+ creditorAccountNumberWithBankCode: zod.z.string(),
34
+ creditorIban: zod.z.string(),
35
+ debtorHolderName: zod.z.string(),
36
+ debtorAccountNumberWithBankCode: zod.z.string(),
37
+ debtorIban: zod.z.string()
38
+ });
39
+
40
+ const saveErsteCodeInputSchema = zod.z.object({
41
+ code: zod.z.string()
42
+ });
43
+
44
+ const simulateDepositInputSchema = zod.z.object({
45
+ amount: zod.z.number(),
46
+ currency: zod.z.enum(generalCodes.CURRENCY_CODES),
47
+ bankingVs: zod.z.string().max(10),
48
+ message: zod.z.string(),
49
+ creditorIban: zod.z.string().max(34),
50
+ creditorHolderName: zod.z.string(),
51
+ debtorIban: zod.z.string().max(34),
52
+ debtorHolderName: zod.z.string()
53
+ });
54
+
55
+ const setLastSyncAtInputSchema = zod.z.object({
56
+ iban: zod.z.string(),
57
+ lastSyncedAt: zod.z.coerce.date()
58
+ });
59
+
60
+ const initiateConnectorInputSchema = zod.z.object({
61
+ connectorKey: zod.z.string()
62
+ });
63
+
64
+ const getBatchesInputSchema = zod.z.object({
65
+ limit: zod.z.number().positive().optional().default(100),
66
+ offset: zod.z.number().min(0).optional().default(0)
67
+ });
68
+
69
+ const monthAgo = new Date(
70
+ Date.now() - 30 * 24 * 60 * 60 * 1e3
71
+ // 30 days ago
72
+ );
73
+ const stagingAccounts = [
74
+ {
75
+ connectorKey: "MOCK_COBS",
76
+ id: "staging-czk",
77
+ identification: {
78
+ number: "1234567890",
79
+ bankCode: "5051",
80
+ iban: "CZ0350510000000000000449",
81
+ holderName: "Devizov\xE1 Burza a.s."
82
+ },
83
+ fallbackLastSync: monthAgo,
84
+ currency: "CZK",
85
+ production: false
86
+ },
87
+ {
88
+ connectorKey: "MOCK_COBS",
89
+ id: "staging-eur",
90
+ identification: {
91
+ number: "2345678901",
92
+ bankCode: "5051",
93
+ iban: "CZ0750510000000000000668",
94
+ holderName: "Devizov\xE1 Burza a.s."
95
+ },
96
+ fallbackLastSync: monthAgo,
97
+ currency: "EUR",
98
+ production: false
99
+ },
100
+ {
101
+ connectorKey: "FIO",
102
+ id: "kleinpetr-test",
103
+ identification: {
104
+ number: "2502130437",
105
+ bankCode: "2010",
106
+ iban: "CZ8520100000002502130437",
107
+ holderName: "Petr Klein"
108
+ },
109
+ fallbackLastSync: monthAgo,
110
+ currency: "CZK",
111
+ production: true
112
+ },
113
+ {
114
+ connectorKey: "MOCK",
115
+ id: "id-test",
116
+ identification: {
117
+ number: "777777777",
118
+ bankCode: "0800",
119
+ iban: "PL0000000000002444430437",
120
+ holderName: "Test Test"
121
+ },
122
+ fallbackLastSync: monthAgo,
123
+ currency: "PLN",
124
+ production: false
125
+ }
126
+ // {
127
+ // connectorKey: 'MOCK_COBS',
128
+ // id: 'staging-usd',
129
+ // identification: {
130
+ // iban: 'CZ3456789012345678901234',
131
+ // accountNumberWithBankCode: '3456789012/3456',
132
+ // },
133
+ // currency: 'USD',
134
+ // bankCode: '0800',
135
+ // accountNumber: '3456789012',
136
+ // production: false,
137
+ // },
138
+ ];
139
+ const productionAccounts = [
140
+ // Banka CREDITAS (2250)
141
+ {
142
+ connectorKey: "CREDITAS",
143
+ id: "2250-CZK",
144
+ identification: {
145
+ number: "100602391",
146
+ bankCode: "2250",
147
+ iban: "CZ5122500000000100602391",
148
+ holderName: "Devizov\xE1 Burza a.s."
149
+ },
150
+ fallbackLastSync: monthAgo,
151
+ currency: "CZK",
152
+ production: true
153
+ },
154
+ {
155
+ connectorKey: "CREDITAS",
156
+ id: "2250-EUR",
157
+ identification: {
158
+ number: "100602404",
159
+ bankCode: "2250",
160
+ iban: "CZ8822500000000100602404",
161
+ holderName: "Devizov\xE1 Burza a.s."
162
+ },
163
+ fallbackLastSync: monthAgo,
164
+ currency: "EUR",
165
+ production: true
166
+ },
167
+ {
168
+ connectorKey: "CREDITAS",
169
+ id: "2250-USD",
170
+ identification: {
171
+ number: "100602412",
172
+ bankCode: "2250",
173
+ iban: "CZ6622500000000100602412",
174
+ holderName: "Devizov\xE1 Burza a.s."
175
+ },
176
+ fallbackLastSync: monthAgo,
177
+ currency: "USD",
178
+ production: true
179
+ },
180
+ {
181
+ connectorKey: "CREDITAS",
182
+ id: "2250-GBP",
183
+ identification: {
184
+ number: "100602420",
185
+ bankCode: "2250",
186
+ iban: "CZ4422500000000100602420",
187
+ holderName: "Devizov\xE1 Burza a.s."
188
+ },
189
+ fallbackLastSync: monthAgo,
190
+ currency: "GBP",
191
+ production: true
192
+ },
193
+ {
194
+ connectorKey: "CREDITAS",
195
+ id: "2250-CHF",
196
+ identification: {
197
+ number: "100602439",
198
+ bankCode: "2250",
199
+ iban: "CZ1622500000000100602439",
200
+ holderName: "Devizov\xE1 Burza a.s."
201
+ },
202
+ fallbackLastSync: monthAgo,
203
+ currency: "CHF",
204
+ production: true
205
+ },
206
+ {
207
+ connectorKey: "CREDITAS",
208
+ id: "2250-PLN",
209
+ identification: {
210
+ number: "100602447",
211
+ bankCode: "2250",
212
+ iban: "CZ9122500000000100602447",
213
+ holderName: "Devizov\xE1 Burza a.s."
214
+ },
215
+ fallbackLastSync: monthAgo,
216
+ currency: "PLN",
217
+ production: true
218
+ },
219
+ // Fio banka (2010)
220
+ {
221
+ connectorKey: "FINBRICKS",
222
+ id: "2010-CZK",
223
+ identification: {
224
+ number: "2500845869",
225
+ bankCode: "2010",
226
+ iban: "CZ0420100000002500845869",
227
+ holderName: "Devizov\xE1 Burza a.s."
228
+ },
229
+ fallbackLastSync: monthAgo,
230
+ currency: "CZK",
231
+ production: true
232
+ },
233
+ {
234
+ connectorKey: "FINBRICKS",
235
+ id: "2010-EUR",
236
+ identification: {
237
+ number: "2600884128",
238
+ bankCode: "2010",
239
+ iban: "CZ0820100000002600884128",
240
+ holderName: "Devizov\xE1 Burza a.s."
241
+ },
242
+ fallbackLastSync: monthAgo,
243
+ currency: "EUR",
244
+ production: true
245
+ },
246
+ {
247
+ connectorKey: "FINBRICKS",
248
+ id: "2010-USD",
249
+ identification: {
250
+ number: "2700884133",
251
+ bankCode: "2010",
252
+ iban: "CZ1420100000002700884133",
253
+ holderName: "Devizov\xE1 Burza a.s."
254
+ },
255
+ fallbackLastSync: monthAgo,
256
+ currency: "USD",
257
+ production: true
258
+ },
259
+ {
260
+ connectorKey: "FINBRICKS",
261
+ id: "2010-CHF",
262
+ identification: {
263
+ number: "2401098351",
264
+ bankCode: "2010",
265
+ iban: "CZ0920100000002401098351",
266
+ holderName: "Devizov\xE1 Burza a.s."
267
+ },
268
+ fallbackLastSync: monthAgo,
269
+ currency: "CHF",
270
+ production: true
271
+ },
272
+ {
273
+ connectorKey: "FINBRICKS",
274
+ id: "2010-GBP",
275
+ identification: {
276
+ number: "2400884131",
277
+ bankCode: "2010",
278
+ iban: "CZ3320100000002400884131",
279
+ holderName: "Devizov\xE1 Burza a.s."
280
+ },
281
+ fallbackLastSync: monthAgo,
282
+ currency: "GBP",
283
+ production: true
284
+ },
285
+ {
286
+ connectorKey: "FINBRICKS",
287
+ id: "2010-PLN",
288
+ identification: {
289
+ number: "2701098353",
290
+ bankCode: "2010",
291
+ iban: "CZ8720100000002701098353",
292
+ holderName: "Devizov\xE1 Burza a.s."
293
+ },
294
+ fallbackLastSync: monthAgo,
295
+ currency: "PLN",
296
+ production: true
297
+ },
298
+ {
299
+ connectorKey: "FINBRICKS",
300
+ id: "2010-CAD",
301
+ identification: {
302
+ number: "2701478102",
303
+ bankCode: "2010",
304
+ iban: "CZ5520100000002701478102",
305
+ holderName: "Devizov\xE1 Burza a.s."
306
+ },
307
+ fallbackLastSync: monthAgo,
308
+ currency: "CAD",
309
+ production: true
310
+ },
311
+ // Komerční banka (0100)
312
+ {
313
+ connectorKey: "FINBRICKS",
314
+ id: "0100-CZK",
315
+ identification: {
316
+ number: "115-1483630297",
317
+ bankCode: "0100",
318
+ iban: "CZ5901000001151483630297",
319
+ holderName: "Devizov\xE1 Burza a.s."
320
+ },
321
+ fallbackLastSync: monthAgo,
322
+ currency: "CZK",
323
+ production: true
324
+ },
325
+ {
326
+ connectorKey: "FINBRICKS",
327
+ id: "0100-EUR",
328
+ identification: {
329
+ number: "115-1484940297",
330
+ bankCode: "0100",
331
+ iban: "CZ4201000001151484940297",
332
+ holderName: "Devizov\xE1 Burza a.s."
333
+ },
334
+ fallbackLastSync: monthAgo,
335
+ currency: "EUR",
336
+ production: true
337
+ },
338
+ {
339
+ connectorKey: "FINBRICKS",
340
+ id: "0100-USD",
341
+ identification: {
342
+ number: "115-1483960237",
343
+ bankCode: "0100",
344
+ iban: "CZ6201000001151483960237",
345
+ holderName: "Devizov\xE1 Burza a.s."
346
+ },
347
+ fallbackLastSync: monthAgo,
348
+ currency: "USD",
349
+ production: true
350
+ },
351
+ {
352
+ connectorKey: "FINBRICKS",
353
+ id: "0100-PLN",
354
+ identification: {
355
+ number: "115-6674290267",
356
+ bankCode: "0100",
357
+ iban: "CZ3501000001156674290267",
358
+ holderName: "Devizov\xE1 Burza a.s."
359
+ },
360
+ fallbackLastSync: monthAgo,
361
+ currency: "PLN",
362
+ production: true
363
+ },
364
+ {
365
+ connectorKey: "FINBRICKS",
366
+ id: "0100-SEK",
367
+ identification: {
368
+ number: "115-9578110207",
369
+ bankCode: "0100",
370
+ iban: "CZ2401000001159578110207",
371
+ holderName: "Devizov\xE1 Burza a.s."
372
+ },
373
+ fallbackLastSync: monthAgo,
374
+ currency: "SEK",
375
+ production: true
376
+ },
377
+ // Česká spořitelna (0800)
378
+ {
379
+ connectorKey: "ERSTE",
380
+ id: "0800-CZK",
381
+ identification: {
382
+ number: "4413926379",
383
+ bankCode: "0800",
384
+ iban: "CZ7908000000004413926379",
385
+ holderName: "Devizov\xE1 Burza a.s."
386
+ },
387
+ fallbackLastSync: monthAgo,
388
+ currency: "CZK",
389
+ production: true
390
+ },
391
+ {
392
+ connectorKey: "ERSTE",
393
+ id: "0800-EUR",
394
+ identification: {
395
+ number: "1966646293",
396
+ bankCode: "0800",
397
+ iban: "CZ2308000000001966646293",
398
+ holderName: "Devizov\xE1 Burza a.s."
399
+ },
400
+ fallbackLastSync: monthAgo,
401
+ currency: "EUR",
402
+ production: true
403
+ },
404
+ {
405
+ connectorKey: "ERSTE",
406
+ id: "0800-USD",
407
+ identification: {
408
+ number: "1978982243",
409
+ bankCode: "0800",
410
+ iban: "CZ1908000000001978982243",
411
+ holderName: "Devizov\xE1 Burza a.s."
412
+ },
413
+ fallbackLastSync: monthAgo,
414
+ currency: "USD",
415
+ production: true
416
+ },
417
+ {
418
+ connectorKey: "ERSTE",
419
+ id: "0800-HUF",
420
+ identification: {
421
+ number: "13461252",
422
+ bankCode: "0800",
423
+ iban: "CZ1908000000000013461252",
424
+ holderName: "Devizov\xE1 Burza a.s."
425
+ },
426
+ fallbackLastSync: monthAgo,
427
+ currency: "HUF",
428
+ production: true
429
+ },
430
+ // MONETA Money Bank (0600)
431
+ {
432
+ connectorKey: "FINBRICKS",
433
+ id: "0600-CZK",
434
+ identification: {
435
+ number: "224252186",
436
+ bankCode: "0600",
437
+ iban: "CZ5906000000000224252186",
438
+ holderName: "Devizov\xE1 Burza a.s."
439
+ },
440
+ fallbackLastSync: monthAgo,
441
+ currency: "CZK",
442
+ production: true
443
+ },
444
+ {
445
+ connectorKey: "FINBRICKS",
446
+ id: "0600-EUR",
447
+ identification: {
448
+ number: "224252231",
449
+ bankCode: "0600",
450
+ iban: "CZ0806000000000224252231",
451
+ holderName: "Devizov\xE1 Burza a.s."
452
+ },
453
+ fallbackLastSync: monthAgo,
454
+ currency: "EUR",
455
+ production: true
456
+ },
457
+ {
458
+ connectorKey: "FINBRICKS",
459
+ id: "0600-USD",
460
+ identification: {
461
+ number: "224252290",
462
+ bankCode: "0600",
463
+ iban: "CZ6406000000000224252290",
464
+ holderName: "Devizov\xE1 Burza a.s."
465
+ },
466
+ fallbackLastSync: monthAgo,
467
+ currency: "USD",
468
+ production: true
469
+ },
470
+ // NEY spořitelní družstvo (2260)
471
+ {
472
+ connectorKey: "FINBRICKS",
473
+ id: "2260-CZK",
474
+ identification: {
475
+ number: "1702700014",
476
+ bankCode: "2260",
477
+ iban: "CZ7422600000001702700014",
478
+ holderName: "Devizov\xE1 Burza a.s."
479
+ },
480
+ fallbackLastSync: monthAgo,
481
+ currency: "CZK",
482
+ production: true
483
+ }
484
+ ];
485
+
486
+ const createPaymentCommand = (db$1, { payment }) => {
487
+ return {
488
+ command: db$1.insert(db.tables.payment).values(payment).returning()
489
+ };
490
+ };
491
+
492
+ const updatePaymentCommand = (db$1, { payment }) => {
493
+ return {
494
+ command: db$1.update(db.tables.payment).set(payment).where(drizzleOrm.eq(db.tables.payment.id, payment.id)).returning()
495
+ };
496
+ };
497
+
498
+ const upsertBatchCommand = (db$1, { batch }) => {
499
+ const id = batch.id || backendSdk.uuidv4();
500
+ const command = db$1.insert(db.tables.batch).values({
501
+ ...batch,
502
+ id
503
+ }).onConflictDoUpdate({
504
+ target: db.tables.batch.id,
505
+ set: {
506
+ ...batch
507
+ }
508
+ }).returning();
509
+ return {
510
+ id,
511
+ command
512
+ };
513
+ };
514
+
515
+ const getPaymentByRefIdQuery = async (db$1, { refId }) => {
516
+ const payment = await db$1.select().from(db.tables.payment).where(drizzleOrm.eq(db.tables.payment.refId, refId)).limit(1).get();
517
+ return payment;
518
+ };
519
+
520
+ const getOpenBatchByAccountIdQuery = async (db$1, { accountId }) => {
521
+ const batch = await db$1.select().from(db.tables.batch).where(
522
+ drizzleOrm.and(
523
+ drizzleOrm.eq(db.tables.batch.accountId, accountId),
524
+ drizzleOrm.eq(db.tables.batch.status, "OPEN")
525
+ )
526
+ ).limit(1).get();
527
+ return batch;
528
+ };
529
+
530
+ const getAllOpenBatchesQuery = async (db$1) => {
531
+ return await db$1.select().from(db.tables.batch).where(drizzleOrm.eq(db.tables.batch.status, "OPEN"));
532
+ };
533
+
534
+ const getPaymentsByBankRefIdsQuery = async (db$1, { ids }) => {
535
+ return await db$1.select().from(db.tables.payment).where(drizzleOrm.inArray(db.tables.payment.bankRefId, ids));
536
+ };
537
+
538
+ class CreditasConnector extends db.FinbricksConnector {
539
+ constructor(config) {
540
+ super("CREDITAS", config);
541
+ }
542
+ }
543
+
544
+ class FioConnector extends db.FinbricksConnector {
545
+ constructor(config) {
546
+ super("FIO", config);
547
+ }
548
+ }
549
+
550
+ const initiateConnector = (bank, env) => {
551
+ switch (bank) {
552
+ case "ERSTE":
553
+ return new db.ErsteConnector({
554
+ API_KEY: env.ERSTE_API_KEY,
555
+ CLIENT_ID: env.ERSTE_CLIENT_ID,
556
+ CLIENT_SECRET: env.ERSTE_CLIENT_SECRET,
557
+ REDIRECT_URI: env.ERSTE_REDIRECT_URI,
558
+ AUTH_URI: env.ERSTE_AUTH_URI,
559
+ PAYMENTS_URI: env.ERSTE_PAYMENTS_URI,
560
+ ACCOUNTS_URI: env.ERSTE_ACCOUNTS_URI
561
+ });
562
+ case "CREDITAS":
563
+ return new CreditasConnector({
564
+ BASE_URI: env.FINBRICKS_BASE_URI,
565
+ MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
566
+ PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM
567
+ });
568
+ case "MOCK_COBS":
569
+ return new db.MockCobsConnector({
570
+ BASE_URI: env.FINBRICKS_BASE_URI,
571
+ MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
572
+ PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM
573
+ });
574
+ case "FIO":
575
+ return new FioConnector({
576
+ BASE_URI: env.FINBRICKS_BASE_URI,
577
+ MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
578
+ PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM
579
+ });
580
+ default:
581
+ return new db.MockConnector();
582
+ }
583
+ };
584
+
585
+ const useSync = (kv) => {
586
+ const getLastSync = async ({
587
+ account
588
+ }) => {
589
+ const [rawSync, rawSyncError] = await backendSdk.useResult(
590
+ kv.get(`sync-log:${account.identification.iban}:${account.currency}`)
591
+ );
592
+ if (rawSyncError) throw backendSdk.createInternalError(rawSyncError);
593
+ if (!rawSync) return null;
594
+ return superjson__default.parse(rawSync);
595
+ };
596
+ const setLastSync = async ({
597
+ account,
598
+ log
599
+ }) => {
600
+ const [_, error] = await backendSdk.useResult(
601
+ kv.put(
602
+ `sync-log:${account.identification.iban}:${account.currency}`,
603
+ superjson__default.stringify(log)
604
+ )
605
+ );
606
+ if (error) throw error;
607
+ };
608
+ return {
609
+ getLastSync,
610
+ setLastSync
611
+ };
612
+ };
613
+
614
+ const seperateSupportedPayments = (mappedPayments, accounts) => {
615
+ const [supportedPayments, unsupportedPayments] = mappedPayments.reduce(
616
+ ([valid, invalid], payment) => {
617
+ const isValid = accounts.some(
618
+ (acc) => acc.identification.iban === payment.debtorIban && acc.currency === payment.currency
619
+ );
620
+ if (isValid) {
621
+ valid.push(payment);
622
+ } else {
623
+ invalid.push(payment);
624
+ }
625
+ return [valid, invalid];
626
+ },
627
+ [[], []]
628
+ );
629
+ return {
630
+ supportedPayments,
631
+ unsupportedPayments
632
+ };
633
+ };
634
+
635
+ var __defProp = Object.defineProperty;
636
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
637
+ var __decorateClass = (decorators, target, key, kind) => {
638
+ var result = __getOwnPropDesc(target, key) ;
639
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
640
+ if (decorator = decorators[i])
641
+ result = (decorator(target, key, result) ) || result;
642
+ if (result) __defProp(target, key, result);
643
+ return result;
644
+ };
645
+ class BankServiceBase extends backendSdk.develitWorker(cloudflare_workers.WorkerEntrypoint) {
646
+ constructor(ctx, env) {
647
+ super(ctx, env);
648
+ this.name = "bank-service";
649
+ this.db = d1.drizzle(this.env.BANK_D1, { schema: db.tables });
650
+ }
651
+ get accounts() {
652
+ return this.env.ENVIRONMENT === "production" ? productionAccounts : stagingAccounts;
653
+ }
654
+ accountFetchInterval(connectorKey) {
655
+ switch (connectorKey) {
656
+ case "ERSTE":
657
+ return db.ErsteConnector.FETCH_INTERVAL;
658
+ case "FINBRICKS":
659
+ return db.FinbricksConnector.FETCH_INTERVAL;
660
+ case "MOCK":
661
+ return db.MockConnector.FETCH_INTERVAL;
662
+ default:
663
+ return 0;
664
+ }
665
+ }
666
+ async syncAccounts() {
667
+ return this.handleAction(null, {}, async () => {
668
+ const accountsToSync = [];
669
+ const { getLastSync } = useSync(this.env.BANK_KV);
670
+ for (const account of this.accounts.filter(
671
+ (acc) => !!!acc.connectorKey.includes("MOCK")
672
+ )) {
673
+ let lastSync = await getLastSync({ account });
674
+ if (!lastSync) {
675
+ await this.setLastSyncAt({
676
+ iban: account.identification.iban,
677
+ lastSyncedAt: account.fallbackLastSync
678
+ });
679
+ lastSync = await getLastSync({ account });
680
+ if (!lastSync) continue;
681
+ }
682
+ const accountFetchInterval = this.accountFetchInterval(
683
+ account.connectorKey
684
+ );
685
+ const now = Date.now();
686
+ const lastSyncTime = lastSync.lastSyncedAt.getTime();
687
+ const intervalMs = accountFetchInterval * 1e3;
688
+ const shouldFetch = now - lastSyncTime >= intervalMs;
689
+ if (!shouldFetch) continue;
690
+ accountsToSync.push({
691
+ ...account,
692
+ lastSyncAt: lastSync
693
+ });
694
+ }
695
+ console.log("ACCS TO SYNC", accountsToSync.length);
696
+ if (accountsToSync.length <= 0) return;
697
+ await this.saveOrUpdatePayments(accountsToSync);
698
+ });
699
+ }
700
+ async scheduled(controller) {
701
+ if (controller.cron === this.env.CRON_BATCHES_PROCESSING) {
702
+ console.log("Scheduled CRON batches processing");
703
+ const openBatches = await getAllOpenBatchesQuery(this.db);
704
+ if (!openBatches.length) return;
705
+ for (const batch of openBatches) {
706
+ const connectorKey = this.accounts.find(
707
+ (acc) => acc.id === batch.accountId
708
+ )?.connectorKey;
709
+ if (!connectorKey) {
710
+ this.logError({
711
+ message: "Failed to find connector key",
712
+ batch,
713
+ connectorKey
714
+ });
715
+ continue;
716
+ }
717
+ await this.processBatch({
718
+ batch,
719
+ connectorKey
720
+ });
721
+ }
722
+ }
723
+ if (controller.cron === this.env.CRON_PAYMENTS_PROCESSING) {
724
+ console.log("Scheduled CRON payments processing");
725
+ await this.syncAccounts();
726
+ }
727
+ }
728
+ // @action('initiate-bank-connector')
729
+ async initiateBankConnector(input) {
730
+ return this.handleAction(
731
+ { data: input, schema: initiateConnectorInputSchema },
732
+ { successMessage: "Bank connector initiated successfully" },
733
+ async ({ connectorKey }) => {
734
+ this.bankConnector = initiateConnector(
735
+ connectorKey,
736
+ this.env
737
+ );
738
+ await this.env.BANK_KV.put(
739
+ `${connectorKey}:token`,
740
+ "d32a83cf-d352-41f3-96d7-4d2961336ddc"
741
+ //hard-coded finbricks clientId (valid til 03/26)
742
+ );
743
+ const token = await this.env.BANK_KV.get(`${connectorKey}:token`);
744
+ if (!token) throw backendSdk.createInternalError("Provider not authorized");
745
+ await this.bankConnector.authenticate({
746
+ token
747
+ });
748
+ }
749
+ );
750
+ }
751
+ async addPaymentsToBatch({
752
+ paymentsToBatch
753
+ }) {
754
+ return this.handleAction(null, {}, async () => {
755
+ this.logInput({ paymentsToBatch });
756
+ const mappedPayments = paymentsToBatch.map(
757
+ (payment) => ({
758
+ id: backendSdk.uuidv4(),
759
+ refId: payment.refId,
760
+ amount: payment.amount,
761
+ direction: "OUTGOING",
762
+ paymentType: payment.paymentType,
763
+ currency: payment.currency,
764
+ status: "CREATED",
765
+ initiatedAt: /* @__PURE__ */ new Date(),
766
+ processedAt: null,
767
+ vs: payment.vs || null,
768
+ ss: payment.ss || null,
769
+ ks: payment.ks || null,
770
+ message: payment.message || null,
771
+ creditorHolderName: payment.creditorHolderName,
772
+ creditorAccountNumberWithBankCode: payment.creditorAccountNumberWithBankCode,
773
+ creditorIban: payment.creditorIban,
774
+ debtorHolderName: payment.debtorHolderName,
775
+ debtorAccountNumberWithBankCode: payment.debtorAccountNumberWithBankCode,
776
+ debtorIban: payment.debtorIban
777
+ })
778
+ );
779
+ const { supportedPayments, unsupportedPayments } = seperateSupportedPayments(mappedPayments, this.accounts);
780
+ if (unsupportedPayments.length > 0) {
781
+ this.logError({ unsupportedPayments });
782
+ await this.pushToQueue(
783
+ this.env.QUEUE_BUS_QUEUE,
784
+ unsupportedPayments.map((unsupported) => ({
785
+ eventType: "BANK_PAYMENT",
786
+ bankPayment: {
787
+ ...unsupported,
788
+ status: "FAILED",
789
+ statusReason: "UNSUPPORTED_ACCOUNT",
790
+ paymentType: unsupported.paymentType,
791
+ creditorAccountNumberWithBankCode: unsupported.creditorAccountNumberWithBankCode,
792
+ creditorIban: unsupported.creditorIban,
793
+ creditorHolderName: unsupported.creditorHolderName,
794
+ debtorAccountNumberWithBankCode: unsupported.debtorAccountNumberWithBankCode,
795
+ debtorIban: unsupported.debtorIban,
796
+ debtorHolderName: unsupported.debtorHolderName
797
+ },
798
+ metadata: {
799
+ correlationId: backendSdk.uuidv4(),
800
+ timestamp: (/* @__PURE__ */ new Date()).toDateString()
801
+ }
802
+ }))
803
+ );
804
+ }
805
+ for (const acc of this.accounts) {
806
+ const newPayments = supportedPayments.filter(
807
+ (payment) => payment.debtorIban === acc.identification.iban && payment.currency === acc.currency
808
+ );
809
+ if (newPayments.length === 0) {
810
+ continue;
811
+ }
812
+ this.log({
813
+ message: `\u{1F4B0} Processing ${newPayments.length} payments for account ${acc.id} (${acc.identification.iban}, ${acc.currency})`
814
+ });
815
+ const existingBatch = await getOpenBatchByAccountIdQuery(this.db, {
816
+ accountId: acc.id
817
+ });
818
+ if (existingBatch) {
819
+ this.log({
820
+ message: `\u{1F504} Found existing OPEN batch for account ${acc.id}, merging ${existingBatch.payments.length} existing + ${newPayments.length} new payments`
821
+ });
822
+ } else {
823
+ this.log({
824
+ message: `\u2728 Creating new batch for account ${acc.id} with ${newPayments.length} payments`
825
+ });
826
+ }
827
+ const { command } = upsertBatchCommand(this.db, {
828
+ batch: existingBatch ? {
829
+ ...existingBatch,
830
+ payments: [...existingBatch.payments, ...newPayments]
831
+ } : {
832
+ id: backendSdk.uuidv4(),
833
+ authorizationUrls: [],
834
+ accountId: acc.id,
835
+ payments: newPayments,
836
+ status: "OPEN"
837
+ }
838
+ });
839
+ const upsertedBatch = await command.execute();
840
+ this.log({
841
+ message: `\u2705 Batch upserted successfully for account ${acc.id}`
842
+ });
843
+ return upsertedBatch;
844
+ }
845
+ });
846
+ }
847
+ async processBatch({
848
+ batch,
849
+ connectorKey
850
+ }) {
851
+ return this.handleAction(null, {}, async () => {
852
+ this.logInput({ batch, connectorKey });
853
+ await upsertBatchCommand(this.db, {
854
+ batch: {
855
+ ...batch,
856
+ status: "PROCESSING"
857
+ }
858
+ }).command.execute();
859
+ await this.initiateBankConnector({ connectorKey });
860
+ if (!this.bankConnector) {
861
+ throw backendSdk.createInternalError(null, {
862
+ message: `\u274C Failed to initialize ${connectorKey} bank connector`
863
+ });
864
+ }
865
+ this.log({
866
+ message: `\u2705 Bank connector initialized successfully for account ${connectorKey}`
867
+ });
868
+ const preparedBatch = {
869
+ retriedPayments: [],
870
+ newlyPreparedPayments: [],
871
+ failedPayments: []
872
+ };
873
+ for (const payment of batch.payments) {
874
+ const existingPayment = await getPaymentByRefIdQuery(this.db, {
875
+ refId: payment.refId
876
+ });
877
+ if (existingPayment) {
878
+ preparedBatch.retriedPayments.push({
879
+ ...existingPayment,
880
+ status: "PREPARED"
881
+ });
882
+ this.log({
883
+ message: `\u2705 Payment ${existingPayment.id} already exists`
884
+ });
885
+ continue;
886
+ }
887
+ const newlyPreparedPayment = await this.bankConnector.preparePayment(payment);
888
+ if (!newlyPreparedPayment) {
889
+ preparedBatch.failedPayments.push({ ...payment, status: "FAILED" });
890
+ continue;
891
+ }
892
+ preparedBatch.newlyPreparedPayments.push(newlyPreparedPayment);
893
+ }
894
+ await Promise.all([
895
+ [
896
+ ...preparedBatch.failedPayments,
897
+ ...preparedBatch.newlyPreparedPayments
898
+ ].map(
899
+ (p) => createPaymentCommand(this.db, { payment: p }).command.execute()
900
+ )
901
+ ]);
902
+ const { authorizationUrls, payments, metadata } = await this.bankConnector.initiateBatchFromPayments({
903
+ payments: [
904
+ ...preparedBatch.newlyPreparedPayments,
905
+ ...preparedBatch.retriedPayments
906
+ ]
907
+ });
908
+ if (!authorizationUrls) {
909
+ this.logError({ message: "Failed to retrieve signing URI" });
910
+ return;
911
+ }
912
+ const { command: upsertBatch } = upsertBatchCommand(this.db, {
913
+ batch: {
914
+ ...batch,
915
+ payments,
916
+ metadata,
917
+ status: "READY_TO_SIGN",
918
+ authorizationUrls,
919
+ batchPaymentInitiatedAt: /* @__PURE__ */ new Date()
920
+ }
921
+ });
922
+ const updatePayments = payments.map(
923
+ (payment) => updatePaymentCommand(this.db, {
924
+ payment: { ...payment, status: "INITIALIZED" }
925
+ }).command
926
+ );
927
+ await this.db.batch([upsertBatch, ...updatePayments]);
928
+ this.log({
929
+ message: "Authorization Urls for batch to create",
930
+ authorizationUrl: authorizationUrls[0]
931
+ });
932
+ await this.pushToQueue(this.env.NOTIFICATIONS_QUEUE, {
933
+ type: "email",
934
+ payload: {
935
+ email: {
936
+ to: ["petr@develit.io"],
937
+ subject: "Payment Authorization",
938
+ text: authorizationUrls[0] || "No Authorization Url"
939
+ }
940
+ },
941
+ metadata: {
942
+ initiator: {
943
+ service: this.name
944
+ }
945
+ }
946
+ });
947
+ this.logOutput({ message: "Batch successfully processed" });
948
+ });
949
+ }
950
+ async queue(b) {
951
+ await this.handleAction(
952
+ null,
953
+ { successMessage: "Queue handler executed successfully" },
954
+ async () => {
955
+ this.logQueuePull(b);
956
+ const queueHandlerResponse = await this.addPaymentsToBatch({
957
+ paymentsToBatch: b.messages.map(({ body }) => body)
958
+ });
959
+ this.logOutput(queueHandlerResponse);
960
+ }
961
+ );
962
+ }
963
+ async getErsteCodeURI() {
964
+ return this.handleAction(
965
+ null,
966
+ { successMessage: "Erste URI code obtained." },
967
+ async () => {
968
+ this.bankConnector = initiateConnector("ERSTE", this.env);
969
+ const uri = this.bankConnector instanceof db.ErsteConnector ? this.bankConnector.adminCodeCreationURI : "nothing";
970
+ return { uri };
971
+ }
972
+ );
973
+ }
974
+ async saveErsteCode(input) {
975
+ return this.handleAction(
976
+ { data: input, schema: saveErsteCodeInputSchema },
977
+ { successMessage: "Erste code saved." },
978
+ async ({ code }) => {
979
+ this.bankConnector = initiateConnector("ERSTE", this.env);
980
+ const token = await this.bankConnector.authenticate({
981
+ token: code
982
+ });
983
+ await this.env.BANK_KV.put("ERSTE:refreshToken", token);
984
+ }
985
+ );
986
+ }
987
+ async saveOrUpdatePayments(accounts) {
988
+ const { setLastSync } = useSync(this.env.BANK_KV);
989
+ return this.handleAction(null, {}, async () => {
990
+ const allFetchedPayments = [];
991
+ const connectorKeys = new Set(
992
+ accounts.map(({ connectorKey }) => connectorKey)
993
+ );
994
+ for (const connectorKey of connectorKeys) {
995
+ await this.initiateBankConnector({ connectorKey });
996
+ for (const account of accounts.filter(
997
+ (acc) => acc.connectorKey === connectorKey
998
+ )) {
999
+ const { lastSyncAt } = account;
1000
+ const payments = await this.bankConnector.getAllAccountPayments({
1001
+ db: this.db,
1002
+ env: this.env.ENVIRONMENT,
1003
+ account,
1004
+ lastSync: lastSyncAt
1005
+ });
1006
+ if (!payments || payments.length === 0) continue;
1007
+ this.log(payments);
1008
+ payments.forEach((payment) => {
1009
+ payment.direction = db.getPaymentDirection(
1010
+ payment,
1011
+ account.identification.iban
1012
+ );
1013
+ });
1014
+ allFetchedPayments.push(...payments);
1015
+ }
1016
+ }
1017
+ if (allFetchedPayments.length < 1) return;
1018
+ const bankRefIds = allFetchedPayments.map((payment) => payment.bankRefId).filter(Boolean);
1019
+ const alreadyExistingPayments = await getPaymentsByBankRefIdsQuery(
1020
+ this.db,
1021
+ { ids: bankRefIds }
1022
+ );
1023
+ const paymentsCommands = allFetchedPayments.map((payment) => {
1024
+ const isAlreadyExisting = alreadyExistingPayments.some(
1025
+ (existingPayment) => existingPayment.bankRefId === payment.bankRefId
1026
+ );
1027
+ if (isAlreadyExisting)
1028
+ return updatePaymentCommand(this.db, { payment }).command;
1029
+ return createPaymentCommand(this.db, { payment }).command;
1030
+ });
1031
+ await this.db.batch([paymentsCommands[0], ...paymentsCommands.slice(1)]);
1032
+ console.log("FETCHEDPAYMANETS TO SYNC", allFetchedPayments.length);
1033
+ for (const account of accounts) {
1034
+ const paymentsForAccount = allFetchedPayments.filter(
1035
+ (payment) => (payment.direction === "OUTGOING" ? payment.debtorIban : payment.creditorIban) === account.identification.iban
1036
+ );
1037
+ let lastSyncPayment;
1038
+ lastSyncPayment = paymentsForAccount.filter(({ status }) => status !== "COMPLETED").sort(
1039
+ (a, b) => (a.createdAt?.getTime() || 0) - (b.createdAt?.getTime() || 0)
1040
+ )[0];
1041
+ if (!lastSyncPayment) {
1042
+ lastSyncPayment = paymentsForAccount.sort(
1043
+ (a, b) => (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0)
1044
+ )[0];
1045
+ }
1046
+ if (lastSyncPayment.createdAt) {
1047
+ await setLastSync({
1048
+ account,
1049
+ log: {
1050
+ lastSyncedAt: lastSyncPayment.createdAt
1051
+ }
1052
+ });
1053
+ }
1054
+ }
1055
+ await this.pushToQueue(
1056
+ this.env.QUEUE_BUS_QUEUE,
1057
+ allFetchedPayments.map((payment) => ({
1058
+ eventType: "BANK_PAYMENT",
1059
+ bankPayment: payment,
1060
+ metadata: {
1061
+ correlationId: backendSdk.uuidv4(),
1062
+ timestamp: (/* @__PURE__ */ new Date()).toDateString()
1063
+ }
1064
+ }))
1065
+ );
1066
+ });
1067
+ }
1068
+ async simulateDeposit(input) {
1069
+ return this.handleAction(
1070
+ { data: input, schema: simulateDepositInputSchema },
1071
+ { successMessage: "Deposit saved." },
1072
+ async ({
1073
+ amount,
1074
+ currency,
1075
+ bankingVs,
1076
+ message,
1077
+ creditorIban,
1078
+ creditorHolderName,
1079
+ debtorIban,
1080
+ debtorHolderName
1081
+ }) => {
1082
+ const payment = {
1083
+ id: backendSdk.uuidv4(),
1084
+ amount,
1085
+ direction: "INCOMING",
1086
+ paymentType: "DOMESTIC",
1087
+ currency,
1088
+ status: "COMPLETED",
1089
+ batchId: backendSdk.uuidv4(),
1090
+ initiatedAt: /* @__PURE__ */ new Date(),
1091
+ processedAt: /* @__PURE__ */ new Date(),
1092
+ vs: bankingVs,
1093
+ message,
1094
+ creditorIban,
1095
+ creditorHolderName,
1096
+ debtorIban,
1097
+ debtorHolderName
1098
+ };
1099
+ const { command } = createPaymentCommand(this.db, { payment });
1100
+ const createdPayment = await command.execute().then(backendSdk.first);
1101
+ this.logQueuePush({ payment, isPaymentExecuted: true });
1102
+ await this.pushToQueue(this.env.QUEUE_BUS_QUEUE, {
1103
+ eventType: "BANK_PAYMENT",
1104
+ bankPayment: createdPayment,
1105
+ metadata: {
1106
+ correlationId: createdPayment.id,
1107
+ timestamp: (/* @__PURE__ */ new Date()).toDateString()
1108
+ }
1109
+ });
1110
+ return createdPayment;
1111
+ }
1112
+ );
1113
+ }
1114
+ async setLastSyncAt(input) {
1115
+ return this.handleAction(
1116
+ { data: input, schema: setLastSyncAtInputSchema },
1117
+ { successMessage: "Last sync set." },
1118
+ async ({ iban, lastSyncedAt }) => {
1119
+ const { setLastSync } = useSync(this.env.BANK_KV);
1120
+ const configAccount = this.accounts.find(
1121
+ (acc) => acc.identification.iban === iban
1122
+ );
1123
+ if (!configAccount) {
1124
+ throw backendSdk.createInternalError(null, {
1125
+ status: 404,
1126
+ message: "Account for this organization does not exist."
1127
+ });
1128
+ }
1129
+ await setLastSync({
1130
+ log: {
1131
+ lastSyncedAt
1132
+ },
1133
+ account: configAccount
1134
+ });
1135
+ return {};
1136
+ }
1137
+ );
1138
+ }
1139
+ async sendPayment(input) {
1140
+ return this.handleAction(
1141
+ { data: input, schema: sendPaymentInputSchema },
1142
+ { successMessage: "Payment queued successfully" },
1143
+ async (data) => {
1144
+ await this.pushToQueue(
1145
+ this.env.PAYMENTS_READY_TO_BATCH_QUEUE,
1146
+ data
1147
+ );
1148
+ }
1149
+ );
1150
+ }
1151
+ async getBankAccounts() {
1152
+ return backendSdk.RPCResponse.ok("Bank accounts retrieved successfully", {
1153
+ data: this.accounts
1154
+ });
1155
+ }
1156
+ async getBatches(input) {
1157
+ return this.handleAction(
1158
+ { data: input, schema: getBatchesInputSchema },
1159
+ { successMessage: "Batches retrieved successfully" },
1160
+ async ({ limit = 100, offset = 0 }) => {
1161
+ const [batchesResult, countResult] = await Promise.all([
1162
+ this.db.select().from(db.tables.batch).limit(limit).offset(offset).orderBy(drizzleOrm.desc(db.tables.batch.createdAt)),
1163
+ this.db.select({ count: drizzleOrm.sql`count(*)`.as("count") }).from(db.tables.batch)
1164
+ ]);
1165
+ const totalCount = Number(countResult[0]?.count) || 0;
1166
+ return {
1167
+ batches: batchesResult,
1168
+ totalCount
1169
+ };
1170
+ }
1171
+ );
1172
+ }
1173
+ }
1174
+ __decorateClass([
1175
+ backendSdk.action("synchronize-accounts")
1176
+ ], BankServiceBase.prototype, "syncAccounts");
1177
+ __decorateClass([
1178
+ backendSdk.action("scheduled")
1179
+ ], BankServiceBase.prototype, "scheduled");
1180
+ __decorateClass([
1181
+ backendSdk.action("add-payments-to-batch")
1182
+ ], BankServiceBase.prototype, "addPaymentsToBatch");
1183
+ __decorateClass([
1184
+ backendSdk.action("process-batch")
1185
+ ], BankServiceBase.prototype, "processBatch");
1186
+ __decorateClass([
1187
+ backendSdk.action("queue-handler")
1188
+ ], BankServiceBase.prototype, "queue");
1189
+ __decorateClass([
1190
+ backendSdk.action("get-erste-code-uri")
1191
+ ], BankServiceBase.prototype, "getErsteCodeURI");
1192
+ __decorateClass([
1193
+ backendSdk.action("save-erste-code")
1194
+ ], BankServiceBase.prototype, "saveErsteCode");
1195
+ __decorateClass([
1196
+ backendSdk.action("save-or-update-payments")
1197
+ ], BankServiceBase.prototype, "saveOrUpdatePayments");
1198
+ __decorateClass([
1199
+ backendSdk.action("simulate-deposit")
1200
+ ], BankServiceBase.prototype, "simulateDeposit");
1201
+ __decorateClass([
1202
+ backendSdk.action("set-last-sync-at")
1203
+ ], BankServiceBase.prototype, "setLastSyncAt");
1204
+ __decorateClass([
1205
+ backendSdk.action("send-payment")
1206
+ ], BankServiceBase.prototype, "sendPayment");
1207
+ __decorateClass([
1208
+ backendSdk.action("get-bank-accounts")
1209
+ ], BankServiceBase.prototype, "getBankAccounts");
1210
+ __decorateClass([
1211
+ backendSdk.action("getBatches")
1212
+ ], BankServiceBase.prototype, "getBatches");
1213
+ function defineBankService() {
1214
+ return class BankService extends BankServiceBase {
1215
+ constructor(ctx, env) {
1216
+ super(ctx, env);
1217
+ }
1218
+ };
1219
+ }
1220
+
1221
+ const BankService = defineBankService();
1222
+
1223
+ exports.default = BankService;
1224
+ exports.defineBankService = defineBankService;