90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import { createSignal, onMount, Show } from "solid-js";
|
|
import { Title } from "@solidjs/meta";
|
|
import { useNavigate, useSearchParams } from "@solidjs/router";
|
|
import EmbeddedCheckout from "~/components/EmbeddedCheckout";
|
|
import { api } from "~/lib/api";
|
|
import PageContainer from "~/components/layout/PageContainer";
|
|
|
|
const priceMap: Record<string, string> = {
|
|
basic: process.env.STRIPE_PRICE_BASIC ?? "",
|
|
plus: process.env.STRIPE_PRICE_PLUS ?? "",
|
|
premium: process.env.STRIPE_PRICE_PREMIUM ?? "",
|
|
};
|
|
|
|
export default function CheckoutPage() {
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const [clientSecret, setClientSecret] = createSignal("");
|
|
const [error, setError] = createSignal<string | null>(null);
|
|
const [loading, setLoading] = createSignal(true);
|
|
|
|
onMount(async () => {
|
|
const plan = Array.isArray(searchParams.plan) ? searchParams.plan[0] : searchParams.plan;
|
|
const priceIdParam = Array.isArray(searchParams.priceId) ? searchParams.priceId[0] : searchParams.priceId;
|
|
const priceId = priceIdParam ?? (plan ? priceMap[plan] : "");
|
|
|
|
if (!priceId) {
|
|
setError("No plan selected. Please select a plan to continue.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const returnUrl = `${window.location.origin}/billing/return`;
|
|
const result = await api.billing.createCheckoutSession.mutate({
|
|
priceId,
|
|
returnUrl,
|
|
});
|
|
setClientSecret(result.clientSecret);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Failed to create checkout session");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<main class="min-h-screen py-8 md:py-12">
|
|
<Title>Checkout — Kordant</Title>
|
|
<PageContainer>
|
|
<div class="max-w-2xl mx-auto">
|
|
<div class="mb-8">
|
|
<h1 class="text-2xl font-bold text-[var(--color-text-primary)]">Complete your purchase</h1>
|
|
<p class="text-sm text-[var(--color-text-secondary)] mt-1">
|
|
Secure payment powered by Stripe
|
|
</p>
|
|
</div>
|
|
|
|
<Show when={loading()}>
|
|
<div class="flex items-center justify-center min-h-[300px]">
|
|
<div class="text-center">
|
|
<div class="animate-spin h-8 w-8 border-2 border-[var(--color-brand-primary)] border-t-transparent rounded-full mx-auto mb-3" />
|
|
<p class="text-sm text-[var(--color-text-secondary)]">Preparing checkout...</p>
|
|
</div>
|
|
</div>
|
|
</Show>
|
|
|
|
<Show when={error() && !loading()}>
|
|
<div class="text-center py-8 border border-[var(--color-border)] rounded-xl">
|
|
<p class="text-[var(--color-error)] mb-4">{error()}</p>
|
|
<button
|
|
onClick={() => navigate("/pricing")}
|
|
class="text-sm text-[var(--color-brand-primary)] hover:underline"
|
|
>
|
|
← Back to pricing
|
|
</button>
|
|
</div>
|
|
</Show>
|
|
|
|
<Show when={clientSecret() && !loading() && !error()}>
|
|
<EmbeddedCheckout
|
|
clientSecret={clientSecret()}
|
|
onCheckoutComplete={() => navigate("/dashboard")}
|
|
/>
|
|
</Show>
|
|
</div>
|
|
</PageContainer>
|
|
</main>
|
|
);
|
|
}
|