razorpay — overview
The first production payment gateway provider — UPI intent/checkout, dynamic QR, cards, and payment links, for India clinics. Community-style module, installable/removable. Issue #263, PR 1 of #365.
What it is
The thin vendor "wire" under the gateway-adapter architecture (ADR 0022, mirroring ADR 0016):
RazorpayAdapter(adapter.py) implementspayment_gateways.adapters.GatewayAdapterand registers intopayment_gateways.adapters.gateway_registryfromon_activate()(never at import time). Deliversupi/qr/card/payment_link.client.py— thinhttpxREST client (no SDK dependency), HTTP Basic Authkey_id:key_secret, mirroringwhatsapp_kapso's/ verifactu's own client pattern.- Public
/webhook/{clinic_id}— Razorpay's signed webhook, verified by a per-clinic HMAC secret; the clinic is resolved by the URL path segment (only ever used to pick which secret to verify against, never trusted for tenancy on its own). - Settings: per-clinic credentials (mode test/live is derived from the key prefix, never chosen — #482) (Fernet-encrypted), webhook secret, active flag, webhook health (last received/processed, last event type, last error).
All lifecycle/ledger logic (PaymentRequest/GatewayRefundRequest state machine, calling into payments.workflow) lives in payment_gateways. This module owns no ledger state — only its own razorpay_settings table.
Data model
razorpay_settings— one row per clinic:mode,key_id(plaintext — meant for the browser),key_secret_encrypted,webhook_secret_encrypted,is_active,is_verified, webhook health fields.
Module boundary
manifest.depends = ["payment_gateways"] — nothing else. Patient contact info for checkout prefill is resolved by payment_gateways.service (which has patients in its own depends) and handed to this module's adapter as GatewayCustomerInfo; this module never imports patients/budget/payments directly. Enforced by tests/test_module_isolation.py.
Method mapping
constants.RAZORPAY_METHOD_MAP translates Razorpay's own payment.entity.method values onto core payments.PAYMENT_METHODS: upi→upi, card→card, netbanking→netbanking, emi→card, wallet→other, paylater→other, anything unrecognized → other. The requested rail (which checkout flow was started) is only a hint; the confirmed method always comes from what Razorpay reports.