From a2af10606e7b06abcba829071d5dbb856ff853a7 Mon Sep 17 00:00:00 2001 From: shialoth Date: Wed, 30 Apr 2025 00:34:19 +0530 Subject: [PATCH] new payment system --- package.json | 11 + src/app/dashboard/api/orders/route.ts | 70 ++++++ src/app/dashboard/rent/Boxes.tsx | 335 +++++++++++--------------- src/components/GpuPaymentModal.tsx | 163 +++++++++++++ src/components/Providers.tsx | 2 +- 5 files changed, 388 insertions(+), 193 deletions(-) create mode 100644 src/app/dashboard/api/orders/route.ts create mode 100644 src/components/GpuPaymentModal.tsx diff --git a/package.json b/package.json index fe49948..2598c77 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,20 @@ "dependencies": { "@geist-ui/core": "^2.3.8", "@privy-io/react-auth": "^2.0.4", + "@solana/spl-token": "^0.4.13", + "@solana/wallet-adapter-base": "^0.9.26", + "@solana/wallet-adapter-phantom": "^0.9.27", + "@solana/wallet-adapter-react": "^0.15.38", + "@solana/wallet-adapter-react-ui": "^0.9.38", + "@solana/wallet-adapter-wallets": "^0.19.36", + "@solana/web3.js": "^1.98.2", + "@supabase/supabase-js": "^2.49.4", + "axios": "^1.9.0", "chart.js": "^4.4.7", "framer-motion": "^11.18.1", "geist": "^1.3.1", "next": "^14.2.23", + "nodemailer": "^6.10.1", "react": "^18.2.0", "react-chartjs-2": "^5.3.0", "react-dom": "^18.2.0", @@ -24,6 +34,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", + "@types/nodemailer": "^6.4.17", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8.0.0", diff --git a/src/app/dashboard/api/orders/route.ts b/src/app/dashboard/api/orders/route.ts new file mode 100644 index 0000000..6df271b --- /dev/null +++ b/src/app/dashboard/api/orders/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createClient } from '@supabase/supabase-js'; +import nodemailer from 'nodemailer'; + +const supabase = createClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY! +); + +const emailUser = process.env.EMAIL_USER!; +const emailPass = process.env.EMAIL_PASS!; +const businessEmail = process.env.BUSINESS_EMAIL!; + +export async function POST(req: NextRequest) { + try { + const { gpuId, userEmail, txSignature, amountSol, status } = await req.json(); + + if (!gpuId || !userEmail || !amountSol || !status) { + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); + } + + if (!['pending', 'success', 'failed'].includes(status)) { + return NextResponse.json({ error: 'Invalid status value' }, { status: 400 }); + } + + const { error } = await supabase.from('orders').insert({ + gpu_id: gpuId, + user_email: userEmail, + amount_sol: amountSol, + sol_tx_signature: txSignature || null, + status, + }); + + if (error) { + console.error('[Supabase error]', error); + return NextResponse.json({ error: 'Database error' }, { status: 500 }); + } + + // Send confirmation email + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: emailUser, + pass: emailPass, + }, + }); + + await transporter.sendMail({ + from: `"GPU Store" <${emailUser}>`, + to: userEmail, + subject: `GPU Payment ${status === 'success' ? 'Confirmed' : 'Attempted'}`, + html: ` +

Hi,

+

Your order for GPU ID ${gpuId} has status: ${status}.

+

Amount: ${amountSol} SOL

+ ${ + txSignature + ? `

Transaction: ${txSignature}

` + : '

No transaction was recorded.

' + } +

Thank you for using our platform.

+ `, + }); + + return NextResponse.json({ message: 'Order recorded and email sent' }); + } catch (err: any) { + console.error('[Order API Error]', err); + return NextResponse.json({ error: 'Server error' }, { status: 500 }); + } +} diff --git a/src/app/dashboard/rent/Boxes.tsx b/src/app/dashboard/rent/Boxes.tsx index 2c20989..567778e 100644 --- a/src/app/dashboard/rent/Boxes.tsx +++ b/src/app/dashboard/rent/Boxes.tsx @@ -1,234 +1,185 @@ -"use client"; -import Link from "next/link"; -import { motion } from "framer-motion"; +'use client'; + +import { useState } from 'react'; +import { motion } from 'framer-motion'; +import GpuPaymentModal from '@/components/GpuPaymentModal' interface IGPU { + id: string; title: string; subTitle: string; price: string; + price_usd: number; isRecentlyPurchased?: boolean; - link: string; } export default function Boxes() { + const [selectedGpu, setSelectedGpu] = useState(null); + const gpus: IGPU[] = [ { - title: "NVIDIA RTX A4000", - subTitle: "16GB GDDR6 - 6144 CUDA Cores", - price: "$0.20/hour ($147/mo)", + id: 'a4000', + title: 'NVIDIA RTX A4000', + subTitle: '16GB GDDR6 - 6144 CUDA Cores', + price: '$0.20/hour ($147/mo)', + price_usd: 147, isRecentlyPurchased: true, - link: "https://nowpayments.io/payment?iid=5964763281", }, { - title: "NVIDIA RTX A5000", - subTitle: "24 GB GDDR6 - 8192 CUDA Cores", - price: "$0.26/hr ($190/month)", - link: "https://nowpayments.io/payment?iid=4873013025", + id: 'a5000', + title: 'NVIDIA RTX A5000', + subTitle: '24 GB GDDR6 - 8192 CUDA Cores', + price: '$0.26/hr ($190/month)', + price_usd: 190, }, { - title: "4x NVIDIA RTX 4090", - subTitle: "24 GB GDDR6X - 16384 GPU CUDA Cores", - price: "$0.37/hr ($268/month)", - link: "https://nowpayments.io/payment?iid=5977605820", + id: '4x4090', + title: '4x NVIDIA RTX 4090', + subTitle: '24 GB GDDR6X - 16384 GPU CUDA Cores', + price: '$0.37/hr ($268/month)', + price_usd: 268, }, { - title: "NVIDIA V100", - subTitle: "32 GB HBM2 - 5120 CUDA Cores", - price: "$0.44/hr ($317/month)", - link: "https://nowpayments.io/payment?iid=5437270588", + id: 'v100', + title: 'NVIDIA V100', + subTitle: '32 GB HBM2 - 5120 CUDA Cores', + price: '$0.44/hr ($317/month)', + price_usd: 317, }, { - title: "NVIDIA L4 Ada", - subTitle: "24 GB GDDR6 - 7680 CUDA Cores", - price: "$0.52/hr ($372/month)", - link: "https://nowpayments.io/payment?iid=6283623234", + id: 'l4ada', + title: 'NVIDIA L4 Ada', + subTitle: '24 GB GDDR6 - 7680 CUDA Cores', + price: '$0.52/hr ($372/month)', + price_usd: 372, }, { - title: "NVIDIA RTX A6000", - subTitle: "48 GB GDDR6 - 10752 CUDA Cores", - price: "$0.53/hr ($380/month)", - link: "https://nowpayments.io/payment?iid=6074730706", + id: 'a6000', + title: 'NVIDIA RTX A6000', + subTitle: '48 GB GDDR6 - 10752 CUDA Cores', + price: '$0.53/hr ($380/month)', + price_usd: 380, }, { - title: "NVIDIA L40S Ada", - subTitle: "48 GB GDDR6 - 18176 CUDA Cores", - price: "$1.24/hr ($890/month)", - link: "https://nowpayments.io/payment?iid=4570621084", + id: 'l40sada', + title: 'NVIDIA L40S Ada', + subTitle: '48 GB GDDR6 - 18176 CUDA Cores', + price: '$1.24/hr ($890/month)', + price_usd: 890, }, { - title: "NVIDIA A100 PCIe", - subTitle: "40 GB HBM2 - 6912 CUDA Cores", - price: "$1.62/hr ($1,166/month)", - link: "https://nowpayments.io/payment?iid=6381921922", + id: 'a100', + title: 'NVIDIA A100 PCIe', + subTitle: '40 GB HBM2 - 6912 CUDA Cores', + price: '$1.62/hr ($1,166/month)', + price_usd: 1166, }, { - title: "NVIDIA H100 NVL", - subTitle: "94 GB HBM3 - 14592 CUDA Cores", - price: "$3.35/hr ($2,410/month)", - link: "https://nowpayments.io/payment?iid=5319698362", + id: 'h100nvl', + title: 'NVIDIA H100 NVL', + subTitle: '94 GB HBM3 - 14592 CUDA Cores', + price: '$3.35/hr ($2,410/month)', + price_usd: 2410, }, { - title: "NVIDIA H100 SXM", - subTitle: "80 GB HBM3 - 14592 CUDA Cores", - price: "$3.60/hr ($2,592/month)", - link: "https://nowpayments.io/payment?iid=4768823499", + id: 'h100sxm', + title: 'NVIDIA H100 SXM', + subTitle: '80 GB HBM3 - 14592 CUDA Cores', + price: '$3.60/hr ($2,592/month)', + price_usd: 2592, }, ]; return ( -
- {gpus.map((gpu, index) => ( - -
-
- - - - - - - - - - - - -

{gpu.title}

+ <> +
+ {gpus.map((gpu, index) => ( + +
+
+ {/* GPU SVG ICON */} + + + + + + + + + + + +

{gpu.title}

+
+

{gpu.subTitle}

-

{gpu.subTitle}

-
- -
-
-

- {gpu.price} -

- - {gpu.isRecentlyPurchased && ( -

- - - - Recently purchased +

+
+

+ {gpu.price}

- )} + {gpu.isRecentlyPurchased && ( +

+ + + + Recently purchased +

+ )} +
+ +
- - Rent now - -
+ {/* Corner Decorations */} + {['top', 'bottom'].map((pos) => + ['left', 'right'].map((side) => ( + + + + + + + )) + )} + + ))} +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} -
+ {selectedGpu && ( + setSelectedGpu(null)} + gpu={{ + id: selectedGpu.id, + title: selectedGpu.title, + price_usd: selectedGpu.price_usd, + }} + /> + )} + ); } diff --git a/src/components/GpuPaymentModal.tsx b/src/components/GpuPaymentModal.tsx new file mode 100644 index 0000000..f9683c5 --- /dev/null +++ b/src/components/GpuPaymentModal.tsx @@ -0,0 +1,163 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { + Connection, + PublicKey, + Transaction, + SystemProgram, +} from '@solana/web3.js'; +import axios from 'axios'; +import { usePrivy } from '@privy-io/react-auth'; +import { + useSolanaWallets, + useSignTransaction, +} from '@privy-io/react-auth/solana'; + +interface GpuPaymentModalProps { + isOpen: boolean; + onClose: () => void; + gpu: { + id: string; + title: string; + price_usd: number; + }; +} + +const SOLANA_RPC = 'https://api.mainnet-beta.solana.com'; +const BUSINESS_WALLET = 'Y93ednSpre2XRjPTBafHU1BXPXKMPhujcYAEshS5pXm8K'; // <-- Replace this + +export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps) => { + const { user } = usePrivy(); + const { wallets } = useSolanaWallets(); + const { signTransaction } = useSignTransaction(); + + const [solPrice, setSolPrice] = useState(null); + const [loading, setLoading] = useState(false); + const [errorMsg, setErrorMsg] = useState(null); + const [successMsg, setSuccessMsg] = useState(null); + + const connection = new Connection(SOLANA_RPC); + + useEffect(() => { + const fetchSolPrice = async () => { + try { + const { data } = await axios.get( + 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd' + ); + setSolPrice(data.solana.usd); + } catch { + setErrorMsg('Failed to fetch SOL price.'); + } + }; + + if (isOpen) { + fetchSolPrice(); + setErrorMsg(null); + setSuccessMsg(null); + } + }, [isOpen]); + + const handlePayment = async () => { + setLoading(true); + setErrorMsg(null); + setSuccessMsg(null); + + try { + const solWallet = wallets[0]; + if (!solWallet || !solWallet.address) { + throw new Error('No connected Solana wallet found.'); + } + + 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( + SystemProgram.transfer({ + fromPubkey: fromPubKey, + toPubkey: toPubKey, + lamports: Math.floor(parseFloat(solAmount) * 1e9), + }) + ); + + transaction.feePayer = fromPubKey; + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + + const signedTx = await signTransaction({ + transaction, + connection, + address: solWallet.address, + }); + + 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', + }); + + 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); + } + }; + + if (!isOpen) return null; + + const solAmount = solPrice ? (gpu.price_usd / solPrice).toFixed(6) : '...'; + + return ( +
+
+

{gpu.title}

+

+ Price: ${gpu.price_usd} USD (~{solAmount} SOL) +

+ + {errorMsg && ( +
+ {errorMsg} +
+ )} + + {successMsg && ( +
+ {successMsg} +
+ )} + +
+ + +
+
+
+ ); +}; + +export default GpuPaymentModal; diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx index a28ffe3..a9ca767 100644 --- a/src/components/Providers.tsx +++ b/src/components/Providers.tsx @@ -11,7 +11,7 @@ const solanaConnectors = toSolanaWalletConnectors({ export default function Providers({ children }: { children: React.ReactNode }) { return (