generated from vignesh/searchengine
docs: add Cashfree payment gateway documentation and integration guides
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 41s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 41s
This commit is contained in:
parent
30576a9c6e
commit
befd83ad53
24 changed files with 3578 additions and 0 deletions
134
document/cashfree/01_overview_and_quickstart.md
Normal file
134
document/cashfree/01_overview_and_quickstart.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Cashfree Payments - Overview & Quickstart Guide
|
||||
|
||||
## 1. Introduction
|
||||
Cashfree Payments is a full-stack payments and API banking platform in India providing payment gateway, automated payouts, identity verification (Secure ID), and settlement infrastructure.
|
||||
|
||||
The core service offerings include:
|
||||
1. **Payment Gateway (PG)**: Multi-channel online payment collection supporting 120+ payment modes (UPI, Cards, Netbanking, Wallets, EMI, Pay Later).
|
||||
2. **Payouts**: Bulk and instant bank/UPI disbursements (IMPS, NEFT, RTGS, UPI) 24x7x365.
|
||||
3. **Secure ID**: KYC, Bank account penny-drop verification, UPI VPA verification, PAN, Aadhaar, GSTIN validation.
|
||||
4. **Auto-Collect / Virtual Accounts**: Dedicated virtual accounts (VBA) and virtual UPI IDs to reconcile B2B/inward payments automatically.
|
||||
|
||||
---
|
||||
|
||||
## 2. Platform Architecture & Environments
|
||||
|
||||
Cashfree operates two distinct environments:
|
||||
|
||||
| Environment | Base URL (Payment Gateway) | Base URL (Payouts) | Purpose |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Sandbox (Test)** | `https://sandbox.cashfree.com/pg` | `https://payout-api.cashfree.com/payout/v1` (test mode) | Testing workflows, simulated cards & UPI |
|
||||
| **Production (Live)** | `https://api.cashfree.com/pg` | `https://payout-api.cashfree.com/payout/v1` | Real financial transactions & settlements |
|
||||
|
||||
### Dashboard URLs
|
||||
- **Sandbox Merchant Dashboard**: `https://merchant.cashfree.com/merchants/test`
|
||||
- **Production Merchant Dashboard**: `https://merchant.cashfree.com`
|
||||
|
||||
---
|
||||
|
||||
## 3. High-Level Payment Gateway Lifecycle
|
||||
|
||||
The complete order-to-settlement lifecycle follows an asynchronous, event-driven pattern:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Customer as Customer / User
|
||||
participant Frontend as Tradhox Frontend (Web / App)
|
||||
participant Backend as Tradhox Payment Service
|
||||
participant Cashfree as Cashfree Payment Gateway
|
||||
participant Bank as Issuing Bank / UPI Network
|
||||
|
||||
Customer->>Frontend: Click "Pay Now" at Checkout
|
||||
Frontend->>Backend: Request Order Creation
|
||||
Backend->>Cashfree: POST /orders (x-client-id, x-client-secret)
|
||||
Cashfree-->>Backend: Return cf_order_id & payment_session_id
|
||||
Backend-->>Frontend: Return payment_session_id
|
||||
Frontend->>Cashfree: Render Cashfree Checkout SDK / Drop-in
|
||||
Customer->>Cashfree: Selects UPI / Card / Netbanking & Authorizes
|
||||
Cashfree->>Bank: Process Payment
|
||||
Bank-->>Cashfree: Success confirmation
|
||||
Cashfree-->>Frontend: Return payment status & redirect to return_url
|
||||
Cashfree->>Backend: POST Webhook (PAYMENT_SUCCESS_WEBHOOK with signature)
|
||||
Backend->>Backend: Verify HMAC-SHA256 signature & Update DB
|
||||
Backend-->>Cashfree: 200 OK Response
|
||||
Backend->>Cashfree: GET /orders/{order_id} (Server-to-Server reconciliation)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Concepts
|
||||
|
||||
### Order (`order_id`)
|
||||
- Every transaction begins by generating an `order_id` in your system.
|
||||
- An order contains the order amount, customer details, expiry time, return URL, and notification webhook URL.
|
||||
- Once created, Cashfree returns a `payment_session_id`.
|
||||
|
||||
### Payment Session (`payment_session_id`)
|
||||
- A secure, short-lived session token used by frontend SDKs (Cashfree JS, React, Flutter, Android, iOS) to securely render payment methods without exposing secret keys.
|
||||
|
||||
### Idempotency
|
||||
- Protect critical endpoints like Order creation and Refund creation using `x-idempotency-key` to ensure duplicate network retries do not duplicate charges.
|
||||
|
||||
### Webhooks
|
||||
- Asynchronous notifications sent directly from Cashfree to your server when payment, refund, or settlement events occur. Webhooks are cryptographically signed with HMAC-SHA256.
|
||||
|
||||
---
|
||||
|
||||
## 5. Quickstart Step-by-Step
|
||||
|
||||
### Step 1: Obtain API Keys
|
||||
1. Log in to the [Cashfree Merchant Dashboard](https://merchant.cashfree.com).
|
||||
2. Switch to **Test Environment** (Sandbox).
|
||||
3. Navigate to **Developers** -> **API Keys**.
|
||||
4. Generate **App ID** (`x-client-id`) and **Secret Key** (`x-client-secret`).
|
||||
|
||||
### Step 2: Make Your First API Call
|
||||
Create an order using cURL:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://sandbox.cashfree.com/pg/orders \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'x-api-version: 2023-08-01' \
|
||||
--header 'x-client-id: <YOUR_APP_ID>' \
|
||||
--header 'x-client-secret: <YOUR_SECRET_KEY>' \
|
||||
--data '{
|
||||
"order_id": "order_test_1001",
|
||||
"order_amount": 100.00,
|
||||
"order_currency": "INR",
|
||||
"customer_details": {
|
||||
"customer_id": "cust_101",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9999999999",
|
||||
"customer_name": "Vignesh"
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Step 3: Response Structure
|
||||
```json
|
||||
{
|
||||
"cf_order_id": 14958231,
|
||||
"order_id": "order_test_1001",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 100.00,
|
||||
"order_status": "ACTIVE",
|
||||
"payment_session_id": "session_test_xyz123...",
|
||||
"order_expiry_time": "2026-10-18T21:30:00Z",
|
||||
"created_at": "2026-10-18T21:00:00Z",
|
||||
"customer_details": {
|
||||
"customer_id": "cust_101",
|
||||
"customer_name": "Vignesh",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9999999999"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The frontend SDK uses this `payment_session_id` to initiate checkout.
|
||||
96
document/cashfree/02_authentication_and_headers.md
Normal file
96
document/cashfree/02_authentication_and_headers.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Cashfree Payments - Authentication, Headers & Security
|
||||
|
||||
## 1. Authentication Model
|
||||
|
||||
Cashfree Payment Gateway uses API key-based authentication passed via HTTP headers for all server-to-server REST calls.
|
||||
|
||||
### Required Authentication Headers
|
||||
|
||||
| Header | Type | Description | Example |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `x-client-id` | String | Your unique Merchant App ID obtained from Dashboard | `TEST103848...` or `103848...` |
|
||||
| `x-client-secret` | String | Your private Merchant Secret Key (keep confidential) | `cfsk_ma_test_abc123...` |
|
||||
| `x-api-version` | String | Cashfree API version to ensure backward compatibility | `2023-08-01` (Recommended) |
|
||||
| `Content-Type` | String | Content encoding (JSON) | `application/json` |
|
||||
|
||||
> [!CAUTION]
|
||||
> Never expose `x-client-secret` in frontend applications (browser JavaScript, mobile apps). All calls containing credentials must originate from your secure backend (such as Tradhox Payment Service).
|
||||
|
||||
---
|
||||
|
||||
## 2. Optional & Operational Headers
|
||||
|
||||
| Header | Description | Use Case |
|
||||
| :--- | :--- | :--- |
|
||||
| `x-idempotency-key` | Unique string (e.g. UUIDv4) generated by caller | Prevents duplicate processing of payments, orders, or refunds during network retries |
|
||||
| `x-request-id` | Tracing identifier | Correlation ID for debugging issues with Cashfree customer support |
|
||||
|
||||
### Idempotency Flow
|
||||
When an API request includes an `x-idempotency-key`:
|
||||
1. If the request succeeds, Cashfree caches the response against the key.
|
||||
2. If the request times out or disconnects and you retry with the **same** idempotency key, Cashfree returns the cached response rather than executing a second charge or refund.
|
||||
3. If the payload differs with the same key, Cashfree returns an `idempotency_conflict` HTTP 409 error.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rate Limits & Throttling
|
||||
|
||||
Cashfree enforces rate limits per endpoint category to ensure system stability.
|
||||
|
||||
### Throttling Response Headers
|
||||
|
||||
Cashfree returns the following headers on API responses:
|
||||
|
||||
```http
|
||||
x-ratelimit-limit: 120
|
||||
x-ratelimit-remaining: 119
|
||||
x-ratelimit-retry-after: 60
|
||||
```
|
||||
|
||||
- When the limit is exceeded, Cashfree responds with HTTP `429 Too Many Requests`.
|
||||
- Standard limits:
|
||||
- **Orders API**: 120 requests/minute (Sandbox), 1000+ requests/minute (Production).
|
||||
- **Refunds API**: 60 requests/minute.
|
||||
- **Verification / Secure ID**: Based on plan quota.
|
||||
|
||||
### Handling 429 Errors in Python / Django
|
||||
Implement exponential backoff when detecting HTTP 429:
|
||||
```python
|
||||
import time
|
||||
import requests
|
||||
|
||||
def call_cashfree_with_retry(method, url, headers, json_data=None, max_retries=3):
|
||||
retries = 0
|
||||
backoff = 1.0 # seconds
|
||||
while retries < max_retries:
|
||||
response = requests.request(method, url, headers=headers, json=json_data, timeout=10)
|
||||
if response.status_code == 429:
|
||||
retry_after = int(response.headers.get('x-ratelimit-retry-after', backoff))
|
||||
time.sleep(retry_after)
|
||||
retries += 1
|
||||
backoff *= 2
|
||||
continue
|
||||
return response
|
||||
return response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API Versioning
|
||||
Cashfree uses date-based versioning. Currently supported versions:
|
||||
- `2023-08-01` (Latest stable - recommended for Tradhox)
|
||||
- `2022-09-01`
|
||||
- `2021-05-21` (Legacy)
|
||||
|
||||
Specifying `x-api-version: 2023-08-01` ensures responses follow the modern schema including `payment_session_id`, `customer_details`, and enhanced webhook payloads.
|
||||
|
||||
---
|
||||
|
||||
## 5. Security & Compliance Checklist
|
||||
|
||||
1. **IP Whitelisting**:
|
||||
- In Production, configure your server public IPs (e.g. EC2 NAT Gateway or Elastic IP) in **Cashfree Dashboard -> Developers -> IP Whitelist**.
|
||||
2. **Webhook Signature Verification**:
|
||||
- Never trust incoming webhooks without verifying the `x-webhook-signature` using HMAC-SHA256 and checking the timestamp to prevent replay attacks.
|
||||
3. **Data Protection & RBI Tokenization**:
|
||||
- Do not store raw customer Credit/Debit card numbers (PAN) or CVVs on your servers. Use Cashfree's RBI-compliant Token Vault (Card-on-File Tokenization).
|
||||
191
document/cashfree/03_orders_api.md
Normal file
191
document/cashfree/03_orders_api.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# Cashfree Payments - Orders API Specification
|
||||
|
||||
## 1. Overview
|
||||
The Orders API is the starting point of every payment workflow in Cashfree. An order represents an intent to collect payment from a customer for a specific amount.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Order
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders
|
||||
```
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg/orders`
|
||||
- **Production**: `https://api.cashfree.com/pg/orders`
|
||||
|
||||
### Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <APP_ID>
|
||||
x-client-secret: <SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4> (Recommended)
|
||||
```
|
||||
|
||||
### Request Payload Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `order_id` | String | **Yes** | Unique merchant order ID. Alphanumeric, underscores, hyphens (3-50 chars). |
|
||||
| `order_amount` | Number | **Yes** | Total payable amount up to 2 decimal places (e.g. `249.00`). |
|
||||
| `order_currency` | String | **Yes** | 3-letter ISO currency code (`INR`, `USD`, `EUR`, etc.). |
|
||||
| `customer_details` | Object | **Yes** | Customer identification object. |
|
||||
| ↳ `customer_id` | String | **Yes** | Unique customer identifier in your system (e.g. `user_10294`). |
|
||||
| ↳ `customer_phone` | String | **Yes** | 10-digit mobile number for Indian transactions. |
|
||||
| ↳ `customer_email` | String | **Yes** | Customer email address. |
|
||||
| ↳ `customer_name` | String | No | Full name of the customer. |
|
||||
| `order_meta` | Object | No | Meta information controlling redirect and notifications. |
|
||||
| ↳ `return_url` | String | No | URL to redirect customer after payment. Supports `{order_id}` placeholder. |
|
||||
| ↳ `notify_url` | String | No | Webhook URL for server-to-server notifications. |
|
||||
| ↳ `payment_methods` | String | No | Restrict allowed payment modes (e.g. `upi,cc,dc,nb`). |
|
||||
| `order_expiry_time` | String | No | Expiration timestamp in ISO 8601 UTC format. |
|
||||
| `order_note` | String | No | Brief note describing the transaction. |
|
||||
| `order_tags` | Object | No | Custom key-value pairs for metadata and analytics. |
|
||||
|
||||
### Sample Request
|
||||
```json
|
||||
{
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"order_amount": 1499.00,
|
||||
"order_currency": "INR",
|
||||
"customer_details": {
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/",
|
||||
"payment_methods": "upi,cc,dc,nb,wallet"
|
||||
},
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"order_note": "Subscription payment for Tradhox Pro Tier"
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Response (`201 Created`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 1499.00,
|
||||
"order_status": "ACTIVE",
|
||||
"payment_session_id": "session_a1b2c3d4e5f6g7h8_sandbox",
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"created_at": "2026-10-18T21:05:00Z",
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/",
|
||||
"payment_methods": null
|
||||
},
|
||||
"customer_details": {
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Order Statuses (`order_status`)
|
||||
|
||||
| Status | Description | Action Required |
|
||||
| :--- | :--- | :--- |
|
||||
| `ACTIVE` | Order is open and waiting for customer payment | Customer can attempt payment using `payment_session_id` |
|
||||
| `PAID` | Payment successfully captured | Fulfill order / update order status to completed |
|
||||
| `EXPIRED` | Order passed its `order_expiry_time` without payment | Customer must initiate a new checkout |
|
||||
| `TERMINATED` | Order was cancelled by merchant using Terminate API | No further payments accepted |
|
||||
|
||||
---
|
||||
|
||||
## 4. Get Order Details
|
||||
|
||||
Use this endpoint to verify an order's status server-side after return or webhook arrival.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 1499.00,
|
||||
"order_status": "PAID",
|
||||
"payment_session_id": null,
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"created_at": "2026-10-18T21:05:00Z",
|
||||
"order_splits": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Get Payments for an Order
|
||||
|
||||
Retrieves all payment attempts associated with an order, including failed, user-dropped, and successful attempts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/payments
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cf_payment_id": "1948572019",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "payment",
|
||||
"payment_currency": "INR",
|
||||
"payment_amount": 1499.00,
|
||||
"payment_time": "2026-10-18T21:06:12Z",
|
||||
"payment_completion_time": "2026-10-18T21:06:18Z",
|
||||
"payment_status": "SUCCESS",
|
||||
"payment_message": "Transaction Successful",
|
||||
"bank_reference": "629184719283",
|
||||
"auth_id": null,
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent",
|
||||
"upi_id": "user@okaxis"
|
||||
}
|
||||
},
|
||||
"payment_group": "upi"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Payment Statuses (`payment_status`)
|
||||
- `SUCCESS`: Payment captured successfully.
|
||||
- `FAILED`: Payment declined by bank or user entered wrong credentials.
|
||||
- `PENDING`: Waiting for bank processing (common in Netbanking / UPI collect).
|
||||
- `USER_DROPPED`: Customer cancelled or closed payment window without completing.
|
||||
|
||||
---
|
||||
|
||||
## 6. Terminate an Order
|
||||
|
||||
Marks an unpaid `ACTIVE` order as `TERMINATED`, preventing any further payment attempts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
PATCH /orders/{order_id}
|
||||
```
|
||||
|
||||
### Request Payload
|
||||
```json
|
||||
{
|
||||
"order_status": "TERMINATED"
|
||||
}
|
||||
```
|
||||
232
document/cashfree/04_payment_methods_and_checkout.md
Normal file
232
document/cashfree/04_payment_methods_and_checkout.md
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
# Cashfree Payments - Checkout Integrations & Payment Methods
|
||||
|
||||
## 1. Checkout Integration Models
|
||||
|
||||
Cashfree provides 3 integration approaches depending on control vs. effort:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Checkout Approach] --> B[1. Hosted Checkout]
|
||||
A --> C[2. Drop-in JS SDK]
|
||||
A --> D[3. Seamless / Custom API]
|
||||
|
||||
B -->|Fastest| E[Redirect to Cashfree Hosted Page]
|
||||
C -->|Recommended| F[Embed Cashfree Modal or Inline UI on your site]
|
||||
D -->|Full UI Control| G[Direct Server/App API calls with custom frontend forms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Approach 1: Hosted Checkout (Redirect Flow)
|
||||
|
||||
The fastest way to accept payments. After backend generates `payment_session_id`, redirect the user to Cashfree's hosted page:
|
||||
|
||||
### Redirect URL Format
|
||||
- **Sandbox**: `https://payments-test.cashfree.com/order/#<payment_session_id>`
|
||||
- **Production**: `https://payments.cashfree.com/order/#<payment_session_id>`
|
||||
|
||||
### Example Redirect
|
||||
```python
|
||||
# Django view example
|
||||
from django.shortcuts import redirect
|
||||
|
||||
def start_payment_view(request):
|
||||
session_id = create_cashfree_order_session(...)
|
||||
checkout_url = f"https://payments-test.cashfree.com/order/#{session_id}"
|
||||
return redirect(checkout_url)
|
||||
```
|
||||
|
||||
Once payment completes, Cashfree redirects the customer back to your configured `return_url`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Approach 2: Cashfree Drop-in Web SDK (v3)
|
||||
|
||||
The recommended integration for modern web apps (React, Next.js, Vue, or Vanilla JS). Renders an in-page modal or iframe with zero PCI-DSS compliance overhead.
|
||||
|
||||
### Step 1: Add Cashfree JS Script
|
||||
```html
|
||||
<!-- Production -->
|
||||
<script src="https://sdk.cashfree.com/js/v3/cashfree.js"></script>
|
||||
```
|
||||
|
||||
### Step 2: Initialize and Trigger Checkout
|
||||
```javascript
|
||||
// Initialize SDK with environment mode
|
||||
const cashfree = Cashfree({
|
||||
mode: "sandbox" // "sandbox" or "production"
|
||||
});
|
||||
|
||||
// Trigger checkout with session ID
|
||||
function openCheckout(paymentSessionId) {
|
||||
const checkoutOptions = {
|
||||
paymentSessionId: paymentSessionId,
|
||||
redirectTarget: "_self" // "_self", "_blank", or "_modal"
|
||||
};
|
||||
|
||||
cashfree.checkout(checkoutOptions).then((result) => {
|
||||
if (result.error) {
|
||||
console.error("Payment error or dismiss:", result.error);
|
||||
}
|
||||
if (result.redirect) {
|
||||
console.log("Customer redirected");
|
||||
}
|
||||
if (result.paymentDetails) {
|
||||
console.log("Payment completed:", result.paymentDetails);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Approach 3: Seamless / Headless Payment API
|
||||
|
||||
For custom mobile apps or fully native checkouts where your app collects payment details directly.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders/sessions
|
||||
```
|
||||
Or Order Pay API:
|
||||
```http
|
||||
POST /orders/pay
|
||||
```
|
||||
|
||||
### 1. UPI Payment Modes
|
||||
|
||||
#### A. UPI Intent (Mobile Apps - Google Pay, PhonePe, Paytm chooser)
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns `data.payload.intent` link (`upi://pay?...`) which triggers mobile UPI apps directly.*
|
||||
|
||||
#### B. UPI Collect (VPA Prompt)
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "collect",
|
||||
"upi_id": "customer@okhdfcbank"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Triggers a push notification on the customer's UPI app.*
|
||||
|
||||
#### C. Dynamic UPI QR Code
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "qrcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns a dynamic Base64 encoded QR image and UPI URI for in-store or desktop scanning.*
|
||||
|
||||
---
|
||||
|
||||
### 2. Cards (Credit / Debit)
|
||||
|
||||
Supports Visa, Mastercard, RuPay, American Express, Diners, Maestro.
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"card": {
|
||||
"channel": "link",
|
||||
"card_number": "4012001037141112",
|
||||
"card_holder_name": "Vignesh Tech",
|
||||
"card_expiry_mm": "12",
|
||||
"card_expiry_yy": "28",
|
||||
"card_cvv": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns 3D-Secure ACS authentication URL (`action: "3ds"`, `url: "https://..."`) to redirect customer for OTP verification.*
|
||||
|
||||
#### Saved Card Tokenization (Token Vault / CoFT)
|
||||
To comply with RBI guidelines, card numbers cannot be stored. Cashfree provides Card-on-File Tokenization:
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"card": {
|
||||
"channel": "link",
|
||||
"card_alias": "token_ref_98234",
|
||||
"card_cvv": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Net Banking
|
||||
|
||||
Supports 50+ Indian commercial and public sector banks.
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"netbanking": {
|
||||
"channel": "link",
|
||||
"netbanking_bank_code": 3003
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Major Bank Codes
|
||||
| Bank Name | Bank Code |
|
||||
| :--- | :--- |
|
||||
| **HDFC Bank** | `3003` |
|
||||
| **ICICI Bank** | `3021` |
|
||||
| **State Bank of India (SBI)** | `3044` |
|
||||
| **Axis Bank** | `3001` |
|
||||
| **Kotak Mahindra Bank** | `3032` |
|
||||
| **Punjab National Bank (PNB)** | `3038` |
|
||||
| **Bank of Baroda** | `3005` |
|
||||
| **Canara Bank** | `3011` |
|
||||
|
||||
---
|
||||
|
||||
### 4. Wallets
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"app": {
|
||||
"channel": "link",
|
||||
"provider": "paytm",
|
||||
"phone": "9876543210"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported Wallet Providers:
|
||||
`paytm`, `phonepe`, `mobikwik`, `freecharge`, `airtelmoney`, `olamoney`, `amazonpay`.
|
||||
|
||||
---
|
||||
|
||||
### 5. Pay Later & EMI
|
||||
|
||||
- **Pay Later Providers**: `simpl`, `lazypay`, `zestmoney`.
|
||||
- **Cardless EMI**: `zestmoney`, `earlysalary`, `instacred`.
|
||||
- **Bank EMI**: Available for major credit cards with 3, 6, 9, 12, 18, 24 month tenures.
|
||||
238
document/cashfree/05_webhooks_and_signature_verification.md
Normal file
238
document/cashfree/05_webhooks_and_signature_verification.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# Cashfree Payments - Webhooks & Signature Verification
|
||||
|
||||
## 1. Webhooks Architecture
|
||||
|
||||
Webhooks are automated HTTP POST requests sent from Cashfree to your backend when critical transaction events occur. They serve as the single source of truth for transaction states (such as asynchronous UPI confirmation, Netbanking approval, or 3DS completion).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Never fulfill orders solely based on frontend redirection or query parameters. Always verify the transaction status via Webhooks or the server-to-server `GET /orders/{order_id}` API.
|
||||
|
||||
---
|
||||
|
||||
## 2. Webhook Event Types
|
||||
|
||||
Cashfree emits events categorized by product:
|
||||
|
||||
| Event Type (`type`) | Description |
|
||||
| :--- | :--- |
|
||||
| `PAYMENT_SUCCESS_WEBHOOK` | Customer successfully paid; funds captured. |
|
||||
| `PAYMENT_FAILED_WEBHOOK` | Transaction declined by bank, insufficient funds, or wrong PIN. |
|
||||
| `PAYMENT_USER_DROPPED_WEBHOOK` | Customer exited checkout without completing payment. |
|
||||
| `REFUND_STATUS_WEBHOOK` | Refund status changed (`SUCCESS`, `FAILED`, `CANCELLED`). |
|
||||
| `SETTLEMENT_SUCCESS_WEBHOOK` | Settlement transferred to merchant bank account. |
|
||||
| `DISPUTE_CREATED_WEBHOOK` | Customer or bank raised a chargeback dispute. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Webhook Payload Structure
|
||||
|
||||
### Sample `PAYMENT_SUCCESS_WEBHOOK`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"order": {
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"order_amount": 1499.00,
|
||||
"order_currency": "INR",
|
||||
"order_tags": null
|
||||
},
|
||||
"payment": {
|
||||
"cf_payment_id": "291847101",
|
||||
"payment_status": "SUCCESS",
|
||||
"payment_amount": 1499.00,
|
||||
"payment_currency": "INR",
|
||||
"payment_message": "00::Transaction Success",
|
||||
"payment_time": "2026-10-18T21:06:12Z",
|
||||
"payment_completion_time": "2026-10-18T21:06:18Z",
|
||||
"bank_reference": "629184719283",
|
||||
"auth_id": null,
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent",
|
||||
"upi_id": "user@okaxis"
|
||||
}
|
||||
},
|
||||
"payment_group": "upi"
|
||||
},
|
||||
"customer_details": {
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
}
|
||||
},
|
||||
"event_time": "2026-10-18T21:06:20Z",
|
||||
"type": "PAYMENT_SUCCESS_WEBHOOK"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Cryptographic Signature Verification
|
||||
|
||||
Every Cashfree webhook includes two security headers:
|
||||
- `x-webhook-signature`: Base64 encoded HMAC-SHA256 signature string.
|
||||
- `x-webhook-timestamp`: Epoch timestamp in milliseconds (or ISO format) when webhook was dispatched.
|
||||
|
||||
### Verification Algorithm
|
||||
|
||||
```text
|
||||
1. Read raw request body bytes (do NOT parse or re-serialize JSON before hashing!).
|
||||
2. Concatenate: signed_data = x-webhook-timestamp + raw_body
|
||||
3. Compute HMAC-SHA256 using your Cashfree Secret Key (client_secret).
|
||||
4. Base64 encode the resulting hash.
|
||||
5. Compare the computed signature with x-webhook-signature using timing-safe comparison.
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[x-webhook-timestamp] --> C[Concatenate]
|
||||
B[Raw Request Body Bytes] --> C
|
||||
C --> D[HMAC-SHA256 with Secret Key]
|
||||
D --> E[Base64 Encode]
|
||||
E --> F{Constant-time Comparison with x-webhook-signature}
|
||||
F -->|Match| G[Process Webhook]
|
||||
F -->|Mismatch| H[Reject 400 Bad Request]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Production-Ready Python / Django Implementation
|
||||
|
||||
Save this verification helper in your service (e.g. [`payments/utils.py`](file:///d:/Tradhox_workspace/paymentservice/payments/utils.py)):
|
||||
|
||||
```python
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def verify_cashfree_webhook_signature(
|
||||
raw_body: bytes,
|
||||
signature: str,
|
||||
timestamp: str,
|
||||
secret_key: str,
|
||||
max_drift_seconds: int = 300
|
||||
) -> bool:
|
||||
"""
|
||||
Verifies Cashfree webhook signature using HMAC-SHA256.
|
||||
|
||||
Args:
|
||||
raw_body: Exact raw bytes from request.body
|
||||
signature: Value from x-webhook-signature header
|
||||
timestamp: Value from x-webhook-timestamp header
|
||||
secret_key: Cashfree Merchant Client Secret Key
|
||||
max_drift_seconds: Allowed time difference in seconds (anti-replay)
|
||||
|
||||
Returns:
|
||||
bool: True if signature is authentic and timestamp is fresh, False otherwise
|
||||
"""
|
||||
if not signature or not timestamp or not secret_key:
|
||||
logger.warning("Missing required fields for webhook signature verification")
|
||||
return False
|
||||
|
||||
# 1. Anti-Replay Check (5 minutes threshold)
|
||||
try:
|
||||
ts_val = int(timestamp)
|
||||
# If timestamp is in milliseconds, convert to seconds
|
||||
if ts_val > 10000000000:
|
||||
ts_val = ts_val // 1000
|
||||
current_time = int(time.time())
|
||||
if abs(current_time - ts_val) > max_drift_seconds:
|
||||
logger.warning(f"Webhook timestamp expired: drift={abs(current_time - ts_val)}s")
|
||||
return False
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid timestamp format: {timestamp}")
|
||||
return False
|
||||
|
||||
# 2. Build signed payload: timestamp + raw_body
|
||||
ts_bytes = str(timestamp).encode('utf-8')
|
||||
payload = ts_bytes + raw_body
|
||||
|
||||
# 3. Compute HMAC-SHA256
|
||||
computed_hmac = hmac.new(
|
||||
key=secret_key.encode('utf-8'),
|
||||
msg=payload,
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
|
||||
computed_signature = base64.b64encode(computed_hmac).decode('utf-8')
|
||||
|
||||
# 4. Constant-time comparison
|
||||
is_valid = hmac.compare_digest(computed_signature, signature)
|
||||
if not is_valid:
|
||||
logger.warning("Webhook signature mismatch!")
|
||||
return is_valid
|
||||
```
|
||||
|
||||
### Complete Django Webhook View Example
|
||||
```python
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.conf import settings
|
||||
import json
|
||||
import logging
|
||||
from .utils import verify_cashfree_webhook_signature
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@csrf_exempt
|
||||
def cashfree_webhook_view(request):
|
||||
if request.method != "POST":
|
||||
return HttpResponse(status=405)
|
||||
|
||||
signature = request.headers.get("x-webhook-signature")
|
||||
timestamp = request.headers.get("x-webhook-timestamp")
|
||||
secret_key = getattr(settings, "CASHFREE_SECRET_KEY", "")
|
||||
|
||||
# Verify signature
|
||||
if not verify_cashfree_webhook_signature(request.body, signature, timestamp, secret_key):
|
||||
return JsonResponse({"error": "Invalid signature"}, status=400)
|
||||
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
event_type = payload.get("type")
|
||||
data = payload.get("data", {})
|
||||
|
||||
order_info = data.get("order", {})
|
||||
payment_info = data.get("payment", {})
|
||||
order_id = order_info.get("order_id")
|
||||
|
||||
logger.info(f"Received Cashfree Webhook: {event_type} for order {order_id}")
|
||||
|
||||
if event_type == "PAYMENT_SUCCESS_WEBHOOK":
|
||||
# Idempotent check: Ensure order is not already marked paid
|
||||
# fulfill_order(order_id, payment_info)
|
||||
pass
|
||||
elif event_type == "PAYMENT_FAILED_WEBHOOK":
|
||||
# record_payment_failure(order_id, payment_info)
|
||||
pass
|
||||
elif event_type == "REFUND_STATUS_WEBHOOK":
|
||||
# update_refund_status(data)
|
||||
pass
|
||||
|
||||
# Return 200 OK immediately
|
||||
return JsonResponse({"status": "acknowledged"}, status=200)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing webhook: {e}", exc_info=True)
|
||||
return JsonResponse({"status": "internal_error"}, status=500)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Retries and Delivery Guarantee
|
||||
|
||||
- Cashfree requires your server to respond with HTTP `200 OK` within **5 seconds**.
|
||||
- If your server responds with an error code (5xx, 4xx) or times out, Cashfree retries up to **7 times** with exponential backoff:
|
||||
- 1st retry: after 1 minute
|
||||
- 2nd retry: after 5 minutes
|
||||
- 3rd retry: after 15 minutes
|
||||
- 4th retry: after 30 minutes
|
||||
- 5th retry: after 1 hour
|
||||
- 6th retry: after 2 hours
|
||||
- 7th retry: after 4 hours
|
||||
- **Always design webhook handlers to be strictly idempotent** by verifying against the database before executing side-effects.
|
||||
155
document/cashfree/06_refunds_and_disputes.md
Normal file
155
document/cashfree/06_refunds_and_disputes.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Cashfree Payments - Refunds & Disputes API
|
||||
|
||||
## 1. Refunds Overview
|
||||
|
||||
Cashfree provides programmatic refund capabilities for both full and partial order amounts. Refunds can be initiated directly against any successfully paid order.
|
||||
|
||||
### Refund Speeds
|
||||
1. **Standard Refund (`STANDARD`)**:
|
||||
- Follows regular banking clearinghouse cycles.
|
||||
- Takes 5 to 7 business days to reflect in the customer's source account/card.
|
||||
2. **Instant Refund (`INSTANT`)**:
|
||||
- Uses instant payment rails (UPI / IMPS).
|
||||
- Credits customer account within seconds or minutes.
|
||||
- Requires Instant Refunds to be enabled in Cashfree Dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create a Refund
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders/{order_id}/refunds
|
||||
```
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg/orders/{order_id}/refunds`
|
||||
- **Production**: `https://api.cashfree.com/pg/orders/{order_id}/refunds`
|
||||
|
||||
### Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <APP_ID>
|
||||
x-client-secret: <SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4>
|
||||
```
|
||||
|
||||
### Request Payload Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `refund_amount` | Number | **Yes** | Amount to be refunded. Cannot exceed remaining order balance. |
|
||||
| `refund_id` | String | **Yes** | Unique merchant refund ID (alphanumeric, underscores, hyphens). |
|
||||
| `refund_note` | String | No | Reason or note for the refund (e.g. `Customer requested return`). |
|
||||
| `refund_speed` | String | No | `"STANDARD"` (default) or `"INSTANT"`. |
|
||||
| `refund_splits` | Array | No | For split payments (Easy Split marketplace vendors). |
|
||||
|
||||
### Sample Request
|
||||
```json
|
||||
{
|
||||
"refund_amount": 499.00,
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"refund_note": "Partial return for damaged item",
|
||||
"refund_speed": "STANDARD"
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Response (`201 Created` / `200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_refund_id": "39104812",
|
||||
"cf_payment_id": "291847101",
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "refund",
|
||||
"refund_currency": "INR",
|
||||
"refund_amount": 499.00,
|
||||
"refund_note": "Partial return for damaged item",
|
||||
"refund_status": "SUCCESS",
|
||||
"refund_mode": "STANDARD",
|
||||
"refund_type": "MERCHANT_INITIATED",
|
||||
"refund_arn": "62918401928374",
|
||||
"created_at": "2026-10-18T21:15:00Z",
|
||||
"processed_at": "2026-10-18T21:15:02Z",
|
||||
"status_description": "Refund processed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For Card refunds, `refund_arn` represents the **Acquirer Reference Number (ARN)**. Provide this number to customers so their issuing bank can trace the credited amount.
|
||||
|
||||
---
|
||||
|
||||
## 3. Refund Statuses (`refund_status`)
|
||||
|
||||
| Status | Description | Action Required |
|
||||
| :--- | :--- | :--- |
|
||||
| `SUCCESS` | Refund processed successfully by Cashfree and partner bank. | Update order/refund record as refunded. |
|
||||
| `PENDING` | Refund is under processing with the customer's bank. | Wait for `REFUND_STATUS_WEBHOOK` or poll status. |
|
||||
| `CANCELLED` | Refund was cancelled by merchant before processing. | No funds deducted. |
|
||||
| `FAILED` | Bank declined the refund (e.g. account closed, card expired). | Retry or disburse via Payouts directly. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Get Refund Details
|
||||
|
||||
Fetch details and status of a specific refund.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/refunds/{refund_id}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. List All Refunds for an Order
|
||||
|
||||
Retrieves all refunds (partial or full) issued against an order.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/refunds
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cf_refund_id": "39104812",
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"refund_amount": 499.00,
|
||||
"refund_status": "SUCCESS",
|
||||
"refund_arn": "62918401928374"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Disputes & Chargeback Management
|
||||
|
||||
When a cardholder or account owner disputes a transaction with their bank:
|
||||
|
||||
### Dispute Lifecycle
|
||||
1. **Retrieval Request**: Bank requests transaction proof before escalating.
|
||||
2. **Chargeback / Dispute Created**: Funds temporarily debited; merchant notified via `DISPUTE_CREATED_WEBHOOK`.
|
||||
3. **Contest Phase**: Merchant submits proof (invoice, AWB tracking, delivery signature).
|
||||
4. **Resolution**: Bank or Card Network (Visa/Mastercard) awards dispute to merchant or customer.
|
||||
|
||||
### Dispute Webhook Payload
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"dispute_id": "disp_8291038",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"cf_payment_id": "291847101",
|
||||
"dispute_amount": 1499.00,
|
||||
"dispute_status": "DISPUTE_CREATED",
|
||||
"respond_by": "2026-10-25T23:59:59Z",
|
||||
"reason_code": "4853",
|
||||
"reason_description": "Goods or services not received"
|
||||
},
|
||||
"event_time": "2026-10-18T21:20:00Z",
|
||||
"type": "DISPUTE_CREATED_WEBHOOK"
|
||||
}
|
||||
```
|
||||
88
document/cashfree/07_settlements_and_reconciliation.md
Normal file
88
document/cashfree/07_settlements_and_reconciliation.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Cashfree Payments - Settlements & Reconciliation
|
||||
|
||||
## 1. Settlement Overview
|
||||
|
||||
Settlement is the process where captured customer payments are transferred from Cashfree to your registered business bank account after deducting MDR (Merchant Discount Rate) fees and GST.
|
||||
|
||||
### Standard Settlement Cycle
|
||||
- **Standard (T+1 or T+2 Days)**: Funds captured on Day T are credited to the merchant bank account on the next 1-2 banking business days.
|
||||
- **Instant / On-Demand Settlements**: Allows merchants to trigger immediate settlement transfers 24x7 without waiting for standard banking cutoff windows.
|
||||
|
||||
---
|
||||
|
||||
## 2. Settlement Calculations & Breakdown
|
||||
|
||||
For every transaction, the settlement formula is:
|
||||
|
||||
$$\text{Settlement Amount} = \text{Order Amount} - (\text{Payment Gateway Fee} + \text{GST on Fee}) \pm \text{Adjustments}$$
|
||||
|
||||
### Sample Breakdown
|
||||
- **Order Amount**: ₹1,000.00
|
||||
- **MDR (PG Fee @ 1.9%)**: ₹19.00
|
||||
- **GST on MDR (18%)**: ₹3.42
|
||||
- **Net Credited**: ₹977.58
|
||||
|
||||
---
|
||||
|
||||
## 3. Settlement APIs
|
||||
|
||||
### 1. Get Settlements by Order ID
|
||||
|
||||
Check if and when a specific order was settled.
|
||||
|
||||
```http
|
||||
GET /orders/{order_id}/settlements
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"settlement_currency": "INR",
|
||||
"settlement_amount": 977.58,
|
||||
"service_charge": 19.00,
|
||||
"service_tax": 3.42,
|
||||
"settlement_id": "8291048",
|
||||
"transfer_utr": "HDFCN26101892837",
|
||||
"transfer_time": "2026-10-19T06:30:00Z",
|
||||
"settlement_status": "SUCCESS"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Get All Settlements
|
||||
|
||||
Fetch consolidated batch settlements across a date range.
|
||||
|
||||
```http
|
||||
GET /settlements?start_date=2026-10-01&end_date=2026-10-18&limit=50
|
||||
```
|
||||
|
||||
#### Response Fields
|
||||
- `cf_settlement_id`: Unique identifier for the settlement batch.
|
||||
- `settlement_amount`: Total amount credited to merchant.
|
||||
- `transfer_utr`: Bank UTR (Unique Transaction Reference) for reconciliation against bank statements.
|
||||
- `settlement_status`: `SUCCESS`, `PENDING`, `FAILED`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Settlement Webhooks
|
||||
|
||||
Configure `SETTLEMENT_SUCCESS_WEBHOOK` to receive real-time notification when bank transfer completes.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"settlement_id": "8291048",
|
||||
"settlement_amount": 145892.40,
|
||||
"settlement_currency": "INR",
|
||||
"transfer_utr": "HDFCN26101994827",
|
||||
"transfer_time": "2026-10-19T06:30:00Z",
|
||||
"settlement_status": "SUCCESS"
|
||||
},
|
||||
"event_time": "2026-10-19T06:30:05Z",
|
||||
"type": "SETTLEMENT_SUCCESS_WEBHOOK"
|
||||
}
|
||||
```
|
||||
177
document/cashfree/08_payouts_and_transfers.md
Normal file
177
document/cashfree/08_payouts_and_transfers.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# Cashfree Payouts & Automated Disbursements
|
||||
|
||||
## 1. Overview
|
||||
Cashfree Payouts allows automated disbursements to vendor bank accounts, delivery partners, refunds, affiliate payouts, and seller settlements 24x7x365 across India.
|
||||
|
||||
### Supported Rails
|
||||
- **UPI**: Instant, up to ₹1,00,000 per transaction (or ₹5,00,000 for select categories).
|
||||
- **IMPS**: Instant, available 24x7, up to ₹5,00,000 per transaction.
|
||||
- **NEFT**: Batch settlements throughout the day, no upper limit.
|
||||
- **RTGS**: Real-time gross settlement for high-value transactions (minimum ₹2,00,000).
|
||||
- **Card Payouts**: Direct credits to Visa/Mastercard debit cards.
|
||||
|
||||
---
|
||||
|
||||
## 2. Environments & Base URLs
|
||||
|
||||
| Environment | Base URL |
|
||||
| :--- | :--- |
|
||||
| **Sandbox (Gamma)** | `https://payout-gamma.cashfree.com/payout/v1` |
|
||||
| **Production** | `https://payout-api.cashfree.com/payout/v1` |
|
||||
|
||||
### Authentication
|
||||
Payouts API supports passing credentials in headers or exchanging them for a short-lived bearer token:
|
||||
```http
|
||||
X-Client-Id: <PAYOUT_CLIENT_ID>
|
||||
X-Client-Secret: <PAYOUT_CLIENT_SECRET>
|
||||
```
|
||||
Or via Auth Token:
|
||||
```http
|
||||
POST /payout/v1/authorize
|
||||
Authorization: Bearer <TOKEN>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Beneficiary Management
|
||||
|
||||
Before transferring funds, register the beneficiary (or use quick-transfer APIs).
|
||||
|
||||
### Add Beneficiary
|
||||
```http
|
||||
POST /payout/v1/addBeneficiary
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"beneId": "vendor_vignesh_01",
|
||||
"name": "Vignesh Tech",
|
||||
"email": "vignesh@tradhox.com",
|
||||
"phone": "9876543210",
|
||||
"bankAccount": "50100234567890",
|
||||
"ifsc": "HDFC0001234",
|
||||
"address1": "Tradhox HQ, Tech Park",
|
||||
"city": "Bengaluru",
|
||||
"state": "Karnataka",
|
||||
"pincode": "560001"
|
||||
}
|
||||
```
|
||||
|
||||
*For UPI Disbursements, replace `bankAccount` and `ifsc` with `vpa`:*
|
||||
```json
|
||||
{
|
||||
"beneId": "seller_upi_42",
|
||||
"name": "Seller Partner",
|
||||
"email": "partner@tradhox.com",
|
||||
"phone": "9876543210",
|
||||
"vpa": "partner@okaxis",
|
||||
"address1": "Seller Hub",
|
||||
"city": "Mumbai",
|
||||
"state": "Maharashtra",
|
||||
"pincode": "400001"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Request a Transfer (Disbursement)
|
||||
|
||||
Initiates a payout from your Cashfree account balance to the beneficiary.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /payout/v1/requestTransfer
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"beneId": "vendor_vignesh_01",
|
||||
"amount": "25000.00",
|
||||
"transferMode": "IMPS",
|
||||
"remarks": "Vendor weekly payout settlement"
|
||||
}
|
||||
```
|
||||
|
||||
#### Transfer Modes (`transferMode`):
|
||||
- `IMPS`: Instant bank transfer.
|
||||
- `NEFT`: Standard bank clearing.
|
||||
- `RTGS`: High value bank transfer (>= ₹2,00,000).
|
||||
- `UPI`: Instant transfer to UPI VPA.
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Transfer initiated successfully",
|
||||
"data": {
|
||||
"referenceId": "1948571029",
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"utr": "629184910284",
|
||||
"acknowledged": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Get Transfer Status
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /payout/v1/getTransferStatus?transferId=tradhox_payout_88291
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Transfer details fetched successfully",
|
||||
"data": {
|
||||
"transfer": {
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"referenceId": "1948571029",
|
||||
"status": "SUCCESS",
|
||||
"statusDescription": "Transfer completed successfully",
|
||||
"amount": "25000.00",
|
||||
"utr": "629184910284",
|
||||
"addedOn": "2026-10-18 21:20:00",
|
||||
"processedOn": "2026-10-18 21:20:03"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Payout Statuses
|
||||
- `SUCCESS`: Transfer completed; beneficiary account credited.
|
||||
- `PENDING`: In progress with clearing network (e.g. NEFT window or slow bank response).
|
||||
- `FAILED`: Transfer rejected by beneficiary bank (invalid account, IFSC, or limit reached).
|
||||
- `REVERSED`: Transaction initially cleared but subsequently reversed by receiving bank.
|
||||
|
||||
---
|
||||
|
||||
## 6. Check Account Balance
|
||||
|
||||
Verify available wallet balance before triggering payouts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /payout/v1/getBalance
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Balance fetched successfully",
|
||||
"data": {
|
||||
"balance": "485290.50",
|
||||
"availableBalance": "485290.50"
|
||||
}
|
||||
}
|
||||
```
|
||||
157
document/cashfree/09_secureid_and_kyc_verification.md
Normal file
157
document/cashfree/09_secureid_and_kyc_verification.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Cashfree Secure ID & KYC Verification APIs
|
||||
|
||||
## 1. Overview
|
||||
Cashfree Secure ID provides API-driven identity, bank account, and compliance verification against authoritative Indian databases (NPCI, NSDL, UIDAI, GSTN).
|
||||
|
||||
Common use cases for Tradhox:
|
||||
- Verifying vendor / seller bank accounts before onboarding.
|
||||
- Verifying PAN & GSTIN for marketplace merchant compliance.
|
||||
- Verifying customer UPI IDs to prevent payment failures and fraud.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bank Account Verification (Penny Drop)
|
||||
|
||||
Deposits ₹1.00 into the target bank account via IMPS to verify that the account is active and retrieve the exact registered name.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /verification/bank-account/sync?name={name}&phone={phone}&bankAccount={account}&ifsc={ifsc}
|
||||
```
|
||||
Or via POST:
|
||||
```http
|
||||
POST /verification/bank-account/sync
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"bankAccount": "50100234567890",
|
||||
"ifsc": "HDFC0001234",
|
||||
"name": "Vignesh Tech",
|
||||
"phone": "9876543210"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Bank account verified successfully",
|
||||
"accountStatus": "VALID",
|
||||
"accountStatusCode": "ACCOUNT_IS_VALID",
|
||||
"data": {
|
||||
"nameAtBank": "VIGNESH TECH PRIVATE LIMITED",
|
||||
"refId": "1948571029",
|
||||
"bankName": "HDFC BANK",
|
||||
"branch": "INDIRANAGAR",
|
||||
"city": "BANGALORE",
|
||||
"micr": "560240012",
|
||||
"nameMatchScore": 95,
|
||||
"nameMatchResult": "SUCCESS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> The `nameMatchScore` provides a 0-100 fuzzy matching score comparing the provided name with the official `nameAtBank`.
|
||||
|
||||
---
|
||||
|
||||
## 3. UPI VPA Verification
|
||||
|
||||
Checks whether a UPI ID exists without deducting or transferring money.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/vpa
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"vpa": "vignesh@okhdfcbank",
|
||||
"name": "Vignesh"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "VPA verified successfully",
|
||||
"vpa": "vignesh@okhdfcbank",
|
||||
"accountExists": "YES",
|
||||
"nameAtBank": "Vignesh Tech",
|
||||
"nameMatchScore": 100
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. PAN Verification
|
||||
|
||||
Validates PAN against the Income Tax Department / NSDL database.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/pan
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234F",
|
||||
"name": "Vignesh Tech"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "VALID",
|
||||
"pan": "ABCDE1234F",
|
||||
"type": "Individual",
|
||||
"registered_name": "VIGNESH TECH",
|
||||
"name_match_score": 100,
|
||||
"aadhaar_seeding_status": "LINKED"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. GSTIN Verification
|
||||
|
||||
Validates Goods and Services Tax Identification Number.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/gstin
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"gstin": "29ABCDE1234F1Z5"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "VALID",
|
||||
"gstin": "29ABCDE1234F1Z5",
|
||||
"legal_name": "TRADHOX TECHNOLOGIES PRIVATE LIMITED",
|
||||
"trade_name": "TRADHOX",
|
||||
"registration_date": "2024-01-15",
|
||||
"gstin_status": "Active",
|
||||
"taxpayer_type": "Regular",
|
||||
"principal_place_of_business": {
|
||||
"address": "Tech Park, Outer Ring Road",
|
||||
"state": "Karnataka",
|
||||
"pincode": "560103"
|
||||
}
|
||||
}
|
||||
```
|
||||
78
document/cashfree/10_error_codes_and_test_data.md
Normal file
78
document/cashfree/10_error_codes_and_test_data.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Cashfree Payments - Error Codes & Sandbox Test Data
|
||||
|
||||
## 1. Error Response Structure
|
||||
|
||||
All Cashfree Payment Gateway APIs return consistent JSON error objects when a request fails:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Order with id 'tradhox_ord_987654' is already paid",
|
||||
"code": "order_already_paid",
|
||||
"type": "invalid_request_error"
|
||||
}
|
||||
```
|
||||
|
||||
### Top-Level Error Types (`type`)
|
||||
|
||||
| Error Type | HTTP Status | Meaning |
|
||||
| :--- | :--- | :--- |
|
||||
| `invalid_request_error` | `400`, `404`, `409` | Malformed body, missing required parameters, invalid IDs, or invalid state transitions. |
|
||||
| `authentication_error` | `401` | Invalid or missing `x-client-id`, `x-client-secret`, or expired tokens. |
|
||||
| `rate_limit_error` | `429` | Request volume exceeded allowed threshold. Implement backoff. |
|
||||
| `api_error` | `500`, `502`, `503` | Temporary server-side issue at Cashfree or partner bank. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Common API Error Codes
|
||||
|
||||
| Error Code (`code`) | HTTP Status | Description & Recommended Handling |
|
||||
| :--- | :--- | :--- |
|
||||
| `order_already_paid` | 400 | The order has already been successfully paid. Do not prompt user again; fetch order status. |
|
||||
| `order_expired` | 400 | The order has passed its `order_expiry_time`. Generate a new order. |
|
||||
| `order_terminated` | 400 | The order was marked as terminated. |
|
||||
| `order_not_found` | 404 | No order exists matching the provided `order_id`. Verify ID spelling. |
|
||||
| `duplicate_order_id` | 409 | An order with this `order_id` already exists. Use a unique order ID generator. |
|
||||
| `idempotency_conflict` | 409 | The same `x-idempotency-key` was submitted with a different request payload. |
|
||||
| `invalid_customer_details` | 400 | Phone number is not 10 digits or email format is invalid. |
|
||||
| `payment_declined` | 400 | Customer's issuing bank declined the card or UPI payment. |
|
||||
| `insufficient_balance` | 400 | Insufficient balance in merchant account to process instant refund or payout. |
|
||||
| `unauthorized_ip` | 401 | Calling server IP is not in your Merchant Dashboard IP Whitelist. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Sandbox Test Credentials & Simulators
|
||||
|
||||
When testing in the **Sandbox** environment (`https://sandbox.cashfree.com/pg`):
|
||||
|
||||
### 1. Test Cards
|
||||
|
||||
| Card Type | Card Number | Expiry | CVV | Expected Result |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Visa (Success)** | `4012 0010 3714 1112` | Any future date (e.g. `12/28`) | `123` | **SUCCESS** (3DS OTP: `123456`) |
|
||||
| **Mastercard (Success)** | `5123 4567 8901 2346` | Any future date | `123` | **SUCCESS** |
|
||||
| **RuPay (Success)** | `6071 2345 6789 0123` | Any future date | `123` | **SUCCESS** |
|
||||
| **Card Declined** | `4012 0010 3714 1113` | Any future date | `123` | **FAILED** (`payment_declined`) |
|
||||
| **Insufficient Funds** | `4012 0010 3714 1114` | Any future date | `123` | **FAILED** (`insufficient_funds`) |
|
||||
| **3DS Timeout** | `4012 0010 3714 1115` | Any future date | `123` | **FAILED** (`user_dropped`) |
|
||||
|
||||
#### Test 3D-Secure OTP
|
||||
On the simulated bank OTP page, enter `123456` or click the **Success** button.
|
||||
|
||||
---
|
||||
|
||||
### 2. Test UPI Handles
|
||||
|
||||
| VPA / UPI ID | Action / Behaviour |
|
||||
| :--- | :--- |
|
||||
| `success@upi` | Auto-approves payment; triggers instant `PAYMENT_SUCCESS_WEBHOOK`. |
|
||||
| `failure@upi` | Auto-declines payment; returns bank refusal. |
|
||||
| `test@okhdfcbank` | Simulates UPI Intent and Collect prompt. |
|
||||
|
||||
---
|
||||
|
||||
### 3. Test Netbanking
|
||||
|
||||
Select any major bank (e.g. HDFC, ICICI, SBI) in Sandbox Checkout. Cashfree displays a mock banking portal with options:
|
||||
- **Success**: Simulates customer approving netbanking login and OTP.
|
||||
- **Fail**: Simulates bank session timeout or incorrect credentials.
|
||||
- **Pending**: Simulates delayed bank settlement.
|
||||
197
document/cashfree/11_django_integration_blueprint.md
Normal file
197
document/cashfree/11_django_integration_blueprint.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# Cashfree Payments - Django Microservice Architecture Blueprint
|
||||
|
||||
## 1. Architecture Overview for Tradhox Payment Service
|
||||
|
||||
This blueprint outlines the recommended production architecture for integrating Cashfree Payment Gateway into the `paymentservice` Django app.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Tradhox Customer Website / App] -->|1. Request Order| B[POST /api/payments/orders/create/]
|
||||
B -->|2. Create Order via SDK| C[Cashfree API Gateway]
|
||||
C -->|3. Return payment_session_id| B
|
||||
B -->|4. Return session_id| A
|
||||
A -->|5. Render Cashfree Checkout| D[Customer Completes Payment]
|
||||
D -->|6. Direct S2S Webhook| E[POST /api/payments/webhooks/cashfree/]
|
||||
E -->|7. Verify HMAC-SHA256| F[Update Database & Emit Events]
|
||||
A -->|8. Redirect on Return| G[GET /api/payments/orders/status/]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Django Settings Configuration
|
||||
|
||||
Add the following to [`config/settings.py`](file:///d:/Tradhox_workspace/paymentservice/config/settings.py):
|
||||
|
||||
```python
|
||||
# Cashfree Payment Gateway Configuration
|
||||
CASHFREE_APP_ID = os.environ.get('CASHFREE_APP_ID', 'TEST103848...')
|
||||
CASHFREE_SECRET_KEY = os.environ.get('CASHFREE_SECRET_KEY', 'cfsk_ma_test_...')
|
||||
CASHFREE_ENVIRONMENT = os.environ.get('CASHFREE_ENVIRONMENT', 'sandbox') # 'sandbox' or 'production'
|
||||
CASHFREE_API_VERSION = os.environ.get('CASHFREE_API_VERSION', '2023-08-01')
|
||||
|
||||
if CASHFREE_ENVIRONMENT == 'production':
|
||||
CASHFREE_BASE_URL = 'https://api.cashfree.com/pg'
|
||||
else:
|
||||
CASHFREE_BASE_URL = 'https://sandbox.cashfree.com/pg'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Cashfree Client Class
|
||||
|
||||
Implement [`payments/cashfree_client.py`](file:///d:/Tradhox_workspace/paymentservice/payments/cashfree_client.py):
|
||||
|
||||
```python
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import requests
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CashfreeClient:
|
||||
def __init__(self):
|
||||
self.app_id = settings.CASHFREE_APP_ID
|
||||
self.secret_key = settings.CASHFREE_SECRET_KEY
|
||||
self.base_url = settings.CASHFREE_BASE_URL
|
||||
self.api_version = settings.CASHFREE_API_VERSION
|
||||
|
||||
def _get_headers(self, idempotency_key=None):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-version': self.api_version,
|
||||
'x-client-id': self.app_id,
|
||||
'x-client-secret': self.secret_key,
|
||||
}
|
||||
if idempotency_key:
|
||||
headers['x-idempotency-key'] = idempotency_key
|
||||
return headers
|
||||
|
||||
def create_order(self, order_id, amount, currency, customer, return_url, notify_url=None, note=None):
|
||||
url = f"{self.base_url}/orders"
|
||||
payload = {
|
||||
"order_id": str(order_id),
|
||||
"order_amount": float(amount),
|
||||
"order_currency": currency,
|
||||
"customer_details": {
|
||||
"customer_id": str(customer.get("id")),
|
||||
"customer_name": customer.get("name", "Customer"),
|
||||
"customer_email": customer.get("email"),
|
||||
"customer_phone": str(customer.get("phone")),
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": return_url,
|
||||
"notify_url": notify_url or return_url
|
||||
}
|
||||
}
|
||||
if note:
|
||||
payload["order_note"] = note
|
||||
|
||||
response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def get_order(self, order_id):
|
||||
url = f"{self.base_url}/orders/{order_id}"
|
||||
response = requests.get(url, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def create_refund(self, order_id, refund_id, amount, note="Refund", speed="STANDARD"):
|
||||
url = f"{self.base_url}/orders/{order_id}/refunds"
|
||||
payload = {
|
||||
"refund_id": str(refund_id),
|
||||
"refund_amount": float(amount),
|
||||
"refund_note": note,
|
||||
"refund_speed": speed
|
||||
}
|
||||
response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def verify_webhook_signature(self, raw_body: bytes, signature: str, timestamp: str) -> bool:
|
||||
if not signature or not timestamp or not self.secret_key:
|
||||
return False
|
||||
|
||||
try:
|
||||
ts_val = int(timestamp)
|
||||
if ts_val > 10000000000:
|
||||
ts_val = ts_val // 1000
|
||||
if abs(int(time.time()) - ts_val) > 300:
|
||||
logger.warning("Webhook timestamp older than 5 minutes")
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
payload = str(timestamp).encode('utf-8') + raw_body
|
||||
computed_hmac = hmac.new(
|
||||
key=self.secret_key.encode('utf-8'),
|
||||
msg=payload,
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
computed_sig = base64.b64encode(computed_hmac).decode('utf-8')
|
||||
return hmac.compare_digest(computed_sig, signature)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Suggested Database Models
|
||||
|
||||
Add to [`payments/models.py`](file:///d:/Tradhox_workspace/paymentservice/payments/models.py):
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class PaymentOrder(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('ACTIVE', 'Active'),
|
||||
('PAID', 'Paid'),
|
||||
('EXPIRED', 'Expired'),
|
||||
('TERMINATED', 'Terminated'),
|
||||
]
|
||||
|
||||
order_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
cf_order_id = models.CharField(max_length=64, blank=True, null=True)
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
currency = models.CharField(max_length=3, default='INR')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE')
|
||||
customer_id = models.CharField(max_length=64)
|
||||
customer_email = models.EmailField()
|
||||
customer_phone = models.CharField(max_length=15)
|
||||
payment_session_id = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('SUCCESS', 'Success'),
|
||||
('FAILED', 'Failed'),
|
||||
('PENDING', 'Pending'),
|
||||
('USER_DROPPED', 'User Dropped'),
|
||||
]
|
||||
|
||||
cf_payment_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='transactions')
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
|
||||
payment_method = models.CharField(max_length=50, blank=True, null=True)
|
||||
bank_reference = models.CharField(max_length=100, blank=True, null=True)
|
||||
payment_time = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class PaymentRefund(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('SUCCESS', 'Success'),
|
||||
('PENDING', 'Pending'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('FAILED', 'Failed'),
|
||||
]
|
||||
|
||||
refund_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
cf_refund_id = models.CharField(max_length=64, blank=True, null=True)
|
||||
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='refunds')
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
|
||||
refund_arn = models.CharField(max_length=64, blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
46
document/cashfree/README.md
Normal file
46
document/cashfree/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Cashfree Knowledge Base
|
||||
|
||||
Complete end-to-end documentation for integrating and managing Cashfree across the Tradhox Payments Microservice (`paymentservice`).
|
||||
|
||||
---
|
||||
|
||||
## Documentation Index
|
||||
|
||||
| Module | Document | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **01** | [Overview & Quickstart](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/01_overview_and_quickstart.md) | Platform overview, environment endpoints (Sandbox & Production), architecture sequence diagram, and first API call. |
|
||||
| **02** | [Authentication, Headers & Security](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/02_authentication_and_headers.md) | API key credentials (`x-client-id`, `x-client-secret`), versioning (`2023-08-01`), idempotency, rate limiting, and security compliance. |
|
||||
| **03** | [Orders API Specification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/03_orders_api.md) | Create Order (`POST /orders`), order lifecycle (`ACTIVE`, `PAID`, `EXPIRED`, `TERMINATED`), Get Order status, and payments lookup. |
|
||||
| **04** | [Checkout Integrations & Payment Methods](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/04_payment_methods_and_checkout.md) | Hosted Checkout (redirect), Drop-in JS SDK v3, and Seamless API for UPI (Intent, Collect, QR), Cards (Token Vault), Netbanking, and Wallets. |
|
||||
| **05** | [Webhooks & Signature Verification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/05_webhooks_and_signature_verification.md) | Event schemas, HMAC-SHA256 signature verification algorithm, anti-replay timestamp validation, and production Django webhook view. |
|
||||
| **06** | [Refunds & Disputes API](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/06_refunds_and_disputes.md) | Full/Partial refunds (`POST /orders/{order_id}/refunds`), Standard vs Instant refunds, ARN tracking, and chargeback dispute workflows. |
|
||||
| **07** | [Settlements & Reconciliation](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/07_settlements_and_reconciliation.md) | T+1/T+2 banking settlement cycles, MDR fee & tax calculations, Order-level settlement API, UTR tracking, and settlement webhooks. |
|
||||
| **08** | [Payouts & Automated Disbursements](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/08_payouts_and_transfers.md) | 24x7 automated disbursements via UPI, IMPS, NEFT, RTGS, Beneficiary onboarding, transfer status tracking, and balance inquiries. |
|
||||
| **09** | [Secure ID & KYC Verification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/09_secureid_and_kyc_verification.md) | Bank Account Penny Drop verification, UPI VPA verification, PAN validation, and GSTIN lookup for merchant & vendor compliance. |
|
||||
| **10** | [Error Codes & Sandbox Test Data](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/10_error_codes_and_test_data.md) | Standard error formats, common error codes dictionary, test Visa/Mastercard/RuPay card numbers, test UPI handles, and simulated netbanking. |
|
||||
| **11** | [Django Integration Blueprint](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/11_django_integration_blueprint.md) | Production Python `CashfreeClient`, Django models (`PaymentOrder`, `PaymentTransaction`, `PaymentRefund`), settings configuration, and API views. |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Summary
|
||||
|
||||
### Base URLs
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg`
|
||||
- **Production**: `https://api.cashfree.com/pg`
|
||||
|
||||
### Standard Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <CASHFREE_APP_ID>
|
||||
x-client-secret: <CASHFREE_SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4>
|
||||
```
|
||||
|
||||
### Webhook Verification Formula
|
||||
```text
|
||||
payload = x-webhook-timestamp + raw_body
|
||||
computed_hmac = HMAC_SHA256(secret_key, payload)
|
||||
computed_signature = Base64Encode(computed_hmac)
|
||||
valid = (computed_signature == x-webhook-signature)
|
||||
```
|
||||
134
documentation/cashfree/01_overview_and_quickstart.md
Normal file
134
documentation/cashfree/01_overview_and_quickstart.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Cashfree Payments - Overview & Quickstart Guide
|
||||
|
||||
## 1. Introduction
|
||||
Cashfree Payments is a full-stack payments and API banking platform in India providing payment gateway, automated payouts, identity verification (Secure ID), and settlement infrastructure.
|
||||
|
||||
The core service offerings include:
|
||||
1. **Payment Gateway (PG)**: Multi-channel online payment collection supporting 120+ payment modes (UPI, Cards, Netbanking, Wallets, EMI, Pay Later).
|
||||
2. **Payouts**: Bulk and instant bank/UPI disbursements (IMPS, NEFT, RTGS, UPI) 24x7x365.
|
||||
3. **Secure ID**: KYC, Bank account penny-drop verification, UPI VPA verification, PAN, Aadhaar, GSTIN validation.
|
||||
4. **Auto-Collect / Virtual Accounts**: Dedicated virtual accounts (VBA) and virtual UPI IDs to reconcile B2B/inward payments automatically.
|
||||
|
||||
---
|
||||
|
||||
## 2. Platform Architecture & Environments
|
||||
|
||||
Cashfree operates two distinct environments:
|
||||
|
||||
| Environment | Base URL (Payment Gateway) | Base URL (Payouts) | Purpose |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Sandbox (Test)** | `https://sandbox.cashfree.com/pg` | `https://payout-api.cashfree.com/payout/v1` (test mode) | Testing workflows, simulated cards & UPI |
|
||||
| **Production (Live)** | `https://api.cashfree.com/pg` | `https://payout-api.cashfree.com/payout/v1` | Real financial transactions & settlements |
|
||||
|
||||
### Dashboard URLs
|
||||
- **Sandbox Merchant Dashboard**: `https://merchant.cashfree.com/merchants/test`
|
||||
- **Production Merchant Dashboard**: `https://merchant.cashfree.com`
|
||||
|
||||
---
|
||||
|
||||
## 3. High-Level Payment Gateway Lifecycle
|
||||
|
||||
The complete order-to-settlement lifecycle follows an asynchronous, event-driven pattern:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Customer as Customer / User
|
||||
participant Frontend as Tradhox Frontend (Web / App)
|
||||
participant Backend as Tradhox Payment Service
|
||||
participant Cashfree as Cashfree Payment Gateway
|
||||
participant Bank as Issuing Bank / UPI Network
|
||||
|
||||
Customer->>Frontend: Click "Pay Now" at Checkout
|
||||
Frontend->>Backend: Request Order Creation
|
||||
Backend->>Cashfree: POST /orders (x-client-id, x-client-secret)
|
||||
Cashfree-->>Backend: Return cf_order_id & payment_session_id
|
||||
Backend-->>Frontend: Return payment_session_id
|
||||
Frontend->>Cashfree: Render Cashfree Checkout SDK / Drop-in
|
||||
Customer->>Cashfree: Selects UPI / Card / Netbanking & Authorizes
|
||||
Cashfree->>Bank: Process Payment
|
||||
Bank-->>Cashfree: Success confirmation
|
||||
Cashfree-->>Frontend: Return payment status & redirect to return_url
|
||||
Cashfree->>Backend: POST Webhook (PAYMENT_SUCCESS_WEBHOOK with signature)
|
||||
Backend->>Backend: Verify HMAC-SHA256 signature & Update DB
|
||||
Backend-->>Cashfree: 200 OK Response
|
||||
Backend->>Cashfree: GET /orders/{order_id} (Server-to-Server reconciliation)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Concepts
|
||||
|
||||
### Order (`order_id`)
|
||||
- Every transaction begins by generating an `order_id` in your system.
|
||||
- An order contains the order amount, customer details, expiry time, return URL, and notification webhook URL.
|
||||
- Once created, Cashfree returns a `payment_session_id`.
|
||||
|
||||
### Payment Session (`payment_session_id`)
|
||||
- A secure, short-lived session token used by frontend SDKs (Cashfree JS, React, Flutter, Android, iOS) to securely render payment methods without exposing secret keys.
|
||||
|
||||
### Idempotency
|
||||
- Protect critical endpoints like Order creation and Refund creation using `x-idempotency-key` to ensure duplicate network retries do not duplicate charges.
|
||||
|
||||
### Webhooks
|
||||
- Asynchronous notifications sent directly from Cashfree to your server when payment, refund, or settlement events occur. Webhooks are cryptographically signed with HMAC-SHA256.
|
||||
|
||||
---
|
||||
|
||||
## 5. Quickstart Step-by-Step
|
||||
|
||||
### Step 1: Obtain API Keys
|
||||
1. Log in to the [Cashfree Merchant Dashboard](https://merchant.cashfree.com).
|
||||
2. Switch to **Test Environment** (Sandbox).
|
||||
3. Navigate to **Developers** -> **API Keys**.
|
||||
4. Generate **App ID** (`x-client-id`) and **Secret Key** (`x-client-secret`).
|
||||
|
||||
### Step 2: Make Your First API Call
|
||||
Create an order using cURL:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://sandbox.cashfree.com/pg/orders \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'x-api-version: 2023-08-01' \
|
||||
--header 'x-client-id: <YOUR_APP_ID>' \
|
||||
--header 'x-client-secret: <YOUR_SECRET_KEY>' \
|
||||
--data '{
|
||||
"order_id": "order_test_1001",
|
||||
"order_amount": 100.00,
|
||||
"order_currency": "INR",
|
||||
"customer_details": {
|
||||
"customer_id": "cust_101",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9999999999",
|
||||
"customer_name": "Vignesh"
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Step 3: Response Structure
|
||||
```json
|
||||
{
|
||||
"cf_order_id": 14958231,
|
||||
"order_id": "order_test_1001",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 100.00,
|
||||
"order_status": "ACTIVE",
|
||||
"payment_session_id": "session_test_xyz123...",
|
||||
"order_expiry_time": "2026-10-18T21:30:00Z",
|
||||
"created_at": "2026-10-18T21:00:00Z",
|
||||
"customer_details": {
|
||||
"customer_id": "cust_101",
|
||||
"customer_name": "Vignesh",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9999999999"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The frontend SDK uses this `payment_session_id` to initiate checkout.
|
||||
96
documentation/cashfree/02_authentication_and_headers.md
Normal file
96
documentation/cashfree/02_authentication_and_headers.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Cashfree Payments - Authentication, Headers & Security
|
||||
|
||||
## 1. Authentication Model
|
||||
|
||||
Cashfree Payment Gateway uses API key-based authentication passed via HTTP headers for all server-to-server REST calls.
|
||||
|
||||
### Required Authentication Headers
|
||||
|
||||
| Header | Type | Description | Example |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `x-client-id` | String | Your unique Merchant App ID obtained from Dashboard | `TEST103848...` or `103848...` |
|
||||
| `x-client-secret` | String | Your private Merchant Secret Key (keep confidential) | `cfsk_ma_test_abc123...` |
|
||||
| `x-api-version` | String | Cashfree API version to ensure backward compatibility | `2023-08-01` (Recommended) |
|
||||
| `Content-Type` | String | Content encoding (JSON) | `application/json` |
|
||||
|
||||
> [!CAUTION]
|
||||
> Never expose `x-client-secret` in frontend applications (browser JavaScript, mobile apps). All calls containing credentials must originate from your secure backend (such as Tradhox Payment Service).
|
||||
|
||||
---
|
||||
|
||||
## 2. Optional & Operational Headers
|
||||
|
||||
| Header | Description | Use Case |
|
||||
| :--- | :--- | :--- |
|
||||
| `x-idempotency-key` | Unique string (e.g. UUIDv4) generated by caller | Prevents duplicate processing of payments, orders, or refunds during network retries |
|
||||
| `x-request-id` | Tracing identifier | Correlation ID for debugging issues with Cashfree customer support |
|
||||
|
||||
### Idempotency Flow
|
||||
When an API request includes an `x-idempotency-key`:
|
||||
1. If the request succeeds, Cashfree caches the response against the key.
|
||||
2. If the request times out or disconnects and you retry with the **same** idempotency key, Cashfree returns the cached response rather than executing a second charge or refund.
|
||||
3. If the payload differs with the same key, Cashfree returns an `idempotency_conflict` HTTP 409 error.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rate Limits & Throttling
|
||||
|
||||
Cashfree enforces rate limits per endpoint category to ensure system stability.
|
||||
|
||||
### Throttling Response Headers
|
||||
|
||||
Cashfree returns the following headers on API responses:
|
||||
|
||||
```http
|
||||
x-ratelimit-limit: 120
|
||||
x-ratelimit-remaining: 119
|
||||
x-ratelimit-retry-after: 60
|
||||
```
|
||||
|
||||
- When the limit is exceeded, Cashfree responds with HTTP `429 Too Many Requests`.
|
||||
- Standard limits:
|
||||
- **Orders API**: 120 requests/minute (Sandbox), 1000+ requests/minute (Production).
|
||||
- **Refunds API**: 60 requests/minute.
|
||||
- **Verification / Secure ID**: Based on plan quota.
|
||||
|
||||
### Handling 429 Errors in Python / Django
|
||||
Implement exponential backoff when detecting HTTP 429:
|
||||
```python
|
||||
import time
|
||||
import requests
|
||||
|
||||
def call_cashfree_with_retry(method, url, headers, json_data=None, max_retries=3):
|
||||
retries = 0
|
||||
backoff = 1.0 # seconds
|
||||
while retries < max_retries:
|
||||
response = requests.request(method, url, headers=headers, json=json_data, timeout=10)
|
||||
if response.status_code == 429:
|
||||
retry_after = int(response.headers.get('x-ratelimit-retry-after', backoff))
|
||||
time.sleep(retry_after)
|
||||
retries += 1
|
||||
backoff *= 2
|
||||
continue
|
||||
return response
|
||||
return response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API Versioning
|
||||
Cashfree uses date-based versioning. Currently supported versions:
|
||||
- `2023-08-01` (Latest stable - recommended for Tradhox)
|
||||
- `2022-09-01`
|
||||
- `2021-05-21` (Legacy)
|
||||
|
||||
Specifying `x-api-version: 2023-08-01` ensures responses follow the modern schema including `payment_session_id`, `customer_details`, and enhanced webhook payloads.
|
||||
|
||||
---
|
||||
|
||||
## 5. Security & Compliance Checklist
|
||||
|
||||
1. **IP Whitelisting**:
|
||||
- In Production, configure your server public IPs (e.g. EC2 NAT Gateway or Elastic IP) in **Cashfree Dashboard -> Developers -> IP Whitelist**.
|
||||
2. **Webhook Signature Verification**:
|
||||
- Never trust incoming webhooks without verifying the `x-webhook-signature` using HMAC-SHA256 and checking the timestamp to prevent replay attacks.
|
||||
3. **Data Protection & RBI Tokenization**:
|
||||
- Do not store raw customer Credit/Debit card numbers (PAN) or CVVs on your servers. Use Cashfree's RBI-compliant Token Vault (Card-on-File Tokenization).
|
||||
191
documentation/cashfree/03_orders_api.md
Normal file
191
documentation/cashfree/03_orders_api.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# Cashfree Payments - Orders API Specification
|
||||
|
||||
## 1. Overview
|
||||
The Orders API is the starting point of every payment workflow in Cashfree. An order represents an intent to collect payment from a customer for a specific amount.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Order
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders
|
||||
```
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg/orders`
|
||||
- **Production**: `https://api.cashfree.com/pg/orders`
|
||||
|
||||
### Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <APP_ID>
|
||||
x-client-secret: <SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4> (Recommended)
|
||||
```
|
||||
|
||||
### Request Payload Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `order_id` | String | **Yes** | Unique merchant order ID. Alphanumeric, underscores, hyphens (3-50 chars). |
|
||||
| `order_amount` | Number | **Yes** | Total payable amount up to 2 decimal places (e.g. `249.00`). |
|
||||
| `order_currency` | String | **Yes** | 3-letter ISO currency code (`INR`, `USD`, `EUR`, etc.). |
|
||||
| `customer_details` | Object | **Yes** | Customer identification object. |
|
||||
| ↳ `customer_id` | String | **Yes** | Unique customer identifier in your system (e.g. `user_10294`). |
|
||||
| ↳ `customer_phone` | String | **Yes** | 10-digit mobile number for Indian transactions. |
|
||||
| ↳ `customer_email` | String | **Yes** | Customer email address. |
|
||||
| ↳ `customer_name` | String | No | Full name of the customer. |
|
||||
| `order_meta` | Object | No | Meta information controlling redirect and notifications. |
|
||||
| ↳ `return_url` | String | No | URL to redirect customer after payment. Supports `{order_id}` placeholder. |
|
||||
| ↳ `notify_url` | String | No | Webhook URL for server-to-server notifications. |
|
||||
| ↳ `payment_methods` | String | No | Restrict allowed payment modes (e.g. `upi,cc,dc,nb`). |
|
||||
| `order_expiry_time` | String | No | Expiration timestamp in ISO 8601 UTC format. |
|
||||
| `order_note` | String | No | Brief note describing the transaction. |
|
||||
| `order_tags` | Object | No | Custom key-value pairs for metadata and analytics. |
|
||||
|
||||
### Sample Request
|
||||
```json
|
||||
{
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"order_amount": 1499.00,
|
||||
"order_currency": "INR",
|
||||
"customer_details": {
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/",
|
||||
"payment_methods": "upi,cc,dc,nb,wallet"
|
||||
},
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"order_note": "Subscription payment for Tradhox Pro Tier"
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Response (`201 Created`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 1499.00,
|
||||
"order_status": "ACTIVE",
|
||||
"payment_session_id": "session_a1b2c3d4e5f6g7h8_sandbox",
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"created_at": "2026-10-18T21:05:00Z",
|
||||
"order_meta": {
|
||||
"return_url": "https://betapayments.tradhox.com/api/payments/return?order_id={order_id}",
|
||||
"notify_url": "https://betapayments.tradhox.com/api/payments/webhooks/",
|
||||
"payment_methods": null
|
||||
},
|
||||
"customer_details": {
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Order Statuses (`order_status`)
|
||||
|
||||
| Status | Description | Action Required |
|
||||
| :--- | :--- | :--- |
|
||||
| `ACTIVE` | Order is open and waiting for customer payment | Customer can attempt payment using `payment_session_id` |
|
||||
| `PAID` | Payment successfully captured | Fulfill order / update order status to completed |
|
||||
| `EXPIRED` | Order passed its `order_expiry_time` without payment | Customer must initiate a new checkout |
|
||||
| `TERMINATED` | Order was cancelled by merchant using Terminate API | No further payments accepted |
|
||||
|
||||
---
|
||||
|
||||
## 4. Get Order Details
|
||||
|
||||
Use this endpoint to verify an order's status server-side after return or webhook arrival.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "order",
|
||||
"order_currency": "INR",
|
||||
"order_amount": 1499.00,
|
||||
"order_status": "PAID",
|
||||
"payment_session_id": null,
|
||||
"order_expiry_time": "2026-10-18T22:30:00Z",
|
||||
"created_at": "2026-10-18T21:05:00Z",
|
||||
"order_splits": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Get Payments for an Order
|
||||
|
||||
Retrieves all payment attempts associated with an order, including failed, user-dropped, and successful attempts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/payments
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cf_payment_id": "1948572019",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "payment",
|
||||
"payment_currency": "INR",
|
||||
"payment_amount": 1499.00,
|
||||
"payment_time": "2026-10-18T21:06:12Z",
|
||||
"payment_completion_time": "2026-10-18T21:06:18Z",
|
||||
"payment_status": "SUCCESS",
|
||||
"payment_message": "Transaction Successful",
|
||||
"bank_reference": "629184719283",
|
||||
"auth_id": null,
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent",
|
||||
"upi_id": "user@okaxis"
|
||||
}
|
||||
},
|
||||
"payment_group": "upi"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Payment Statuses (`payment_status`)
|
||||
- `SUCCESS`: Payment captured successfully.
|
||||
- `FAILED`: Payment declined by bank or user entered wrong credentials.
|
||||
- `PENDING`: Waiting for bank processing (common in Netbanking / UPI collect).
|
||||
- `USER_DROPPED`: Customer cancelled or closed payment window without completing.
|
||||
|
||||
---
|
||||
|
||||
## 6. Terminate an Order
|
||||
|
||||
Marks an unpaid `ACTIVE` order as `TERMINATED`, preventing any further payment attempts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
PATCH /orders/{order_id}
|
||||
```
|
||||
|
||||
### Request Payload
|
||||
```json
|
||||
{
|
||||
"order_status": "TERMINATED"
|
||||
}
|
||||
```
|
||||
232
documentation/cashfree/04_payment_methods_and_checkout.md
Normal file
232
documentation/cashfree/04_payment_methods_and_checkout.md
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
# Cashfree Payments - Checkout Integrations & Payment Methods
|
||||
|
||||
## 1. Checkout Integration Models
|
||||
|
||||
Cashfree provides 3 integration approaches depending on control vs. effort:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Checkout Approach] --> B[1. Hosted Checkout]
|
||||
A --> C[2. Drop-in JS SDK]
|
||||
A --> D[3. Seamless / Custom API]
|
||||
|
||||
B -->|Fastest| E[Redirect to Cashfree Hosted Page]
|
||||
C -->|Recommended| F[Embed Cashfree Modal or Inline UI on your site]
|
||||
D -->|Full UI Control| G[Direct Server/App API calls with custom frontend forms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Approach 1: Hosted Checkout (Redirect Flow)
|
||||
|
||||
The fastest way to accept payments. After backend generates `payment_session_id`, redirect the user to Cashfree's hosted page:
|
||||
|
||||
### Redirect URL Format
|
||||
- **Sandbox**: `https://payments-test.cashfree.com/order/#<payment_session_id>`
|
||||
- **Production**: `https://payments.cashfree.com/order/#<payment_session_id>`
|
||||
|
||||
### Example Redirect
|
||||
```python
|
||||
# Django view example
|
||||
from django.shortcuts import redirect
|
||||
|
||||
def start_payment_view(request):
|
||||
session_id = create_cashfree_order_session(...)
|
||||
checkout_url = f"https://payments-test.cashfree.com/order/#{session_id}"
|
||||
return redirect(checkout_url)
|
||||
```
|
||||
|
||||
Once payment completes, Cashfree redirects the customer back to your configured `return_url`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Approach 2: Cashfree Drop-in Web SDK (v3)
|
||||
|
||||
The recommended integration for modern web apps (React, Next.js, Vue, or Vanilla JS). Renders an in-page modal or iframe with zero PCI-DSS compliance overhead.
|
||||
|
||||
### Step 1: Add Cashfree JS Script
|
||||
```html
|
||||
<!-- Production -->
|
||||
<script src="https://sdk.cashfree.com/js/v3/cashfree.js"></script>
|
||||
```
|
||||
|
||||
### Step 2: Initialize and Trigger Checkout
|
||||
```javascript
|
||||
// Initialize SDK with environment mode
|
||||
const cashfree = Cashfree({
|
||||
mode: "sandbox" // "sandbox" or "production"
|
||||
});
|
||||
|
||||
// Trigger checkout with session ID
|
||||
function openCheckout(paymentSessionId) {
|
||||
const checkoutOptions = {
|
||||
paymentSessionId: paymentSessionId,
|
||||
redirectTarget: "_self" // "_self", "_blank", or "_modal"
|
||||
};
|
||||
|
||||
cashfree.checkout(checkoutOptions).then((result) => {
|
||||
if (result.error) {
|
||||
console.error("Payment error or dismiss:", result.error);
|
||||
}
|
||||
if (result.redirect) {
|
||||
console.log("Customer redirected");
|
||||
}
|
||||
if (result.paymentDetails) {
|
||||
console.log("Payment completed:", result.paymentDetails);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Approach 3: Seamless / Headless Payment API
|
||||
|
||||
For custom mobile apps or fully native checkouts where your app collects payment details directly.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders/sessions
|
||||
```
|
||||
Or Order Pay API:
|
||||
```http
|
||||
POST /orders/pay
|
||||
```
|
||||
|
||||
### 1. UPI Payment Modes
|
||||
|
||||
#### A. UPI Intent (Mobile Apps - Google Pay, PhonePe, Paytm chooser)
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns `data.payload.intent` link (`upi://pay?...`) which triggers mobile UPI apps directly.*
|
||||
|
||||
#### B. UPI Collect (VPA Prompt)
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "collect",
|
||||
"upi_id": "customer@okhdfcbank"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Triggers a push notification on the customer's UPI app.*
|
||||
|
||||
#### C. Dynamic UPI QR Code
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "qrcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns a dynamic Base64 encoded QR image and UPI URI for in-store or desktop scanning.*
|
||||
|
||||
---
|
||||
|
||||
### 2. Cards (Credit / Debit)
|
||||
|
||||
Supports Visa, Mastercard, RuPay, American Express, Diners, Maestro.
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"card": {
|
||||
"channel": "link",
|
||||
"card_number": "4012001037141112",
|
||||
"card_holder_name": "Vignesh Tech",
|
||||
"card_expiry_mm": "12",
|
||||
"card_expiry_yy": "28",
|
||||
"card_cvv": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*Returns 3D-Secure ACS authentication URL (`action: "3ds"`, `url: "https://..."`) to redirect customer for OTP verification.*
|
||||
|
||||
#### Saved Card Tokenization (Token Vault / CoFT)
|
||||
To comply with RBI guidelines, card numbers cannot be stored. Cashfree provides Card-on-File Tokenization:
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"card": {
|
||||
"channel": "link",
|
||||
"card_alias": "token_ref_98234",
|
||||
"card_cvv": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Net Banking
|
||||
|
||||
Supports 50+ Indian commercial and public sector banks.
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"netbanking": {
|
||||
"channel": "link",
|
||||
"netbanking_bank_code": 3003
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Major Bank Codes
|
||||
| Bank Name | Bank Code |
|
||||
| :--- | :--- |
|
||||
| **HDFC Bank** | `3003` |
|
||||
| **ICICI Bank** | `3021` |
|
||||
| **State Bank of India (SBI)** | `3044` |
|
||||
| **Axis Bank** | `3001` |
|
||||
| **Kotak Mahindra Bank** | `3032` |
|
||||
| **Punjab National Bank (PNB)** | `3038` |
|
||||
| **Bank of Baroda** | `3005` |
|
||||
| **Canara Bank** | `3011` |
|
||||
|
||||
---
|
||||
|
||||
### 4. Wallets
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_session_id": "session_xyz...",
|
||||
"payment_method": {
|
||||
"app": {
|
||||
"channel": "link",
|
||||
"provider": "paytm",
|
||||
"phone": "9876543210"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported Wallet Providers:
|
||||
`paytm`, `phonepe`, `mobikwik`, `freecharge`, `airtelmoney`, `olamoney`, `amazonpay`.
|
||||
|
||||
---
|
||||
|
||||
### 5. Pay Later & EMI
|
||||
|
||||
- **Pay Later Providers**: `simpl`, `lazypay`, `zestmoney`.
|
||||
- **Cardless EMI**: `zestmoney`, `earlysalary`, `instacred`.
|
||||
- **Bank EMI**: Available for major credit cards with 3, 6, 9, 12, 18, 24 month tenures.
|
||||
238
documentation/cashfree/05_webhooks_and_signature_verification.md
Normal file
238
documentation/cashfree/05_webhooks_and_signature_verification.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# Cashfree Payments - Webhooks & Signature Verification
|
||||
|
||||
## 1. Webhooks Architecture
|
||||
|
||||
Webhooks are automated HTTP POST requests sent from Cashfree to your backend when critical transaction events occur. They serve as the single source of truth for transaction states (such as asynchronous UPI confirmation, Netbanking approval, or 3DS completion).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Never fulfill orders solely based on frontend redirection or query parameters. Always verify the transaction status via Webhooks or the server-to-server `GET /orders/{order_id}` API.
|
||||
|
||||
---
|
||||
|
||||
## 2. Webhook Event Types
|
||||
|
||||
Cashfree emits events categorized by product:
|
||||
|
||||
| Event Type (`type`) | Description |
|
||||
| :--- | :--- |
|
||||
| `PAYMENT_SUCCESS_WEBHOOK` | Customer successfully paid; funds captured. |
|
||||
| `PAYMENT_FAILED_WEBHOOK` | Transaction declined by bank, insufficient funds, or wrong PIN. |
|
||||
| `PAYMENT_USER_DROPPED_WEBHOOK` | Customer exited checkout without completing payment. |
|
||||
| `REFUND_STATUS_WEBHOOK` | Refund status changed (`SUCCESS`, `FAILED`, `CANCELLED`). |
|
||||
| `SETTLEMENT_SUCCESS_WEBHOOK` | Settlement transferred to merchant bank account. |
|
||||
| `DISPUTE_CREATED_WEBHOOK` | Customer or bank raised a chargeback dispute. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Webhook Payload Structure
|
||||
|
||||
### Sample `PAYMENT_SUCCESS_WEBHOOK`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"order": {
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"order_amount": 1499.00,
|
||||
"order_currency": "INR",
|
||||
"order_tags": null
|
||||
},
|
||||
"payment": {
|
||||
"cf_payment_id": "291847101",
|
||||
"payment_status": "SUCCESS",
|
||||
"payment_amount": 1499.00,
|
||||
"payment_currency": "INR",
|
||||
"payment_message": "00::Transaction Success",
|
||||
"payment_time": "2026-10-18T21:06:12Z",
|
||||
"payment_completion_time": "2026-10-18T21:06:18Z",
|
||||
"bank_reference": "629184719283",
|
||||
"auth_id": null,
|
||||
"payment_method": {
|
||||
"upi": {
|
||||
"channel": "intent",
|
||||
"upi_id": "user@okaxis"
|
||||
}
|
||||
},
|
||||
"payment_group": "upi"
|
||||
},
|
||||
"customer_details": {
|
||||
"customer_name": "Vignesh Tech",
|
||||
"customer_id": "tradhox_cust_42",
|
||||
"customer_email": "vignesh@tradhox.com",
|
||||
"customer_phone": "9876543210"
|
||||
}
|
||||
},
|
||||
"event_time": "2026-10-18T21:06:20Z",
|
||||
"type": "PAYMENT_SUCCESS_WEBHOOK"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Cryptographic Signature Verification
|
||||
|
||||
Every Cashfree webhook includes two security headers:
|
||||
- `x-webhook-signature`: Base64 encoded HMAC-SHA256 signature string.
|
||||
- `x-webhook-timestamp`: Epoch timestamp in milliseconds (or ISO format) when webhook was dispatched.
|
||||
|
||||
### Verification Algorithm
|
||||
|
||||
```text
|
||||
1. Read raw request body bytes (do NOT parse or re-serialize JSON before hashing!).
|
||||
2. Concatenate: signed_data = x-webhook-timestamp + raw_body
|
||||
3. Compute HMAC-SHA256 using your Cashfree Secret Key (client_secret).
|
||||
4. Base64 encode the resulting hash.
|
||||
5. Compare the computed signature with x-webhook-signature using timing-safe comparison.
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[x-webhook-timestamp] --> C[Concatenate]
|
||||
B[Raw Request Body Bytes] --> C
|
||||
C --> D[HMAC-SHA256 with Secret Key]
|
||||
D --> E[Base64 Encode]
|
||||
E --> F{Constant-time Comparison with x-webhook-signature}
|
||||
F -->|Match| G[Process Webhook]
|
||||
F -->|Mismatch| H[Reject 400 Bad Request]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Production-Ready Python / Django Implementation
|
||||
|
||||
Save this verification helper in your service (e.g. [`payments/utils.py`](file:///d:/Tradhox_workspace/paymentservice/payments/utils.py)):
|
||||
|
||||
```python
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def verify_cashfree_webhook_signature(
|
||||
raw_body: bytes,
|
||||
signature: str,
|
||||
timestamp: str,
|
||||
secret_key: str,
|
||||
max_drift_seconds: int = 300
|
||||
) -> bool:
|
||||
"""
|
||||
Verifies Cashfree webhook signature using HMAC-SHA256.
|
||||
|
||||
Args:
|
||||
raw_body: Exact raw bytes from request.body
|
||||
signature: Value from x-webhook-signature header
|
||||
timestamp: Value from x-webhook-timestamp header
|
||||
secret_key: Cashfree Merchant Client Secret Key
|
||||
max_drift_seconds: Allowed time difference in seconds (anti-replay)
|
||||
|
||||
Returns:
|
||||
bool: True if signature is authentic and timestamp is fresh, False otherwise
|
||||
"""
|
||||
if not signature or not timestamp or not secret_key:
|
||||
logger.warning("Missing required fields for webhook signature verification")
|
||||
return False
|
||||
|
||||
# 1. Anti-Replay Check (5 minutes threshold)
|
||||
try:
|
||||
ts_val = int(timestamp)
|
||||
# If timestamp is in milliseconds, convert to seconds
|
||||
if ts_val > 10000000000:
|
||||
ts_val = ts_val // 1000
|
||||
current_time = int(time.time())
|
||||
if abs(current_time - ts_val) > max_drift_seconds:
|
||||
logger.warning(f"Webhook timestamp expired: drift={abs(current_time - ts_val)}s")
|
||||
return False
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid timestamp format: {timestamp}")
|
||||
return False
|
||||
|
||||
# 2. Build signed payload: timestamp + raw_body
|
||||
ts_bytes = str(timestamp).encode('utf-8')
|
||||
payload = ts_bytes + raw_body
|
||||
|
||||
# 3. Compute HMAC-SHA256
|
||||
computed_hmac = hmac.new(
|
||||
key=secret_key.encode('utf-8'),
|
||||
msg=payload,
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
|
||||
computed_signature = base64.b64encode(computed_hmac).decode('utf-8')
|
||||
|
||||
# 4. Constant-time comparison
|
||||
is_valid = hmac.compare_digest(computed_signature, signature)
|
||||
if not is_valid:
|
||||
logger.warning("Webhook signature mismatch!")
|
||||
return is_valid
|
||||
```
|
||||
|
||||
### Complete Django Webhook View Example
|
||||
```python
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.conf import settings
|
||||
import json
|
||||
import logging
|
||||
from .utils import verify_cashfree_webhook_signature
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@csrf_exempt
|
||||
def cashfree_webhook_view(request):
|
||||
if request.method != "POST":
|
||||
return HttpResponse(status=405)
|
||||
|
||||
signature = request.headers.get("x-webhook-signature")
|
||||
timestamp = request.headers.get("x-webhook-timestamp")
|
||||
secret_key = getattr(settings, "CASHFREE_SECRET_KEY", "")
|
||||
|
||||
# Verify signature
|
||||
if not verify_cashfree_webhook_signature(request.body, signature, timestamp, secret_key):
|
||||
return JsonResponse({"error": "Invalid signature"}, status=400)
|
||||
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
event_type = payload.get("type")
|
||||
data = payload.get("data", {})
|
||||
|
||||
order_info = data.get("order", {})
|
||||
payment_info = data.get("payment", {})
|
||||
order_id = order_info.get("order_id")
|
||||
|
||||
logger.info(f"Received Cashfree Webhook: {event_type} for order {order_id}")
|
||||
|
||||
if event_type == "PAYMENT_SUCCESS_WEBHOOK":
|
||||
# Idempotent check: Ensure order is not already marked paid
|
||||
# fulfill_order(order_id, payment_info)
|
||||
pass
|
||||
elif event_type == "PAYMENT_FAILED_WEBHOOK":
|
||||
# record_payment_failure(order_id, payment_info)
|
||||
pass
|
||||
elif event_type == "REFUND_STATUS_WEBHOOK":
|
||||
# update_refund_status(data)
|
||||
pass
|
||||
|
||||
# Return 200 OK immediately
|
||||
return JsonResponse({"status": "acknowledged"}, status=200)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing webhook: {e}", exc_info=True)
|
||||
return JsonResponse({"status": "internal_error"}, status=500)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Retries and Delivery Guarantee
|
||||
|
||||
- Cashfree requires your server to respond with HTTP `200 OK` within **5 seconds**.
|
||||
- If your server responds with an error code (5xx, 4xx) or times out, Cashfree retries up to **7 times** with exponential backoff:
|
||||
- 1st retry: after 1 minute
|
||||
- 2nd retry: after 5 minutes
|
||||
- 3rd retry: after 15 minutes
|
||||
- 4th retry: after 30 minutes
|
||||
- 5th retry: after 1 hour
|
||||
- 6th retry: after 2 hours
|
||||
- 7th retry: after 4 hours
|
||||
- **Always design webhook handlers to be strictly idempotent** by verifying against the database before executing side-effects.
|
||||
155
documentation/cashfree/06_refunds_and_disputes.md
Normal file
155
documentation/cashfree/06_refunds_and_disputes.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Cashfree Payments - Refunds & Disputes API
|
||||
|
||||
## 1. Refunds Overview
|
||||
|
||||
Cashfree provides programmatic refund capabilities for both full and partial order amounts. Refunds can be initiated directly against any successfully paid order.
|
||||
|
||||
### Refund Speeds
|
||||
1. **Standard Refund (`STANDARD`)**:
|
||||
- Follows regular banking clearinghouse cycles.
|
||||
- Takes 5 to 7 business days to reflect in the customer's source account/card.
|
||||
2. **Instant Refund (`INSTANT`)**:
|
||||
- Uses instant payment rails (UPI / IMPS).
|
||||
- Credits customer account within seconds or minutes.
|
||||
- Requires Instant Refunds to be enabled in Cashfree Dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create a Refund
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /orders/{order_id}/refunds
|
||||
```
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg/orders/{order_id}/refunds`
|
||||
- **Production**: `https://api.cashfree.com/pg/orders/{order_id}/refunds`
|
||||
|
||||
### Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <APP_ID>
|
||||
x-client-secret: <SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4>
|
||||
```
|
||||
|
||||
### Request Payload Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `refund_amount` | Number | **Yes** | Amount to be refunded. Cannot exceed remaining order balance. |
|
||||
| `refund_id` | String | **Yes** | Unique merchant refund ID (alphanumeric, underscores, hyphens). |
|
||||
| `refund_note` | String | No | Reason or note for the refund (e.g. `Customer requested return`). |
|
||||
| `refund_speed` | String | No | `"STANDARD"` (default) or `"INSTANT"`. |
|
||||
| `refund_splits` | Array | No | For split payments (Easy Split marketplace vendors). |
|
||||
|
||||
### Sample Request
|
||||
```json
|
||||
{
|
||||
"refund_amount": 499.00,
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"refund_note": "Partial return for damaged item",
|
||||
"refund_speed": "STANDARD"
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Response (`201 Created` / `200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_refund_id": "39104812",
|
||||
"cf_payment_id": "291847101",
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"entity": "refund",
|
||||
"refund_currency": "INR",
|
||||
"refund_amount": 499.00,
|
||||
"refund_note": "Partial return for damaged item",
|
||||
"refund_status": "SUCCESS",
|
||||
"refund_mode": "STANDARD",
|
||||
"refund_type": "MERCHANT_INITIATED",
|
||||
"refund_arn": "62918401928374",
|
||||
"created_at": "2026-10-18T21:15:00Z",
|
||||
"processed_at": "2026-10-18T21:15:02Z",
|
||||
"status_description": "Refund processed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For Card refunds, `refund_arn` represents the **Acquirer Reference Number (ARN)**. Provide this number to customers so their issuing bank can trace the credited amount.
|
||||
|
||||
---
|
||||
|
||||
## 3. Refund Statuses (`refund_status`)
|
||||
|
||||
| Status | Description | Action Required |
|
||||
| :--- | :--- | :--- |
|
||||
| `SUCCESS` | Refund processed successfully by Cashfree and partner bank. | Update order/refund record as refunded. |
|
||||
| `PENDING` | Refund is under processing with the customer's bank. | Wait for `REFUND_STATUS_WEBHOOK` or poll status. |
|
||||
| `CANCELLED` | Refund was cancelled by merchant before processing. | No funds deducted. |
|
||||
| `FAILED` | Bank declined the refund (e.g. account closed, card expired). | Retry or disburse via Payouts directly. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Get Refund Details
|
||||
|
||||
Fetch details and status of a specific refund.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/refunds/{refund_id}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. List All Refunds for an Order
|
||||
|
||||
Retrieves all refunds (partial or full) issued against an order.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /orders/{order_id}/refunds
|
||||
```
|
||||
|
||||
### Sample Response (`200 OK`)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cf_refund_id": "39104812",
|
||||
"refund_id": "tradhox_ref_10029",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"refund_amount": 499.00,
|
||||
"refund_status": "SUCCESS",
|
||||
"refund_arn": "62918401928374"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Disputes & Chargeback Management
|
||||
|
||||
When a cardholder or account owner disputes a transaction with their bank:
|
||||
|
||||
### Dispute Lifecycle
|
||||
1. **Retrieval Request**: Bank requests transaction proof before escalating.
|
||||
2. **Chargeback / Dispute Created**: Funds temporarily debited; merchant notified via `DISPUTE_CREATED_WEBHOOK`.
|
||||
3. **Contest Phase**: Merchant submits proof (invoice, AWB tracking, delivery signature).
|
||||
4. **Resolution**: Bank or Card Network (Visa/Mastercard) awards dispute to merchant or customer.
|
||||
|
||||
### Dispute Webhook Payload
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"dispute_id": "disp_8291038",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"cf_payment_id": "291847101",
|
||||
"dispute_amount": 1499.00,
|
||||
"dispute_status": "DISPUTE_CREATED",
|
||||
"respond_by": "2026-10-25T23:59:59Z",
|
||||
"reason_code": "4853",
|
||||
"reason_description": "Goods or services not received"
|
||||
},
|
||||
"event_time": "2026-10-18T21:20:00Z",
|
||||
"type": "DISPUTE_CREATED_WEBHOOK"
|
||||
}
|
||||
```
|
||||
88
documentation/cashfree/07_settlements_and_reconciliation.md
Normal file
88
documentation/cashfree/07_settlements_and_reconciliation.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Cashfree Payments - Settlements & Reconciliation
|
||||
|
||||
## 1. Settlement Overview
|
||||
|
||||
Settlement is the process where captured customer payments are transferred from Cashfree to your registered business bank account after deducting MDR (Merchant Discount Rate) fees and GST.
|
||||
|
||||
### Standard Settlement Cycle
|
||||
- **Standard (T+1 or T+2 Days)**: Funds captured on Day T are credited to the merchant bank account on the next 1-2 banking business days.
|
||||
- **Instant / On-Demand Settlements**: Allows merchants to trigger immediate settlement transfers 24x7 without waiting for standard banking cutoff windows.
|
||||
|
||||
---
|
||||
|
||||
## 2. Settlement Calculations & Breakdown
|
||||
|
||||
For every transaction, the settlement formula is:
|
||||
|
||||
$$\text{Settlement Amount} = \text{Order Amount} - (\text{Payment Gateway Fee} + \text{GST on Fee}) \pm \text{Adjustments}$$
|
||||
|
||||
### Sample Breakdown
|
||||
- **Order Amount**: ₹1,000.00
|
||||
- **MDR (PG Fee @ 1.9%)**: ₹19.00
|
||||
- **GST on MDR (18%)**: ₹3.42
|
||||
- **Net Credited**: ₹977.58
|
||||
|
||||
---
|
||||
|
||||
## 3. Settlement APIs
|
||||
|
||||
### 1. Get Settlements by Order ID
|
||||
|
||||
Check if and when a specific order was settled.
|
||||
|
||||
```http
|
||||
GET /orders/{order_id}/settlements
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"cf_order_id": "291847101",
|
||||
"order_id": "tradhox_ord_987654",
|
||||
"settlement_currency": "INR",
|
||||
"settlement_amount": 977.58,
|
||||
"service_charge": 19.00,
|
||||
"service_tax": 3.42,
|
||||
"settlement_id": "8291048",
|
||||
"transfer_utr": "HDFCN26101892837",
|
||||
"transfer_time": "2026-10-19T06:30:00Z",
|
||||
"settlement_status": "SUCCESS"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Get All Settlements
|
||||
|
||||
Fetch consolidated batch settlements across a date range.
|
||||
|
||||
```http
|
||||
GET /settlements?start_date=2026-10-01&end_date=2026-10-18&limit=50
|
||||
```
|
||||
|
||||
#### Response Fields
|
||||
- `cf_settlement_id`: Unique identifier for the settlement batch.
|
||||
- `settlement_amount`: Total amount credited to merchant.
|
||||
- `transfer_utr`: Bank UTR (Unique Transaction Reference) for reconciliation against bank statements.
|
||||
- `settlement_status`: `SUCCESS`, `PENDING`, `FAILED`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Settlement Webhooks
|
||||
|
||||
Configure `SETTLEMENT_SUCCESS_WEBHOOK` to receive real-time notification when bank transfer completes.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"settlement_id": "8291048",
|
||||
"settlement_amount": 145892.40,
|
||||
"settlement_currency": "INR",
|
||||
"transfer_utr": "HDFCN26101994827",
|
||||
"transfer_time": "2026-10-19T06:30:00Z",
|
||||
"settlement_status": "SUCCESS"
|
||||
},
|
||||
"event_time": "2026-10-19T06:30:05Z",
|
||||
"type": "SETTLEMENT_SUCCESS_WEBHOOK"
|
||||
}
|
||||
```
|
||||
177
documentation/cashfree/08_payouts_and_transfers.md
Normal file
177
documentation/cashfree/08_payouts_and_transfers.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# Cashfree Payouts & Automated Disbursements
|
||||
|
||||
## 1. Overview
|
||||
Cashfree Payouts allows automated disbursements to vendor bank accounts, delivery partners, refunds, affiliate payouts, and seller settlements 24x7x365 across India.
|
||||
|
||||
### Supported Rails
|
||||
- **UPI**: Instant, up to ₹1,00,000 per transaction (or ₹5,00,000 for select categories).
|
||||
- **IMPS**: Instant, available 24x7, up to ₹5,00,000 per transaction.
|
||||
- **NEFT**: Batch settlements throughout the day, no upper limit.
|
||||
- **RTGS**: Real-time gross settlement for high-value transactions (minimum ₹2,00,000).
|
||||
- **Card Payouts**: Direct credits to Visa/Mastercard debit cards.
|
||||
|
||||
---
|
||||
|
||||
## 2. Environments & Base URLs
|
||||
|
||||
| Environment | Base URL |
|
||||
| :--- | :--- |
|
||||
| **Sandbox (Gamma)** | `https://payout-gamma.cashfree.com/payout/v1` |
|
||||
| **Production** | `https://payout-api.cashfree.com/payout/v1` |
|
||||
|
||||
### Authentication
|
||||
Payouts API supports passing credentials in headers or exchanging them for a short-lived bearer token:
|
||||
```http
|
||||
X-Client-Id: <PAYOUT_CLIENT_ID>
|
||||
X-Client-Secret: <PAYOUT_CLIENT_SECRET>
|
||||
```
|
||||
Or via Auth Token:
|
||||
```http
|
||||
POST /payout/v1/authorize
|
||||
Authorization: Bearer <TOKEN>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Beneficiary Management
|
||||
|
||||
Before transferring funds, register the beneficiary (or use quick-transfer APIs).
|
||||
|
||||
### Add Beneficiary
|
||||
```http
|
||||
POST /payout/v1/addBeneficiary
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"beneId": "vendor_vignesh_01",
|
||||
"name": "Vignesh Tech",
|
||||
"email": "vignesh@tradhox.com",
|
||||
"phone": "9876543210",
|
||||
"bankAccount": "50100234567890",
|
||||
"ifsc": "HDFC0001234",
|
||||
"address1": "Tradhox HQ, Tech Park",
|
||||
"city": "Bengaluru",
|
||||
"state": "Karnataka",
|
||||
"pincode": "560001"
|
||||
}
|
||||
```
|
||||
|
||||
*For UPI Disbursements, replace `bankAccount` and `ifsc` with `vpa`:*
|
||||
```json
|
||||
{
|
||||
"beneId": "seller_upi_42",
|
||||
"name": "Seller Partner",
|
||||
"email": "partner@tradhox.com",
|
||||
"phone": "9876543210",
|
||||
"vpa": "partner@okaxis",
|
||||
"address1": "Seller Hub",
|
||||
"city": "Mumbai",
|
||||
"state": "Maharashtra",
|
||||
"pincode": "400001"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Request a Transfer (Disbursement)
|
||||
|
||||
Initiates a payout from your Cashfree account balance to the beneficiary.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /payout/v1/requestTransfer
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"beneId": "vendor_vignesh_01",
|
||||
"amount": "25000.00",
|
||||
"transferMode": "IMPS",
|
||||
"remarks": "Vendor weekly payout settlement"
|
||||
}
|
||||
```
|
||||
|
||||
#### Transfer Modes (`transferMode`):
|
||||
- `IMPS`: Instant bank transfer.
|
||||
- `NEFT`: Standard bank clearing.
|
||||
- `RTGS`: High value bank transfer (>= ₹2,00,000).
|
||||
- `UPI`: Instant transfer to UPI VPA.
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Transfer initiated successfully",
|
||||
"data": {
|
||||
"referenceId": "1948571029",
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"utr": "629184910284",
|
||||
"acknowledged": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Get Transfer Status
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /payout/v1/getTransferStatus?transferId=tradhox_payout_88291
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Transfer details fetched successfully",
|
||||
"data": {
|
||||
"transfer": {
|
||||
"transferId": "tradhox_payout_88291",
|
||||
"referenceId": "1948571029",
|
||||
"status": "SUCCESS",
|
||||
"statusDescription": "Transfer completed successfully",
|
||||
"amount": "25000.00",
|
||||
"utr": "629184910284",
|
||||
"addedOn": "2026-10-18 21:20:00",
|
||||
"processedOn": "2026-10-18 21:20:03"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Payout Statuses
|
||||
- `SUCCESS`: Transfer completed; beneficiary account credited.
|
||||
- `PENDING`: In progress with clearing network (e.g. NEFT window or slow bank response).
|
||||
- `FAILED`: Transfer rejected by beneficiary bank (invalid account, IFSC, or limit reached).
|
||||
- `REVERSED`: Transaction initially cleared but subsequently reversed by receiving bank.
|
||||
|
||||
---
|
||||
|
||||
## 6. Check Account Balance
|
||||
|
||||
Verify available wallet balance before triggering payouts.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /payout/v1/getBalance
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Balance fetched successfully",
|
||||
"data": {
|
||||
"balance": "485290.50",
|
||||
"availableBalance": "485290.50"
|
||||
}
|
||||
}
|
||||
```
|
||||
157
documentation/cashfree/09_secureid_and_kyc_verification.md
Normal file
157
documentation/cashfree/09_secureid_and_kyc_verification.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Cashfree Secure ID & KYC Verification APIs
|
||||
|
||||
## 1. Overview
|
||||
Cashfree Secure ID provides API-driven identity, bank account, and compliance verification against authoritative Indian databases (NPCI, NSDL, UIDAI, GSTN).
|
||||
|
||||
Common use cases for Tradhox:
|
||||
- Verifying vendor / seller bank accounts before onboarding.
|
||||
- Verifying PAN & GSTIN for marketplace merchant compliance.
|
||||
- Verifying customer UPI IDs to prevent payment failures and fraud.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bank Account Verification (Penny Drop)
|
||||
|
||||
Deposits ₹1.00 into the target bank account via IMPS to verify that the account is active and retrieve the exact registered name.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
GET /verification/bank-account/sync?name={name}&phone={phone}&bankAccount={account}&ifsc={ifsc}
|
||||
```
|
||||
Or via POST:
|
||||
```http
|
||||
POST /verification/bank-account/sync
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"bankAccount": "50100234567890",
|
||||
"ifsc": "HDFC0001234",
|
||||
"name": "Vignesh Tech",
|
||||
"phone": "9876543210"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "Bank account verified successfully",
|
||||
"accountStatus": "VALID",
|
||||
"accountStatusCode": "ACCOUNT_IS_VALID",
|
||||
"data": {
|
||||
"nameAtBank": "VIGNESH TECH PRIVATE LIMITED",
|
||||
"refId": "1948571029",
|
||||
"bankName": "HDFC BANK",
|
||||
"branch": "INDIRANAGAR",
|
||||
"city": "BANGALORE",
|
||||
"micr": "560240012",
|
||||
"nameMatchScore": 95,
|
||||
"nameMatchResult": "SUCCESS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> The `nameMatchScore` provides a 0-100 fuzzy matching score comparing the provided name with the official `nameAtBank`.
|
||||
|
||||
---
|
||||
|
||||
## 3. UPI VPA Verification
|
||||
|
||||
Checks whether a UPI ID exists without deducting or transferring money.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/vpa
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"vpa": "vignesh@okhdfcbank",
|
||||
"name": "Vignesh"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"subCode": "200",
|
||||
"message": "VPA verified successfully",
|
||||
"vpa": "vignesh@okhdfcbank",
|
||||
"accountExists": "YES",
|
||||
"nameAtBank": "Vignesh Tech",
|
||||
"nameMatchScore": 100
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. PAN Verification
|
||||
|
||||
Validates PAN against the Income Tax Department / NSDL database.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/pan
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234F",
|
||||
"name": "Vignesh Tech"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "VALID",
|
||||
"pan": "ABCDE1234F",
|
||||
"type": "Individual",
|
||||
"registered_name": "VIGNESH TECH",
|
||||
"name_match_score": 100,
|
||||
"aadhaar_seeding_status": "LINKED"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. GSTIN Verification
|
||||
|
||||
Validates Goods and Services Tax Identification Number.
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /verification/gstin
|
||||
```
|
||||
|
||||
#### Request Payload
|
||||
```json
|
||||
{
|
||||
"gstin": "29ABCDE1234F1Z5"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (`200 OK`)
|
||||
```json
|
||||
{
|
||||
"status": "VALID",
|
||||
"gstin": "29ABCDE1234F1Z5",
|
||||
"legal_name": "TRADHOX TECHNOLOGIES PRIVATE LIMITED",
|
||||
"trade_name": "TRADHOX",
|
||||
"registration_date": "2024-01-15",
|
||||
"gstin_status": "Active",
|
||||
"taxpayer_type": "Regular",
|
||||
"principal_place_of_business": {
|
||||
"address": "Tech Park, Outer Ring Road",
|
||||
"state": "Karnataka",
|
||||
"pincode": "560103"
|
||||
}
|
||||
}
|
||||
```
|
||||
78
documentation/cashfree/10_error_codes_and_test_data.md
Normal file
78
documentation/cashfree/10_error_codes_and_test_data.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Cashfree Payments - Error Codes & Sandbox Test Data
|
||||
|
||||
## 1. Error Response Structure
|
||||
|
||||
All Cashfree Payment Gateway APIs return consistent JSON error objects when a request fails:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Order with id 'tradhox_ord_987654' is already paid",
|
||||
"code": "order_already_paid",
|
||||
"type": "invalid_request_error"
|
||||
}
|
||||
```
|
||||
|
||||
### Top-Level Error Types (`type`)
|
||||
|
||||
| Error Type | HTTP Status | Meaning |
|
||||
| :--- | :--- | :--- |
|
||||
| `invalid_request_error` | `400`, `404`, `409` | Malformed body, missing required parameters, invalid IDs, or invalid state transitions. |
|
||||
| `authentication_error` | `401` | Invalid or missing `x-client-id`, `x-client-secret`, or expired tokens. |
|
||||
| `rate_limit_error` | `429` | Request volume exceeded allowed threshold. Implement backoff. |
|
||||
| `api_error` | `500`, `502`, `503` | Temporary server-side issue at Cashfree or partner bank. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Common API Error Codes
|
||||
|
||||
| Error Code (`code`) | HTTP Status | Description & Recommended Handling |
|
||||
| :--- | :--- | :--- |
|
||||
| `order_already_paid` | 400 | The order has already been successfully paid. Do not prompt user again; fetch order status. |
|
||||
| `order_expired` | 400 | The order has passed its `order_expiry_time`. Generate a new order. |
|
||||
| `order_terminated` | 400 | The order was marked as terminated. |
|
||||
| `order_not_found` | 404 | No order exists matching the provided `order_id`. Verify ID spelling. |
|
||||
| `duplicate_order_id` | 409 | An order with this `order_id` already exists. Use a unique order ID generator. |
|
||||
| `idempotency_conflict` | 409 | The same `x-idempotency-key` was submitted with a different request payload. |
|
||||
| `invalid_customer_details` | 400 | Phone number is not 10 digits or email format is invalid. |
|
||||
| `payment_declined` | 400 | Customer's issuing bank declined the card or UPI payment. |
|
||||
| `insufficient_balance` | 400 | Insufficient balance in merchant account to process instant refund or payout. |
|
||||
| `unauthorized_ip` | 401 | Calling server IP is not in your Merchant Dashboard IP Whitelist. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Sandbox Test Credentials & Simulators
|
||||
|
||||
When testing in the **Sandbox** environment (`https://sandbox.cashfree.com/pg`):
|
||||
|
||||
### 1. Test Cards
|
||||
|
||||
| Card Type | Card Number | Expiry | CVV | Expected Result |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Visa (Success)** | `4012 0010 3714 1112` | Any future date (e.g. `12/28`) | `123` | **SUCCESS** (3DS OTP: `123456`) |
|
||||
| **Mastercard (Success)** | `5123 4567 8901 2346` | Any future date | `123` | **SUCCESS** |
|
||||
| **RuPay (Success)** | `6071 2345 6789 0123` | Any future date | `123` | **SUCCESS** |
|
||||
| **Card Declined** | `4012 0010 3714 1113` | Any future date | `123` | **FAILED** (`payment_declined`) |
|
||||
| **Insufficient Funds** | `4012 0010 3714 1114` | Any future date | `123` | **FAILED** (`insufficient_funds`) |
|
||||
| **3DS Timeout** | `4012 0010 3714 1115` | Any future date | `123` | **FAILED** (`user_dropped`) |
|
||||
|
||||
#### Test 3D-Secure OTP
|
||||
On the simulated bank OTP page, enter `123456` or click the **Success** button.
|
||||
|
||||
---
|
||||
|
||||
### 2. Test UPI Handles
|
||||
|
||||
| VPA / UPI ID | Action / Behaviour |
|
||||
| :--- | :--- |
|
||||
| `success@upi` | Auto-approves payment; triggers instant `PAYMENT_SUCCESS_WEBHOOK`. |
|
||||
| `failure@upi` | Auto-declines payment; returns bank refusal. |
|
||||
| `test@okhdfcbank` | Simulates UPI Intent and Collect prompt. |
|
||||
|
||||
---
|
||||
|
||||
### 3. Test Netbanking
|
||||
|
||||
Select any major bank (e.g. HDFC, ICICI, SBI) in Sandbox Checkout. Cashfree displays a mock banking portal with options:
|
||||
- **Success**: Simulates customer approving netbanking login and OTP.
|
||||
- **Fail**: Simulates bank session timeout or incorrect credentials.
|
||||
- **Pending**: Simulates delayed bank settlement.
|
||||
197
documentation/cashfree/11_django_integration_blueprint.md
Normal file
197
documentation/cashfree/11_django_integration_blueprint.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# Cashfree Payments - Django Microservice Architecture Blueprint
|
||||
|
||||
## 1. Architecture Overview for Tradhox Payment Service
|
||||
|
||||
This blueprint outlines the recommended production architecture for integrating Cashfree Payment Gateway into the `paymentservice` Django app.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Tradhox Customer Website / App] -->|1. Request Order| B[POST /api/payments/orders/create/]
|
||||
B -->|2. Create Order via SDK| C[Cashfree API Gateway]
|
||||
C -->|3. Return payment_session_id| B
|
||||
B -->|4. Return session_id| A
|
||||
A -->|5. Render Cashfree Checkout| D[Customer Completes Payment]
|
||||
D -->|6. Direct S2S Webhook| E[POST /api/payments/webhooks/cashfree/]
|
||||
E -->|7. Verify HMAC-SHA256| F[Update Database & Emit Events]
|
||||
A -->|8. Redirect on Return| G[GET /api/payments/orders/status/]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Django Settings Configuration
|
||||
|
||||
Add the following to [`config/settings.py`](file:///d:/Tradhox_workspace/paymentservice/config/settings.py):
|
||||
|
||||
```python
|
||||
# Cashfree Payment Gateway Configuration
|
||||
CASHFREE_APP_ID = os.environ.get('CASHFREE_APP_ID', 'TEST103848...')
|
||||
CASHFREE_SECRET_KEY = os.environ.get('CASHFREE_SECRET_KEY', 'cfsk_ma_test_...')
|
||||
CASHFREE_ENVIRONMENT = os.environ.get('CASHFREE_ENVIRONMENT', 'sandbox') # 'sandbox' or 'production'
|
||||
CASHFREE_API_VERSION = os.environ.get('CASHFREE_API_VERSION', '2023-08-01')
|
||||
|
||||
if CASHFREE_ENVIRONMENT == 'production':
|
||||
CASHFREE_BASE_URL = 'https://api.cashfree.com/pg'
|
||||
else:
|
||||
CASHFREE_BASE_URL = 'https://sandbox.cashfree.com/pg'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Cashfree Client Class
|
||||
|
||||
Implement [`payments/cashfree_client.py`](file:///d:/Tradhox_workspace/paymentservice/payments/cashfree_client.py):
|
||||
|
||||
```python
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import requests
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CashfreeClient:
|
||||
def __init__(self):
|
||||
self.app_id = settings.CASHFREE_APP_ID
|
||||
self.secret_key = settings.CASHFREE_SECRET_KEY
|
||||
self.base_url = settings.CASHFREE_BASE_URL
|
||||
self.api_version = settings.CASHFREE_API_VERSION
|
||||
|
||||
def _get_headers(self, idempotency_key=None):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-version': self.api_version,
|
||||
'x-client-id': self.app_id,
|
||||
'x-client-secret': self.secret_key,
|
||||
}
|
||||
if idempotency_key:
|
||||
headers['x-idempotency-key'] = idempotency_key
|
||||
return headers
|
||||
|
||||
def create_order(self, order_id, amount, currency, customer, return_url, notify_url=None, note=None):
|
||||
url = f"{self.base_url}/orders"
|
||||
payload = {
|
||||
"order_id": str(order_id),
|
||||
"order_amount": float(amount),
|
||||
"order_currency": currency,
|
||||
"customer_details": {
|
||||
"customer_id": str(customer.get("id")),
|
||||
"customer_name": customer.get("name", "Customer"),
|
||||
"customer_email": customer.get("email"),
|
||||
"customer_phone": str(customer.get("phone")),
|
||||
},
|
||||
"order_meta": {
|
||||
"return_url": return_url,
|
||||
"notify_url": notify_url or return_url
|
||||
}
|
||||
}
|
||||
if note:
|
||||
payload["order_note"] = note
|
||||
|
||||
response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def get_order(self, order_id):
|
||||
url = f"{self.base_url}/orders/{order_id}"
|
||||
response = requests.get(url, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def create_refund(self, order_id, refund_id, amount, note="Refund", speed="STANDARD"):
|
||||
url = f"{self.base_url}/orders/{order_id}/refunds"
|
||||
payload = {
|
||||
"refund_id": str(refund_id),
|
||||
"refund_amount": float(amount),
|
||||
"refund_note": note,
|
||||
"refund_speed": speed
|
||||
}
|
||||
response = requests.post(url, json=payload, headers=self._get_headers(), timeout=10)
|
||||
return response.status_code, response.json()
|
||||
|
||||
def verify_webhook_signature(self, raw_body: bytes, signature: str, timestamp: str) -> bool:
|
||||
if not signature or not timestamp or not self.secret_key:
|
||||
return False
|
||||
|
||||
try:
|
||||
ts_val = int(timestamp)
|
||||
if ts_val > 10000000000:
|
||||
ts_val = ts_val // 1000
|
||||
if abs(int(time.time()) - ts_val) > 300:
|
||||
logger.warning("Webhook timestamp older than 5 minutes")
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
payload = str(timestamp).encode('utf-8') + raw_body
|
||||
computed_hmac = hmac.new(
|
||||
key=self.secret_key.encode('utf-8'),
|
||||
msg=payload,
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
computed_sig = base64.b64encode(computed_hmac).decode('utf-8')
|
||||
return hmac.compare_digest(computed_sig, signature)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Suggested Database Models
|
||||
|
||||
Add to [`payments/models.py`](file:///d:/Tradhox_workspace/paymentservice/payments/models.py):
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class PaymentOrder(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('ACTIVE', 'Active'),
|
||||
('PAID', 'Paid'),
|
||||
('EXPIRED', 'Expired'),
|
||||
('TERMINATED', 'Terminated'),
|
||||
]
|
||||
|
||||
order_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
cf_order_id = models.CharField(max_length=64, blank=True, null=True)
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
currency = models.CharField(max_length=3, default='INR')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE')
|
||||
customer_id = models.CharField(max_length=64)
|
||||
customer_email = models.EmailField()
|
||||
customer_phone = models.CharField(max_length=15)
|
||||
payment_session_id = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('SUCCESS', 'Success'),
|
||||
('FAILED', 'Failed'),
|
||||
('PENDING', 'Pending'),
|
||||
('USER_DROPPED', 'User Dropped'),
|
||||
]
|
||||
|
||||
cf_payment_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='transactions')
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
|
||||
payment_method = models.CharField(max_length=50, blank=True, null=True)
|
||||
bank_reference = models.CharField(max_length=100, blank=True, null=True)
|
||||
payment_time = models.DateTimeField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class PaymentRefund(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('SUCCESS', 'Success'),
|
||||
('PENDING', 'Pending'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('FAILED', 'Failed'),
|
||||
]
|
||||
|
||||
refund_id = models.CharField(max_length=64, unique=True, primary_key=True)
|
||||
cf_refund_id = models.CharField(max_length=64, blank=True, null=True)
|
||||
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='refunds')
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
|
||||
refund_arn = models.CharField(max_length=64, blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
46
documentation/cashfree/README.md
Normal file
46
documentation/cashfree/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Cashfree Knowledge Base
|
||||
|
||||
Complete end-to-end documentation for integrating and managing Cashfree across the Tradhox Payments Microservice (`paymentservice`).
|
||||
|
||||
---
|
||||
|
||||
## Documentation Index
|
||||
|
||||
| Module | Document | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **01** | [Overview & Quickstart](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/01_overview_and_quickstart.md) | Platform overview, environment endpoints (Sandbox & Production), architecture sequence diagram, and first API call. |
|
||||
| **02** | [Authentication, Headers & Security](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/02_authentication_and_headers.md) | API key credentials (`x-client-id`, `x-client-secret`), versioning (`2023-08-01`), idempotency, rate limiting, and security compliance. |
|
||||
| **03** | [Orders API Specification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/03_orders_api.md) | Create Order (`POST /orders`), order lifecycle (`ACTIVE`, `PAID`, `EXPIRED`, `TERMINATED`), Get Order status, and payments lookup. |
|
||||
| **04** | [Checkout Integrations & Payment Methods](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/04_payment_methods_and_checkout.md) | Hosted Checkout (redirect), Drop-in JS SDK v3, and Seamless API for UPI (Intent, Collect, QR), Cards (Token Vault), Netbanking, and Wallets. |
|
||||
| **05** | [Webhooks & Signature Verification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/05_webhooks_and_signature_verification.md) | Event schemas, HMAC-SHA256 signature verification algorithm, anti-replay timestamp validation, and production Django webhook view. |
|
||||
| **06** | [Refunds & Disputes API](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/06_refunds_and_disputes.md) | Full/Partial refunds (`POST /orders/{order_id}/refunds`), Standard vs Instant refunds, ARN tracking, and chargeback dispute workflows. |
|
||||
| **07** | [Settlements & Reconciliation](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/07_settlements_and_reconciliation.md) | T+1/T+2 banking settlement cycles, MDR fee & tax calculations, Order-level settlement API, UTR tracking, and settlement webhooks. |
|
||||
| **08** | [Payouts & Automated Disbursements](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/08_payouts_and_transfers.md) | 24x7 automated disbursements via UPI, IMPS, NEFT, RTGS, Beneficiary onboarding, transfer status tracking, and balance inquiries. |
|
||||
| **09** | [Secure ID & KYC Verification](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/09_secureid_and_kyc_verification.md) | Bank Account Penny Drop verification, UPI VPA verification, PAN validation, and GSTIN lookup for merchant & vendor compliance. |
|
||||
| **10** | [Error Codes & Sandbox Test Data](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/10_error_codes_and_test_data.md) | Standard error formats, common error codes dictionary, test Visa/Mastercard/RuPay card numbers, test UPI handles, and simulated netbanking. |
|
||||
| **11** | [Django Integration Blueprint](file:///d:/Tradhox_workspace/paymentservice/document/cashfree/11_django_integration_blueprint.md) | Production Python `CashfreeClient`, Django models (`PaymentOrder`, `PaymentTransaction`, `PaymentRefund`), settings configuration, and API views. |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Summary
|
||||
|
||||
### Base URLs
|
||||
- **Sandbox**: `https://sandbox.cashfree.com/pg`
|
||||
- **Production**: `https://api.cashfree.com/pg`
|
||||
|
||||
### Standard Request Headers
|
||||
```http
|
||||
Content-Type: application/json
|
||||
x-api-version: 2023-08-01
|
||||
x-client-id: <CASHFREE_APP_ID>
|
||||
x-client-secret: <CASHFREE_SECRET_KEY>
|
||||
x-idempotency-key: <UUIDv4>
|
||||
```
|
||||
|
||||
### Webhook Verification Formula
|
||||
```text
|
||||
payload = x-webhook-timestamp + raw_body
|
||||
computed_hmac = HMAC_SHA256(secret_key, payload)
|
||||
computed_signature = Base64Encode(computed_hmac)
|
||||
valid = (computed_signature == x-webhook-signature)
|
||||
```
|
||||
Loading…
Reference in a new issue