/* eslint-disable @typescript-eslint/indent */ import type { TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types'; import { Alert } from '@mui/material'; import { useLocalStorageState, useRequest } from 'ahooks'; import type { Axios } from 'axios'; import { createContext, useContext, useEffect, useState } from 'react'; import axios from 'axios'; import { joinURL } from 'ufo'; import useBus from 'use-bus'; import api from '../libs/api'; import { getPrefix, PAYMENT_KIT_DID } from '../libs/util'; import { CachedRequest } from '../libs/cached-request'; export interface Settings { paymentMethods: TPaymentMethodExpanded[]; baseCurrency: TPaymentCurrency; } export type PaymentContextType = { livemode: boolean; session: import('@arcblock/did-connect-react/lib/types').SessionContext['session'] & { user: any }; connect: import('@arcblock/did-connect-react/lib/types').SessionContext['connectApi']; prefix: string; settings: Settings; refresh: (forceRefresh?: boolean) => void; getCurrency: (currencyId: string) => TPaymentCurrency | undefined; getMethod: (methodId: string) => TPaymentMethodExpanded | undefined; setLivemode: (livemode: boolean) => void; api: Axios; payable: boolean; setPayable: (status: boolean) => void; paymentState: { paying: boolean; stripePaying: boolean; }; setPaymentState: (state: Partial<{ paying: boolean; stripePaying: boolean }>) => void; }; export type PaymentContextProps = { session: import('@arcblock/did-connect-react/lib/types').SessionContext['session']; connect: import('@arcblock/did-connect-react/lib/types').SessionContext['connectApi']; children: any; // eslint-disable-next-line react/require-default-props baseUrl?: string; // eslint-disable-next-line react/require-default-props authToken?: string; }; const formatData = (data: any) => { if (!data) { return { paymentMethods: [], baseCurrency: {}, }; } return { ...data, paymentMethods: data.paymentMethods || [], baseCurrency: data.baseCurrency || {}, }; }; // @ts-ignore const PaymentContext = createContext({ api }); const { Provider, Consumer } = PaymentContext; const getSettings = (forceRefresh = false) => { const livemode = localStorage.getItem('livemode') !== 'false'; const cacheKey = `payment-settings-${window.location.pathname}-${livemode}`; const cachedRequest = new CachedRequest(cacheKey, () => api.get('/api/settings', { params: { livemode } })); return cachedRequest.fetch(forceRefresh); }; const getCurrency = (currencyId: string, methods: TPaymentMethodExpanded[]) => { const currencies = methods.reduce((acc, x) => acc.concat(x.payment_currencies), [] as TPaymentCurrency[]); return currencies.find((x) => x.id === currencyId); }; const getMethod = (methodId: string, methods: TPaymentMethodExpanded[]) => { return methods.find((x) => x.id === methodId); }; function PaymentProvider({ session, connect, children, baseUrl = undefined, authToken = undefined, }: PaymentContextProps) { const [crossOriginLoading, setCrossOriginLoading] = useState(false); if (authToken) { window.__PAYMENT_KIT_AUTH_TOKEN = authToken; } else { window.__PAYMENT_KIT_AUTH_TOKEN = ''; } // Fetch cross-origin blocklet info if needed useEffect(() => { const fetchCrossOriginBlockletInfo = async () => { if (!baseUrl) { window.__PAYMENT_KIT_BASE_URL = ''; setCrossOriginLoading(false); return; } const tmp = new URL(baseUrl); // Same origin check if (tmp.origin === window.location.origin) { window.__PAYMENT_KIT_BASE_URL = ''; setCrossOriginLoading(false); return; } setCrossOriginLoading(true); try { const scriptUrl = joinURL(tmp.origin, '__blocklet__.js?type=json'); const cacheKey = `cross-origin-blocklet-${tmp.origin}`; const cachedRequest = new CachedRequest( cacheKey, () => axios.get(scriptUrl).then((res) => ({ data: res.data })), { strategy: 'session', ttl: 10 * 60 * 1000, // 10 minutes TTL } ); const blockletInfo = await cachedRequest.fetch(); const componentId = (blockletInfo?.componentId || '').split('/').pop(); // Set base URL based on component type if (componentId === PAYMENT_KIT_DID) { window.__PAYMENT_KIT_BASE_URL = joinURL(tmp.origin, blockletInfo.prefix || '/'); } else { const component = (blockletInfo?.componentMountPoints || []).find((x: any) => x?.did === PAYMENT_KIT_DID); window.__PAYMENT_KIT_BASE_URL = component ? joinURL(tmp.origin, component.mountPoint) : baseUrl; // Fallback to original baseUrl } } catch (err) { console.warn(`Failed to fetch blocklet json from ${baseUrl}:`, err); // Fallback to original baseUrl on error window.__PAYMENT_KIT_BASE_URL = baseUrl; } finally { setCrossOriginLoading(false); } }; fetchCrossOriginBlockletInfo(); }, [baseUrl]); const [livemode, setLivemode] = useLocalStorageState('livemode', { defaultValue: true }); const { data = { paymentMethods: [], baseCurrency: {}, }, error, run, loading, } = useRequest(getSettings, { refreshDeps: [livemode], }); // Listen for settings changes and force refresh useBus( // @ts-ignore ['paymentMethod.created', 'paymentMethod.updated', 'paymentCurrency.added', 'paymentCurrency.updated'], () => run(true), [run] ); const prefix = getPrefix(); const [payable, setPayable] = useState(true); const [paymentState, setPaymentState] = useState({ paying: false, stripePaying: false, }); const updatePaymentState = (state: Partial<{ paying: boolean; stripePaying: boolean }>) => { setPaymentState((prev) => ({ ...prev, ...state })); }; if (error) { return {error.message}; } if (loading || crossOriginLoading) { return null; } return ( getCurrency(currencyId, (data as Settings)?.paymentMethods || []), getMethod: (methodId: string) => getMethod(methodId, (data as Settings)?.paymentMethods || []), refresh: run, setLivemode, api, payable, setPayable, paymentState, setPaymentState: updatePaymentState, }}> {children} ); } function usePaymentContext() { const context = useContext(PaymentContext); return context; } export { PaymentContext, PaymentProvider, Consumer as SettingsConsumer, usePaymentContext };