diff --git a/src/routes/banking.ts b/src/routes/banking.ts index 1e88cee..fd228b7 100644 --- a/src/routes/banking.ts +++ b/src/routes/banking.ts @@ -1,9 +1,149 @@ import { FastifyInstance } from "fastify"; -import jwt from "jsonwebtoken"; import {insertHistoryItem} from "../utils/history"; +import axios from "axios" +import dayjs from "dayjs" +import {secrets} from "../utils/secrets"; export default async function bankingRoutes(server: FastifyInstance) { + const goCardLessBaseUrl = secrets.GOCARDLESS_BASE_URL + const goCardLessSecretId = secrets.GOCARDLESS_SECRET_ID + const goCardLessSecretKey = secrets.GOCARDLESS_SECRET_KEY + + let tokenData = null + + const getToken = async () => { + const res = await axios({ + url: goCardLessBaseUrl + "/token/new/", + method: "POST", + data: { + secret_id: goCardLessSecretId, + secret_key: goCardLessSecretKey, + }, + }) + + tokenData = res.data + tokenData.created_at = new Date().toISOString() + server.log.info("Got new GoCardless token") + } + + const checkToken = async () => { + if (tokenData) { + const expired = dayjs(tokenData.created_at) + .add(tokenData.access_expires, "seconds") + .isBefore(dayjs()) + if (expired) { + server.log.info("Token expired — refreshing…") + await getToken() + } + } else { + await getToken() + } + } + + // 🔹 Generate Link + server.get("/banking/link/:institutionid", async (req, reply) => { + await checkToken() + + const {institutionid} = req.params as {institutionid: string} + + try { + const { data } = await axios({ + url: `${goCardLessBaseUrl}/requisitions/`, + method: "POST", + headers: { + Authorization: `Bearer ${tokenData.access}`, + accept: "application/json", + }, + data: { + redirect: "https://app.fedeo.de/settings/banking", + institution_id: institutionid, + user_language: "de", + }, + }) + + await server.supabase + .from("bankrequisitions") + .insert({ + tenant: req.user.tenant_id, + institutionId: institutionid, + id: data.id, + status: data.status, + }) + + return reply.send({ link: data.link }) + } catch (err) { + server.log.error(err.response?.data || err.message) + return reply.code(500).send({ error: "Failed to generate link" }) + } + }) + + // 🔹 Check Institution + server.get("/banking/institutions/:bic", async (req, reply) => { + const { bic } = req.params as {bic: string} + if (!bic) return reply.code(400).send("BIC not provided") + + await checkToken() + + try { + const { data } = await axios({ + url: `${goCardLessBaseUrl}/institutions/?country=de`, + method: "GET", + headers: { + Authorization: `Bearer ${tokenData.access}`, + }, + }) + + const bank = data.find((i) => i.bic.toLowerCase() === bic.toLowerCase()) + if (!bank) return reply.code(404).send("Bank not found") + + return reply.send(bank) + } catch (err) { + server.log.error(err.response?.data || err.message) + return reply.code(500).send("Failed to fetch institutions") + } + }) + + // 🔹 List Requisitions + server.get("/banking/requisitions/:reqId", async (req, reply) => { + const { reqId } = req.params as {reqId: string} + if (!reqId) return reply.code(400).send("Requisition ID not provided") + + await checkToken() + + try { + const { data } = await axios({ + url: `${goCardLessBaseUrl}/requisitions/${reqId}`, + method: "GET", + headers: { + Authorization: `Bearer ${tokenData.access}`, + }, + }) + + if (data.accounts) { + data.accounts = await Promise.all( + data.accounts.map(async (accId) => { + const { data: accountData } = await axios({ + url: `${goCardLessBaseUrl}/accounts/${accId}`, + method: "GET", + headers: { + Authorization: `Bearer ${tokenData.access}`, + accept: "application/json", + }, + }) + return accountData + }) + ) + } + + return reply.send(data) + } catch (err) { + server.log.error(err.response?.data || err.message) + return reply.code(500).send("Failed to fetch requisition data") + } + }) + + //Create Banking Statement server.post("/banking/statements", async (req, reply) => { if (!req.user) { @@ -71,4 +211,7 @@ export default async function bankingRoutes(server: FastifyInstance) { -} \ No newline at end of file + + +} +