# Razorpay Payments Setup (India + International) LarShield pricing uses **Razorpay** for one-time plan purchases: - **INR** checkout for Indian customers (UPI, cards, netbanking, wallets) - **USD** checkout for international customers (international cards) - A **platform fee** is added on top of the plan price at checkout ## 1. What the customer pays | Line item | Calculation | |---|---| | Service price | Plan price from `subscription_tiers` | | Platform fee | `PLATFORM_FEE_PERCENT` (default **2%**) of the service price | | GST | `PLATFORM_FEE_GST_PERCENT` (default **18%**) of the platform fee, **INR only** | | Total | Service + platform fee + GST | Example (Quick Scan, INR): ``` Service price Rs.499.00 Platform fee (2%) Rs. 9.98 GST (18%) Rs. 1.80 Total payable Rs.510.78 ``` USD checkouts add the 2% platform fee only (no Indian GST). All amounts are recalculated server-side in minor units (paise/cents). The browser never sends an amount. ## 2. Create the Razorpay account 1. Sign up at . 2. Complete KYC (business PAN, bank account) to move from Test to Live. 3. **Enable international payments**: Dashboard -> Account & Settings -> Payment methods -> International payments -> Request activation. 4. **Enable UPI / wallets / netbanking** under Payment methods if they are not already on. ## 3. Get the API keys Dashboard -> Account & Settings -> API Keys -> Generate Key. You get: ``` Key ID rzp_test_xxxxxxxxxxxx (or rzp_live_...) Key Secret xxxxxxxxxxxxxxxxxxxxxxxx ``` Store the **Key ID** and **Key Secret** only on the server (HF Secrets). The Key ID is sent to the browser as part of the order response; the Secret is never exposed. ## 4. Configure the webhook Dashboard -> Account & Settings -> Webhooks -> Add New Webhook. - **URL**: `https://.hf.space/api/billing/webhook` - **Secret**: choose a strong random string, save it as `RAZORPAY_WEBHOOK_SECRET` - **Active events**: - `payment.captured` - `order.paid` - `payment.failed` The webhook is the safety net: if the customer closes the tab after paying, the webhook still activates the plan. Fulfilment is idempotent (same payment is never credited twice). ## 5. Environment variables Set these in **Hugging Face Space -> Settings -> Secrets** (or in `.env` locally): ```env RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxxxx RAZORPAY_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx RAZORPAY_WEBHOOK_SECRET=whsec_your_random_secret PLATFORM_FEE_PERCENT=2 PLATFORM_FEE_GST_PERCENT=18 USD_TO_INR_RATE=88 ``` Then **Factory Rebuild** the Space. ## 6. How the flow works ``` Pricing card "Get Started" | v POST /api/billing/quote -> service + fee + GST + total (displayed in modal) | v POST /api/billing/create-order -> Razorpay order created, pending Payment row saved | v Razorpay Checkout (UPI / card / netbanking / wallet / international card) | v handler -> POST /api/billing/verify-payment - HMAC SHA256 signature check - amount + currency + order id confirmed with Razorpay - organization tier upgraded - +3 scan credits added - audit log written | v Success modal and page reload Fallback: Razorpay webhook (payment.captured / order.paid) performs the same activation if the browser never returns. ``` ## 7. Test cards Razorpay test mode: | Method | Details | |---|---| | Card (success) | `4111 1111 1111 1111`, any future expiry, CVV `123` | | Card (failure) | `5104 0600 0000 0008` | | UPI (success) | `success@razorpay` | | UPI (failure) | `failure@razorpay` | Test mode does not move real money. ## 8. What gets written to the database `payments` row: | Column | Meaning | |---|---| | `amount` | Total charged (service + fee + GST), minor units | | `base_amount` | Service price, minor units | | `platform_fee` | Platform fee, minor units | | `tax_amount` | GST, minor units | | `currency` | `INR` or `USD` | | `razorpay_order_id` | Razorpay order id | | `razorpay_payment_id` | Razorpay payment id (unique - idempotency) | | `status` | `created` -> `successful` / `failed` | | `tier_id` | `quick` / `advanced` / `deep` | On success the app also: - sets `organizations.subscription_tier` to the purchased tier - adds `+3` to `organization_scan_quotas` for that scan type - writes an `audit_logs` entry ## 9. Admin controls Super Admin -> Dashboard -> **Dynamic Billing Control** edits USD and INR prices. The API is `PUT /api/billing/tiers/` and accepts: ```json { "monthly_price": 499, "yearly_price": 4990, "monthly_price_inr": 49900, "yearly_price_inr": 499000 } ``` All values are minor units. ## 10. Troubleshooting | Symptom | Cause / fix | |---|---| | `Payment gateway is not configured yet` | `RAZORPAY_KEY_ID` / `RAZORPAY_KEY_SECRET` missing in Space Secrets, or Space not rebuilt | | `Could not start the payment` | Invalid keys, or the Razorpay account is not activated for that currency | | International cards fail | International payments not enabled in Razorpay Dashboard | | Payment succeeds but plan not upgraded | Webhook URL/secret wrong, or `verify-payment` never reached. Open Razorpay Dashboard -> Payments, copy `pay_...` id, then contact support; the audit log will show whether fulfilment ran | | `Invalid payment signature` | Wrong `RAZORPAY_KEY_SECRET` for the key id used to create the order | | Old INR price shown | Update the tier in Super Admin -> Dynamic Billing Control |