243 lines
6 KiB
TypeScript
243 lines
6 KiB
TypeScript
"use client";
|
|
import {
|
|
Elements,
|
|
PaymentElement,
|
|
useElements,
|
|
useStripe,
|
|
} from "@stripe/react-stripe-js";
|
|
import type { Stripe } from "@stripe/stripe-js";
|
|
import { ArrowRight, ExternalLink } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { type FormEvent, useState } from "react";
|
|
import LoadingButton from "~/components/custom_ui/loading-button";
|
|
import { LoadingPage } from "~/components/loading";
|
|
import { Status500 } from "~/components/status-page";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
|
import { env } from "~/env";
|
|
import { formatCurrency } from "~/lib/utils";
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
import type { PaymentsId } from "~/schemas/public/Payments";
|
|
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
|
import { api } from "~/utils/api";
|
|
|
|
type AcquistoProcessingProps = {
|
|
paymentId: PaymentsId;
|
|
packId: PrezziarioIdprezziario;
|
|
stripePromise: Promise<Stripe | null>;
|
|
};
|
|
|
|
export const AcquistoProcessing = ({
|
|
paymentId,
|
|
packId,
|
|
stripePromise,
|
|
}: AcquistoProcessingProps) => {
|
|
const [stripeIntent, setStripeIntent] = useState<{
|
|
clientSecret: string | null;
|
|
titolo: string;
|
|
prezzo: number;
|
|
descrizione: string;
|
|
} | null>(null);
|
|
const { t } = useTranslation();
|
|
const [generatedIntent, setGeneratedIntent] = useState(false);
|
|
|
|
const { mutate, isPending } = api.stripe.createIntent.useMutation({
|
|
onSuccess: (data) => {
|
|
setStripeIntent(data);
|
|
},
|
|
});
|
|
|
|
if (generatedIntent && !isPending && !stripeIntent) {
|
|
return <Status500 />;
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto my-4 w-full max-w-3xl space-y-8 rounded-md">
|
|
<h3 className="mt-2 text-center font-bold text-2xl uppercase tracking-wide">
|
|
{t.acquisto.titolo}
|
|
</h3>
|
|
{(() => {
|
|
if (!generatedIntent) {
|
|
return (
|
|
<PricePreparation
|
|
onProcedi={() => {
|
|
setGeneratedIntent(true);
|
|
mutate({
|
|
paymentId,
|
|
});
|
|
}}
|
|
packId={packId}
|
|
/>
|
|
);
|
|
}
|
|
if (isPending) {
|
|
return <LoadingPage />;
|
|
}
|
|
if (stripeIntent?.clientSecret) {
|
|
return (
|
|
<>
|
|
<Elements
|
|
options={{
|
|
appearance: {
|
|
theme: "stripe",
|
|
variables: {
|
|
colorPrimary: "#18181b",
|
|
colorText: "#18181b",
|
|
},
|
|
},
|
|
clientSecret: stripeIntent.clientSecret,
|
|
}}
|
|
stripe={stripePromise}
|
|
>
|
|
<CheckoutForm
|
|
descrizione={stripeIntent.descrizione}
|
|
prezzo={stripeIntent.prezzo}
|
|
titolo={stripeIntent.titolo}
|
|
/>
|
|
</Elements>
|
|
</>
|
|
);
|
|
}
|
|
})()}
|
|
</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: “{prezziario.idprezziario}”
|
|
</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>
|
|
);
|
|
};
|
|
|
|
const CheckoutForm = ({
|
|
titolo,
|
|
prezzo,
|
|
descrizione,
|
|
}: {
|
|
titolo: string;
|
|
prezzo: number;
|
|
descrizione: string;
|
|
}) => {
|
|
const stripe = useStripe();
|
|
const elements = useElements();
|
|
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
console.log("submitting payment");
|
|
if (!stripe || !elements) {
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
const { error } = await stripe.confirmPayment({
|
|
confirmParams: {
|
|
return_url: `${env.NEXT_PUBLIC_BASE_URL}/servizio/acquisto-processing`,
|
|
},
|
|
elements,
|
|
});
|
|
if (error.type === "card_error" || error.type === "validation_error") {
|
|
setMessage(error.message || "An unexpected error occurred.");
|
|
} else {
|
|
setMessage("An unexpected error occurred.");
|
|
}
|
|
setIsLoading(false);
|
|
};
|
|
|
|
return (
|
|
<Card className="flex w-full flex-col justify-between text-left">
|
|
<CardHeader>
|
|
<CardTitle>
|
|
<p className="text-2xl">{titolo}</p>
|
|
</CardTitle>
|
|
<p>{descrizione}</p>
|
|
<span>
|
|
<span className="font-bold text-3xl">
|
|
{formatCurrency(prezzo / 100)}
|
|
</span>{" "}
|
|
<span className="font-normal text-base sm:text-sm">
|
|
{t.pricing_cmp.risultati_iva}
|
|
</span>
|
|
</span>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form className="space-y-5" id="payment-form" onSubmit={handleSubmit}>
|
|
<PaymentElement
|
|
id="payment-element"
|
|
options={{ layout: "accordion" }}
|
|
/>
|
|
|
|
<LoadingButton
|
|
aria-label="Procedi al pagamento"
|
|
className="w-full"
|
|
disabled={isLoading || !stripe || !elements}
|
|
loading={isLoading || !stripe || !elements}
|
|
type="submit"
|
|
>
|
|
{t.procedi}
|
|
</LoadingButton>
|
|
{message && <div id="payment-message">{message}</div>}
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|