Add bank synchronization features with FinTS support and update dependencies

This commit is contained in:
Matthias
2026-06-15 13:56:32 +02:00
parent fc0a6fb975
commit d65e7681ac
23 changed files with 2609 additions and 150 deletions

92
convex/bank/sync.ts Normal file
View File

@@ -0,0 +1,92 @@
import { action } from "../_generated/server";
import { v } from "convex/values";
import { internal } from "../_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
import { hasComdirectCredentials } from "./comdirectProvider";
import { getFintsConfigStatus } from "./fintsConfig";
import type { Doc } from "../_generated/dataModel";
type CapabilitiesResult = {
comdirectRestAvailable: boolean;
fintsReady: boolean;
fintsMissing: string[];
fintsWarnings: string[];
useFinTsDirect: boolean;
};
export const getCapabilities = action({
args: {},
returns: v.object({
comdirectRestAvailable: v.boolean(),
fintsReady: v.boolean(),
fintsMissing: v.array(v.string()),
fintsWarnings: v.array(v.string()),
useFinTsDirect: v.boolean(),
}),
handler: async (ctx): Promise<CapabilitiesResult> => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Nicht angemeldet");
const comdirectRestAvailable = hasComdirectCredentials();
await ctx.runMutation(internal.bank.internal.upsertBankConfig, {
userId,
comdirectHasCredentials: comdirectRestAvailable,
});
const bankConfig: Doc<"bankConfig"> | null = await ctx.runQuery(
internal.bank.internal.getBankConfig,
{ userId },
);
const preference: "auto" | "comdirect" | "fints" =
bankConfig?.providerPreference ?? "auto";
const fintsStatus = getFintsConfigStatus({
blz: bankConfig?.fints.blz,
url: bankConfig?.fints.url,
login: bankConfig?.fints.login,
productId: bankConfig?.fints.productId,
});
const useFinTsDirect: boolean =
preference === "fints" || (preference === "auto" && !comdirectRestAvailable);
return {
comdirectRestAvailable,
fintsReady: fintsStatus.ready,
fintsMissing: fintsStatus.missing,
fintsWarnings: fintsStatus.warnings,
useFinTsDirect,
};
},
});
export const run = action({
args: {
from: v.string(),
to: v.string(),
accountId: v.optional(v.id("accounts")),
pin: v.optional(v.string()),
},
returns: v.object({
importedCount: v.number(),
skippedCount: v.number(),
provider: v.union(v.literal("comdirect"), v.literal("fints")),
awaitingTan: v.boolean(),
}),
handler: async (ctx, args): Promise<{
importedCount: number;
skippedCount: number;
provider: "comdirect" | "fints";
awaitingTan: boolean;
}> => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Nicht angemeldet");
return await ctx.runAction(internal.bank.orchestrator.runSyncInternal, {
userId,
from: args.from,
to: args.to,
accountId: args.accountId,
pin: args.pin,
});
},
});