Purchase flow: name -> placeholder -> configure via the shipped PlanConfigurator -> Paddle overlay with custom_data -> paste install UUID to link and issue. lineItemsFor mirrors the Go catalogue.LineItems/billable exactly (base included in exactly one place). ManageBillingButton opens the hosted portal. Paddle token and env are baked into the build, never fetched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
1.2 KiB
TypeScript
41 lines
1.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { initPaddle } from "@/lib/paddle";
|
|
|
|
/* Opens the Paddle overlay with the resolved line items and custom_data. The
|
|
* items come from the configurator via catalogue pricing; custom_data is what
|
|
* lets the webhook route without a lookup table. */
|
|
export function CheckoutButton({
|
|
items,
|
|
customData,
|
|
disabled,
|
|
label = "Continue to payment",
|
|
}: {
|
|
items: { priceId: string; quantity: number }[];
|
|
customData: { account_id: string; instance_id: string };
|
|
disabled?: boolean;
|
|
label?: string;
|
|
}) {
|
|
const [busy, setBusy] = useState(false);
|
|
async function open() {
|
|
setBusy(true);
|
|
const paddle = await initPaddle();
|
|
setBusy(false);
|
|
paddle?.Checkout.open({
|
|
items: items.map((i) => ({ priceId: i.priceId, quantity: i.quantity })),
|
|
customData,
|
|
});
|
|
}
|
|
return (
|
|
<button
|
|
type="button"
|
|
disabled={disabled || busy || items.length === 0}
|
|
onClick={open}
|
|
className="rounded border border-accent/50 px-3 py-1.5 text-[0.85rem] text-accent disabled:opacity-40"
|
|
>
|
|
{busy ? "Opening…" : label}
|
|
</button>
|
|
);
|
|
}
|