const BASE_URL = "https://api.comdirect.de"; export function createRequestId(): string { return Math.floor(Math.random() * 1_000_000_000) .toString() .padStart(9, "0"); } export function buildRequestInfo(sessionUuid: string, requestId?: string): string { return JSON.stringify({ clientRequestId: { sessionId: sessionUuid, requestId: requestId ?? createRequestId(), }, }); } export async function oauthToken( body: Record, clientId: string, clientSecret: string, ): Promise<{ access_token: string; refresh_token: string; expires_in: number }> { const params = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, ...body, }); const response = await fetch(`${BASE_URL}/oauth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString(), }); if (!response.ok) { throw new Error(`OAuth fehlgeschlagen: ${response.status}`); } return await response.json(); } export async function getSessions( accessToken: string, sessionUuid: string, ): Promise> { const response = await fetch(`${BASE_URL}/api/session/clients/user/v1/sessions`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "Content-Type": "application/json", "x-http-request-info": buildRequestInfo(sessionUuid), }, }); if (!response.ok) throw new Error(`Sessions-Abruf fehlgeschlagen: ${response.status}`); return await response.json(); } export async function validateSession( accessToken: string, sessionUuid: string, identifier: string, ): Promise<{ challengeId: string; challengeType: string; challenge?: string }> { const response = await fetch( `${BASE_URL}/api/session/clients/user/v1/sessions/${identifier}/validate`, { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "Content-Type": "application/json", "x-http-request-info": buildRequestInfo(sessionUuid), }, body: JSON.stringify({ identifier, sessionTanActive: true, activated2FA: true, }), }, ); if (response.status !== 201) { throw new Error(`Session-Validierung fehlgeschlagen: ${response.status}`); } const authInfo = response.headers.get("x-once-authentication-info"); if (!authInfo) throw new Error("Keine TAN-Challenge erhalten"); const parsed = JSON.parse(authInfo) as { id: string; typ: string; challenge?: string }; return { challengeId: parsed.id, challengeType: parsed.typ, challenge: parsed.challenge, }; } export async function activateSession( accessToken: string, sessionUuid: string, identifier: string, challengeId: string, tan?: string, ): Promise { const headers: Record = { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "Content-Type": "application/json", "x-http-request-info": buildRequestInfo(sessionUuid), "x-once-authentication-info": JSON.stringify({ id: challengeId }), }; const body: Record = { identifier, sessionTanActive: true, activated2FA: true, }; if (tan) { body.tan = tan; } const response = await fetch( `${BASE_URL}/api/session/clients/user/v1/sessions/${identifier}`, { method: "PATCH", headers, body: JSON.stringify(body), }, ); if (!response.ok) { throw new Error(`Session-Aktivierung fehlgeschlagen: ${response.status}`); } } export async function getSecondaryToken( accessToken: string, clientId: string, clientSecret: string, ): Promise<{ access_token: string; refresh_token: string }> { return await oauthToken( { grant_type: "cd_secondary", token: accessToken }, clientId, clientSecret, ); } export async function refreshAccessToken( refreshToken: string, clientId: string, clientSecret: string, ): Promise<{ access_token: string; refresh_token: string }> { return await oauthToken( { grant_type: "refresh_token", refresh_token: refreshToken }, clientId, clientSecret, ); } export async function getAccountBalances( accessToken: string, sessionUuid: string, ): Promise<{ values: Array> }> { const response = await fetch(`${BASE_URL}/api/banking/clients/user/v2/accounts/balances`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "Content-Type": "application/json", "x-http-request-info": buildRequestInfo(sessionUuid), }, }); if (!response.ok) throw new Error(`Salden-Abruf fehlgeschlagen: ${response.status}`); return await response.json(); } export async function getTransactions( accessToken: string, sessionUuid: string, accountId: string, params: { transactionState: "BOOKED" | "NOTBOOKED"; pagingFirst: number; minBookingDate: string; maxBookingDate: string; }, ): Promise<{ paging: { index: number; matches: number }; values: Array> }> { const query = new URLSearchParams({ transactionState: params.transactionState, "paging-first": String(params.pagingFirst), "paging-count": "50", "min-bookingDate": params.minBookingDate, "max-bookingDate": params.maxBookingDate, }); const response = await fetch( `${BASE_URL}/api/banking/v1/accounts/${accountId}/transactions?${query.toString()}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", "Content-Type": "application/json", "x-http-request-info": buildRequestInfo(sessionUuid), }, }, ); if (!response.ok) throw new Error(`Umsatz-Abruf fehlgeschlagen: ${response.status}`); return await response.json(); }