From 6356e3bdc45cc6dc6710ad8a532fa9b842d02a36 Mon Sep 17 00:00:00 2001 From: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Date: Sat, 21 Feb 2026 12:26:49 +0530 Subject: [PATCH] Payment Flow resolved (#127) * feat: implement payment success deep link handling and confirmation banner - Added a new state to manage payment confirmation in BillingPanel. - Implemented a deep link listener for payment success and cancellation events. - Created functions to build payment success and cancel deep links. - Displayed a confirmation banner upon successful payment, which auto-hides after 5 seconds. * feat: enhance payment success handling in BillingPanel - Updated the onPaymentSuccess function to be asynchronous. - Added a call to fetch the current billing plan from the backend after a successful payment. - Implemented error handling for the API call to ensure robustness. * fix: improve payment success timeout handling in BillingPanel - Introduced a timeoutRef to manage the auto-hide functionality of the payment confirmation banner. - Ensured that the timeout is cleared when the component unmounts to prevent memory leaks. --- .../settings/panels/BillingPanel.tsx | 59 +++++++++++++++++++ src/utils/deeplink.ts | 9 +++ src/utils/desktopDeepLinkListener.ts | 45 ++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/src/components/settings/panels/BillingPanel.tsx b/src/components/settings/panels/BillingPanel.tsx index 6ec781155..0d704211b 100644 --- a/src/components/settings/panels/BillingPanel.tsx +++ b/src/components/settings/panels/BillingPanel.tsx @@ -38,8 +38,10 @@ const BillingPanel = () => { const [paymentMethod, setPaymentMethod] = useState<'card' | 'crypto'>('card'); const [isPurchasing, setIsPurchasing] = useState(false); const [purchasingTier, setPurchasingTier] = useState(null); + const [paymentConfirmed, setPaymentConfirmed] = useState(false); const pollRef = useRef | null>(null); const pollStartRef = useRef(0); + const timeoutRef = useRef(null); // Fetch current plan on mount useEffect(() => { @@ -60,6 +62,40 @@ const BillingPanel = () => { }; }, []); + // Handle payment:success deep link event + useEffect(() => { + const onPaymentSuccess = async () => { + // Stop any in-flight poll — we know checkout completed + if (pollRef.current) { + clearInterval(pollRef.current); + pollRef.current = null; + } + setIsPurchasing(false); + setPurchasingTier(null); + setPaymentConfirmed(true); + + // Fetch current plan from backend, then refresh user/teams in store + try { + await billingApi.getCurrentPlan(); + } catch (e) { + console.error('Failed to fetch current plan after payment', e); + } + dispatch(fetchCurrentUser()); + + // Auto-hide the success banner after 5 s + timeoutRef.current = window.setTimeout(() => setPaymentConfirmed(false), 5_000); + }; + + window.addEventListener('payment:success', onPaymentSuccess); + return () => { + window.removeEventListener('payment:success', onPaymentSuccess); + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [dispatch]); + // ── Poll for plan change after checkout ───────────────────────────── const currentTierRef = useRef(currentTier); useEffect(() => { @@ -295,6 +331,29 @@ const BillingPanel = () => { })} + {/* ── Payment confirmed banner ─────────────────────────── */} + {paymentConfirmed && ( +
+
+ + + +

+ Payment confirmed! Your plan has been updated. +

+
+
+ )} + {/* ── Purchasing overlay message ────────────────────────── */} {isPurchasing && (
diff --git a/src/utils/deeplink.ts b/src/utils/deeplink.ts index 476c625a7..12e8a5124 100644 --- a/src/utils/deeplink.ts +++ b/src/utils/deeplink.ts @@ -21,3 +21,12 @@ export const buildDesktopDeeplink = (token: string): string => { const encoded = encodeURIComponent(token); return `${DESKTOP_SCHEME}://auth?token=${encoded}`; }; + +export const buildPaymentSuccessDeeplink = (sessionId: string): string => { + const encoded = encodeURIComponent(sessionId); + return `${DESKTOP_SCHEME}://payment/success?session_id=${encoded}`; +}; + +export const buildPaymentCancelDeeplink = (): string => { + return `${DESKTOP_SCHEME}://payment/cancel`; +}; diff --git a/src/utils/desktopDeepLinkListener.ts b/src/utils/desktopDeepLinkListener.ts index 7b43910b1..7e21306d9 100644 --- a/src/utils/desktopDeepLinkListener.ts +++ b/src/utils/desktopDeepLinkListener.ts @@ -29,6 +29,46 @@ const handleAuthDeepLink = async (parsed: URL) => { window.location.hash = '/onboarding'; }; +/** + * Handle `alphahuman://payment/success?session_id=...` deep links. + * Fired when a Stripe checkout session completes and the browser redirects + * back to the desktop app. + */ +const handlePaymentDeepLink = async (parsed: URL) => { + const path = parsed.pathname.replace(/^\/+/, ''); + + try { + await invoke('show_window'); + } catch { + // Not fatal + } + + if (path === 'success') { + const sessionId = parsed.searchParams.get('session_id'); + + if (!sessionId) { + console.warn('[DeepLink] Payment success missing session_id'); + return; + } + + console.log('[DeepLink] Payment success, session_id:', sessionId); + + // Broadcast to the app so billing components can react + window.dispatchEvent( + new CustomEvent('payment:success', { detail: { sessionId } }), + ); + + // Navigate to billing settings to show confirmation + window.location.hash = '/settings/billing'; + } else if (path === 'cancel') { + console.log('[DeepLink] Payment cancelled'); + window.dispatchEvent(new CustomEvent('payment:cancel', {})); + window.location.hash = '/settings/billing'; + } else { + console.warn('[DeepLink] Unknown payment path:', path); + } +}; + /** * Handle `alphahuman://oauth/success?integrationId=...&skillId=...` * and `alphahuman://oauth/error?error=...&provider=...` deep links. @@ -74,6 +114,8 @@ const handleOAuthDeepLink = async (parsed: URL) => { * - `alphahuman://auth?token=...` → login flow * - `alphahuman://oauth/success?...` → OAuth completion * - `alphahuman://oauth/error?...` → OAuth failure + * - `alphahuman://payment/success?session_id=...` → Stripe payment confirmation + * - `alphahuman://payment/cancel` → Stripe payment cancellation */ const handleDeepLinkUrls = async (urls: string[] | null | undefined) => { if (!urls || urls.length === 0) { @@ -95,6 +137,9 @@ const handleDeepLinkUrls = async (urls: string[] | null | undefined) => { case 'oauth': await handleOAuthDeepLink(parsed); break; + case 'payment': + await handlePaymentDeepLink(parsed); + break; default: console.warn('[DeepLink] Unknown deep link hostname:', parsed.hostname); break;