payment system fix
This commit is contained in:
parent
a2af10606e
commit
6524ea415e
@ -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
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user