feat: 接入公开币种目录并统一多币种金额与语言初始化处理
This commit is contained in:
@@ -9,8 +9,12 @@ const ALLOWED_PARENT_ORIGINS: string[] = [
|
||||
process.env.NEXT_PUBLIC_MAIN_SITE_URL,
|
||||
process.env.NEXT_PUBLIC_PARENT_ORIGIN,
|
||||
// 开发环境
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:5173",
|
||||
"http://192.168.0.101:5173",
|
||||
"http://localhost:3801",
|
||||
"http://127.0.0.1:3801",
|
||||
"http://192.168.0.101:3801",
|
||||
// 生产环境应从环境变量读取
|
||||
].filter((o): o is string => Boolean(o));
|
||||
|
||||
@@ -58,8 +62,6 @@ export function generateCSP(): string {
|
||||
// 表单提交允许同源
|
||||
"form-action": ["'self'"],
|
||||
|
||||
// 不升级 HTTPS
|
||||
"upgrade-insecure-requests": [],
|
||||
};
|
||||
|
||||
// 构建 CSP 字符串
|
||||
@@ -90,10 +92,6 @@ export const securityHeaders = [
|
||||
key: "Content-Security-Policy",
|
||||
value: generateCSP(),
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "SAMEORIGIN", // 允许同源,通过 CSP frame-ancestors 控制跨域
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
|
||||
@@ -2,20 +2,40 @@
|
||||
* 与后端约定:金额存最小货币单位(如 NPR 2 位小数 → 分);展示时除以 10^decimals。
|
||||
*/
|
||||
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
|
||||
const DEFAULT_DECIMAL_PLACES = 2;
|
||||
|
||||
export function getCurrencyDecimalPlaces(currencyCode: string): number {
|
||||
const code = currencyCode.trim().toUpperCase();
|
||||
const row = usePlayerSessionStore
|
||||
.getState()
|
||||
.currencies.find((item) => item.code === code);
|
||||
|
||||
const decimals = row?.decimal_places;
|
||||
if (typeof decimals === "number" && Number.isFinite(decimals) && decimals >= 0) {
|
||||
return decimals;
|
||||
}
|
||||
|
||||
return DEFAULT_DECIMAL_PLACES;
|
||||
}
|
||||
|
||||
export function formatMinorAsCurrency(
|
||||
minor: number | string,
|
||||
currencyCode: string,
|
||||
decimalPlaces = DEFAULT_DECIMAL_PLACES,
|
||||
decimalPlaces?: number,
|
||||
): string {
|
||||
const resolvedDecimalPlaces =
|
||||
typeof decimalPlaces === "number" && Number.isFinite(decimalPlaces) && decimalPlaces >= 0
|
||||
? decimalPlaces
|
||||
: getCurrencyDecimalPlaces(currencyCode);
|
||||
const n = typeof minor === "string" ? Number(minor) : minor;
|
||||
if (!Number.isFinite(n)) return `${currencyCode} —`;
|
||||
const divisor = 10 ** decimalPlaces;
|
||||
const divisor = 10 ** resolvedDecimalPlaces;
|
||||
const major = n / divisor;
|
||||
return `${currencyCode} ${major.toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimalPlaces,
|
||||
maximumFractionDigits: decimalPlaces,
|
||||
minimumFractionDigits: resolvedDecimalPlaces,
|
||||
maximumFractionDigits: resolvedDecimalPlaces,
|
||||
})}`;
|
||||
}
|
||||
|
||||
@@ -24,13 +44,22 @@ export function formatMinorAsCurrency(
|
||||
*/
|
||||
export function parseDecimalInputToMinor(
|
||||
raw: string,
|
||||
decimalPlaces = DEFAULT_DECIMAL_PLACES,
|
||||
decimalPlacesOrCurrencyCode?: number | string,
|
||||
): number | null {
|
||||
const decimalPlaces =
|
||||
typeof decimalPlacesOrCurrencyCode === "string"
|
||||
? getCurrencyDecimalPlaces(decimalPlacesOrCurrencyCode)
|
||||
: typeof decimalPlacesOrCurrencyCode === "number" &&
|
||||
Number.isFinite(decimalPlacesOrCurrencyCode) &&
|
||||
decimalPlacesOrCurrencyCode >= 0
|
||||
? decimalPlacesOrCurrencyCode
|
||||
: decimalPlacesOrCurrencyCode;
|
||||
const resolvedDecimalPlaces = decimalPlaces ?? DEFAULT_DECIMAL_PLACES;
|
||||
const cleaned = raw.replace(/,/g, "").trim();
|
||||
if (cleaned === "") return null;
|
||||
const n = Number(cleaned);
|
||||
if (!Number.isFinite(n) || n < 0) return null;
|
||||
const factor = 10 ** decimalPlaces;
|
||||
const factor = 10 ** resolvedDecimalPlaces;
|
||||
const minor = Math.round(n * factor);
|
||||
if (!Number.isSafeInteger(minor)) return null;
|
||||
return minor;
|
||||
|
||||
31
src/lib/player-currency.ts
Normal file
31
src/lib/player-currency.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { PlayerMeData } from "@/types/api/player-me";
|
||||
import type { WalletBalanceData } from "@/types/api/wallet-balance";
|
||||
|
||||
type CurrencyProfile = Pick<PlayerMeData, "default_currency"> | null | undefined;
|
||||
type CurrencyBalance = Pick<WalletBalanceData, "currency_code"> | null | undefined;
|
||||
|
||||
/**
|
||||
* 玩家端当前业务币种:
|
||||
* 优先钱包接口返回币种,再回退玩家默认币种,环境变量仅作联调兜底。
|
||||
*/
|
||||
export function resolvePlayerCurrency(
|
||||
profile: CurrencyProfile,
|
||||
balance?: CurrencyBalance,
|
||||
): string {
|
||||
const fromBalance = balance?.currency_code?.trim();
|
||||
if (fromBalance) {
|
||||
return fromBalance.toUpperCase();
|
||||
}
|
||||
|
||||
const fromProfile = profile?.default_currency?.trim();
|
||||
if (fromProfile) {
|
||||
return fromProfile.toUpperCase();
|
||||
}
|
||||
|
||||
const fromEnv = process.env.NEXT_PUBLIC_LOTTERY_PLAY_CURRENCY?.trim();
|
||||
if (fromEnv) {
|
||||
return fromEnv.toUpperCase();
|
||||
}
|
||||
|
||||
return "NPR";
|
||||
}
|
||||
Reference in New Issue
Block a user