Payments - Cashfree
Cashfree handles one-time payments in INR. UPI, credit/debit cards, net banking, and wallets are all supported.
Architecture
Section titled “Architecture”Browser -> /checkout?plan=starter-website -> fetch POST /api/payment/create-order -> Cashfree REST API (creates order) <-- { payment_session_id, order_id, mode } -> Cashfree JS SDK (.checkout()) -> Cashfree hosted payment page -> payment completed -> redirect to /payment-return?order_id=JA-xxx -> fetch GET /api/payment/verify?order_id=JA-xxx -> Cashfree REST API (checks status) -> show success / pending / failedFile structure
Section titled “File structure”functions/ api/payment/ create-order.ts <-- POST /api/payment/create-order verify.ts <-- GET /api/payment/verify
src/pages/ checkout.astro <-- payment form + Cashfree SDK payment-return.astro <-- post-payment status pagecreate-order.ts
Section titled “create-order.ts”type Env = { CASHFREE_APP_ID: string; CASHFREE_SECRET_KEY: string; CASHFREE_ENV: string;};
type PagesContext = { request: Request; env: Env;};
const PLANS: Record<string, { name: string; amount: number }> = { "starter-website": { name: "Starter Website", amount: 24999 }, "business-website": { name: "Business Website", amount: 44999 },};
const json = (body: object, status = 200) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" }, });
export const onRequestPost = async ({ request, env }: PagesContext) => { const body = await request.json(); const { plan, customer_name, customer_email, customer_phone } = body;
const planDetails = PLANS[plan]; if (!planDetails) return json({ error: "Invalid plan" }, 400);
const appId = env.CASHFREE_APP_ID; const secretKey = env.CASHFREE_SECRET_KEY; const cfEnv = env.CASHFREE_ENV ?? "production";
if (!appId || !secretKey) return json({ error: "Payment gateway not configured" }, 500);
const orderId = `ORDER-${Date.now()}-${Math.random().toString(36).substring(2, 7).toUpperCase()}`;
const apiUrl = cfEnv === "sandbox" ? "https://sandbox.cashfree.com/pg/orders" : "https://api.cashfree.com/pg/orders";
const res = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", "x-client-id": appId, "x-client-secret": secretKey, "x-api-version": "2023-08-01", }, body: JSON.stringify({ order_id: orderId, order_amount: planDetails.amount, order_currency: "INR", customer_details: { customer_id: customer_email.replace(/[^a-zA-Z0-9_-]/g, "_").substring(0, 50), customer_name, customer_email, customer_phone, }, order_meta: { return_url: `https://domain.com/payment-return?order_id=${orderId}`, }, order_note: planDetails.name, }), });
const order = await res.json() as { payment_session_id: string }; return json({ payment_session_id: order.payment_session_id, order_id: orderId, mode: cfEnv });};Checkout page (client-side)
Section titled “Checkout page (client-side)”<!-- Load SDK in <head> or before the script --><script is:inline src="https://sdk.cashfree.com/js/v3/cashfree.js"></script>const res = await fetch("/api/payment/create-order", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ plan, customer_name, customer_email, customer_phone }),});const data = await res.json();
// Launch Cashfree hosted checkoutconst cashfree = window.Cashfree({ mode: data.mode === "sandbox" ? "sandbox" : "production" });cashfree.checkout({ paymentSessionId: data.payment_session_id, redirectTarget: "_self" });Environment variables
Section titled “Environment variables”Set in Cloudflare Pages -> Settings -> Environment variables:
| Variable | Value |
|---|---|
CASHFREE_APP_ID |
App ID from Cashfree dashboard |
CASHFREE_SECRET_KEY |
Secret key from Cashfree dashboard |
CASHFREE_ENV |
sandbox for testing, production for live |
Testing (sandbox)
Section titled “Testing (sandbox)”- Set
CASHFREE_ENV=sandboxin Cloudflare env vars - Use Cashfree sandbox test card:
4111 1111 1111 1111, any future date, any CVV - Verify the full flow: checkout -> payment -> return page shows success
Switch to production only after the full flow is confirmed working in sandbox.
What to Disallow in robots.txt
Section titled “What to Disallow in robots.txt”Disallow: /checkoutDisallow: /payment-returnDisallow: /thank-youBoth /checkout and /payment-return should also have noindex={true} set in the page’s Layout props.