coinley-test
Version:
Beautiful blockchain payment gateway SDK for seamless crypto payments - React & vanilla js
1,043 lines • 162 kB
JavaScript
import React, { useState, useRef, useEffect, createContext, forwardRef, useContext, useImperativeHandle } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ArrowLeft, X, Sparkles, Wallet, Zap, QrCode, Loader2, ChevronDown, CheckCircle2, Copy, ExternalLink, AlertCircle } from "lucide-react";
import { createCoinleyWalletConfig, CoinleyWalletProvider, WalletModal, useWallet, useWalletConnect, useWalletDetection, useWalletTransaction, useWalletModal } from "@coinley/wallet-connect-core";
import axios from "axios";
import ReactDOM from "react-dom";
var jsxRuntime = { exports: {} };
var reactJsxRuntime_production_min = {};
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var f = React, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m$1 = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
function q(c, a, g) {
var b, d = {}, e = null, h = null;
void 0 !== g && (e = "" + g);
void 0 !== a.key && (e = "" + a.key);
void 0 !== a.ref && (h = a.ref);
for (b in a) m$1.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
}
reactJsxRuntime_production_min.Fragment = l;
reactJsxRuntime_production_min.jsx = q;
reactJsxRuntime_production_min.jsxs = q;
{
jsxRuntime.exports = reactJsxRuntime_production_min;
}
var jsxRuntimeExports = jsxRuntime.exports;
class PaymentAPI {
constructor(baseURL, apiKey, apiSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.api = axios.create({
baseURL: baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL,
timeout: 3e4,
headers: {
"Content-Type": "application/json"
}
});
this.api.interceptors.request.use(
(config) => {
var _a;
config.headers["X-API-Key"] = this.apiKey;
config.headers["X-API-Secret"] = this.apiSecret;
const token = this.generateMerchantToken();
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
console.log("API Request:", {
method: (_a = config.method) == null ? void 0 : _a.toUpperCase(),
url: config.url,
data: config.data
});
return config;
},
(error) => {
console.error("Request interceptor error:", error);
return Promise.reject(error);
}
);
this.api.interceptors.response.use(
(response) => {
console.log("API Response:", {
status: response.status,
url: response.config.url,
data: response.data
});
return response;
},
(error) => {
var _a, _b, _c, _d, _e;
console.error("API Error:", {
status: (_a = error.response) == null ? void 0 : _a.status,
data: (_b = error.response) == null ? void 0 : _b.data,
message: error.message
});
if (((_c = error.response) == null ? void 0 : _c.status) === 401) {
throw new Error("Authentication failed. Please check your API credentials.");
} else if (((_d = error.response) == null ? void 0 : _d.status) === 404) {
throw new Error("API endpoint not found. Please check your API URL.");
} else if (((_e = error.response) == null ? void 0 : _e.status) >= 500) {
throw new Error("Server error. Please try again later.");
}
throw error;
}
);
}
generateMerchantToken() {
try {
const credentials = `${this.apiKey}:${this.apiSecret}`;
return btoa(credentials);
} catch (error) {
console.error("Failed to generate token:", error);
return null;
}
}
async createPayment(params) {
var _a, _b;
try {
const response = await this.api.post("/api/payments/create", params);
return response.data;
} catch (error) {
if ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) {
throw new Error(error.response.data.error);
}
throw new Error(error.message || "Failed to create payment");
}
}
async getPayment(paymentId) {
var _a, _b;
try {
const response = await this.api.get(`/api/payments/${paymentId}`);
return response.data;
} catch (error) {
if ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) {
throw new Error(error.response.data.error);
}
throw new Error(error.message || "Failed to get payment details");
}
}
async getNetworks() {
try {
const response = await this.api.get("/api/networks");
return response.data;
} catch (error) {
console.error("Get networks failed:", error);
return {
networks: [
{
id: "ethereum",
name: "Ethereum",
shortName: "ethereum",
chainId: "1",
type: "ethereum",
explorerUrl: "https://etherscan.io",
isTestnet: false
},
{
id: "bsc",
name: "Binance Smart Chain",
shortName: "bsc",
chainId: "56",
type: "bsc",
explorerUrl: "https://bscscan.com",
isTestnet: false
},
{
id: "polygon",
name: "Polygon",
shortName: "polygon",
chainId: "137",
type: "ethereum",
explorerUrl: "https://polygonscan.com",
isTestnet: false
}
]
};
}
}
async getStablecoins() {
try {
const response = await this.api.get("/api/networks/stablecoins");
return response.data;
} catch (error) {
console.error("Get stablecoins failed:", error);
return {
stablecoins: [
{
id: "usdt-eth",
name: "Tether USD",
symbol: "USDT",
contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
decimals: 6,
isStablecoin: true,
networkId: "ethereum",
Network: {
id: "ethereum",
name: "Ethereum",
shortName: "ethereum",
type: "ethereum"
}
},
{
id: "usdc-eth",
name: "USD Coin",
symbol: "USDC",
contractAddress: "0xA0b86a33E6441d81d0B93bF9EE0f74ca32F7e6f6",
decimals: 6,
isStablecoin: true,
networkId: "ethereum",
Network: {
id: "ethereum",
name: "Ethereum",
shortName: "ethereum",
type: "ethereum"
}
}
]
};
}
}
async verifyQRPayment(paymentId) {
var _a, _b;
try {
const response = await this.api.post("/api/payments/verify-qr", {
paymentId
});
return response.data;
} catch (error) {
if ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) {
throw new Error(error.response.data.error);
}
throw new Error(error.message || "Failed to verify payment");
}
}
async processPayment(paymentId, transactionHash, network, senderAddress) {
var _a, _b;
try {
const response = await this.api.post("/api/payments/process", {
paymentId,
transactionHash,
network,
senderAddress
});
return response.data;
} catch (error) {
if ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) {
throw new Error(error.response.data.error);
}
throw new Error(error.message || "Failed to process payment");
}
}
async healthCheck() {
try {
const response = await this.api.get("/api/health");
return response.status === 200;
} catch (error) {
console.error("Health check failed:", error);
return false;
}
}
}
const WalletIntegration = ({
selectedNetwork,
selectedToken,
paymentData,
config,
onTransactionSent,
onError,
isConnecting,
setIsConnecting
}) => {
const { isConnected, address, disconnect } = useWallet();
const { connectWallet, isPending } = useWalletConnect();
const { detectedWallets, hasWallets, walletCount } = useWalletDetection();
const { sendTransaction } = useWalletTransaction();
const { openModal } = useWalletModal();
detectedWallets.filter(
(wallet) => wallet.name.toLowerCase().includes("metamask") || wallet.provider === "injected" || wallet.name.toLowerCase().includes("coinbase") || wallet.name.toLowerCase().includes("wallet connect")
);
const handleConnectSpecificWallet = async (walletType) => {
try {
setIsConnecting(true);
if (isConnected) {
await disconnect();
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
if (walletType === "metamask" && window.ethereum && window.ethereum.isMetaMask) {
try {
await window.ethereum.request({ method: "eth_requestAccounts" });
console.log("✅ Connected directly to MetaMask");
} catch (error) {
console.error("MetaMask connection failed:", error);
throw error;
}
} else {
await connectWallet(walletType);
}
} catch (error) {
console.error("Wallet connection failed:", error);
onError(error.message || "Failed to connect wallet");
} finally {
setIsConnecting(false);
}
};
const handleOpenModal = () => {
try {
console.log("Attempting to open wallet modal...");
openModal();
} catch (error) {
console.error("Failed to open modal:", error);
handleConnectSpecificWallet("injected");
}
};
const handleSendPayment = async () => {
var _a;
if (!isConnected || !paymentData || !selectedToken) {
onError("Wallet not connected or payment data missing");
return;
}
try {
setIsConnecting(true);
console.log("🔍 Transaction Debug:");
console.log("Connected wallet address:", address);
console.log("Payment data:", paymentData);
console.log("Selected token:", selectedToken);
console.log("Selected network:", selectedNetwork);
let recipientAddress = (_a = paymentData.metadata) == null ? void 0 : _a.recipientWallet;
console.log("Merchant recipient address:", recipientAddress);
if (!recipientAddress || typeof recipientAddress !== "string") {
throw new Error("Merchant wallet address not found in payment data");
}
if (!recipientAddress.match(/^0x[a-fA-F0-9]{40}$/)) {
throw new Error(`Invalid merchant address format: ${recipientAddress}`);
}
let txHash;
if (selectedToken.contractAddress) {
console.log("🔍 ERC-20 Transaction Details:");
console.log("Token contract:", selectedToken.contractAddress);
console.log("Token decimals:", selectedToken.decimals);
console.log("Payment amount:", paymentData.totalAmount);
const decimals = selectedToken.decimals || 6;
const amount = Math.floor(paymentData.totalAmount * Math.pow(10, decimals));
console.log("Calculated amount (with decimals):", amount);
const methodId = "0xa9059cbb";
const addressWithoutPrefix = recipientAddress.slice(2);
const recipientPadded = addressWithoutPrefix.toLowerCase().padStart(64, "0");
const amountPadded = amount.toString(16).padStart(64, "0");
const transferData = `${methodId}${recipientPadded}${amountPadded}`;
console.log("🔍 Transaction Data:");
console.log("Method ID:", methodId);
console.log("Recipient (padded):", recipientPadded);
console.log("Amount (padded):", amountPadded);
console.log("Full data:", transferData);
const txParams = {
to: selectedToken.contractAddress,
data: transferData,
value: "0x0"
};
console.log("📤 Sending ERC-20 transaction:", txParams);
txHash = await sendTransaction(txParams);
} else {
const value = Math.floor(paymentData.totalAmount * Math.pow(10, 18));
const valueHex = `0x${value.toString(16)}`;
const txParams = {
to: recipientAddress,
value: valueHex
};
console.log("📤 Sending native transaction:", txParams);
txHash = await sendTransaction(txParams);
}
if (txHash) {
console.log("✅ Transaction sent successfully:", txHash);
onTransactionSent(txHash);
}
} catch (error) {
console.error("❌ Transaction failed:", error);
onError(error.message || "Transaction failed");
} finally {
setIsConnecting(false);
}
};
const handleDisconnect = async () => {
try {
await disconnect();
} catch (error) {
console.error("Disconnect error:", error);
}
};
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center space-y-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Wallet, { className: "w-8 h-8 text-[#7042D2]" }) }),
!isConnected ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold", children: "Connect Your Wallet" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-600 dark:text-gray-400 text-sm", children: "Choose your preferred wallet to complete the payment" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(
"button",
{
onClick: () => handleConnectSpecificWallet("metamask"),
disabled: isPending || isConnecting,
className: "w-full p-3 border border-gray-300 rounded-lg hover:border-[#7042D2] flex items-center space-x-3 transition-colors disabled:opacity-50",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl", children: "🦊" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left flex-1", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: "MetaMask" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500", children: "Connect with MetaMask" })
] })
]
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs(
"button",
{
onClick: handleOpenModal,
disabled: isPending || isConnecting,
className: "w-full p-3 border border-gray-300 rounded-lg hover:border-[#7042D2] flex items-center space-x-3 transition-colors disabled:opacity-50",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl", children: "💼" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left flex-1", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: "Other Wallets" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500", children: "Choose from available wallets" })
] })
]
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => handleConnectSpecificWallet("injected"),
disabled: isPending || isConnecting,
className: "w-full p-2 text-sm border border-dashed border-gray-300 rounded-lg hover:border-[#7042D2] transition-colors disabled:opacity-50",
children: "Try Direct Connection"
}
)
] }),
isConnecting && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center space-x-2 text-gray-600", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-4 h-4 animate-spin" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connecting..." })
] })
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-green-600", children: "Wallet Connected ✅" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Address:" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-sm", children: [
address == null ? void 0 : address.slice(0, 6),
"...",
address == null ? void 0 : address.slice(-4)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleDisconnect,
className: "text-xs text-red-600 hover:text-red-800",
children: "Disconnect"
}
)
] })
] }),
paymentData && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Amount:" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-medium", children: [
paymentData.totalAmount,
" ",
selectedToken == null ? void 0 : selectedToken.symbol
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Network:" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: selectedNetwork == null ? void 0 : selectedNetwork.name })
] })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleSendPayment,
disabled: isConnecting,
className: "w-full bg-[#7042D2] text-white py-4 rounded-lg font-semibold hover:bg-[#7042D2]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2",
children: isConnecting ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-5 h-5 animate-spin" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Processing Payment..." })
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-5 h-5" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Send Payment" })
] })
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-gray-500", children: [
"Make sure you have enough ",
selectedToken == null ? void 0 : selectedToken.symbol,
" and ETH for gas fees"
] })
] })
] }) });
};
const CoinleyPayment = ({
apiKey,
apiSecret,
apiUrl = "http://localhost:9000",
onSuccess,
onError,
onClose,
isOpen,
config,
theme = "light",
merchantName,
debug = false
}) => {
const [currentStep, setCurrentStep] = useState("select-method");
const [networks, setNetworks] = useState([]);
const [tokens, setTokens] = useState([]);
const [selectedNetwork, setSelectedNetwork] = useState(null);
const [selectedToken, setSelectedToken] = useState(null);
const [paymentMethod, setPaymentMethod] = useState(null);
const [qrCode, setQrCode] = useState("");
const [paymentData, setPaymentData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [copied, setCopied] = useState(false);
const [txHash, setTxHash] = useState("");
const [isConnecting, setIsConnecting] = useState(false);
const paymentAPI = useRef(new PaymentAPI(apiUrl, apiKey, apiSecret));
const walletConfig = createCoinleyWalletConfig({
appName: merchantName || "Coinley Payment",
appDescription: "Crypto payment processing",
// Fix: Provide proper chain configurations instead of just IDs
chains: [
{
id: 1,
name: "Ethereum",
network: "ethereum",
rpcUrls: {
default: { http: ["https://eth.llamarpc.com"] }
},
nativeCurrency: {
name: "Ethereum",
symbol: "ETH",
decimals: 18
}
},
{
id: 56,
name: "BNB Smart Chain",
network: "bsc",
rpcUrls: {
default: { http: ["https://bsc-dataseed1.binance.org"] }
},
nativeCurrency: {
name: "BNB",
symbol: "BNB",
decimals: 18
}
}
]
});
useEffect(() => {
if (isOpen) {
initializePayment();
}
}, [isOpen]);
const initializePayment = async () => {
try {
setLoading(true);
setError("");
const [networksRes, tokensRes] = await Promise.all([
paymentAPI.current.getNetworks(),
paymentAPI.current.getStablecoins()
]);
setNetworks(networksRes.networks || []);
setTokens(tokensRes.stablecoins || []);
if (debug) {
console.log("Initialized networks:", networksRes.networks);
console.log("Initialized tokens:", tokensRes.stablecoins);
}
} catch (err) {
console.error("Failed to initialize payment:", err);
setError("Failed to load payment options. Please try again.");
} finally {
setLoading(false);
}
};
const handleMethodSelect = (method) => {
setPaymentMethod(method);
setCurrentStep("select-network");
};
const handleNetworkSelect = async (network) => {
setSelectedNetwork(network);
const networkTokens = tokens.filter(
(token) => {
var _a;
return ((_a = token.Network) == null ? void 0 : _a.shortName) === network.shortName || token.networkId === network.id;
}
);
if (networkTokens.length > 1) {
setCurrentStep("select-token");
} else if (networkTokens.length === 1) {
const token = networkTokens[0];
setSelectedToken(token);
await initiatePayment(network, token);
} else {
setError(`No supported tokens found for ${network.name}`);
setCurrentStep("error");
}
};
const handleTokenSelect = async (token) => {
setSelectedToken(token);
await initiatePayment(selectedNetwork, token);
};
const initiatePayment = async (network, token) => {
try {
setLoading(true);
const paymentPayload = {
amount: config.amount,
currency: token.symbol,
network: network.shortName,
customerEmail: config.customerEmail,
callbackUrl: config.callbackUrl,
metadata: {
...config.metadata,
paymentMethod,
selectedNetwork: network.shortName,
selectedToken: token.symbol,
// Add the merchant wallet addresses to metadata
merchantWalletAddresses: config.merchantWalletAddresses
}
};
console.log("🔍 Payment payload with merchant wallets:", paymentPayload);
const payment = await paymentAPI.current.createPayment(paymentPayload);
setPaymentData(payment.payment);
if (paymentMethod === "wallet") {
setCurrentStep("wallet-connect");
} else {
await generateQRCode(payment.payment);
setCurrentStep("qr-code");
}
} catch (err) {
console.error("Payment initiation failed:", err);
setError(err.message || "Failed to create payment");
setCurrentStep("error");
} finally {
setLoading(false);
}
};
const generateQRCode = async (payment) => {
var _a, _b;
try {
const recipientAddress = ((_a = payment.metadata) == null ? void 0 : _a.recipientWallet) || ((_b = config.merchantWalletAddresses) == null ? void 0 : _b[(selectedNetwork == null ? void 0 : selectedNetwork.shortName) || ""]);
if (!recipientAddress) {
throw new Error("Recipient wallet address not found");
}
let qrData = "";
if ((selectedNetwork == null ? void 0 : selectedNetwork.type) === "ethereum" || (selectedNetwork == null ? void 0 : selectedNetwork.type) === "bsc") {
qrData = (selectedToken == null ? void 0 : selectedToken.contractAddress) ? `ethereum:${selectedToken.contractAddress}/transfer?address=${recipientAddress}&uint256=${payment.totalAmount * Math.pow(10, selectedToken.decimals)}` : `ethereum:${recipientAddress}?value=${payment.totalAmount}e18`;
} else if ((selectedNetwork == null ? void 0 : selectedNetwork.type) === "tron") {
qrData = `tron:${recipientAddress}?amount=${payment.totalAmount}`;
} else {
qrData = `${selectedToken == null ? void 0 : selectedToken.symbol}:${recipientAddress}?amount=${payment.totalAmount}`;
}
setQrCode(recipientAddress);
startPaymentVerification(payment.id);
} catch (err) {
console.error("QR code generation failed:", err);
setError("Failed to generate QR code");
setCurrentStep("error");
}
};
const startPaymentVerification = async (paymentId) => {
const maxAttempts = 60;
let attempts = 0;
const checkPayment = async () => {
var _a;
try {
const result = await paymentAPI.current.verifyQRPayment(paymentId);
if (result.verified && ((_a = result.payment) == null ? void 0 : _a.transactionHash)) {
setTxHash(result.payment.transactionHash);
await handlePaymentSuccess(paymentId, result.payment.transactionHash);
return true;
}
attempts++;
if (attempts < maxAttempts) {
setTimeout(checkPayment, 5e3);
} else {
setError("Payment verification timeout. Please check your transaction.");
setCurrentStep("error");
}
return false;
} catch (err) {
console.error("Payment verification error:", err);
attempts++;
if (attempts < maxAttempts) {
setTimeout(checkPayment, 5e3);
} else {
setError("Payment verification failed");
setCurrentStep("error");
}
return false;
}
};
setTimeout(checkPayment, 2e3);
};
const handleTransactionSent = async (transactionHash) => {
setTxHash(transactionHash);
setCurrentStep("processing");
try {
await paymentAPI.current.processPayment(
paymentData.id,
transactionHash,
(selectedNetwork == null ? void 0 : selectedNetwork.shortName) || "",
""
);
await handlePaymentSuccess(paymentData.id, transactionHash);
} catch (error2) {
console.error("Payment processing failed:", error2);
await handlePaymentSuccess(paymentData.id, transactionHash);
}
};
const handlePaymentSuccess = async (paymentId, transactionHash) => {
setCurrentStep("success");
if (onSuccess) {
onSuccess(paymentId, transactionHash, {
network: selectedNetwork == null ? void 0 : selectedNetwork.name,
token: selectedToken == null ? void 0 : selectedToken.symbol,
amount: paymentData == null ? void 0 : paymentData.totalAmount,
method: paymentMethod
});
}
};
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2e3);
} catch (err) {
console.error("Failed to copy:", err);
}
};
const handleClose = () => {
setCurrentStep("select-method");
setPaymentMethod(null);
setSelectedNetwork(null);
setSelectedToken(null);
setQrCode("");
setPaymentData(null);
setError("");
setTxHash("");
if (onClose) {
onClose();
}
};
const goBack = () => {
if (currentStep === "select-network") {
setCurrentStep("select-method");
} else if (currentStep === "select-token") {
setCurrentStep("select-network");
} else if (currentStep === "wallet-connect" || currentStep === "qr-code") {
const networkTokens = tokens.filter(
(token) => {
var _a;
return ((_a = token.Network) == null ? void 0 : _a.shortName) === (selectedNetwork == null ? void 0 : selectedNetwork.shortName) || token.networkId === (selectedNetwork == null ? void 0 : selectedNetwork.id);
}
);
if (networkTokens.length > 1) {
setCurrentStep("select-token");
} else {
setCurrentStep("select-network");
}
}
};
if (!isOpen) return null;
return /* @__PURE__ */ jsxRuntimeExports.jsxs(CoinleyWalletProvider, { config: walletConfig, walletConfig: { appName: merchantName || "Coinley Payment" }, children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { scale: 0.9, opacity: 0 },
animate: { scale: 1, opacity: 1 },
exit: { scale: 0.9, opacity: 0 },
className: `relative w-full max-w-md mx-4 rounded-2xl shadow-2xl overflow-hidden ${theme === "dark" ? "bg-gray-900 text-white" : "bg-white text-gray-900"}`,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative p-6 border-b border-gray-200 dark:border-gray-700", children: [
currentStep !== "select-method" && currentStep !== "success" && currentStep !== "error" && /* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: goBack,
className: "absolute left-4 top-6 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { className: "w-5 h-5" })
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleClose,
className: "absolute right-4 top-6 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "w-5 h-5" })
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center space-x-2 mb-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "w-5 h-5 text-[#7042D2]" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold", children: "Pay with Crypto" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "w-5 h-5 text-[#7042D2]" })
] }),
merchantName && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1", children: merchantName }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-2xl font-bold text-[#7042D2]", children: [
"$",
config.amount.toFixed(2)
] }) })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-6 min-h-[400px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(AnimatePresence, { mode: "wait", children: [
currentStep === "select-method" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-center mb-6", children: "Choose Payment Method" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
motion.button,
{
whileHover: { scale: 1.02 },
whileTap: { scale: 0.98 },
onClick: () => handleMethodSelect("wallet"),
className: "w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-[#7042D2] bg-opacity-10 rounded-lg group-hover:bg-opacity-20 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Wallet, { className: "w-6 h-6 text-[#7042D2]" }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold", children: "Connect Wallet" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400", children: "MetaMask, WalletConnect & more" })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-5 h-5 text-[#7042D2] ml-auto" })
] })
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
motion.button,
{
whileHover: { scale: 1.02 },
whileTap: { scale: 0.98 },
onClick: () => handleMethodSelect("qr"),
className: "w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-[#7042D2] bg-opacity-10 rounded-lg group-hover:bg-opacity-20 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(QrCode, { className: "w-6 h-6 text-[#7042D2]" }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold", children: "QR Code" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Scan with mobile wallet" })
] })
] })
}
)
]
},
"select-method"
),
currentStep === "select-network" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-center mb-6", children: "Select Network" }),
loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-8 h-8 animate-spin text-[#7042D2]" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: networks.map((network) => /* @__PURE__ */ jsxRuntimeExports.jsx(
motion.button,
{
whileHover: { scale: 1.02 },
whileTap: { scale: 0.98 },
onClick: () => handleNetworkSelect(network),
className: "w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-4", children: [
network.logo ? /* @__PURE__ */ jsxRuntimeExports.jsx(
"img",
{
src: network.logo,
alt: network.name,
className: "w-8 h-8 rounded-full"
}
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-[#7042D2]", children: network.shortName.charAt(0).toUpperCase() }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left flex-1", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold", children: network.name }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400", children: network.shortName.toUpperCase() })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "w-5 h-5 text-gray-400 transform -rotate-90" })
] })
},
network.id
)) })
]
},
"select-network"
),
currentStep === "select-token" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-semibold text-center mb-6", children: [
"Select Token on ",
selectedNetwork == null ? void 0 : selectedNetwork.name
] }),
loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-8 h-8 animate-spin text-[#7042D2]" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: tokens.filter(
(token) => {
var _a;
return ((_a = token.Network) == null ? void 0 : _a.shortName) === (selectedNetwork == null ? void 0 : selectedNetwork.shortName) || token.networkId === (selectedNetwork == null ? void 0 : selectedNetwork.id);
}
).map((token) => /* @__PURE__ */ jsxRuntimeExports.jsx(
motion.button,
{
whileHover: { scale: 1.02 },
whileTap: { scale: 0.98 },
onClick: () => handleTokenSelect(token),
className: "w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-4", children: [
token.logo ? /* @__PURE__ */ jsxRuntimeExports.jsx(
"img",
{
src: token.logo,
alt: token.symbol,
className: "w-8 h-8 rounded-full"
}
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-[#7042D2]", children: token.symbol.charAt(0) }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left flex-1", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold", children: token.name }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 dark:text-gray-400", children: [
token.symbol,
token.isStablecoin && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2 px-2 py-0.5 bg-green-100 text-green-600 text-xs rounded-full", children: "Stablecoin" })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "w-5 h-5 text-gray-400 transform -rotate-90" })
] })
},
token.id
)) })
]
},
"select-token"
),
currentStep === "wallet-connect" && /* @__PURE__ */ jsxRuntimeExports.jsx(
motion.div,
{
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
WalletIntegration,
{
selectedNetwork,
selectedToken,
paymentData,
config,
onTransactionSent: handleTransactionSent,
onError: setError,
isConnecting,
setIsConnecting
}
)
},
"wallet-connect"
),
currentStep === "qr-code" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
className: "text-center space-y-6",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold", children: "Scan QR Code" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-600 dark:text-gray-400", children: "Use your mobile wallet to scan and pay" })
] }),
qrCode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white p-4 rounded-xl mx-auto inline-block", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-48 h-48 flex items-center justify-center text-gray-500", children: [
"QR Code: ",
qrCode.slice(0, 10),
"...",
qrCode.slice(-4)
] }) }),
paymentData && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Network:" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: selectedNetwork == null ? void 0 : selectedNetwork.name })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Amount:" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-medium", children: [
paymentData.totalAmount,
" ",
selectedToken == null ? void 0 : selectedToken.symbol
] })
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center space-x-2 text-sm text-gray-600 dark:text-gray-400", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-4 h-4 animate-spin" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Waiting for payment..." })
] })
] })
]
},
"qr-code"
),
currentStep === "processing" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { scale: 0.8, opacity: 0 },
animate: { scale: 1, opacity: 1 },
className: "text-center space-y-6",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "w-8 h-8 animate-spin text-[#7042D2]" }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-[#7042D2]", children: "Processing Payment..." }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-600 dark:text-gray-400 mt-2", children: "Your transaction is being processed" })
] })
]
},
"processing"
),
currentStep === "success" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
motion.div,
{
initial: { scale: 0.8, opacity: 0 },
animate: { scale: 1, opacity: 1 },
className: "text-center space-y-6",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "w-8 h-8 text-green-500" }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-green-600", children: "Payment Successful!" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-600 dark:text-gray-400 mt-2", children: "Your payment has been processed successfully" })
] }),
txHash && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-gray-50 dark:bg-gray-800 rounded-lg p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: "Transaction:" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-sm", children: [
txHash.slice(0, 6),
"...",
txHash.slice(-4)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => copyToClipboard(txHash),
className: "p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "w-4 h-4" })
}
),
(selectedNetwork == null ? void 0 : selectedNetwork.explorerUrl) && /* @__PURE__ */ jsxRuntimeExports.jsx(
"a",
{
hr