mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(switch-tariff): route trial & expired subs to the purchase flow (#629889)
The switch sheet's fallback checked detail.error_code, but the backend sends detail.code — so the expired-fallback was effectively dead. Centralize it into shouldUsePurchaseFlow(), which accepts either key and now also handles the new trial_cannot_switch code: a trial has no paid value to prorate, so the backend rejects the switch and the sheet hands the tariff to the regular purchase form instead of surfacing a raw error. The switch button is already hidden for trials via canSwitch; this is the server-side backstop for races / direct calls, and it un-breaks the expired fallback at the same time.
This commit is contained in:
@@ -26,6 +26,25 @@ import type { Tariff } from '../../../types';
|
|||||||
// TariffPurchaseForm instead of attempting another switch.
|
// TariffPurchaseForm instead of attempting another switch.
|
||||||
// ──────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// paid value to prorate and would otherwise be handed a full target period
|
||||||
|
// (`trial_cannot_switch`, bug #629889). Both 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 {
|
||||||
|
if (!(error instanceof AxiosError)) return false;
|
||||||
|
const detail = error.response?.data?.detail as
|
||||||
|
| { code?: string; error_code?: string; use_purchase_flow?: boolean }
|
||||||
|
| undefined;
|
||||||
|
if (!detail || typeof detail !== 'object') return false;
|
||||||
|
const code = detail.code ?? detail.error_code;
|
||||||
|
return (
|
||||||
|
(code === 'subscription_expired' || code === 'trial_cannot_switch') &&
|
||||||
|
detail.use_purchase_flow === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export interface SwitchTariffSheetProps {
|
export interface SwitchTariffSheetProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
tariffId: number | null;
|
tariffId: number | null;
|
||||||
@@ -69,22 +88,15 @@ export function SwitchTariffSheet({
|
|||||||
navigate('/subscriptions', { replace: true });
|
navigate('/subscriptions', { replace: true });
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
// Backend signal: the subscription lapsed mid-flight. Hand the
|
// Backend signal: this subscription can't be switched (it lapsed, or it's
|
||||||
// selected tariff back to the parent so it can open the regular
|
// a trial). Hand the selected tariff back to the parent so it opens the
|
||||||
// purchase form instead.
|
// regular purchase form instead.
|
||||||
if (error instanceof AxiosError) {
|
if (shouldUsePurchaseFlow(error)) {
|
||||||
const detail = error.response?.data?.detail;
|
const targetTariff = tariffs.find((tariff) => tariff.id === tariffId);
|
||||||
if (
|
if (targetTariff) {
|
||||||
typeof detail === 'object' &&
|
onClose();
|
||||||
detail?.error_code === 'subscription_expired' &&
|
onExpiredFallback(targetTariff);
|
||||||
detail?.use_purchase_flow === true
|
queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] });
|
||||||
) {
|
|
||||||
const targetTariff = tariffs.find((tariff) => tariff.id === tariffId);
|
|
||||||
if (targetTariff) {
|
|
||||||
onClose();
|
|
||||||
onExpiredFallback(targetTariff);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -213,15 +225,10 @@ export function SwitchTariffSheet({
|
|||||||
|
|
||||||
{switchMutation.isError &&
|
{switchMutation.isError &&
|
||||||
(() => {
|
(() => {
|
||||||
// Suppress the toast when the subscription_expired
|
// Suppress the toast when the purchase-flow fallback already
|
||||||
// fallback already triggered: the parent is now
|
// triggered (expired / trial): the parent is now showing the
|
||||||
// showing the regular purchase form, surfacing the
|
// regular purchase form, so the raw axios message would mislead.
|
||||||
// raw axios message here would be misleading.
|
if (shouldUsePurchaseFlow(switchMutation.error)) {
|
||||||
const detail =
|
|
||||||
switchMutation.error instanceof AxiosError
|
|
||||||
? switchMutation.error.response?.data?.detail
|
|
||||||
: null;
|
|
||||||
if (typeof detail === 'object' && detail?.error_code === 'subscription_expired') {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user