Merge PR #486: fallback на покупку при переключении с бесплатного тарифа

fix(subscription): fall back to purchase flow for free-tariff switches
This commit is contained in:
Egor
2026-07-13 15:36:37 +03:00
committed by GitHub
3 changed files with 20 additions and 5 deletions

View File

@@ -137,6 +137,13 @@ export function TariffPickerGrid({
purchaseOptions && purchaseOptions &&
'subscription_is_expired' in purchaseOptions && 'subscription_is_expired' in purchaseOptions &&
purchaseOptions.subscription_is_expired === true; purchaseOptions.subscription_is_expired === true;
// Free (0₽) source tariff: the backend blocks the prorated switch
// (free_tariff_cannot_switch) — offer the purchase flow instead.
const isOnFreeTariff =
isTariffsMode &&
purchaseOptions &&
'subscription_on_free_tariff' in purchaseOptions &&
purchaseOptions.subscription_on_free_tariff === true;
const canSwitch = const canSwitch =
!isMultiTariff && !isMultiTariff &&
subscription && subscription &&
@@ -144,6 +151,7 @@ export function TariffPickerGrid({
!isCurrentTariff && !isCurrentTariff &&
!subscription.is_trial && !subscription.is_trial &&
!isSubscriptionExpired && !isSubscriptionExpired &&
!isOnFreeTariff &&
(subscription.is_active || subscription.is_limited); (subscription.is_active || subscription.is_limited);
const isLegacySubscription = const isLegacySubscription =
subscription && !subscription.is_trial && !subscription.tariff_id; subscription && !subscription.is_trial && !subscription.tariff_id;

View File

@@ -27,11 +27,13 @@ import type { Tariff } from '../../../types';
// ────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────
// The backend rejects a switch that must instead go through the purchase flow: // The backend rejects a switch that must instead go through the purchase flow:
// the subscription lapsed (`subscription_expired`), or it is a trial that has no // the subscription lapsed (`subscription_expired`), it is a trial that has no
// paid value to prorate and would otherwise be handed a full target period // paid value to prorate and would otherwise be handed a full target period
// (`trial_cannot_switch`, bug #629889). Both arrive as detail.code + // (`trial_cannot_switch`, bug #629889), or it sits on a free 0₽ tariff whose
// use_purchase_flow=true; some payloads use the legacy `error_code` key, so we // spammed/gifted remainder must reset rather than be prorated and carried
// accept either. // (`free_tariff_cannot_switch`, TARIFF_SWITCH_RESET_FREE_DAYS). All arrive as
// detail.code + use_purchase_flow=true; some payloads use the legacy
// `error_code` key, so we accept either.
function shouldUsePurchaseFlow(error: unknown): boolean { function shouldUsePurchaseFlow(error: unknown): boolean {
if (!(error instanceof AxiosError)) return false; if (!(error instanceof AxiosError)) return false;
const detail = error.response?.data?.detail as const detail = error.response?.data?.detail as
@@ -40,7 +42,9 @@ function shouldUsePurchaseFlow(error: unknown): boolean {
if (!detail || typeof detail !== 'object') return false; if (!detail || typeof detail !== 'object') return false;
const code = detail.code ?? detail.error_code; const code = detail.code ?? detail.error_code;
return ( return (
(code === 'subscription_expired' || code === 'trial_cannot_switch') && (code === 'subscription_expired' ||
code === 'trial_cannot_switch' ||
code === 'free_tariff_cannot_switch') &&
detail.use_purchase_flow === true detail.use_purchase_flow === true
); );
} }

View File

@@ -364,6 +364,9 @@ export interface TariffsPurchaseOptions {
// New fields for expired subscription handling // New fields for expired subscription handling
subscription_status?: string; subscription_status?: string;
subscription_is_expired?: boolean; subscription_is_expired?: boolean;
// Free (0₽) source tariff: switch is blocked (free days must reset),
// tariff cards must offer the purchase flow instead of the prorated switch
subscription_on_free_tariff?: boolean;
has_subscription?: boolean; has_subscription?: boolean;
// Multi-tariff: all available tariffs already purchased // Multi-tariff: all available tariffs already purchased
all_tariffs_purchased?: boolean; all_tariffs_purchased?: boolean;