fix(pricing): update final release prices (#288)

## Summary
- update subscription pricing to the final release amounts for monthly
and annual billing
- keep billing UI formatting aligned with the configured currency so
1.99 €/month displays correctly
- refresh subscription and Stripe sync tests to lock the new values in
place
This commit is contained in:
Víctor Falcón 2026-04-15 14:49:02 +01:00 committed by GitHub
parent 094fb1b744
commit 319ca758e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 13 deletions

View File

@ -48,7 +48,7 @@ return [
'plans' => [
'monthly' => [
'name' => 'Standard Monthly',
'price' => 7.80,
'price' => 3.99,
'original_price' => null,
'stripe_lookup_key' => env('STRIPE_PRO_MONTHLY_LOOKUP_KEY', 'whisper_pro_monthly'),
'billing_period' => 'month',
@ -64,8 +64,8 @@ return [
],
'yearly' => [
'name' => 'Standard Yearly',
'price' => 46.80,
'original_price' => 93.60,
'price' => 23.88,
'original_price' => 47.88,
'stripe_lookup_key' => env('STRIPE_PRO_YEARLY_LOOKUP_KEY', 'whisper_pro_yearly'),
'billing_period' => 'year',
'features' => [

View File

@ -96,11 +96,15 @@ function PlanCard({
plan,
isSelected,
onSelect,
currency,
locale,
}: {
planKey: string;
plan: Plan;
isSelected: boolean;
onSelect: () => void;
currency: string;
locale: string;
}) {
const savingsPercent =
plan.original_price && plan.billing_period === 'year'
@ -137,7 +141,7 @@ function PlanCard({
</div>
<div className="mt-1 flex items-baseline gap-1">
<span className="text-xl font-bold">
${monthlyEquivalent.toFixed(0)}
{formatCurrency(monthlyEquivalent * 100, currency, locale)}
</span>
<span className="text-sm text-muted-foreground">
{__('/month')}
@ -145,7 +149,8 @@ function PlanCard({
</div>
{plan.billing_period === 'year' && (
<span className="mt-2 text-xs text-muted-foreground">
{__('Billed annually at')} ${plan.price}
{__('Billed annually at')}{' '}
{formatCurrency(plan.price * 100, currency, locale)}
</span>
)}
</button>
@ -155,9 +160,13 @@ function PlanCard({
function UpgradeSection({
planEntries,
defaultPlan,
currency,
locale,
}: {
planEntries: [string, Plan][];
defaultPlan: string;
currency: string;
locale: string;
}) {
const [selectedPlan, setSelectedPlan] = useState(defaultPlan);
const selectedPlanData = planEntries.find(
@ -188,6 +197,8 @@ function UpgradeSection({
plan={plan}
isSelected={key === selectedPlan}
onSelect={() => setSelectedPlan(key)}
currency={currency}
locale={locale}
/>
))}
</div>
@ -310,6 +321,8 @@ export default function Billing() {
<UpgradeSection
planEntries={planEntries}
defaultPlan={pricing.defaultPlan}
currency={pricing.currency}
locale={locale}
/>
)}
</SettingsLayout>

View File

@ -190,10 +190,18 @@ test('pricing config includes all plan details', function () {
->component('welcome')
->has('pricing.plans.monthly', fn ($plan) => $plan
->has('name')
->has('price')
->has('original_price')
->where('price', 3.99)
->where('original_price', null)
->has('stripe_lookup_key')
->has('billing_period')
->where('billing_period', 'month')
->has('features')
)
->has('pricing.plans.yearly', fn ($plan) => $plan
->has('name')
->where('price', 23.88)
->where('original_price', 47.88)
->has('stripe_lookup_key')
->where('billing_period', 'year')
->has('features')
)
->has('pricing.promo', fn ($promo) => $promo

View File

@ -53,14 +53,14 @@ beforeEach(function () {
'subscriptions.plans' => [
'monthly' => [
'name' => 'Monthly',
'price' => 7.80,
'price' => 3.99,
'billing_period' => 'month',
'stripe_lookup_key' => 'whisper_pro_monthly',
'features' => [],
],
'yearly' => [
'name' => 'Yearly',
'price' => 46.80,
'price' => 23.88,
'billing_period' => 'year',
'stripe_lookup_key' => 'whisper_pro_yearly',
'features' => [],
@ -76,7 +76,7 @@ test('skips plans without stripe_lookup_key', function () {
'subscriptions.plans' => [
'monthly' => [
'name' => 'Monthly',
'price' => 7.80,
'price' => 3.99,
'billing_period' => 'month',
'features' => [],
// no stripe_lookup_key
@ -99,8 +99,8 @@ test('skips plans without stripe_lookup_key', function () {
test('reports correct counts when all prices are already in sync', function () {
bindMockStripeClientForSync([
'whisper_pro_monthly' => makeStripePrice('price_monthly', 780, 'eur', 'month'),
'whisper_pro_yearly' => makeStripePrice('price_yearly', 4680, 'eur', 'year'),
'whisper_pro_monthly' => makeStripePrice('price_monthly', 399, 'eur', 'month'),
'whisper_pro_yearly' => makeStripePrice('price_yearly', 2388, 'eur', 'year'),
]);
$this->artisan('stripe:sync-prices')