import React, { useCallback, useEffect, useState } from 'react';
import { parseUrl } from 'query-string';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigation, useRoute } from '@react-navigation/native';
import { View } from 'react-native';
import { WebView, WebViewNavigation } from 'react-native-webview';
import { QuoteResponse, CryptoCurrency, Order } from '@consensys/on-ramp-sdk';
import { baseStyles } from '../../../../styles/common';
import { useTheme } from '../../../../util/theme';
import { getFiatOnRampAggNavbar } from '../../Navbar';
import { useFiatOnRampSDK, SDK } from '../sdk';
import { NETWORK_NATIVE_SYMBOL } from '../../../../constants/on-ramp';
import { addFiatOrder } from '../../../../reducers/fiatOrders';
import Engine from '../../../../core/Engine';
import { toLowerCaseEquals } from '../../../../util/general';
import { protectWalletModalVisible } from '../../../../actions/user';
import {
processAggregatorOrder,
aggregatorInitialFiatOrder,
} from '../orderProcessor/aggregator';
import NotificationManager from '../../../../core/NotificationManager';
import { FiatOrder, getNotificationDetails } from '../../FiatOrders';
import ScreenLayout from '../components/ScreenLayout';
import ErrorView from '../components/ErrorView';
import ErrorViewWithReporting from '../components/ErrorViewWithReporting';
import { strings } from '../../../../../locales/i18n';
import useAnalytics from '../hooks/useAnalytics';
import { hexToBN } from '../../../../util/number';
const CheckoutWebView = () => {
const { selectedAddress, selectedChainId, sdkError, callbackBaseUrl } =
useFiatOnRampSDK();
const dispatch = useDispatch();
const trackEvent = useAnalytics();
const [error, setError] = useState('');
const [key, setKey] = useState(0);
const navigation = useNavigation();
// @ts-expect-error useRoute params error
const { params }: { params: QuoteResponse } = useRoute();
const { colors } = useTheme();
const accounts = useSelector(
(state: any) =>
state.engine.backgroundState.AccountTrackerController.accounts,
);
const uri = params?.buyURL;
const handleCancelPress = useCallback(() => {
trackEvent('ONRAMP_CANCELED', {
location: 'Provider Webview',
chain_id_destination: selectedChainId,
provider_onramp: params.provider.name,
});
}, [params.provider.name, selectedChainId, trackEvent]);
useEffect(() => {
navigation.setOptions(
getFiatOnRampAggNavbar(
navigation,
{ title: params.provider.name },
colors,
handleCancelPress,
),
);
}, [navigation, colors, params.provider.name, handleCancelPress]);
const addTokenToTokensController = async (token: CryptoCurrency) => {
if (!token) return;
const { address, symbol, decimals, network } = token;
const chainId = network?.chainId;
if (
Number(chainId) !== Number(selectedChainId) ||
NETWORK_NATIVE_SYMBOL[chainId] === symbol
) {
return;
}
// @ts-expect-error Engine context typing
const { TokensController } = Engine.context;
if (
!TokensController.state.tokens.includes((t: any) =>
toLowerCaseEquals(t.address, address),
)
) {
await TokensController.addToken(address, symbol, decimals);
}
};
const handleAddFiatOrder = useCallback(
(order) => {
dispatch(addFiatOrder(order));
},
[dispatch],
);
const handleDispatchUserWalletProtection = useCallback(() => {
dispatch(protectWalletModalVisible());
}, [dispatch]);
const handleNavigationStateChange = async (navState: WebViewNavigation) => {
if (navState?.url.startsWith(callbackBaseUrl)) {
try {
const parsedUrl = parseUrl(navState?.url);
if (Object.keys(parsedUrl.query).length === 0) {
// There was no query params in the URL to parse
// Most likely the user clicked the X in Wyre widget
// @ts-expect-error navigation prop mismatch
navigation.dangerouslyGetParent()?.pop();
return;
}
const orders = await SDK.orders();
const orderId = await orders.getOrderIdFromCallback(
params?.provider.id,
navState?.url,
);
if (!orderId) {
throw new Error(
`Order ID could not be retrieved. Callback was ${navState?.url}`,
);
}
const transformedOrder = {
...(await processAggregatorOrder(
aggregatorInitialFiatOrder({
id: orderId,
account: selectedAddress,
network: selectedChainId,
}),
)),
id: orderId,
account: selectedAddress,
network: selectedChainId,
};
// add the order to the redux global store
handleAddFiatOrder(transformedOrder);
// register the token automatically
await addTokenToTokensController(
(transformedOrder as any)?.data?.cryptoCurrency,
);
// prompt user to protect his/her wallet
handleDispatchUserWalletProtection();
// close the checkout webview
// @ts-expect-error navigation prop mismatch
navigation.dangerouslyGetParent()?.pop();
NotificationManager.showSimpleNotification(
getNotificationDetails(transformedOrder as any),
);
trackEvent('ONRAMP_PURCHASE_SUBMITTED', {
provider_onramp: ((transformedOrder as FiatOrder)?.data as Order)
?.provider?.name,
payment_method_id: ((transformedOrder as FiatOrder)?.data as Order)
?.paymentMethod?.id,
currency_source: ((transformedOrder as FiatOrder)?.data as Order)
?.fiatCurrency.symbol,
currency_destination: ((transformedOrder as FiatOrder)?.data as Order)
?.cryptoCurrency.symbol,
chain_id_destination: selectedChainId,
is_apple_pay: false,
has_zero_native_balance: accounts[selectedAddress]?.balance
? (hexToBN(accounts[selectedAddress].balance) as any)?.isZero?.()
: undefined,
});
} catch (navStateError) {
setError((navStateError as Error)?.message);
}
}
};
if (sdkError) {
return (
);
}
if (error) {
return (
{
setKey((prevKey) => prevKey + 1);
setError('');
}}
/>
);
}
if (uri) {
return (
{
const { nativeEvent } = syntheticEvent;
if (
nativeEvent.url === uri ||
nativeEvent.url.startsWith(callbackBaseUrl)
) {
const webviewHttpError = strings(
'fiat_on_ramp_aggregator.webview_received_error',
{ code: nativeEvent.statusCode },
);
setError(webviewHttpError);
}
}}
allowsInlineMediaPlayback
enableApplePay
mediaPlaybackRequiresUserAction={false}
onNavigationStateChange={handleNavigationStateChange}
/>
);
}
};
export default CheckoutWebView;