Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
},
"dependencies": {
"@creit.tech/stellar-wallets-kit": "^1.9.5",
"@rozoai/intent-common": "0.1.26-beta.1",
"@rozoai/intent-pay": "0.1.38-beta.6",
"@rozoai/intent-common": "0.1.26-beta.5",
"@rozoai/intent-pay": "0.1.38-beta.16",
"@stellar/stellar-sdk": "^14.6.1",
"@tanstack/react-query": "^5.0.0",
"@web3icons/react": "^4.1.19",
Expand Down
2 changes: 1 addition & 1 deletion packages/connectkit/bundle-analysis.html

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions packages/connectkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rozoai/intent-pay",
"version": "0.1.38-beta.6",
"version": "0.1.38-beta.16",
"private": false,
"description": "Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.",
"keywords": [
Expand Down Expand Up @@ -40,7 +40,7 @@
"lint": "oxlint src/",
"format": "oxfmt src/",
"format:check": "oxfmt --check src/",
"test": "tsx node_modules/tape/bin/tape test/**/*.test.ts",
"test": "vitest run",
"release": "bumpp",
"publish:beta": "node scripts/release.js beta",
"publish:latest": "node scripts/release.js latest"
Expand All @@ -49,7 +49,7 @@
"@reown/appkit": "^1.7.0",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-typescript": "^12.1.2",
"@rozoai/intent-common": "0.1.26-beta.1",
"@rozoai/intent-common": "0.1.26-beta.5",
"@solana/spl-memo": "^0.2.5",
"@solana/spl-token": "^0.4.14",
"@solana/wallet-adapter-base": "^0.9.27",
Expand All @@ -58,6 +58,7 @@
"@trpc/client": "^11.0.0-next-beta.318",
"@trpc/server": "^11.0.0-next-beta.318",
"@walletconnect/sign-client": "^2.22.3",
"@walletconnect/universal-provider": "^2.22.3",
"@worldcoin/minikit-js": "^1.9.7",
"bs58": "^6.0.0",
"buffer": "^6.0.3",
Expand All @@ -80,12 +81,10 @@
"@walletconnect/types": "^2.22.3",
"oxfmt": "latest",
"oxlint": "latest",
"rollup": "^3.29.5",
"rollup": "^4.24.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-visualizer": "^6.0.5",
"tape": "^5.10.2",
"ts-node": "^10.9.2",
"tsx": "^4.23.1"
"vitest": "^3.2.4"
},
"peerDependencies": {
"@creit.tech/stellar-wallets-kit": "^1.9.5",
Expand Down
1 change: 1 addition & 0 deletions packages/connectkit/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default [
"@trpc/client",
"@wagmi/connectors",
"@walletconnect/sign-client",
"@walletconnect/universal-provider",
"@worldcoin/minikit-js",
"bs58",
"buffer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ const TitleAmount = styled(motion.h1)<{
margin-bottom: 24px;
padding: 0;
line-height: 1.15;
font-size: clamp(28px, 6vw, 48px);
font-size: clamp(28px, 12vw, 64px);
font-weight: var(--ck-modal-h1-font-weight, 600);
text-wrap: balance;
overflow: hidden;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ROUTES } from "../../../../constants/routes";
import { usePayContext } from "../../../../hooks/usePayContext";

Expand Down Expand Up @@ -65,6 +65,15 @@ const PayWithSolanaToken: React.FC = () => {
hydrateOrder,
} = useRozoPay();
const handleContactClick = useContactSupport();
// Dedupes the payId fetch+checkout across overlapping handleTransfer calls
// (e.g. the auto-transfer effect firing twice in quick succession). Claimed
// synchronously — before any await — so a second call always sees and
// awaits the first call's in-flight promise instead of racing a second
// checkout POST. See PayWithStellarToken for the same fix + rationale.
const checkoutInFlightRef = useRef<Promise<{
hydratedOrder: RozoPayHydratedOrderWithOrg;
paymentId: string | undefined;
}> | null>(null);

const { capture } = useAnalytics();
const [payState, setPayStateInner] = useState<PayState>(
Expand Down Expand Up @@ -202,36 +211,55 @@ const PayWithSolanaToken: React.FC = () => {
setFeeData(feeData.data);

if (isPayIdMode) {
// payId mode: checkout (refresh) the payment with the selected source token
const paymentRes = await getPayment(existingPayId!);
if (!paymentRes?.data) {
throw new Error("Failed to fetch payment");
}
// payId mode: checkout (refresh) the payment with the selected
// source token. Claim checkoutInFlightRef synchronously (no await
// before this point in the branch) so a second overlapping
// handleTransfer call — e.g. the auto-transfer effect firing twice
// in quick succession — always sees and awaits this same promise
// instead of racing past a not-yet-set guard and firing a second
// checkout POST.
if (!checkoutInFlightRef.current) {
checkoutInFlightRef.current = (async () => {
const paymentRes = await getPayment(existingPayId!);
if (!paymentRes?.data) {
throw new Error("Failed to fetch payment");
}

let sourceChainId = Number(option.required.token.chainId);
let sourceChainId = Number(option.required.token.chainId);

if (sourceChainId === solana.chainId) {
sourceChainId = rozoSolana.chainId;
}
if (sourceChainId === solana.chainId) {
sourceChainId = rozoSolana.chainId;
}

const checkoutRes = await checkoutPayment(
existingPayId!,
buildCheckoutPayload(paymentRes.data, {
chainId: option.required.token.chainId,
tokenSymbol: option.required.token.symbol,
tokenAddress: option.required.token.token,
amount: String(option.required.usd),
}),
);
if (!checkoutRes?.data) {
throw new Error("Failed to checkout payment");
const checkoutRes = await checkoutPayment(
existingPayId!,
buildCheckoutPayload(paymentRes.data, {
chainId: option.required.token.chainId,
tokenSymbol: option.required.token.symbol,
tokenAddress: option.required.token.token,
amount: String(option.required.usd),
}),
);
if (!checkoutRes?.data) {
throw new Error("Failed to checkout payment");
}

return {
paymentId: checkoutRes.data.id,
hydratedOrder: formatPaymentResponseToHydratedOrder(
checkoutRes.data,
),
};
})();
} else {
log?.(
"[PayWithSolanaToken] isPayIdMode checkout already in flight, awaiting shared result",
);
}
paymentId = checkoutRes.data.id;

const formattedOrder = formatPaymentResponseToHydratedOrder(
checkoutRes.data,
);
hydratedOrder = formattedOrder;
const checkoutResult = await checkoutInFlightRef.current;
paymentId = checkoutResult.paymentId;
hydratedOrder = checkoutResult.hydratedOrder;
} else if (
(state === "payment_unpaid" || state === "payment_started") &&
!needRozoPayment
Expand Down Expand Up @@ -430,6 +458,12 @@ const PayWithSolanaToken: React.FC = () => {
}
} catch (error) {
console.error("Failed to pay with solana token", error);

// Clear the in-flight guard so a Retry Payment click (a genuine new
// attempt) can re-checkout instead of forever awaiting this failed
// attempt's rejected promise.
checkoutInFlightRef.current = null;

// Use `resolvedPaymentId` (the ID from this attempt) rather than
// the stale `rozoPaymentId` from the React state closure.
const paymentIdToReset = resolvedPaymentId ?? rozoPaymentId;
Expand Down
Loading
Loading