From a05b8732157d79716e5c38085123a385db308993 Mon Sep 17 00:00:00 2001 From: yan9651688 <174289968+yan9651688@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:33:04 +0800 Subject: [PATCH] fix(payment): prevent configured currencies from looking like USD Subscription plans can carry a display-only ISO currency code, but the admin plan table and user plan cards still used a hard-coded dollar sign. Resolve the symbol through the shared currency helper while retaining existing number formatting, the currency code label, and legacy blank-currency behavior. Constraint: Plans without a currency must keep the legacy USD display Rejected: Reformat complete prices with Intl.NumberFormat | it would change existing decimals and remove the explicit currency code Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep plan currency symbols aligned across admin and user displays Tested: targeted Vitest 4 tests; targeted ESLint; vue-tsc; Vite production build; full Vitest 1222 tests passed Not-tested: Full Vitest has 2 unrelated upstream failures in admin.system.rollback.spec.ts Related: https://github.com/Wei-Shaw/sub2api/issues/4609 --- .../payment/SubscriptionPlanCard.vue | 6 +- .../__tests__/SubscriptionPlanCard.spec.ts | 13 ++- .../admin/orders/AdminPaymentPlansView.vue | 9 +- .../__tests__/AdminPaymentPlansView.spec.ts | 106 ++++++++++++++++++ 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 frontend/src/views/admin/orders/__tests__/AdminPaymentPlansView.spec.ts diff --git a/frontend/src/components/payment/SubscriptionPlanCard.vue b/frontend/src/components/payment/SubscriptionPlanCard.vue index d37645642a..55cf3384c5 100644 --- a/frontend/src/components/payment/SubscriptionPlanCard.vue +++ b/frontend/src/components/payment/SubscriptionPlanCard.vue @@ -26,13 +26,13 @@
- $ + {{ planCurrencySymbol }} {{ plan.price }} {{ plan.currency }}
/ {{ validitySuffix }}
- ${{ plan.original_price }} + {{ planCurrencySymbol }}{{ plan.original_price }} {{ discountText }}
@@ -106,6 +106,7 @@ import type { SubscriptionPlan } from '@/types/payment' import type { UserSubscription } from '@/types' import { useAppStore } from '@/stores/app' import { hasPeakRate as groupHasPeakRate, formatPeakRateWindow, serverTimezoneLabel } from '@/utils/peak-rate' +import { currencySymbol } from '@/components/payment/currency' import { platformAccentBarClass, platformBadgeLightClass, @@ -148,6 +149,7 @@ const rateDisplay = computed(() => { }) const appStore = useAppStore() +const planCurrencySymbol = computed(() => currencySymbol(props.plan.currency || 'USD')) const hasPeakRate = computed(() => groupHasPeakRate(props.plan)) diff --git a/frontend/src/components/payment/__tests__/SubscriptionPlanCard.spec.ts b/frontend/src/components/payment/__tests__/SubscriptionPlanCard.spec.ts index 230692d7fb..5e17321359 100644 --- a/frontend/src/components/payment/__tests__/SubscriptionPlanCard.spec.ts +++ b/frontend/src/components/payment/__tests__/SubscriptionPlanCard.spec.ts @@ -2,6 +2,7 @@ import { mount } from "@vue/test-utils"; import { describe, expect, it } from "vitest"; import { createPinia } from "pinia"; import { createI18n } from "vue-i18n"; +import type { SubscriptionPlan } from "@/types/payment"; import SubscriptionPlanCard from "../SubscriptionPlanCard.vue"; const i18n = createI18n({ @@ -25,7 +26,7 @@ const i18n = createI18n({ }, }); -const mountPlanCard = (groupPlatform: string) => +const mountPlanCard = (groupPlatform: string, overrides: Partial = {}) => mount(SubscriptionPlanCard, { props: { plan: { @@ -41,6 +42,7 @@ const mountPlanCard = (groupPlatform: string) => validity_unit: "day", supported_model_scopes: ["claude", "gemini_text", "gemini_image"], is_active: true, + ...overrides, }, }, global: { plugins: [i18n, createPinia()] }, @@ -62,4 +64,13 @@ describe("SubscriptionPlanCard", () => { expect(text).toContain("Gemini"); expect(text).toContain("Imagen"); }); + + it("uses the configured currency symbol while preserving USD for legacy plans", () => { + const cnyPlan = mountPlanCard("openai", { currency: "CNY", original_price: 20 }).text(); + + expect(cnyPlan).toContain("¥10CNY"); + expect(cnyPlan).toContain("¥20CNY"); + expect(mountPlanCard("openai", { currency: "USD" }).text()).toContain("$10USD"); + expect(mountPlanCard("openai", { currency: "" }).text()).toContain("$10"); + }); }); diff --git a/frontend/src/views/admin/orders/AdminPaymentPlansView.vue b/frontend/src/views/admin/orders/AdminPaymentPlansView.vue index 17a17a5f18..93a5303a76 100644 --- a/frontend/src/views/admin/orders/AdminPaymentPlansView.vue +++ b/frontend/src/views/admin/orders/AdminPaymentPlansView.vue @@ -29,9 +29,9 @@