import { TRPCError } from "@trpc/server"; import { add, format } from "date-fns"; import { env } from "~/env"; import stripe from "~/lib/stripe"; import { orderTypeEnum } from "~/schemas/public/OrderTypeEnum"; import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini"; import { paymentStatusEnum } from "~/schemas/public/PaymentStatusEnum"; import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe"; import { tipologiaPosizioneEnum } from "~/schemas/public/TipologiaPosizioneEnum"; import { db } from "~/server/db"; import { NewMail } from "~/server/services/mailer"; import { CONDIZIONI_DEFAULT } from "~/server/services/prezziario.service"; import { getUser } from "~/server/services/user.service"; import { addEventToQueue } from "./event_queue.controller"; export const stripeHealthCheck = async () => { try { const endpoints = await stripe.webhookEndpoints.list(); return { success: true, endpoints, secretKey: env.STRIPE_SECRET_KEY, publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY, }; } catch (e) { console.error("Stripe health check failed:", e); return { success: false, endpoints: null, secretKey: env.STRIPE_SECRET_KEY, publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY, }; } }; export const whIntentCreatedHandler = async ({ pIntentId, ordineId, }: { pIntentId: Ordini["intent_id"]; ordineId: OrdiniOrdineId; }) => { try { return await db .updateTable("ordini") .set({ intent_id: pIntentId, paymentstatus: paymentStatusEnum.enum.processing, }) .where("ordine_id", "=", ordineId) .returning("ordine_id") .execute(); } catch (error) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Error whIntentCreatedHandler: ${(error as Error).message}`, }); } }; export const whIntentSucceededHandler = async ({ pIntentId, ordineId, pm_id, }: { pIntentId: Ordini["intent_id"]; ordineId: OrdiniOrdineId; pm_id: string; }) => { try { const paymentMethod = await stripe.paymentMethods.retrieve(pm_id); await db.transaction().execute(async (trx) => { const ordine = await trx .updateTable("ordini") .set({ paid_at: new Date(), paymentmethod: paymentMethod.type, paymentstatus: paymentStatusEnum.enum.success, isActive: true, }) .where("intent_id", "=", pIntentId) .where("ordine_id", "=", ordineId) .returningAll() .executeTakeFirstOrThrow(() => { throw new Error( `Payment not found - intent_id: ${pIntentId}, ordineId: ${ordineId}`, ); }); const condizioniFilename = `condizioni_contrattuali_${(ordine.paid_at || ordine.created_at).toISOString()}.pdf`; const ricevutaFilename = `ricevuta_cortesia_${(ordine.paid_at || ordine.created_at).toISOString()}.pdf`; const condizioni = await trx .selectFrom("prezziario") .select("testo_condizioni") .where("idprezziario", "=", ordine.packid) .executeTakeFirst(); const utente = await getUser({ db: trx, userId: ordine.userid, }); const lock_expires = add(new Date(), { seconds: 30 }); // Lock for 30 sec switch (ordine.type) { case orderTypeEnum.enum.Acconto: { if (!ordine.servizio_id) { throw new Error( `No servizio_id for ordine_id: ${ordine.ordine_id}`, ); } const servizio = await trx .updateTable("servizio") .where("servizio_id", "=", ordine.servizio_id) .set({ decorrenza: new Date(), isOkAcconto: true, }) .returning(["interruzioneDays"]) .executeTakeFirstOrThrow( () => new Error( `Failed to update servizio - servizioId: ${ordine.servizio_id}`, ), ); if (utente.comms_enabled) { await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "servizioAttivato", }, mail: { subject: "Pagamento Confermato - Acconto Infoalloggi.it", to: utente.email, attachments: [ { filename: condizioniFilename, generate: { type: "condizioni", stringId: (condizioni?.testo_condizioni || CONDIZIONI_DEFAULT) as TestiEStringheStingaId, }, encoding: "base64", contentType: "application/pdf", }, { filename: ricevutaFilename, generate: { type: "ricevuta_cortesia", ordineId: ordine.ordine_id, }, encoding: "base64", contentType: "application/pdf", }, ], }, userId: utente.id, }, }, lock_expires, }); await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "avvisoInterruzione", }, mail: { subject: "Promemoria durata servizio - Infoalloggi.it", to: utente.email, }, userId: utente.id, }, }, lock_expires: add(new Date(), { days: servizio.interruzioneDays - 5, }), }); } //SERVIZIO ATTIVATO break; } case orderTypeEnum.enum.Saldo: { if (!ordine.servizio_id) { throw new Error( `No servizio_id for ordine_id: ${ordine.ordine_id}`, ); } const servizio = await trx .selectFrom("servizio") .select(["servizio.tipologia"]) .where("servizio_id", "=", ordine.servizio_id) .executeTakeFirstOrThrow(() => { throw new Error( `Servizio not found - servizio_id: ${ordine.servizio_id}`, ); }); await trx .updateTable("servizio") .where("servizio_id", "=", ordine.servizio_id) .set({ isOkConsulenza: servizio.tipologia === tipologiaPosizioneEnum.enum.Transitorio, isOkSaldo: true, }) .execute(); if (utente.comms_enabled) { await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "pagamentoConferma", }, mail: { subject: "Pagamento Confermato - Saldo Infoalloggi.it", to: utente.email, attachments: [ { filename: ricevutaFilename, generate: { type: "ricevuta_cortesia", ordineId: ordine.ordine_id, }, encoding: "base64", contentType: "application/pdf", }, ], }, userId: utente.id, }, }, lock_expires, }); } break; } case orderTypeEnum.enum.Rinnovo: { // nessuna azione aggiuntiva richiesta if (utente.comms_enabled) { await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "pagamentoConferma", }, mail: { subject: "Pagamento Confermato - Rinnovo Infoalloggi.it", to: utente.email, attachments: [ { filename: ricevutaFilename, generate: { type: "ricevuta_cortesia", ordineId: ordine.ordine_id, }, encoding: "base64", contentType: "application/pdf", }, ], }, userId: utente.id, }, }, lock_expires, }); } break; } case orderTypeEnum.enum.Consulenza: { if (!ordine.servizio_id) { throw new Error( `No servizio_id for ordine_id: ${ordine.ordine_id}`, ); } await trx .updateTable("servizio") .where("servizio_id", "=", ordine.servizio_id) .set({ isOkConsulenza: true, }) .execute(); if (utente.comms_enabled) { await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "pagamentoConferma", }, mail: { subject: "Pagamento Confermato - Consulenza Infoalloggi.it", to: utente.email, attachments: [ { filename: ricevutaFilename, generate: { type: "ricevuta_cortesia", ordineId: ordine.ordine_id, }, encoding: "base64", contentType: "application/pdf", }, ], }, userId: utente.id, }, }, lock_expires, }); } break; } case orderTypeEnum.enum.Altro: break; } await addEventToQueue({ data: { tipo: "email", data: { template: { mailType: "generic", props: { title: "Nuovo pagamento ricevuto", testo: `È stato ricevuto un nuovo pagamento.`, }, }, mail: { subject: "Ric pagamento", to: "web@infoalloggi.it", attachments: [ { filename: `ricevuta_cortesia_${utente.username.replace(/\s+/g, "-")}-${format(ordine.created_at, "dd-MM-yyyy")}.pdf`, generate: { type: "ricevuta_cortesia", ordineId: ordine.ordine_id, }, encoding: "base64", contentType: "application/pdf", }, { filename: condizioniFilename, generate: { type: "condizioni", stringId: (condizioni?.testo_condizioni || CONDIZIONI_DEFAULT) as TestiEStringheStingaId, }, encoding: "base64", contentType: "application/pdf", }, ], }, }, }, lock_expires, }); }); return true; } catch (error) { console.error("whIntentSucceededHandler error:", { pIntentId, ordineId, pm_id, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Error updating payment intent: ${(error as Error).message}`, cause: error, }); } }; export const whIntentFailedHandler = async ({ pIntentId, }: { pIntentId: Ordini["intent_id"]; }) => { const userId = await db .updateTable("ordini") .set({ paymentstatus: paymentStatusEnum.enum.failed, }) .returning("userid") .where("paymentstatus", "=", paymentStatusEnum.enum.processing) .where("intent_id", "=", pIntentId) .execute(); if (userId[0]) { const utente = await getUser({ db, userId: userId[0].userid, }); await NewMail({ template: { mailType: "pagamentoErrore", }, mail: { subject: "Pagamento Fallito - Infoalloggi.it", to: utente.email, }, userId: utente.id, }); } return true; };