Entity Relationship Diagram
Domain Ownership
Auth owns
users — creates row on first login
otp_sessions — created on trigger, invalidated on verify
user_sessions — one per device login, revoked on logout
User Profile reads/writes
users — profile fields only (first_name, last_name, email, dob, gender, address, occupation, employer)
- Cannot write:
phone, aadhaar (identity fields owned by Auth)
Wallet owns
customer_wallets — one per user, creation status tracked here
payment_orders — co-owned with Insurance for the payment session
Insurance owns
insurance_plans — seeded catalog from Narayana Health
insurance_policies — purchased policies
policy_dependants — family members under a policy
PII Storage Policy
These rules are non-negotiable and enforced at the application layer.
| Field | Storage Rule | API Response |
|---|
phone | Stored as +91XXXXXXXXXX | Returned as-is |
aadhaar | Last 4 digits only stored in DB | Returned as XXXX-XXXX-1234 |
refresh_token | SHA-256 hash stored — never plaintext | Never returned |
otp | Hash stored — never plaintext | Never returned |
email | Plaintext, optional | Returned if set |
| Wallet responses | Full Juspay JSON stored as JSONB blob for debugging | Internal only |
Wallet Creation State Machine
Payment Order Lifecycle
| Status | Triggered By |
|---|
CREATED | App calls /nh/payment/session |
SESSION_CREATED | Backend gets sdk_payload from Juspay |
PENDING | App polling, Juspay not yet terminal |
SUCCESS | Juspay returns CHARGED (insurance) or wallet.topup.status = SUCCESS |
FAILED | Juspay returns FAILED |
CANCELLED | User cancels in SDK |