162 lines
4.4 KiB
TypeScript
162 lines
4.4 KiB
TypeScript
"use node";
|
||
|
||
import {
|
||
FinTSClient,
|
||
FinTSConfig,
|
||
type BankingInformation,
|
||
type ClientResponse,
|
||
} from "lib-fints";
|
||
import { resolveFintsEnvFields } from "./fintsConfig";
|
||
|
||
export type FintsInteractionKind =
|
||
| { type: "sync"; syncSystemId: boolean }
|
||
| { type: "balance"; accountNumber: string }
|
||
| { type: "statements"; accountNumber: string; from?: string; to?: string; preferCamt: boolean };
|
||
|
||
export type SerializedFintsSession = {
|
||
bankingInformation: BankingInformation;
|
||
tanMethodId?: number;
|
||
tanMediaName?: string;
|
||
tanReference: string;
|
||
tanContinuation: "sync" | "balance" | "statements";
|
||
};
|
||
|
||
export type FintsEnvConfig = {
|
||
productId: string;
|
||
productVersion: string;
|
||
url: string;
|
||
blz: string;
|
||
login: string;
|
||
pin: string;
|
||
tanMethodId?: number;
|
||
tanMediaName?: string;
|
||
bankingInformation?: BankingInformation;
|
||
};
|
||
|
||
/** @deprecated Import from ./fintsConfig */
|
||
export { FINTS_PRODUCT_ID_PLACEHOLDER, getFintsConfigStatus } from "./fintsConfig";
|
||
export type { FintsConfigStatus } from "./fintsConfig";
|
||
|
||
export function resolveFintsEnv(overrides?: {
|
||
blz?: string;
|
||
url?: string;
|
||
login?: string;
|
||
productId?: string;
|
||
productVersion?: string;
|
||
tanMethodId?: number;
|
||
tanMediaName?: string;
|
||
bankingInformationJson?: string;
|
||
pin?: string;
|
||
}): FintsEnvConfig {
|
||
const fields = resolveFintsEnvFields(overrides);
|
||
if (fields.usesProductIdPlaceholder) {
|
||
console.warn("[fints] FINTS_PRODUCT_ID fehlt – Platzhalter wird verwendet");
|
||
}
|
||
|
||
let bankingInformation: BankingInformation | undefined;
|
||
if (overrides?.bankingInformationJson) {
|
||
bankingInformation = JSON.parse(overrides.bankingInformationJson) as BankingInformation;
|
||
}
|
||
|
||
return {
|
||
productId: fields.productId,
|
||
productVersion: fields.productVersion,
|
||
url: fields.url,
|
||
blz: fields.blz,
|
||
login: fields.login,
|
||
pin: fields.pin,
|
||
tanMethodId: overrides?.tanMethodId,
|
||
tanMediaName: overrides?.tanMediaName,
|
||
bankingInformation,
|
||
};
|
||
}
|
||
|
||
export function createFinTsClient(config: FintsEnvConfig): FinTSClient {
|
||
const fintsConfig = config.bankingInformation
|
||
? FinTSConfig.fromBankingInformation(
|
||
config.productId,
|
||
config.productVersion,
|
||
config.bankingInformation,
|
||
config.login,
|
||
config.pin,
|
||
config.tanMethodId,
|
||
config.tanMediaName,
|
||
)
|
||
: FinTSConfig.forFirstTimeUse(
|
||
config.productId,
|
||
config.productVersion,
|
||
config.url,
|
||
config.blz,
|
||
config.login,
|
||
config.pin,
|
||
);
|
||
|
||
const client = new FinTSClient(fintsConfig);
|
||
if (config.tanMethodId) {
|
||
client.selectTanMethod(config.tanMethodId);
|
||
}
|
||
if (config.tanMediaName) {
|
||
client.selectTanMedia(config.tanMediaName);
|
||
}
|
||
return client;
|
||
}
|
||
|
||
export function buildSessionSnapshot(
|
||
client: FinTSClient,
|
||
tanReference: string,
|
||
tanContinuation: SerializedFintsSession["tanContinuation"],
|
||
): SerializedFintsSession {
|
||
return {
|
||
bankingInformation: client.config.bankingInformation,
|
||
tanMethodId: client.config.selectedTanMethod?.id,
|
||
tanMediaName: client.config.selectedTanMethod?.activeTanMedia?.[0],
|
||
tanReference,
|
||
tanContinuation,
|
||
};
|
||
}
|
||
|
||
export async function continueWithTan(
|
||
client: FinTSClient,
|
||
session: SerializedFintsSession,
|
||
tan?: string,
|
||
): Promise<ClientResponse> {
|
||
switch (session.tanContinuation) {
|
||
case "sync":
|
||
return await client.synchronizeWithTan(session.tanReference, tan);
|
||
case "balance":
|
||
return await client.getAccountBalanceWithTan(session.tanReference, tan);
|
||
case "statements":
|
||
return await client.getAccountStatementsWithTan(session.tanReference, tan);
|
||
}
|
||
}
|
||
|
||
export function encodePhotoTan(response: ClientResponse): {
|
||
mimeType?: string;
|
||
base64?: string;
|
||
} {
|
||
if (!response.tanPhoto?.image) return {};
|
||
const bytes =
|
||
response.tanPhoto.image instanceof Uint8Array
|
||
? response.tanPhoto.image
|
||
: new Uint8Array(response.tanPhoto.image);
|
||
let binary = "";
|
||
for (const byte of bytes) {
|
||
binary += String.fromCharCode(byte);
|
||
}
|
||
return {
|
||
mimeType: response.tanPhoto.mimeType,
|
||
base64: btoa(binary),
|
||
};
|
||
}
|
||
|
||
export function pickDecoupledTanMethod(client: FinTSClient): number | undefined {
|
||
const methods = client.config.availableTanMethods;
|
||
const decoupled = methods.find((m) => m.isDecoupled);
|
||
if (decoupled) return decoupled.id;
|
||
return methods[0]?.id;
|
||
}
|
||
|
||
export function sleep(ms: number): Promise<void> {
|
||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
}
|