mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 08:25:35 +00:00
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
This commit is contained in:
@@ -26,13 +26,13 @@
|
||||
</div>
|
||||
<div class="shrink-0 text-right">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-xs text-gray-400 dark:text-dark-500">$</span>
|
||||
<span class="text-xs text-gray-400 dark:text-dark-500">{{ planCurrencySymbol }}</span>
|
||||
<span :class="['text-2xl font-extrabold tracking-tight', textClass]">{{ plan.price }}</span>
|
||||
<span v-if="plan.currency" class="text-xs font-medium text-gray-400 dark:text-dark-500">{{ plan.currency }}</span>
|
||||
</div>
|
||||
<span class="text-[11px] text-gray-400 dark:text-dark-500">/ {{ validitySuffix }}</span>
|
||||
<div v-if="plan.original_price" class="mt-0.5 flex items-center justify-end gap-1.5">
|
||||
<span class="text-xs text-gray-400 line-through dark:text-dark-500">${{ plan.original_price }}<template v-if="plan.currency"> {{ plan.currency }}</template></span>
|
||||
<span class="text-xs text-gray-400 line-through dark:text-dark-500">{{ planCurrencySymbol }}{{ plan.original_price }}<template v-if="plan.currency"> {{ plan.currency }}</template></span>
|
||||
<span :class="['rounded px-1 py-0.5 text-[10px] font-semibold', discountClass]">{{ discountText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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<SubscriptionPlan> = {}) =>
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user