@develit-services/bank 0.3.44 → 0.3.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database/schema.cjs +4 -4
- package/dist/database/schema.d.cts +1 -1
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/database/schema.mjs +4 -4
- package/dist/export/worker.cjs +166 -183
- package/dist/export/worker.d.cts +43 -22
- package/dist/export/worker.d.mts +43 -22
- package/dist/export/worker.d.ts +43 -22
- package/dist/export/worker.mjs +126 -143
- package/dist/export/workflows.cjs +214 -18
- package/dist/export/workflows.mjs +207 -12
- package/dist/export/wrangler.d.cts +1 -2
- package/dist/export/wrangler.d.mts +1 -2
- package/dist/export/wrangler.d.ts +1 -2
- package/dist/shared/{bank.DFRS9oN5.mjs → bank.B1Gpn3ht.mjs} +9 -172
- package/dist/shared/{bank.D8wUOeOK.d.cts → bank.BEL1HIxZ.d.cts} +20 -1
- package/dist/shared/{bank.D8wUOeOK.d.mts → bank.BEL1HIxZ.d.mts} +20 -1
- package/dist/shared/{bank.D8wUOeOK.d.ts → bank.BEL1HIxZ.d.ts} +20 -1
- package/dist/shared/bank.BUEmFxS8.mjs +174 -0
- package/dist/shared/{bank.C335-raZ.cjs → bank.CPYfE-Ei.cjs} +9 -197
- package/dist/shared/bank.CpwLFudl.cjs +198 -0
- package/dist/shared/bank.D-3fzX63.mjs +170 -0
- package/dist/shared/bank.D0a-MZon.cjs +184 -0
- package/dist/shared/{bank.BchnXQDL.d.cts → bank.Dh_H_5rC.d.cts} +0 -1
- package/dist/shared/{bank.BchnXQDL.d.mts → bank.Dh_H_5rC.d.mts} +0 -1
- package/dist/shared/{bank.BchnXQDL.d.ts → bank.Dh_H_5rC.d.ts} +0 -1
- package/dist/types.cjs +14 -14
- package/dist/types.d.cts +3 -6
- package/dist/types.d.mts +3 -6
- package/dist/types.d.ts +3 -6
- package/dist/types.mjs +5 -5
- package/package.json +1 -1
- package/dist/shared/bank.Cj-oB02_.mjs +0 -392
- package/dist/shared/bank.ClRIj2Ma.cjs +0 -11
- package/dist/shared/bank.DhPfZ6b_.mjs +0 -9
- package/dist/shared/bank.DwyrLcKu.cjs +0 -402
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import { t as tables, F as FinbricksConnector, M as MockConnector, E as ErsteConnector } from './bank.DFRS9oN5.mjs';
|
|
2
|
-
import { uuidv4, first } from '@develit-io/backend-sdk';
|
|
3
|
-
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
4
|
-
import { NonRetryableError } from 'cloudflare:workflows';
|
|
5
|
-
import { drizzle } from 'drizzle-orm/d1';
|
|
6
|
-
import { eq } from 'drizzle-orm';
|
|
7
|
-
import 'jose';
|
|
8
|
-
import '@develit-io/general-codes';
|
|
9
|
-
import { createHash } from 'node:crypto';
|
|
10
|
-
import { M as MockCobsConnector } from './bank.DhPfZ6b_.mjs';
|
|
11
|
-
|
|
12
|
-
const createPaymentCommand = (db, { payment }) => {
|
|
13
|
-
return {
|
|
14
|
-
command: db.insert(tables.payment).values({
|
|
15
|
-
...payment,
|
|
16
|
-
creditorIban: payment.creditor.iban,
|
|
17
|
-
debtorIban: payment.debtor.iban
|
|
18
|
-
}).returning()
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const upsertBatchCommand = (db, { batch }) => {
|
|
23
|
-
const id = batch.id || uuidv4();
|
|
24
|
-
const command = db.insert(tables.batch).values({
|
|
25
|
-
...batch,
|
|
26
|
-
id
|
|
27
|
-
}).onConflictDoUpdate({
|
|
28
|
-
target: tables.batch.id,
|
|
29
|
-
set: {
|
|
30
|
-
...batch
|
|
31
|
-
}
|
|
32
|
-
}).returning();
|
|
33
|
-
return {
|
|
34
|
-
id,
|
|
35
|
-
command
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const getAccountByIdQuery = async (db, { accountId }) => {
|
|
40
|
-
return await db.select().from(tables.account).where(eq(tables.account.id, accountId)).get();
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const getBatchByIdQuery = async (db, { batchId }) => {
|
|
44
|
-
return await db.select().from(tables.batch).where(eq(tables.batch.id, batchId)).get();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const getCredentialsByAccountId = async (db, encryptionKey, { accountId }) => {
|
|
48
|
-
const cred = await db.select().from(tables.accountCredentials).where(eq(tables.accountCredentials.accountId, accountId)).get();
|
|
49
|
-
return cred ? {
|
|
50
|
-
...cred,
|
|
51
|
-
value: await decrypt(cred.value, encryptionKey)
|
|
52
|
-
} : void 0;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
class CreditasConnector extends FinbricksConnector {
|
|
56
|
-
constructor(config) {
|
|
57
|
-
super("CREDITAS", config);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
class FioConnector extends FinbricksConnector {
|
|
62
|
-
constructor(config) {
|
|
63
|
-
super("FIO", config);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
class MonetaConnector extends FinbricksConnector {
|
|
68
|
-
constructor(config) {
|
|
69
|
-
super("MONETA", config);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const initiateConnector = ({
|
|
74
|
-
bank,
|
|
75
|
-
env,
|
|
76
|
-
connectedAccounts
|
|
77
|
-
}) => {
|
|
78
|
-
switch (bank) {
|
|
79
|
-
case "ERSTE":
|
|
80
|
-
return new ErsteConnector({
|
|
81
|
-
API_KEY: env.ERSTE_API_KEY,
|
|
82
|
-
CLIENT_ID: env.ERSTE_CLIENT_ID,
|
|
83
|
-
CLIENT_SECRET: env.ERSTE_CLIENT_SECRET,
|
|
84
|
-
REDIRECT_URI: env.REDIRECT_URI,
|
|
85
|
-
AUTH_URI: env.ERSTE_AUTH_URI,
|
|
86
|
-
PAYMENTS_URI: env.ERSTE_PAYMENTS_URI,
|
|
87
|
-
ACCOUNTS_URI: env.ERSTE_ACCOUNTS_URI,
|
|
88
|
-
connectedAccounts
|
|
89
|
-
});
|
|
90
|
-
case "CREDITAS":
|
|
91
|
-
return new CreditasConnector({
|
|
92
|
-
BASE_URI: env.FINBRICKS_BASE_URI,
|
|
93
|
-
MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
|
|
94
|
-
PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM,
|
|
95
|
-
REDIRECT_URI: env.REDIRECT_URI,
|
|
96
|
-
connectedAccounts
|
|
97
|
-
});
|
|
98
|
-
case "MOCK_COBS":
|
|
99
|
-
return new MockCobsConnector({
|
|
100
|
-
BASE_URI: env.FINBRICKS_BASE_URI,
|
|
101
|
-
MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
|
|
102
|
-
PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM,
|
|
103
|
-
REDIRECT_URI: env.REDIRECT_URI,
|
|
104
|
-
connectedAccounts
|
|
105
|
-
});
|
|
106
|
-
case "FIO":
|
|
107
|
-
return new FioConnector({
|
|
108
|
-
BASE_URI: env.FINBRICKS_BASE_URI,
|
|
109
|
-
MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
|
|
110
|
-
PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM,
|
|
111
|
-
REDIRECT_URI: env.REDIRECT_URI,
|
|
112
|
-
connectedAccounts
|
|
113
|
-
});
|
|
114
|
-
case "MONETA":
|
|
115
|
-
return new MonetaConnector({
|
|
116
|
-
BASE_URI: env.FINBRICKS_BASE_URI,
|
|
117
|
-
MERCHANT_ID: env.FINBRICKS_MERCHANT_ID,
|
|
118
|
-
PRIVATE_KEY_PEM: env.FINBRICKS_PRIVATE_KEY_PEM,
|
|
119
|
-
REDIRECT_URI: env.REDIRECT_URI,
|
|
120
|
-
connectedAccounts
|
|
121
|
-
});
|
|
122
|
-
default:
|
|
123
|
-
return new MockConnector();
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
async function importAesKey(base64Key) {
|
|
128
|
-
const raw = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0));
|
|
129
|
-
return await crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, [
|
|
130
|
-
"encrypt",
|
|
131
|
-
"decrypt"
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
134
|
-
async function encrypt(value, key) {
|
|
135
|
-
const encoder = new TextEncoder();
|
|
136
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
137
|
-
const ciphertext = await crypto.subtle.encrypt(
|
|
138
|
-
{ name: "AES-GCM", iv },
|
|
139
|
-
key,
|
|
140
|
-
encoder.encode(value)
|
|
141
|
-
);
|
|
142
|
-
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
143
|
-
combined.set(iv, 0);
|
|
144
|
-
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
145
|
-
return btoa(String.fromCharCode(...combined));
|
|
146
|
-
}
|
|
147
|
-
async function decrypt(base64, key) {
|
|
148
|
-
const raw = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
149
|
-
const iv = raw.slice(0, 12);
|
|
150
|
-
const ciphertext = raw.slice(12);
|
|
151
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
152
|
-
{ name: "AES-GCM", iv },
|
|
153
|
-
key,
|
|
154
|
-
ciphertext
|
|
155
|
-
);
|
|
156
|
-
return new TextDecoder().decode(decrypted);
|
|
157
|
-
}
|
|
158
|
-
function canonicalize(value) {
|
|
159
|
-
if (value === null || typeof value !== "object") return value;
|
|
160
|
-
if (Array.isArray(value)) {
|
|
161
|
-
return value.map(canonicalize);
|
|
162
|
-
}
|
|
163
|
-
const obj = value;
|
|
164
|
-
const sorted = Object.keys(obj).sort().reduce((acc, key) => {
|
|
165
|
-
acc[key] = canonicalize(obj[key]);
|
|
166
|
-
return acc;
|
|
167
|
-
}, {});
|
|
168
|
-
return sorted;
|
|
169
|
-
}
|
|
170
|
-
function checksum(input) {
|
|
171
|
-
const canonical = canonicalize(input);
|
|
172
|
-
const json = JSON.stringify(canonical);
|
|
173
|
-
return createHash("sha256").update(json).digest("hex");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const PROCESS_BATCH_WORKFLOW_EVENTS = {
|
|
177
|
-
batchAuthorized: "batch-authorized"
|
|
178
|
-
};
|
|
179
|
-
function pushToQueue(queue, message) {
|
|
180
|
-
if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
|
|
181
|
-
return queue.sendBatch(
|
|
182
|
-
message.map((m) => ({
|
|
183
|
-
body: m,
|
|
184
|
-
contentType: "v8"
|
|
185
|
-
}))
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
class BankProcessBatch extends WorkflowEntrypoint {
|
|
189
|
-
async run(event, step) {
|
|
190
|
-
const { batchId } = event.payload;
|
|
191
|
-
const db = drizzle(this.env.BANK_D1, { schema: tables });
|
|
192
|
-
const batch = await step.do("load batch", async () => {
|
|
193
|
-
const batch2 = await getBatchByIdQuery(db, { batchId });
|
|
194
|
-
if (!batch2) {
|
|
195
|
-
throw new NonRetryableError(`Batch not found`);
|
|
196
|
-
}
|
|
197
|
-
if (batch2.status === "SIGNED")
|
|
198
|
-
throw new NonRetryableError(`Batch already signed`);
|
|
199
|
-
return batch2;
|
|
200
|
-
});
|
|
201
|
-
await step.do("lock batch", async () => {
|
|
202
|
-
const paymentsChecksum = checksum(batch.payments);
|
|
203
|
-
if (batch.paymentsChecksum && batch.paymentsChecksum !== paymentsChecksum)
|
|
204
|
-
throw new NonRetryableError(
|
|
205
|
-
`Batch payments have been modified externally, manual review needed`
|
|
206
|
-
);
|
|
207
|
-
if (batch.status === "FAILED")
|
|
208
|
-
throw new NonRetryableError(
|
|
209
|
-
`Batch is in FAILED status and cannot be processed, manual review needed`
|
|
210
|
-
);
|
|
211
|
-
if (batch.status === "OPEN" || batch.status === "FULL") {
|
|
212
|
-
const updatedBatch = await upsertBatchCommand(db, {
|
|
213
|
-
batch: {
|
|
214
|
-
...batch,
|
|
215
|
-
status: "PROCESSING",
|
|
216
|
-
paymentsChecksum
|
|
217
|
-
}
|
|
218
|
-
}).command.execute().then(first);
|
|
219
|
-
return { status: updatedBatch.status };
|
|
220
|
-
}
|
|
221
|
-
return { status: batch.status };
|
|
222
|
-
});
|
|
223
|
-
const preparedPayments = await step.do(
|
|
224
|
-
"prepare payments",
|
|
225
|
-
{
|
|
226
|
-
retries: { limit: 5, delay: 2e3, backoff: "constant" },
|
|
227
|
-
timeout: "30 seconds"
|
|
228
|
-
},
|
|
229
|
-
async () => {
|
|
230
|
-
const account = await getAccountByIdQuery(db, {
|
|
231
|
-
accountId: batch.accountId
|
|
232
|
-
});
|
|
233
|
-
if (!account) {
|
|
234
|
-
throw new NonRetryableError(`Account not found: ${batch.accountId}`);
|
|
235
|
-
}
|
|
236
|
-
const encryptionKey = await importAesKey(this.env.ENCRYPTION_KEY);
|
|
237
|
-
const credentials = await getCredentialsByAccountId(db, encryptionKey, {
|
|
238
|
-
accountId: account.id
|
|
239
|
-
});
|
|
240
|
-
if (!credentials) {
|
|
241
|
-
throw new NonRetryableError(
|
|
242
|
-
`No credentials found for account: ${account.id}`
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
if (credentials.expiresAt < /* @__PURE__ */ new Date()) {
|
|
246
|
-
throw new NonRetryableError(
|
|
247
|
-
`Credentials have expired for account: ${account.id}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
const connector = initiateConnector({
|
|
251
|
-
env: this.env,
|
|
252
|
-
bank: account.connectorKey,
|
|
253
|
-
connectedAccounts: [
|
|
254
|
-
{
|
|
255
|
-
...account,
|
|
256
|
-
iban: account.iban,
|
|
257
|
-
token: credentials.value
|
|
258
|
-
}
|
|
259
|
-
]
|
|
260
|
-
});
|
|
261
|
-
const preparedPayments2 = [];
|
|
262
|
-
for (const payment of batch.payments) {
|
|
263
|
-
const prepared = await connector.preparePayment(payment);
|
|
264
|
-
if (prepared) {
|
|
265
|
-
preparedPayments2.push(prepared);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return preparedPayments2;
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
const batchResult = await step.do(
|
|
272
|
-
"initiate batch from payments",
|
|
273
|
-
{
|
|
274
|
-
retries: { limit: 5, delay: 2e3, backoff: "constant" },
|
|
275
|
-
timeout: "30 seconds"
|
|
276
|
-
},
|
|
277
|
-
async () => {
|
|
278
|
-
if (batch.batchPaymentInitiatedAt) {
|
|
279
|
-
return {
|
|
280
|
-
authorizationUrls: batch.authorizationUrls,
|
|
281
|
-
initializedPayments: preparedPayments.map((p) => ({
|
|
282
|
-
...p,
|
|
283
|
-
status: "INITIALIZED"
|
|
284
|
-
})),
|
|
285
|
-
metadata: batch.metadata
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
const account = await getAccountByIdQuery(db, {
|
|
289
|
-
accountId: batch.accountId
|
|
290
|
-
});
|
|
291
|
-
if (!account) {
|
|
292
|
-
throw new NonRetryableError(`Account not found: ${batch.accountId}`);
|
|
293
|
-
}
|
|
294
|
-
const encryptionKey = await importAesKey(this.env.ENCRYPTION_KEY);
|
|
295
|
-
const credentials = await getCredentialsByAccountId(db, encryptionKey, {
|
|
296
|
-
accountId: account.id
|
|
297
|
-
});
|
|
298
|
-
if (!credentials) {
|
|
299
|
-
throw new NonRetryableError(
|
|
300
|
-
`No credentials found for account: ${account.id}`
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
if (credentials.expiresAt < /* @__PURE__ */ new Date()) {
|
|
304
|
-
throw new NonRetryableError(
|
|
305
|
-
`Credentials have expired for account: ${account.id}`
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
const connector = initiateConnector({
|
|
309
|
-
env: this.env,
|
|
310
|
-
bank: account.connectorKey,
|
|
311
|
-
connectedAccounts: [
|
|
312
|
-
{
|
|
313
|
-
...account,
|
|
314
|
-
iban: account.iban,
|
|
315
|
-
token: credentials.value
|
|
316
|
-
}
|
|
317
|
-
]
|
|
318
|
-
});
|
|
319
|
-
const result = await connector.initiateBatchFromPayments({
|
|
320
|
-
batchId: batch.id,
|
|
321
|
-
payments: preparedPayments
|
|
322
|
-
});
|
|
323
|
-
const {
|
|
324
|
-
authorizationUrls,
|
|
325
|
-
payments: initializedPayments,
|
|
326
|
-
metadata
|
|
327
|
-
} = result;
|
|
328
|
-
if (!authorizationUrls || authorizationUrls.length === 0) {
|
|
329
|
-
throw new Error("Failed to retrieve authorization URLs");
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
authorizationUrls,
|
|
333
|
-
initializedPayments,
|
|
334
|
-
metadata
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
);
|
|
338
|
-
await step.do("update batch and payments to initialized", async () => {
|
|
339
|
-
const upsertBatch = upsertBatchCommand(db, {
|
|
340
|
-
batch: {
|
|
341
|
-
...batch,
|
|
342
|
-
authorizationUrls: batchResult.authorizationUrls,
|
|
343
|
-
metadata: batchResult.metadata,
|
|
344
|
-
status: "READY_TO_SIGN",
|
|
345
|
-
batchPaymentInitiatedAt: /* @__PURE__ */ new Date()
|
|
346
|
-
}
|
|
347
|
-
}).command;
|
|
348
|
-
await upsertBatch.execute();
|
|
349
|
-
return {
|
|
350
|
-
status: "READY_TO_SIGN",
|
|
351
|
-
paymentsInitialized: batchResult.initializedPayments.length
|
|
352
|
-
};
|
|
353
|
-
});
|
|
354
|
-
await step.do("send authorization email", async () => {
|
|
355
|
-
await pushToQueue(this.env.NOTIFICATIONS_QUEUE, {
|
|
356
|
-
type: "email",
|
|
357
|
-
payload: {
|
|
358
|
-
email: {
|
|
359
|
-
to: [this.env.BANK_AUTH_RECIPIENT],
|
|
360
|
-
subject: "Payment Authorization",
|
|
361
|
-
text: Array.isArray(batchResult.authorizationUrls) ? batchResult.authorizationUrls[0] || "No Authorization URL" : batchResult.authorizationUrls || "No Authorization URL"
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
metadata: {
|
|
365
|
-
initiator: {
|
|
366
|
-
service: "bank"
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
return { emailSent: true };
|
|
371
|
-
});
|
|
372
|
-
const authorizationEvent = await step.waitForEvent(
|
|
373
|
-
"wait for batch authorization",
|
|
374
|
-
{
|
|
375
|
-
type: "batch-authorized",
|
|
376
|
-
timeout: "3 days"
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
await step.do("handle authorization result", async () => {
|
|
380
|
-
const authPayload = authorizationEvent.payload;
|
|
381
|
-
const updatedBatch = await upsertBatchCommand(db, {
|
|
382
|
-
batch: {
|
|
383
|
-
...batch,
|
|
384
|
-
status: authPayload.authorized ? "SIGNED" : "SIGNATURE_FAILED"
|
|
385
|
-
}
|
|
386
|
-
}).command.execute().then(first);
|
|
387
|
-
return { updatedBatch };
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export { BankProcessBatch as B, PROCESS_BATCH_WORKFLOW_EVENTS as P, getCredentialsByAccountId as a, initiateConnector as b, createPaymentCommand as c, encrypt as e, getAccountByIdQuery as g, importAesKey as i, upsertBatchCommand as u };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const database_schema = require('./bank.C335-raZ.cjs');
|
|
4
|
-
|
|
5
|
-
class MockCobsConnector extends database_schema.FinbricksConnector {
|
|
6
|
-
constructor(config) {
|
|
7
|
-
super("MOCK_COBS", config);
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
exports.MockCobsConnector = MockCobsConnector;
|