mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: support method query param fallback for external browser redirects
When payment providers redirect to external browser, sessionStorage is unavailable. TopUpResult now reads method from URL query params and polls via /latest endpoint as fallback.
This commit is contained in:
@@ -114,6 +114,14 @@ export const balanceApi = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get latest pending payment by method (fallback when sessionStorage unavailable)
|
||||
getLatestPayment: async (method: string): Promise<PendingPayment> => {
|
||||
const response = await apiClient.get<PendingPayment>(
|
||||
`/cabinet/balance/pending-payments/${encodeURIComponent(method)}/latest`,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Manually check payment status
|
||||
checkPaymentStatus: async (method: string, paymentId: number): Promise<ManualCheckResponse> => {
|
||||
const response = await apiClient.post<ManualCheckResponse>(
|
||||
|
||||
@@ -226,6 +226,9 @@ export default function TopUpResult() {
|
||||
// Load saved payment info from sessionStorage (once on mount)
|
||||
const [pendingInfo] = useState(() => loadTopUpPendingInfo());
|
||||
|
||||
// Fallback: read method from query params (for external browser redirects where sessionStorage is unavailable)
|
||||
const methodFromUrl = searchParams.get('method');
|
||||
|
||||
// Detect if user arrived via redirect with success param (no polling needed)
|
||||
const redirectStatus = searchParams.get('status') || searchParams.get('payment');
|
||||
const isRedirectSuccess = redirectStatus
|
||||
@@ -233,28 +236,30 @@ export default function TopUpResult() {
|
||||
: searchParams.get('success') === 'true';
|
||||
const isRedirectFailed = redirectStatus ? isFailedStatus(redirectStatus) : false;
|
||||
|
||||
// Determine if we can poll (need method + numeric payment_id)
|
||||
// Determine if we can poll by specific payment_id (need method + numeric payment_id)
|
||||
const parsedPaymentId = pendingInfo?.payment_id ? parseInt(pendingInfo.payment_id, 10) : NaN;
|
||||
const canPoll =
|
||||
const canPollById =
|
||||
!!(pendingInfo?.method_id && !isNaN(parsedPaymentId)) &&
|
||||
!isRedirectSuccess &&
|
||||
!isRedirectFailed;
|
||||
|
||||
// Poll payment status
|
||||
// Fallback: poll by method via /latest endpoint when no sessionStorage data
|
||||
const canPollByMethod =
|
||||
!canPollById && !!methodFromUrl && !isRedirectSuccess && !isRedirectFailed;
|
||||
|
||||
// Poll payment status by specific ID (primary path — sessionStorage available)
|
||||
const { data: paymentStatus, refetch } = useQuery({
|
||||
queryKey: ['topup-status', pendingInfo?.method_id, parsedPaymentId],
|
||||
queryFn: () => balanceApi.getPendingPayment(pendingInfo!.method_id, parsedPaymentId),
|
||||
enabled: canPoll && !pollTimedOut,
|
||||
enabled: canPollById && !pollTimedOut,
|
||||
refetchInterval: (query) => {
|
||||
const payment = query.state.data;
|
||||
if (!payment) return POLL_INTERVAL_MS;
|
||||
|
||||
// Stop polling if paid or failed
|
||||
if (payment.is_paid || isPaidStatus(payment.status) || isFailedStatus(payment.status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check timeout
|
||||
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
||||
setPollTimedOut(true);
|
||||
return false;
|
||||
@@ -265,34 +270,64 @@ export default function TopUpResult() {
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
// Poll payment status by method latest (fallback — external browser, no sessionStorage)
|
||||
const { data: latestPayment, refetch: refetchLatest } = useQuery({
|
||||
queryKey: ['topup-status-latest', methodFromUrl],
|
||||
queryFn: () => balanceApi.getLatestPayment(methodFromUrl!),
|
||||
enabled: canPollByMethod && !pollTimedOut,
|
||||
refetchInterval: (query) => {
|
||||
const payment = query.state.data;
|
||||
if (!payment) return POLL_INTERVAL_MS;
|
||||
|
||||
if (payment.is_paid || isPaidStatus(payment.status) || isFailedStatus(payment.status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
||||
setPollTimedOut(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return POLL_INTERVAL_MS;
|
||||
},
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
// Merge both polling sources
|
||||
const effectivePayment = paymentStatus ?? latestPayment;
|
||||
|
||||
const handleRetryPoll = useCallback(() => {
|
||||
pollStart.current = Date.now();
|
||||
setPollTimedOut(false);
|
||||
refetch();
|
||||
}, [setPollTimedOut, refetch]);
|
||||
if (canPollById) {
|
||||
refetch();
|
||||
} else {
|
||||
refetchLatest();
|
||||
}
|
||||
}, [canPollById, setPollTimedOut, refetch, refetchLatest]);
|
||||
|
||||
const handleGoBack = useCallback(() => {
|
||||
clearTopUpPendingInfo();
|
||||
navigate('/balance', { replace: true });
|
||||
}, [navigate]);
|
||||
|
||||
// Redirect to balance if no data at all
|
||||
// Redirect to balance if absolutely no data source available
|
||||
useEffect(() => {
|
||||
if (!pendingInfo && !redirectStatus) {
|
||||
if (!pendingInfo && !redirectStatus && !methodFromUrl) {
|
||||
navigate('/balance', { replace: true });
|
||||
}
|
||||
}, [pendingInfo, redirectStatus, navigate]);
|
||||
}, [pendingInfo, redirectStatus, methodFromUrl, navigate]);
|
||||
|
||||
// Determine current visual state
|
||||
const amountKopeks = paymentStatus?.amount_kopeks ?? pendingInfo?.amount_kopeks ?? null;
|
||||
const amountKopeks = effectivePayment?.amount_kopeks ?? pendingInfo?.amount_kopeks ?? null;
|
||||
|
||||
const resolvedPaid =
|
||||
isRedirectSuccess ||
|
||||
paymentStatus?.is_paid ||
|
||||
(paymentStatus && isPaidStatus(paymentStatus.status));
|
||||
effectivePayment?.is_paid ||
|
||||
(effectivePayment && isPaidStatus(effectivePayment.status));
|
||||
|
||||
const resolvedFailed =
|
||||
isRedirectFailed || (paymentStatus && isFailedStatus(paymentStatus.status));
|
||||
isRedirectFailed || (effectivePayment && isFailedStatus(effectivePayment.status));
|
||||
|
||||
// Clean up sessionStorage and invalidate queries when payment resolves
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user