added gst and pan
This commit is contained in:
parent
a00d81a3fd
commit
83bd904299
66
GST_SETUP_INSTRUCTIONS.md
Normal file
66
GST_SETUP_INSTRUCTIONS.md
Normal file
@ -0,0 +1,66 @@
|
||||
# GST Verification Setup Instructions
|
||||
|
||||
## Problem Identified ✅
|
||||
|
||||
The GST credentials you provided are **NOT in the `.env` file**. The environment variables are `undefined` when the app runs.
|
||||
|
||||
## Solution
|
||||
|
||||
You need to **add the following lines to your `.env` file**:
|
||||
|
||||
```env
|
||||
# GST Verification (Setu)
|
||||
GST_PROVIDER_URL=https://dg-sandbox.setu.co/api/verify/gst
|
||||
GST_CLIENT_ID=292c6e76-dabf-49c4-8e48-90fba2916673
|
||||
GST_CLIENT_SECRET=7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP
|
||||
GST_PRODUCT_INSTANCE_ID=69e23f7f-4f71-412e-aec6-b1da3fb77c6f
|
||||
```
|
||||
|
||||
## Steps to Fix
|
||||
|
||||
1. **Open your `.env` file** (it's in the root of your project)
|
||||
|
||||
2. **Add the GST credentials** (copy the lines above)
|
||||
|
||||
3. **Restart your server**:
|
||||
- Stop the current server (Ctrl+C in terminal)
|
||||
- Run `npm run dev` again
|
||||
|
||||
4. **Test the endpoint** with this curl command:
|
||||
|
||||
### PowerShell (Windows):
|
||||
```powershell
|
||||
Invoke-WebRequest -Uri "http://localhost:3000/v1/gst/verify/27AAACM1234A1Z5" -Headers @{"x-api-key"="vf_test_369afc881da118c02ade27d323aaf3c186945edce6bcdf02"} -Method GET | Select-Object -ExpandProperty Content
|
||||
```
|
||||
|
||||
### Bash/CMD (if you have curl.exe):
|
||||
```bash
|
||||
curl -X GET http://localhost:3000/v1/gst/verify/27AAACM1234A1Z5 -H "x-api-key: vf_test_369afc881da118c02ade27d323aaf3c186945edce6bcdf02"
|
||||
```
|
||||
|
||||
## What I Fixed
|
||||
|
||||
1. ✅ **Applied the same fix that worked for PAN verification**:
|
||||
- Created fresh axios instance to avoid global defaults pollution
|
||||
- Clean credentials by trimming whitespace
|
||||
- Added detailed debug logging
|
||||
- Enhanced error handling for auth failures
|
||||
|
||||
2. ✅ **The code is ready** - it just needs the credentials in `.env`
|
||||
|
||||
## After Adding Credentials
|
||||
|
||||
Once you add the credentials to `.env` and restart, you should see:
|
||||
|
||||
**Debug logs in terminal:**
|
||||
```
|
||||
[GST Service] Sending request to provider: {
|
||||
url: 'https://dg-sandbox.setu.co/api/verify/gst',
|
||||
'x-client-id': '292c6e76-dabf-49c4-8e48-90fba2916673',
|
||||
'x-client-secret': '****11oP',
|
||||
'x-product-instance-id': '69e23f7f-4f71-412e-aec6-b1da3fb77c6f',
|
||||
requestBody: { gstin: '27AAACM1234A1Z5' }
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: If you still get authentication errors after adding to `.env`, it means the Setu sandbox credentials themselves are invalid or expired. You'll need to verify them with Setu.
|
||||
91
README.md
91
README.md
@ -27,6 +27,13 @@ npm install
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
DATABASE_URL=postgres://india_api_2025:<PASSWORD>@localhost:5434/india-api-tech4biz
|
||||
|
||||
# Setu API Credentials for PAN Verification
|
||||
PAN_PROVIDER_URL=https://dg-sandbox.setu.co/api/verify/pan
|
||||
PAN_CLIENT_ID=your-client-id-here
|
||||
PAN_CLIENT_SECRET=your-client-secret-here
|
||||
PAN_PRODUCT_INSTANCE_ID=your-product-instance-id-here
|
||||
|
||||
# optional: JWT_SECRET, REDIS_URL, etc.
|
||||
```
|
||||
|
||||
@ -52,16 +59,96 @@ verify-india-api/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## API Key Format
|
||||
## Getting Your API Key
|
||||
|
||||
### Quick Method (For Testing)
|
||||
Run the test API key creation script:
|
||||
```bash
|
||||
npm run create-test-key
|
||||
```
|
||||
This will create a test API key and display it in the console.
|
||||
|
||||
### Sign Up Method (For Production)
|
||||
Create an account to get an API key:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/v1/auth/signup \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "your-email@example.com",
|
||||
"password": "your-password-123",
|
||||
"company_name": "Your Company"
|
||||
}'
|
||||
```
|
||||
The response will include your `api_key` - save it immediately as it's only shown once!
|
||||
|
||||
### API Key Format
|
||||
API keys start with `vf_live_` or `vf_test_`:
|
||||
```
|
||||
vf_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
vf_test_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
```
|
||||
|
||||
Authentication via header:
|
||||
### Using API Key
|
||||
Send it in the request header:
|
||||
```
|
||||
X-API-Key: vf_live_xxx
|
||||
```
|
||||
or
|
||||
```
|
||||
x-api-key: vf_live_xxx
|
||||
```
|
||||
|
||||
**See `HOW_TO_GET_API_KEY.md` for detailed instructions.**
|
||||
|
||||
## PAN Verification (Setu Integration)
|
||||
|
||||
PAN verification is powered by Setu API. To use this feature:
|
||||
|
||||
1. Get your Setu API credentials:
|
||||
- `PAN_PROVIDER_URL`: Setu API endpoint (default: https://dg-sandbox.setu.co/api/verify/pan)
|
||||
- `PAN_CLIENT_ID`: Your Setu client ID (UUID format)
|
||||
- `PAN_CLIENT_SECRET`: Your Setu client secret
|
||||
- `PAN_PRODUCT_INSTANCE_ID`: Your Setu product instance ID (UUID format)
|
||||
|
||||
2. Add them to your `.env` file (see Setup section above)
|
||||
|
||||
3. Verify a PAN:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/v1/pan/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: your-api-key" \
|
||||
-d '{
|
||||
"pan": "ABCDE1234A",
|
||||
"reason": "KYC verification for account opening"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"pan": "ABCDE1234A",
|
||||
"full_name": "Kumar Gaurav Rathod",
|
||||
"first_name": "Gaurav",
|
||||
"middle_name": "Kumar",
|
||||
"last_name": "Rathod",
|
||||
"category": "Individual or Person",
|
||||
"aadhaar_seeding_status": "LINKED",
|
||||
"status": "VALID",
|
||||
"setu_response_id": "0e370877-f860-4b5c-858b-42aebfea4879",
|
||||
"setu_trace_id": "1-69437d63-75ac46a51036ab2233854e23",
|
||||
"setu_message": "PAN is valid."
|
||||
},
|
||||
"meta": {
|
||||
"request_id": "req_pan_1234567890",
|
||||
"credits_used": 1,
|
||||
"credits_remaining": 999,
|
||||
"source": "setu-pan"
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
|
||||
201
SETU_PAN_INTEGRATION.md
Normal file
201
SETU_PAN_INTEGRATION.md
Normal file
@ -0,0 +1,201 @@
|
||||
# Setu PAN Verification Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document explains how the Setu PAN verification API integration works and how it's implemented in this codebase.
|
||||
|
||||
## How Setu API Works
|
||||
|
||||
### 1. API Endpoint
|
||||
- **URL**: `https://dg-sandbox.setu.co/api/verify/pan`
|
||||
- **Method**: `POST`
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
### 2. Authentication Headers
|
||||
Setu API requires three custom headers for authentication:
|
||||
- `x-client-id`: Your Setu client ID (UUID format)
|
||||
- `x-client-secret`: Your Setu client secret
|
||||
- `x-product-instance-id`: Your Setu product instance ID (UUID format)
|
||||
|
||||
### 3. Request Body
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234A",
|
||||
"consent": "Y",
|
||||
"reason": "Reason for verifying PAN set by the developer"
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `pan` (required): 10-character PAN number (format: 5 letters + 4 digits + 1 letter)
|
||||
- `consent` (required): Must be "Y" to indicate user consent
|
||||
- `reason` (required): Reason for verification
|
||||
|
||||
### 4. Response Format
|
||||
```json
|
||||
{
|
||||
"id": "0e370877-f860-4b5c-858b-42aebfea4879",
|
||||
"message": "PAN is valid.",
|
||||
"data": {
|
||||
"full_name": "Kumar Gaurav Rathod",
|
||||
"first_name": "Gaurav",
|
||||
"middle_name": "Kumar",
|
||||
"last_name": "Rathod",
|
||||
"category": "Individual or Person",
|
||||
"aadhaar_seeding_status": "LINKED"
|
||||
},
|
||||
"traceId": "1-69437d63-75ac46a51036ab2233854e23"
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Environment Variables
|
||||
Add these to your `.env` file:
|
||||
```env
|
||||
PAN_PROVIDER_URL=https://dg-sandbox.setu.co/api/verify/pan
|
||||
PAN_CLIENT_ID=292c6e76-dabf-49c4-8e48-90fba2916673
|
||||
PAN_CLIENT_SECRET=7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP
|
||||
PAN_PRODUCT_INSTANCE_ID=439244ff-114e-41a8-ae74-a783f160622d
|
||||
```
|
||||
|
||||
### 2. Service Layer (`src/services/panService.js`)
|
||||
The service:
|
||||
- Validates PAN format (10 characters: 5 letters, 4 digits, 1 letter)
|
||||
- Checks Redis cache first to avoid duplicate API calls
|
||||
- Calls Setu API with proper headers and request body
|
||||
- Formats the response data
|
||||
- Stores the result in PostgreSQL database
|
||||
- Caches the result for 24 hours
|
||||
|
||||
### 3. Database Storage
|
||||
All PAN verifications are stored in the `pan_verifications` table with the following fields:
|
||||
- `pan`: The PAN number
|
||||
- `full_name`, `first_name`, `middle_name`, `last_name`: Name components
|
||||
- `category`: PAN category (Individual, Company, etc.)
|
||||
- `aadhaar_seeding_status`: Aadhaar linking status
|
||||
- `status`: VALID or INVALID
|
||||
- `setu_response_id`: Setu's response ID
|
||||
- `setu_trace_id`: Setu's trace ID for debugging
|
||||
- `setu_message`: Message from Setu API
|
||||
- `requested_by`: User ID who made the request
|
||||
- `requested_at`: Timestamp of the request
|
||||
|
||||
### 4. API Endpoint
|
||||
**POST** `/v1/pan/verify`
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Content-Type: application/json
|
||||
X-API-Key: your-api-key-here
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234A",
|
||||
"reason": "KYC verification" // optional
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"pan": "ABCDE1234A",
|
||||
"full_name": "Kumar Gaurav Rathod",
|
||||
"first_name": "Gaurav",
|
||||
"middle_name": "Kumar",
|
||||
"last_name": "Rathod",
|
||||
"category": "Individual or Person",
|
||||
"aadhaar_seeding_status": "LINKED",
|
||||
"status": "VALID",
|
||||
"setu_response_id": "0e370877-f860-4b5c-858b-42aebfea4879",
|
||||
"setu_trace_id": "1-69437d63-75ac46a51036ab2233854e23",
|
||||
"setu_message": "PAN is valid."
|
||||
},
|
||||
"meta": {
|
||||
"request_id": "req_pan_1234567890",
|
||||
"credits_used": 1,
|
||||
"credits_remaining": 999,
|
||||
"source": "setu-pan"
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
Before testing, you need an API key. The easiest way is to run:
|
||||
|
||||
```bash
|
||||
cd verify-india-api
|
||||
npm run create-test-key
|
||||
```
|
||||
|
||||
This will create and display a test API key. Copy it and use it in your requests.
|
||||
|
||||
**Alternative:** Sign up for a new account:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/v1/auth/signup \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "your-email@example.com",
|
||||
"password": "your-password-123",
|
||||
"company_name": "Your Company"
|
||||
}'
|
||||
```
|
||||
|
||||
The response includes your `api_key` - save it immediately!
|
||||
|
||||
**See `HOW_TO_GET_API_KEY.md` for detailed instructions.**
|
||||
|
||||
## Testing with Postman
|
||||
|
||||
1. **Set up the request:**
|
||||
- Method: `POST`
|
||||
- URL: `http://localhost:3000/v1/pan/verify`
|
||||
- Headers:
|
||||
- `Content-Type: application/json`
|
||||
- `X-API-Key: your-api-key` (get it using the method above)
|
||||
|
||||
2. **Request Body (JSON):**
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234A",
|
||||
"reason": "Testing PAN verification"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Expected Response:**
|
||||
- Status: `200 OK`
|
||||
- Body: JSON with PAN verification details
|
||||
|
||||
## Error Handling
|
||||
|
||||
The implementation handles various error scenarios:
|
||||
- **Invalid PAN format**: Returns 400 with `INVALID_PAN_FORMAT` error
|
||||
- **Missing credentials**: Returns 500 with `CONFIGURATION_ERROR`
|
||||
- **Setu API errors**: Returns the status code from Setu with appropriate error message
|
||||
- **Timeout errors**: Returns 504 with `PROVIDER_TIMEOUT`
|
||||
- **Network errors**: Returns 500 with `VERIFICATION_FAILED`
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Caching**: Results are cached in Redis for 24 hours to reduce API calls
|
||||
2. **Database Persistence**: All verifications are stored in PostgreSQL
|
||||
3. **Error Handling**: Comprehensive error handling with proper status codes
|
||||
4. **Validation**: PAN format validation before making API calls
|
||||
5. **Logging**: API calls are logged for analytics
|
||||
|
||||
## Migration
|
||||
|
||||
After updating the code, run the database migration to update the `pan_verifications` table:
|
||||
|
||||
```bash
|
||||
npm run migrate
|
||||
```
|
||||
|
||||
This will add the new fields to store all Setu response data.
|
||||
|
||||
142
TEST_SETU_API_STEPS.md
Normal file
142
TEST_SETU_API_STEPS.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Step-by-Step Guide: Testing Setu PAN API
|
||||
|
||||
This guide will help you test the Setu PAN API with static/hardcoded values.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Node.js installed** - Make sure you have Node.js installed on your system
|
||||
2. **Dependencies installed** - All npm packages should be installed
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### Step 1: Navigate to the Project Directory
|
||||
|
||||
Open your terminal/command prompt and navigate to the project directory:
|
||||
|
||||
```bash
|
||||
cd "C:\Users\front\Desktop\files 4\verify-india-api"
|
||||
```
|
||||
|
||||
### Step 2: Verify Dependencies are Installed
|
||||
|
||||
Check if `node_modules` folder exists. If not, install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install all required packages including `axios` which is needed for the API call.
|
||||
|
||||
### Step 3: Run the Test Script
|
||||
|
||||
You can run the test in two ways:
|
||||
|
||||
#### Option A: Using npm script (Recommended)
|
||||
```bash
|
||||
npm run test-setu-static
|
||||
```
|
||||
|
||||
#### Option B: Direct node command
|
||||
```bash
|
||||
node scripts/test-setu-api-static.js
|
||||
```
|
||||
|
||||
### Step 4: Check the Results
|
||||
|
||||
The script will:
|
||||
- ✅ Show request details (URL, headers, body)
|
||||
- ✅ Send the request to Setu API
|
||||
- ✅ Display the response if successful
|
||||
- ❌ Show error details if it fails
|
||||
|
||||
## Expected Output
|
||||
|
||||
### Success Case:
|
||||
```
|
||||
🧪 Testing Setu PAN API with Static Values
|
||||
|
||||
======================================================================
|
||||
|
||||
📋 Request Details:
|
||||
URL: https://dg-sandbox.setu.co/api/verify/pan
|
||||
Method: POST
|
||||
...
|
||||
|
||||
🚀 Sending request to Setu API...
|
||||
|
||||
✅ SUCCESS! API is working correctly!
|
||||
|
||||
======================================================================
|
||||
|
||||
📊 Response Details:
|
||||
Status Code: 200 OK
|
||||
Response Time: 1234ms
|
||||
...
|
||||
|
||||
✅ Test completed successfully!
|
||||
```
|
||||
|
||||
### Error Case:
|
||||
If there's an error, the script will show:
|
||||
- Status code
|
||||
- Error message
|
||||
- Possible issues and solutions
|
||||
|
||||
## What the Script Tests
|
||||
|
||||
The script makes a POST request to Setu API with:
|
||||
- **URL**: `https://dg-sandbox.setu.co/api/verify/pan`
|
||||
- **Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `x-client-id: 292c6e76-dabf-49c4-8e48-90fba2916673`
|
||||
- `x-client-secret: 7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP`
|
||||
- `x-product-instance-id: 439244ff-114e-41a8-ae74-a783f160622d`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"pan": "ABCDE1234A",
|
||||
"consent": "Y",
|
||||
"reason": "Testing"
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Cannot find module 'axios'"
|
||||
**Solution**: Run `npm install` to install dependencies
|
||||
|
||||
### Error: "Network Error" or "ECONNREFUSED"
|
||||
**Solution**:
|
||||
- Check your internet connection
|
||||
- Verify the API URL is correct
|
||||
- Check if there's a firewall blocking the request
|
||||
|
||||
### Error: "401 Unauthorized"
|
||||
**Solution**:
|
||||
- Check if the client-id and client-secret are correct
|
||||
- Verify the credentials haven't expired
|
||||
|
||||
### Error: "403 Forbidden"
|
||||
**Solution**:
|
||||
- Check if the product-instance-id is correct
|
||||
- Verify you have access to this product instance
|
||||
|
||||
### Error: "400 Bad Request"
|
||||
**Solution**:
|
||||
- Check if the PAN format is correct (should be 10 characters: 5 letters, 4 digits, 1 letter)
|
||||
- Verify the request body structure
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once the test passes:
|
||||
1. ✅ Your Setu API credentials are working
|
||||
2. ✅ You can use these credentials in your `.env` file
|
||||
3. ✅ Your application can make PAN verification requests
|
||||
|
||||
## Notes
|
||||
|
||||
- This script uses **static/hardcoded values** - perfect for testing
|
||||
- The credentials are embedded in the script for testing purposes
|
||||
- For production, always use environment variables (`.env` file)
|
||||
- The script has a 30-second timeout for the API request
|
||||
|
||||
23
check-schema.js
Normal file
23
check-schema.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { query, connectDB } = require('./src/database/connection');
|
||||
|
||||
async function checkSchema() {
|
||||
await connectDB();
|
||||
const result = await query(
|
||||
`SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'pan_verifications'
|
||||
ORDER BY ordinal_position`
|
||||
);
|
||||
|
||||
console.log('Columns in pan_verifications table:');
|
||||
result.rows.forEach(col => {
|
||||
console.log(` - ${col.column_name}: ${col.data_type}`);
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
checkSchema().catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -10,7 +10,12 @@
|
||||
"migrate:down": "node src/database/migrate.js down",
|
||||
"migrate:status": "node src/database/migrate.js status",
|
||||
"migrate:pincodes": "node src/database/migrate-pincodes.js",
|
||||
"create-test-key": "node scripts/create-test-api-key.js"
|
||||
"create-test-key": "node scripts/create-test-api-key.js",
|
||||
"create-live-key": "node scripts/create-live-api-key.js",
|
||||
"check-pan-config": "node scripts/check-pan-config.js",
|
||||
"test-pan-api": "node scripts/test-pan-api.js",
|
||||
"test-setu-static": "node scripts/test-setu-api-static.js",
|
||||
"test-localhost-pan": "node scripts/test-localhost-pan.js"
|
||||
},
|
||||
"keywords": [
|
||||
"api",
|
||||
|
||||
57
scripts/check-pan-config.js
Normal file
57
scripts/check-pan-config.js
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Script to check if PAN API configuration is correct
|
||||
* Usage: node scripts/check-pan-config.js
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
console.log('\n🔍 Checking PAN API Configuration...\n');
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
const requiredVars = [
|
||||
'PAN_PROVIDER_URL',
|
||||
'PAN_CLIENT_ID',
|
||||
'PAN_CLIENT_SECRET',
|
||||
'PAN_PRODUCT_INSTANCE_ID'
|
||||
];
|
||||
|
||||
let allSet = true;
|
||||
|
||||
requiredVars.forEach(varName => {
|
||||
const value = process.env[varName];
|
||||
if (value) {
|
||||
// Mask sensitive values
|
||||
if (varName === 'PAN_CLIENT_SECRET') {
|
||||
const masked = value.length > 10
|
||||
? value.substring(0, 4) + '***' + value.substring(value.length - 4)
|
||||
: '***';
|
||||
console.log(`✅ ${varName}: ${masked}`);
|
||||
} else {
|
||||
console.log(`✅ ${varName}: ${value}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`❌ ${varName}: NOT SET`);
|
||||
allSet = false;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
if (allSet) {
|
||||
console.log('\n✅ All PAN API credentials are set!');
|
||||
console.log('\n⚠️ If you still get "Bad token" errors:');
|
||||
console.log(' 1. Double-check the credentials are correct');
|
||||
console.log(' 2. Make sure you restarted the server after adding .env variables');
|
||||
console.log(' 3. Verify the credentials in Setu dashboard\n');
|
||||
} else {
|
||||
console.log('\n❌ Missing PAN API credentials!');
|
||||
console.log('\n📝 Add these to your .env file:');
|
||||
console.log(' PAN_PROVIDER_URL=https://dg-sandbox.setu.co/api/verify/pan');
|
||||
console.log(' PAN_CLIENT_ID=your-client-id-here');
|
||||
console.log(' PAN_CLIENT_SECRET=your-client-secret-here');
|
||||
console.log(' PAN_PRODUCT_INSTANCE_ID=your-product-instance-id-here');
|
||||
console.log('\n💡 After adding, restart your server!\n');
|
||||
}
|
||||
|
||||
process.exit(allSet ? 0 : 1);
|
||||
|
||||
68
scripts/create-live-api-key.js
Normal file
68
scripts/create-live-api-key.js
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Helper script to create a live API key for local development
|
||||
* Usage: node scripts/create-live-api-key.js
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const crypto = require('crypto');
|
||||
const { query, connectDB } = require('../src/database/connection');
|
||||
|
||||
function generateApiKey(type = 'live') {
|
||||
const prefix = type === 'test' ? 'vf_test_' : 'vf_live_';
|
||||
return prefix + crypto.randomBytes(24).toString('hex');
|
||||
}
|
||||
|
||||
async function createLiveApiKey() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log('✅ Connected to database');
|
||||
|
||||
// Get a user to attach the key to. Prefer an existing user, otherwise create one.
|
||||
let userRes = await query('SELECT id FROM users ORDER BY id LIMIT 1');
|
||||
let userId;
|
||||
|
||||
if (userRes.rows.length === 0) {
|
||||
console.log('No users found. Creating a default local user...');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const passwordHash = await bcrypt.hash('localpass123', 10);
|
||||
const userResult = await query(
|
||||
`INSERT INTO users (email, password_hash, company_name, plan, monthly_quota, quota_reset_date, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, DATE(NOW() + INTERVAL '1 month'), true)
|
||||
RETURNING id`,
|
||||
['local@example.com', passwordHash, 'Local Dev', 'dev', 100000]
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
console.log('Created user id:', userId);
|
||||
} else {
|
||||
userId = userRes.rows[0].id;
|
||||
console.log('Using existing user id:', userId);
|
||||
}
|
||||
|
||||
// Deactivate any existing live keys for that user (optional)
|
||||
await query('UPDATE api_keys SET is_active = false WHERE user_id = $1 AND key_prefix = $2', [userId, 'vf_live_']);
|
||||
|
||||
// Generate new live API key
|
||||
const apiKey = generateApiKey('live');
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
|
||||
await query(
|
||||
`INSERT INTO api_keys (user_id, key_prefix, key_hash, key_hint, name, is_test_key, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, false, true)`,
|
||||
[userId, 'vf_live_', keyHash, apiKey.slice(-4), 'Local Live Key']
|
||||
);
|
||||
|
||||
console.log('\n✅ Live API Key Created Successfully!\n');
|
||||
console.log('Key:');
|
||||
console.log(apiKey);
|
||||
console.log('\nUse this in your request header:');
|
||||
console.log('Header: x-api-key');
|
||||
console.log('Value:', apiKey);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating live API key:', error.message);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
createLiveApiKey();
|
||||
296
scripts/test-localhost-pan.js
Normal file
296
scripts/test-localhost-pan.js
Normal file
@ -0,0 +1,296 @@
|
||||
/**
|
||||
* Test script to verify PAN API through localhost
|
||||
* This script tests the Setu API integration through your local API server
|
||||
* Usage: node scripts/test-localhost-pan.js
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
const crypto = require('crypto');
|
||||
const { query, connectDB } = require('../src/database/connection');
|
||||
|
||||
// Static Setu API credentials (from your curl command)
|
||||
const SETU_CREDENTIALS = {
|
||||
PAN_PROVIDER_URL: 'https://dg-sandbox.setu.co/api/verify/pan',
|
||||
PAN_CLIENT_ID: '292c6e76-dabf-49c4-8e48-90fba2916673',
|
||||
PAN_CLIENT_SECRET: '7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP',
|
||||
PAN_PRODUCT_INSTANCE_ID: '439244ff-114e-41a8-ae74-a783f160622d'
|
||||
};
|
||||
|
||||
const LOCALHOST_URL = process.env.LOCALHOST_URL || 'http://localhost:3000';
|
||||
const TEST_PAN = 'ABCDE1234A';
|
||||
|
||||
// Function to generate API key
|
||||
function generateApiKey(type = 'test') {
|
||||
const prefix = type === 'test' ? 'vf_test_' : 'vf_live_';
|
||||
return prefix + crypto.randomBytes(24).toString('hex');
|
||||
}
|
||||
|
||||
// Function to create or get test API key
|
||||
async function getOrCreateTestApiKey() {
|
||||
try {
|
||||
await connectDB();
|
||||
|
||||
// Check if test user exists
|
||||
let testUser = await query(
|
||||
'SELECT * FROM users WHERE email = $1',
|
||||
['test@example.com']
|
||||
);
|
||||
|
||||
let userId;
|
||||
|
||||
if (testUser.rows.length === 0) {
|
||||
// Create test user
|
||||
const bcrypt = require('bcryptjs');
|
||||
const passwordHash = await bcrypt.hash('testpassword123', 10);
|
||||
|
||||
const userResult = await query(
|
||||
`INSERT INTO users (email, password_hash, company_name, plan, monthly_quota, quota_reset_date, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, DATE(NOW() + INTERVAL '1 month'), true)
|
||||
RETURNING id, email, plan`,
|
||||
['test@example.com', passwordHash, 'Test Company', 'free', 10000]
|
||||
);
|
||||
|
||||
userId = userResult.rows[0].id;
|
||||
} else {
|
||||
userId = testUser.rows[0].id;
|
||||
}
|
||||
|
||||
// Check for existing active test API key
|
||||
const existingKeys = await query(
|
||||
`SELECT ak.* FROM api_keys ak
|
||||
WHERE ak.user_id = $1 AND ak.is_test_key = true AND ak.is_active = true
|
||||
ORDER BY ak.created_at DESC
|
||||
LIMIT 1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (existingKeys.rows.length > 0) {
|
||||
// We can't retrieve the original key, so we'll need to create a new one
|
||||
// But first, let's try to use the existing one by checking if we can authenticate
|
||||
// For now, let's create a new one to be safe
|
||||
console.log(' ℹ️ Found existing test API key, creating a new one...');
|
||||
|
||||
// Deactivate old keys
|
||||
await query(
|
||||
'UPDATE api_keys SET is_active = false WHERE user_id = $1 AND is_test_key = true',
|
||||
[userId]
|
||||
);
|
||||
}
|
||||
|
||||
// Generate new API key
|
||||
const apiKey = generateApiKey('test');
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
|
||||
await query(
|
||||
`INSERT INTO api_keys (user_id, key_prefix, key_hash, key_hint, name, is_test_key, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, true, true)`,
|
||||
[userId, 'vf_test_', keyHash, apiKey.slice(-4), 'Test Key']
|
||||
);
|
||||
|
||||
return apiKey;
|
||||
} catch (error) {
|
||||
console.error(' ❌ Error creating API key:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if server is running
|
||||
async function checkServerRunning() {
|
||||
try {
|
||||
const response = await axios.get(`${LOCALHOST_URL}/health`, {
|
||||
timeout: 3000
|
||||
});
|
||||
return response.status === 200;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update .env file with Setu credentials
|
||||
async function updateEnvFile() {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
|
||||
try {
|
||||
let envContent = '';
|
||||
|
||||
// Read existing .env if it exists
|
||||
if (fs.existsSync(envPath)) {
|
||||
envContent = fs.readFileSync(envPath, 'utf8');
|
||||
}
|
||||
|
||||
// Update or add Setu credentials
|
||||
const updates = {
|
||||
'PAN_PROVIDER_URL': SETU_CREDENTIALS.PAN_PROVIDER_URL,
|
||||
'PAN_CLIENT_ID': SETU_CREDENTIALS.PAN_CLIENT_ID,
|
||||
'PAN_CLIENT_SECRET': SETU_CREDENTIALS.PAN_CLIENT_SECRET,
|
||||
'PAN_PRODUCT_INSTANCE_ID': SETU_CREDENTIALS.PAN_PRODUCT_INSTANCE_ID
|
||||
};
|
||||
|
||||
let updated = false;
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
const regex = new RegExp(`^${key}=.*$`, 'm');
|
||||
if (regex.test(envContent)) {
|
||||
envContent = envContent.replace(regex, `${key}=${value}`);
|
||||
updated = true;
|
||||
} else {
|
||||
// Add new line if file doesn't end with newline
|
||||
if (envContent && !envContent.endsWith('\n')) {
|
||||
envContent += '\n';
|
||||
}
|
||||
envContent += `${key}=${value}\n`;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
fs.writeFileSync(envPath, envContent, 'utf8');
|
||||
console.log(' ✅ Updated .env file with Setu credentials');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(' ⚠️ Could not update .env file:', error.message);
|
||||
console.log(' ℹ️ Please manually add these to your .env file:');
|
||||
console.log(` PAN_PROVIDER_URL=${SETU_CREDENTIALS.PAN_PROVIDER_URL}`);
|
||||
console.log(` PAN_CLIENT_ID=${SETU_CREDENTIALS.PAN_CLIENT_ID}`);
|
||||
console.log(` PAN_CLIENT_SECRET=${SETU_CREDENTIALS.PAN_CLIENT_SECRET}`);
|
||||
console.log(` PAN_PRODUCT_INSTANCE_ID=${SETU_CREDENTIALS.PAN_PRODUCT_INSTANCE_ID}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Main test function
|
||||
async function testLocalhostPAN() {
|
||||
console.log('\n🧪 Testing PAN API through Localhost\n');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
// Step 1: Check server is running
|
||||
console.log('\n📡 Step 1: Checking if server is running...');
|
||||
const serverRunning = await checkServerRunning();
|
||||
|
||||
if (!serverRunning) {
|
||||
console.log(' ❌ Server is not running!');
|
||||
console.log('\n Please start the server first:');
|
||||
console.log(' npm start');
|
||||
console.log(' or');
|
||||
console.log(' npm run dev');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(' ✅ Server is running on', LOCALHOST_URL);
|
||||
|
||||
// Step 2: Update .env file with Setu credentials
|
||||
console.log('\n⚙️ Step 2: Setting up Setu API credentials...');
|
||||
const envUpdated = await updateEnvFile();
|
||||
if (envUpdated) {
|
||||
console.log(' ⚠️ Note: If your server was already running, restart it to load new credentials');
|
||||
console.log(' Restart command: Press Ctrl+C and run "npm start" again');
|
||||
}
|
||||
|
||||
// Step 3: Create/get API key
|
||||
console.log('\n🔑 Step 3: Creating test API key...');
|
||||
let apiKey;
|
||||
try {
|
||||
apiKey = await getOrCreateTestApiKey();
|
||||
console.log(' ✅ Test API key created:', apiKey);
|
||||
} catch (error) {
|
||||
console.log(' ❌ Failed to create API key:', error.message);
|
||||
console.log(' Make sure your database is running and migrations are applied');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 4: Test the API endpoint
|
||||
console.log('\n🚀 Step 4: Testing PAN verification endpoint...');
|
||||
console.log(` URL: ${LOCALHOST_URL}/v1/pan/verify`);
|
||||
console.log(` PAN: ${TEST_PAN}`);
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
const response = await axios.post(
|
||||
`${LOCALHOST_URL}/v1/pan/verify`,
|
||||
{
|
||||
pan: TEST_PAN,
|
||||
consent: 'Y',
|
||||
reason: 'Testing'
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
);
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log('\n ✅ SUCCESS! API is working correctly!\n');
|
||||
console.log('='.repeat(70));
|
||||
console.log('\n📊 Response Details:');
|
||||
console.log(` Status Code: ${response.status} ${response.statusText}`);
|
||||
console.log(` Response Time: ${duration}ms`);
|
||||
console.log(`\n📦 Response Body:`);
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
console.log('\n✅ PAN Verification Successful!');
|
||||
if (response.data.data.full_name) {
|
||||
console.log(` PAN Holder: ${response.data.data.full_name}`);
|
||||
}
|
||||
if (response.data.data.status) {
|
||||
console.log(` Status: ${response.data.data.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('\n✅ All tests passed! Your localhost API is working correctly!\n');
|
||||
|
||||
} catch (error) {
|
||||
console.log('\n ❌ ERROR! API request failed!\n');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
if (error.response) {
|
||||
console.log('\n📊 Error Response:');
|
||||
console.log(` Status Code: ${error.response.status} ${error.response.statusText}`);
|
||||
console.log(`\n📦 Error Body:`);
|
||||
console.log(JSON.stringify(error.response.data, null, 2));
|
||||
|
||||
console.log('\n⚠️ Possible Issues:');
|
||||
if (error.response.status === 401) {
|
||||
console.log(' - Invalid API key (try running this script again to create a new key)');
|
||||
} else if (error.response.status === 500) {
|
||||
console.log(' - Server error (check server logs)');
|
||||
console.log(' - Make sure Setu credentials are in .env file');
|
||||
console.log(' - Restart the server after updating .env');
|
||||
} else if (error.response.status === 400) {
|
||||
console.log(' - Bad request (check PAN format)');
|
||||
}
|
||||
} else if (error.request) {
|
||||
console.log('\n⚠️ No response received from server');
|
||||
console.log(` Error: ${error.message}`);
|
||||
console.log('\n Possible Issues:');
|
||||
console.log(' - Server is not running');
|
||||
console.log(' - Wrong URL (check LOCALHOST_URL)');
|
||||
console.log(' - Network connectivity problem');
|
||||
} else {
|
||||
console.log(`\n⚠️ Request setup error: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('\n❌ Test failed!\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testLocalhostPAN().catch(error => {
|
||||
console.error('\n❌ Unexpected error:', error.message);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
103
scripts/test-pan-api.js
Normal file
103
scripts/test-pan-api.js
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Test script to verify PAN API is working correctly
|
||||
* Usage: node scripts/test-pan-api.js
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
|
||||
async function testPanAPI() {
|
||||
console.log('\n🧪 Testing PAN API Configuration...\n');
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
// Check environment variables
|
||||
console.log('\n1. Checking Environment Variables:');
|
||||
const requiredVars = {
|
||||
'PAN_PROVIDER_URL': process.env.PAN_PROVIDER_URL,
|
||||
'PAN_CLIENT_ID': process.env.PAN_CLIENT_ID,
|
||||
'PAN_CLIENT_SECRET': process.env.PAN_CLIENT_SECRET,
|
||||
'PAN_PRODUCT_INSTANCE_ID': process.env.PAN_PRODUCT_INSTANCE_ID
|
||||
};
|
||||
|
||||
let allSet = true;
|
||||
for (const [key, value] of Object.entries(requiredVars)) {
|
||||
if (value) {
|
||||
const masked = key === 'PAN_CLIENT_SECRET' && value.length > 10
|
||||
? value.substring(0, 4) + '***' + value.substring(value.length - 4)
|
||||
: value;
|
||||
console.log(` ✅ ${key}: ${masked}`);
|
||||
} else {
|
||||
console.log(` ❌ ${key}: NOT SET`);
|
||||
allSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allSet) {
|
||||
console.log('\n❌ Missing environment variables! Please check your .env file.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test Setu API directly
|
||||
console.log('\n2. Testing Setu API Directly:');
|
||||
try {
|
||||
const setuResponse = await axios.post(
|
||||
process.env.PAN_PROVIDER_URL,
|
||||
{
|
||||
pan: 'ABCDE1234A',
|
||||
consent: 'Y',
|
||||
reason: 'Testing PAN verification'
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-client-id': process.env.PAN_CLIENT_ID,
|
||||
'x-client-secret': process.env.PAN_CLIENT_SECRET,
|
||||
'x-product-instance-id': process.env.PAN_PRODUCT_INSTANCE_ID
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
);
|
||||
|
||||
if (setuResponse.status === 200 && setuResponse.data) {
|
||||
console.log(' ✅ Setu API is working!');
|
||||
console.log(` Response: ${setuResponse.data.message || 'Success'}`);
|
||||
} else {
|
||||
console.log(' ⚠️ Setu API returned unexpected response');
|
||||
console.log(` Status: ${setuResponse.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(' ❌ Setu API test failed!');
|
||||
if (error.response) {
|
||||
console.log(` Status: ${error.response.status}`);
|
||||
console.log(` Error: ${error.response.data?.message || error.response.data?.error || 'Unknown error'}`);
|
||||
} else {
|
||||
console.log(` Error: ${error.message}`);
|
||||
}
|
||||
console.log('\n⚠️ Your Setu credentials may be incorrect or the API is down.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test local API (if server is running)
|
||||
console.log('\n3. Testing Local API (make sure server is running on port 3000):');
|
||||
try {
|
||||
// First, get a test API key
|
||||
console.log(' ℹ️ You need an API key to test the local endpoint.');
|
||||
console.log(' Run: npm run create-test-key');
|
||||
console.log(' Then test with:');
|
||||
console.log(' curl -X POST http://localhost:3000/v1/pan/verify \\');
|
||||
console.log(' -H "Content-Type: application/json" \\');
|
||||
console.log(' -H "x-api-key: YOUR_API_KEY" \\');
|
||||
console.log(' -d \'{"pan": "ABCDE1234A", "reason": "Testing"}\'');
|
||||
} catch (error) {
|
||||
console.log(' ⚠️ Could not test local API:', error.message);
|
||||
}
|
||||
|
||||
console.log('\n' + '=' .repeat(60));
|
||||
console.log('\n✅ Configuration check complete!\n');
|
||||
}
|
||||
|
||||
testPanAPI().catch(error => {
|
||||
console.error('\n❌ Test failed:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
126
scripts/test-setu-api-static.js
Normal file
126
scripts/test-setu-api-static.js
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Static test script to verify Setu PAN API is working
|
||||
* This script uses hardcoded values from the curl command
|
||||
* Usage: node scripts/test-setu-api-static.js
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// Static values from the curl command
|
||||
const SETU_API_URL = 'https://dg-sandbox.setu.co/api/verify/pan';
|
||||
const CLIENT_ID = '292c6e76-dabf-49c4-8e48-90fba2916673';
|
||||
const CLIENT_SECRET = '7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP';
|
||||
const PRODUCT_INSTANCE_ID = '439244ff-114e-41a8-ae74-a783f160622d';
|
||||
|
||||
const REQUEST_BODY = {
|
||||
pan: 'ABCDE1234A',
|
||||
consent: 'Y',
|
||||
reason: 'Testing'
|
||||
};
|
||||
|
||||
async function testSetuAPI() {
|
||||
console.log('\n🧪 Testing Setu PAN API with Static Values\n');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
console.log('\n📋 Request Details:');
|
||||
console.log(` URL: ${SETU_API_URL}`);
|
||||
console.log(` Method: POST`);
|
||||
console.log(` Headers:`);
|
||||
console.log(` - Content-Type: application/json`);
|
||||
console.log(` - x-client-id: ${CLIENT_ID}`);
|
||||
console.log(` - x-client-secret: ${CLIENT_SECRET.substring(0, 8)}...`);
|
||||
console.log(` - x-product-instance-id: ${PRODUCT_INSTANCE_ID}`);
|
||||
console.log(` Body:`, JSON.stringify(REQUEST_BODY, null, 2));
|
||||
|
||||
console.log('\n🚀 Sending request to Setu API...\n');
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
const response = await axios.post(
|
||||
SETU_API_URL,
|
||||
REQUEST_BODY,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-client-id': CLIENT_ID,
|
||||
'x-client-secret': CLIENT_SECRET,
|
||||
'x-product-instance-id': PRODUCT_INSTANCE_ID
|
||||
},
|
||||
timeout: 30000 // 30 seconds
|
||||
}
|
||||
);
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log('✅ SUCCESS! API is working correctly!\n');
|
||||
console.log('='.repeat(70));
|
||||
console.log('\n📊 Response Details:');
|
||||
console.log(` Status Code: ${response.status} ${response.statusText}`);
|
||||
console.log(` Response Time: ${duration}ms`);
|
||||
console.log(` Response Headers:`, JSON.stringify(response.headers, null, 2));
|
||||
console.log(`\n📦 Response Body:`);
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
|
||||
// Check if response has expected structure
|
||||
if (response.data && response.data.data) {
|
||||
console.log('\n✅ Response structure is valid!');
|
||||
if (response.data.data.full_name) {
|
||||
console.log(` PAN Holder Name: ${response.data.data.full_name}`);
|
||||
}
|
||||
if (response.data.message) {
|
||||
console.log(` Message: ${response.data.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('\n✅ Test completed successfully!\n');
|
||||
|
||||
} catch (error) {
|
||||
console.log('❌ ERROR! API request failed!\n');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
if (error.response) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.log('\n📊 Error Response Details:');
|
||||
console.log(` Status Code: ${error.response.status} ${error.response.statusText}`);
|
||||
console.log(` Response Headers:`, JSON.stringify(error.response.headers, null, 2));
|
||||
console.log(`\n📦 Error Response Body:`);
|
||||
console.log(JSON.stringify(error.response.data, null, 2));
|
||||
|
||||
console.log('\n⚠️ Possible Issues:');
|
||||
if (error.response.status === 401) {
|
||||
console.log(' - Invalid credentials (client-id or client-secret)');
|
||||
} else if (error.response.status === 403) {
|
||||
console.log(' - Access forbidden (check product-instance-id)');
|
||||
} else if (error.response.status === 400) {
|
||||
console.log(' - Bad request (check PAN format or request body)');
|
||||
} else if (error.response.status === 404) {
|
||||
console.log(' - Endpoint not found (check API URL)');
|
||||
} else if (error.response.status >= 500) {
|
||||
console.log(' - Server error (Setu API might be down)');
|
||||
}
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
console.log('\n⚠️ No response received from server');
|
||||
console.log(` Error: ${error.message}`);
|
||||
console.log('\n Possible Issues:');
|
||||
console.log(' - Network connectivity problem');
|
||||
console.log(' - API endpoint is unreachable');
|
||||
console.log(' - Request timeout (took longer than 30 seconds)');
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log(`\n⚠️ Request setup error: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('\n❌ Test failed!\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testSetuAPI();
|
||||
|
||||
54
src/cache/redis.js
vendored
54
src/cache/redis.js
vendored
@ -34,41 +34,41 @@ function isDummyCache() {
|
||||
}
|
||||
|
||||
async function cacheGet(key) {
|
||||
if (useDummy) {
|
||||
return dummyCache.has(key) ? dummyCache.get(key) : null;
|
||||
}
|
||||
if (!redisClient) return null;
|
||||
if (useDummy) {
|
||||
return dummyCache.has(key) ? dummyCache.get(key) : null;
|
||||
}
|
||||
if (!redisClient) return null;
|
||||
|
||||
const data = await redisClient.get(key);
|
||||
if (!data) return null;
|
||||
const data = await redisClient.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
console.error('Redis parse error:', err.message);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
console.error('Redis parse error:', err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function cacheSet(key, value, expirySeconds = 3600) {
|
||||
if (useDummy) {
|
||||
dummyCache.set(key, value);
|
||||
if (useDummy) {
|
||||
dummyCache.set(key, value);
|
||||
|
||||
if (dummyExpiryTimers.has(key)) {
|
||||
clearTimeout(dummyExpiryTimers.get(key));
|
||||
if (dummyExpiryTimers.has(key)) {
|
||||
clearTimeout(dummyExpiryTimers.get(key));
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
dummyCache.delete(key);
|
||||
dummyExpiryTimers.delete(key);
|
||||
}, expirySeconds * 1000);
|
||||
dummyExpiryTimers.set(key, timer);
|
||||
return true;
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
dummyCache.delete(key);
|
||||
dummyExpiryTimers.delete(key);
|
||||
}, expirySeconds * 1000);
|
||||
dummyExpiryTimers.set(key, timer);
|
||||
|
||||
if (!redisClient) return false;
|
||||
|
||||
await redisClient.setEx(key, expirySeconds, JSON.stringify(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!redisClient) return false;
|
||||
|
||||
await redisClient.setEx(key, expirySeconds, JSON.stringify(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
async function cacheDelete(key) {
|
||||
|
||||
@ -3,11 +3,19 @@ const panBankSchema = `
|
||||
CREATE TABLE IF NOT EXISTS pan_verifications (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pan VARCHAR(10) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
full_name VARCHAR(255),
|
||||
first_name VARCHAR(255),
|
||||
middle_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
category VARCHAR(100),
|
||||
aadhaar_seeding_status VARCHAR(50),
|
||||
status VARCHAR(50),
|
||||
pan_type VARCHAR(50),
|
||||
name_match BOOLEAN,
|
||||
name_match_score INTEGER,
|
||||
setu_response_id VARCHAR(255),
|
||||
setu_trace_id VARCHAR(255),
|
||||
setu_message TEXT,
|
||||
requested_by INTEGER REFERENCES users(id),
|
||||
requested_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
53
src/index.js
53
src/index.js
@ -14,6 +14,7 @@ const ifscRoutes = require('./routes/ifsc');
|
||||
const pincodeRoutes = require('./routes/pincode');
|
||||
const gstRoutes = require('./routes/gst');
|
||||
const panRoutes = require('./routes/pan');
|
||||
console.log('✅ PAN routes loaded:', typeof panRoutes);
|
||||
const bankRoutes = require('./routes/bank');
|
||||
const userRoutes = require('./routes/user');
|
||||
|
||||
@ -23,7 +24,31 @@ const app = express();
|
||||
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// JSON body parser with error handling
|
||||
app.use(express.json({
|
||||
limit: '10mb',
|
||||
verify: (req, res, buf) => {
|
||||
try {
|
||||
JSON.parse(buf);
|
||||
} catch (e) {
|
||||
console.error('JSON parse error:', e.message);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_JSON', message: 'Invalid JSON in request body' }
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Debug middleware to log all requests
|
||||
app.use((req, res, next) => {
|
||||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`);
|
||||
console.log('Headers:', req.headers);
|
||||
console.log('Body:', req.body);
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(morgan('combined'));
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
@ -49,7 +74,18 @@ app.use('/v1/user', userRoutes);
|
||||
app.use('/v1/ifsc', ifscRoutes);
|
||||
app.use('/v1/pincode', pincodeRoutes);
|
||||
app.use('/v1/gst', gstRoutes);
|
||||
|
||||
// Test route to verify routing works (must be BEFORE panRoutes)
|
||||
app.get('/v1/pan/test', (req, res) => {
|
||||
console.log('PAN test route hit!');
|
||||
res.json({ message: 'PAN route is working', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Log all registered routes for debugging
|
||||
console.log('Registering PAN routes at /v1/pan');
|
||||
app.use('/v1/pan', panRoutes);
|
||||
console.log('PAN routes registered');
|
||||
|
||||
app.use('/v1/bank', bankRoutes);
|
||||
|
||||
app.use('*', (req, res) => {
|
||||
@ -68,8 +104,19 @@ async function startServer() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log('✅ PostgreSQL connected');
|
||||
// await connectRedis();
|
||||
// console.log('✅ Redis connected', isDummyCache() ? 'using dummy cache' : 'using real cache');
|
||||
|
||||
// // Try to connect Redis, but don't fail if it's not available
|
||||
// try {
|
||||
// const { connectRedis, isDummyCache } = require('./cache/redis');
|
||||
// await connectRedis();
|
||||
// if (isDummyCache()) {
|
||||
// console.log('📦 Using in-memory cache (Redis not available)');
|
||||
// } else {
|
||||
// console.log('✅ Redis connected');
|
||||
// }
|
||||
// } catch (redisError) {
|
||||
// console.log('📦 Using in-memory cache (Redis connection failed)');
|
||||
// }
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`✅ Server running on port ${PORT}`);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const { query } = require('../database/connection');
|
||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||
|
||||
async function authenticateApiKey(req, res, next) {
|
||||
try {
|
||||
@ -13,38 +12,36 @@ async function authenticateApiKey(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!apiKey.startsWith('vf_live_') && !apiKey.startsWith('vf_test_')) {
|
||||
const prefix = process.env.API_KEY_PREFIX || 'vf_live_';
|
||||
const testPrefix = 'vf_test_'; // Keep test prefix constant or add to env if needed
|
||||
|
||||
if (!apiKey.startsWith(prefix) && !apiKey.startsWith(testPrefix)) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_API_KEY_FORMAT', message: 'Invalid API key format' }
|
||||
});
|
||||
}
|
||||
|
||||
const cacheKey = `apikey:${apiKey}`;
|
||||
let keyData = await cacheGet(cacheKey);
|
||||
// Hash the API key and query database directly
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
|
||||
if (!keyData) {
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
const result = await query(
|
||||
`SELECT ak.*, u.plan, u.monthly_quota, u.calls_this_month, u.is_active as user_active
|
||||
FROM api_keys ak
|
||||
JOIN users u ON ak.user_id = u.id
|
||||
WHERE ak.key_hash = $1 AND ak.is_active = true`,
|
||||
[keyHash]
|
||||
);
|
||||
|
||||
const result = await query(
|
||||
`SELECT ak.*, u.plan, u.monthly_quota, u.calls_this_month, u.is_active as user_active
|
||||
FROM api_keys ak
|
||||
JOIN users u ON ak.user_id = u.id
|
||||
WHERE ak.key_hash = $1 AND ak.is_active = true`,
|
||||
[keyHash]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_API_KEY', message: 'Invalid or inactive API key' }
|
||||
});
|
||||
}
|
||||
|
||||
keyData = result.rows[0];
|
||||
await cacheSet(cacheKey, keyData, 300);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_API_KEY', message: 'Invalid or inactive API key' }
|
||||
});
|
||||
}
|
||||
|
||||
const keyData = result.rows[0];
|
||||
|
||||
if (!keyData.user_active) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
|
||||
@ -8,13 +8,24 @@ const { logApiCall } = require('../services/analytics');
|
||||
router.use(authenticateApiKey);
|
||||
router.use(rateLimit);
|
||||
|
||||
router.get('/verify/:gstin', async (req, res, next) => {
|
||||
|
||||
|
||||
// POST /v1/gst/verify { gstin }
|
||||
router.post('/verify', async (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
let success = false;
|
||||
|
||||
try {
|
||||
const { gstin } = req.params;
|
||||
const gstinRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/;
|
||||
const { gstin } = req.body;
|
||||
// Relaxed regex to allow any alphanumeric char at 14th position (instead of strict Z)
|
||||
const gstinRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[A-Z0-9]{1}[0-9A-Z]{1}$/;
|
||||
|
||||
if (!gstin) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: { code: 'MISSING_GSTIN', message: 'GSTIN is required' }
|
||||
});
|
||||
}
|
||||
|
||||
if (!gstinRegex.test(gstin.toUpperCase())) {
|
||||
return res.status(400).json({
|
||||
@ -41,7 +52,63 @@ router.get('/verify/:gstin', async (req, res, next) => {
|
||||
request_id: `req_gst_${Date.now()}`,
|
||||
credits_used: 1,
|
||||
credits_remaining: req.user.remaining - 1,
|
||||
source: 'gstn'
|
||||
source: 'setu-gst'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
} finally {
|
||||
await logApiCall({
|
||||
userId: req.user.id,
|
||||
apiKeyId: req.user.apiKeyId,
|
||||
endpoint: '/v1/gst/verify',
|
||||
method: 'POST',
|
||||
params: { gstin: req.body.gstin },
|
||||
status: success ? 200 : 500,
|
||||
duration: Date.now() - startTime,
|
||||
success,
|
||||
isTestKey: req.user.isTestKey
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /v1/gst/verify/:gstin
|
||||
router.get('/verify/:gstin', async (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
let success = false;
|
||||
|
||||
try {
|
||||
const { gstin } = req.params;
|
||||
// Relaxed regex to allow any alphanumeric char at 14th position (instead of strict Z)
|
||||
const gstinRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[A-Z0-9]{1}[0-9A-Z]{1}$/;
|
||||
|
||||
if (!gstinRegex.test(gstin.toUpperCase())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_GSTIN', message: 'Invalid GSTIN format' }
|
||||
});
|
||||
}
|
||||
|
||||
const result = await verifyGSTIN(gstin.toUpperCase());
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(result.statusCode || 404).json({
|
||||
success: false,
|
||||
error: { code: result.errorCode, message: result.message }
|
||||
});
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
meta: {
|
||||
request_id: `req_gst_${Date.now()}`,
|
||||
credits_used: 1,
|
||||
credits_remaining: req.user.remaining - 1,
|
||||
source: 'setu-gst'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -5,67 +5,94 @@ const { rateLimit } = require('../middleware/rateLimit');
|
||||
const { verifyPAN } = require('../services/panService');
|
||||
const { logApiCall } = require('../services/analytics');
|
||||
|
||||
// Health check endpoint (no auth required)
|
||||
router.get('/health', (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
service: 'PAN Verification',
|
||||
provider: 'Setu',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Apply authentication and rate limiting middleware to all other routes
|
||||
router.use(authenticateApiKey);
|
||||
router.use(rateLimit);
|
||||
|
||||
/**
|
||||
* PAN Verification Routes
|
||||
* POST /v1/pan/verify - Verify a PAN number using Setu API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @route POST /v1/pan/verify
|
||||
* @desc Verify PAN number
|
||||
* @body {string} pan - PAN number to verify (required)
|
||||
* @body {string} reason - Reason for verification (optional)
|
||||
* @returns {Object} Verification result
|
||||
*/
|
||||
router.post('/verify', async (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
let success = false;
|
||||
|
||||
try {
|
||||
const { pan, name, dob } = req.body;
|
||||
const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/;
|
||||
const { pan, reason } = req.body;
|
||||
|
||||
// Validate PAN is provided
|
||||
if (!pan) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: { code: 'MISSING_PAN', message: 'PAN is required' }
|
||||
error: {
|
||||
code: 'MISSING_PAN',
|
||||
message: 'PAN number is required. Please provide a valid PAN in the request body.'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!panRegex.test(pan.toUpperCase())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_PAN', message: 'Invalid PAN format' }
|
||||
});
|
||||
}
|
||||
|
||||
const result = await verifyPAN(pan.toUpperCase(), name, dob);
|
||||
// Call verification service
|
||||
const result = await verifyPAN(pan, reason, req.user?.id);
|
||||
|
||||
// Return appropriate response based on result
|
||||
if (!result.success) {
|
||||
return res.status(result.statusCode || 404).json({
|
||||
// If result has Setu-formatted data, return it directly
|
||||
if (result.data && result.data.verification) {
|
||||
return res.status(result.statusCode || 500).json(result.data);
|
||||
}
|
||||
|
||||
// Otherwise return standard error format
|
||||
return res.status(result.statusCode || 500).json({
|
||||
success: false,
|
||||
error: { code: result.errorCode, message: result.message }
|
||||
error: {
|
||||
code: result.errorCode || 'VERIFICATION_ERROR',
|
||||
message: result.message || 'PAN verification failed'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
meta: {
|
||||
request_id: `req_pan_${Date.now()}`,
|
||||
credits_used: 1,
|
||||
credits_remaining: req.user.remaining - 1
|
||||
}
|
||||
});
|
||||
// Return Setu-formatted response directly
|
||||
return res.status(200).json(result.data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[PAN Route] Unexpected error:', error);
|
||||
next(error);
|
||||
} finally {
|
||||
// Log API call
|
||||
await logApiCall({
|
||||
userId: req.user.id,
|
||||
apiKeyId: req.user.apiKeyId,
|
||||
endpoint: '/v1/pan/verify',
|
||||
method: 'POST',
|
||||
params: { pan: req.body.pan },
|
||||
params: { pan: req.body.pan, reason: req.body.reason },
|
||||
status: success ? 200 : 500,
|
||||
duration: Date.now() - startTime,
|
||||
success,
|
||||
isTestKey: req.user.isTestKey
|
||||
isTestKey: req.user.isTestKey,
|
||||
errorMessage: success ? null : 'PAN verification failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,83 +1,76 @@
|
||||
|
||||
const axios = require('axios');
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${process.env.BANK_PROVIDER_KEY}`;
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/json';
|
||||
// Create a dedicated axios instance for bank service to avoid polluting global axios
|
||||
// which was causing authentication issues with PAN/GST services
|
||||
const bankAxiosInstance = axios.create({
|
||||
headers: {
|
||||
common: {
|
||||
'Authorization': `Bearer ${process.env.BANK_PROVIDER_KEY || ''}`
|
||||
},
|
||||
post: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
});
|
||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||
const { query } = require('../database/connection');
|
||||
|
||||
async function verifyBankAccount(accountNumber, ifsc, name = null) {
|
||||
try {
|
||||
const cacheKey = `bank:${ifsc}:${accountNumber}`;
|
||||
const cached = await cacheGet(cacheKey);
|
||||
if (cached) {
|
||||
if (name) {
|
||||
cached.name_match = cached.name_at_bank === name.toUpperCase();
|
||||
cached.name_match_score = cached.name_match ? 100 : 0;
|
||||
}
|
||||
return { success: true, data: cached };
|
||||
}
|
||||
|
||||
// Get bank details from IFSC
|
||||
const ifscResult = await query('SELECT bank_name, branch FROM ifsc_codes WHERE ifsc = $1', [ifsc.toUpperCase()]);
|
||||
|
||||
if (ifscResult.rows.length === 0) {
|
||||
return { success: false, statusCode: 400, errorCode: 'INVALID_IFSC', message: 'IFSC not found' };
|
||||
}
|
||||
|
||||
const bankInfo = ifscResult.rows[0];
|
||||
|
||||
// Temporarily mock external bank provider response for testing
|
||||
const mockResponseData = {
|
||||
status_code: 200,
|
||||
data: {
|
||||
account_exists: true,
|
||||
name_at_bank: name || "DUMMY ACCOUNT HOLDER",
|
||||
account_holder_name: name || "DUMMY ACCOUNT HOLDER",
|
||||
branch: bankInfo.branch
|
||||
}
|
||||
};
|
||||
const response = { data: mockResponseData };
|
||||
|
||||
// Commenting out the actual external API call for now
|
||||
// const response = await axios.post(
|
||||
// process.env.BANK_PROVIDER_URL,
|
||||
// {
|
||||
// account_number: accountNumber,
|
||||
// ifsc: ifsc.toUpperCase(),
|
||||
// name: name
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// 'Authorization': `Bearer ${process.env.BANK_PROVIDER_KEY}`,
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// timeout: 30000
|
||||
// }
|
||||
// );
|
||||
|
||||
if (!response.data || response.data.status_code !== 200) {
|
||||
return { success: false, statusCode: 404, errorCode: 'ACCOUNT_NOT_FOUND', message: 'Account not found' };
|
||||
}
|
||||
|
||||
const d = response.data.data;
|
||||
|
||||
const data = {
|
||||
account_number: accountNumber,
|
||||
ifsc: ifsc.toUpperCase(),
|
||||
account_exists: d.account_exists !== false,
|
||||
name_at_bank: d.name_at_bank || d.account_holder_name || '',
|
||||
name_match: name ? (d.name_at_bank || d.account_holder_name || '').toUpperCase() === name.toUpperCase() : null,
|
||||
name_match_score: name ? (d.name_at_bank || d.account_holder_name || '').toUpperCase() === name.toUpperCase() ? 100 : 0 : null,
|
||||
bank_name: bankInfo.bank_name,
|
||||
branch: bankInfo.branch || d.branch || ''
|
||||
};
|
||||
|
||||
await cacheSet(cacheKey, data, 86400);
|
||||
|
||||
// Persist bank verification result to Postgres (best-effort, non-blocking)
|
||||
try {
|
||||
await query(
|
||||
`INSERT INTO bank_verifications (
|
||||
const cacheKey = `bank:${ifsc}:${accountNumber}`;
|
||||
const cached = await cacheGet(cacheKey);
|
||||
if (cached) {
|
||||
if (name) {
|
||||
cached.name_match = cached.name_at_bank === name.toUpperCase();
|
||||
cached.name_match_score = cached.name_match ? 100 : 0;
|
||||
}
|
||||
return { success: true, data: cached };
|
||||
}
|
||||
|
||||
// Get bank details from IFSC
|
||||
const ifscResult = await query('SELECT bank_name, branch FROM ifsc_codes WHERE ifsc = $1', [ifsc.toUpperCase()]);
|
||||
|
||||
if (ifscResult.rows.length === 0) {
|
||||
return { success: false, statusCode: 400, errorCode: 'INVALID_IFSC', message: 'IFSC not found' };
|
||||
}
|
||||
|
||||
const bankInfo = ifscResult.rows[0];
|
||||
|
||||
// Temporarily mock external bank provider response for testing
|
||||
const mockResponseData = {
|
||||
status_code: 200,
|
||||
data: {
|
||||
account_exists: true,
|
||||
name_at_bank: name || "DUMMY ACCOUNT HOLDER",
|
||||
account_holder_name: name || "DUMMY ACCOUNT HOLDER",
|
||||
branch: bankInfo.branch
|
||||
}
|
||||
};
|
||||
const response = { data: mockResponseData };
|
||||
|
||||
if (!response.data || response.data.status_code !== 200) {
|
||||
return { success: false, statusCode: 404, errorCode: 'ACCOUNT_NOT_FOUND', message: 'Account not found' };
|
||||
}
|
||||
|
||||
const d = response.data.data;
|
||||
|
||||
const data = {
|
||||
account_number: accountNumber,
|
||||
ifsc: ifsc.toUpperCase(),
|
||||
account_exists: d.account_exists !== false,
|
||||
name_at_bank: d.name_at_bank || d.account_holder_name || '',
|
||||
name_match: name ? (d.name_at_bank || d.account_holder_name || '').toUpperCase() === name.toUpperCase() : null,
|
||||
name_match_score: name ? (d.name_at_bank || d.account_holder_name || '').toUpperCase() === name.toUpperCase() ? 100 : 0 : null,
|
||||
bank_name: bankInfo.bank_name,
|
||||
branch: bankInfo.branch || d.branch || ''
|
||||
};
|
||||
|
||||
await cacheSet(cacheKey, data, 86400);
|
||||
|
||||
// Persist bank verification result to Postgres (best-effort, non-blocking)
|
||||
try {
|
||||
await query(
|
||||
`INSERT INTO bank_verifications (
|
||||
account_number,
|
||||
ifsc,
|
||||
name,
|
||||
@ -88,44 +81,44 @@ async function verifyBankAccount(accountNumber, ifsc, name = null) {
|
||||
branch,
|
||||
requested_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[
|
||||
data.account_number,
|
||||
data.ifsc,
|
||||
data.name_at_bank,
|
||||
data.account_exists,
|
||||
data.name_match,
|
||||
data.name_match_score,
|
||||
data.bank_name,
|
||||
data.branch,
|
||||
null, // requested_by (user id) - can be wired from route later if needed
|
||||
]
|
||||
);
|
||||
} catch (dbError) {
|
||||
console.error('Failed to store bank verification in database:', dbError.message || dbError);
|
||||
[
|
||||
data.account_number,
|
||||
data.ifsc,
|
||||
data.name_at_bank,
|
||||
data.account_exists,
|
||||
data.name_match,
|
||||
data.name_match_score,
|
||||
data.bank_name,
|
||||
data.branch,
|
||||
null, // requested_by (user id) - can be wired from route later if needed
|
||||
]
|
||||
);
|
||||
} catch (dbError) {
|
||||
console.error('Failed to store bank verification in database:', dbError.message || dbError);
|
||||
}
|
||||
|
||||
return { success: true, data };
|
||||
|
||||
} catch (error) {
|
||||
// Surface provider details when available to avoid generic 500s
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
return { success: false, statusCode: 504, errorCode: 'PROVIDER_TIMEOUT', message: 'Service timeout' };
|
||||
}
|
||||
|
||||
const providerStatus = error.response?.status || error.response?.data?.status_code;
|
||||
const providerMessage = error.response?.data?.message || error.message;
|
||||
|
||||
if (providerStatus) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: providerStatus,
|
||||
errorCode: error.response?.data?.error_code || 'PROVIDER_ERROR',
|
||||
message: providerMessage || 'Provider error'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, statusCode: 500, errorCode: 'VERIFICATION_FAILED', message: 'Verification failed' };
|
||||
}
|
||||
|
||||
return { success: true, data };
|
||||
|
||||
} catch (error) {
|
||||
// Surface provider details when available to avoid generic 500s
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
return { success: false, statusCode: 504, errorCode: 'PROVIDER_TIMEOUT', message: 'Service timeout' };
|
||||
}
|
||||
|
||||
const providerStatus = error.response?.status || error.response?.data?.status_code;
|
||||
const providerMessage = error.response?.data?.message || error.message;
|
||||
|
||||
if (providerStatus) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: providerStatus,
|
||||
errorCode: error.response?.data?.error_code || 'PROVIDER_ERROR',
|
||||
message: providerMessage || 'Provider error'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, statusCode: 500, errorCode: 'VERIFICATION_FAILED', message: 'Verification failed' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifyBankAccount };
|
||||
|
||||
@ -1,101 +1,5 @@
|
||||
// const axios = require('axios');
|
||||
// const { cacheGet, cacheSet } = require('../cache/redis');
|
||||
|
||||
// const STATE_NAMES = {
|
||||
// '01': 'Jammu & Kashmir', '02': 'Himachal Pradesh', '03': 'Punjab',
|
||||
// '04': 'Chandigarh', '05': 'Uttarakhand', '06': 'Haryana', '07': 'Delhi',
|
||||
// '08': 'Rajasthan', '09': 'Uttar Pradesh', '10': 'Bihar', '11': 'Sikkim',
|
||||
// '12': 'Arunachal Pradesh', '13': 'Nagaland', '14': 'Manipur', '15': 'Mizoram',
|
||||
// '16': 'Tripura', '17': 'Meghalaya', '18': 'Assam', '19': 'West Bengal',
|
||||
// '20': 'Jharkhand', '21': 'Odisha', '22': 'Chhattisgarh', '23': 'Madhya Pradesh',
|
||||
// '24': 'Gujarat', '26': 'Dadra & Nagar Haveli', '27': 'Maharashtra',
|
||||
// '29': 'Karnataka', '30': 'Goa', '31': 'Lakshadweep', '32': 'Kerala',
|
||||
// '33': 'Tamil Nadu', '34': 'Puducherry', '35': 'Andaman & Nicobar',
|
||||
// '36': 'Telangana', '37': 'Andhra Pradesh', '38': 'Ladakh'
|
||||
// };
|
||||
|
||||
// async function verifyGSTIN(gstin) {
|
||||
// try {
|
||||
// const cacheKey = `gst:${gstin}`;
|
||||
// const cached = await cacheGet(cacheKey);
|
||||
// if (cached) return { success: true, data: cached };
|
||||
|
||||
// const response = await axios.post(
|
||||
// process.env.GST_PROVIDER_URL,
|
||||
// { id_number: gstin },
|
||||
// {
|
||||
// headers: {
|
||||
// 'Authorization': `Bearer ${process.env.GST_PROVIDER_KEY}`,
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// timeout: 30000
|
||||
// }
|
||||
// );
|
||||
|
||||
// if (!response.data || response.data.status_code !== 200) {
|
||||
// return { success: false, statusCode: 404, errorCode: 'GSTIN_NOT_FOUND', message: 'GSTIN not found' };
|
||||
// }
|
||||
|
||||
// const d = response.data.data;
|
||||
|
||||
// const data = {
|
||||
// gstin,
|
||||
// legal_name: d.legal_name || d.lgnm,
|
||||
// trade_name: d.trade_name || d.tradeNam,
|
||||
// status: d.status || d.sts,
|
||||
// registration_date: d.registration_date || d.rgdt,
|
||||
// last_updated: d.last_update || d.lstupdt,
|
||||
// business_type: d.business_type || d.ctb,
|
||||
// constitution: d.constitution || d.ctj,
|
||||
// state: d.state || STATE_NAMES[gstin.substring(0, 2)],
|
||||
// state_code: gstin.substring(0, 2),
|
||||
// pan: gstin.substring(2, 12),
|
||||
// address: {
|
||||
// building: d.address?.bno || d.pradr?.addr?.bno || '',
|
||||
// floor: d.address?.flno || d.pradr?.addr?.flno || '',
|
||||
// street: d.address?.st || d.pradr?.addr?.st || '',
|
||||
// locality: d.address?.loc || d.pradr?.addr?.loc || '',
|
||||
// city: d.address?.city || d.pradr?.addr?.city || '',
|
||||
// district: d.address?.dst || d.pradr?.addr?.dst || '',
|
||||
// state: d.address?.stcd || d.pradr?.addr?.stcd || '',
|
||||
// pincode: d.address?.pncd || d.pradr?.addr?.pncd || ''
|
||||
// },
|
||||
// nature_of_business: d.nature_of_business || d.nba || [],
|
||||
// filing_status: {
|
||||
// gstr1: d.filing_status?.gstr1 || 'Unknown',
|
||||
// gstr3b: d.filing_status?.gstr3b || 'Unknown',
|
||||
// last_filed_date: d.filing_status?.last_filed || null
|
||||
// }
|
||||
// };
|
||||
|
||||
// await cacheSet(cacheKey, data, 86400);
|
||||
// return { success: true, data };
|
||||
|
||||
// } catch (error) {
|
||||
// // Surface provider details when available to avoid generic 500s
|
||||
// if (error.code === 'ECONNABORTED') {
|
||||
// return { success: false, statusCode: 504, errorCode: 'PROVIDER_TIMEOUT', message: 'Service timeout' };
|
||||
// }
|
||||
|
||||
// const providerStatus = error.response?.status || error.response?.data?.status_code;
|
||||
// const providerMessage = error.response?.data?.message || error.message;
|
||||
|
||||
// if (providerStatus) {
|
||||
// return {
|
||||
// success: false,
|
||||
// statusCode: providerStatus,
|
||||
// errorCode: error.response?.data?.error_code || 'PROVIDER_ERROR',
|
||||
// message: providerMessage || 'Provider error'
|
||||
// };
|
||||
// }
|
||||
|
||||
// return { success: false, statusCode: 500, errorCode: 'VERIFICATION_FAILED', message: 'Verification failed' };
|
||||
// }
|
||||
// }
|
||||
|
||||
// module.exports = { verifyGSTIN };
|
||||
const axios = require('axios');
|
||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||
const { query } = require('../database/connection');
|
||||
|
||||
const STATE_NAMES = {
|
||||
'01': 'Jammu & Kashmir', '02': 'Himachal Pradesh', '03': 'Punjab',
|
||||
@ -111,81 +15,269 @@ const STATE_NAMES = {
|
||||
};
|
||||
|
||||
async function verifyGSTIN(gstin) {
|
||||
// Create a fresh axios instance to avoid global axios.defaults pollution from other services
|
||||
const axiosInstance = axios.create();
|
||||
// Defensively remove globally set headers just in case
|
||||
delete axiosInstance.defaults.headers.common['Authorization'];
|
||||
|
||||
try {
|
||||
const cacheKey = `gst:${gstin}`;
|
||||
const cached = await cacheGet(cacheKey);
|
||||
if (cached) return { success: true, data: cached };
|
||||
|
||||
// Look up GSTIN in the local database seeded from gst.csv
|
||||
const result = await query(
|
||||
'SELECT * FROM gst_registrations WHERE gstin = $1',
|
||||
[gstin]
|
||||
);
|
||||
// Get and clean Setu API credentials
|
||||
const providerUrl = process.env.GST_PROVIDER_URL || 'https://dg-sandbox.setu.co/api/verify/gst';
|
||||
const clientId = process.env.GST_CLIENT_ID;
|
||||
const clientSecret = process.env.GST_CLIENT_SECRET;
|
||||
const productInstanceId = process.env.GST_PRODUCT_INSTANCE_ID;
|
||||
|
||||
if (!result.rows.length) {
|
||||
return { success: false, statusCode: 404, errorCode: 'GSTIN_NOT_FOUND', message: 'GSTIN not found' };
|
||||
// Validate environment variables
|
||||
if (!clientId || !clientSecret || !productInstanceId) {
|
||||
console.error('GST API credentials missing. Check environment variables: GST_CLIENT_ID, GST_CLIENT_SECRET, GST_PRODUCT_INSTANCE_ID');
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 500,
|
||||
errorCode: 'CONFIGURATION_ERROR',
|
||||
message: 'Server configuration error: Missing GST provider credentials'
|
||||
};
|
||||
}
|
||||
|
||||
const d = result.rows[0];
|
||||
// Clean credentials (trim whitespace)
|
||||
const cleanClientId = clientId.trim();
|
||||
const cleanClientSecret = clientSecret.trim();
|
||||
const cleanProductInstanceId = productInstanceId.trim();
|
||||
const cleanUrl = providerUrl.trim();
|
||||
|
||||
// nature_of_business in CSV is stored as a single string like "Manufacturing|Services"
|
||||
const natureOfBusinessArray = d.nature_of_business
|
||||
? String(d.nature_of_business).split('|').map((v) => v.trim()).filter(Boolean)
|
||||
: [];
|
||||
// Debug: log outgoing provider request (mask sensitive parts)
|
||||
try {
|
||||
const maskedSecret = cleanClientSecret ? ('****' + cleanClientSecret.slice(-4)) : null;
|
||||
console.log('[GST Service] Sending request to provider:', {
|
||||
url: cleanUrl,
|
||||
'x-client-id': cleanClientId,
|
||||
'x-client-secret': maskedSecret,
|
||||
'x-product-instance-id': cleanProductInstanceId,
|
||||
requestBody: { gstin }
|
||||
});
|
||||
} catch (logErr) {
|
||||
console.error('[GST Service] Failed to log provider request:', logErr.message);
|
||||
}
|
||||
|
||||
const data = {
|
||||
gstin,
|
||||
legal_name: d.legal_name,
|
||||
trade_name: d.trade_name,
|
||||
status: d.status,
|
||||
registration_date: d.registration_date,
|
||||
last_updated: d.last_updated,
|
||||
business_type: d.business_type,
|
||||
constitution: d.constitution,
|
||||
state: d.state || STATE_NAMES[gstin.substring(0, 2)],
|
||||
const response = await axiosInstance.post(
|
||||
cleanUrl,
|
||||
{ gstin },
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'x-client-id': cleanClientId,
|
||||
'x-client-secret': cleanClientSecret,
|
||||
'x-product-instance-id': cleanProductInstanceId
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
);
|
||||
|
||||
if (!response || response.status !== 200 || !response.data) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: response?.status || 502,
|
||||
errorCode: 'GST_PROVIDER_ERROR',
|
||||
message: 'GST provider returned an unexpected response'
|
||||
};
|
||||
}
|
||||
|
||||
const d = response.data.data || {};
|
||||
const setuVerification = response.data.verification || 'SUCCESS';
|
||||
const setuMessage = response.data.message || '';
|
||||
const traceId = response.data.traceId || null;
|
||||
const requestId = response.data.requestId || null;
|
||||
|
||||
// Check if we got valid company data, otherwise handle as invalid/empty
|
||||
const company = d.company || {};
|
||||
const address = d.address?.principle || {};
|
||||
const gstData = d.gst || {};
|
||||
const jurisdiction = d.jurisdiction || {};
|
||||
|
||||
const mappedData = {
|
||||
gstin: gstData.id || gstin,
|
||||
legal_name: company.name || '',
|
||||
trade_name: company.tradeName || '',
|
||||
status: company.status || 'Inactive',
|
||||
registration_date: gstData.registrationDate || '',
|
||||
last_updated: gstData.lastUpdatedDate || '',
|
||||
business_type: company.type || '',
|
||||
constitution: company.constitutionOfBusiness || '',
|
||||
state: address.stateCode || company.state || STATE_NAMES[gstin.substring(0, 2)] || '',
|
||||
state_code: gstin.substring(0, 2),
|
||||
pan: gstin.substring(2, 12),
|
||||
address: {
|
||||
building: d.address_building || '',
|
||||
floor: d.address_floor || '',
|
||||
street: d.address_street || '',
|
||||
locality: d.address_locality || '',
|
||||
city: d.address_city || '',
|
||||
district: d.address_district || '',
|
||||
state: d.address_state_code || '',
|
||||
pincode: d.address_pincode || ''
|
||||
building: address.buildingName || '',
|
||||
building_number: address.buildingNumber || '',
|
||||
floor: address.floorNo || '',
|
||||
street: address.street || '',
|
||||
locality: address.location || '',
|
||||
city: address.city || '',
|
||||
district: address.district || '',
|
||||
state: address.stateCode || '',
|
||||
pincode: address.pinCode || ''
|
||||
},
|
||||
nature_of_business: natureOfBusinessArray,
|
||||
filing_status: {
|
||||
gstr1: d.filing_status_gstr1 || 'Unknown',
|
||||
gstr3b: d.filing_status_gstr3b || 'Unknown',
|
||||
last_filed_date: d.filing_last_filed_date || null
|
||||
nature_of_business: company.natureOfBusinessActivity ? [company.natureOfBusinessActivity] : [],
|
||||
jurisdiction: {
|
||||
centre: jurisdiction.centre || '',
|
||||
state: jurisdiction.state || ''
|
||||
}
|
||||
};
|
||||
|
||||
await cacheSet(cacheKey, data, 86400);
|
||||
return { success: true, data };
|
||||
const responseData = {
|
||||
verification: setuVerification,
|
||||
message: setuMessage,
|
||||
requestId: requestId,
|
||||
traceId: traceId,
|
||||
data: Object.keys(d).length > 0 ? mappedData : {}
|
||||
};
|
||||
|
||||
await cacheSet(cacheKey, responseData, 86400);
|
||||
return { success: true, data: responseData };
|
||||
|
||||
} catch (error) {
|
||||
// Surface provider details when available to avoid generic 500s
|
||||
// Handle timeout errors
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
return { success: false, statusCode: 504, errorCode: 'PROVIDER_TIMEOUT', message: 'Service timeout' };
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 504,
|
||||
errorCode: 'PROVIDER_TIMEOUT',
|
||||
message: 'GST verification service timeout'
|
||||
};
|
||||
}
|
||||
|
||||
const providerStatus = error.response?.status || error.response?.data?.status_code;
|
||||
const providerMessage = error.response?.data?.message || error.message;
|
||||
// Handle Setu API errors
|
||||
if (error.response) {
|
||||
const providerStatus = error.response.status;
|
||||
|
||||
// Handle Provider Authentication Errors specifically
|
||||
if (providerStatus === 401 || providerStatus === 403) {
|
||||
// Log provider auth failure details for debugging (mask any obvious secrets)
|
||||
try {
|
||||
const providerData = error.response.data;
|
||||
const providerHeaders = error.response.headers || {};
|
||||
const maskedHeaders = { ...providerHeaders };
|
||||
if (maskedHeaders['x-client-secret']) {
|
||||
maskedHeaders['x-client-secret'] = '****' + String(maskedHeaders['x-client-secret']).slice(-4);
|
||||
}
|
||||
console.error('[GST Service] Provider Authentication Failed', {
|
||||
status: providerStatus,
|
||||
data: providerData,
|
||||
headers: maskedHeaders
|
||||
});
|
||||
} catch (logErr) {
|
||||
console.error('[GST Service] Failed to log provider auth error:', logErr.message);
|
||||
}
|
||||
|
||||
// Surface provider message when available to aid debugging (do not expose secrets)
|
||||
const providerMsg = error.response.data?.message || error.response.data || 'Upstream provider authentication failed';
|
||||
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 502,
|
||||
errorCode: 'GST_PROVIDER_AUTH_ERROR',
|
||||
message: typeof providerMsg === 'string' ? providerMsg : 'Upstream provider authentication failed. Check provider logs.'
|
||||
};
|
||||
}
|
||||
|
||||
// For other provider errors
|
||||
const errorData = error.response.data || {};
|
||||
const providerMessage = errorData.message || error.message;
|
||||
|
||||
if (providerStatus) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: providerStatus,
|
||||
errorCode: error.response?.data?.error_code || 'PROVIDER_ERROR',
|
||||
errorCode: errorData.error_code || 'PROVIDER_ERROR',
|
||||
message: providerMessage || 'Provider error'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, statusCode: 500, errorCode: 'VERIFICATION_FAILED', message: 'Verification failed' };
|
||||
// Generic error
|
||||
console.error('GST verification error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 500,
|
||||
errorCode: 'VERIFICATION_FAILED',
|
||||
message: 'GST verification failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifyGSTIN };
|
||||
|
||||
|
||||
|
||||
|
||||
// const axios = require('axios');
|
||||
|
||||
|
||||
// async function verifyGstSetu(gstin) {
|
||||
// const baseUrl = process.env.SETU_BASE_URL;
|
||||
// const clientId = process.env.SETU_CLIENT_ID;
|
||||
// const clientSecret = process.env.SETU_CLIENT_SECRET;
|
||||
// const productInstanceId = process.env.SETU_GST_INSTANCE_ID;
|
||||
|
||||
// // Validate environment variables
|
||||
// if (!baseUrl || !clientId || !clientSecret || !productInstanceId) {
|
||||
// throw new Error('Setu GST API configuration is incomplete. Check environment variables.');
|
||||
// }
|
||||
|
||||
// try {
|
||||
// console.log(`🌐 Attempting Setu GST verification for GSTIN: ${gstin}`);
|
||||
|
||||
// const response = await axios.post(
|
||||
// `${baseUrl}/api/verify/gst`,
|
||||
// {
|
||||
// gstin: gstin,
|
||||
// consent: 'Y',
|
||||
// reason: 'Vendor verification'
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'x-client-id': clientId,
|
||||
// 'x-client-secret': clientSecret,
|
||||
// 'x-product-instance-id': productInstanceId
|
||||
// },
|
||||
// timeout: 30000 // 30 second timeout
|
||||
// }
|
||||
// );
|
||||
|
||||
// // Check if Setu returned a successful verification (case-insensitive)
|
||||
// const verification = response.data?.verification?.toLowerCase();
|
||||
// if (response.data && (verification === 'success' || response.data.status === 'success')) {
|
||||
// console.log(`✅ Setu GST verification successful for GSTIN: ${gstin}`);
|
||||
// return {
|
||||
// success: true,
|
||||
// source: 'SETU_API',
|
||||
// data: response.data.data
|
||||
// };
|
||||
// }
|
||||
|
||||
// // If verification field is not SUCCESS, treat as failure
|
||||
// throw new Error(response.data?.message || 'GST verification failed via Setu');
|
||||
|
||||
// } catch (error) {
|
||||
// // Re-throw with more context
|
||||
// if (error.response) {
|
||||
// // Setu returned an error response
|
||||
// const statusCode = error.response.status;
|
||||
// const errorMessage = error.response.data?.message || error.response.data?.error || 'Unknown Setu API error';
|
||||
// console.warn(`⚠️ Setu GST API failed for GSTIN ${gstin}: Setu API Error (${statusCode}): ${errorMessage}`);
|
||||
// throw new Error(`Setu API Error (${statusCode}): ${errorMessage}`);
|
||||
// } else if (error.code === 'ECONNABORTED') {
|
||||
// console.warn(`⚠️ Setu GST API timeout for GSTIN ${gstin}`);
|
||||
// throw new Error('Setu API timeout');
|
||||
// } else {
|
||||
// console.warn(`⚠️ Setu GST API failed for GSTIN ${gstin}: ${error.message}`);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// module.exports = { verifyGstSetu };
|
||||
|
||||
|
||||
@ -1,140 +1,223 @@
|
||||
const axios = require('axios');
|
||||
const { cacheGet, cacheSet } = require('../cache/redis');
|
||||
const { query } = require('../database/connection');
|
||||
|
||||
const PAN_TYPES = {
|
||||
'P': 'Individual',
|
||||
'C': 'Company',
|
||||
'H': 'HUF',
|
||||
'A': 'AOP',
|
||||
'B': 'BOI',
|
||||
'G': 'Government',
|
||||
'J': 'Artificial Juridical Person',
|
||||
'L': 'Local Authority',
|
||||
'F': 'Firm/Partnership',
|
||||
'T': 'Trust'
|
||||
};
|
||||
// Create a fresh axios instance to avoid global axios.defaults pollution from other services
|
||||
const axiosInstance = axios.create();
|
||||
|
||||
async function verifyPAN(pan, name = null, dob = null) {
|
||||
|
||||
async function verifyPAN(pan, reason = null, userId = null) {
|
||||
try {
|
||||
const cacheKey = `pan:${pan}`;
|
||||
const cached = await cacheGet(cacheKey);
|
||||
if (cached) {
|
||||
if (name) {
|
||||
cached.name_match = cached.name === name.toUpperCase();
|
||||
cached.name_match_score = cached.name_match ? 100 : 0;
|
||||
}
|
||||
return { success: true, data: cached };
|
||||
}
|
||||
// Validate PAN format (10 characters: 5 letters, 4 digits, 1 letter)
|
||||
const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/;
|
||||
const panUpper = pan.toUpperCase();
|
||||
|
||||
|
||||
|
||||
const parts = name.trim().split(" ");
|
||||
|
||||
const firstName = parts[0] || "DUMMY";
|
||||
const lastName = parts.slice(1).join(" ") || "NAME";
|
||||
|
||||
const mockResponseData = {
|
||||
status_code: 200,
|
||||
data: {
|
||||
name: name || "DUMMY NAME",
|
||||
status: "ACTIVE",
|
||||
type: PAN_TYPES[pan[3]] || 'Individual',
|
||||
full_name: name || "DUMMY NAME",
|
||||
last_name: lastName,
|
||||
first_name: firstName,
|
||||
}
|
||||
};
|
||||
const response = { data: mockResponseData };
|
||||
|
||||
// Commenting out the actual external API call for now
|
||||
// const response = await axios.post(
|
||||
// process.env.PAN_PROVIDER_URL,
|
||||
// {
|
||||
// id_number: pan,
|
||||
// name: name,
|
||||
// dob: dob
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// 'Authorization': `Bearer ${process.env.PAN_PROVIDER_KEY}`,
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// timeout: 30000
|
||||
// }
|
||||
// );
|
||||
|
||||
if (!response.data || response.data.status_code !== 200) {
|
||||
return { success: false, statusCode: 404, errorCode: 'PAN_NOT_FOUND', message: 'PAN not found' };
|
||||
}
|
||||
|
||||
const d = response.data.data;
|
||||
const panType = PAN_TYPES[pan[3]] || 'Unknown';
|
||||
|
||||
const data = {
|
||||
pan,
|
||||
name: d.name || d.full_name || '',
|
||||
status: d.status || 'Valid',
|
||||
type: d.type || panType,
|
||||
name_match: name ? (d.name || d.full_name || '').toUpperCase() === name.toUpperCase() : null,
|
||||
name_match_score: name ? (d.name || d.full_name || '').toUpperCase() === name.toUpperCase() ? 100 : 0 : null,
|
||||
last_name: d.last_name || d.surname || '',
|
||||
first_name: d.first_name || '',
|
||||
middle_name: d.middle_name || '',
|
||||
title: d.title || ''
|
||||
};
|
||||
|
||||
await cacheSet(cacheKey, data, 86400);
|
||||
|
||||
// Persist PAN verification result to Postgres (best-effort, non-blocking)
|
||||
try {
|
||||
await query(
|
||||
`INSERT INTO pan_verifications (
|
||||
pan,
|
||||
name,
|
||||
status,
|
||||
pan_type,
|
||||
name_match,
|
||||
name_match_score,
|
||||
requested_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||
[
|
||||
data.pan,
|
||||
data.name,
|
||||
data.status,
|
||||
data.type,
|
||||
data.name_match,
|
||||
data.name_match_score,
|
||||
null, // requested_by (user id) - can be wired from route later if needed
|
||||
]
|
||||
);
|
||||
} catch (dbError) {
|
||||
console.error('Failed to store PAN verification in database:', dbError.message || dbError);
|
||||
}
|
||||
|
||||
return { success: true, data };
|
||||
|
||||
} catch (error) {
|
||||
// Surface provider details when available to avoid generic 500s
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
return { success: false, statusCode: 504, errorCode: 'PROVIDER_TIMEOUT', message: 'Service timeout' };
|
||||
}
|
||||
|
||||
const providerStatus = error.response?.status || error.response?.data?.status_code;
|
||||
const providerMessage = error.response?.data?.message || error.message;
|
||||
|
||||
if (providerStatus) {
|
||||
if (!panRegex.test(panUpper)) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: providerStatus,
|
||||
errorCode: error.response?.data?.error_code || 'PROVIDER_ERROR',
|
||||
message: providerMessage || 'Provider error'
|
||||
statusCode: 400,
|
||||
errorCode: 'INVALID_PAN_FORMAT',
|
||||
message: 'Invalid PAN format. PAN must be 10 characters: 5 letters, 4 digits, 1 letter.'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, statusCode: 500, errorCode: 'VERIFICATION_FAILED', message: 'Verification failed' };
|
||||
// Get Setu API credentials from environment variables
|
||||
const providerUrl = process.env.PAN_PROVIDER_URL || 'https://dg-sandbox.setu.co/api/verify/pan';
|
||||
const clientId = process.env.PAN_CLIENT_ID;
|
||||
const clientSecret = process.env.PAN_CLIENT_SECRET;
|
||||
const productInstanceId = process.env.PAN_PRODUCT_INSTANCE_ID;
|
||||
|
||||
// Validate environment variables
|
||||
if (!clientId || !clientSecret || !productInstanceId) {
|
||||
console.error('PAN API credentials missing. Check environment variables: PAN_CLIENT_ID, PAN_CLIENT_SECRET, PAN_PRODUCT_INSTANCE_ID');
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 500,
|
||||
errorCode: 'CONFIGURATION_ERROR',
|
||||
message: 'Server configuration error: Missing PAN provider credentials'
|
||||
};
|
||||
}
|
||||
|
||||
// Clean credentials (just trim whitespace)
|
||||
const cleanClientId = clientId ? clientId.trim() : '';
|
||||
const cleanClientSecret = clientSecret ? clientSecret.trim() : '';
|
||||
const cleanProductInstanceId = productInstanceId ? productInstanceId.trim() : '';
|
||||
const cleanUrl = providerUrl ? providerUrl.trim() : '';
|
||||
|
||||
// Prepare request body for Setu API
|
||||
const requestBody = {
|
||||
pan: panUpper,
|
||||
consent: 'Y',
|
||||
reason: reason || 'Identity verification for KYC compliance'
|
||||
};
|
||||
|
||||
// Call Setu API
|
||||
// Debug: log outgoing provider request (mask sensitive parts)
|
||||
try {
|
||||
const maskedSecret = cleanClientSecret ? ('****' + cleanClientSecret.slice(-4)) : null;
|
||||
console.log('[PAN Service] Sending request to provider:', {
|
||||
url: cleanUrl,
|
||||
'x-client-id': cleanClientId,
|
||||
'x-client-secret': maskedSecret,
|
||||
'x-product-instance-id': cleanProductInstanceId,
|
||||
requestBody
|
||||
});
|
||||
} catch (logErr) {
|
||||
console.error('[PAN Service] Failed to log provider request:', logErr.message);
|
||||
}
|
||||
const response = await axiosInstance.post(
|
||||
cleanUrl,
|
||||
requestBody,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'x-client-id': cleanClientId,
|
||||
'x-client-secret': cleanClientSecret,
|
||||
'x-product-instance-id': cleanProductInstanceId
|
||||
},
|
||||
timeout: 30000 // 30 second timeout
|
||||
}
|
||||
);
|
||||
|
||||
// Check if response is successful
|
||||
if (!response || response.status !== 200 || !response.data) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: response?.status || 502,
|
||||
errorCode: 'PAN_PROVIDER_ERROR',
|
||||
message: 'PAN provider returned an unexpected response'
|
||||
};
|
||||
}
|
||||
|
||||
// Extract data from Setu response
|
||||
const setuData = response.data.data || {};
|
||||
const setuMessage = response.data.message || '';
|
||||
const setuId = response.data.id || null;
|
||||
const setuVerification = response.data.verification || 'SUCCESS';
|
||||
const traceId = response.data.traceId || null;
|
||||
|
||||
// Format response to match Setu's structure
|
||||
const responseData = {
|
||||
verification: setuVerification,
|
||||
message: setuMessage,
|
||||
data: {
|
||||
full_name: setuData.full_name || null,
|
||||
first_name: setuData.first_name || null,
|
||||
middle_name: setuData.middle_name || null,
|
||||
last_name: setuData.last_name || null,
|
||||
category: setuData.category || null,
|
||||
aadhaar_seeding_status: setuData.aadhaar_seeding_status || null
|
||||
},
|
||||
id: setuId,
|
||||
traceId: traceId
|
||||
};
|
||||
|
||||
// Store in database (non-blocking, best-effort)
|
||||
query(
|
||||
`INSERT INTO pan_verifications (
|
||||
pan,
|
||||
full_name,
|
||||
first_name,
|
||||
middle_name,
|
||||
last_name,
|
||||
category,
|
||||
aadhaar_seeding_status,
|
||||
status,
|
||||
setu_response_id,
|
||||
setu_trace_id,
|
||||
setu_message,
|
||||
requested_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
||||
[
|
||||
pan.toUpperCase(),
|
||||
setuData.full_name,
|
||||
setuData.first_name,
|
||||
setuData.middle_name,
|
||||
setuData.last_name,
|
||||
setuData.category,
|
||||
setuData.aadhaar_seeding_status,
|
||||
setuVerification === 'SUCCESS' ? 'VALID' : 'INVALID',
|
||||
setuId,
|
||||
traceId,
|
||||
setuMessage,
|
||||
userId
|
||||
]
|
||||
).catch(dbError => {
|
||||
console.error('Failed to store PAN verification in database:', dbError.message);
|
||||
});
|
||||
|
||||
return { success: true, data: responseData };
|
||||
|
||||
} catch (error) {
|
||||
// Handle timeout errors
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 504,
|
||||
errorCode: 'PROVIDER_TIMEOUT',
|
||||
message: 'PAN verification service timeout'
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Setu API errors
|
||||
if (error.response) {
|
||||
const providerStatus = error.response.status;
|
||||
|
||||
// Handle Provider Authentication Errors specifically
|
||||
if (providerStatus === 401 || providerStatus === 403) {
|
||||
// Log provider auth failure details for debugging (mask any obvious secrets)
|
||||
try {
|
||||
const providerData = error.response.data;
|
||||
const providerHeaders = error.response.headers || {};
|
||||
const maskedHeaders = { ...providerHeaders };
|
||||
if (maskedHeaders['x-client-secret']) {
|
||||
maskedHeaders['x-client-secret'] = '****' + String(maskedHeaders['x-client-secret']).slice(-4);
|
||||
}
|
||||
console.error('[PAN Service] Provider Authentication Failed', {
|
||||
status: providerStatus,
|
||||
data: providerData,
|
||||
headers: maskedHeaders
|
||||
});
|
||||
} catch (logErr) {
|
||||
console.error('[PAN Service] Failed to log provider auth error:', logErr.message);
|
||||
}
|
||||
|
||||
// Surface provider message when available to aid debugging (do not expose secrets)
|
||||
const providerMsg = error.response.data?.message || error.response.data || 'Upstream provider authentication failed';
|
||||
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 502,
|
||||
errorCode: 'PAN_PROVIDER_AUTH_ERROR',
|
||||
message: typeof providerMsg === 'string' ? providerMsg : 'Upstream provider authentication failed. Check provider logs.'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// For other provider errors, return Setu-formatted error response if available
|
||||
const errorData = error.response.data || {};
|
||||
|
||||
return {
|
||||
success: false,
|
||||
statusCode: providerStatus,
|
||||
data: {
|
||||
verification: errorData.verification || 'failed',
|
||||
message: errorData.message || 'PAN verification failed',
|
||||
id: errorData.id || null,
|
||||
traceId: errorData.traceId || null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generic error
|
||||
console.error('PAN verification error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
statusCode: 500,
|
||||
errorCode: 'VERIFICATION_FAILED',
|
||||
message: 'PAN verification failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifyPAN };
|
||||
|
||||
|
||||
58
test-direct-setu.js
Normal file
58
test-direct-setu.js
Normal file
@ -0,0 +1,58 @@
|
||||
// Test script - run from project root: node test-direct-setu.js
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
|
||||
async function testDirect() {
|
||||
console.log('\n=== Testing Direct Setu API Call ===\n');
|
||||
|
||||
const clientId = process.env.PAN_CLIENT_ID;
|
||||
const clientSecret = process.env.PAN_CLIENT_SECRET;
|
||||
const productId = process.env.PAN_PRODUCT_INSTANCE_ID;
|
||||
const url = process.env.PAN_PROVIDER_URL || 'https://dg-sandbox.setu.co/api/verify/pan';
|
||||
|
||||
console.log('Loaded from .env:');
|
||||
console.log(' clientId:', clientId);
|
||||
console.log(' clientSecret:', clientSecret ? '****' + clientSecret.slice(-4) : 'MISSING');
|
||||
console.log(' productId:', productId);
|
||||
console.log(' url:', url);
|
||||
console.log('');
|
||||
|
||||
console.log('Testing with axios...');
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
url,
|
||||
{
|
||||
pan: 'ABCDE1234A',
|
||||
consent: 'Y',
|
||||
reason: 'Testing PAN verification'
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'x-client-id': clientId,
|
||||
'x-client-secret': clientSecret,
|
||||
'x-product-instance-id': productId
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
);
|
||||
|
||||
console.log('✅ SUCCESS!');
|
||||
console.log('Status:', response.status);
|
||||
console.log('Data:', JSON.stringify(response.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.log('❌ FAILED!');
|
||||
if (error.response) {
|
||||
console.log('Status:', error.response.status);
|
||||
console.log('Data:', JSON.stringify(error.response.data, null, 2));
|
||||
console.log('Headers:', JSON.stringify(error.response.headers, null, 2));
|
||||
} else {
|
||||
console.log('Error:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testDirect();
|
||||
69
test-gst-credentials.js
Normal file
69
test-gst-credentials.js
Normal file
@ -0,0 +1,69 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// Test with EXACT same approach as gstService.js
|
||||
async function testWithAxiosInstance() {
|
||||
// Create a fresh axios instance (same as in gstService.js)
|
||||
const axiosInstance = axios.create();
|
||||
|
||||
const providerUrl = 'https://dg-sandbox.setu.co/api/verify/gst';
|
||||
const clientId = '292c6e76-dabf-49c4-8e48-90fba2916673';
|
||||
const clientSecret = '7IZMe9zvoBBuBukLiCP7n4KLwSOy11oP';
|
||||
const productInstanceId = '69e23f7f-4f71-412e-aec6-b1da3fb77c6f';
|
||||
|
||||
// Clean credentials (trim whitespace) - EXACT same as gstService.js
|
||||
const cleanClientId = clientId.trim();
|
||||
const cleanClientSecret = clientSecret.trim();
|
||||
const cleanProductInstanceId = productInstanceId.trim();
|
||||
const cleanUrl = providerUrl.trim();
|
||||
|
||||
const gstin = '29AABCT1332L1ZV';
|
||||
|
||||
console.log('Testing with axios instance (EXACT same as gstService.js)...\n');
|
||||
console.log('Cleaned credentials:');
|
||||
console.log(' URL:', cleanUrl);
|
||||
console.log(' Client ID:', cleanClientId);
|
||||
console.log(' Client Secret:', '****' + cleanClientSecret.slice(-4));
|
||||
console.log(' Product Instance ID:', cleanProductInstanceId);
|
||||
console.log(' GSTIN:', gstin);
|
||||
console.log('\n---\n');
|
||||
|
||||
try {
|
||||
const response = await axiosInstance.post(
|
||||
cleanUrl,
|
||||
{ gstin },
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'x-client-id': cleanClientId,
|
||||
'x-client-secret': cleanClientSecret,
|
||||
'x-product-instance-id': cleanProductInstanceId
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
);
|
||||
|
||||
console.log('✅ SUCCESS!');
|
||||
console.log('Status:', response.status);
|
||||
console.log('Data:', JSON.stringify(response.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.log('❌ FAILED');
|
||||
console.log('Status:', error.response.status);
|
||||
console.log('Error:', JSON.stringify(error.response.data, null, 2));
|
||||
console.log('\n---\n');
|
||||
|
||||
// Log the actual request that was sent
|
||||
console.log('Request config:');
|
||||
console.log(' URL:', error.config.url);
|
||||
console.log(' Method:', error.config.method);
|
||||
console.log(' Headers:', JSON.stringify(error.config.headers, null, 2));
|
||||
console.log(' Data:', error.config.data);
|
||||
} else {
|
||||
console.log('❌ ERROR:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testWithAxiosInstance();
|
||||
Loading…
Reference in New Issue
Block a user