feat(billing): brand checkout + auto-prefill user details

#1 Checkout branding (within Razorpay's hosted-UI limits):
- image: /icons/192.png — Tia logo in the checkout header
- theme color #fb7185 (Tia rose, matches app theme-color) — was terracotta
- Note: Razorpay Checkout is their hosted UI; logo + brand color + name are
  the only customisable bits. Fonts/layout cannot be changed (platform limit).

#2 Auto-prefill name/email/phone:
- UpgradeButton now fetches /api/auth/profile on mount and passes
  prefill {name, email, contact} to Razorpay. User can still edit in checkout.
- Saves manual entry; uses the phone we now collect.

(#3 "seller doesn't support recurring payments" is a Razorpay ACCOUNT setting,
 not code — needs subscriptions enabled on the account. Handled separately.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-06-06 13:39:57 +05:30
parent 082956adea
commit b5f1e5540b

View file

@ -8,8 +8,10 @@ interface RazorpayOptions {
subscription_id: string;
name: string;
description: string;
image?: string; // brand logo shown in the checkout header
handler: (resp: RazorpayResponse) => void;
prefill?: { email?: string; contact?: string };
prefill?: { name?: string; email?: string; contact?: string };
notes?: Record<string, string>;
theme?: { color?: string };
modal?: { ondismiss?: () => void };
}
@ -73,9 +75,19 @@ export function UpgradeButton({
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [done, setDone] = useState(false);
// Auto-prefill from the signed-in user's profile (editable in checkout).
const [profile, setProfile] = useState<{ name?: string; email?: string; phone?: string }>({});
// Warm the checkout script so the first click is instant.
useEffect(() => { loadCheckout(); }, []);
// Warm the checkout script + load profile so the first click is instant.
useEffect(() => {
loadCheckout();
fetch("/api/auth/profile", { credentials: "include" })
.then((r) => r.json())
.then((d) => {
if (d.user) setProfile({ name: d.user.name, email: d.user.email, phone: d.user.phone || undefined });
})
.catch(() => {});
}, []);
const handleUpgrade = async () => {
setLoading(true);
@ -91,14 +103,23 @@ export function UpgradeButton({
const { subscriptionId, keyId } = createData as { subscriptionId: string; keyId: string };
// Build prefill from the explicit prop (if any) + the loaded profile.
// Razorpay shows these pre-filled but the user can still edit them.
const prefill: { name?: string; email?: string; contact?: string } = {};
const prefillEmail = email || profile.email;
if (profile.name) prefill.name = profile.name;
if (prefillEmail) prefill.email = prefillEmail;
if (profile.phone) prefill.contact = profile.phone;
// 2. Open Razorpay Checkout for the mandate.
const rzp = new window.Razorpay({
key: keyId, // id only — never the secret
subscription_id: subscriptionId,
name: "Tia",
description: "Tia Premium — ₹199/month",
prefill: email ? { email } : undefined,
theme: { color: "#C26B4E" }, // heirloom terracotta
image: `${window.location.origin}/icons/192.png`, // brand logo in checkout header
prefill: Object.keys(prefill).length ? prefill : undefined,
theme: { color: "#fb7185" }, // Tia rose (matches app theme-color)
handler: async (resp) => {
// 3. Verify for UX feedback only — entitlement comes via webhook.
try {