Back to Home
Payments & GST
Processor abstraction for Stripe and Razorpay with auto-routing, webhooks, and GST invoicing.
PropelKit uses a processor abstraction that lets you switch between Stripe and Razorpay (or use both with auto-routing) without changing your application code.
Architecture
Terminal
Your Code
|
v
getPaymentProcessor(currency?) <-- Factory function
|
|--- currency === 'INR' && razorpay available → RazorpayProcessor
|--- currency === 'USD' && stripe available → StripeProcessor
|--- fallback to configured default
v
PaymentProcessor interface
|
|-- createCheckoutSession()
|-- cancelSubscription()
|-- upgradeSubscription()
|-- fetchCustomerPortalUrl() (Stripe only)
|-- handleWebhook()Configuration
Set in src/config/features.ts:
TypeScript
paymentProcessor: 'stripe' // International (USD, EUR, GBP)
paymentProcessor: 'razorpay' // India (INR, UPI)
paymentProcessor: 'both' // Auto-routes: INR → Razorpay, USD → StripeStripe
Checkout flow:
- Server creates a Checkout Session with processor.createCheckoutSession()
- User is redirected to checkout.stripe.com
- After payment, Stripe calls your webhook
- Webhook handler creates/updates subscription in database
- User is redirected to success page
Customer Portal
Stripe supports self-service billing management. Users can update payment methods, cancel subscriptions, and view invoices through Stripe's hosted portal.
TypeScript
const portalUrl = await processor.fetchCustomerPortalUrl({ tenantContext });
redirect(portalUrl);Stripe Webhook Events
- checkout.session.completed — Payment successful
- invoice.paid — Recurring charge received
- customer.subscription.updated — Plan changed
- customer.subscription.deleted — Subscription cancelled
- payment_intent.payment_failed — Payment failed
Razorpay
Checkout flow:
- Server creates a subscription with processor.createCheckoutSession()
- Returns razorpaySubscriptionId and razorpayKey
- Client opens Razorpay checkout widget
- User pays (supports UPI, cards, net banking, wallets)
- Razorpay calls your webhook
- Webhook handler creates/updates subscription in database
Razorpay Webhook Events
- payment.authorized / payment.captured — Payment received
- payment.failed — Payment failed
- subscription.activated / subscription.charged — Subscription events
- subscription.cancelled / subscription.completed — Subscription ended
Customer Storage
Payment processor customer IDs are stored on profiles (single-user) or organizations (multi-tenant):
SQL
processor_customer_id -- The Stripe/Razorpay customer ID
payment_processor -- 'stripe' | 'razorpay'GST & Invoicing
India-compliant GST calculation and PDF invoice generation. Enable with features.gst: true.
GST Calculation
GST calculation example
TypeScript
import { calculateGST, validateGSTIN } from '@/lib/gst/gst-calculator';
const result = calculateGST(100000, { // Amount in paise
gstin: customerGstin,
billingStateCode: 'KA',
});
// Returns:
// {
// baseAmount: 100000,
// gstAmount: 18000,
// totalAmount: 118000,
// cgst: 9000, // Intra-state (same state as company)
// sgst: 9000, // Intra-state
// igst: null, // Inter-state would be 18000 here
// isInterState: false,
// }
// Validate GSTIN format
validateGSTIN('27AAAPA5055K1Z5'); // trueGST Rules
- Intra-state (customer in same state as company): CGST 9% + SGST 9%
- Inter-state (customer in different state): IGST 18%
- State code is extracted from the first 2 digits of the GSTIN
Invoice Generation
Invoice generation
TypeScript
import { generateInvoicePDF } from '@/lib/gst/invoice-generator';
const pdfBuffer = await generateInvoicePDF({
invoiceNumber: 'INV-2601-ABC123',
invoiceDate: new Date(),
customerName: 'John Doe',
customerEmail: 'john@example.com',
customerGSTIN: '27AAAPA5055K1Z5',
items: [{ description: 'Pro Plan', quantity: 1, unitPrice: 49900, amount: 49900 }],
baseAmount: 49900,
});Company GST information is read from brand.company in src/config/brand.ts.