import { initializePaddle, type Paddle } from "@paddle/paddle-js"; let cached: Promise | null = null; /* One Paddle instance for the app. The token and environment are baked into the * build (NEXT_PUBLIC_*), never fetched, so a production build can never load a * sandbox token by accident. */ export function initPaddle(): Promise { if (!cached) { cached = initializePaddle({ environment: (process.env.NEXT_PUBLIC_PADDLE_ENV as "sandbox" | "production") ?? "sandbox", token: process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN ?? "", }); } return cached; } export interface PricedLine { priceId: string; /* Already localised and currency-formatted by Paddle, e.g. "£39.00". The line * total for the quantity, not the unit price. */ total: string; unit: string; } export interface PricePreview { currency: string; /* Grand total, formatted. */ total: string; lines: Record; } /* * previewPrices asks Paddle for the real localised prices of a set of line items, * so the order summary shows what the customer will actually pay rather than a * hardcoded number that would drift from the dashboard. * * It returns null when Paddle is unavailable or a price cannot be previewed (an * unconfigured sandbox price, an ad blocker). The caller falls back to showing * the line items without amounts rather than a wrong total — the real figure * still appears in the checkout overlay, which is the authority. */ export async function previewPrices( items: { priceId: string; quantity: number }[], ): Promise { if (items.length === 0) return { currency: "", total: "", lines: {} }; const paddle = await initPaddle(); if (!paddle) return null; try { const res = await paddle.PricePreview({ items: items.map((i) => ({ priceId: i.priceId, quantity: i.quantity })), }); const currency = res.data.currencyCode; const lines: Record = {}; // Paddle gives per-line totals but no grand total, so sum the raw minor // units and format once. The checkout overlay is the authority; this is // the honest preview beside it. let subtotalMinor = 0; for (const li of res.data.details.lineItems) { lines[li.price.id] = { priceId: li.price.id, total: li.formattedTotals.subtotal, unit: li.formattedUnitTotals.subtotal, }; subtotalMinor += Number.parseInt(li.totals.subtotal, 10) || 0; } const total = new Intl.NumberFormat(undefined, { style: "currency", currency, }).format(subtotalMinor / 100); return { currency, total, lines }; } catch { return null; } }