2025-08-28 18:27:07 +02:00
|
|
|
import { TRPCError } from "@trpc/server";
|
2025-10-20 16:22:20 +02:00
|
|
|
import { env } from "~/env";
|
2026-02-20 11:42:46 +01:00
|
|
|
import stripe from "~/lib/stripe";
|
2025-08-28 18:27:07 +02:00
|
|
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
2025-08-04 17:45:44 +02:00
|
|
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
|
|
|
|
import type { Payments, PaymentsId } from "~/schemas/public/Payments";
|
2026-03-04 19:12:01 +01:00
|
|
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
2025-08-04 17:45:44 +02:00
|
|
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { db } from "~/server/db";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { NewMail } from "~/server/services/mailer";
|
|
|
|
|
import { getUser } from "~/server/services/user.service";
|
2026-03-04 20:22:17 +01:00
|
|
|
import { addEventToQueue } from "./event_queue.controller";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2026-01-14 12:02:03 +01:00
|
|
|
export const stripeHealthCheck = async () => {
|
|
|
|
|
try {
|
2026-02-20 11:42:46 +01:00
|
|
|
const endpoints = await stripe.webhookEndpoints.list();
|
2026-02-20 11:36:47 +01:00
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
endpoints,
|
|
|
|
|
secretKey: env.STRIPE_SECRET_KEY,
|
|
|
|
|
publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY,
|
|
|
|
|
};
|
2026-01-14 12:02:03 +01:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Stripe health check failed:", e);
|
2026-02-20 11:36:47 +01:00
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
endpoints: null,
|
|
|
|
|
secretKey: env.STRIPE_SECRET_KEY,
|
|
|
|
|
publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY,
|
|
|
|
|
};
|
2026-01-14 12:02:03 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-04 17:45:44 +02:00
|
|
|
export const createIntentHandler = async ({
|
2025-08-28 18:27:07 +02:00
|
|
|
paymentId,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
paymentId: PaymentsId;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
try {
|
|
|
|
|
const payment = await db
|
|
|
|
|
.selectFrom("payments")
|
|
|
|
|
.selectAll("payments")
|
|
|
|
|
.innerJoin("users", "users.id", "payments.userid")
|
|
|
|
|
.select("users.email")
|
|
|
|
|
.innerJoin("ordini", "ordini.ordine_id", "payments.ordine_id")
|
|
|
|
|
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
|
|
|
|
.select(["prezziario.nome_it", "prezziario.desc_it"])
|
2025-09-05 16:59:52 +02:00
|
|
|
.where("payments.id", "=", paymentId)
|
2026-03-05 10:29:08 +01:00
|
|
|
.executeTakeFirstOrThrow(
|
|
|
|
|
() => new Error(`Payment not found - paymentId: ${paymentId}`),
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2026-02-20 11:42:46 +01:00
|
|
|
const paymentIntent = await stripe.paymentIntents.create({
|
2025-08-28 18:27:07 +02:00
|
|
|
amount: payment.amount_cent,
|
|
|
|
|
//payment_method_configuration: "pmc_1P4NfjILe4KoQRqXYChFCTFj",
|
|
|
|
|
automatic_payment_methods: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
currency: "eur",
|
|
|
|
|
description: payment.nome_it,
|
2025-08-28 18:27:07 +02:00
|
|
|
metadata: {
|
|
|
|
|
paymentId,
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId: payment.servizio_id,
|
|
|
|
|
userId: payment.userid,
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
|
|
|
|
receipt_email: payment.email,
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return {
|
|
|
|
|
clientSecret: paymentIntent.client_secret,
|
|
|
|
|
descrizione: payment.desc_it,
|
2025-08-29 16:18:32 +02:00
|
|
|
prezzo: payment.amount_cent,
|
|
|
|
|
titolo: payment.nome_it,
|
2025-08-28 18:27:07 +02:00
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
|
message: `Error creating payment intent: ${(error as Error).message}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const whIntentCreatedHandler = async ({
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId,
|
|
|
|
|
paymentId,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId: Payments["intent_id"];
|
|
|
|
|
paymentId: PaymentsId;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2026-02-24 14:06:47 +01:00
|
|
|
try {
|
|
|
|
|
return await db
|
|
|
|
|
.updateTable("payments")
|
|
|
|
|
.set({
|
|
|
|
|
intent_id: pIntentId,
|
|
|
|
|
paymentstatus: PaymentStatusEnum.processing,
|
|
|
|
|
})
|
|
|
|
|
.where("id", "=", paymentId)
|
|
|
|
|
.returning("id")
|
|
|
|
|
.execute();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
|
message: `Error whIntentCreatedHandler: ${(error as Error).message}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const whIntentSucceededHandler = async ({
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId,
|
|
|
|
|
paymentId,
|
|
|
|
|
pm_id,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId: Payments["intent_id"];
|
|
|
|
|
paymentId: PaymentsId;
|
|
|
|
|
pm_id: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
try {
|
2026-02-20 11:42:46 +01:00
|
|
|
const paymentMethod = await stripe.paymentMethods.retrieve(pm_id);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
await db.transaction().execute(async (trx) => {
|
|
|
|
|
const payment = await trx
|
|
|
|
|
.updateTable("payments")
|
|
|
|
|
.set({
|
|
|
|
|
paid_at: new Date(),
|
|
|
|
|
paymentmethod: paymentMethod.type,
|
2025-08-29 16:18:32 +02:00
|
|
|
paymentstatus: PaymentStatusEnum.success,
|
2025-08-28 18:27:07 +02:00
|
|
|
})
|
|
|
|
|
.where("intent_id", "=", pIntentId)
|
|
|
|
|
.where("payments.id", "=", paymentId)
|
|
|
|
|
.returning(["servizio_id", "ordine_id"])
|
2026-03-05 10:29:08 +01:00
|
|
|
.executeTakeFirstOrThrow(() => {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Payment not found - intent_id: ${pIntentId}, paymentId: ${paymentId}`,
|
|
|
|
|
);
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const ordine = await trx
|
|
|
|
|
.selectFrom("ordini")
|
|
|
|
|
.selectAll("ordini")
|
|
|
|
|
.where("ordine_id", "=", payment.ordine_id)
|
2026-03-05 10:29:08 +01:00
|
|
|
.executeTakeFirstOrThrow(() => {
|
|
|
|
|
throw new Error(`Ordine not found - ordine_id: ${payment.ordine_id}`);
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
await trx
|
|
|
|
|
.updateTable("ordini")
|
|
|
|
|
.where("ordine_id", "=", payment.ordine_id)
|
|
|
|
|
.set({
|
|
|
|
|
isActive: true,
|
|
|
|
|
})
|
|
|
|
|
.execute();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const servizio = await trx
|
|
|
|
|
.selectFrom("servizio")
|
|
|
|
|
.select("servizio.tipologia")
|
|
|
|
|
.where("servizio_id", "=", payment.servizio_id)
|
2026-03-05 10:29:08 +01:00
|
|
|
.executeTakeFirstOrThrow(() => {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Servizio not found - servizio_id: ${payment.servizio_id}`,
|
|
|
|
|
);
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const utente = await getUser({
|
|
|
|
|
db: trx,
|
|
|
|
|
userId: ordine.userid,
|
|
|
|
|
});
|
2026-03-05 10:29:08 +01:00
|
|
|
const lock_expires = new Date(Date.now() + 60 * 1000); // Lock for 1 minute
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
switch (ordine.type) {
|
|
|
|
|
case OrderTypeEnum.Acconto:
|
|
|
|
|
await trx
|
|
|
|
|
.updateTable("servizio")
|
|
|
|
|
.where("servizio_id", "=", payment.servizio_id)
|
|
|
|
|
.set({
|
|
|
|
|
decorrenza: new Date(),
|
|
|
|
|
isOkAcconto: true,
|
|
|
|
|
})
|
|
|
|
|
.execute();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2026-03-04 20:22:17 +01:00
|
|
|
await addEventToQueue({
|
|
|
|
|
data: {
|
|
|
|
|
tipo: "email",
|
|
|
|
|
data: {
|
|
|
|
|
template: {
|
|
|
|
|
mailType: "servizioAttivato",
|
2026-03-04 19:12:01 +01:00
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
mail: {
|
|
|
|
|
subject: "Pagamento Confermato - Acconto Infoalloggi.it",
|
|
|
|
|
to: utente.email,
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
|
|
|
|
|
//ATTENTION: hardcoded condizioni key
|
2026-03-05 10:29:08 +01:00
|
|
|
|
2026-03-04 20:45:21 +01:00
|
|
|
generate: {
|
|
|
|
|
type: "condizioni",
|
|
|
|
|
stringId: "CONDIZIONI_CERCHI" as TestiEStringheStingaId,
|
|
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
encoding: "base64",
|
|
|
|
|
contentType: "application/pdf",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
2026-03-05 10:29:08 +01:00
|
|
|
|
2026-03-04 20:45:21 +01:00
|
|
|
generate: {
|
|
|
|
|
type: "ricevuta_cortesia",
|
|
|
|
|
ordineId: ordine.ordine_id,
|
|
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
encoding: "base64",
|
|
|
|
|
contentType: "application/pdf",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-04 19:12:01 +01:00
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
userId: utente.id,
|
|
|
|
|
},
|
2025-11-19 17:18:28 +01:00
|
|
|
},
|
2026-03-05 10:29:08 +01:00
|
|
|
lock_expires,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
2026-03-04 20:22:17 +01:00
|
|
|
//SERVIZIO ATTIVATO
|
|
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
break;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
case OrderTypeEnum.Saldo:
|
|
|
|
|
await trx
|
|
|
|
|
.updateTable("servizio")
|
|
|
|
|
.where("servizio_id", "=", payment.servizio_id)
|
|
|
|
|
.set({
|
|
|
|
|
isOkConsulenza:
|
|
|
|
|
servizio.tipologia === TipologiaPosizioneEnum.Transitorio,
|
2025-08-29 16:18:32 +02:00
|
|
|
isOkSaldo: true,
|
2025-08-28 18:27:07 +02:00
|
|
|
})
|
|
|
|
|
.execute();
|
2026-03-04 20:22:17 +01:00
|
|
|
await addEventToQueue({
|
|
|
|
|
data: {
|
|
|
|
|
tipo: "email",
|
|
|
|
|
data: {
|
|
|
|
|
template: {
|
|
|
|
|
mailType: "pagamentoConferma",
|
2026-03-04 19:12:01 +01:00
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
mail: {
|
|
|
|
|
subject: "Pagamento Confermato - Saldo Infoalloggi.it",
|
|
|
|
|
to: utente.email,
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
2026-03-04 20:45:21 +01:00
|
|
|
generate: {
|
|
|
|
|
type: "ricevuta_cortesia",
|
|
|
|
|
ordineId: ordine.ordine_id,
|
|
|
|
|
},
|
2026-03-05 10:29:08 +01:00
|
|
|
|
2026-03-04 20:22:17 +01:00
|
|
|
encoding: "base64",
|
|
|
|
|
contentType: "application/pdf",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
userId: utente.id,
|
|
|
|
|
},
|
2025-11-19 17:18:28 +01:00
|
|
|
},
|
2026-03-05 10:29:08 +01:00
|
|
|
lock_expires,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
2026-03-04 20:22:17 +01:00
|
|
|
//PAGAMENTO SALDO CONFERMATO
|
2025-08-28 18:27:07 +02:00
|
|
|
break;
|
|
|
|
|
case OrderTypeEnum.Consulenza:
|
|
|
|
|
await trx
|
|
|
|
|
.updateTable("servizio")
|
|
|
|
|
.where("servizio_id", "=", payment.servizio_id)
|
|
|
|
|
.set({
|
|
|
|
|
isOkConsulenza: true,
|
|
|
|
|
})
|
|
|
|
|
.execute();
|
2026-03-04 20:22:17 +01:00
|
|
|
await addEventToQueue({
|
|
|
|
|
data: {
|
|
|
|
|
tipo: "email",
|
|
|
|
|
data: {
|
|
|
|
|
template: {
|
|
|
|
|
mailType: "pagamentoConferma",
|
|
|
|
|
},
|
|
|
|
|
mail: {
|
|
|
|
|
subject: "Pagamento Confermato - Consulenza Infoalloggi.it",
|
|
|
|
|
to: utente.email,
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
2026-03-05 10:29:08 +01:00
|
|
|
|
2026-03-04 20:45:21 +01:00
|
|
|
generate: {
|
|
|
|
|
type: "ricevuta_cortesia",
|
|
|
|
|
ordineId: ordine.ordine_id,
|
|
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
encoding: "base64",
|
|
|
|
|
contentType: "application/pdf",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-04 19:12:01 +01:00
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
userId: utente.id,
|
|
|
|
|
},
|
2025-11-19 17:18:28 +01:00
|
|
|
},
|
2026-03-05 10:29:08 +01:00
|
|
|
lock_expires,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
2026-03-04 20:22:17 +01:00
|
|
|
//CONSULENZA ATTIVATA
|
2025-08-28 18:27:07 +02:00
|
|
|
break;
|
|
|
|
|
case OrderTypeEnum.Altro:
|
|
|
|
|
// TODO: Handle other types
|
2026-03-04 20:22:17 +01:00
|
|
|
await addEventToQueue({
|
|
|
|
|
data: {
|
|
|
|
|
tipo: "email",
|
|
|
|
|
data: {
|
|
|
|
|
template: {
|
|
|
|
|
mailType: "pagamentoConferma",
|
|
|
|
|
},
|
|
|
|
|
mail: {
|
|
|
|
|
subject: "Pagamento Confermato - Infoalloggi.it",
|
|
|
|
|
to: utente.email,
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
2026-03-05 10:29:08 +01:00
|
|
|
|
2026-03-04 20:45:21 +01:00
|
|
|
generate: {
|
|
|
|
|
type: "ricevuta_cortesia",
|
|
|
|
|
ordineId: ordine.ordine_id,
|
|
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
encoding: "base64",
|
|
|
|
|
contentType: "application/pdf",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-04 19:12:01 +01:00
|
|
|
},
|
2026-03-04 20:22:17 +01:00
|
|
|
userId: utente.id,
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
2026-03-05 10:29:08 +01:00
|
|
|
lock_expires,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
2026-03-05 10:29:08 +01:00
|
|
|
// Enhanced error logging
|
|
|
|
|
console.error("whIntentSucceededHandler error:", {
|
|
|
|
|
pIntentId,
|
|
|
|
|
paymentId,
|
|
|
|
|
pm_id,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
|
|
|
});
|
2025-08-28 18:27:07 +02:00
|
|
|
throw new TRPCError({
|
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
|
message: `Error updating payment intent: ${(error as Error).message}`,
|
2026-03-05 10:29:08 +01:00
|
|
|
cause: error,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const whIntentFailedHandler = async ({
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
pIntentId: Payments["intent_id"];
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const userId = await db
|
|
|
|
|
.updateTable("payments")
|
|
|
|
|
.set({
|
|
|
|
|
paymentstatus: PaymentStatusEnum.failed,
|
|
|
|
|
})
|
|
|
|
|
.returning("userid")
|
|
|
|
|
.where("intent_id", "=", pIntentId)
|
|
|
|
|
.execute();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (userId[0]) {
|
|
|
|
|
const utente = await getUser({
|
|
|
|
|
db,
|
|
|
|
|
userId: userId[0].userid,
|
|
|
|
|
});
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
await NewMail({
|
2025-11-19 17:18:28 +01:00
|
|
|
template: {
|
|
|
|
|
mailType: "pagamentoErrore",
|
|
|
|
|
},
|
|
|
|
|
mail: {
|
2026-03-04 19:12:01 +01:00
|
|
|
subject: "Pagamento Fallito - Infoalloggi.it",
|
2025-11-19 17:18:28 +01:00
|
|
|
to: utente.email,
|
|
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
userId: utente.id,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|