SentinelScan-WSS / docs /RAZORPAY_SETUP.md
grateful790's picture
Add Razorpay checkout with platform fee and INR/USD pricing
335a9ba
|
Raw History Blame Contribute Delete
5.59 kB
# 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 <https://dashboard.razorpay.com/signup>.
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://<your-space>.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/<tier_id>` 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 |