@festapp/banksync 0.2.2 → 0.2.4
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/README.md +14 -0
- package/dist/{chunk-N7BVOSFN.cjs → chunk-5SYCSFXK.cjs} +19 -8
- package/dist/{chunk-3WM2GGFL.js → chunk-JFSJO3OJ.js} +17 -6
- package/dist/cloudflare.cjs +24 -16
- package/dist/cloudflare.js +10 -2
- package/dist/index.cjs +4 -2
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -281,3 +281,17 @@ polling, or authorize replay of another consumer's history.
|
|
|
281
281
|
Canonical v2 transactions preserve bank-provided VS and payer reference without inferring an order identifier. Each consumer (Festapp, Mendelio, or another application) owns payment matching and business rules. The existing v1 RF-to-VS projection remains only as an explicit backwards-compatible adapter.
|
|
282
282
|
|
|
283
283
|
Successful schema 11 Fio imports immediately sweep the canonical webhook outbox into the delivery queue, including retryable dispatch failures from earlier imports. Periodic reconciliation remains recovery; consumers do not wait for its next tick in the normal path.
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
### Fio token authorization failures
|
|
287
|
+
|
|
288
|
+
A newly created Fio token must also be authorized in Internetbanking; creating
|
|
289
|
+
or copying it alone does not activate it. Fio documents HTTP 500 as a nonexistent
|
|
290
|
+
or inactive token ([Fio API documentation](https://www.fio.cz/docs/cz/API_Bankovnictvi.pdf),
|
|
291
|
+
section 8). An observed inactive token returned that response after 30.4 seconds.
|
|
292
|
+
The client allows 55 seconds; an egress proxy should allow at least 45 seconds
|
|
293
|
+
upstream and mark forwarded responses with `x-fio-upstream-status`. Only a direct
|
|
294
|
+
or explicitly forwarded bank 500 becomes `fio_token_invalid_or_inactive`.
|
|
295
|
+
Transport timeouts and proxy failures remain transient, not token diagnoses.
|
|
296
|
+
Manual sync returns HTTP 422 with that stable error code, which is also stored
|
|
297
|
+
in `api_last_error` and exposed by the owner-scoped account status endpoint.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/normalize.ts
|
|
2
2
|
var ISO_ALIASES = {
|
|
3
3
|
"k\u010D": "CZK",
|
|
4
4
|
"kc": "CZK",
|
|
@@ -568,6 +568,13 @@ var FioApiError = class extends Error {
|
|
|
568
568
|
}
|
|
569
569
|
|
|
570
570
|
};
|
|
571
|
+
var FioTokenInvalidOrInactive = (_class = class extends FioApiError {
|
|
572
|
+
__init() {this.code = "fio_token_invalid_or_inactive"}
|
|
573
|
+
constructor() {
|
|
574
|
+
super("fio_token_invalid_or_inactive", 500);_class.prototype.__init.call(this);;
|
|
575
|
+
this.name = "FioTokenInvalidOrInactive";
|
|
576
|
+
}
|
|
577
|
+
}, _class);
|
|
571
578
|
var FioRateLimited = class extends FioApiError {
|
|
572
579
|
constructor(status, retryAfterS) {
|
|
573
580
|
super(`Fio API ${status}`, status);
|
|
@@ -591,10 +598,10 @@ async function fioRequest(op, token, directUrl, proxy, date, toDate) {
|
|
|
591
598
|
method: "POST",
|
|
592
599
|
headers: { "content-type": "application/json", "x-fio-proxy-secret": proxy.secret },
|
|
593
600
|
body: JSON.stringify(date === void 0 ? { op, token } : { op, token, date, ...toDate ? { toDate } : {} }),
|
|
594
|
-
signal: AbortSignal.timeout(
|
|
601
|
+
signal: AbortSignal.timeout(55e3)
|
|
595
602
|
});
|
|
596
603
|
}
|
|
597
|
-
return fetch(directUrl, { signal: AbortSignal.timeout(
|
|
604
|
+
return fetch(directUrl, { signal: AbortSignal.timeout(55e3) });
|
|
598
605
|
}
|
|
599
606
|
function retryAfterSeconds(headers) {
|
|
600
607
|
const raw = headers.get("Retry-After");
|
|
@@ -602,7 +609,10 @@ function retryAfterSeconds(headers) {
|
|
|
602
609
|
const n = Number.parseInt(raw, 10);
|
|
603
610
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
604
611
|
}
|
|
605
|
-
async function ensureFioResponse(res) {
|
|
612
|
+
async function ensureFioResponse(res, proxy) {
|
|
613
|
+
if (res.status === 500 && !proxy || proxy && [422, 500].includes(res.status) && res.headers.get("x-fio-upstream-status") === "500") {
|
|
614
|
+
throw new FioTokenInvalidOrInactive();
|
|
615
|
+
}
|
|
606
616
|
if (res.status === 429 || res.status === 409) {
|
|
607
617
|
throw new FioRateLimited(res.status, retryAfterSeconds(res.headers));
|
|
608
618
|
}
|
|
@@ -615,7 +625,7 @@ async function ensureFioResponse(res) {
|
|
|
615
625
|
}
|
|
616
626
|
async function fetchNewTransactions(token, proxy) {
|
|
617
627
|
const res = await fioRequest("transactions", token, `${endpoint("last", token)}/transactions.json`, proxy);
|
|
618
|
-
await ensureFioResponse(res);
|
|
628
|
+
await ensureFioResponse(res, proxy);
|
|
619
629
|
const json = await res.json();
|
|
620
630
|
const transactions = _nullishCoalesce(_optionalChain([json, 'access', _4 => _4.accountStatement, 'optionalAccess', _5 => _5.transactionList, 'optionalAccess', _6 => _6.transaction]), () => ( []));
|
|
621
631
|
return Array.isArray(transactions) ? transactions : [transactions];
|
|
@@ -625,7 +635,7 @@ async function setFioPointer(token, yyyyMmDd, proxy) {
|
|
|
625
635
|
throw new Error("invalid_fio_pointer_date");
|
|
626
636
|
}
|
|
627
637
|
const res = await fioRequest("set-last-date", token, `${endpoint("set-last-date", token)}/${yyyyMmDd}/`, proxy, yyyyMmDd);
|
|
628
|
-
await ensureFioResponse(res);
|
|
638
|
+
await ensureFioResponse(res, proxy);
|
|
629
639
|
}
|
|
630
640
|
function column(raw, idx) {
|
|
631
641
|
const value = _optionalChain([raw, 'access', _7 => _7[`column${idx}`], 'optionalAccess', _8 => _8.value]);
|
|
@@ -702,7 +712,7 @@ async function fetchFioStatement(token, from, to, proxy) {
|
|
|
702
712
|
for (const date of [from, to]) if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || !Number.isFinite(Date.parse(date))) throw new Error("invalid_statement_window");
|
|
703
713
|
if (from > to || Date.parse(to) - Date.parse(from) > 90 * 864e5) throw new Error("unbounded_statement_window");
|
|
704
714
|
const response = await fioRequest("periods", token, `${endpoint("periods", token)}/${from}/${to}/transactions.json`, proxy, from, to);
|
|
705
|
-
await ensureFioResponse(response);
|
|
715
|
+
await ensureFioResponse(response, proxy);
|
|
706
716
|
const document = await response.json();
|
|
707
717
|
const statement = document.accountStatement;
|
|
708
718
|
if (!_optionalChain([statement, 'optionalAccess', _9 => _9.info])) throw new Error("missing_statement_info");
|
|
@@ -732,4 +742,5 @@ async function fetchFioStatement(token, from, to, proxy) {
|
|
|
732
742
|
|
|
733
743
|
|
|
734
744
|
|
|
735
|
-
|
|
745
|
+
|
|
746
|
+
exports.normalizeCurrency = normalizeCurrency; exports.toCents = toCents; exports.decimalToCents = decimalToCents; exports.BANK_CODES = BANK_CODES; exports.detectProvider = detectProvider; exports.parseEmail = parseEmail; exports.encodeRf = encodeRf; exports.decodeRf = decodeRf; exports.resolveVariableSymbol = resolveVariableSymbol; exports.extractReferenceCandidates = extractReferenceCandidates; exports.buildWebhookEnvelope = buildWebhookEnvelope; exports.signWebhook = signWebhook; exports.WebhookVerificationError = WebhookVerificationError; exports.verifyWebhook = verifyWebhook; exports.FioApiError = FioApiError; exports.FioTokenInvalidOrInactive = FioTokenInvalidOrInactive; exports.FioRateLimited = FioRateLimited; exports.FioTransientFailure = FioTransientFailure; exports.fetchNewTransactions = fetchNewTransactions; exports.setFioPointer = setFioPointer; exports.mapFioTransaction = mapFioTransaction; exports.fetchFioStatement = fetchFioStatement;
|
|
@@ -568,6 +568,13 @@ var FioApiError = class extends Error {
|
|
|
568
568
|
}
|
|
569
569
|
status;
|
|
570
570
|
};
|
|
571
|
+
var FioTokenInvalidOrInactive = class extends FioApiError {
|
|
572
|
+
code = "fio_token_invalid_or_inactive";
|
|
573
|
+
constructor() {
|
|
574
|
+
super("fio_token_invalid_or_inactive", 500);
|
|
575
|
+
this.name = "FioTokenInvalidOrInactive";
|
|
576
|
+
}
|
|
577
|
+
};
|
|
571
578
|
var FioRateLimited = class extends FioApiError {
|
|
572
579
|
constructor(status, retryAfterS) {
|
|
573
580
|
super(`Fio API ${status}`, status);
|
|
@@ -591,10 +598,10 @@ async function fioRequest(op, token, directUrl, proxy, date, toDate) {
|
|
|
591
598
|
method: "POST",
|
|
592
599
|
headers: { "content-type": "application/json", "x-fio-proxy-secret": proxy.secret },
|
|
593
600
|
body: JSON.stringify(date === void 0 ? { op, token } : { op, token, date, ...toDate ? { toDate } : {} }),
|
|
594
|
-
signal: AbortSignal.timeout(
|
|
601
|
+
signal: AbortSignal.timeout(55e3)
|
|
595
602
|
});
|
|
596
603
|
}
|
|
597
|
-
return fetch(directUrl, { signal: AbortSignal.timeout(
|
|
604
|
+
return fetch(directUrl, { signal: AbortSignal.timeout(55e3) });
|
|
598
605
|
}
|
|
599
606
|
function retryAfterSeconds(headers) {
|
|
600
607
|
const raw = headers.get("Retry-After");
|
|
@@ -602,7 +609,10 @@ function retryAfterSeconds(headers) {
|
|
|
602
609
|
const n = Number.parseInt(raw, 10);
|
|
603
610
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
604
611
|
}
|
|
605
|
-
async function ensureFioResponse(res) {
|
|
612
|
+
async function ensureFioResponse(res, proxy) {
|
|
613
|
+
if (res.status === 500 && !proxy || proxy && [422, 500].includes(res.status) && res.headers.get("x-fio-upstream-status") === "500") {
|
|
614
|
+
throw new FioTokenInvalidOrInactive();
|
|
615
|
+
}
|
|
606
616
|
if (res.status === 429 || res.status === 409) {
|
|
607
617
|
throw new FioRateLimited(res.status, retryAfterSeconds(res.headers));
|
|
608
618
|
}
|
|
@@ -615,7 +625,7 @@ async function ensureFioResponse(res) {
|
|
|
615
625
|
}
|
|
616
626
|
async function fetchNewTransactions(token, proxy) {
|
|
617
627
|
const res = await fioRequest("transactions", token, `${endpoint("last", token)}/transactions.json`, proxy);
|
|
618
|
-
await ensureFioResponse(res);
|
|
628
|
+
await ensureFioResponse(res, proxy);
|
|
619
629
|
const json = await res.json();
|
|
620
630
|
const transactions = json.accountStatement?.transactionList?.transaction ?? [];
|
|
621
631
|
return Array.isArray(transactions) ? transactions : [transactions];
|
|
@@ -625,7 +635,7 @@ async function setFioPointer(token, yyyyMmDd, proxy) {
|
|
|
625
635
|
throw new Error("invalid_fio_pointer_date");
|
|
626
636
|
}
|
|
627
637
|
const res = await fioRequest("set-last-date", token, `${endpoint("set-last-date", token)}/${yyyyMmDd}/`, proxy, yyyyMmDd);
|
|
628
|
-
await ensureFioResponse(res);
|
|
638
|
+
await ensureFioResponse(res, proxy);
|
|
629
639
|
}
|
|
630
640
|
function column(raw, idx) {
|
|
631
641
|
const value = raw[`column${idx}`]?.value;
|
|
@@ -702,7 +712,7 @@ async function fetchFioStatement(token, from, to, proxy) {
|
|
|
702
712
|
for (const date of [from, to]) if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || !Number.isFinite(Date.parse(date))) throw new Error("invalid_statement_window");
|
|
703
713
|
if (from > to || Date.parse(to) - Date.parse(from) > 90 * 864e5) throw new Error("unbounded_statement_window");
|
|
704
714
|
const response = await fioRequest("periods", token, `${endpoint("periods", token)}/${from}/${to}/transactions.json`, proxy, from, to);
|
|
705
|
-
await ensureFioResponse(response);
|
|
715
|
+
await ensureFioResponse(response, proxy);
|
|
706
716
|
const document = await response.json();
|
|
707
717
|
const statement = document.accountStatement;
|
|
708
718
|
if (!statement?.info) throw new Error("missing_statement_info");
|
|
@@ -726,6 +736,7 @@ export {
|
|
|
726
736
|
WebhookVerificationError,
|
|
727
737
|
verifyWebhook,
|
|
728
738
|
FioApiError,
|
|
739
|
+
FioTokenInvalidOrInactive,
|
|
729
740
|
FioRateLimited,
|
|
730
741
|
FioTransientFailure,
|
|
731
742
|
fetchNewTransactions,
|
package/dist/cloudflare.cjs
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
var _chunk5SYCSFXKcjs = require('./chunk-5SYCSFXK.cjs');
|
|
14
15
|
|
|
15
16
|
// src/mime.ts
|
|
16
17
|
var _postalmime = require('postal-mime'); var _postalmime2 = _interopRequireDefault(_postalmime);
|
|
@@ -67,13 +68,13 @@ function classifyEmail(extracted, identity, account, allowlist) {
|
|
|
67
68
|
if (account.ingest_mode === "api") {
|
|
68
69
|
return { kind: "reject", reason: "email_ingest_disabled", bankAccountId: account.id, received: false };
|
|
69
70
|
}
|
|
70
|
-
const provider =
|
|
71
|
+
const provider = _chunk5SYCSFXKcjs.detectProvider.call(void 0, extracted.text, account.account_number);
|
|
71
72
|
if (provider === "unknown") {
|
|
72
73
|
return { kind: "reject", reason: `unknown_provider: ${account.account_number}`, bankAccountId: account.id, received: true };
|
|
73
74
|
}
|
|
74
75
|
let parsed;
|
|
75
76
|
try {
|
|
76
|
-
parsed =
|
|
77
|
+
parsed = _chunk5SYCSFXKcjs.parseEmail.call(void 0, extracted.text, provider);
|
|
77
78
|
} catch (err) {
|
|
78
79
|
return { kind: "reject", reason: `${err}`.replace(/^Error:\s*/, ""), bankAccountId: account.id, received: true };
|
|
79
80
|
}
|
|
@@ -610,7 +611,7 @@ async function insertTransaction(db, args) {
|
|
|
610
611
|
const v2 = _optionalChain([schema, 'optionalAccess', _16 => _16.value]) === "11";
|
|
611
612
|
const p = {
|
|
612
613
|
...args.payload,
|
|
613
|
-
vs: v2 ? _nullishCoalesce(_nullishCoalesce(args.payload.raw_vs, () => ( args.payload.vs)), () => ( null)) :
|
|
614
|
+
vs: v2 ? _nullishCoalesce(_nullishCoalesce(args.payload.raw_vs, () => ( args.payload.vs)), () => ( null)) : _chunk5SYCSFXKcjs.resolveVariableSymbol.call(void 0, args.payload)
|
|
614
615
|
};
|
|
615
616
|
if (p.transaction_id != null && p.source === "fio_api") {
|
|
616
617
|
const hit = await db.prepare(SQL_FIND_DUPLICATE_FIO).bind(args.bank_account_id, p.transaction_id).first();
|
|
@@ -1227,7 +1228,7 @@ async function ensureDeliveryJobs(db, transactionId, cutover) {
|
|
|
1227
1228
|
for (const row of candidates.results) {
|
|
1228
1229
|
const { pairing_code, consumer_app_id, event_version, created_at: _createdAt, ...transaction } = row;
|
|
1229
1230
|
const deliveryId = _ulid.ulid.call(void 0, );
|
|
1230
|
-
const envelope =
|
|
1231
|
+
const envelope = _chunk5SYCSFXKcjs.buildWebhookEnvelope.call(void 0, {
|
|
1231
1232
|
delivery_id: deliveryId,
|
|
1232
1233
|
pairing_code: row.pairing_code,
|
|
1233
1234
|
transaction,
|
|
@@ -1549,7 +1550,7 @@ async function httpResult(response, deliveryId, usedPrevSecret, primaryStatus) {
|
|
|
1549
1550
|
return "receipt" in parsed ? { kind: "http", httpStatus: response.status, usedPrevSecret, primaryStatus, receipt: parsed.receipt } : { kind: "http", httpStatus: response.status, usedPrevSecret, primaryStatus, receiptError: parsed.error };
|
|
1550
1551
|
}
|
|
1551
1552
|
async function postSigned(url2, envelope, secret) {
|
|
1552
|
-
const signed = await
|
|
1553
|
+
const signed = await _chunk5SYCSFXKcjs.signWebhook.call(void 0, { envelope, secret });
|
|
1553
1554
|
const controller = new AbortController();
|
|
1554
1555
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1555
1556
|
try {
|
|
@@ -2707,7 +2708,7 @@ async function recoverBankAccount(db, account, env, proxy) {
|
|
|
2707
2708
|
let statement;
|
|
2708
2709
|
if (batch.cipher !== null && batch.key_version !== null) statement = JSON.parse(await decryptSecret(batch.cipher, batch.key_version, env));
|
|
2709
2710
|
else {
|
|
2710
|
-
statement = await
|
|
2711
|
+
statement = await _chunk5SYCSFXKcjs.fetchFioStatement.call(void 0, credential, batch.from_date, batch.to_date, proxy);
|
|
2711
2712
|
await renew();
|
|
2712
2713
|
const encrypted = await encryptSecret(JSON.stringify(statement), env);
|
|
2713
2714
|
await db.prepare(`UPDATE bank_recovery_batches SET cipher=?,key_version=?,state='spooled' WHERE id=?`).bind(encrypted.cipher, encrypted.keyVersion, batch.id).run();
|
|
@@ -2719,7 +2720,7 @@ async function recoverBankAccount(db, account, env, proxy) {
|
|
|
2719
2720
|
let inserted = 0, skipped = 0;
|
|
2720
2721
|
for (const raw of statement.transactions) {
|
|
2721
2722
|
await renew();
|
|
2722
|
-
const mapped =
|
|
2723
|
+
const mapped = _chunk5SYCSFXKcjs.mapFioTransaction.call(void 0, raw);
|
|
2723
2724
|
if (!mapped || mapped.identity_kind !== "movement") throw new Error("fio_unverified_movement");
|
|
2724
2725
|
if (mapped.currency !== String(_nullishCoalesce(_optionalChain([statement, 'access', _63 => _63.info, 'optionalAccess', _64 => _64.currency]), () => ( ""))).toUpperCase()) throw new Error("fio_statement_currency_mismatch");
|
|
2725
2726
|
const result = await insertTransaction(db, { bank_account_id: account.id, payload: mapped });
|
|
@@ -2968,7 +2969,7 @@ async function runBankApiSync(env, account) {
|
|
|
2968
2969
|
const token = await decryptSecret(account.api_token_cipher, account.api_token_key_ver, env);
|
|
2969
2970
|
if (isBackfill && account.api_last_success_at === null) {
|
|
2970
2971
|
await markBankAccountApiFetchStarted(env.DB, account.id);
|
|
2971
|
-
await
|
|
2972
|
+
await _chunk5SYCSFXKcjs.setFioPointer.call(void 0, token, yyyyMmDdDaysAgo(90), fioProxyConfig(env));
|
|
2972
2973
|
await markBankAccountApiPointerSet(env.DB, account.id);
|
|
2973
2974
|
await writeEvent(env.DB, {
|
|
2974
2975
|
event_type: "api_pointer_set",
|
|
@@ -2988,7 +2989,7 @@ async function runBankApiSync(env, account) {
|
|
|
2988
2989
|
};
|
|
2989
2990
|
}
|
|
2990
2991
|
await markBankAccountApiFetchStarted(env.DB, account.id);
|
|
2991
|
-
const raw = await
|
|
2992
|
+
const raw = await _chunk5SYCSFXKcjs.fetchNewTransactions.call(void 0, token, fioProxyConfig(env));
|
|
2992
2993
|
let inserted = 0;
|
|
2993
2994
|
let skippedDuplicate = 0;
|
|
2994
2995
|
let skippedOutgoing = 0;
|
|
@@ -2997,7 +2998,7 @@ async function runBankApiSync(env, account) {
|
|
|
2997
2998
|
for (const item of raw) {
|
|
2998
2999
|
let mapped;
|
|
2999
3000
|
try {
|
|
3000
|
-
mapped =
|
|
3001
|
+
mapped = _chunk5SYCSFXKcjs.mapFioTransaction.call(void 0, item);
|
|
3001
3002
|
} catch (err) {
|
|
3002
3003
|
parseErrors++;
|
|
3003
3004
|
await insertParseLog(env.DB, {
|
|
@@ -3052,8 +3053,8 @@ async function runBankApiSync(env, account) {
|
|
|
3052
3053
|
deferred: false
|
|
3053
3054
|
};
|
|
3054
3055
|
} catch (err) {
|
|
3055
|
-
await markBankAccountApiFetchFailure(env.DB, account.id, String(err));
|
|
3056
|
-
if (err instanceof
|
|
3056
|
+
await markBankAccountApiFetchFailure(env.DB, account.id, err instanceof _chunk5SYCSFXKcjs.FioTokenInvalidOrInactive ? err.code : String(err));
|
|
3057
|
+
if (err instanceof _chunk5SYCSFXKcjs.FioRateLimited) {
|
|
3057
3058
|
await writeEvent(env.DB, {
|
|
3058
3059
|
event_type: "api_rate_limited",
|
|
3059
3060
|
bank_account_id: account.id,
|
|
@@ -3078,11 +3079,11 @@ async function runDueBankApiSyncs(env) {
|
|
|
3078
3079
|
deferred: result.deferred
|
|
3079
3080
|
});
|
|
3080
3081
|
} catch (err) {
|
|
3081
|
-
if (err instanceof
|
|
3082
|
+
if (err instanceof _chunk5SYCSFXKcjs.FioRateLimited) {
|
|
3082
3083
|
log("api_rate_limited", { bank_account_id: account.id, account_type: account.account_type, retry_after_s: err.retryAfterS });
|
|
3083
3084
|
continue;
|
|
3084
3085
|
}
|
|
3085
|
-
if (err instanceof
|
|
3086
|
+
if (err instanceof _chunk5SYCSFXKcjs.FioTransientFailure) {
|
|
3086
3087
|
logError("api_transient_failure", err, { bank_account_id: account.id, account_type: account.account_type });
|
|
3087
3088
|
continue;
|
|
3088
3089
|
}
|
|
@@ -3408,6 +3409,10 @@ async function dispatch(url2, req, env, authCtx, bodyText) {
|
|
|
3408
3409
|
return jsonResponse(accounts);
|
|
3409
3410
|
}
|
|
3410
3411
|
const baMatch = url2.pathname.match(/^\/bank-accounts\/(\d+)$/);
|
|
3412
|
+
if (baMatch && req.method === "GET") {
|
|
3413
|
+
const account = await requireOwnedAccount(env, Number(baMatch[1]), authCtx);
|
|
3414
|
+
return account instanceof Response ? account : jsonResponse(account);
|
|
3415
|
+
}
|
|
3411
3416
|
if (baMatch && req.method === "PUT") {
|
|
3412
3417
|
const id = Number(baMatch[1]);
|
|
3413
3418
|
const existing = await requireOwnedAccount(env, id, authCtx);
|
|
@@ -3546,7 +3551,10 @@ async function dispatch(url2, req, env, authCtx, bodyText) {
|
|
|
3546
3551
|
api_backfill_done: _nullishCoalesce(_optionalChain([updated, 'optionalAccess', _99 => _99.api_backfill_done]), () => ( false))
|
|
3547
3552
|
});
|
|
3548
3553
|
} catch (err) {
|
|
3549
|
-
if (err instanceof
|
|
3554
|
+
if (err instanceof _chunk5SYCSFXKcjs.FioTokenInvalidOrInactive) {
|
|
3555
|
+
return jsonResponse({ error: err.code, bank_http_status: err.status }, 422);
|
|
3556
|
+
}
|
|
3557
|
+
if (err instanceof _chunk5SYCSFXKcjs.FioRateLimited || err instanceof _chunk5SYCSFXKcjs.FioTransientFailure) {
|
|
3550
3558
|
logError("fio_api_manual_sync_failed", err, { bank_account_id: id });
|
|
3551
3559
|
return jsonResponse({ error: "fio_api_transient_failure" }, 503);
|
|
3552
3560
|
}
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FioRateLimited,
|
|
3
|
+
FioTokenInvalidOrInactive,
|
|
3
4
|
FioTransientFailure,
|
|
4
5
|
buildWebhookEnvelope,
|
|
5
6
|
detectProvider,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
resolveVariableSymbol,
|
|
11
12
|
setFioPointer,
|
|
12
13
|
signWebhook
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JFSJO3OJ.js";
|
|
14
15
|
|
|
15
16
|
// src/mime.ts
|
|
16
17
|
import PostalMime from "postal-mime";
|
|
@@ -3052,7 +3053,7 @@ async function runBankApiSync(env, account) {
|
|
|
3052
3053
|
deferred: false
|
|
3053
3054
|
};
|
|
3054
3055
|
} catch (err) {
|
|
3055
|
-
await markBankAccountApiFetchFailure(env.DB, account.id, String(err));
|
|
3056
|
+
await markBankAccountApiFetchFailure(env.DB, account.id, err instanceof FioTokenInvalidOrInactive ? err.code : String(err));
|
|
3056
3057
|
if (err instanceof FioRateLimited) {
|
|
3057
3058
|
await writeEvent(env.DB, {
|
|
3058
3059
|
event_type: "api_rate_limited",
|
|
@@ -3408,6 +3409,10 @@ async function dispatch(url2, req, env, authCtx, bodyText) {
|
|
|
3408
3409
|
return jsonResponse(accounts);
|
|
3409
3410
|
}
|
|
3410
3411
|
const baMatch = url2.pathname.match(/^\/bank-accounts\/(\d+)$/);
|
|
3412
|
+
if (baMatch && req.method === "GET") {
|
|
3413
|
+
const account = await requireOwnedAccount(env, Number(baMatch[1]), authCtx);
|
|
3414
|
+
return account instanceof Response ? account : jsonResponse(account);
|
|
3415
|
+
}
|
|
3411
3416
|
if (baMatch && req.method === "PUT") {
|
|
3412
3417
|
const id = Number(baMatch[1]);
|
|
3413
3418
|
const existing = await requireOwnedAccount(env, id, authCtx);
|
|
@@ -3546,6 +3551,9 @@ async function dispatch(url2, req, env, authCtx, bodyText) {
|
|
|
3546
3551
|
api_backfill_done: updated?.api_backfill_done ?? false
|
|
3547
3552
|
});
|
|
3548
3553
|
} catch (err) {
|
|
3554
|
+
if (err instanceof FioTokenInvalidOrInactive) {
|
|
3555
|
+
return jsonResponse({ error: err.code, bank_http_status: err.status }, 422);
|
|
3556
|
+
}
|
|
3549
3557
|
if (err instanceof FioRateLimited || err instanceof FioTransientFailure) {
|
|
3550
3558
|
logError("fio_api_manual_sync_failed", err, { bank_account_id: id });
|
|
3551
3559
|
return jsonResponse({ error: "fio_api_transient_failure" }, 503);
|
package/dist/index.cjs
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
var _chunkN7BVOSFNcjs = require('./chunk-N7BVOSFN.cjs');
|
|
23
22
|
|
|
23
|
+
var _chunk5SYCSFXKcjs = require('./chunk-5SYCSFXK.cjs');
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
@@ -41,4 +41,6 @@ var _chunkN7BVOSFNcjs = require('./chunk-N7BVOSFN.cjs');
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
exports.BANK_CODES = _chunk5SYCSFXKcjs.BANK_CODES; exports.FioApiError = _chunk5SYCSFXKcjs.FioApiError; exports.FioRateLimited = _chunk5SYCSFXKcjs.FioRateLimited; exports.FioTokenInvalidOrInactive = _chunk5SYCSFXKcjs.FioTokenInvalidOrInactive; exports.FioTransientFailure = _chunk5SYCSFXKcjs.FioTransientFailure; exports.WebhookVerificationError = _chunk5SYCSFXKcjs.WebhookVerificationError; exports.buildWebhookEnvelope = _chunk5SYCSFXKcjs.buildWebhookEnvelope; exports.decimalToCents = _chunk5SYCSFXKcjs.decimalToCents; exports.decodeRf = _chunk5SYCSFXKcjs.decodeRf; exports.detectProvider = _chunk5SYCSFXKcjs.detectProvider; exports.encodeRf = _chunk5SYCSFXKcjs.encodeRf; exports.extractReferenceCandidates = _chunk5SYCSFXKcjs.extractReferenceCandidates; exports.fetchNewTransactions = _chunk5SYCSFXKcjs.fetchNewTransactions; exports.mapFioTransaction = _chunk5SYCSFXKcjs.mapFioTransaction; exports.normalizeCurrency = _chunk5SYCSFXKcjs.normalizeCurrency; exports.parseEmail = _chunk5SYCSFXKcjs.parseEmail; exports.resolveVariableSymbol = _chunk5SYCSFXKcjs.resolveVariableSymbol; exports.setFioPointer = _chunk5SYCSFXKcjs.setFioPointer; exports.signWebhook = _chunk5SYCSFXKcjs.signWebhook; exports.toCents = _chunk5SYCSFXKcjs.toCents; exports.verifyWebhook = _chunk5SYCSFXKcjs.verifyWebhook;
|
package/dist/index.d.cts
CHANGED
|
@@ -88,6 +88,13 @@ declare class FioApiError extends Error {
|
|
|
88
88
|
readonly status: number;
|
|
89
89
|
constructor(message: string, status: number);
|
|
90
90
|
}
|
|
91
|
+
/** Fio documents HTTP 500 as a nonexistent/inactive token, including one
|
|
92
|
+
* created but not yet authorized in Internetbanking. Never infer this from
|
|
93
|
+
* a proxy-generated 500 or a transport timeout. */
|
|
94
|
+
declare class FioTokenInvalidOrInactive extends FioApiError {
|
|
95
|
+
readonly code = "fio_token_invalid_or_inactive";
|
|
96
|
+
constructor();
|
|
97
|
+
}
|
|
91
98
|
declare class FioRateLimited extends FioApiError {
|
|
92
99
|
readonly retryAfterS: number | null;
|
|
93
100
|
constructor(status: number, retryAfterS: number | null);
|
|
@@ -111,4 +118,4 @@ declare function fetchNewTransactions(token: string, proxy?: FioProxyConfig): Pr
|
|
|
111
118
|
declare function setFioPointer(token: string, yyyyMmDd: string, proxy?: FioProxyConfig): Promise<void>;
|
|
112
119
|
declare function mapFioTransaction(raw: FioTransaction): Omit<Transaction, 'id' | 'bank_account_id'> | null;
|
|
113
120
|
|
|
114
|
-
export { BANK_CODES, type EmailProvider, FioApiError, type FioColumn, type FioProxyConfig, FioRateLimited, type FioTransaction, FioTransientFailure, type ParsedEmailTransaction, type ReferenceSource, type SignedWebhook, Transaction, type VerifyWebhookArgs, WebhookEnvelope, type WebhookVerificationCode, WebhookVerificationError, buildWebhookEnvelope, decimalToCents, decodeRf, detectProvider, encodeRf, extractReferenceCandidates, fetchNewTransactions, mapFioTransaction, normalizeCurrency, parseEmail, resolveVariableSymbol, setFioPointer, signWebhook, toCents, verifyWebhook };
|
|
121
|
+
export { BANK_CODES, type EmailProvider, FioApiError, type FioColumn, type FioProxyConfig, FioRateLimited, FioTokenInvalidOrInactive, type FioTransaction, FioTransientFailure, type ParsedEmailTransaction, type ReferenceSource, type SignedWebhook, Transaction, type VerifyWebhookArgs, WebhookEnvelope, type WebhookVerificationCode, WebhookVerificationError, buildWebhookEnvelope, decimalToCents, decodeRf, detectProvider, encodeRf, extractReferenceCandidates, fetchNewTransactions, mapFioTransaction, normalizeCurrency, parseEmail, resolveVariableSymbol, setFioPointer, signWebhook, toCents, verifyWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -88,6 +88,13 @@ declare class FioApiError extends Error {
|
|
|
88
88
|
readonly status: number;
|
|
89
89
|
constructor(message: string, status: number);
|
|
90
90
|
}
|
|
91
|
+
/** Fio documents HTTP 500 as a nonexistent/inactive token, including one
|
|
92
|
+
* created but not yet authorized in Internetbanking. Never infer this from
|
|
93
|
+
* a proxy-generated 500 or a transport timeout. */
|
|
94
|
+
declare class FioTokenInvalidOrInactive extends FioApiError {
|
|
95
|
+
readonly code = "fio_token_invalid_or_inactive";
|
|
96
|
+
constructor();
|
|
97
|
+
}
|
|
91
98
|
declare class FioRateLimited extends FioApiError {
|
|
92
99
|
readonly retryAfterS: number | null;
|
|
93
100
|
constructor(status: number, retryAfterS: number | null);
|
|
@@ -111,4 +118,4 @@ declare function fetchNewTransactions(token: string, proxy?: FioProxyConfig): Pr
|
|
|
111
118
|
declare function setFioPointer(token: string, yyyyMmDd: string, proxy?: FioProxyConfig): Promise<void>;
|
|
112
119
|
declare function mapFioTransaction(raw: FioTransaction): Omit<Transaction, 'id' | 'bank_account_id'> | null;
|
|
113
120
|
|
|
114
|
-
export { BANK_CODES, type EmailProvider, FioApiError, type FioColumn, type FioProxyConfig, FioRateLimited, type FioTransaction, FioTransientFailure, type ParsedEmailTransaction, type ReferenceSource, type SignedWebhook, Transaction, type VerifyWebhookArgs, WebhookEnvelope, type WebhookVerificationCode, WebhookVerificationError, buildWebhookEnvelope, decimalToCents, decodeRf, detectProvider, encodeRf, extractReferenceCandidates, fetchNewTransactions, mapFioTransaction, normalizeCurrency, parseEmail, resolveVariableSymbol, setFioPointer, signWebhook, toCents, verifyWebhook };
|
|
121
|
+
export { BANK_CODES, type EmailProvider, FioApiError, type FioColumn, type FioProxyConfig, FioRateLimited, FioTokenInvalidOrInactive, type FioTransaction, FioTransientFailure, type ParsedEmailTransaction, type ReferenceSource, type SignedWebhook, Transaction, type VerifyWebhookArgs, WebhookEnvelope, type WebhookVerificationCode, WebhookVerificationError, buildWebhookEnvelope, decimalToCents, decodeRf, detectProvider, encodeRf, extractReferenceCandidates, fetchNewTransactions, mapFioTransaction, normalizeCurrency, parseEmail, resolveVariableSymbol, setFioPointer, signWebhook, toCents, verifyWebhook };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
BANK_CODES,
|
|
3
3
|
FioApiError,
|
|
4
4
|
FioRateLimited,
|
|
5
|
+
FioTokenInvalidOrInactive,
|
|
5
6
|
FioTransientFailure,
|
|
6
7
|
WebhookVerificationError,
|
|
7
8
|
buildWebhookEnvelope,
|
|
@@ -19,11 +20,12 @@ import {
|
|
|
19
20
|
signWebhook,
|
|
20
21
|
toCents,
|
|
21
22
|
verifyWebhook
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-JFSJO3OJ.js";
|
|
23
24
|
export {
|
|
24
25
|
BANK_CODES,
|
|
25
26
|
FioApiError,
|
|
26
27
|
FioRateLimited,
|
|
28
|
+
FioTokenInvalidOrInactive,
|
|
27
29
|
FioTransientFailure,
|
|
28
30
|
WebhookVerificationError,
|
|
29
31
|
buildWebhookEnvelope,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@festapp/banksync",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Bank transaction ingestion for Cloudflare Workers: Czech bank email and Fio API parsing, durable deduplication, and signed webhook delivery.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.24.0",
|