@coin-voyage/paykit
Version:
Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.
198 lines (197 loc) • 7.5 kB
JavaScript
import { useAccount } from "@coin-voyage/crypto/hooks";
import { zPayOrder } from "@coin-voyage/shared/schemas";
import { ChainType, PayOrderMode, } from "@coin-voyage/shared/types";
import { useResolveSuiNSName } from "@mysten/dapp-kit";
import { useCallback, useRef, useState } from "react";
import { mainnet } from "viem/chains";
import { useEnsName } from "wagmi";
import { useBackendApi } from "../components/contexts/api";
import { ROUTE } from "../types/routes";
import { usePayOrderQuotes } from "./usePayOrderQuotes";
import { usePayToAddress } from "./usePayToAddress";
import { usePayWithCard } from "./usePayWithCard";
import { usePayFromWallet } from "./usePayWithToken";
export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
const api = useBackendApi();
const [connectorChainType, setConnectorChainType] = useState();
const [selectedWallet, setSelectedWallet] = useState();
const latestRequestedIdRef = useRef(null);
const inFlightPayOrderIdsRef = useRef(new Set());
const { account: senderAccount } = useAccount({
selectedWallet,
chainType: connectorChainType,
});
const { data: suiEnsName } = useResolveSuiNSName(senderAccount.address, {
enabled: !!senderAccount.address && senderAccount.chainType === ChainType.SUI && senderAccount.isConnected,
});
const { data: evmEnsName } = useEnsName({
chainId: mainnet.id,
address: senderAccount.address,
query: {
enabled: !!senderAccount.address && senderAccount.chainType === ChainType.EVM && senderAccount.isConnected,
},
});
const payOrderQuotes = usePayOrderQuotes({
payOrder,
address: senderAccount.address,
chainType: connectorChainType,
});
const [selectedCurrencyOption, setSelectedCurrencyOption] = useState();
const [paymentMethod, setPaymentMethod] = useState();
const [payToAddressChainId, setPayToAddressChainId] = useState();
const [payToAddressCurrency, setPayToAddressCurrency] = useState();
const { payFromWallet } = usePayFromWallet({
senderAddr: senderAccount.address,
payOrder,
setPayOrder,
chainType: connectorChainType,
log,
});
const { payToAddress } = usePayToAddress({
payOrder,
setPayOrder,
log,
});
const { payWithCard } = usePayWithCard({
payOrder,
setPayOrder,
log,
});
const fetchPayOrder = useCallback(async (id) => {
const { data: order, error } = await api.getPayOrder(id);
if (!order || error) {
log(`[CHECKOUT] No order found for ${id}: ${JSON.stringify(error)}`);
return undefined;
}
return order;
}, [api, log]);
const refreshOrder = useCallback(async () => {
if (!payOrder?.id)
return;
const order = await fetchPayOrder(payOrder.id);
if (order)
setPayOrder(order);
}, [payOrder, fetchPayOrder, setPayOrder]);
const setPayOrderId = useCallback(async (payOrderId) => {
if (!payOrderId)
return;
if (payOrder?.id === payOrderId)
return;
if (inFlightPayOrderIdsRef.current.has(payOrderId))
return;
latestRequestedIdRef.current = payOrderId;
inFlightPayOrderIdsRef.current.add(payOrderId);
try {
const order = await fetchPayOrder(payOrderId);
if (order && latestRequestedIdRef.current === payOrderId) {
setPayOrder(order);
}
return order;
}
finally {
inFlightPayOrderIdsRef.current.delete(payOrderId);
}
}, [fetchPayOrder, payOrder?.id, setPayOrder]);
const copyDepositPayOrder = useCallback(async () => {
if (!payOrder?.id) {
log(`No payOrder to copy`);
return;
}
if (payOrder.mode !== PayOrderMode.DEPOSIT) {
log(`Cannot recreate SALE order`);
return;
}
const asset = payOrder.fulfillment.asset;
const { data, error } = await api.createDepositPayOrder({
intent: {
asset,
amount: {
...(asset ? { token_amount: payOrder.fulfillment.amount.ui_amount } : {}),
...(payOrder.fulfillment.fiat
? {
fiat: {
amount: payOrder.fulfillment.amount.value_usd,
unit: payOrder.fulfillment.fiat,
},
}
: {}),
},
receiving_address: payOrder.fulfillment.receiving_address,
},
metadata: payOrder.metadata,
});
if (!data || error) {
log(`[CHECKOUT] Error creating payOrder: ${JSON.stringify(error)}`);
return;
}
await setPayOrderId(data.id);
}, [api, payOrder, log, setPayOrderId]);
const createDepositPayOrder = useCallback(async (params, onError) => {
try {
const result = zPayOrder.safeParse(params);
if (result.error) {
onError?.(`[CREATE DEPOSIT]: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`);
return;
}
const { data: payOrder, error } = await api.createDepositPayOrder(params);
if (!payOrder || error) {
const errorMessage = error?.message || "Unable to create payment order";
onError?.(errorMessage);
log(`[CREATE DEPOSIT] Error creating payOrder: ${JSON.stringify(error)}`);
return;
}
setPayOrder(payOrder);
return payOrder;
}
catch (e) {
if (e instanceof Error)
onError?.(e.message);
else
onError?.("Unknown error");
}
}, [api, setPayOrder, log]);
const clearUserSelection = useCallback(() => {
setSelectedCurrencyOption(undefined);
setConnectorChainType(undefined);
setSelectedWallet(undefined);
setRoute(ROUTE.SELECT_METHOD);
}, [setRoute]);
const resetPaymentState = useCallback(() => {
latestRequestedIdRef.current = null;
inFlightPayOrderIdsRef.current.clear();
setPayOrder(undefined);
setPaymentMethod(undefined);
setSelectedCurrencyOption(undefined);
setConnectorChainType(undefined);
setSelectedWallet(undefined);
setPayToAddressChainId(undefined);
setPayToAddressCurrency(undefined);
setRoute(ROUTE.SELECT_METHOD);
}, [setPayOrder, setRoute]);
return {
setPayId: setPayOrderId,
createDepositPayOrder,
copyDepositPayOrder,
clearUserSelection,
resetPaymentState,
payOrder,
paymentMethod,
setPaymentMethod,
connectorChainType,
setConnectorChainType,
selectedWallet,
setSelectedWallet,
selectedCurrencyOption,
setSelectedCurrencyOption,
payOrderQuotes,
payFromWallet,
payWithCard,
payToAddress,
refreshOrder,
senderEnsName: evmEnsName ?? suiEnsName ?? undefined,
payToAddressChainId,
setPayToAddressChainId,
payToAddressCurrency,
setPayToAddressCurrency,
};
}