import { z } from "zod"; import { Enums, prisma } from "#lib/server/prisma.js"; import { mollieClient } from "#lib/mollie.js"; import { PaymentMethod } from "@mollie/api-client"; import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { authorizationHeaders, authorizationMiddleware, } from "#lib/middleware/authorization.js"; import { UUidWithPrefix } from "#components/Ausweis/types.js"; import { getPrismaAusweisAdapter } from "#lib/server/ausweis.js"; import { PRICES, SERVICES, VALID_UUID_PREFIXES } from "#lib/constants.js"; import { Rechnung } from "#lib/client/prisma.js"; import { RechnungSchema } from "src/generated/zod/rechnung.js"; import { generatePrefixedId } from "#lib/db.js"; export const PUT = defineApiRoute({ meta: { contentTypes: ["application/json"], description: "Erstellt eine neue Rechnung mit der ein Energieausweis bezahlt werden kann. Gibt eine Checkout URL zurück, welche der Nutzer folgen muss, um zum Kaufabschluss zu gelangen.", summary: "Erstellt eine neue Rechnung zu einem Energieausweis Vorgang.", tags: ["Rechnungen"], }, input: z .object({ ausweisart: z.nativeEnum(Enums.Ausweisart), ausweis_id: UUidWithPrefix, }) .merge( RechnungSchema.omit({ benutzer_id: true, bezahlt_am: true, erstellt_am: true, id: true, status: true, betrag: true, storniert_am: true, transaktions_referenz: true, created_at: true, updated_at: true }) ), output: z.object({ checkout_url: z.string().optional(), id: UUidWithPrefix, }), headers: authorizationHeaders, middleware: authorizationMiddleware, async fetch(input, ctx, user) { // Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben // wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann. const { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input; const adapter = getPrismaAusweisAdapter(ausweis_id); if (!adapter) { throw new APIError({ code: "BAD_REQUEST", message: "Ungültige Ausweis UID" }) } const ausweis = await adapter.findUnique({ where: { id: ausweis_id }, include: { rechnung: true } }) if (!ausweis) { throw new APIError({ code: "NOT_FOUND", message: "Ausweis nicht gefunden.", }); } if (ausweis.rechnung) { throw new APIError({ code: "BAD_REQUEST", message: "Eine Rechnung für diesen Ausweis existiert bereits.", }); } if (ausweis.benutzer_id !== user.id) { throw new APIError({ code: "UNAUTHORIZED", message: "Ausweis gehört nicht dem Nutzer.", }); } let betrag = PRICES[ausweisart][ausweis.ausweistyp] const servicePriceList = SERVICES[ausweisart] for (const service of input.services) { betrag += servicePriceList[service] } const id = generatePrefixedId(9, VALID_UUID_PREFIXES.Rechnung); // Wir erstellen eine neue Rechnung in unserer Datenbank. let rechnung: Rechnung | null = null; if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { rechnung = await prisma.rechnung.create({ data: { id, benutzer_id: user.id, betrag, bezahlmethode: bezahlmethode, status: Enums.Rechnungsstatus.open, abweichende_versand_adresse: input.abweichende_versand_adresse, email: input.email, empfaenger: input.empfaenger, ort: input.ort, plz: input.plz, strasse: input.strasse, telefon: input.telefon, versand_ort: input.versand_ort, versand_empfaenger: input.versand_empfaenger, versand_plz: input.versand_plz, versand_strasse: input.versand_strasse, versand_zusatzzeile: input.versand_zusatzzeile, zusatzzeile: input.zusatzzeile, verbrauchsausweis_wohnen: { connect: { id: ausweis_id } }, services, partner_code } }); } else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { rechnung = await prisma.rechnung.create({ data: { id, benutzer_id: user.id, betrag, bezahlmethode: bezahlmethode, status: Enums.Rechnungsstatus.open, abweichende_versand_adresse: input.abweichende_versand_adresse, email: input.email, empfaenger: input.empfaenger, ort: input.ort, plz: input.plz, strasse: input.strasse, telefon: input.telefon, versand_ort: input.versand_ort, versand_empfaenger: input.versand_empfaenger, versand_plz: input.versand_plz, versand_strasse: input.versand_strasse, versand_zusatzzeile: input.versand_zusatzzeile, zusatzzeile: input.zusatzzeile, verbrauchsausweis_gewerbe: { connect: { id: ausweis_id } }, services, partner_code } }); } else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) { rechnung = await prisma.rechnung.create({ data: { id, benutzer_id: user.id, betrag, bezahlmethode: bezahlmethode, status: Enums.Rechnungsstatus.open, abweichende_versand_adresse: input.abweichende_versand_adresse, email: input.email, empfaenger: input.empfaenger, ort: input.ort, plz: input.plz, strasse: input.strasse, telefon: input.telefon, versand_ort: input.versand_ort, versand_empfaenger: input.versand_empfaenger, versand_plz: input.versand_plz, versand_strasse: input.versand_strasse, versand_zusatzzeile: input.versand_zusatzzeile, zusatzzeile: input.zusatzzeile, bedarfsausweis_wohnen: { connect: { id: ausweis_id } }, services, partner_code } }); } if (!rechnung) { throw new APIError({ code: "INTERNAL_SERVER_ERROR", message: "Rechnung konnte nicht erstellt werden.", }); } await adapter.update({ where: { id: ausweis_id }, data: { bestellt: true } }) if (bezahlmethode === Enums.Bezahlmethoden.rechnung) { return { id } } // Wir erstellen eine Mollie Payment Referenz. const payment = await mollieClient.payments.create({ amount: { value: betrag.toFixed(2), currency: "EUR", }, metadata: { rechnung_id: rechnung.id, }, method: input.bezahlmethode as PaymentMethod, description: "Verbrauchsausweis Wohnen 2016", redirectUrl: `https://online-energieausweis.org/payment/success?a=${ausweis.id}&r=${rechnung.id}`, webhookUrl: `https://online-energieausweis.org/api/webhooks/mollie`, cancelUrl: `https://online-energieausweis.org/kundendaten?a=${ausweis.id}&r=${rechnung.id}` }); const checkoutUrl = payment.getCheckoutUrl(); if (!checkoutUrl) { throw new APIError({ code: "INTERNAL_SERVER_ERROR", message: "Checkout URL konnte nicht erstellt werden.", }); } return { id, checkout_url: checkoutUrl, }; }, });