Internal client operations workspace by Ripple Nexus. Today it powers Career Booster billing (payment links, multi-currency, branded emails) and is designed to expand into broader invoicing + client onboarding workflows.
ripple-nexus/
βββ .env.local # Environment variables (real keys inside)
βββ .env.example # Template for reference
βββ next.config.js
βββ tailwind.config.js
βββ tsconfig.json
βββ prisma/
β βββ schema.prisma # PostgreSQL schema (Invoice + ExchangeRateCache)
βββ src/
βββ app/
β βββ layout.tsx # Root layout (Plus Jakarta Sans font)
β βββ globals.css # Design tokens, animations
β βββ page.tsx # π Dashboard β invoice list + stats
β βββ invoices/
β β βββ page.tsx # Redirects β /
β β βββ new/
β β β βββ page.tsx # β Create Invoice form (live pricing preview)
β β βββ [id]/
β β βββ page.tsx # π Invoice detail view (full premium UI)
β βββ api/
β βββ invoices/
β β βββ route.ts # GET list / POST create
β β βββ stats/route.ts # GET dashboard stats
β β βββ [id]/
β β βββ route.ts # GET single / PATCH update
β β βββ resend-email/
β β βββ route.ts # POST resend invoice email
β βββ currency/
β β βββ route.ts # GET exchange rates + live pricing preview
β βββ razorpay/
β βββ create-link/
β β βββ route.ts # POST regenerate payment link
β βββ webhook/
β βββ route.ts # POST Razorpay webhook (mark PAID)
βββ lib/
β βββ db.ts # Prisma singleton
β βββ pricing.ts # Base prices, fee logic, calculator, formatter
β βββ currency.ts # Countryβcurrency map, exchange rate fetcher
β βββ razorpay.ts # Payment link creation + webhook verification
β βββ email.ts # Resend integration + HTML email templates
βββ types/
βββ index.ts # Shared TypeScript types
- Node.js 18+
- PostgreSQL (local or hosted β Supabase/Neon free tier works perfectly)
- npm or pnpm
cd ripple-nexus
npm install.env.local is already populated with your live Razorpay keys:
RAZORPAY_KEY_ID=rzp_live_SajWG4jNWIcHmU
RAZORPAY_KEY_SECRET=f8yvo1nUfNfNdi3V7dsLc1TF
You still need to fill in:
# Your PostgreSQL connection string
DATABASE_URL=postgresql://user:password@host:5432/ripple_nexus
# Resend email API key β get free at https://resend.com
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Update this after setting webhook in Razorpay dashboard
RAZORPAY_WEBHOOK_SECRET=your_webhook_secret
# For production, set your actual domain
NEXT_PUBLIC_APP_URL=https://invoices.ripplenexus.comnpx prisma generate # Generate Prisma client
npx prisma db push # Push schema to your PostgreSQL
# OR for migrations:
npx prisma migrate dev --name initnpm run dev
# β http://localhost:3000| Service | Key | Status |
|---|---|---|
| Razorpay Key ID | rzp_live_SajWG4jNWIcHmU |
β Live |
| Razorpay Secret | f8yvo1nUfNfNdi3V7dsLc1TF |
β Live |
| Resend | β | |
| Exchange Rate API | β | β Uses free fallback (open.er-api.com) |
β οΈ Security: Never commit.env.localto Git. It's in.gitignoreby default.
- Log in to Razorpay Dashboard
- Go to Settings β Webhooks β Add New Webhook
- Set URL:
https://your-domain.com/api/razorpay/webhook - Select events:
payment_link.paid,payment_link.expired - Copy the Webhook Secret β paste into
.env.localasRAZORPAY_WEBHOOK_SECRET
For local testing, use ngrok:
ngrok http 3000
# Use the https URL as your webhook URL- Sign up at resend.com (free: 3,000 emails/month)
- Add and verify your domain (
ripplenexus.com) - Create an API key β paste as
RESEND_API_KEY - Set
FROM_EMAIL=invoices@ripplenexus.com
- Auto-detection: Country selected β currency auto-mapped (40+ countries)
- Manual override: Admin can type any ISO 4217 code (e.g.
AED,SGD) - Exchange rates: Fetched live from
open.er-api.com(no key needed) orexchangerate-api.com(optional key for higher limits) - Cache: Rates cached in memory for 6 hours to avoid excessive API calls
- INR payments: 2% processing fee
- International: 3.5% processing fee
- Locked on creation: Currency cannot change after invoice is generated
| Client Type | Resume (INR) | LinkedIn (INR) | Cover Letter |
|---|---|---|---|
| Fresher | βΉ1,499 | βΉ999 | FREE |
| Mid-Career | βΉ1,999 | βΉ1,299 | FREE |
| Executive | βΉ3,499 | βΉ1,999 | FREE |
| Executive Plus | βΉ4,999 | βΉ2,499 | FREE |
All prices converted to client's local currency at live exchange rates.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/invoices |
List all invoices (filters: status, clientType) |
| POST | /api/invoices |
Create new invoice |
| GET | /api/invoices/:id |
Get single invoice |
| PATCH | /api/invoices/:id |
Update invoice |
| POST | /api/invoices/:id/resend-email |
Resend invoice email |
| GET | /api/invoices/stats |
Dashboard statistics |
| GET | /api/currency |
Get exchange rates + live pricing preview |
| POST | /api/razorpay/create-link |
(Re)create Razorpay payment link |
| POST | /api/razorpay/webhook |
Razorpay webhook (mark paid) |
model Invoice {
id String # cuid
invoiceNumber String # e.g. RN-2404-8371
clientName/Email/Phone/Type/Country
currency String # ISO 4217, LOCKED on create
currencySymbol String
exchangeRate Float # INRβcurrency rate at creation
resumeBaseInr / linkedinBaseInr / coverLetterBaseInr
resumeConverted / linkedinConverted / coverLetterConverted
subtotalConverted / processingFeeRate / processingFeeConverted
totalPayable Float # Final amount in client currency
status PENDING|PAID|CANCELLED|EXPIRED
razorpayLinkId / razorpayLinkUrl / razorpayPaymentId
paidAt DateTime?
emailSentAt / emailResendCount
invoiceDate / dueDate (7 days)
}npm install -g vercel
vercel --prodAdd all .env.local variables in Vercel dashboard under Settings β Environment Variables.
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
EXPOSE 3000
CMD ["npm", "start"]- Supabase (free PostgreSQL, great DX): supabase.com
- Neon (serverless PostgreSQL, generous free tier): neon.tech
- PlanetScale (MySQL-compatible): planetscale.com
- Razorpay webhook signature verified via HMAC-SHA256
- Currency locked on invoice creation β cannot be changed
- Razorpay amount matches invoice exactly (smallest unit)
- Input validation via Zod on all POST routes
- Admin-only access via middleware + login
- Rate limit invoice creation endpoint
- Add CORS headers for production
This app is private by default: all routes (including /api/*) are protected by middleware.ts, and access is granted only after signing in at /login.
Set these in production:
ADMIN_PASSWORD=your_admin_password
ADMIN_SESSION_SECRET=your_long_random_secretPrisma client not found
npx prisma generateExchange rate API failing The system automatically falls back to hardcoded approximate rates. For production, get a free key at exchangerate-api.com.
Razorpay "Currency not supported" Some currencies are not supported by Razorpay international payments. The system will create the payment link β if Razorpay rejects it, fall back to INR or USD.
Emails not sending
- Verify Resend API key is correct
- Confirm your sending domain is verified in Resend dashboard
- Check
FROM_EMAILdomain matches your verified domain
Built for Ripple Nexus internal use.
Razorpay Live Key: rzp_live_SajWG4jNWIcHmU