import bridge from '@arcblock/bridge'; import useArcblockBrowser from '@arcblock/react-hooks/lib/useBrowser'; import { useLocaleContext } from '@arcblock/ux/lib/Locale/context'; import { Box, Grow, Link, Paper, Stack, styled, Typography } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; import { joinURL } from 'ufo'; import Button from '@arcblock/ux/lib/Button'; import { usePaymentContext } from '../contexts/payment'; import { VendorPlaceholder, VendorProgressItem } from './progress-item'; type Props = { mode: string; pageInfo?: any; vendorCount?: number; sessionId?: string; message: string; action: string; payee: string; invoiceId?: string; subscriptionId?: string; subscriptions?: any[]; }; interface VendorStatus { success: boolean; status: 'delivered' | 'pending' | 'failed'; progress: number; message: string; appUrl?: string; title?: string; name?: string; key?: string; vendorType: string; } interface VendorResponse { payment_status: string; session_status: string; vendors: VendorStatus[]; error: string | null; } export default function PaymentSuccess({ mode, pageInfo = {}, vendorCount = 0, sessionId = '', message, action, payee, invoiceId = '', subscriptionId = '', subscriptions = [], }: Props) { const { t, locale } = useLocaleContext(); const { prefix, api } = usePaymentContext(); const [vendorStatus, setVendorStatus] = useState(null); const [isAllCompleted, setIsAllCompleted] = useState(false); const [hasFailed, setHasFailed] = useState(false); const timerRef = useRef(Date.now()); const browser = useArcblockBrowser(); const inArcsphere = browser?.arcSphere; let next: any = null; // Fetch vendor status when vendorCount > 0 useEffect(() => { if (vendorCount === 0 || !sessionId) return undefined; const fetchVendorStatus = async (interval?: NodeJS.Timeout) => { try { const response = await api.get(joinURL(prefix, `/api/vendors/order/${sessionId}/status`), {}); const needCheckError = Date.now() - timerRef.current > 6 * 1000; const allCompleted = response.data?.vendors?.every((vendor: any) => vendor.progress >= 100); const hasAnyFailed = response.data?.vendors?.some( (vendor: any) => vendor.status === 'failed' || (needCheckError && !!vendor.error && !!vendor.error_message) ); if (hasAnyFailed || allCompleted) { clearInterval(interval); } setHasFailed(hasAnyFailed); setIsAllCompleted(!hasAnyFailed && allCompleted); setVendorStatus(response.data); } catch (error) { console.error('Failed to fetch vendor status:', error); } }; fetchVendorStatus(); // Poll every 5 seconds if progress is less than 80% const interval = setInterval(() => { fetchVendorStatus(interval); }, 5000); return () => clearInterval(interval); }, [vendorCount, api, prefix, sessionId]); useEffect(() => { if (inArcsphere && isAllCompleted) { try { bridge?.call?.('arc__toast', { text: t('payment.checkout.vendor.arcSphereToast') }); const timer = setTimeout(() => { bridge?.call?.('arcClosePage'); }, 3000); return () => clearTimeout(timer); } catch (error) { // } } return undefined; }, [isAllCompleted, t, locale, inArcsphere]); const renderPlaceholders = () => { const placeholders = []; for (let i = 0; i < vendorCount; i++) { placeholders.push(); } return placeholders; }; const renderVendors = () => { if (!vendorStatus) return renderPlaceholders(); return vendorStatus.vendors?.map((vendor, index) => { return ; }); }; // Render vendor progress component const renderVendorProgress = () => { if (vendorCount === 0) return null; return ( {renderVendors()} {hasFailed ? ( {t('payment.checkout.vendor.failedMsg')} ) : null} {pageInfo?.success_message?.[locale] && isAllCompleted ? ( {pageInfo?.success_message?.[locale]} ) : null} ); }; if (['subscription', 'setup'].includes(action)) { if (subscriptions && subscriptions.length > 1) { next = ( {subscriptions.map((subscription) => ( {subscription.description} ))} ); } else if (subscriptionId) { next = ( ); } } else if (invoiceId) { next = ( {t('payment.checkout.next.invoice', { payee })} ); } return (
{message} {t('payment.checkout.completed.tip', { payee })} {renderVendorProgress()} {next} ); } const Div = styled('div')` width: 80px; height: 115px; .check-icon { width: 80px; height: 80px; position: relative; border-radius: 50%; box-sizing: content-box; border: 4px solid ${(props) => props.theme.palette.success.main}; } .check-icon::before { top: 3px; left: -2px; width: 30px; transform-origin: 100% 50%; border-radius: 100px 0 0 100px; } .check-icon::after { top: 0; left: 30px; width: 60px; transform-origin: 0 50%; border-radius: 0 100px 100px 0; animation: rotate-circle 4.25s ease-in; } .check-icon::before, .check-icon::after { content: ''; height: 100px; position: absolute; background: ${(props) => props.theme.palette.background.default}; transform: rotate(-45deg); } .check-icon .icon-line { height: 5px; background-color: ${(props) => props.theme.palette.success.main}; display: block; border-radius: 2px; position: absolute; z-index: 10; } .check-icon .icon-line.line-tip { top: 46px; left: 14px; width: 25px; transform: rotate(45deg); animation: icon-line-tip 0.75s; } .check-icon .icon-line.line-long { top: 38px; right: 8px; width: 47px; transform: rotate(-45deg); animation: icon-line-long 0.75s; } .check-icon .icon-circle { top: -4px; left: -4px; z-index: 10; width: 80px; height: 80px; border-radius: 50%; position: absolute; box-sizing: content-box; border: 4px solid rgba(76, 175, 80, 0.5); } .check-icon .icon-fix { top: 8px; width: 5px; left: 26px; z-index: 1; height: 85px; position: absolute; transform: rotate(-45deg); background-color: ${(props) => props.theme.palette.background.default}; } @keyframes rotate-circle { 0% { transform: rotate(-45deg); } 5% { transform: rotate(-45deg); } 12% { transform: rotate(-405deg); } 100% { transform: rotate(-405deg); } } @keyframes icon-line-tip { 0% { width: 0; left: 1px; top: 19px; } 54% { width: 0; left: 1px; top: 19px; } 70% { width: 50px; left: -8px; top: 37px; } 84% { width: 17px; left: 21px; top: 48px; } 100% { width: 25px; left: 14px; top: 45px; } } @keyframes icon-line-long { 0% { width: 0; right: 46px; top: 54px; } 65% { width: 0; right: 46px; top: 54px; } 84% { width: 55px; right: 0px; top: 35px; } 100% { width: 47px; right: 8px; top: 38px; } } `;