@coin-voyage/paykit 2.4.2 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -17
- package/dist/components/Pages/CardPayment/index.js +5 -3
- package/dist/components/Pages/Confirmation/index.js +1 -1
- package/dist/components/Pages/PayToAddress/index.js +3 -3
- package/dist/components/Pages/PayWithToken/index.js +4 -38
- package/dist/components/Pages/PreparingPayment/index.d.ts +1 -0
- package/dist/components/Pages/PreparingPayment/index.js +50 -0
- package/dist/components/pay-button/index.js +173 -93
- package/dist/components/pay-modal/index.d.ts +3 -1
- package/dist/components/pay-modal/index.js +2 -2
- package/dist/components/ui/Modal/index.d.ts +2 -1
- package/dist/components/ui/Modal/index.js +8 -1
- package/dist/config/route-config.js +7 -0
- package/dist/hooks/usePayWithToken.d.ts +1 -1
- package/dist/hooks/usePayWithToken.js +59 -19
- package/dist/hooks/usePaymentLifecycle.d.ts +5 -1
- package/dist/hooks/usePaymentLifecycle.js +52 -46
- package/dist/hooks/usePaymentState.d.ts +4 -3
- package/dist/hooks/usePaymentState.js +34 -7
- package/dist/hooks/useTokenOptions.js +1 -1
- package/dist/providers/paykit-provider.js +29 -22
- package/dist/types/routes.d.ts +15 -14
- package/dist/types/routes.js +15 -14
- package/package.json +3 -6
- package/dist/utils/item.d.ts +0 -5
- package/dist/utils/item.js +0 -6
package/README.md
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
# CoinVoyage Payments
|
|
2
2
|
|
|
3
|
-
CoinVoyage enables seamless crypto payments
|
|
3
|
+
CoinVoyage enables seamless crypto payments in your app.
|
|
4
4
|
|
|
5
|
-
Onboard users from any chain
|
|
5
|
+
Onboard users from any chain and any coin with one click.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
and much more...
|
|
9
|
+
- Cross-chain payments across many tokens and chains.
|
|
10
|
+
- Single-transaction payment flows for end users.
|
|
11
|
+
- Permissionless payment execution without custodial fund handling.
|
|
12
|
+
- Support for major browser and mobile wallets.
|
|
13
|
+
- Fast integration for embedded crypto payment UX.
|
|
16
14
|
|
|
17
15
|
## Documentation
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
Read the docs at [docs.coinvoyage.io](https://docs.coinvoyage.io).
|
|
20
18
|
|
|
21
19
|
## Examples
|
|
22
20
|
|
|
23
|
-
[
|
|
24
|
-
|
|
25
|
-
### Try in Playground
|
|
26
|
-
|
|
27
|
-
Coming soon.
|
|
21
|
+
See [coin-voyage/examples](https://github.com/coin-voyage/examples).
|
|
28
22
|
|
|
29
23
|
## Support
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
Contact [help@coinvoyage.io](mailto:help@coinvoyage.io) for integration support.
|
|
32
26
|
|
|
33
27
|
## Credits
|
|
34
28
|
|
|
35
|
-
CoinVoyage PayKit is a fork of [
|
|
29
|
+
CoinVoyage PayKit is a fork of [ConnectKit](https://github.com/family/connectkit) by [Family](https://family.co).
|
|
@@ -102,6 +102,8 @@ function StripeOnrampCheckout({ paymentData }) {
|
|
|
102
102
|
}
|
|
103
103
|
function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessionUpdate, }) {
|
|
104
104
|
const mountNodeRef = useRef(null);
|
|
105
|
+
const onUiLoadedRef = useLatestRef(onUiLoaded);
|
|
106
|
+
const onSessionUpdateRef = useLatestRef(onSessionUpdate);
|
|
105
107
|
useEffect(() => {
|
|
106
108
|
const mountNode = mountNodeRef.current;
|
|
107
109
|
if (!mountNode) {
|
|
@@ -113,10 +115,10 @@ function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessio
|
|
|
113
115
|
appearance: theme ? { theme } : undefined,
|
|
114
116
|
});
|
|
115
117
|
const handleUiLoaded = () => {
|
|
116
|
-
|
|
118
|
+
onUiLoadedRef.current?.();
|
|
117
119
|
};
|
|
118
120
|
const handleSessionUpdated = (event) => {
|
|
119
|
-
|
|
121
|
+
onSessionUpdateRef.current?.(event.payload.session.status);
|
|
120
122
|
};
|
|
121
123
|
session.addEventListener("onramp_ui_loaded", handleUiLoaded);
|
|
122
124
|
session.addEventListener("onramp_session_updated", handleSessionUpdated);
|
|
@@ -126,7 +128,7 @@ function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessio
|
|
|
126
128
|
session.removeEventListener("onramp_session_updated", handleSessionUpdated);
|
|
127
129
|
mountNode.replaceChildren();
|
|
128
130
|
};
|
|
129
|
-
}, [clientSecret,
|
|
131
|
+
}, [clientSecret, onSessionUpdateRef, onUiLoadedRef, stripeOnramp, theme]);
|
|
130
132
|
return _jsx(OnrampMount, { ref: mountNodeRef });
|
|
131
133
|
}
|
|
132
134
|
function useCardPaymentData() {
|
|
@@ -19,7 +19,7 @@ export default function Confirmation() {
|
|
|
19
19
|
onSuccess();
|
|
20
20
|
}
|
|
21
21
|
}, [uiState, onSuccess]);
|
|
22
|
-
return (_jsx(PageContent, { "$center": true, children: _jsxs(ModalContent, { "$center": true, style: { paddingBottom: 0 }, children: [_jsx(AnimationContainer, { children: _jsxs(InsetContainer, { children: [_jsx(Spinner, { "$status": uiState === "loading" }), _jsx(SuccessIcon, { "$status": uiState === "success" }), _jsx(ErrorIcon, { "$status": uiState === "error" }), _jsx(WarningIcon, { "$status": uiState === "warning" })] }) }), _jsx(ModalH1, { children: title }), txURL && (_jsxs(Link, { href: txURL, target: "_blank", rel: "noopener noreferrer", children: [_jsx(ExternalLinkIcon, { width: 14, height: 14, fillOpacity: 0.9 }), "
|
|
22
|
+
return (_jsx(PageContent, { "$center": true, children: _jsxs(ModalContent, { "$center": true, style: { paddingBottom: 0 }, children: [_jsx(AnimationContainer, { children: _jsxs(InsetContainer, { children: [_jsx(Spinner, { "$status": uiState === "loading" }), _jsx(SuccessIcon, { "$status": uiState === "success" }), _jsx(ErrorIcon, { "$status": uiState === "error" }), _jsx(WarningIcon, { "$status": uiState === "warning" })] }) }), _jsx(ModalH1, { children: title }), txURL && (_jsxs(Link, { href: txURL, target: "_blank", rel: "noopener noreferrer", className: "text-sm", children: [_jsx(ExternalLinkIcon, { width: 14, height: 14, fillOpacity: 0.9 }), "View Transaction"] })), _jsx(PoweredByFooter, {})] }) }));
|
|
23
23
|
}
|
|
24
24
|
function getConfirmationState(payOrder, isDeposit, locales, optimisticConfirmation) {
|
|
25
25
|
if (!payOrder) {
|
|
@@ -22,16 +22,16 @@ export default function PayToAddress() {
|
|
|
22
22
|
function PayToAddressView() {
|
|
23
23
|
const { paymentState, triggerResize } = usePayContext();
|
|
24
24
|
const { payToAddressChainId: payToAddressChain, payToAddressCurrency } = paymentState;
|
|
25
|
-
const { data
|
|
25
|
+
const { data, isLoading, isError } = useDepositAddressQuery({
|
|
26
26
|
enabled: payToAddressCurrency != undefined,
|
|
27
27
|
});
|
|
28
28
|
if (isError) {
|
|
29
29
|
return payToAddressChain ? _jsx(DepositFailed, {}) : null;
|
|
30
30
|
}
|
|
31
|
-
if (isLoading || !
|
|
31
|
+
if (isLoading || !data) {
|
|
32
32
|
return _jsx(DepositAddressLoading, {});
|
|
33
33
|
}
|
|
34
|
-
return (_jsx(DepositAddressInfo, { details:
|
|
34
|
+
return (_jsx(DepositAddressInfo, { details: data, triggerResize: triggerResize, isDeposit: paymentState.payOrder?.mode === PayOrderMode.DEPOSIT }));
|
|
35
35
|
}
|
|
36
36
|
function DepositAddressLoading() {
|
|
37
37
|
const locales = useLocales();
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useAccount } from "@coin-voyage/crypto/hooks";
|
|
3
|
-
import { getChainLogo
|
|
4
|
-
import {
|
|
3
|
+
import { getChainLogo } from "@coin-voyage/shared/chain";
|
|
4
|
+
import { PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/types";
|
|
5
5
|
import { assert } from "@coin-voyage/shared/utils";
|
|
6
6
|
import { AnimatePresence, motion } from "framer-motion";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
8
|
-
import { useChainId, useSwitchChain } from "wagmi";
|
|
9
8
|
import { AlertIcon, RetryIconCircle } from "../../../assets/icons";
|
|
10
9
|
import useLocales from "../../../hooks/useLocales";
|
|
11
10
|
import styled from "../../../styles/styled";
|
|
@@ -19,7 +18,6 @@ import Tooltip from "../../ui/Tooltip";
|
|
|
19
18
|
var PayState;
|
|
20
19
|
(function (PayState) {
|
|
21
20
|
PayState["RequestingPayment"] = "Requesting Payment";
|
|
22
|
-
PayState["SwitchingChain"] = "Switching Chain";
|
|
23
21
|
PayState["RequestCancelled"] = "Payment Cancelled";
|
|
24
22
|
PayState["RequestSuccessful"] = "Payment Successful";
|
|
25
23
|
})(PayState || (PayState = {}));
|
|
@@ -29,30 +27,9 @@ export default function PayWithToken() {
|
|
|
29
27
|
const { account } = useAccount();
|
|
30
28
|
const [payState, setPayState] = useState(PayState.RequestingPayment);
|
|
31
29
|
const locales = useLocales();
|
|
32
|
-
const walletChainId = useChainId();
|
|
33
|
-
const { switchChainAsync } = useSwitchChain();
|
|
34
30
|
assert(payOrder !== undefined, "Pay order must be defined");
|
|
35
31
|
const isExpired = payOrder.status === PayOrderStatus.EXPIRED;
|
|
36
32
|
const isDeposit = payOrder.mode === PayOrderMode.DEPOSIT;
|
|
37
|
-
const chainType = getChainTypeByChainId(paymentState.selectedCurrencyOption?.chain_id);
|
|
38
|
-
const trySwitchingChain = useCallback(async (option, forceSwitch = false) => {
|
|
39
|
-
if (walletChainId === option.chain_id && !forceSwitch)
|
|
40
|
-
return true;
|
|
41
|
-
try {
|
|
42
|
-
const switched = await switchChainAsync({ chainId: option.chain_id });
|
|
43
|
-
return switched?.id === option.chain_id;
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
console.error("Failed to switch chain", e);
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}, [walletChainId, switchChainAsync]);
|
|
50
|
-
const ensureCorrectChain = useCallback(async (token) => {
|
|
51
|
-
if (chainType !== ChainType.EVM)
|
|
52
|
-
return true;
|
|
53
|
-
setPayState(PayState.SwitchingChain);
|
|
54
|
-
return trySwitchingChain(token);
|
|
55
|
-
}, [chainType, trySwitchingChain]);
|
|
56
33
|
const executePayment = useCallback(async (token) => {
|
|
57
34
|
const txHash = await payFromWallet(token);
|
|
58
35
|
if (!txHash)
|
|
@@ -66,10 +43,6 @@ export default function PayWithToken() {
|
|
|
66
43
|
const handleTransfer = useCallback(async (token) => {
|
|
67
44
|
if (isRestricted)
|
|
68
45
|
return;
|
|
69
|
-
if (!(await ensureCorrectChain(token))) {
|
|
70
|
-
setPayState(PayState.RequestCancelled);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
46
|
setPayState(PayState.RequestingPayment);
|
|
74
47
|
try {
|
|
75
48
|
await executePayment(token);
|
|
@@ -77,17 +50,10 @@ export default function PayWithToken() {
|
|
|
77
50
|
setTimeout(() => setRoute(ROUTE.CONFIRMATION), 200);
|
|
78
51
|
}
|
|
79
52
|
catch (e) {
|
|
80
|
-
if (e?.name === "ConnectorChainMismatchError") {
|
|
81
|
-
log("Chain mismatch detected, retrying");
|
|
82
|
-
if (await trySwitchingChain(token, true)) {
|
|
83
|
-
await executePayment(token);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
53
|
setPayState(PayState.RequestCancelled);
|
|
88
54
|
log("Failed to pay with token", e);
|
|
89
55
|
}
|
|
90
|
-
}, [isRestricted,
|
|
56
|
+
}, [isRestricted, executePayment, setPayState, setRoute, log]);
|
|
91
57
|
useEffect(() => {
|
|
92
58
|
if (!selectedCurrencyOption)
|
|
93
59
|
return;
|
|
@@ -109,7 +75,7 @@ export default function PayWithToken() {
|
|
|
109
75
|
handleTransfer(selectedCurrencyOption);
|
|
110
76
|
}
|
|
111
77
|
};
|
|
112
|
-
return (_jsxs(PageContent, { children: [_jsx(LoadingContainer, { children: _jsxs(AnimationContainer, { "$shake": payState === PayState.RequestCancelled, "$circle": true, children: [_jsx(AnimatePresence, { children: payState === PayState.RequestCancelled || (isExpired && isDeposit) ? (_jsx(RetryButton, { "aria-label": "Retry", initial: { opacity: 0, scale: 0.8 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.8 }, whileTap: { scale: 0.9 }, transition: { duration: 0.1 }, onClick: onRetry, children: _jsx(RetryIconContainer, { children: _jsx(Tooltip, { open: payState === PayState.RequestCancelled, message: locales.tryAgainQuestion, xOffset: -6, children: _jsx(RetryIconCircle, {}) }) }) })) : (_jsx(ChainLogoContainer, { children: getChainLogo(selectedCurrencyOption?.chain_id) }, "ChainLogoContainer")) }), _jsx(AnimatePresence, { children: _jsx(CircleSpinner, { logo: _jsx("img", { src: selectedCurrencyOption?.image_uri, alt: selectedCurrencyOption?.ticker }, selectedCurrencyOption?.image_uri), loading: payState === PayState.RequestingPayment && !isExpired, unavailable: false }, "CircleSpinner") })] }) }), !isExpired ? (_jsxs(ModalContent, { children: [payState === PayState.RequestCancelled && (_jsxs(_Fragment, { children: [_jsxs(ModalH1, { "$warning": true, children: [_jsx(AlertIcon, {}), locales.injectionScreen_rejected_h1] }), _jsx(ModalBody, { children: locales.injectionScreen_rejected_p })] })), payState === PayState.
|
|
78
|
+
return (_jsxs(PageContent, { children: [_jsx(LoadingContainer, { children: _jsxs(AnimationContainer, { "$shake": payState === PayState.RequestCancelled, "$circle": true, children: [_jsx(AnimatePresence, { children: payState === PayState.RequestCancelled || (isExpired && isDeposit) ? (_jsx(RetryButton, { "aria-label": "Retry", initial: { opacity: 0, scale: 0.8 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.8 }, whileTap: { scale: 0.9 }, transition: { duration: 0.1 }, onClick: onRetry, children: _jsx(RetryIconContainer, { children: _jsx(Tooltip, { open: payState === PayState.RequestCancelled, message: locales.tryAgainQuestion, xOffset: -6, children: _jsx(RetryIconCircle, {}) }) }) })) : (_jsx(ChainLogoContainer, { children: getChainLogo(selectedCurrencyOption?.chain_id) }, "ChainLogoContainer")) }), _jsx(AnimatePresence, { children: _jsx(CircleSpinner, { logo: _jsx("img", { src: selectedCurrencyOption?.image_uri, alt: selectedCurrencyOption?.ticker }, selectedCurrencyOption?.image_uri), loading: payState === PayState.RequestingPayment && !isExpired, unavailable: false }, "CircleSpinner") })] }) }), !isExpired ? (_jsxs(ModalContent, { children: [payState === PayState.RequestCancelled && (_jsxs(_Fragment, { children: [_jsxs(ModalH1, { "$warning": true, children: [_jsx(AlertIcon, {}), locales.injectionScreen_rejected_h1] }), _jsx(ModalBody, { children: locales.injectionScreen_rejected_p })] })), payState === PayState.RequestingPayment && (_jsxs(_Fragment, { children: [_jsx(ModalH1, { children: locales.requesting_payment_h1 }), _jsx(ModalBody, { children: locales.requesting_payment_p })] })), payState === PayState.RequestSuccessful && _jsx(ModalH1, { children: locales.injectionScreen_connected_h1 })] })) : (_jsxs(ModalContent, { children: [_jsxs(ModalH1, { "$warning": true, children: [_jsx(AlertIcon, {}), locales.payWithTokenScreen_expired_h1] }), _jsx(ModalBody, { children: locales.payWithTokenScreen_expired_p })] }))] }));
|
|
113
79
|
}
|
|
114
80
|
const LoadingContainer = styled(motion.div) `
|
|
115
81
|
display: flex;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function PreparingPayment(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import styled from "../../../styles/styled";
|
|
4
|
+
import usePayContext from "../../contexts/pay";
|
|
5
|
+
import { PageContent } from "../../ui/Modal/styles";
|
|
6
|
+
import OptionsList from "../../ui/OptionsList";
|
|
7
|
+
import PoweredByFooter from "../../ui/PoweredByFooter";
|
|
8
|
+
import { Skeleton } from "../../ui/Skeleton";
|
|
9
|
+
export default function PreparingPayment() {
|
|
10
|
+
const { triggerResize } = usePayContext();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
triggerResize();
|
|
13
|
+
}, [triggerResize]);
|
|
14
|
+
return (_jsxs(PageContent, { children: [_jsxs(HeaderSkeleton, { children: [_jsx(AmountSkeleton, {}), _jsxs(SubtitleSkeletonRow, { children: [_jsxs(LogoSkeletonStack, { children: [_jsx(LogoSkeleton, {}), _jsx(LogoSkeleton, { "$overlap": true }), _jsx(LogoSkeleton, { "$overlap": true }), _jsx(LogoSkeleton, { "$overlap": true })] }), _jsx(SubtitleSkeleton, {})] })] }), _jsx(OptionsList, { options: [], isLoading: true, requiredSkeletons: 3 }), _jsx(PoweredByFooter, {})] }));
|
|
15
|
+
}
|
|
16
|
+
const HeaderSkeleton = styled.div `
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
`;
|
|
21
|
+
const BaseSkeleton = styled(Skeleton) `
|
|
22
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
23
|
+
`;
|
|
24
|
+
const AmountSkeleton = styled(BaseSkeleton) `
|
|
25
|
+
width: 184px;
|
|
26
|
+
height: 50px;
|
|
27
|
+
border-radius: 16px;
|
|
28
|
+
`;
|
|
29
|
+
const SubtitleSkeletonRow = styled.div `
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
margin: 24px 0;
|
|
35
|
+
`;
|
|
36
|
+
const LogoSkeletonStack = styled.div `
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
`;
|
|
40
|
+
const LogoSkeleton = styled(BaseSkeleton) `
|
|
41
|
+
width: 24px;
|
|
42
|
+
height: 24px;
|
|
43
|
+
border-radius: 9999px;
|
|
44
|
+
margin-left: ${(props) => (props.$overlap ? -8 : 0)}px;
|
|
45
|
+
`;
|
|
46
|
+
const SubtitleSkeleton = styled(BaseSkeleton) `
|
|
47
|
+
width: 160px;
|
|
48
|
+
height: 18px;
|
|
49
|
+
border-radius: 9999px;
|
|
50
|
+
`;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useIsClient } from "@coin-voyage/shared/hooks";
|
|
3
|
-
import { useQuery } from "@tanstack/react-query";
|
|
4
3
|
import { AnimatePresence } from "framer-motion";
|
|
5
4
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
6
5
|
import { usePaymentLifecycle } from "../../hooks/usePaymentLifecycle";
|
|
7
6
|
import { ResetContainer } from "../../styles";
|
|
7
|
+
import { ROUTE } from "../../types/routes";
|
|
8
8
|
import usePayContext from "../contexts/pay";
|
|
9
9
|
import ThemedButton, { ThemeContainer } from "../ui/ThemedButton";
|
|
10
10
|
import { SkeletonContainer, TextContainer } from "./styles";
|
|
@@ -19,97 +19,18 @@ export function PayButton(props) {
|
|
|
19
19
|
}
|
|
20
20
|
/** Like PayButton, but with custom styling. */
|
|
21
21
|
function PayButtonCustom(props) {
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
const { children, closeOnSuccess, resetOnSuccess } = props;
|
|
26
|
-
const hasDepositParams = "toAddress" in props;
|
|
27
|
-
const hasPayId = "payId" in props;
|
|
28
|
-
const depositParams = useMemo(() => {
|
|
29
|
-
if (!hasDepositParams)
|
|
30
|
-
return null;
|
|
31
|
-
return {
|
|
32
|
-
intent: {
|
|
33
|
-
asset: {
|
|
34
|
-
address: props.toToken,
|
|
35
|
-
chain_id: props.toChain,
|
|
36
|
-
},
|
|
37
|
-
receiving_address: props.toAddress,
|
|
38
|
-
amount: {
|
|
39
|
-
token_amount: props.toAmount,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
metadata: props.metadata,
|
|
43
|
-
};
|
|
44
|
-
}, [hasDepositParams, props]);
|
|
45
|
-
const payId = hasPayId ? props.payId : null;
|
|
22
|
+
const { options } = usePayContext();
|
|
23
|
+
usePayModalCallbacks(props.onOpen, props.onClose);
|
|
24
|
+
const { order, show, hide } = usePayButtonController(props);
|
|
46
25
|
usePaymentLifecycle(order, {
|
|
47
|
-
onPaymentStarted,
|
|
48
|
-
onPaymentCompleted,
|
|
49
|
-
onPaymentBounced,
|
|
26
|
+
onPaymentStarted: props.onPaymentStarted,
|
|
27
|
+
onPaymentCompleted: props.onPaymentCompleted,
|
|
28
|
+
onPaymentBounced: props.onPaymentBounced,
|
|
29
|
+
}, {
|
|
30
|
+
optimisticConfirmation: options?.optimisticConfirmation,
|
|
50
31
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
useQuery({
|
|
54
|
-
queryKey: [
|
|
55
|
-
"payOrder",
|
|
56
|
-
depositParams?.intent.asset?.address,
|
|
57
|
-
depositParams?.intent.asset?.chain_id,
|
|
58
|
-
depositParams?.intent.receiving_address,
|
|
59
|
-
depositParams?.intent.amount.token_amount,
|
|
60
|
-
metadataKey,
|
|
61
|
-
],
|
|
62
|
-
enabled: hasDepositParams,
|
|
63
|
-
staleTime: Infinity,
|
|
64
|
-
queryFn: async () => {
|
|
65
|
-
if (!depositParams)
|
|
66
|
-
return null;
|
|
67
|
-
await paymentState.createDepositPayOrder(depositParams, (msg) => props.onPaymentCreationError?.({
|
|
68
|
-
type: "payorder_creation_error",
|
|
69
|
-
errorMessage: msg,
|
|
70
|
-
}));
|
|
71
|
-
return null;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
// Load payment by ID when using payId
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (!payId)
|
|
77
|
-
return;
|
|
78
|
-
setPayId(payId);
|
|
79
|
-
}, [payId, setPayId]);
|
|
80
|
-
// Register open/close handlers
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
setOnOpen(props.onOpen);
|
|
83
|
-
setOnClose(props.onClose);
|
|
84
|
-
return () => {
|
|
85
|
-
setOnOpen(undefined);
|
|
86
|
-
setOnClose(undefined);
|
|
87
|
-
};
|
|
88
|
-
}, [props.onOpen, props.onClose, setOnOpen, setOnClose]);
|
|
89
|
-
const show = useCallback(() => {
|
|
90
|
-
if (!order)
|
|
91
|
-
return;
|
|
92
|
-
showModal({
|
|
93
|
-
closeOnSuccess,
|
|
94
|
-
resetOnSuccess,
|
|
95
|
-
});
|
|
96
|
-
}, [order, closeOnSuccess, resetOnSuccess, showModal]);
|
|
97
|
-
const hide = useCallback(() => {
|
|
98
|
-
setOpen(false);
|
|
99
|
-
}, [setOpen]);
|
|
100
|
-
// Auto-open modal
|
|
101
|
-
const hasAutoOpened = useRef(false);
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!props.defaultOpen)
|
|
104
|
-
return;
|
|
105
|
-
if (hasAutoOpened.current)
|
|
106
|
-
return;
|
|
107
|
-
if (!order)
|
|
108
|
-
return;
|
|
109
|
-
show();
|
|
110
|
-
hasAutoOpened.current = true;
|
|
111
|
-
}, [order, props.defaultOpen, show]);
|
|
112
|
-
return children({ show, hide });
|
|
32
|
+
useDefaultOpen(props.defaultOpen, show);
|
|
33
|
+
return props.children({ show, hide });
|
|
113
34
|
}
|
|
114
35
|
PayButtonCustom.displayName = "PayButton.Custom";
|
|
115
36
|
PayButton.Custom = PayButtonCustom;
|
|
@@ -140,7 +61,166 @@ const contentVariants = {
|
|
|
140
61
|
},
|
|
141
62
|
};
|
|
142
63
|
function ButtonInner({ label }) {
|
|
143
|
-
return (_jsx(AnimatePresence, { initial: false, children: _jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: {
|
|
144
|
-
|
|
145
|
-
|
|
64
|
+
return (_jsx(AnimatePresence, { initial: false, children: _jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: { height: 40 }, children: label }) }));
|
|
65
|
+
}
|
|
66
|
+
function isDepositParams(props) {
|
|
67
|
+
return "toAddress" in props;
|
|
68
|
+
}
|
|
69
|
+
function isPayIdProps(props) {
|
|
70
|
+
return "payId" in props;
|
|
71
|
+
}
|
|
72
|
+
function useResolvedPaymentInput(props) {
|
|
73
|
+
const hasDepositParams = isDepositParams(props);
|
|
74
|
+
const hasPayId = isPayIdProps(props);
|
|
75
|
+
const toToken = hasDepositParams ? props.toToken : undefined;
|
|
76
|
+
const toChain = hasDepositParams ? props.toChain : undefined;
|
|
77
|
+
const toAddress = hasDepositParams ? props.toAddress : undefined;
|
|
78
|
+
const toAmount = hasDepositParams ? props.toAmount : undefined;
|
|
79
|
+
const metadata = hasDepositParams ? props.metadata : undefined;
|
|
80
|
+
const payId = hasPayId ? props.payId : null;
|
|
81
|
+
const depositParams = useMemo(() => {
|
|
82
|
+
if (!hasDepositParams)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
intent: {
|
|
86
|
+
asset: {
|
|
87
|
+
address: toToken,
|
|
88
|
+
chain_id: toChain,
|
|
89
|
+
},
|
|
90
|
+
receiving_address: toAddress,
|
|
91
|
+
amount: {
|
|
92
|
+
token_amount: toAmount,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
metadata,
|
|
96
|
+
};
|
|
97
|
+
}, [hasDepositParams, toToken, toChain, toAddress, toAmount, metadata]);
|
|
98
|
+
if (payId) {
|
|
99
|
+
return {
|
|
100
|
+
kind: "payId",
|
|
101
|
+
payId,
|
|
102
|
+
depositParams: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
kind: "deposit",
|
|
107
|
+
payId: null,
|
|
108
|
+
depositParams: depositParams,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function usePayModalCallbacks(onOpen, onClose) {
|
|
112
|
+
const { setOnOpen, setOnClose } = usePayContext();
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
setOnOpen(onOpen);
|
|
115
|
+
setOnClose(onClose);
|
|
116
|
+
return () => {
|
|
117
|
+
setOnOpen(undefined);
|
|
118
|
+
setOnClose(undefined);
|
|
119
|
+
};
|
|
120
|
+
}, [onClose, onOpen, setOnClose, setOnOpen]);
|
|
121
|
+
}
|
|
122
|
+
function useDefaultOpen(enabled, show) {
|
|
123
|
+
const hasAutoOpened = useRef(false);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!enabled)
|
|
126
|
+
return;
|
|
127
|
+
if (hasAutoOpened.current)
|
|
128
|
+
return;
|
|
129
|
+
hasAutoOpened.current = true;
|
|
130
|
+
show();
|
|
131
|
+
}, [enabled, show]);
|
|
132
|
+
}
|
|
133
|
+
function useDepositPayOrderLoader(params) {
|
|
134
|
+
const { paymentState } = usePayContext();
|
|
135
|
+
const { depositParams, onPaymentCreationError } = params;
|
|
136
|
+
const handlePaymentCreationError = useCallback((msg) => {
|
|
137
|
+
onPaymentCreationError?.({
|
|
138
|
+
type: "payorder_creation_error",
|
|
139
|
+
errorMessage: msg,
|
|
140
|
+
});
|
|
141
|
+
}, [onPaymentCreationError]);
|
|
142
|
+
return useCallback(async () => {
|
|
143
|
+
if (!depositParams)
|
|
144
|
+
return undefined;
|
|
145
|
+
return paymentState.createDepositPayOrder(depositParams, handlePaymentCreationError);
|
|
146
|
+
}, [depositParams, handlePaymentCreationError, paymentState]);
|
|
147
|
+
}
|
|
148
|
+
function usePayButtonController(props) {
|
|
149
|
+
const { paymentState, showModal, setOpen, setRoute, open, route, displayError } = usePayContext();
|
|
150
|
+
const { payOrder: order, setPayId } = paymentState;
|
|
151
|
+
const { closeOnSuccess, resetOnSuccess, onPaymentCreationError } = props;
|
|
152
|
+
const paymentInput = useResolvedPaymentInput(props);
|
|
153
|
+
const queuedModalOptionsRef = useRef(null);
|
|
154
|
+
const paymentKind = paymentInput.kind;
|
|
155
|
+
const handlePaymentCreationError = useCallback((event) => {
|
|
156
|
+
queuedModalOptionsRef.current = null;
|
|
157
|
+
displayError(event.errorMessage);
|
|
158
|
+
onPaymentCreationError?.(event);
|
|
159
|
+
}, [displayError, onPaymentCreationError]);
|
|
160
|
+
const loadDepositPayOrder = useDepositPayOrderLoader({
|
|
161
|
+
depositParams: paymentInput.depositParams,
|
|
162
|
+
onPaymentCreationError: handlePaymentCreationError,
|
|
163
|
+
});
|
|
164
|
+
const payId = paymentKind === "payId" ? paymentInput.payId : null;
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!payId)
|
|
167
|
+
return;
|
|
168
|
+
setPayId(payId);
|
|
169
|
+
}, [payId, setPayId]);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const queuedModalOptions = queuedModalOptionsRef.current;
|
|
172
|
+
if (!queuedModalOptions || !order)
|
|
173
|
+
return;
|
|
174
|
+
queuedModalOptionsRef.current = null;
|
|
175
|
+
showModal(queuedModalOptions);
|
|
176
|
+
}, [order, showModal]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (paymentKind !== "deposit")
|
|
179
|
+
return;
|
|
180
|
+
if (open || route !== ROUTE.PREPARING_PAYMENT)
|
|
181
|
+
return;
|
|
182
|
+
queuedModalOptionsRef.current = null;
|
|
183
|
+
}, [open, paymentKind, route]);
|
|
184
|
+
const show = useCallback(() => {
|
|
185
|
+
const nextModalOptions = {
|
|
186
|
+
closeOnSuccess,
|
|
187
|
+
resetOnSuccess,
|
|
188
|
+
};
|
|
189
|
+
if (order) {
|
|
190
|
+
showModal(nextModalOptions);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
queuedModalOptionsRef.current = nextModalOptions;
|
|
194
|
+
if (paymentKind === "deposit") {
|
|
195
|
+
displayError(null);
|
|
196
|
+
setRoute(ROUTE.PREPARING_PAYMENT);
|
|
197
|
+
setOpen(true);
|
|
198
|
+
void loadDepositPayOrder();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (payId) {
|
|
202
|
+
void setPayId(payId);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}, [
|
|
206
|
+
closeOnSuccess,
|
|
207
|
+
displayError,
|
|
208
|
+
loadDepositPayOrder,
|
|
209
|
+
order,
|
|
210
|
+
payId,
|
|
211
|
+
paymentKind,
|
|
212
|
+
resetOnSuccess,
|
|
213
|
+
setOpen,
|
|
214
|
+
setPayId,
|
|
215
|
+
setRoute,
|
|
216
|
+
showModal,
|
|
217
|
+
]);
|
|
218
|
+
const hide = useCallback(() => {
|
|
219
|
+
setOpen(false);
|
|
220
|
+
}, [setOpen]);
|
|
221
|
+
return {
|
|
222
|
+
order,
|
|
223
|
+
show,
|
|
224
|
+
hide,
|
|
225
|
+
};
|
|
146
226
|
}
|
|
@@ -9,7 +9,7 @@ import { isWalletConnectConnector } from "../../utils";
|
|
|
9
9
|
import usePayContext from "../contexts/pay";
|
|
10
10
|
import Modal from "../ui/Modal";
|
|
11
11
|
const pages = Object.fromEntries(Object.entries(routeConfig).map(([key, cfg]) => [key, cfg.component]));
|
|
12
|
-
export function PayModal() {
|
|
12
|
+
export function PayModal({ onExited }) {
|
|
13
13
|
const { route, setOpen, setRoute, paymentState, mode, theme, customTheme } = usePayContext();
|
|
14
14
|
const { setConnectorChainType, clearUserSelection, setPayToAddressChainId: setPayToAddressChain, setPayToAddressCurrency, setSelectedCurrencyOption, selectedWallet, selectedCurrencyOption, connectorChainType, } = paymentState;
|
|
15
15
|
const locales = useLocales({
|
|
@@ -61,5 +61,5 @@ export function PayModal() {
|
|
|
61
61
|
setRoute(config.onBack);
|
|
62
62
|
}
|
|
63
63
|
}, [config, actions, setRoute, clearUserSelection]);
|
|
64
|
-
return (_jsx(ThemeProvider, { theme: theme, customTheme: customTheme, mode: mode, children: _jsx(Modal, { pages: pages, pageId: route, heading: heading, depth: depth, onClose: () => setOpen(false), onInfo: showInfoButton ? () => setRoute(ROUTE.ABOUT) : undefined, onBack: showBackButton ? onBack : undefined }) }));
|
|
64
|
+
return (_jsx(ThemeProvider, { theme: theme, customTheme: customTheme, mode: mode, children: _jsx(Modal, { pages: pages, pageId: route, heading: heading, depth: depth, onClose: () => setOpen(false), onExited: onExited, onInfo: showInfoButton ? () => setRoute(ROUTE.ABOUT) : undefined, onBack: showBackButton ? onBack : undefined }) }));
|
|
65
65
|
}
|
|
@@ -10,8 +10,9 @@ type ModalProps = {
|
|
|
10
10
|
positionInside?: boolean;
|
|
11
11
|
inline?: boolean;
|
|
12
12
|
onClose?: () => void;
|
|
13
|
+
onExited?: () => void;
|
|
13
14
|
onBack?: () => void;
|
|
14
15
|
onInfo?: () => void;
|
|
15
16
|
};
|
|
16
|
-
export default function Modal({ pages, pageId, heading, depth, positionInside, inline, onClose, onBack, onInfo, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export default function Modal({ pages, pageId, heading, depth, positionInside, inline, onClose, onExited, onBack, onInfo, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
18
|
export {};
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { getChainTypeName } from "@coin-voyage/shared/chain";
|
|
3
3
|
import { FocusTrap, usePrevious } from "@coin-voyage/shared/hooks";
|
|
4
4
|
import { isMobile } from "@coin-voyage/shared/utils";
|
|
5
|
+
import { useEffect } from "react";
|
|
5
6
|
import useLocales from "../../../hooks/useLocales";
|
|
6
7
|
import { useThemeContext } from "../../../providers/theme/provider";
|
|
7
8
|
import { ResetContainer } from "../../../styles";
|
|
@@ -37,7 +38,7 @@ export const contentVariants = {
|
|
|
37
38
|
transition: { duration: contentTransitionDuration, ease: [0.26, 0.08, 0.25, 1] },
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
|
-
export default function Modal({ pages, pageId, heading, depth = 1, positionInside, inline, onClose, onBack, onInfo, }) {
|
|
41
|
+
export default function Modal({ pages, pageId, heading, depth = 1, positionInside, inline, onClose, onExited, onBack, onInfo, }) {
|
|
41
42
|
const context = usePayContext();
|
|
42
43
|
const themeContext = useThemeContext();
|
|
43
44
|
const mobile = isMobile();
|
|
@@ -50,6 +51,12 @@ export default function Modal({ pages, pageId, heading, depth = 1, positionInsid
|
|
|
50
51
|
resizeDependency: context.resize,
|
|
51
52
|
});
|
|
52
53
|
const prevDepth = usePrevious(depth, depth);
|
|
54
|
+
const prevMounted = usePrevious(mounted, mounted);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (prevMounted && !mounted && !context.open) {
|
|
57
|
+
onExited?.();
|
|
58
|
+
}
|
|
59
|
+
}, [context.open, mounted, onExited, prevMounted]);
|
|
53
60
|
const Content = (_jsx(ResetContainer, { "$useTheme": themeContext.theme, "$useMode": themeContext.mode, "$customTheme": themeContext.customTheme, children: _jsxs(ModalContainer, { role: "dialog", style: { pointerEvents: rendered ? "auto" : "none", position: positionInside ? "absolute" : undefined }, children: [!inline && _jsx(BackgroundOverlay, { "$active": rendered, onClick: onClose, "$blur": context.options?.overlayBlur }), _jsxs(Container, { style: dimensionsCSS, initial: false, children: [_jsx("div", { style: {
|
|
54
61
|
pointerEvents: inTransition ? "all" : "none",
|
|
55
62
|
position: "absolute",
|
|
@@ -9,6 +9,7 @@ import MobileConnectors from "../components/Pages/MobileConnectors";
|
|
|
9
9
|
import Onboarding from "../components/Pages/Onboarding";
|
|
10
10
|
import PayToAddress from "../components/Pages/PayToAddress";
|
|
11
11
|
import PayWithToken from "../components/Pages/PayWithToken";
|
|
12
|
+
import PreparingPayment from "../components/Pages/PreparingPayment";
|
|
12
13
|
import SelectChain from "../components/Pages/SelectChain";
|
|
13
14
|
import SelectMethod from "../components/Pages/SelectMethod";
|
|
14
15
|
import SelectPayToAddressChain from "../components/Pages/SelectPayToAddressChain";
|
|
@@ -22,6 +23,12 @@ export const routeConfig = {
|
|
|
22
23
|
heading: (ctx) => ctx.locales.selectMethodScreen_heading,
|
|
23
24
|
showBackButton: false,
|
|
24
25
|
},
|
|
26
|
+
[ROUTE.PREPARING_PAYMENT]: {
|
|
27
|
+
component: _jsx(PreparingPayment, {}),
|
|
28
|
+
heading: () => "Preparing payment",
|
|
29
|
+
showBackButton: false,
|
|
30
|
+
showInfoButton: false,
|
|
31
|
+
},
|
|
25
32
|
[ROUTE.CARD_PAYMENT]: {
|
|
26
33
|
component: _jsx(CardPayment, {}),
|
|
27
34
|
heading: () => "Pay with Card",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ChainType, type PayOrder } from "@coin-voyage/shared/types";
|
|
2
2
|
import type { CurrencyAndQuoteID } from "../types/state";
|
|
3
3
|
interface PayFromWalletParams {
|
|
4
4
|
senderAddr: string | undefined;
|
|
@@ -1,8 +1,57 @@
|
|
|
1
1
|
import { usePrepareTransaction } from "@coin-voyage/crypto/hooks";
|
|
2
|
-
import {
|
|
2
|
+
import { PaymentRail, StepKind, } from "@coin-voyage/shared/types";
|
|
3
3
|
import { assert } from "@coin-voyage/shared/utils";
|
|
4
4
|
import { useBackendApi } from "../components/contexts/api";
|
|
5
5
|
import { fetchPaymentDetails } from "../lib/api/payment-details";
|
|
6
|
+
import { isCryptoPaymentData, isDepositStepData } from "@coin-voyage/shared/payment";
|
|
7
|
+
async function executeWalletStep({ actions, paymentData, senderAddr, step, }) {
|
|
8
|
+
if (step.kind === StepKind.KIND_TRANSACTION) {
|
|
9
|
+
assert(isCryptoPaymentData(step.data), "Transaction step is missing executable wallet data");
|
|
10
|
+
return actions.execute({
|
|
11
|
+
from: senderAddr,
|
|
12
|
+
paymentData: step.data,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
assert(step.kind === StepKind.KIND_DEPOSIT, "Unsupported wallet payment step");
|
|
16
|
+
assert(isDepositStepData(step.data), "Deposit step is missing a deposit address");
|
|
17
|
+
return actions.execute({
|
|
18
|
+
amount: BigInt(paymentData.src.currency_amount.raw_amount),
|
|
19
|
+
from: senderAddr,
|
|
20
|
+
to: step.data.deposit_address,
|
|
21
|
+
chainId: paymentData.src.chain_id,
|
|
22
|
+
token: step.data.currency.address
|
|
23
|
+
? { address: step.data.currency.address, decimals: paymentData.src.decimals }
|
|
24
|
+
: undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function executeWalletPayment({ actions, paymentData, senderAddr, log, }) {
|
|
28
|
+
let lastTxHash;
|
|
29
|
+
let sourceTxHash;
|
|
30
|
+
for (const [index, step] of paymentData.steps.entries()) {
|
|
31
|
+
if (step.rail !== PaymentRail.CRYPTO) {
|
|
32
|
+
throw new Error(`Unsupported payment rail for wallet execution: ${step.rail}`);
|
|
33
|
+
}
|
|
34
|
+
log(`[PAY-WITH-TOKEN] Executing step ${index + 1}/${paymentData.steps.length}: ${step.kind}`);
|
|
35
|
+
const txHash = await executeWalletStep({
|
|
36
|
+
actions,
|
|
37
|
+
paymentData,
|
|
38
|
+
senderAddr,
|
|
39
|
+
log,
|
|
40
|
+
step,
|
|
41
|
+
});
|
|
42
|
+
if (!txHash) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (!sourceTxHash && step.kind === StepKind.KIND_DEPOSIT) {
|
|
46
|
+
sourceTxHash = txHash;
|
|
47
|
+
}
|
|
48
|
+
lastTxHash = txHash;
|
|
49
|
+
log(`[PAY-WITH-TOKEN] Step ${index + 1}/${paymentData.steps.length} hash: ${txHash}`);
|
|
50
|
+
}
|
|
51
|
+
const txHash = sourceTxHash ?? lastTxHash;
|
|
52
|
+
assert(Boolean(txHash), "Payment execution did not produce a transaction hash");
|
|
53
|
+
return txHash;
|
|
54
|
+
}
|
|
6
55
|
export function usePayFromWallet({ senderAddr, payOrder, setPayOrder, chainType, log }) {
|
|
7
56
|
const actions = usePrepareTransaction(chainType);
|
|
8
57
|
const api = useBackendApi();
|
|
@@ -25,24 +74,15 @@ export function usePayFromWallet({ senderAddr, payOrder, setPayOrder, chainType,
|
|
|
25
74
|
const paymentData = paymentDetails.data;
|
|
26
75
|
log(`[PAY-WITH-TOKEN] Final Quote for Order: ${JSON.stringify(paymentDetails)}, params: ${JSON.stringify(params)}`);
|
|
27
76
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: await actions.execute({
|
|
38
|
-
amount: BigInt(paymentData.src.currency_amount.raw_amount),
|
|
39
|
-
from: senderAddr,
|
|
40
|
-
to: depositAddress,
|
|
41
|
-
chainId: paymentData.src.chain_id,
|
|
42
|
-
token: paymentData.src.address
|
|
43
|
-
? { address: paymentData.src.address, decimals: paymentData.src.decimals }
|
|
44
|
-
: undefined,
|
|
45
|
-
});
|
|
77
|
+
const txHash = await executeWalletPayment({
|
|
78
|
+
actions,
|
|
79
|
+
paymentData,
|
|
80
|
+
senderAddr,
|
|
81
|
+
log,
|
|
82
|
+
});
|
|
83
|
+
if (!txHash) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
46
86
|
const nextPaymentData = {
|
|
47
87
|
...paymentData,
|
|
48
88
|
source_tx_hash: txHash,
|
|
@@ -4,13 +4,17 @@ type PaymentLifecycleHandlers = {
|
|
|
4
4
|
onPaymentCompleted: ((event: PayOrderCompletedEvent) => void) | undefined;
|
|
5
5
|
onPaymentBounced: ((event: PayOrderRefundedEvent) => void) | undefined;
|
|
6
6
|
};
|
|
7
|
+
type PaymentLifecycleOptions = {
|
|
8
|
+
optimisticConfirmation?: boolean;
|
|
9
|
+
};
|
|
7
10
|
/**
|
|
8
11
|
* Handles payment lifecycle events of an order, such as started, completed, and bounced.
|
|
9
12
|
* @param {PayOrder | undefined} order The pay order to monitor.
|
|
10
13
|
* @param {PaymentLifecycleHandlers} handlers Handlers for payment lifecycle events.
|
|
14
|
+
* @param {PaymentLifecycleOptions} options Lifecycle behavior flags.
|
|
11
15
|
* @returns
|
|
12
16
|
*/
|
|
13
|
-
export declare function usePaymentLifecycle(order: PayOrder | undefined, handlers: PaymentLifecycleHandlers): {
|
|
17
|
+
export declare function usePaymentLifecycle(order: PayOrder | undefined, handlers: PaymentLifecycleHandlers, options?: PaymentLifecycleOptions): {
|
|
14
18
|
isStarted: boolean;
|
|
15
19
|
isFinalized: boolean;
|
|
16
20
|
order: PayOrder | undefined;
|
|
@@ -1,76 +1,82 @@
|
|
|
1
1
|
import { PayOrderMode, PayOrderStatus, } from "@coin-voyage/shared/types";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
const COMPLETED_STATES = [PayOrderStatus.COMPLETED, PayOrderStatus.REFUNDED];
|
|
4
|
+
const STARTED_STATES = [
|
|
5
|
+
...COMPLETED_STATES,
|
|
6
|
+
PayOrderStatus.PARTIAL_PAYMENT,
|
|
7
|
+
PayOrderStatus.AWAITING_CONFIRMATION,
|
|
8
|
+
PayOrderStatus.OPTIMISTIC_CONFIRMED,
|
|
9
|
+
PayOrderStatus.EXECUTING_ORDER,
|
|
10
|
+
];
|
|
3
11
|
/**
|
|
4
12
|
* Handles payment lifecycle events of an order, such as started, completed, and bounced.
|
|
5
13
|
* @param {PayOrder | undefined} order The pay order to monitor.
|
|
6
14
|
* @param {PaymentLifecycleHandlers} handlers Handlers for payment lifecycle events.
|
|
15
|
+
* @param {PaymentLifecycleOptions} options Lifecycle behavior flags.
|
|
7
16
|
* @returns
|
|
8
17
|
*/
|
|
9
|
-
export function usePaymentLifecycle(order, handlers) {
|
|
18
|
+
export function usePaymentLifecycle(order, handlers, options = {}) {
|
|
10
19
|
const sentStart = useRef(false);
|
|
11
20
|
const sentComplete = useRef(false);
|
|
21
|
+
const currentOrderId = useRef(undefined);
|
|
12
22
|
const { onPaymentStarted, onPaymentCompleted, onPaymentBounced } = handlers;
|
|
23
|
+
const { optimisticConfirmation = true } = options;
|
|
24
|
+
const orderId = order?.id;
|
|
13
25
|
const orderStatus = order?.status;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const finalizedStatus = [...completedStates];
|
|
32
|
-
if (isSale) {
|
|
33
|
-
finalizedStatus.push(PayOrderStatus.OPTIMISTIC_CONFIRMED, PayOrderStatus.EXECUTING_ORDER);
|
|
34
|
-
}
|
|
35
|
-
return finalizedStatus.includes(orderStatus);
|
|
36
|
-
}, [orderStatus, isSale, completedStates]);
|
|
26
|
+
const orderMetadata = order?.metadata;
|
|
27
|
+
const payment = order?.payment;
|
|
28
|
+
const allowOptimisticCompletion = optimisticConfirmation && order?.mode === PayOrderMode.SALE;
|
|
29
|
+
const isStarted = !!orderStatus && STARTED_STATES.includes(orderStatus);
|
|
30
|
+
const isFinalized = !!orderStatus &&
|
|
31
|
+
(COMPLETED_STATES.includes(orderStatus) ||
|
|
32
|
+
(allowOptimisticCompletion &&
|
|
33
|
+
(orderStatus === PayOrderStatus.OPTIMISTIC_CONFIRMED || orderStatus === PayOrderStatus.EXECUTING_ORDER)));
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!orderId)
|
|
36
|
+
return;
|
|
37
|
+
if (currentOrderId.current === orderId)
|
|
38
|
+
return;
|
|
39
|
+
currentOrderId.current = orderId;
|
|
40
|
+
sentStart.current = false;
|
|
41
|
+
sentComplete.current = false;
|
|
42
|
+
}, [orderId]);
|
|
37
43
|
useEffect(() => {
|
|
38
|
-
if (sentStart.current || !
|
|
44
|
+
if (sentStart.current || !orderId || !payment || !orderStatus || !isStarted)
|
|
39
45
|
return;
|
|
40
46
|
sentStart.current = true;
|
|
41
47
|
onPaymentStarted?.({
|
|
42
48
|
type: "payorder_confirming",
|
|
43
|
-
payorder_id:
|
|
44
|
-
status:
|
|
45
|
-
metadata:
|
|
46
|
-
payment_data:
|
|
49
|
+
payorder_id: orderId,
|
|
50
|
+
status: orderStatus,
|
|
51
|
+
metadata: orderMetadata,
|
|
52
|
+
payment_data: payment,
|
|
47
53
|
});
|
|
48
|
-
}, [
|
|
54
|
+
}, [isStarted, onPaymentStarted, orderId, orderMetadata, orderStatus, payment]);
|
|
49
55
|
useEffect(() => {
|
|
50
|
-
if (sentComplete.current || !
|
|
56
|
+
if (sentComplete.current || !orderId || !payment || !orderStatus || !isFinalized)
|
|
51
57
|
return;
|
|
52
58
|
sentComplete.current = true;
|
|
53
|
-
if (
|
|
59
|
+
if (orderStatus === PayOrderStatus.REFUNDED) {
|
|
54
60
|
onPaymentBounced?.({
|
|
55
61
|
type: "payorder_refunded",
|
|
56
|
-
payorder_id:
|
|
57
|
-
status:
|
|
58
|
-
metadata:
|
|
59
|
-
payment_data:
|
|
60
|
-
refund_address:
|
|
61
|
-
refund_tx_hash:
|
|
62
|
+
payorder_id: orderId,
|
|
63
|
+
status: orderStatus,
|
|
64
|
+
metadata: orderMetadata,
|
|
65
|
+
payment_data: payment,
|
|
66
|
+
refund_address: payment.refund_address ?? "",
|
|
67
|
+
refund_tx_hash: payment.refund_tx_hash ?? "",
|
|
62
68
|
});
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
onPaymentCompleted?.({
|
|
66
72
|
type: "payorder_completed",
|
|
67
|
-
payorder_id:
|
|
68
|
-
status:
|
|
69
|
-
metadata:
|
|
70
|
-
payment_data:
|
|
71
|
-
source_tx_hash:
|
|
72
|
-
destination_tx_hash:
|
|
73
|
+
payorder_id: orderId,
|
|
74
|
+
status: orderStatus,
|
|
75
|
+
metadata: orderMetadata,
|
|
76
|
+
payment_data: payment,
|
|
77
|
+
source_tx_hash: payment.source_tx_hash ?? "",
|
|
78
|
+
destination_tx_hash: payment.destination_tx_hash ?? "",
|
|
73
79
|
});
|
|
74
|
-
}, [
|
|
80
|
+
}, [isFinalized, onPaymentBounced, onPaymentCompleted, orderId, orderMetadata, orderStatus, payment]);
|
|
75
81
|
return { isStarted, isFinalized, order };
|
|
76
82
|
}
|
|
@@ -5,10 +5,11 @@ import { CurrencyAndQuoteID } from "../types/state";
|
|
|
5
5
|
import { usePayOrderQuotes } from "./usePayOrderQuotes";
|
|
6
6
|
/** Loads a PayOrder + manages the corresponding modal. */
|
|
7
7
|
export interface PaymentState {
|
|
8
|
-
setPayId: (id: string | null) => Promise<
|
|
9
|
-
createDepositPayOrder: (params: PayOrderParams, onError?: (message: string) => void) => Promise<
|
|
8
|
+
setPayId: (id: string | null) => Promise<PayOrder | undefined>;
|
|
9
|
+
createDepositPayOrder: (params: PayOrderParams, onError?: (message: string) => void) => Promise<PayOrder | undefined>;
|
|
10
10
|
copyDepositPayOrder: () => Promise<void>;
|
|
11
11
|
clearUserSelection: () => void;
|
|
12
|
+
resetPaymentState: () => void;
|
|
12
13
|
payOrder: PayOrder | undefined;
|
|
13
14
|
paymentMethod: PaymentMethod | undefined;
|
|
14
15
|
setPaymentMethod: React.Dispatch<React.SetStateAction<PaymentMethod | undefined>>;
|
|
@@ -31,7 +32,7 @@ export interface PaymentState {
|
|
|
31
32
|
}
|
|
32
33
|
export declare function usePaymentState({ payOrder, setPayOrder, setRoute, log, }: {
|
|
33
34
|
payOrder: PayOrder | undefined;
|
|
34
|
-
setPayOrder: (o: PayOrder) => void;
|
|
35
|
+
setPayOrder: (o: PayOrder | undefined) => void;
|
|
35
36
|
setRoute: React.Dispatch<React.SetStateAction<ROUTE>>;
|
|
36
37
|
log: (...args: unknown[]) => void;
|
|
37
38
|
}): PaymentState;
|
|
@@ -15,7 +15,8 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
|
|
|
15
15
|
const api = useBackendApi();
|
|
16
16
|
const [connectorChainType, setConnectorChainType] = useState();
|
|
17
17
|
const [selectedWallet, setSelectedWallet] = useState();
|
|
18
|
-
const
|
|
18
|
+
const latestRequestedIdRef = useRef(null);
|
|
19
|
+
const inFlightPayOrderIdsRef = useRef(new Set());
|
|
19
20
|
const { account: senderAccount } = useAccount({
|
|
20
21
|
selectedWallet,
|
|
21
22
|
chainType: connectorChainType,
|
|
@@ -74,13 +75,23 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
|
|
|
74
75
|
const setPayOrderId = useCallback(async (payOrderId) => {
|
|
75
76
|
if (!payOrderId)
|
|
76
77
|
return;
|
|
77
|
-
if (
|
|
78
|
+
if (payOrder?.id === payOrderId)
|
|
78
79
|
return;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
if (inFlightPayOrderIdsRef.current.has(payOrderId))
|
|
81
|
+
return;
|
|
82
|
+
latestRequestedIdRef.current = payOrderId;
|
|
83
|
+
inFlightPayOrderIdsRef.current.add(payOrderId);
|
|
84
|
+
try {
|
|
85
|
+
const order = await fetchPayOrder(payOrderId);
|
|
86
|
+
if (order && latestRequestedIdRef.current === payOrderId) {
|
|
87
|
+
setPayOrder(order);
|
|
88
|
+
}
|
|
89
|
+
return order;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
inFlightPayOrderIdsRef.current.delete(payOrderId);
|
|
93
|
+
}
|
|
94
|
+
}, [fetchPayOrder, payOrder?.id, setPayOrder]);
|
|
84
95
|
const copyDepositPayOrder = useCallback(async () => {
|
|
85
96
|
if (!payOrder?.id) {
|
|
86
97
|
log(`No payOrder to copy`);
|
|
@@ -124,10 +135,13 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
|
|
|
124
135
|
}
|
|
125
136
|
const { data: payOrder, error } = await api.createDepositPayOrder(params);
|
|
126
137
|
if (!payOrder || error) {
|
|
138
|
+
const errorMessage = error?.message || "Unable to create payment order";
|
|
139
|
+
onError?.(errorMessage);
|
|
127
140
|
log(`[CREATE DEPOSIT] Error creating payOrder: ${JSON.stringify(error)}`);
|
|
128
141
|
return;
|
|
129
142
|
}
|
|
130
143
|
setPayOrder(payOrder);
|
|
144
|
+
return payOrder;
|
|
131
145
|
}
|
|
132
146
|
catch (e) {
|
|
133
147
|
if (e instanceof Error)
|
|
@@ -142,11 +156,24 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
|
|
|
142
156
|
setSelectedWallet(undefined);
|
|
143
157
|
setRoute(ROUTE.SELECT_METHOD);
|
|
144
158
|
}, [setRoute]);
|
|
159
|
+
const resetPaymentState = useCallback(() => {
|
|
160
|
+
latestRequestedIdRef.current = null;
|
|
161
|
+
inFlightPayOrderIdsRef.current.clear();
|
|
162
|
+
setPayOrder(undefined);
|
|
163
|
+
setPaymentMethod(undefined);
|
|
164
|
+
setSelectedCurrencyOption(undefined);
|
|
165
|
+
setConnectorChainType(undefined);
|
|
166
|
+
setSelectedWallet(undefined);
|
|
167
|
+
setPayToAddressChainId(undefined);
|
|
168
|
+
setPayToAddressCurrency(undefined);
|
|
169
|
+
setRoute(ROUTE.SELECT_METHOD);
|
|
170
|
+
}, [setPayOrder, setRoute]);
|
|
145
171
|
return {
|
|
146
172
|
setPayId: setPayOrderId,
|
|
147
173
|
createDepositPayOrder,
|
|
148
174
|
copyDepositPayOrder,
|
|
149
175
|
clearUserSelection,
|
|
176
|
+
resetPaymentState,
|
|
150
177
|
payOrder,
|
|
151
178
|
paymentMethod,
|
|
152
179
|
setPaymentMethod,
|
|
@@ -33,7 +33,7 @@ export function useTokenOptions(disabled) {
|
|
|
33
33
|
disabled: isDisabled,
|
|
34
34
|
iconShape: "squircle",
|
|
35
35
|
icons: [
|
|
36
|
-
_jsx(TokenChainLogo, { src: quote.image_uri, alt: quote.ticker
|
|
36
|
+
_jsx(TokenChainLogo, { src: quote.image_uri, alt: `${quote.ticker} logo`, chainId: quote.chain_id }, `${quote.ticker}-${quote.chain_id}`),
|
|
37
37
|
],
|
|
38
38
|
onClick: () => {
|
|
39
39
|
setSelectedCurrencyOption(quote);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { getDepositAddress, getFiatPaymentStep, getWalletPaymentStep } from "@coin-voyage/shared/payment";
|
|
3
2
|
import { useInWagmiContext } from "@coin-voyage/crypto/evm";
|
|
4
3
|
import { useAccount, useConnectCallback } from "@coin-voyage/crypto/hooks";
|
|
4
|
+
import { getDepositAddress, getPaymentStep } from "@coin-voyage/shared/payment";
|
|
5
5
|
import { PaymentMethod, PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/types";
|
|
6
6
|
import { Buffer } from "buffer";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -35,6 +35,7 @@ export const PayKitProvider = ({ apiKey, environment = "production", ...props })
|
|
|
35
35
|
};
|
|
36
36
|
function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, options, onConnect, onConnectValidation, onDisconnect, debugMode = false, children, }) {
|
|
37
37
|
const { account } = useAccount();
|
|
38
|
+
const { isConnected, chainType } = account;
|
|
38
39
|
const [allowedWallets, setAllowedWallets] = useState(onConnectValidation ? [] : null);
|
|
39
40
|
useConnectCallback({
|
|
40
41
|
onConnect,
|
|
@@ -61,6 +62,10 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
61
62
|
const [resize, onResize] = useState(0);
|
|
62
63
|
const onOpenRef = useRef(undefined);
|
|
63
64
|
const onCloseRef = useRef(undefined);
|
|
65
|
+
const openRef = useRef(false);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
openRef.current = open;
|
|
68
|
+
}, [open]);
|
|
64
69
|
const setOnOpen = useCallback((fn) => {
|
|
65
70
|
onOpenRef.current = fn;
|
|
66
71
|
}, []);
|
|
@@ -100,20 +105,23 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
100
105
|
setErrorMessage(null);
|
|
101
106
|
}, [route, open]);
|
|
102
107
|
const resetAfterClose = useCallback(() => {
|
|
103
|
-
if (paymentCompleted
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
if (!paymentCompleted || !modalOptions?.resetOnSuccess)
|
|
109
|
+
return;
|
|
110
|
+
setPaymentCompleted(false);
|
|
111
|
+
paymentStateRef.current.resetPaymentState();
|
|
107
112
|
}, [modalOptions?.resetOnSuccess, paymentCompleted]);
|
|
108
113
|
const setOpen = useCallback((nextOpen) => {
|
|
114
|
+
if (openRef.current === nextOpen) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
openRef.current = nextOpen;
|
|
109
118
|
setOpenState(nextOpen);
|
|
110
119
|
if (nextOpen) {
|
|
111
120
|
onOpenRef.current?.();
|
|
112
121
|
return;
|
|
113
122
|
}
|
|
114
|
-
resetAfterClose();
|
|
115
123
|
onCloseRef.current?.();
|
|
116
|
-
}, [
|
|
124
|
+
}, []);
|
|
117
125
|
const onSuccess = useCallback(() => {
|
|
118
126
|
setPaymentCompleted(true);
|
|
119
127
|
if (modalOptions?.closeOnSuccess) {
|
|
@@ -160,7 +168,7 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
160
168
|
}
|
|
161
169
|
}, [setOpen, setPayId, setConnectorChainType]);
|
|
162
170
|
useEffect(() => {
|
|
163
|
-
const intervalMs = getPollingIntervalMs(payOrder);
|
|
171
|
+
const intervalMs = getPollingIntervalMs(payOrder?.status, payOrder?.mode);
|
|
164
172
|
if (!intervalMs)
|
|
165
173
|
return;
|
|
166
174
|
const timeoutId = setTimeout(refreshOrder, intervalMs);
|
|
@@ -174,7 +182,6 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
174
182
|
const showModal = useCallback((modalOptions) => {
|
|
175
183
|
setModalOptions(modalOptions);
|
|
176
184
|
setOpen(true);
|
|
177
|
-
const { isConnected, chainType } = account;
|
|
178
185
|
const { payOrder, paymentMethod } = paymentState;
|
|
179
186
|
const status = payOrder?.status;
|
|
180
187
|
const payment = payOrder?.payment;
|
|
@@ -186,18 +193,18 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
186
193
|
return;
|
|
187
194
|
}
|
|
188
195
|
if (status === PayOrderStatus.AWAITING_PAYMENT && payment) {
|
|
189
|
-
|
|
190
|
-
const walletStep = getWalletPaymentStep(payment);
|
|
191
|
-
const depositAddress = getDepositAddress(payment);
|
|
192
|
-
if (paymentMethod === PaymentMethod.CARD && fiatStep) {
|
|
196
|
+
if (paymentMethod === PaymentMethod.CARD) {
|
|
193
197
|
setRoute(ROUTE.CARD_PAYMENT);
|
|
194
198
|
return;
|
|
195
199
|
}
|
|
196
|
-
|
|
200
|
+
const step = getPaymentStep(payment);
|
|
201
|
+
const isTransactionStep = step?.kind === "transaction";
|
|
202
|
+
if (paymentMethod === PaymentMethod.WALLET || isTransactionStep) {
|
|
197
203
|
paymentState.setSelectedCurrencyOption(payment.src);
|
|
198
204
|
setRoute(ROUTE.WALLET_PAYMENT);
|
|
199
205
|
return;
|
|
200
206
|
}
|
|
207
|
+
const depositAddress = getDepositAddress(payment);
|
|
201
208
|
if (paymentMethod === PaymentMethod.DEPOSIT_ADDRESS && depositAddress) {
|
|
202
209
|
paymentState.setPayToAddressChainId(payment.src.chain_id);
|
|
203
210
|
paymentState.setPayToAddressCurrency(payment.src);
|
|
@@ -206,7 +213,7 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
215
|
setRoute(isConnected ? ROUTE.WALLET_TOKEN_SELECT : ROUTE.SELECT_METHOD);
|
|
209
|
-
}, [
|
|
216
|
+
}, [isConnected, chainType, paymentState, setOpen]);
|
|
210
217
|
const triggerResize = useCallback(() => onResize((prev) => prev + 1), []);
|
|
211
218
|
const displayError = useCallback((message, code) => {
|
|
212
219
|
setErrorMessage(message);
|
|
@@ -247,22 +254,22 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
247
254
|
paymentState,
|
|
248
255
|
allowedWallets,
|
|
249
256
|
};
|
|
250
|
-
return (_jsx(PayContext.Provider, { value: value, children: _jsxs(StyledThemeProvider, { theme: defaultTheme, children: [children, _jsx(PayModal, {})] }) }));
|
|
257
|
+
return (_jsx(PayContext.Provider, { value: value, children: _jsxs(StyledThemeProvider, { theme: defaultTheme, children: [children, _jsx(PayModal, { onExited: resetAfterClose })] }) }));
|
|
251
258
|
}
|
|
252
259
|
// === Helper functions ===
|
|
253
260
|
const NON_FINAL_PAY_ORDER_STATUSES = [PayOrderStatus.PENDING, PayOrderStatus.AWAITING_PAYMENT, PayOrderStatus.EXPIRED];
|
|
254
261
|
const isFinalPayOrderStatus = (status) => !!status && !NON_FINAL_PAY_ORDER_STATUSES.includes(status);
|
|
255
|
-
const getPollingIntervalMs = (
|
|
256
|
-
if (!
|
|
262
|
+
const getPollingIntervalMs = (status, mode) => {
|
|
263
|
+
if (!status)
|
|
257
264
|
return null;
|
|
258
|
-
if (
|
|
265
|
+
if (status === PayOrderStatus.AWAITING_PAYMENT) {
|
|
259
266
|
return 5000;
|
|
260
267
|
}
|
|
261
|
-
if ([PayOrderStatus.AWAITING_CONFIRMATION, PayOrderStatus.OPTIMISTIC_CONFIRMED].includes(
|
|
268
|
+
if ([PayOrderStatus.AWAITING_CONFIRMATION, PayOrderStatus.OPTIMISTIC_CONFIRMED].includes(status)) {
|
|
262
269
|
return 2500;
|
|
263
270
|
}
|
|
264
|
-
if (
|
|
265
|
-
return
|
|
271
|
+
if (status === PayOrderStatus.EXECUTING_ORDER) {
|
|
272
|
+
return mode === PayOrderMode.DEPOSIT ? 1000 : 2500;
|
|
266
273
|
}
|
|
267
274
|
return null;
|
|
268
275
|
};
|
package/dist/types/routes.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export declare enum ROUTE {
|
|
2
2
|
SELECT_METHOD = 0,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
PREPARING_PAYMENT = 1,
|
|
4
|
+
ADDRESS_CHAIN_SELECT = 2,
|
|
5
|
+
ADDRESS_TOKEN_SELECT = 3,
|
|
6
|
+
PAY_TO_ADDRESS = 4,
|
|
7
|
+
WALLET_CHAIN_SELECT = 5,
|
|
8
|
+
WALLET_TOKEN_SELECT = 6,
|
|
9
|
+
CONNECTORS = 7,
|
|
10
|
+
MOBILECONNECTORS = 8,
|
|
11
|
+
CONNECT = 9,
|
|
12
|
+
WALLET_PAYMENT = 10,
|
|
13
|
+
CARD_PAYMENT = 11,
|
|
14
|
+
CONFIRMATION = 12,
|
|
15
|
+
ONBOARDING = 13,
|
|
16
|
+
ABOUT = 14,
|
|
17
|
+
DOWNLOAD = 15
|
|
17
18
|
}
|
package/dist/types/routes.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
export var ROUTE;
|
|
2
2
|
(function (ROUTE) {
|
|
3
3
|
ROUTE[ROUTE["SELECT_METHOD"] = 0] = "SELECT_METHOD";
|
|
4
|
-
ROUTE[ROUTE["
|
|
5
|
-
ROUTE[ROUTE["
|
|
6
|
-
ROUTE[ROUTE["
|
|
7
|
-
ROUTE[ROUTE["
|
|
8
|
-
ROUTE[ROUTE["
|
|
9
|
-
ROUTE[ROUTE["
|
|
10
|
-
ROUTE[ROUTE["
|
|
11
|
-
ROUTE[ROUTE["
|
|
12
|
-
ROUTE[ROUTE["
|
|
13
|
-
ROUTE[ROUTE["
|
|
14
|
-
ROUTE[ROUTE["
|
|
4
|
+
ROUTE[ROUTE["PREPARING_PAYMENT"] = 1] = "PREPARING_PAYMENT";
|
|
5
|
+
ROUTE[ROUTE["ADDRESS_CHAIN_SELECT"] = 2] = "ADDRESS_CHAIN_SELECT";
|
|
6
|
+
ROUTE[ROUTE["ADDRESS_TOKEN_SELECT"] = 3] = "ADDRESS_TOKEN_SELECT";
|
|
7
|
+
ROUTE[ROUTE["PAY_TO_ADDRESS"] = 4] = "PAY_TO_ADDRESS";
|
|
8
|
+
ROUTE[ROUTE["WALLET_CHAIN_SELECT"] = 5] = "WALLET_CHAIN_SELECT";
|
|
9
|
+
ROUTE[ROUTE["WALLET_TOKEN_SELECT"] = 6] = "WALLET_TOKEN_SELECT";
|
|
10
|
+
ROUTE[ROUTE["CONNECTORS"] = 7] = "CONNECTORS";
|
|
11
|
+
ROUTE[ROUTE["MOBILECONNECTORS"] = 8] = "MOBILECONNECTORS";
|
|
12
|
+
ROUTE[ROUTE["CONNECT"] = 9] = "CONNECT";
|
|
13
|
+
ROUTE[ROUTE["WALLET_PAYMENT"] = 10] = "WALLET_PAYMENT";
|
|
14
|
+
ROUTE[ROUTE["CARD_PAYMENT"] = 11] = "CARD_PAYMENT";
|
|
15
|
+
ROUTE[ROUTE["CONFIRMATION"] = 12] = "CONFIRMATION";
|
|
15
16
|
// Other
|
|
16
|
-
ROUTE[ROUTE["ONBOARDING"] =
|
|
17
|
-
ROUTE[ROUTE["ABOUT"] =
|
|
18
|
-
ROUTE[ROUTE["DOWNLOAD"] =
|
|
17
|
+
ROUTE[ROUTE["ONBOARDING"] = 13] = "ONBOARDING";
|
|
18
|
+
ROUTE[ROUTE["ABOUT"] = 14] = "ABOUT";
|
|
19
|
+
ROUTE[ROUTE["DOWNLOAD"] = 15] = "DOWNLOAD";
|
|
19
20
|
})(ROUTE || (ROUTE = {}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coin-voyage/paykit",
|
|
3
3
|
"description": "Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.3",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "Lars <lars@coinvoyage.io>",
|
|
@@ -31,9 +31,6 @@
|
|
|
31
31
|
],
|
|
32
32
|
"server": [
|
|
33
33
|
"dist/server.d.ts"
|
|
34
|
-
],
|
|
35
|
-
"client": [
|
|
36
|
-
"dist/client.d.ts"
|
|
37
34
|
]
|
|
38
35
|
}
|
|
39
36
|
},
|
|
@@ -63,8 +60,8 @@
|
|
|
63
60
|
"@stripe/crypto": "0.0.4",
|
|
64
61
|
"styled-components": "^5.3.11",
|
|
65
62
|
"uuid": "13.0.0",
|
|
66
|
-
"@coin-voyage/crypto": "2.4.
|
|
67
|
-
"@coin-voyage/shared": "2.4.
|
|
63
|
+
"@coin-voyage/crypto": "2.4.3",
|
|
64
|
+
"@coin-voyage/shared": "2.4.3"
|
|
68
65
|
},
|
|
69
66
|
"devDependencies": {
|
|
70
67
|
"@types/qrcode": "1.5.5",
|
package/dist/utils/item.d.ts
DELETED