fix(platform): cleanup cancelled URL param, parallelize stripe calls, add test

- Strip ?subscription=cancelled from address bar in useSubscriptionTierSection
  alongside the existing ?subscription=success cleanup so Stripe cancel
  redirects don't leave stale params in the URL
- Parallelize the two sequential stripe.Subscription.list calls on the
  cancel webhook path using asyncio.gather to reduce handler latency
- Add a test for ?subscription=cancelled being a no-op (no toast, URL cleaned)
This commit is contained in:
majdyz
2026-04-13 23:50:30 +07:00
parent 4bd79d8f6e
commit bb52c5b10d
3 changed files with 34 additions and 14 deletions

View File

@@ -1490,17 +1490,19 @@ async def sync_subscription_from_stripe(stripe_subscription: dict) -> None:
# customer; if they do, keep the user's current tier (the other sub's
# own event will/has already set the correct tier).
try:
other_subs_active = await run_in_threadpool(
stripe.Subscription.list,
customer=customer_id,
status="active",
limit=10,
)
other_subs_trialing = await run_in_threadpool(
stripe.Subscription.list,
customer=customer_id,
status="trialing",
limit=10,
other_subs_active, other_subs_trialing = await asyncio.gather(
run_in_threadpool(
stripe.Subscription.list,
customer=customer_id,
status="active",
limit=10,
),
run_in_threadpool(
stripe.Subscription.list,
customer=customer_id,
status="trialing",
limit=10,
),
)
except stripe.StripeError:
logger.warning(

View File

@@ -307,4 +307,17 @@ describe("SubscriptionTierSection", () => {
// URL param must be stripped so a page refresh doesn't re-trigger the toast
expect(mockRouterReplace).toHaveBeenCalledWith("/profile/credits");
});
it("clears URL param but shows no toast when ?subscription=cancelled is present", async () => {
mockSearchParams.set("subscription", "cancelled");
setupMocks();
render(<SubscriptionTierSection />);
// The cancelled param must be stripped from the URL (same hygiene as success)
await waitFor(() => {
expect(mockRouterReplace).toHaveBeenCalledWith("/profile/credits");
});
// No toast should fire — the user simply abandoned checkout
expect(mockToast).not.toHaveBeenCalled();
});
});

View File

@@ -43,9 +43,14 @@ export function useSubscriptionTierSection() {
description:
"Your plan has been updated. It may take a moment to reflect.",
});
// Strip ?subscription=success from the URL so a page refresh does not
// re-trigger the toast, and so a second checkout in the same session
// correctly fires the toast again.
}
// Strip ?subscription=success|cancelled from the URL so a page refresh
// does not re-trigger side-effects, and so a second checkout in the same
// session correctly fires the toast again.
if (
subscriptionStatus === "success" ||
subscriptionStatus === "cancelled"
) {
router.replace(pathname);
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- refetch and toast