@coin-voyage/paykit 2.4.5-beta.2 → 2.4.5-beta.4
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/dist/components/Pages/CardPayment/index.d.ts +7 -0
- package/dist/components/Pages/CardPayment/index.js +92 -82
- package/dist/components/Pages/MobileConnectors/index.js +2 -1
- package/dist/hooks/useChainOptions.js +1 -1
- package/dist/hooks/usePaymentState.js +2 -3
- package/dist/hooks/useWallets.js +3 -2
- package/package.json +6 -6
|
@@ -1 +1,8 @@
|
|
|
1
|
+
import type { StripeOnramp } from "@stripe/crypto/types";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
1
3
|
export default function CardPayment(): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare const CryptoElements: ({ stripeOnramp, children, }: {
|
|
5
|
+
stripeOnramp: StripeOnramp | Promise<StripeOnramp | null>;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare const useStripeOnrampContext: () => StripeOnramp | null | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { getFiatPaymentData } from "@coin-voyage/shared/payment";
|
|
3
3
|
import { PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/types";
|
|
4
|
-
import { loadStripeOnramp } from "@stripe/crypto
|
|
4
|
+
import { loadStripeOnramp } from "@stripe/crypto";
|
|
5
5
|
import { useQuery } from "@tanstack/react-query";
|
|
6
|
-
import {
|
|
6
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { AlertIcon } from "../../../assets/icons";
|
|
8
8
|
import useLocales from "../../../hooks/useLocales";
|
|
9
9
|
import styled from "../../../styles/styled";
|
|
@@ -15,11 +15,7 @@ import { OrderHeader } from "../../ui/OrderHeader";
|
|
|
15
15
|
import PoweredByFooter from "../../ui/PoweredByFooter";
|
|
16
16
|
import { Spinner } from "../../ui/Spinner";
|
|
17
17
|
export default function CardPayment() {
|
|
18
|
-
const { triggerResize } = usePayContext();
|
|
19
18
|
const { data, isLoading, error, refetch } = useCardPaymentData();
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
triggerResize();
|
|
22
|
-
}, [triggerResize, isLoading, data?.session_id, data?.client_secret, error]);
|
|
23
19
|
return (_jsxs(PageContent, { children: [_jsx(OrderHeader, { minified: true }), _jsx(CardPaymentContent, { paymentData: data, isLoading: isLoading, error: error instanceof Error ? error : null, onRetry: () => {
|
|
24
20
|
void refetch();
|
|
25
21
|
} }), _jsx(PoweredByFooter, {})] }));
|
|
@@ -66,29 +62,14 @@ function StatusCard({ title, body, actionLabel, onAction, loading = false, warni
|
|
|
66
62
|
}, children: [loading ? _jsx(Spinner, {}) : warning ? _jsx(AlertIcon, {}) : null, _jsx(ModalH1, { "$warning": warning, children: title }), _jsx(ModalBody, { children: body }), actionLabel && onAction ? (_jsx(ActionRow, { children: _jsx(Button, { onClick: onAction, children: actionLabel }) })) : null] }));
|
|
67
63
|
}
|
|
68
64
|
function StripeOnrampCheckout({ paymentData }) {
|
|
69
|
-
const {
|
|
70
|
-
const { data: stripeOnramp, isLoading, error, refetch } =
|
|
71
|
-
const refreshOrderRef = useLatestRef(paymentState.refreshOrder);
|
|
65
|
+
const { mode, triggerResize } = usePayContext();
|
|
66
|
+
const { data: stripeOnramp, isLoading, error, refetch } = useLoadStripeOnramp(paymentData.stripe_publishable_key);
|
|
72
67
|
const refreshDebounceRef = useRef(null);
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
const appearance = useMemo(() => {
|
|
69
|
+
return {
|
|
70
|
+
theme: mode === "dark" ? "dark" : "light",
|
|
71
|
+
};
|
|
76
72
|
}, [mode]);
|
|
77
|
-
const handleSessionUpdate = useCallback((status) => {
|
|
78
|
-
if (status === lastStatusRef.current)
|
|
79
|
-
return;
|
|
80
|
-
lastStatusRef.current = status;
|
|
81
|
-
const shouldRefresh = status === "fulfillment_complete" || status === "rejected";
|
|
82
|
-
if (!shouldRefresh) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (refreshDebounceRef.current) {
|
|
86
|
-
window.clearTimeout(refreshDebounceRef.current);
|
|
87
|
-
}
|
|
88
|
-
refreshDebounceRef.current = window.setTimeout(() => {
|
|
89
|
-
void refreshOrderRef.current();
|
|
90
|
-
}, 500);
|
|
91
|
-
}, [refreshOrderRef]);
|
|
92
73
|
useEffect(() => {
|
|
93
74
|
return () => {
|
|
94
75
|
if (refreshDebounceRef.current) {
|
|
@@ -96,44 +77,41 @@ function StripeOnrampCheckout({ paymentData }) {
|
|
|
96
77
|
}
|
|
97
78
|
};
|
|
98
79
|
}, []);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
triggerResize();
|
|
82
|
+
}, [error, isLoading, stripeOnramp, triggerResize]);
|
|
99
83
|
if (error) {
|
|
100
84
|
return (_jsx(StatusCard, { warning: true, title: "Stripe unavailable", body: error.message, actionLabel: "Try again", onAction: () => {
|
|
101
85
|
void refetch();
|
|
102
86
|
} }));
|
|
103
87
|
}
|
|
104
|
-
return (_jsxs(OnrampShell, { children: [isLoading ? (_jsx(OnrampOverlay, { children: _jsx(Spinner, {}) })) : null, stripeOnramp ? (_jsx(
|
|
88
|
+
return (_jsxs(OnrampShell, { children: [isLoading ? (_jsx(OnrampOverlay, { children: _jsx(Spinner, {}) })) : null, paymentData.client_secret && stripeOnramp ? (_jsx(CryptoElements, { stripeOnramp: stripeOnramp, children: _jsx(OnrampElement, { id: "onramp-element", clientSecret: paymentData.client_secret, appearance: appearance, onReady: triggerResize }) })) : (_jsx(OnrampOverlay, { children: _jsx(Spinner, {}) }))] }));
|
|
105
89
|
}
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
90
|
+
function OnrampElement({ clientSecret, appearance, onReady, onChange, ...props }) {
|
|
91
|
+
const stripeOnramp = useStripeOnrampContext();
|
|
92
|
+
const onrampElementRef = useRef(null);
|
|
93
|
+
const [session, setSession] = useState();
|
|
94
|
+
const appearanceJSON = JSON.stringify(appearance);
|
|
110
95
|
useEffect(() => {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
96
|
+
const containerRef = onrampElementRef.current;
|
|
97
|
+
if (containerRef) {
|
|
98
|
+
// NB: ideally we want to be able to hot swap/update onramp iframe
|
|
99
|
+
// This currently results a flash if one needs to mint a new session when they need to update fixed transaction details
|
|
100
|
+
containerRef.innerHTML = "";
|
|
101
|
+
if (clientSecret && stripeOnramp) {
|
|
102
|
+
setSession(stripeOnramp
|
|
103
|
+
.createSession({
|
|
104
|
+
clientSecret,
|
|
105
|
+
appearance: appearanceJSON ? JSON.parse(appearanceJSON) : {},
|
|
106
|
+
})
|
|
107
|
+
.mount(containerRef));
|
|
108
|
+
}
|
|
114
109
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const handleUiLoaded = () => {
|
|
121
|
-
onUiLoadedRef.current?.();
|
|
122
|
-
};
|
|
123
|
-
const handleSessionUpdated = (event) => {
|
|
124
|
-
onSessionUpdateRef.current?.(event.payload.session.status);
|
|
125
|
-
};
|
|
126
|
-
session.addEventListener("onramp_ui_loaded", handleUiLoaded);
|
|
127
|
-
session.addEventListener("onramp_session_updated", handleSessionUpdated);
|
|
128
|
-
session.mount(mountNode);
|
|
129
|
-
return () => {
|
|
130
|
-
session.removeEventListener("onramp_ui_loaded", handleUiLoaded);
|
|
131
|
-
session.removeEventListener("onramp_session_updated", handleSessionUpdated);
|
|
132
|
-
mountNode.replaceChildren();
|
|
133
|
-
};
|
|
134
|
-
}, [clientSecret, onSessionUpdateRef, onUiLoadedRef, stripeOnramp, theme]);
|
|
135
|
-
return _jsx(OnrampMount, { ref: mountNodeRef });
|
|
136
|
-
});
|
|
110
|
+
}, [appearanceJSON, clientSecret, stripeOnramp]);
|
|
111
|
+
useOnrampSessionListener("onramp_ui_loaded", session, onReady);
|
|
112
|
+
useOnrampSessionListener("onramp_session_updated", session, onChange);
|
|
113
|
+
return _jsx(OnrampMount, { ...props, ref: onrampElementRef });
|
|
114
|
+
}
|
|
137
115
|
function useCardPaymentData() {
|
|
138
116
|
const { paymentState } = usePayContext();
|
|
139
117
|
const { payOrder, payWithCard } = paymentState;
|
|
@@ -163,41 +141,73 @@ function useCardPaymentData() {
|
|
|
163
141
|
data: paymentData ?? query.data,
|
|
164
142
|
};
|
|
165
143
|
}
|
|
166
|
-
|
|
144
|
+
const stripeOnrampPromises = new Map();
|
|
145
|
+
function getStripeOnramp(publishableKey) {
|
|
146
|
+
const existingPromise = stripeOnrampPromises.get(publishableKey);
|
|
147
|
+
if (existingPromise) {
|
|
148
|
+
return existingPromise;
|
|
149
|
+
}
|
|
150
|
+
const stripeOnrampPromise = loadStripeOnramp(publishableKey).catch((error) => {
|
|
151
|
+
stripeOnrampPromises.delete(publishableKey);
|
|
152
|
+
throw error;
|
|
153
|
+
});
|
|
154
|
+
stripeOnrampPromises.set(publishableKey, stripeOnrampPromise);
|
|
155
|
+
return stripeOnrampPromise;
|
|
156
|
+
}
|
|
157
|
+
function useLoadStripeOnramp(publishableKey) {
|
|
167
158
|
return useQuery({
|
|
168
159
|
queryKey: ["stripe-onramp", publishableKey],
|
|
169
160
|
enabled: Boolean(publishableKey),
|
|
170
161
|
staleTime: Infinity,
|
|
171
|
-
retry:
|
|
162
|
+
retry: 1,
|
|
163
|
+
refetchOnWindowFocus: false,
|
|
172
164
|
queryFn: async () => {
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
const stripeOnramp = await getStripeOnramp(publishableKey);
|
|
166
|
+
if (!stripeOnramp) {
|
|
167
|
+
throw new Error("Stripe checkout can only be loaded in a browser.");
|
|
175
168
|
}
|
|
176
|
-
|
|
177
|
-
if (!instance) {
|
|
178
|
-
throw new Error("Stripe Onramp is only available in the browser.");
|
|
179
|
-
}
|
|
180
|
-
return instance;
|
|
169
|
+
return stripeOnramp;
|
|
181
170
|
},
|
|
182
171
|
});
|
|
183
172
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
stripeOnrampPromiseCache.set(publishableKey, nextPromise);
|
|
192
|
-
return nextPromise;
|
|
193
|
-
}
|
|
194
|
-
function useLatestRef(value) {
|
|
195
|
-
const ref = useRef(value);
|
|
173
|
+
// ReactContext to simplify access of StripeOnramp object
|
|
174
|
+
const CryptoElementsContext = createContext(null);
|
|
175
|
+
CryptoElementsContext.displayName = "CryptoElementsContext";
|
|
176
|
+
export const CryptoElements = ({ stripeOnramp, children, }) => {
|
|
177
|
+
const [ctx, setContext] = useState(() => ({
|
|
178
|
+
onramp: null,
|
|
179
|
+
}));
|
|
196
180
|
useEffect(() => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
181
|
+
let isMounted = true;
|
|
182
|
+
Promise.resolve(stripeOnramp).then((onramp) => {
|
|
183
|
+
if (onramp && isMounted) {
|
|
184
|
+
setContext((ctx) => (ctx.onramp === onramp ? ctx : { onramp }));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return () => {
|
|
188
|
+
isMounted = false;
|
|
189
|
+
};
|
|
190
|
+
}, [stripeOnramp]);
|
|
191
|
+
return _jsx(CryptoElementsContext.Provider, { value: ctx, children: children });
|
|
192
|
+
};
|
|
193
|
+
// React hook to get StripeOnramp from context
|
|
194
|
+
export const useStripeOnrampContext = () => {
|
|
195
|
+
const context = useContext(CryptoElementsContext);
|
|
196
|
+
return context?.onramp;
|
|
197
|
+
};
|
|
198
|
+
// React element to render Onramp UI
|
|
199
|
+
const useOnrampSessionListener = (type, session, callback) => {
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (session && callback) {
|
|
202
|
+
const listener = (e) => callback(e.payload);
|
|
203
|
+
session.addEventListener(type, listener);
|
|
204
|
+
return () => {
|
|
205
|
+
session.removeEventListener(type, listener);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return () => { };
|
|
209
|
+
}, [session, callback, type]);
|
|
210
|
+
};
|
|
201
211
|
const OnrampShell = styled.div `
|
|
202
212
|
position: relative;
|
|
203
213
|
min-height: 480px;
|
|
@@ -12,6 +12,7 @@ import { ModalContent, PageContent } from "../../ui/Modal/styles";
|
|
|
12
12
|
import { ScrollArea } from "../../ui/ScrollArea";
|
|
13
13
|
import { Spinner } from "../../ui/Spinner";
|
|
14
14
|
import { Container, WalletIcon, WalletItem, WalletLabel, WalletList } from "./styles";
|
|
15
|
+
import { getConnectorId } from "@coin-voyage/crypto/utils";
|
|
15
16
|
const MoreIcon = (_jsx("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M30 42V19M19 30.5H42", stroke: "var(--ck-body-color-muted)", strokeWidth: "3", strokeLinecap: "round" }) }));
|
|
16
17
|
export default function MobileConnectors() {
|
|
17
18
|
const { paymentState } = usePayContext();
|
|
@@ -29,7 +30,7 @@ export default function MobileConnectors() {
|
|
|
29
30
|
// check if wallet is supports currentChainType
|
|
30
31
|
if (connectorChainType && wallet.chainTypes && !wallet.chainTypes.includes(connectorChainType))
|
|
31
32
|
return false;
|
|
32
|
-
if (wallets.find((w) => w.connectors.find((c) => c.connector
|
|
33
|
+
if (wallets.find((w) => w.connectors.find((c) => getConnectorId(c.connector) === walletId)))
|
|
33
34
|
return false;
|
|
34
35
|
return true;
|
|
35
36
|
}) ?? [];
|
|
@@ -3,7 +3,7 @@ import { useConfig as useBigmiConfig } from "@bigmi/react";
|
|
|
3
3
|
import { Arbitrum, Base, Bitcoin, Ethereum, Solana, Sui } from "@coin-voyage/shared/chain";
|
|
4
4
|
import { ChainType, PayOrderMode } from "@coin-voyage/shared/types";
|
|
5
5
|
import { truncateAddress, truncateENSName } from "@coin-voyage/shared/utils";
|
|
6
|
-
import { useWallets } from "@mysten/dapp-kit";
|
|
6
|
+
import { useWallets } from "@mysten/dapp-kit-react";
|
|
7
7
|
import { useWallet } from "@solana/wallet-adapter-react";
|
|
8
8
|
import { useCallback, useMemo } from "react";
|
|
9
9
|
import { useConfig as useWagmiConfig } from "wagmi";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { useAccount } from "@coin-voyage/crypto/hooks";
|
|
1
|
+
import { useAccount, useSuiNSName } from "@coin-voyage/crypto/hooks";
|
|
2
2
|
import { zPayOrder } from "@coin-voyage/shared/schemas";
|
|
3
3
|
import { ChainType, PayOrderMode, } from "@coin-voyage/shared/types";
|
|
4
|
-
import { useResolveSuiNSName } from "@mysten/dapp-kit";
|
|
5
4
|
import { useCallback, useRef, useState } from "react";
|
|
6
5
|
import { mainnet } from "viem/chains";
|
|
7
6
|
import { useEnsName } from "wagmi";
|
|
@@ -21,7 +20,7 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
|
|
|
21
20
|
selectedWallet,
|
|
22
21
|
chainType: connectorChainType,
|
|
23
22
|
});
|
|
24
|
-
const { data: suiEnsName } =
|
|
23
|
+
const { data: suiEnsName } = useSuiNSName(senderAccount.address, {
|
|
25
24
|
enabled: !!senderAccount.address && senderAccount.chainType === ChainType.SUI && senderAccount.isConnected,
|
|
26
25
|
});
|
|
27
26
|
const { data: evmEnsName } = useEnsName({
|
package/dist/hooks/useWallets.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useInstalledWallets } from "@coin-voyage/crypto/hooks";
|
|
|
3
3
|
import usePayContext from "../components/contexts/pay";
|
|
4
4
|
import { walletConfigs } from "../lib/config/wallet";
|
|
5
5
|
import { isInjectedConnector } from "../utils";
|
|
6
|
+
import { getConnectorId } from "@coin-voyage/crypto/utils";
|
|
6
7
|
export const useWallets = () => {
|
|
7
8
|
const { paymentState } = usePayContext();
|
|
8
9
|
const installedWallets = useInstalledWallets(paymentState.connectorChainType);
|
|
@@ -54,8 +55,8 @@ export const useWallets = () => {
|
|
|
54
55
|
self.find((w) => w.id === "io.metamask" || w.id === "io.metamask.mobile")))
|
|
55
56
|
// order by isInstalled injected connectors first
|
|
56
57
|
.sort((a, b) => {
|
|
57
|
-
const AisInstalled = a.isInstalled && isInjectedConnector(a.connectors[0].connector
|
|
58
|
-
const BisInstalled = b.isInstalled && isInjectedConnector(b.connectors[0].connector
|
|
58
|
+
const AisInstalled = a.isInstalled && isInjectedConnector(getConnectorId(a.connectors[0].connector));
|
|
59
|
+
const BisInstalled = b.isInstalled && isInjectedConnector(getConnectorId(b.connectors[0].connector));
|
|
59
60
|
if (AisInstalled && !BisInstalled)
|
|
60
61
|
return -1;
|
|
61
62
|
if (!AisInstalled && BisInstalled)
|
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.5-beta.
|
|
4
|
+
"version": "2.4.5-beta.4",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "Lars <lars@coinvoyage.io>",
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"react-transition-state": "^1.1.4",
|
|
58
58
|
"react-use-measure": "^2.1.1",
|
|
59
59
|
"@stripe/stripe-js": "8.10.0",
|
|
60
|
-
"@stripe/crypto": "
|
|
60
|
+
"@stripe/crypto": "1.0.2",
|
|
61
61
|
"styled-components": "^5.3.11",
|
|
62
62
|
"uuid": "13.0.0",
|
|
63
|
-
"@coin-voyage/
|
|
64
|
-
"@coin-voyage/
|
|
63
|
+
"@coin-voyage/crypto": "2.4.5-beta.1",
|
|
64
|
+
"@coin-voyage/shared": "2.4.5-beta.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/qrcode": "1.5.5",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"typescript-plugin-styled-components": "^3.0.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@bigmi/react": "^0.
|
|
74
|
-
"@mysten/dapp-kit": "^0.
|
|
73
|
+
"@bigmi/react": "^0.8.0",
|
|
74
|
+
"@mysten/dapp-kit-react": "^2.0.3",
|
|
75
75
|
"@solana/wallet-adapter-react": "^0.15.39",
|
|
76
76
|
"@tanstack/react-query": "^5.90.6",
|
|
77
77
|
"react": "^18 || ^19",
|