From ab054884eb51b1de7bc76ace8db7c5e53263adae Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Fri, 26 Jun 2026 20:47:31 +0200 Subject: [PATCH] Integrate Razorpay checkout flow --- src/components/dashboard/CreditsPage.tsx | 205 +++++++++++------------ src/global.d.ts | 37 ++++ src/lib/api.ts | 20 ++- src/lib/razorpay.ts | 92 ++++++++++ src/routes/dashboard/wallet/buy.tsx | 32 ++-- 5 files changed, 257 insertions(+), 129 deletions(-) create mode 100644 src/lib/razorpay.ts diff --git a/src/components/dashboard/CreditsPage.tsx b/src/components/dashboard/CreditsPage.tsx index 9467d76..efb01b8 100644 --- a/src/components/dashboard/CreditsPage.tsx +++ b/src/components/dashboard/CreditsPage.tsx @@ -2,6 +2,7 @@ import { For, Show, createSignal, onMount } from "solid-js"; import { Portal } from "solid-js/web"; import { BTN_GHOST, BTN_ORANGE, BTN_PRIMARY, CARD, ORANGE, NAVY, PKG_CARD } from "~/components/DashboardShell"; import { PROFESSIONAL_ROLE_SET, ROLE_PREFIXES, type RoleKey } from "./RoleDashboardShared"; +import { openRazorpayCheckout } from "~/lib/razorpay"; const API = "/api/gateway"; @@ -102,8 +103,6 @@ export default function CreditsPage(props: Props) { step: "form", error: "", }); - - // Mock card form state const [cardNumber, setCardNumber] = createSignal(""); const [cardExpiry, setCardExpiry] = createSignal(""); const [cardCvv, setCardCvv] = createSignal(""); @@ -356,24 +355,9 @@ export default function CreditsPage(props: Props) { if (!pkg) return; - // Validate card fields - if (cardNumber().replace(/\s/g, "").length < 16) { - setCheckout((c) => ({ ...c, error: "Please enter a valid card number" })); - return; - } - if (!cardExpiry() || cardExpiry().length < 5) { - setCheckout((c) => ({ ...c, error: "Please enter expiry date (MM/YY)" })); - return; - } - if (cardCvv().length < 3) { - setCheckout((c) => ({ ...c, error: "Please enter valid CVV" })); - return; - } - setCheckout((c) => ({ ...c, step: "processing", error: "" })); try { - // Step 1: Create order const orderRes = await apiFetch("/api/payments/create-order", { method: "POST", body: JSON.stringify({ @@ -393,24 +377,25 @@ export default function CreditsPage(props: Props) { return; } - const orderId = orderData.order_id; + const paymentResult = await openRazorpayCheckout({ + amount: orderData.amount, + currency: orderData.currency, + orderId: orderData.order_id, + description: `${pkg.name} - ${pkg.tracecoins_amount} Tracecoins`, + }); - // Step 2: Simulate payment processing (mock) - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // Step 3: Verify payment const verifyRes = await apiFetch("/api/payments/verify", { method: "POST", body: JSON.stringify({ - order_id: orderId, - payment_id: "pay_mock_" + Date.now(), - signature: "mock_signature", + razorpay_order_id: paymentResult.razorpay_order_id, + razorpay_payment_id: paymentResult.razorpay_payment_id, + razorpay_signature: paymentResult.razorpay_signature, }), }); const verifyData = await verifyRes.json().catch(() => ({})); if (verifyRes.ok && verifyData.verified) { - setCheckout((c) => ({ ...c, step: "success", orderId })); + setCheckout((c) => ({ ...c, step: "success", orderId: paymentResult.razorpay_order_id })); setMsg( `Payment successful! ${pkg.tracecoins_amount} Tracecoins have been added to your wallet.` ); @@ -655,52 +640,36 @@ export default function CreditsPage(props: Props) { - {/* Mock Notice */} + {/* Razorpay Notice */}
- Mock Payment: Use any values. This is a simulation. + Razorpay Standard Checkout will open in a secure modal. Cancelled or failed + payments will not be credited.
- {/* Card Number */} -
-
- {/* Expiry & CVV */} -
setCardExpiry(formatExpiry(e.currentTarget.value))} - placeholder="MM/YY" - maxLength="5" + value={cardNumber()} + onInput={(e) => setCardNumber(formatCardNumber(e.currentTarget.value))} + placeholder="1234 5678 9012 3456" + maxLength="19" style={{ width: "100%", padding: "12px", @@ -729,6 +698,68 @@ export default function CreditsPage(props: Props) { }} />
+ +
+
+ + setCardExpiry(formatExpiry(e.currentTarget.value))} + placeholder="MM/YY" + maxLength="5" + style={{ + width: "100%", + padding: "12px", + border: "1px solid #E5E7EB", + "border-radius": "8px", + "font-size": "14px", + "box-sizing": "border-box", + }} + /> +
+
+ + + setCardCvv(e.currentTarget.value.replace(/\D/g, "").slice(0, 4)) + } + placeholder="123" + maxLength="4" + style={{ + width: "100%", + padding: "12px", + border: "1px solid #E5E7EB", + "border-radius": "8px", + "font-size": "14px", + "box-sizing": "border-box", + }} + /> +
+
+
- setCardCvv(e.currentTarget.value.replace(/\D/g, "").slice(0, 4)) - } - placeholder="123" - maxLength="4" + value={cardName()} + onInput={(e) => setCardName(e.currentTarget.value)} + placeholder="John Doe" style={{ width: "100%", padding: "12px", @@ -759,36 +787,7 @@ export default function CreditsPage(props: Props) { }} />
-
- - {/* Cardholder Name */} -
- - setCardName(e.currentTarget.value)} - placeholder="John Doe" - style={{ - width: "100%", - padding: "12px", - border: "1px solid #E5E7EB", - "border-radius": "8px", - "font-size": "14px", - "box-sizing": "border-box", - }} - /> -
+ {/* Pay Button */}