payment system fix

This commit is contained in:
shialoth 2025-04-30 23:08:35 +05:30
parent a2af10606e
commit 6524ea415e
2 changed files with 122 additions and 50 deletions

View File

@ -171,14 +171,15 @@ export default function Boxes() {
{selectedGpu && (
<GpuPaymentModal
isOpen={true}
onClose={() => setSelectedGpu(null)}
gpu={{
id: selectedGpu.id,
title: selectedGpu.title,
price_usd: selectedGpu.price_usd,
}}
/>
isOpen={true}
onClose={() => setSelectedGpu(null)}
gpu={{
id: selectedGpu.id,
title: selectedGpu.title,
price_usd: selectedGpu.price_usd,
price_per_hour: selectedGpu.price, // pass it here
}}
/>
)}
</>
);

View File

@ -11,8 +11,8 @@ import axios from 'axios';
import { usePrivy } from '@privy-io/react-auth';
import {
useSolanaWallets,
useSignTransaction,
} from '@privy-io/react-auth/solana';
import { createClient } from '@supabase/supabase-js';
interface GpuPaymentModalProps {
isOpen: boolean;
@ -21,23 +21,35 @@ interface GpuPaymentModalProps {
id: string;
title: string;
price_usd: number;
price_per_hour: string; // new field
};
}
const SOLANA_RPC = 'https://api.mainnet-beta.solana.com';
const BUSINESS_WALLET = 'Y93ednSpre2XRjPTBafHU1BXPXKMPhujcYAEshS5pXm8K'; // <-- Replace this
const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC!;
const BUSINESS_WALLET = process.env.NEXT_PUBLIC_BUSINESS_WALLET!;
const EMAIL_API_URL = process.env.NEXT_PUBLIC_EMAIL_API_URL!;
export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps) => {
const { user } = usePrivy();
const { wallets } = useSolanaWallets();
const { signTransaction } = useSignTransaction();
const { user, connectWallet } = usePrivy();
const { wallets: privyWallets } = useSolanaWallets();
const [solPrice, setSolPrice] = useState<number | null>(null);
const [emailInput, setEmailInput] = useState('');
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [successMsg, setSuccessMsg] = useState<string | null>(null);
const connection = new Connection(SOLANA_RPC);
const userEmail = user?.email?.address || emailInput || null;
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const [emailTouched, setEmailTouched] = useState(false);
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
useEffect(() => {
const fetchSolPrice = async () => {
@ -64,13 +76,23 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
setSuccessMsg(null);
try {
const solWallet = wallets[0];
if (!userEmail || !isValidEmail(userEmail)) {
throw new Error('Please enter a valid email address for your receipt.');
}
const solWallet = privyWallets[0];
if (!solWallet || !solWallet.address) {
throw new Error('No connected Solana wallet found.');
throw new Error('No connected Solana wallet found. Please connect a wallet via Privy.');
}
if (!solWallet.signTransaction) {
throw new Error('Connected wallet does not support transaction signing.');
}
const fromPubKey = new PublicKey(solWallet.address);
const toPubKey = new PublicKey(BUSINESS_WALLET);
const solAmount = (gpu.price_usd / (solPrice || 1)).toFixed(6);
const transaction = new Transaction().add(
@ -82,34 +104,41 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
);
transaction.feePayer = fromPubKey;
const { blockhash } = await connection.getRecentBlockhash();
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
const signedTx = await signTransaction({
transaction,
connection,
address: solWallet.address,
});
const signedTx = await solWallet.signTransaction(transaction);
const txId = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction(txId, 'confirmed');
await axios.post('/api/orders', {
gpuId: gpu.id,
userEmail: user?.email?.address,
txSignature: txId,
status: 'success',
// Send confirmation email
await axios.post(EMAIL_API_URL, {
email: userEmail,
product: gpu.title,
price_hour: gpu.price_per_hour,
price: gpu.price_usd.toFixed(2),
});
// Store order in Supabase
const { error } = await supabase.from('orders').insert([
{
gpu_id: gpu.id,
user_email: userEmail,
amount_sol: parseFloat(solAmount),
sol_tx_signature: txId,
status: 'success',
},
]);
if (error) {
throw new Error(`Failed to save order: ${error.message}`);
}
setSuccessMsg(`Payment successful! Transaction ID: ${txId}`);
} catch (err: any) {
setErrorMsg(err.message || 'Payment failed.');
await axios.post('/api/orders', {
gpuId: gpu.id,
userEmail: user?.email?.address,
txSignature: null,
status: 'failed',
});
} finally {
setLoading(false);
}
@ -139,22 +168,64 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
</div>
)}
<div className="mt-6 flex justify-end gap-3">
<button
onClick={onClose}
disabled={loading}
className="px-4 py-2 rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition"
>
Cancel
</button>
<button
onClick={handlePayment}
disabled={loading}
className="px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition disabled:opacity-60"
>
{loading ? 'Processing...' : 'Confirm Payment'}
</button>
</div>
{/* Wallet not connected */}
{privyWallets.length === 0 ? (
<div className="mt-6 text-center">
<p className="text-sm text-gray-700 mb-4">
To proceed with payment, please connect a Solana wallet.
</p>
<button
onClick={connectWallet}
className="px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition"
>
Connect Wallet
</button>
</div>
) : (
<>
{/* Show email prompt if not logged in with email */}
{!user?.email?.address && (
<div className="mt-4">
<label className="block text-sm text-gray-700 mb-1">
Email for invoice & access:
</label>
<input
type="email"
placeholder="you@example.com"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
onBlur={() => setEmailTouched(true)}
className="w-full px-3 py-2 border rounded-md text-sm"
/>
{emailTouched && emailInput && !isValidEmail(emailInput) && (
<p className="mt-1 text-xs text-red-600">Please enter a valid email address.</p>
)}
</div>
)}
<div className="mt-6 flex justify-end gap-3">
<button
onClick={onClose}
disabled={loading}
className="px-4 py-2 rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition"
>
Cancel
</button>
<button
onClick={handlePayment}
disabled={
loading ||
(!user?.email?.address && (!emailInput || !isValidEmail(emailInput)))
}
className="px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition disabled:opacity-60"
>
{loading ? 'Processing...' : 'Confirm Payment'}
</button>
</div>
</>
)}
</div>
</div>
);