197 lines
5.8 KiB
TypeScript
197 lines
5.8 KiB
TypeScript
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<string, string>,
|
|
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<Array<{ identifier: string; sessionTanActive: boolean; activated2FA: boolean }>> {
|
|
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<void> {
|
|
const headers: Record<string, string> = {
|
|
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<string, unknown> = {
|
|
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<Record<string, unknown>> }> {
|
|
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<Record<string, unknown>> }> {
|
|
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();
|
|
}
|