Skip to content

Payments - Cashfree

Cashfree handles one-time payments in INR. UPI, credit/debit cards, net banking, and wallets are all supported.


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 / failed

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 page

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 });
};

<!-- 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 checkout
const cashfree = window.Cashfree({ mode: data.mode === "sandbox" ? "sandbox" : "production" });
cashfree.checkout({ paymentSessionId: data.payment_session_id, redirectTarget: "_self" });

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

  1. Set CASHFREE_ENV=sandbox in Cloudflare env vars
  2. Use Cashfree sandbox test card: 4111 1111 1111 1111, any future date, any CVV
  3. Verify the full flow: checkout -> payment -> return page shows success

Switch to production only after the full flow is confirmed working in sandbox.


Disallow: /checkout
Disallow: /payment-return
Disallow: /thank-you

Both /checkout and /payment-return should also have noindex={true} set in the page’s Layout props.