Remove the redundant billing account read, cache packages for one minute, prefetch the billing chunk, and parallelize independent package reads. Co-Authored-By: Claude Code <noreply@anthropic.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import type { MembershipProduct } from "./membership";
|
|
|
|
export const PAYMENT_PACKAGES_CACHE_TTL_MS = 60_000;
|
|
|
|
export type PaymentPackagesPayload = Readonly<{
|
|
enabled: boolean;
|
|
packages: MembershipProduct[];
|
|
}>;
|
|
|
|
type CacheEntry = Readonly<{
|
|
value: PaymentPackagesPayload;
|
|
fetchedAt: number;
|
|
}>;
|
|
|
|
let cache: CacheEntry | null = null;
|
|
let inflight: Promise<PaymentPackagesPayload> | null = null;
|
|
|
|
async function loadPaymentPackages(): Promise<PaymentPackagesPayload> {
|
|
const response = await fetch("/api/payment/packages", { cache: "no-store" });
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok || !payload || typeof payload !== "object") {
|
|
throw new Error("套餐支付暂时不可用,请稍后重试");
|
|
}
|
|
const record = payload as { enabled?: unknown; packages?: unknown };
|
|
return {
|
|
enabled: record.enabled === true,
|
|
packages: Array.isArray(record.packages) ? record.packages as MembershipProduct[] : [],
|
|
};
|
|
}
|
|
|
|
function refreshPaymentPackages(): Promise<PaymentPackagesPayload> {
|
|
if (inflight) return inflight;
|
|
const request = loadPaymentPackages().then((value) => {
|
|
cache = { value, fetchedAt: Date.now() };
|
|
return value;
|
|
}).finally(() => {
|
|
if (inflight === request) inflight = null;
|
|
});
|
|
inflight = request;
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* Return a fresh package response, or serve the last response while refreshing
|
|
* it in the background after the 60-second freshness window expires.
|
|
*/
|
|
export function fetchPaymentPackages(
|
|
onBackgroundRefresh?: (value: PaymentPackagesPayload) => void,
|
|
): Promise<PaymentPackagesPayload> {
|
|
if (!cache) return refreshPaymentPackages();
|
|
if (Date.now() - cache.fetchedAt < PAYMENT_PACKAGES_CACHE_TTL_MS) {
|
|
return Promise.resolve(cache.value);
|
|
}
|
|
const request = refreshPaymentPackages();
|
|
void request.then((value) => onBackgroundRefresh?.(value)).catch(() => {
|
|
// Stale package data remains usable when a background refresh fails.
|
|
});
|
|
return Promise.resolve(cache.value);
|
|
}
|
|
|
|
export function prefetchPaymentPackages(): void {
|
|
void fetchPaymentPackages().catch(() => undefined);
|
|
}
|
|
|
|
export function resetPaymentPackagesCacheForTests(): void {
|
|
cache = null;
|
|
inflight = null;
|
|
}
|