@coin-voyage/paykit 2.4.4 → 2.4.5-beta.1
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/pay-button/index.d.ts +11 -2
- package/dist/components/pay-button/index.js +3 -1
- package/dist/hooks/useOrderStatusWS.d.ts +9 -0
- package/dist/hooks/useOrderStatusWS.js +49 -0
- package/dist/hooks/usePaymentLifecycle.d.ts +4 -2
- package/dist/hooks/usePaymentLifecycle.js +63 -27
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/api/index.d.ts +1 -1
- package/dist/lib/api/index.js +1 -1
- package/dist/providers/paykit-provider.js +20 -21
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChainId, PayOrderCompletedEvent, PayOrderConfirmingEvent, PayOrderCreationErrorEvent, PayOrderMetadata, PayOrderRefundedEvent } from "@coin-voyage/shared/types";
|
|
1
|
+
import type { ChainId, PayOrderCompletedEvent, PayOrderConfirmingEvent, PayOrderCreationErrorEvent, PayOrderExecutingEvent, PayOrderMetadata, PayOrderRefundedEvent, PayOrderStartedEvent } from "@coin-voyage/shared/types";
|
|
2
2
|
import type { CustomTheme, Mode, PayModalOptions, Theme } from "../../types";
|
|
3
3
|
type DepositPayButtonParams = {
|
|
4
4
|
/**
|
|
@@ -34,8 +34,17 @@ type PayButtonCommonProps = PayButtonPaymentProps & {
|
|
|
34
34
|
intent?: string;
|
|
35
35
|
/** Called when invalid properties are used in order to create a deposit payOrder */
|
|
36
36
|
onPaymentCreationError?: (event: PayOrderCreationErrorEvent) => void;
|
|
37
|
-
/** Called when
|
|
37
|
+
/** Called when payment details are available and the order is awaiting payment. */
|
|
38
|
+
onAwaitingPayment?: (event: PayOrderStartedEvent) => void;
|
|
39
|
+
/** Called when the payment is detected and awaiting confirmation. */
|
|
40
|
+
onConfirmingPayment?: (event: PayOrderConfirmingEvent) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Called when the payment is detected and awaiting confirmation.
|
|
43
|
+
* @deprecated Use `onConfirmingPayment` instead.
|
|
44
|
+
*/
|
|
38
45
|
onPaymentStarted?: (event: PayOrderConfirmingEvent) => void;
|
|
46
|
+
/** Called when the payment is confirmed and the order is executing. */
|
|
47
|
+
onExecutingPayment?: (event: PayOrderExecutingEvent) => void;
|
|
39
48
|
/** Called when destination transfer or call completes successfully */
|
|
40
49
|
onPaymentCompleted?: (event: PayOrderCompletedEvent) => void;
|
|
41
50
|
/** Called when destination call reverts and funds are refunded */
|
|
@@ -23,7 +23,9 @@ function PayButtonCustom(props) {
|
|
|
23
23
|
usePayModalCallbacks(props.onOpen, props.onClose);
|
|
24
24
|
const { order, show, hide } = usePayButtonController(props);
|
|
25
25
|
usePaymentLifecycle(order, {
|
|
26
|
-
|
|
26
|
+
onAwaitingPayment: props.onAwaitingPayment,
|
|
27
|
+
onConfirmingPayment: props.onConfirmingPayment ?? props.onPaymentStarted,
|
|
28
|
+
onExecutingPayment: props.onExecutingPayment,
|
|
27
29
|
onPaymentCompleted: props.onPaymentCompleted,
|
|
28
30
|
onPaymentBounced: props.onPaymentBounced,
|
|
29
31
|
}, {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PayOrderEvent } from "@coin-voyage/shared/types";
|
|
2
|
+
interface UseOrderStatusWSProps {
|
|
3
|
+
orderId?: string;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
onEvent?: (eventData: PayOrderEvent) => void;
|
|
6
|
+
onError?: (error: unknown) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useOrderStatusWS({ orderId, enabled, onEvent, onError, }: UseOrderStatusWSProps): void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useBackendApi } from "../components/contexts/api";
|
|
3
|
+
export function useOrderStatusWS({ orderId, enabled = true, onEvent, onError, }) {
|
|
4
|
+
const api = useBackendApi();
|
|
5
|
+
const socketRef = useRef(null);
|
|
6
|
+
const onEventRef = useRef(null);
|
|
7
|
+
const onErrorRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
onEventRef.current = onEvent;
|
|
10
|
+
}, [onEvent]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
onErrorRef.current = onError;
|
|
13
|
+
}, [onError]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!enabled)
|
|
16
|
+
return;
|
|
17
|
+
if (socketRef.current)
|
|
18
|
+
return;
|
|
19
|
+
const socket = api.subscribeOrderStatus();
|
|
20
|
+
socketRef.current = socket;
|
|
21
|
+
socket.onOpen(() => {
|
|
22
|
+
if (orderId) {
|
|
23
|
+
socket.subscribe(orderId);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
socket.subscribeOrg();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
socket.onMessage((msg) => {
|
|
30
|
+
if (msg.type === "event") {
|
|
31
|
+
onEventRef.current?.(msg.data);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
socket.onError((e) => {
|
|
35
|
+
onErrorRef.current?.(e);
|
|
36
|
+
});
|
|
37
|
+
socket.onClose(() => {
|
|
38
|
+
if (socketRef.current === socket) {
|
|
39
|
+
socketRef.current = null;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return () => {
|
|
43
|
+
if (socketRef.current === socket) {
|
|
44
|
+
socketRef.current = null;
|
|
45
|
+
}
|
|
46
|
+
socket.close();
|
|
47
|
+
};
|
|
48
|
+
}, [api, enabled, orderId]);
|
|
49
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { PayOrder, PayOrderCompletedEvent, PayOrderConfirmingEvent, PayOrderRefundedEvent } from "@coin-voyage/shared/types";
|
|
1
|
+
import { PayOrder, PayOrderCompletedEvent, PayOrderConfirmingEvent, PayOrderExecutingEvent, PayOrderRefundedEvent, PayOrderStartedEvent } from "@coin-voyage/shared/types";
|
|
2
2
|
type PaymentLifecycleHandlers = {
|
|
3
|
-
|
|
3
|
+
onAwaitingPayment: ((event: PayOrderStartedEvent) => void) | undefined;
|
|
4
|
+
onConfirmingPayment: ((event: PayOrderConfirmingEvent) => void) | undefined;
|
|
5
|
+
onExecutingPayment: ((event: PayOrderExecutingEvent) => void) | undefined;
|
|
4
6
|
onPaymentCompleted: ((event: PayOrderCompletedEvent) => void) | undefined;
|
|
5
7
|
onPaymentBounced: ((event: PayOrderRefundedEvent) => void) | undefined;
|
|
6
8
|
};
|
|
@@ -2,11 +2,13 @@ import { PayOrderMode, PayOrderStatus, } from "@coin-voyage/shared/types";
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
const COMPLETED_STATES = [PayOrderStatus.COMPLETED, PayOrderStatus.REFUNDED];
|
|
4
4
|
const STARTED_STATES = [
|
|
5
|
-
|
|
5
|
+
PayOrderStatus.AWAITING_PAYMENT,
|
|
6
6
|
PayOrderStatus.PARTIAL_PAYMENT,
|
|
7
7
|
PayOrderStatus.AWAITING_CONFIRMATION,
|
|
8
8
|
PayOrderStatus.OPTIMISTIC_CONFIRMED,
|
|
9
9
|
PayOrderStatus.EXECUTING_ORDER,
|
|
10
|
+
PayOrderStatus.COMPLETED,
|
|
11
|
+
PayOrderStatus.REFUNDED,
|
|
10
12
|
];
|
|
11
13
|
/**
|
|
12
14
|
* Handles payment lifecycle events of an order, such as started, completed, and bounced.
|
|
@@ -16,58 +18,92 @@ const STARTED_STATES = [
|
|
|
16
18
|
* @returns
|
|
17
19
|
*/
|
|
18
20
|
export function usePaymentLifecycle(order, handlers, options = {}) {
|
|
19
|
-
const
|
|
21
|
+
const sentAwaitingPayment = useRef(false);
|
|
22
|
+
const sentConfirmingPayment = useRef(false);
|
|
23
|
+
const sentExecutingPayment = useRef(false);
|
|
20
24
|
const sentComplete = useRef(false);
|
|
25
|
+
const sentBounced = useRef(false);
|
|
21
26
|
const currentOrderId = useRef(undefined);
|
|
22
|
-
const {
|
|
27
|
+
const { onAwaitingPayment, onConfirmingPayment, onExecutingPayment, onPaymentCompleted, onPaymentBounced } = handlers;
|
|
23
28
|
const { optimisticConfirmation = true } = options;
|
|
24
29
|
const orderId = order?.id;
|
|
25
30
|
const orderStatus = order?.status;
|
|
26
31
|
const orderMetadata = order?.metadata;
|
|
27
32
|
const payment = order?.payment;
|
|
28
|
-
const
|
|
33
|
+
const allowOptimisticFinalized = optimisticConfirmation && order?.mode === PayOrderMode.SALE;
|
|
29
34
|
const isStarted = !!orderStatus && STARTED_STATES.includes(orderStatus);
|
|
30
|
-
const isFinalized = !!orderStatus &&
|
|
31
|
-
(
|
|
32
|
-
(
|
|
33
|
-
(orderStatus === PayOrderStatus.OPTIMISTIC_CONFIRMED || orderStatus === PayOrderStatus.EXECUTING_ORDER)));
|
|
35
|
+
const isFinalized = (!!orderStatus && COMPLETED_STATES.includes(orderStatus)) ||
|
|
36
|
+
(allowOptimisticFinalized &&
|
|
37
|
+
(orderStatus === PayOrderStatus.OPTIMISTIC_CONFIRMED || orderStatus === PayOrderStatus.EXECUTING_ORDER));
|
|
34
38
|
useEffect(() => {
|
|
35
39
|
if (!orderId)
|
|
36
40
|
return;
|
|
37
41
|
if (currentOrderId.current === orderId)
|
|
38
42
|
return;
|
|
39
43
|
currentOrderId.current = orderId;
|
|
40
|
-
|
|
44
|
+
sentAwaitingPayment.current = false;
|
|
45
|
+
sentConfirmingPayment.current = false;
|
|
46
|
+
sentExecutingPayment.current = false;
|
|
41
47
|
sentComplete.current = false;
|
|
48
|
+
sentBounced.current = false;
|
|
42
49
|
}, [orderId]);
|
|
43
50
|
useEffect(() => {
|
|
44
|
-
if (
|
|
51
|
+
if (sentAwaitingPayment.current || !orderId || !payment || orderStatus !== PayOrderStatus.AWAITING_PAYMENT)
|
|
45
52
|
return;
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
sentAwaitingPayment.current = true;
|
|
54
|
+
onAwaitingPayment?.({
|
|
55
|
+
type: "payorder_started",
|
|
56
|
+
payorder_id: orderId,
|
|
57
|
+
status: orderStatus,
|
|
58
|
+
metadata: orderMetadata,
|
|
59
|
+
payment_data: payment,
|
|
60
|
+
});
|
|
61
|
+
}, [onAwaitingPayment, orderId, orderMetadata, orderStatus, payment]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (sentConfirmingPayment.current || !orderId || !payment || orderStatus !== PayOrderStatus.AWAITING_CONFIRMATION) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
sentConfirmingPayment.current = true;
|
|
67
|
+
onConfirmingPayment?.({
|
|
48
68
|
type: "payorder_confirming",
|
|
49
69
|
payorder_id: orderId,
|
|
50
70
|
status: orderStatus,
|
|
51
71
|
metadata: orderMetadata,
|
|
52
72
|
payment_data: payment,
|
|
73
|
+
source_tx_hash: payment.source_tx_hash ?? "",
|
|
53
74
|
});
|
|
54
|
-
}, [
|
|
75
|
+
}, [onConfirmingPayment, orderId, orderMetadata, orderStatus, payment]);
|
|
55
76
|
useEffect(() => {
|
|
56
|
-
if (
|
|
77
|
+
if (sentExecutingPayment.current || !orderId || !payment || orderStatus !== PayOrderStatus.EXECUTING_ORDER)
|
|
57
78
|
return;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
sentExecutingPayment.current = true;
|
|
80
|
+
onExecutingPayment?.({
|
|
81
|
+
type: "payorder_executing",
|
|
82
|
+
payorder_id: orderId,
|
|
83
|
+
status: orderStatus,
|
|
84
|
+
metadata: orderMetadata,
|
|
85
|
+
payment_data: payment,
|
|
86
|
+
source_tx_hash: payment.source_tx_hash ?? "",
|
|
87
|
+
});
|
|
88
|
+
}, [onExecutingPayment, orderId, orderMetadata, orderStatus, payment]);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (sentBounced.current || !orderId || !payment || orderStatus !== PayOrderStatus.REFUNDED)
|
|
69
91
|
return;
|
|
70
|
-
|
|
92
|
+
sentBounced.current = true;
|
|
93
|
+
onPaymentBounced?.({
|
|
94
|
+
type: "payorder_refunded",
|
|
95
|
+
payorder_id: orderId,
|
|
96
|
+
status: orderStatus,
|
|
97
|
+
metadata: orderMetadata,
|
|
98
|
+
payment_data: payment,
|
|
99
|
+
refund_address: payment.refund_address ?? "",
|
|
100
|
+
refund_tx_hash: payment.refund_tx_hash ?? "",
|
|
101
|
+
});
|
|
102
|
+
}, [onPaymentBounced, orderId, orderMetadata, orderStatus, payment]);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (sentComplete.current || !orderId || !payment || orderStatus !== PayOrderStatus.COMPLETED)
|
|
105
|
+
return;
|
|
106
|
+
sentComplete.current = true;
|
|
71
107
|
onPaymentCompleted?.({
|
|
72
108
|
type: "payorder_completed",
|
|
73
109
|
payorder_id: orderId,
|
|
@@ -77,6 +113,6 @@ export function usePaymentLifecycle(order, handlers, options = {}) {
|
|
|
77
113
|
source_tx_hash: payment.source_tx_hash ?? "",
|
|
78
114
|
destination_tx_hash: payment.destination_tx_hash ?? "",
|
|
79
115
|
});
|
|
80
|
-
}, [
|
|
116
|
+
}, [onPaymentCompleted, orderId, orderMetadata, orderStatus, payment]);
|
|
81
117
|
return { isStarted, isFinalized, order };
|
|
82
118
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { WalletProvider } from "@coin-voyage/crypto/wallets";
|
|
2
2
|
export { PayButton } from "./components/pay-button";
|
|
3
3
|
export { PayKitProvider } from "./providers/paykit-provider";
|
|
4
|
+
export { useOrderStatusWS } from "./hooks/useOrderStatusWS";
|
|
4
5
|
export { usePayStatus } from "./hooks/usePayStatus";
|
|
5
6
|
export * as themes from "./styles/themes";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { WalletProvider } from "@coin-voyage/crypto/wallets";
|
|
2
2
|
export { PayButton } from "./components/pay-button";
|
|
3
3
|
export { PayKitProvider } from "./providers/paykit-provider";
|
|
4
|
+
export { useOrderStatusWS } from "./hooks/useOrderStatusWS";
|
|
4
5
|
export { usePayStatus } from "./hooks/usePayStatus";
|
|
5
6
|
export * as themes from "./styles/themes";
|
package/dist/lib/api/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiClient as ApiClientInternal, APIEnvironment } from "@coin-voyage/shared/api";
|
|
2
|
-
export declare function ApiClient({ apiKey, environment
|
|
2
|
+
export declare function ApiClient({ apiKey, environment }: {
|
|
3
3
|
apiKey: string;
|
|
4
4
|
environment?: APIEnvironment;
|
|
5
5
|
}): ApiClientInternal;
|
package/dist/lib/api/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiClient as ApiClientInternal } from "@coin-voyage/shared/api";
|
|
2
2
|
import { paykitVersion } from "../../utils/version";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
|
-
export function ApiClient({ apiKey, environment = "production"
|
|
4
|
+
export function ApiClient({ apiKey, environment = "production" }) {
|
|
5
5
|
return new ApiClientInternal({
|
|
6
6
|
apiKey,
|
|
7
7
|
version: paykitVersion,
|
|
@@ -2,13 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useInWagmiContext } from "@coin-voyage/crypto/evm";
|
|
3
3
|
import { useAccount, useConnectCallback } from "@coin-voyage/crypto/hooks";
|
|
4
4
|
import { getDepositAddress, getPaymentStep } from "@coin-voyage/shared/payment";
|
|
5
|
-
import { PaymentMethod,
|
|
5
|
+
import { PaymentMethod, PayOrderStatus } from "@coin-voyage/shared/types";
|
|
6
6
|
import { Buffer } from "buffer";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { ThemeProvider as StyledThemeProvider } from "styled-components";
|
|
9
9
|
import { PayContext } from "../components/contexts/pay/index";
|
|
10
10
|
import { PayModal } from "../components/pay-modal/index";
|
|
11
11
|
import { useThemeFont } from "../hooks/useGoogleFont";
|
|
12
|
+
import { useOrderStatusWS } from "../hooks/useOrderStatusWS";
|
|
12
13
|
import { usePaymentState } from "../hooks/usePaymentState";
|
|
13
14
|
import defaultTheme from "../styles/defaultTheme";
|
|
14
15
|
import { ROUTE } from "../types/routes";
|
|
@@ -167,13 +168,17 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
167
168
|
removeInitStateParam();
|
|
168
169
|
}
|
|
169
170
|
}, [setOpen, setPayId, setConnectorChainType]);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!intervalMs)
|
|
171
|
+
const onOrderStatusEvent = useCallback((event) => {
|
|
172
|
+
if (!("payorder_id" in event) || event.payorder_id !== payOrder?.id)
|
|
173
173
|
return;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
void refreshOrder();
|
|
175
|
+
}, [payOrder?.id, refreshOrder]);
|
|
176
|
+
useOrderStatusWS({
|
|
177
|
+
orderId: payOrder?.id,
|
|
178
|
+
enabled: shouldSubscribeOrderStatus(payOrder?.status),
|
|
179
|
+
onEvent: onOrderStatusEvent,
|
|
180
|
+
onError: log,
|
|
181
|
+
});
|
|
177
182
|
useEffect(() => {
|
|
178
183
|
if (isFinalPayOrderStatus(payOrder?.status)) {
|
|
179
184
|
setRoute(ROUTE.CONFIRMATION);
|
|
@@ -259,17 +264,11 @@ function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, op
|
|
|
259
264
|
// === Helper functions ===
|
|
260
265
|
const NON_FINAL_PAY_ORDER_STATUSES = [PayOrderStatus.PENDING, PayOrderStatus.AWAITING_PAYMENT, PayOrderStatus.EXPIRED];
|
|
261
266
|
const isFinalPayOrderStatus = (status) => !!status && !NON_FINAL_PAY_ORDER_STATUSES.includes(status);
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
if (status === PayOrderStatus.EXECUTING_ORDER) {
|
|
272
|
-
return mode === PayOrderMode.DEPOSIT ? 1000 : 2500;
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
};
|
|
267
|
+
const ORDER_STATUS_SUBSCRIPTION_STATUSES = [
|
|
268
|
+
PayOrderStatus.PENDING,
|
|
269
|
+
PayOrderStatus.AWAITING_PAYMENT,
|
|
270
|
+
PayOrderStatus.AWAITING_CONFIRMATION,
|
|
271
|
+
PayOrderStatus.OPTIMISTIC_CONFIRMED,
|
|
272
|
+
PayOrderStatus.EXECUTING_ORDER,
|
|
273
|
+
];
|
|
274
|
+
const shouldSubscribeOrderStatus = (status) => !!status && ORDER_STATUS_SUBSCRIPTION_STATUSES.includes(status);
|
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.5-beta.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "Lars <lars@coinvoyage.io>",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@stripe/crypto": "0.0.4",
|
|
61
61
|
"styled-components": "^5.3.11",
|
|
62
62
|
"uuid": "13.0.0",
|
|
63
|
-
"@coin-voyage/
|
|
64
|
-
"@coin-voyage/
|
|
63
|
+
"@coin-voyage/shared": "2.4.5-beta.0",
|
|
64
|
+
"@coin-voyage/crypto": "2.4.4"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/qrcode": "1.5.5",
|