feat: Updated purchase page
Server Deploy / deploy (push) Successful in 4m40s

This commit is contained in:
2026-07-27 14:59:43 +01:00
parent 0a86167c44
commit 4ad68e3ac4
14 changed files with 1443 additions and 187 deletions
+59
View File
@@ -15,3 +15,62 @@ export function initPaddle(): Promise<Paddle | undefined> {
}
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<string, PricedLine>;
}
/*
* 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<PricePreview | null> {
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<string, PricedLine> = {};
// 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;
}
}