Learn how to build a secure, scalable donation system with Lovable using step by step guidance, best practices, payment integration and analytics.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
We’ll build a simple, production-capable donation flow in Lovable: a public Donate page, a server API that creates Stripe Checkout sessions using secrets stored in Lovable Cloud, optional Supabase storage of donation records, and a small admin list to view donations. All changes are done in Lovable Chat Mode (edits/diffs), Preview, and Publish — no terminal required. Use Lovable Secrets for keys, test with Stripe test keys in Preview, and enable Stripe webhooks after Publish.
Public Donate page: amount input, email, Donate button that calls a server API to create a Stripe Checkout session and redirects the user to Checkout.
Server API endpoints: /api/create-checkout to create sessions, /api/webhook to receive Stripe webhook events and record donations in Supabase.
Optional Supabase storage: store donation rows so admins can view donations in an admin page.
Prompt 1 — Create Donate UI and client flow
Goal: Add a Donate page and component that posts to API and redirects to Stripe Checkout.
Exact files to create/modify:
Acceptance criteria:
Secrets/integrations:
Prompt text to paste into Lovable Chat Mode (single message):
Please create src/components/DonateForm.tsx and src/pages/Donate.tsx, and register /donate route in src/App.tsx (or next/router routes). DonateForm should:
Prompt 2 — Add server API to create Checkout sessions
Goal: Create server endpoint to call Stripe and return Checkout url.
Exact files to create/modify:
Acceptance criteria:
Secrets/integrations:
Prompt text to paste into Lovable Chat Mode:
Create src/pages/api/create-checkout.ts and src/lib/stripe.ts. The API should:
${process.env.NEXT_PUBLIC_APP_URL}/donation-success?session_id={CHECKOUT_SESSION_ID} (we will set NEXT_PUBLIC_APP_URL via Secrets or environment)
Prompt 3 — Optional: Supabase integration and webhook to record donations
Goal: Record completed donations in Supabase using Stripe webhooks.
Exact files to create/modify:
Acceptance criteria:
Secrets/integrations:
Prompt text to paste into Lovable Chat Mode:
Create src/lib/supabaseClient.ts using SUPABASE_URL and SUPABASE_ANON_KEY, create src/pages/api/webhook.ts to:
Prompt 4 — Admin donations list page
Goal: Add a simple admin page to list donations from Supabase.
Exact files to create/modify:
Acceptance criteria:
Secrets/integrations:
Prompt text to paste into Lovable Chat Mode:
Create src/pages/admin/donations.tsx that lists donations by querying Supabase from src/lib/supabaseClient.ts. Display amount, email, status, created_at. Done when /admin/donations shows rows from the Supabase donations table in Preview (test data).
This prompt helps an AI assistant understand your setup and guide to build the feature
This prompt helps an AI assistant understand your setup and guide to build the feature
This prompt helps an AI assistant understand your setup and guide to build the feature

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A compact answer: Use a payment processor (Stripe Checkout) so you never handle card data, store secrets (Stripe keys, Supabase service role) in Lovable Secrets UI, validate amounts server-side, record donations in a database (Supabase) via server-side webhooks, and deploy server code as serverless endpoints (created in Lovable files and published or exported to GitHub). In Lovable you must edit files with Chat Mode or file diffs, put secrets in Secrets UI, test in Preview for UI flows, and Publish or export to GitHub for a public endpoint (webhooks require a live URL). Focus on security (no card fields on your servers), idempotent webhook handling, rate limits, and clear UX for donors.
// server/api/create-checkout-session.js
// Node/Express style serverless handler for creating a Checkout Session
const stripe = require('stripe')(process.env.STRIPE_SECRET);
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY;
const fetch = require('node-fetch'); // or use supabase client
module.exports = async (req, res) => {
// validate method and body
if (req.method !== 'POST') return res.status(405).end();
const {amount_cents, email} = req.body;
// server-side validation of amount
if (!amount_cents || amount_cents < 100) return res.status(400).json({error:'invalid amount'});
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{price_data:{currency:'usd',product_data:{name:'Donation'},unit_amount: amount_cents},quantity:1}],
mode: 'payment',
customer_email: email,
success_url: process.env.SUCCESS_URL,
cancel_url: process.env.CANCEL_URL
});
return res.json({sessionId: session.id});
};
// server/api/webhook.js
// Verify Stripe signature, then write to Supabase (or your DB). Use STRIPE_WEBHOOK_SECRET in Secrets UI.
const stripe = require('stripe')(process.env.STRIPE_SECRET);
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY;
module.exports = async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// idempotency: use session.id as unique key in DB
await fetch(`${SUPABASE_URL}/rest/v1/donations`, {
method: 'POST',
headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: session.id, email: session.customer_email, amount: session.amount_total, currency: session.currency, created_at: new Date().toISOString() })
});
}
res.json({received: true});
};
<!-- client/donate.html -->
<!-- include publishable key from environment injected at build time or via config -->
<script src="https://js.stripe.com/v3/"></script>
<form id="donationForm">
<input id="email" type="email" required/>
<input id="amount" type="number" min="1" required/>
<button type="submit">Donate</button>
</form>
<script>
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY'); // inject via config/Preview
document.getElementById('donationForm').addEventListener('submit', async e=>{
e.preventDefault();
const amount = Math.round(Number(document.getElementById('amount').value)*100);
const email = document.getElementById('email').value;
const r = await fetch('/api/create-checkout-session', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({amount_cents:amount,email})});
const {sessionId} = await r.json();
const {error} = await stripe.redirectToCheckout({sessionId});
if (error) alert(error.message);
});
</script>
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.