@rozoai/intent-pay 0.1.48 → 0.1.50
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/build/assets/chains.d.ts +16 -1
- package/build/components/RozoPayModal/guards.d.ts +20 -0
- package/build/components/RozoPayModal/guards.test.d.ts +1 -0
- package/build/constants/rozoConfig.d.ts +4 -2
- package/build/hooks/usePaymentState.d.ts +7 -0
- package/build/lib/analytics/events.d.ts +2 -0
- package/build/package.json.js +1 -1
- package/build/payment/createPaymentPayload.d.ts +2 -9
- package/build/provider/StellarContextProvider.d.ts +8 -0
- package/build/src/components/Pages/PayWithToken/index.js +223 -177
- package/build/src/components/Pages/PayWithToken/index.js.map +1 -1
- package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js +42 -4
- package/build/src/components/Pages/Solana/PayWithSolanaToken/index.js.map +1 -1
- package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js +87 -14
- package/build/src/components/Pages/Stellar/PayWithStellarToken/index.js.map +1 -1
- package/build/src/components/RozoPayModal/guards.js +33 -0
- package/build/src/components/RozoPayModal/guards.js.map +1 -0
- package/build/src/components/RozoPayModal/index.js +47 -21
- package/build/src/components/RozoPayModal/index.js.map +1 -1
- package/build/src/constants/rozoConfig.js +5 -5
- package/build/src/constants/rozoConfig.js.map +1 -1
- package/build/src/hooks/usePaymentState.js +48 -8
- package/build/src/hooks/usePaymentState.js.map +1 -1
- package/build/src/lib/analytics/events.js +1 -0
- package/build/src/lib/analytics/events.js.map +1 -1
- package/build/src/payment/createPaymentPayload.js +6 -2
- package/build/src/payment/createPaymentPayload.js.map +1 -1
- package/build/src/payment/paymentEffects.js +4 -3
- package/build/src/payment/paymentEffects.js.map +1 -1
- package/build/src/provider/AnalyticsProvider.js +5 -9
- package/build/src/provider/AnalyticsProvider.js.map +1 -1
- package/build/src/utils/paymentRequestScope.js +27 -1
- package/build/src/utils/paymentRequestScope.js.map +1 -1
- package/build/utils/paymentRequestScope.d.ts +8 -0
- package/package.json +2 -2
package/build/assets/chains.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arbitrum, base, bsc, celo, ethereum, hyperEVM, linea, mantle, optimism, polygon, rozoSolana, rozoStellar, solana, stellar, worldchain } from "@rozoai/intent-common";
|
|
1
2
|
export declare const UnknownChain: (props: any) => import("react").JSX.Element;
|
|
2
3
|
export declare const Ethereum: (props: any) => import("react").JSX.Element;
|
|
3
4
|
export declare const Polygon: (props: any) => import("react").JSX.Element;
|
|
@@ -9,5 +10,19 @@ export declare const Base: (props: any) => import("react").JSX.Element;
|
|
|
9
10
|
export declare const Worldchain: (props: any) => import("react").JSX.Element;
|
|
10
11
|
export declare const Stellar: (props: any) => import("react").JSX.Element;
|
|
11
12
|
export declare const chainToLogo: {
|
|
12
|
-
[
|
|
13
|
+
[arbitrum.chainId]: import("react").JSX.Element;
|
|
14
|
+
[base.chainId]: import("react").JSX.Element;
|
|
15
|
+
[bsc.chainId]: import("react").JSX.Element;
|
|
16
|
+
[celo.chainId]: import("react").JSX.Element;
|
|
17
|
+
[ethereum.chainId]: import("react").JSX.Element;
|
|
18
|
+
[linea.chainId]: import("react").JSX.Element;
|
|
19
|
+
[mantle.chainId]: import("react").JSX.Element;
|
|
20
|
+
[optimism.chainId]: import("react").JSX.Element;
|
|
21
|
+
[polygon.chainId]: import("react").JSX.Element;
|
|
22
|
+
[worldchain.chainId]: import("react").JSX.Element;
|
|
23
|
+
[hyperEVM.chainId]: import("react").JSX.Element;
|
|
24
|
+
[solana.chainId]: import("react").JSX.Element;
|
|
25
|
+
[rozoSolana.chainId]: import("react").JSX.Element;
|
|
26
|
+
[stellar.chainId]: import("react").JSX.Element;
|
|
27
|
+
[rozoStellar.chainId]: import("react").JSX.Element;
|
|
13
28
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type WalletPaymentState = "idle" | "waiting" | "processing";
|
|
2
|
+
type PaymentFsmState = "idle" | "preview" | "unhydrated" | "payment_unpaid" | "payment_started" | "payment_completed" | "payment_bounced" | "payout_completed" | "error";
|
|
3
|
+
export declare const WALLET_REQUEST_MESSAGE = "Wallet still needs your action. Reject or close it in your wallet first.";
|
|
4
|
+
export declare function isWalletWaiting(walletPaymentState: WalletPaymentState): walletPaymentState is "waiting";
|
|
5
|
+
export declare function isWalletProcessing(walletPaymentState: WalletPaymentState): walletPaymentState is "processing";
|
|
6
|
+
export declare function isCloseable({ route, walletPaymentState, paymentFsmState, isDepositAddressReady, enforceSupportedChains, isEthConnected, chainIsSupported, }: {
|
|
7
|
+
route: string;
|
|
8
|
+
walletPaymentState: WalletPaymentState;
|
|
9
|
+
paymentFsmState: PaymentFsmState;
|
|
10
|
+
isDepositAddressReady: boolean;
|
|
11
|
+
enforceSupportedChains: boolean | undefined;
|
|
12
|
+
isEthConnected: boolean;
|
|
13
|
+
chainIsSupported: boolean | null;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export declare function showsBackButton({ route, walletPaymentState, paymentFsmState, }: {
|
|
16
|
+
route: string;
|
|
17
|
+
walletPaymentState: WalletPaymentState;
|
|
18
|
+
paymentFsmState: PaymentFsmState;
|
|
19
|
+
}): boolean;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
export declare const ROZO_INVOICE_URL = "https://invoice.rozo.ai";
|
|
6
6
|
export declare const DEFAULT_ROZO_APP_ID = "rozoIntentPay";
|
|
7
7
|
export declare const DEFAULT_STELLAR_RPC_URL = "https://horizon.stellar.org";
|
|
8
|
-
export declare const POSTHOG_KEY = "
|
|
9
|
-
export declare const POSTHOG_HOST = "https://
|
|
8
|
+
export declare const POSTHOG_KEY = "phc_s5bLUzDdCvQ9cKqdBSNZhcj7N44JuwVvDZHnZGnYzbLV";
|
|
9
|
+
export declare const POSTHOG_HOST = "https://hog.rozo.ai";
|
|
10
|
+
export declare const POSTHOG_UI_HOST = "https://us.posthog.com";
|
|
11
|
+
export declare const POSTHOG_APP_NAME = "rozo-intent-sdk";
|
|
10
12
|
export declare const STELLAR_INSUFFICIENT_XLM_BASE = "Your Stellar account needs a little XLM to pay the network fee. Please add XLM and try again.";
|
|
11
13
|
export declare function getStellarInsufficientXlmMessage(spendable: number, baseFeeXlm: number): string;
|
|
@@ -30,6 +30,8 @@ export interface PaymentState {
|
|
|
30
30
|
paymentWaitingMessage: string | undefined;
|
|
31
31
|
depositAddressState: "idle" | "creating" | "ready";
|
|
32
32
|
setDepositAddressState: (state: "idle" | "creating" | "ready") => void;
|
|
33
|
+
walletPaymentState: "idle" | "waiting" | "processing";
|
|
34
|
+
setWalletPaymentState: (state: "idle" | "waiting" | "processing") => void;
|
|
33
35
|
externalPaymentOptions: ReturnType<typeof useExternalPaymentOptions>;
|
|
34
36
|
selectedWallet: WalletConfigProps | undefined;
|
|
35
37
|
selectedWalletDeepLink: string | undefined;
|
|
@@ -101,6 +103,11 @@ export interface PaymentState {
|
|
|
101
103
|
rozoPaymentId: string | undefined;
|
|
102
104
|
setSenderAddress: (address: string | undefined) => void;
|
|
103
105
|
senderAddress: string | undefined;
|
|
106
|
+
pendingPaymentAttemptId: string | undefined;
|
|
107
|
+
tryClaimPaymentAttempt: (orderId: string) => boolean;
|
|
108
|
+
replacePaymentAttempt: (orderId: string, nextOrderId: string) => boolean;
|
|
109
|
+
releasePaymentAttempt: (orderId: string) => void;
|
|
110
|
+
clearPaymentAttempt: () => void;
|
|
104
111
|
ethWalletAddress: string | undefined;
|
|
105
112
|
solanaPubKey: string | undefined;
|
|
106
113
|
stellarPubKey: string | undefined;
|
|
@@ -13,6 +13,7 @@ export declare const PAYMENT_EVENTS: {
|
|
|
13
13
|
readonly PAYMENT_QUOTE_RECEIVED: "payment_quote_received";
|
|
14
14
|
readonly PAYMENT_QUOTE_FAILED: "payment_quote_failed";
|
|
15
15
|
readonly PAYMENT_CONFIRMED: "payment_confirmed";
|
|
16
|
+
readonly PAYMENT_WALLET_CONFIRMATION_PENDING: "payment_wallet_confirmation_pending";
|
|
16
17
|
readonly PAYMENT_SUBMITTED: "payment_submitted";
|
|
17
18
|
readonly PAYMENT_COMPLETED: "payment_completed";
|
|
18
19
|
readonly PAYMENT_FAILED: "payment_failed";
|
|
@@ -36,6 +37,7 @@ export declare const ROZO_EVENTS: {
|
|
|
36
37
|
readonly PAYMENT_QUOTE_RECEIVED: "payment_quote_received";
|
|
37
38
|
readonly PAYMENT_QUOTE_FAILED: "payment_quote_failed";
|
|
38
39
|
readonly PAYMENT_CONFIRMED: "payment_confirmed";
|
|
40
|
+
readonly PAYMENT_WALLET_CONFIRMATION_PENDING: "payment_wallet_confirmation_pending";
|
|
39
41
|
readonly PAYMENT_SUBMITTED: "payment_submitted";
|
|
40
42
|
readonly PAYMENT_COMPLETED: "payment_completed";
|
|
41
43
|
readonly PAYMENT_FAILED: "payment_failed";
|
package/build/package.json.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiVersion, CreateNewPaymentParams, DepositAddressPaymentOptionMetadata, FeeResponseData, FeeType, RozoPayHydratedOrderWithOrg, RozoPayOrderWithOrg, Token, TokenSymbol, WalletPaymentOption } from "@rozoai/intent-common";
|
|
1
|
+
import { ApiVersion, CreateNewPaymentParams, DepositAddressPaymentOptionMetadata, FeeResponseData, FeeType, PaymentResponse, RozoPayHydratedOrderWithOrg, RozoPayOrderWithOrg, Token, TokenSymbol, WalletPaymentOption } from "@rozoai/intent-common";
|
|
2
2
|
import { PayParams } from "./paymentFsm";
|
|
3
3
|
type OrderLike = RozoPayHydratedOrderWithOrg | RozoPayOrderWithOrg;
|
|
4
4
|
export type CreatePaymentContext = {
|
|
@@ -86,14 +86,7 @@ export type WalletSourceQuoteOrder = {
|
|
|
86
86
|
paymentBreakdown?: FeeResponseData;
|
|
87
87
|
};
|
|
88
88
|
/** Preserve payment/checkout's authoritative source quote on SDK-owned state. */
|
|
89
|
-
export declare function withWalletSourceQuote<T extends RozoPayHydratedOrderWithOrg>(order: T, response:
|
|
90
|
-
source?: {
|
|
91
|
-
amount?: string;
|
|
92
|
-
chainId?: number | string;
|
|
93
|
-
tokenAddress?: string;
|
|
94
|
-
};
|
|
95
|
-
destination?: unknown;
|
|
96
|
-
}): T & WalletSourceQuoteOrder;
|
|
89
|
+
export declare function withWalletSourceQuote<T extends RozoPayHydratedOrderWithOrg>(order: T, response: PaymentResponse): T & WalletSourceQuoteOrder;
|
|
97
90
|
/** Amount authorized by payment/checkout, validated against selected source token. */
|
|
98
91
|
export declare function resolveWalletPaymentAmount(order: WalletSourceQuoteOrder, option: Pick<WalletPaymentOption, "required">): bigint;
|
|
99
92
|
export {};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ISupportedWallet, StellarWalletsKit } from "@creit.tech/stellar-wallets-kit";
|
|
2
2
|
import type { Horizon } from "@stellar/stellar-sdk";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
type StellarContextProvider = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
rpcUrl?: string;
|
|
7
|
+
kit?: StellarWalletsKit;
|
|
8
|
+
stellarWalletPersistence?: boolean;
|
|
9
|
+
log?: (msg: string) => void;
|
|
10
|
+
};
|
|
3
11
|
type StellarContextProviderValue = {
|
|
4
12
|
kit: StellarWalletsKit | undefined;
|
|
5
13
|
isExternalKit: boolean;
|
|
@@ -12,22 +12,23 @@ import { ROZO_EVENTS } from '../../../lib/analytics/events.js';
|
|
|
12
12
|
import { useAnalytics } from '../../../provider/AnalyticsProvider.js';
|
|
13
13
|
import { buildFeeQuoteParams, getCachedFee } from '../../../utils/feeCache.js';
|
|
14
14
|
import Button from '../../Common/Button/index.js';
|
|
15
|
-
import { PageContent, ModalContent, ModalH1, Link } from '../../Common/Modal/styles.js';
|
|
15
|
+
import { PageContent, ModalContent, ModalBody, ModalH1, Link } from '../../Common/Modal/styles.js';
|
|
16
16
|
import PaymentBreakdown from '../../Common/PaymentBreakdown/index.js';
|
|
17
17
|
import TokenLogoSpinner from '../../Spinners/TokenLogoSpinner/index.js';
|
|
18
18
|
|
|
19
19
|
var PayState;
|
|
20
20
|
(function (PayState) {
|
|
21
|
-
PayState["
|
|
21
|
+
PayState["WaitingForConfirmation"] = "Waiting for Confirmation";
|
|
22
22
|
PayState["PreparingTransaction"] = "Preparing Transaction";
|
|
23
23
|
PayState["RequestCancelled"] = "Payment Cancelled";
|
|
24
24
|
PayState["RequestSuccessful"] = "Payment Successful";
|
|
25
|
+
PayState["WaitingForWallet"] = "Wallet Confirmation Pending";
|
|
25
26
|
PayState["RequestFailed"] = "Payment Failed";
|
|
26
27
|
})(PayState || (PayState = {}));
|
|
27
28
|
const PayWithToken = () => {
|
|
28
29
|
const walletChainId = useChainId();
|
|
29
30
|
const { triggerResize, paymentState, setRoute, log } = usePayContext();
|
|
30
|
-
const { payWithToken, setSenderAddress, selectedTokenOption, walletPaymentOptions, } = paymentState;
|
|
31
|
+
const { payWithToken, setSenderAddress, selectedTokenOption, walletPaymentOptions, pendingPaymentAttemptId, tryClaimPaymentAttempt, releasePaymentAttempt, setWalletPaymentState, } = paymentState;
|
|
31
32
|
const { switchChainAsync } = useSwitchChain();
|
|
32
33
|
const { address } = useAccount();
|
|
33
34
|
const { store, setPaymentUnpaid, rozoPaymentId, order, paymentState: rozoPaymentState, } = useRozoPay();
|
|
@@ -54,8 +55,10 @@ const PayWithToken = () => {
|
|
|
54
55
|
// });
|
|
55
56
|
};
|
|
56
57
|
const switchChainErrorRef = useRef(null);
|
|
57
|
-
const
|
|
58
|
-
|
|
58
|
+
const transferInFlightRef = useRef(false);
|
|
59
|
+
const autoTransferOrderRef = useRef(null);
|
|
60
|
+
const trySwitchingChain = async (option) => {
|
|
61
|
+
if (walletChainId !== option.required.token.chainId) {
|
|
59
62
|
const resultChain = await (async () => {
|
|
60
63
|
try {
|
|
61
64
|
return await switchChainAsync({
|
|
@@ -81,202 +84,237 @@ const PayWithToken = () => {
|
|
|
81
84
|
return true;
|
|
82
85
|
};
|
|
83
86
|
const handleTransfer = useCallback(async (option) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// loaded by runSetPayIdEffects, so getFee below runs against the latest
|
|
87
|
-
// payment response (correct appId, amount, destination, etc.).
|
|
88
|
-
const currentState = store.getState();
|
|
89
|
-
const currentOrder = currentState.type !== "idle" ? currentState.order : undefined;
|
|
90
|
-
if (!currentOrder) {
|
|
91
|
-
throw new Error("Order not initialized");
|
|
92
|
-
}
|
|
93
|
-
capture(ROZO_EVENTS.PAYMENT_CONFIRMED, {
|
|
94
|
-
payment_id: rozoPaymentId ?? order?.externalId,
|
|
95
|
-
source_chain: option.required.token.chainId,
|
|
96
|
-
token_symbol: option.required.token.symbol,
|
|
97
|
-
amount: paymentState.payParams?.toUnits != null
|
|
98
|
-
? String(paymentState.payParams.toUnits)
|
|
99
|
-
: order?.destFinalCallTokenAmount?.usd != null
|
|
100
|
-
? String(order.destFinalCallTokenAmount.usd)
|
|
101
|
-
: undefined,
|
|
102
|
-
});
|
|
103
|
-
// Switch chain if necessary
|
|
104
|
-
setPayState(PayState.PreparingTransaction);
|
|
105
|
-
const switchChain = await trySwitchingChain(option);
|
|
106
|
-
if (!switchChain) {
|
|
107
|
-
console.error("Switching chain failed");
|
|
108
|
-
setFeeLoading(false);
|
|
109
|
-
setRoute(ROUTES.ERROR, {
|
|
110
|
-
error: switchChainErrorRef.current ?? "switch_chain_failed",
|
|
111
|
-
});
|
|
87
|
+
if (transferInFlightRef.current) {
|
|
88
|
+
log("[PayWithToken] transfer already in flight");
|
|
112
89
|
return;
|
|
113
90
|
}
|
|
91
|
+
transferInFlightRef.current = true;
|
|
92
|
+
let attemptId;
|
|
114
93
|
try {
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// If already unpaid, continue anyway
|
|
124
|
-
}
|
|
94
|
+
// Read the freshest order straight from the store instead of the React
|
|
95
|
+
// closure snapshot. For payId mode this is the getPayment-derived order
|
|
96
|
+
// loaded by runSetPayIdEffects, so getFee below runs against the latest
|
|
97
|
+
// payment response (correct appId, amount, destination, etc.).
|
|
98
|
+
const currentState = store.getState();
|
|
99
|
+
const currentOrder = currentState.type !== "idle" ? currentState.order : undefined;
|
|
100
|
+
if (!currentOrder) {
|
|
101
|
+
throw new Error("Order not initialized");
|
|
125
102
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const toUnits = destAmountAtomic && destToken
|
|
132
|
-
? formatUnits(BigInt(destAmountAtomic), destToken.decimals)
|
|
133
|
-
: option.required.usd.toString();
|
|
134
|
-
const feeParams = buildFeeQuoteParams({
|
|
135
|
-
order: currentOrder,
|
|
136
|
-
payParams: paymentState.payParams,
|
|
137
|
-
destChainId: destToken.chainId,
|
|
138
|
-
destTokenAddress: destToken.token,
|
|
139
|
-
destAddress: getCanonicalDestination(currentOrder).finalDestinationAddress ??
|
|
140
|
-
"",
|
|
141
|
-
sourceChainId: option.required.token.chainId,
|
|
142
|
-
sourceTokenAddress: option.required.token.token,
|
|
143
|
-
toUnits,
|
|
144
|
-
});
|
|
145
|
-
const feeData = await getCachedFee(feeParams, { signal: request.signal });
|
|
146
|
-
setFeeLoading(false);
|
|
147
|
-
if (request.signal.aborted) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (feeData.error) {
|
|
151
|
-
if (feeData.error.name === "AbortError") {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
capture(ROZO_EVENTS.PAYMENT_FAILED, {
|
|
155
|
-
payment_id: rozoPaymentId ?? order?.externalId,
|
|
156
|
-
error_message: feeData.error.message,
|
|
103
|
+
attemptId = currentOrder.externalId ?? String(currentOrder.id);
|
|
104
|
+
if (!tryClaimPaymentAttempt(attemptId)) {
|
|
105
|
+
log("[PayWithToken] wallet request already pending");
|
|
106
|
+
capture(ROZO_EVENTS.PAYMENT_WALLET_CONFIRMATION_PENDING, {
|
|
107
|
+
payment_id: currentOrder.externalId,
|
|
157
108
|
source_chain: option.required.token.chainId,
|
|
158
|
-
|
|
159
|
-
dest_chain: destToken.chainId,
|
|
160
|
-
dest_token: destToken.symbol,
|
|
109
|
+
token_symbol: option.required.token.symbol,
|
|
161
110
|
});
|
|
162
|
-
|
|
163
|
-
setRoute(ROUTES.ERROR, { error: feeData.error.message });
|
|
111
|
+
setPayState(PayState.WaitingForWallet);
|
|
164
112
|
return;
|
|
165
113
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
?
|
|
174
|
-
:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (result.success) {
|
|
185
|
-
capture(ROZO_EVENTS.PAYMENT_SUBMITTED, {
|
|
186
|
-
payment_id: rozoPaymentId ?? order?.externalId,
|
|
187
|
-
tx_hash: result.txHash,
|
|
188
|
-
source_chain: option.required.token.chainId,
|
|
189
|
-
token_symbol: option.required.token.symbol,
|
|
114
|
+
capture(ROZO_EVENTS.PAYMENT_CONFIRMED, {
|
|
115
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
116
|
+
source_chain: option.required.token.chainId,
|
|
117
|
+
token_symbol: option.required.token.symbol,
|
|
118
|
+
amount: paymentState.payParams?.toUnits != null
|
|
119
|
+
? String(paymentState.payParams.toUnits)
|
|
120
|
+
: order?.destFinalCallTokenAmount?.usd != null
|
|
121
|
+
? String(order.destFinalCallTokenAmount.usd)
|
|
122
|
+
: undefined,
|
|
123
|
+
});
|
|
124
|
+
// Switch chain if necessary
|
|
125
|
+
setPayState(PayState.PreparingTransaction);
|
|
126
|
+
const switchChain = await trySwitchingChain(option);
|
|
127
|
+
if (!switchChain) {
|
|
128
|
+
console.error("Switching chain failed");
|
|
129
|
+
setFeeLoading(false);
|
|
130
|
+
setRoute(ROUTES.ERROR, {
|
|
131
|
+
error: switchChainErrorRef.current ?? "switch_chain_failed",
|
|
190
132
|
});
|
|
191
|
-
|
|
192
|
-
sessionStorage.setItem(`rozo_submitted_at:${rozoPaymentId ?? order?.externalId}`, String(Date.now()));
|
|
193
|
-
}
|
|
194
|
-
catch { }
|
|
195
|
-
setSenderAddress(address);
|
|
196
|
-
setPayState(PayState.RequestSuccessful);
|
|
197
|
-
setTimeout(() => {
|
|
198
|
-
setRoute(ROUTES.CONFIRMATION, { event: "wait-pay-with-token" });
|
|
199
|
-
}, 200);
|
|
200
|
-
// Refresh wallet payment options after 1 second to prevent stale balances
|
|
201
|
-
setTimeout(() => {
|
|
202
|
-
walletPaymentOptions.refreshOptions();
|
|
203
|
-
}, 2000);
|
|
133
|
+
return;
|
|
204
134
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
135
|
+
try {
|
|
136
|
+
const currentRozoPaymentId = rozoPaymentId ?? currentOrder.externalId ?? undefined;
|
|
137
|
+
// Only set unpaid if state is payment_started (for retry scenarios and cross-chain switches)
|
|
138
|
+
if (currentRozoPaymentId && rozoPaymentState === "payment_started") {
|
|
139
|
+
try {
|
|
140
|
+
await setPaymentUnpaid(currentRozoPaymentId);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.error("Failed to set payment unpaid:", e);
|
|
144
|
+
// If already unpaid, continue anyway
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// @NOTE: Fee calculation
|
|
148
|
+
const request = beginRequestScope(PAYMENT_REQUEST_SCOPE);
|
|
149
|
+
setFeeLoading(true);
|
|
150
|
+
const destToken = currentOrder.destFinalCallTokenAmount?.token;
|
|
151
|
+
const destAmountAtomic = currentOrder.destFinalCallTokenAmount?.amount;
|
|
152
|
+
const toUnits = destAmountAtomic && destToken
|
|
153
|
+
? formatUnits(BigInt(destAmountAtomic), destToken.decimals)
|
|
154
|
+
: option.required.usd.toString();
|
|
155
|
+
const feeParams = buildFeeQuoteParams({
|
|
156
|
+
order: currentOrder,
|
|
157
|
+
payParams: paymentState.payParams,
|
|
158
|
+
destChainId: destToken.chainId,
|
|
159
|
+
destTokenAddress: destToken.token,
|
|
160
|
+
destAddress: getCanonicalDestination(currentOrder).finalDestinationAddress ??
|
|
161
|
+
"",
|
|
162
|
+
sourceChainId: option.required.token.chainId,
|
|
163
|
+
sourceTokenAddress: option.required.token.token,
|
|
164
|
+
toUnits,
|
|
210
165
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
166
|
+
const feeData = await getCachedFee(feeParams, { signal: request.signal });
|
|
167
|
+
setFeeLoading(false);
|
|
168
|
+
if (request.signal.aborted) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (feeData.error) {
|
|
172
|
+
if (feeData.error.name === "AbortError") {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
capture(ROZO_EVENTS.PAYMENT_FAILED, {
|
|
176
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
177
|
+
error_message: feeData.error.message,
|
|
178
|
+
source_chain: option.required.token.chainId,
|
|
179
|
+
source_token: option.required.token.symbol,
|
|
180
|
+
dest_chain: destToken.chainId,
|
|
181
|
+
dest_token: destToken.symbol,
|
|
182
|
+
});
|
|
183
|
+
console.error("Fee calculation failed", feeData.error);
|
|
184
|
+
setRoute(ROUTES.ERROR, { error: feeData.error.message });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
setFeeData(feeData.data);
|
|
188
|
+
setPayState(PayState.WaitingForConfirmation);
|
|
189
|
+
const result = await payWithToken({
|
|
190
|
+
...option,
|
|
191
|
+
fees: {
|
|
192
|
+
...option.fees,
|
|
193
|
+
usd: feeData.data?.source.fee != null
|
|
194
|
+
? Number(feeData.data.source.fee)
|
|
195
|
+
: option.fees.usd,
|
|
196
|
+
},
|
|
197
|
+
}, store);
|
|
198
|
+
const completedState = store.getState();
|
|
199
|
+
const canonicalBreakdown = completedState.type !== "idle"
|
|
200
|
+
? completedState.order.paymentBreakdown
|
|
201
|
+
: undefined;
|
|
202
|
+
if (canonicalBreakdown)
|
|
203
|
+
setFeeData(canonicalBreakdown);
|
|
204
|
+
setTxURL(getChainExplorerTxUrl(option.required.token.chainId, result.txHash));
|
|
205
|
+
if (result.success) {
|
|
206
|
+
capture(ROZO_EVENTS.PAYMENT_SUBMITTED, {
|
|
207
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
208
|
+
tx_hash: result.txHash,
|
|
209
|
+
source_chain: option.required.token.chainId,
|
|
210
|
+
token_symbol: option.required.token.symbol,
|
|
211
|
+
});
|
|
224
212
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
213
|
+
sessionStorage.setItem(`rozo_submitted_at:${rozoPaymentId ?? order?.externalId}`, String(Date.now()));
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
216
|
+
setSenderAddress(address);
|
|
217
|
+
setPayState(PayState.RequestSuccessful);
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
setRoute(ROUTES.CONFIRMATION, { event: "wait-pay-with-token" });
|
|
220
|
+
}, 200);
|
|
221
|
+
// Refresh wallet payment options after 1 second to prevent stale balances
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
walletPaymentOptions.refreshOptions();
|
|
224
|
+
}, 2000);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
capture(ROZO_EVENTS.PAYMENT_FAILED, {
|
|
228
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
229
|
+
error_message: "payment_unsuccessful",
|
|
230
|
+
source_chain: option.required.token.chainId,
|
|
231
|
+
});
|
|
232
|
+
setPayState(PayState.RequestFailed);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
// Abort = user navigated away (Back / reset). Not a payment failure.
|
|
237
|
+
if (isAbortError(e))
|
|
238
|
+
return;
|
|
239
|
+
if (e?.name === "ConnectorChainMismatchError") {
|
|
240
|
+
// Workaround for Rainbow wallet bug -- user is able to switch chain without
|
|
241
|
+
// the wallet updating the chain ID for wagmi.
|
|
242
|
+
log("Chain mismatch detected, attempting to switch and retry");
|
|
243
|
+
const switchSuccessful = await trySwitchingChain(option);
|
|
244
|
+
if (switchSuccessful) {
|
|
245
|
+
try {
|
|
246
|
+
const retryResult = await payWithToken(option, store);
|
|
247
|
+
setTxURL(getChainExplorerTxUrl(option.required.token.chainId, retryResult.txHash));
|
|
248
|
+
if (retryResult.success) {
|
|
249
|
+
capture(ROZO_EVENTS.PAYMENT_SUBMITTED, {
|
|
250
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
251
|
+
tx_hash: retryResult.txHash,
|
|
252
|
+
source_chain: option.required.token.chainId,
|
|
253
|
+
token_symbol: option.required.token.symbol,
|
|
254
|
+
});
|
|
255
|
+
try {
|
|
256
|
+
sessionStorage.setItem(`rozo_submitted_at:${rozoPaymentId ?? order?.externalId}`, String(Date.now()));
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
setSenderAddress(address);
|
|
260
|
+
setPayState(PayState.RequestSuccessful);
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
setRoute(ROUTES.CONFIRMATION, {
|
|
263
|
+
event: "wait-pay-with-token",
|
|
264
|
+
});
|
|
265
|
+
}, 200);
|
|
266
|
+
// Refresh wallet payment options after 1 second to prevent stale balances
|
|
267
|
+
setTimeout(() => {
|
|
268
|
+
walletPaymentOptions.refreshOptions();
|
|
269
|
+
}, 2000);
|
|
236
270
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
event: "wait-pay-with-token",
|
|
271
|
+
else {
|
|
272
|
+
capture(ROZO_EVENTS.PAYMENT_FAILED, {
|
|
273
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
274
|
+
error_message: "payment_unsuccessful_after_chain_switch",
|
|
275
|
+
source_chain: option.required.token.chainId,
|
|
243
276
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
walletPaymentOptions.refreshOptions();
|
|
248
|
-
}, 2000);
|
|
277
|
+
setPayState(PayState.RequestFailed);
|
|
278
|
+
}
|
|
279
|
+
return; // Payment handled after switching chain
|
|
249
280
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
error_message: "payment_unsuccessful_after_chain_switch",
|
|
254
|
-
source_chain: option.required.token.chainId,
|
|
255
|
-
});
|
|
256
|
-
setPayState(PayState.RequestFailed);
|
|
281
|
+
catch (retryError) {
|
|
282
|
+
console.error("Failed to pay with token after switching chain", retryError);
|
|
283
|
+
throw retryError;
|
|
257
284
|
}
|
|
258
|
-
return; // Payment handled after switching chain
|
|
259
|
-
}
|
|
260
|
-
catch (retryError) {
|
|
261
|
-
console.error("Failed to pay with token after switching chain", retryError);
|
|
262
|
-
throw retryError;
|
|
263
285
|
}
|
|
264
286
|
}
|
|
287
|
+
capture(ROZO_EVENTS.PAYMENT_FAILED, {
|
|
288
|
+
payment_id: rozoPaymentId ?? order?.externalId,
|
|
289
|
+
error_message: e?.message ?? "unknown_error",
|
|
290
|
+
source_chain: option.required.token.chainId,
|
|
291
|
+
});
|
|
292
|
+
setPayState(PayState.RequestCancelled);
|
|
293
|
+
console.error("Failed to pay with token", e);
|
|
265
294
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
setPayState(PayState.RequestCancelled);
|
|
272
|
-
console.error("Failed to pay with token", e);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
if (attemptId)
|
|
298
|
+
releasePaymentAttempt(attemptId);
|
|
299
|
+
transferInFlightRef.current = false;
|
|
273
300
|
}
|
|
274
301
|
},
|
|
275
302
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
276
303
|
[walletPaymentOptions, rozoPaymentId, order, rozoPaymentState]);
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (!pendingPaymentAttemptId && payState === PayState.WaitingForWallet) {
|
|
306
|
+
setPayState(PayState.RequestCancelled);
|
|
307
|
+
}
|
|
308
|
+
}, [pendingPaymentAttemptId, payState]);
|
|
277
309
|
useEffect(() => {
|
|
278
310
|
if (!selectedTokenOption)
|
|
279
311
|
return;
|
|
312
|
+
const currentState = store.getState();
|
|
313
|
+
const currentOrder = currentState.type !== "idle" ? currentState.order : undefined;
|
|
314
|
+
const orderKey = currentOrder?.externalId ?? String(currentOrder?.id ?? "");
|
|
315
|
+
if (!orderKey || autoTransferOrderRef.current === orderKey)
|
|
316
|
+
return;
|
|
317
|
+
autoTransferOrderRef.current = orderKey;
|
|
280
318
|
const transferTimeout = setTimeout(() => {
|
|
281
319
|
handleTransfer(selectedTokenOption);
|
|
282
320
|
}, 100);
|
|
@@ -287,12 +325,20 @@ const PayWithToken = () => {
|
|
|
287
325
|
}, [selectedTokenOption]);
|
|
288
326
|
useEffect(() => {
|
|
289
327
|
triggerResize();
|
|
328
|
+
setWalletPaymentState(payState === PayState.WaitingForConfirmation ||
|
|
329
|
+
payState === PayState.WaitingForWallet
|
|
330
|
+
? "waiting"
|
|
331
|
+
: "idle");
|
|
332
|
+
return () => setWalletPaymentState("idle");
|
|
290
333
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
291
334
|
}, [payState]);
|
|
292
335
|
if (selectedTokenOption == null) {
|
|
293
336
|
return jsx(PageContent, {});
|
|
294
337
|
}
|
|
295
|
-
|
|
338
|
+
if (payState === PayState.WaitingForWallet) {
|
|
339
|
+
return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: selectedTokenOption.required.token }), jsx(ModalContent, { style: { paddingBottom: 0 }, "$preserveDisplay": true, children: jsx(ModalBody, { children: "Wallet confirmation pending" }) })] }));
|
|
340
|
+
}
|
|
341
|
+
return (jsxs(PageContent, { children: [jsx(TokenLogoSpinner, { token: selectedTokenOption.required.token }), jsxs(ModalContent, { style: { paddingBottom: 0 }, "$preserveDisplay": true, children: [txURL ? (jsx(ModalH1, { children: jsx(Link, { href: txURL, target: "_blank", rel: "noopener noreferrer", children: payState }) })) : (jsx(ModalH1, { children: payState })), (payState !== PayState.RequestCancelled || (feeData && !feeLoading)) && (jsx(PaymentBreakdown, { paymentOption: {
|
|
296
342
|
...selectedTokenOption,
|
|
297
343
|
fees: {
|
|
298
344
|
...selectedTokenOption.fees,
|
|
@@ -300,7 +346,7 @@ const PayWithToken = () => {
|
|
|
300
346
|
? Number(feeData.source.fee)
|
|
301
347
|
: selectedTokenOption.fees.usd,
|
|
302
348
|
},
|
|
303
|
-
}, feeData: feeData, feeLoading: feeLoading }), payState === PayState.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" })), payState === PayState.RequestFailed && (jsx(Button, { onClick: handleContactClick, children: "Contact Support" }))] })] }));
|
|
349
|
+
}, feeData: feeData, feeLoading: feeLoading })), payState === PayState.RequestCancelled && (jsx(Button, { onClick: () => handleTransfer(selectedTokenOption), children: "Retry Payment" })), payState === PayState.RequestFailed && (jsx(Button, { onClick: handleContactClick, children: "Contact Support" }))] })] }));
|
|
304
350
|
};
|
|
305
351
|
|
|
306
352
|
export { PayWithToken as default };
|