From bb52c5b10d816474b909ff4459f666cc96c0a075 Mon Sep 17 00:00:00 2001 From: majdyz Date: Mon, 13 Apr 2026 23:50:30 +0700 Subject: [PATCH] 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) --- .../backend/backend/data/credit.py | 24 ++++++++++--------- .../SubscriptionTierSection.test.tsx | 13 ++++++++++ .../useSubscriptionTierSection.ts | 11 ++++++--- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/autogpt_platform/backend/backend/data/credit.py b/autogpt_platform/backend/backend/data/credit.py index 1ec5a0e636..c9ed3051cd 100644 --- a/autogpt_platform/backend/backend/data/credit.py +++ b/autogpt_platform/backend/backend/data/credit.py @@ -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( diff --git a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/__tests__/SubscriptionTierSection.test.tsx b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/__tests__/SubscriptionTierSection.test.tsx index 3b66c80376..a0693f446a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/__tests__/SubscriptionTierSection.test.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/__tests__/SubscriptionTierSection.test.tsx @@ -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(); + + // 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(); + }); }); diff --git a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/useSubscriptionTierSection.ts b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/useSubscriptionTierSection.ts index f1fc3f6d24..fc20c3c5f3 100644 --- a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/useSubscriptionTierSection.ts +++ b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/useSubscriptionTierSection.ts @@ -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