export type MembershipProductType = "credit_pack" | "trial" | "subscription"; export type MembershipEntitlement = { featureKey: string; allowanceType: string; allowanceCount: number | null; resetPeriod: string; modelTier: string | null; fairUsePolicyId: string | null; metadata: unknown; }; export type MembershipProduct = { id: string; productId: string; code: string; version: number; name: string; description: string; productType: MembershipProductType; billingPeriod: "none" | "day" | "month" | "year"; intervalCount: number; priceCents: number; currency: string; credits: number; entitlements: MembershipEntitlement[]; }; export type MembershipPlanAlias = "trial" | "monthly" | "yearly"; export function membershipHref(source: string, options: { plan?: MembershipPlanAlias } = {}) { const params = new URLSearchParams({ source }); if (options.plan) params.set("plan", options.plan); return `/membership?${params.toString()}`; } export function planDisplayLabel(alias: MembershipPlanAlias) { const labels: Record = { trial: "体验", monthly: "月卡", yearly: "年卡", }; return labels[alias]; } export function formatPrice(priceCents: number, currency = "CNY") { return `${currency === "CNY" ? "¥" : `${currency} `}${(priceCents / 100).toFixed(2)}`; } export function formatMembershipDate(value: string | null | undefined) { if (!value) return ""; const date = new Date(value); if (Number.isNaN(date.getTime())) return ""; return date.toLocaleDateString("zh-CN"); } export function orderStatusLabel(status: string | null | undefined) { const labels: Record = { paid: "已支付", pending: "待支付", failed: "支付失败", closed: "已关闭", cancelled: "已取消", }; return (status && labels[status]) || status || "未知"; } export function membershipSourceNotice(source: string | null | undefined) { const notices: Record = { credits: "来自对话页余额入口", "account-menu": "来自账户菜单", "insufficient-credits": "余额不足,请先补充点数或开通会员后再发送问题", rectification: "生时校正需要点数或会员权益", }; return notices[source ?? ""] ?? ""; } export const BALANCE_SYNC_KEY = "jyotisha:balance:updated"; export const BALANCE_CHANGED_EVENT = "jyotisha:balance:changed"; export function notifyBalanceChanged(credits: number) { try { localStorage.setItem(BALANCE_SYNC_KEY, JSON.stringify({ credits, at: Date.now() })); } catch { // Storage can be unavailable in privacy modes; the window event still fires. } if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent(BALANCE_CHANGED_EVENT, { detail: { credits } })); } } export function planAlias(product: Pick): MembershipPlanAlias | null { if (product.productType === "trial") return "trial"; if (product.productType !== "subscription") return null; if (product.billingPeriod === "month") return "monthly"; if (product.billingPeriod === "year") return "yearly"; return null; } export function selectMembershipPlans(products: readonly MembershipProduct[]) { const aliases: MembershipPlanAlias[] = ["trial", "monthly", "yearly"]; return aliases.flatMap((alias) => { const product = products.find((candidate) => planAlias(candidate) === alias); return product ? [product] : []; }); } export function formatProductDuration(product: Pick) { if (product.productType === "credit_pack") return "购买后立即到账"; const unit = product.billingPeriod === "day" ? "天" : product.billingPeriod === "month" ? "个月" : "年"; return `${product.intervalCount} ${unit}`; } export function productAudience(product: Pick) { if (product.productType === "trial") return "适合初次体验完整咨询流程"; if (product.productType === "credit_pack") return "适合按需补充点数、灵活使用"; if (product.billingPeriod === "year") return "适合长期记录与持续复盘"; return "适合稳定使用咨询与报告功能"; } const featureLabels: Record = { "chat.standard": "标准 AI 咨询", "report.full": "完整个人报告", rectification: "生时校正", }; export function entitlementLabel(entitlement: MembershipEntitlement) { const feature = featureLabels[entitlement.featureKey] ?? entitlement.featureKey; if (entitlement.allowanceType === "unlimited") return `${feature}不限点数(遵循合理使用规则)`; if (entitlement.allowanceType === "credits") return `${entitlement.allowanceCount ?? 0} 点咨询点数`; if (typeof entitlement.allowanceCount === "number") return `${feature} ${entitlement.allowanceCount} 次`; return feature; } export function redeemErrorMessage(status: number, payload: unknown) { if (status === 429) return "请求过于频繁"; if (payload && typeof payload === "object" && "code" in payload) { const code = String((payload as { code?: unknown }).code ?? ""); const messages: Record = { invalid_code: "兑换码不存在", expired_code: "兑换码已过期", already_redeemed: "兑换码已被使用", account_not_eligible: "兑换码不适用于当前账户", rate_limited: "请求过于频繁", }; if (messages[code]) return messages[code]; } if (status >= 500) return "系统异常,请稍后重试"; if (payload && typeof payload === "object" && "error" in payload) { const error = (payload as { error?: unknown }).error; if (typeof error === "string" && error) return error; } return "系统异常,请稍后重试"; }