infoalloggi-monorepo/apps/infoalloggi/src/pages/servizio/pagamento/[ordineId].tsx
Marco Pedone 52801bebd1 feat: refactor payment processing and checkout flow with Stripe integration
- Removed AcquistoProcessing component and replaced it with a new CheckoutForm component for better separation of concerns.
- Added new payment checkout page to handle payment intents and display payment status.
- Updated server-side logic to create payment intents and retrieve payment data for checkout.
- Enhanced error handling and user feedback during payment processing.
- Introduced new utility functions for fetching payment data and managing Stripe integration.
2026-02-20 11:36:47 +01:00

173 lines
4.3 KiB
TypeScript

import { ArrowRight, ExternalLink } from "lucide-react";
import type { GetServerSideProps } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { AreaRiservataLayout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { Button } from "~/components/ui/button";
import { formatCurrency } from "~/lib/utils";
import type { NextPageWithLayout } from "~/pages/_app";
import { useTranslation } from "~/providers/I18nProvider";
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
import { zOrdineId } from "~/server/utils/zod_types";
import { api } from "~/utils/api";
type ServizioPurchaseProps = {
ordineId: OrdiniOrdineId;
};
const ServicePurchasePage: NextPageWithLayout<ServizioPurchaseProps> = ({
ordineId,
}: ServizioPurchaseProps) => {
const router = useRouter();
const { data, isLoading } = api.servizio.getServizioPaymentData.useQuery({
ordineId,
});
if (isLoading) {
return <LoadingPage />;
}
if (!data) {
return <Status500 />;
}
return (
<div className="mx-auto w-full max-w-6xl grow space-y-4 p-2 sm:px-8 sm:py-4">
<PricePreparation
onProcedi={async () =>
await router.push(`/servizio/pagamento-checkout/${data.payment.id}`)
}
packId={data.ordine.packid}
/>
</div>
);
};
const PricePreparation = ({
packId,
onProcedi,
}: {
packId: PrezziarioIdprezziario;
onProcedi: () => void;
}) => {
const { data: prezziario, isLoading } =
api.prezziario.getPrezzoPerServizio.useQuery({
idprezziario: packId,
});
const { t } = useTranslation();
return (
<div className="mx-auto mt-4 max-w-2xl p-2 sm:mt-10">
{isLoading || !prezziario ? (
<>
<h3 className="font-semibold text-2xl">{t.acquisto.preparazione}</h3>
<LoadingPage />
</>
) : (
<div className="space-y-8">
<div className="space-y-6">
<p className="text-3xl">{prezziario.nome_it}</p>
<p className="text-lg">
Cod: &ldquo;{prezziario.idprezziario}&rdquo;
</p>
<p className="font-bold text-4xl">
{formatCurrency(prezziario.prezzo_cent / 100)}
</p>
<p>{prezziario.desc_it}</p>
</div>
{prezziario.testo_condizioni && (
<Link
aria-label="Leggi le condizioni"
className="block w-fit"
href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
target="_blank"
>
<Button className="w-full text-sm">
<span>{t.acquisto.leggi_condizioni}</span>
<ExternalLink />
</Button>
</Link>
)}
<Button
className="w-full text-lg"
disabled={isLoading || !prezziario}
onClick={onProcedi}
size="xl"
variant="info"
>
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
</Button>
</div>
)}
</div>
);
};
ServicePurchasePage.getLayout = function getLayout(page) {
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
};
export default ServicePurchasePage;
export const getServerSideProps = (async (context) => {
const id = context.params?.ordineId;
if (typeof id !== "string") {
console.error("Error: ordineId is not a string");
return {
redirect: {
destination: "/500",
permanent: false,
},
};
}
const parsed = zOrdineId.safeParse(id);
if (!parsed.success) {
console.error("Error parsing ordineId", parsed.error);
return {
redirect: {
destination: "/500",
permanent: false,
},
};
}
const access_token = context.req.cookies.access_token;
const helper = await TrpcAuthedFetchingIstance({ access_token });
if (helper) {
const isAwaitingPayment =
await helper.trpc.servizio.isOrdineAwaitingPayment.fetch({
ordineId: parsed.data,
});
if (!isAwaitingPayment) {
return {
redirect: {
destination: "/area-riservata/dashboard",
permanent: false,
},
};
}
await helper.trpc.servizio.getServizioPaymentData.prefetch({
ordineId: parsed.data,
});
return {
props: {
ordineId: parsed.data,
trpcState: helper.trpc.dehydrate(),
},
};
}
return {
redirect: {
destination: "/500",
permanent: false,
},
};
}) satisfies GetServerSideProps<ServizioPurchaseProps>;