UNPKG

@reown/appkit-pay

Version:
323 lines 11.6 kB
import { proxy, subscribe as sub } from 'valtio/vanilla'; import { subscribeKey as subKey } from 'valtio/vanilla/utils'; import { ConstantsUtil, ParseUtil } from '@reown/appkit-common'; import { AccountController, ChainController, CoreHelperUtil, ModalController, RouterController, SnackController } from '@reown/appkit-controllers'; import { ProviderUtil } from '@reown/appkit-utils'; import { AppKitPayErrorCodes, AppKitPayErrorMessages } from '../types/errors.js'; import { AppKitPayError } from '../types/errors.js'; import { getBuyStatus, getExchanges, getPayUrl } from '../utils/ApiUtil.js'; import { formatCaip19Asset } from '../utils/AssetUtil.js'; import { ensureCorrectNetwork, processEvmErc20Payment, processEvmNativePayment } from '../utils/PaymentUtil.js'; const DEFAULT_PAGE = 0; const state = proxy({ paymentAsset: { network: 'eip155:1', recipient: '0x0', asset: '0x0', amount: 0, metadata: { name: '0x0', symbol: '0x0', decimals: 0 } }, isConfigured: false, error: null, isPaymentInProgress: false, exchanges: [], isLoading: false, openInNewTab: true, redirectUrl: undefined, payWithExchange: undefined, currentPayment: undefined }); export const PayController = { state, subscribe(callback) { return sub(state, () => callback(state)); }, subscribeKey(key, callback) { return subKey(state, key, callback); }, async handleOpenPay(options) { this.resetState(); this.setPaymentConfig(options); this.subscribeEvents(); state.isConfigured = true; await ModalController.open({ view: 'Pay' }); }, resetState() { state.paymentAsset = { network: 'eip155:1', recipient: '0x0', asset: '0x0', amount: 0, metadata: { name: '0x0', symbol: '0x0', decimals: 0 } }; state.isConfigured = false; state.error = null; state.isPaymentInProgress = false; state.isLoading = false; state.currentPayment = undefined; }, setPaymentConfig(config) { if (!config.paymentAsset) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_PAYMENT_CONFIG); } try { state.paymentAsset = config.paymentAsset; state.openInNewTab = config.openInNewTab ?? true; state.redirectUrl = config.redirectUrl; state.payWithExchange = config.payWithExchange; state.error = null; } catch (error) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_PAYMENT_CONFIG, error.message); } }, getPaymentAsset() { return state.paymentAsset; }, getExchanges() { return state.exchanges; }, async fetchExchanges() { try { state.isLoading = true; const response = await getExchanges({ page: DEFAULT_PAGE }); state.exchanges = response.exchanges.slice(0, 2); } catch (error) { SnackController.showError(AppKitPayErrorMessages.UNABLE_TO_GET_EXCHANGES); throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_EXCHANGES); } finally { state.isLoading = false; } }, async getAvailableExchanges(page = DEFAULT_PAGE) { try { const response = await getExchanges({ page }); return response; } catch (error) { throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_EXCHANGES); } }, async getPayUrl(exchangeId, params) { try { const numericAmount = Number(params.amount); const response = await getPayUrl({ exchangeId, asset: formatCaip19Asset(params.network, params.asset), amount: numericAmount.toString(16), recipient: `${params.network}:${params.recipient}` }); return response; } catch (error) { if (error instanceof Error && error.message.includes('is not supported')) { throw new AppKitPayError(AppKitPayErrorCodes.ASSET_NOT_SUPPORTED); } throw new Error(error.message); } }, async openPayUrl(exchangeId, params, openInNewTab = true) { try { const payUrl = await this.getPayUrl(exchangeId, params); if (!payUrl) { throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_PAY_URL); } const target = openInNewTab ? '_blank' : '_self'; CoreHelperUtil.openHref(payUrl.url, target); return payUrl; } catch (error) { if (error instanceof AppKitPayError) { state.error = error.message; } else { state.error = AppKitPayErrorMessages.GENERIC_PAYMENT_ERROR; } throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_PAY_URL); } }, subscribeEvents() { if (state.isConfigured) { return; } ProviderUtil.subscribeProviders(async (_) => { const chainNamespace = ChainController.state.activeChain; const provider = ProviderUtil.getProvider(chainNamespace); if (!provider) { return; } await this.handlePayment(); }); AccountController.subscribeKey('caipAddress', async (caipAddress) => { if (!caipAddress) { return; } await this.handlePayment(); }); }, async handlePayment() { state.currentPayment = { type: 'wallet' }; const caipAddress = AccountController.state.caipAddress; if (!caipAddress) { return; } const { chainId, address } = ParseUtil.parseCaipAddress(caipAddress); const chainNamespace = ChainController.state.activeChain; if (!address || !chainId || !chainNamespace) { return; } const provider = ProviderUtil.getProvider(chainNamespace); if (!provider) { return; } const caipNetwork = ChainController.state.activeCaipNetwork; if (!caipNetwork) { return; } if (state.isPaymentInProgress) { return; } try { state.isPaymentInProgress = true; const requestedCaipNetworks = ChainController.getAllRequestedCaipNetworks(); const approvedCaipNetworkIds = ChainController.getAllApprovedCaipNetworkIds(); await ensureCorrectNetwork({ paymentAssetNetwork: state.paymentAsset.network, activeCaipNetwork: caipNetwork, approvedCaipNetworkIds, requestedCaipNetworks }); await ModalController.open({ view: 'PayLoading' }); switch (chainNamespace) { case ConstantsUtil.CHAIN.EVM: if (state.paymentAsset.asset === 'native') { state.currentPayment.result = await processEvmNativePayment(state.paymentAsset, chainNamespace, address); } if (state.paymentAsset.asset.startsWith('0x')) { state.currentPayment.result = await processEvmErc20Payment(state.paymentAsset, address); } break; default: throw new AppKitPayError(AppKitPayErrorCodes.INVALID_CHAIN_NAMESPACE); } } catch (error) { if (error instanceof AppKitPayError) { state.error = error.message; } else { state.error = AppKitPayErrorMessages.GENERIC_PAYMENT_ERROR; } SnackController.showError(state.error); } finally { state.isPaymentInProgress = false; } }, getExchangeById(exchangeId) { return state.exchanges.find(exchange => exchange.id === exchangeId); }, validatePayConfig(config) { const { paymentAsset } = config; if (!paymentAsset) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_PAYMENT_CONFIG); } if (!paymentAsset.recipient) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_RECIPIENT); } if (!paymentAsset.asset) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_ASSET); } if (!paymentAsset.amount) { throw new AppKitPayError(AppKitPayErrorCodes.INVALID_AMOUNT); } }, handlePayWithWallet() { const caipAddress = AccountController.state.caipAddress; if (!caipAddress) { RouterController.push('Connect'); return; } const { chainId, address } = ParseUtil.parseCaipAddress(caipAddress); const chainNamespace = ChainController.state.activeChain; if (!address || !chainId || !chainNamespace) { RouterController.push('Connect'); return; } this.handlePayment(); }, async handlePayWithExchange(exchangeId) { try { state.currentPayment = { type: 'exchange', exchangeId }; state.isPaymentInProgress = true; const { network, asset, amount, recipient } = state.paymentAsset; const payUrlParams = { network, asset, amount, recipient }; const payUrl = await this.getPayUrl(exchangeId, payUrlParams); if (!payUrl) { throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_INITIATE_PAYMENT); } state.currentPayment.sessionId = payUrl.sessionId; state.currentPayment.status = 'IN_PROGRESS'; state.currentPayment.exchangeId = exchangeId; return { url: payUrl.url, openInNewTab: state.openInNewTab }; } catch (error) { if (error instanceof AppKitPayError) { state.error = error.message; } else { state.error = AppKitPayErrorMessages.GENERIC_PAYMENT_ERROR; } state.isPaymentInProgress = false; SnackController.showError(state.error); return null; } }, async getBuyStatus(exchangeId, sessionId) { try { const status = await getBuyStatus({ sessionId, exchangeId }); return status; } catch (error) { throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_BUY_STATUS); } }, async updateBuyStatus(exchangeId, sessionId) { try { const status = await this.getBuyStatus(exchangeId, sessionId); if (state.currentPayment) { state.currentPayment.status = status.status; state.currentPayment.result = status.txHash; } if (status.status === 'SUCCESS' || status.status === 'FAILED') { state.isPaymentInProgress = false; } } catch (error) { throw new AppKitPayError(AppKitPayErrorCodes.UNABLE_TO_GET_BUY_STATUS); } } }; //# sourceMappingURL=PayController.js.map