Add self-healing for pincode taluk field

This commit is contained in:
Tech4Biz 2025-12-19 15:14:13 +05:30
parent dc49d6f432
commit 5c964d1917
9 changed files with 1909 additions and 30 deletions

1
data/IFSC.csv Normal file
View File

@ -0,0 +1 @@
404: Not Found
1 404: Not Found

1446
data/IFSC_complete.csv Normal file

File diff suppressed because one or more lines are too long

1
data/rbi_ifsc.json Normal file
View File

@ -0,0 +1 @@
404: Not Found

113
src/database/importIFSC.js Normal file
View File

@ -0,0 +1,113 @@
/**
* Import comprehensive IFSC data from Razorpay API
* This script fetches IFSC codes for major banks and imports them to the database
*/
const axios = require('axios');
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:fXlNbqBpxhgchmrvPtlMKgwttWVdMDlO@shuttle.proxy.rlwy.net:23879/railway'
});
// Major Indian banks with their IFSC prefixes
const BANK_PREFIXES = [
{ prefix: 'SBIN', name: 'State Bank of India', count: 100 },
{ prefix: 'HDFC', name: 'HDFC Bank', count: 100 },
{ prefix: 'ICIC', name: 'ICICI Bank', count: 100 },
{ prefix: 'KKBK', name: 'Kotak Mahindra Bank', count: 50 },
{ prefix: 'UTIB', name: 'Axis Bank', count: 50 },
{ prefix: 'PUNB', name: 'Punjab National Bank', count: 50 },
{ prefix: 'BARB', name: 'Bank of Baroda', count: 50 },
{ prefix: 'CNRB', name: 'Canara Bank', count: 50 },
{ prefix: 'UBIN', name: 'Union Bank of India', count: 50 },
{ prefix: 'IOBA', name: 'Indian Overseas Bank', count: 30 },
{ prefix: 'IDIB', name: 'Indian Bank', count: 30 },
{ prefix: 'BKID', name: 'Bank of India', count: 30 },
{ prefix: 'CBIN', name: 'Central Bank of India', count: 30 },
{ prefix: 'YESB', name: 'Yes Bank', count: 30 },
{ prefix: 'INDB', name: 'IndusInd Bank', count: 30 },
{ prefix: 'FDRL', name: 'Federal Bank', count: 30 },
{ prefix: 'KVBL', name: 'Karur Vysya Bank', count: 20 },
{ prefix: 'TMBL', name: 'Tamilnad Mercantile Bank', count: 20 },
{ prefix: 'RATN', name: 'RBL Bank', count: 20 },
{ prefix: 'IDFB', name: 'IDFC First Bank', count: 20 },
];
async function fetchAndInsertIFSC(prefix, bankName, count) {
const inserted = [];
const failed = [];
// Generate IFSC codes to try
for (let i = 0; i <= count; i++) {
const ifsc = `${prefix}${String(i).padStart(7, '0')}`;
try {
const response = await axios.get(`https://ifsc.razorpay.com/${ifsc}`, { timeout: 5000 });
const data = response.data;
// Insert into database
await pool.query(
`INSERT INTO ifsc_codes (ifsc, bank_name, branch, address, city, district, state, contact, upi_enabled, rtgs_enabled, neft_enabled, imps_enabled, micr_code, swift_code, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW())
ON CONFLICT (ifsc) DO UPDATE SET
bank_name = EXCLUDED.bank_name,
branch = EXCLUDED.branch,
address = EXCLUDED.address,
city = EXCLUDED.city,
updated_at = NOW()`,
[
ifsc,
bankName,
data.BRANCH || '',
data.ADDRESS || '',
data.CITY || '',
data.DISTRICT || '',
data.STATE || '',
data.CONTACT || '',
data.UPI || false,
data.RTGS || false,
data.NEFT || false,
data.IMPS || false,
data.MICR || '',
data.SWIFT || ''
]
);
inserted.push(ifsc);
process.stdout.write(`${ifsc} `);
} catch (error) {
if (error.response && error.response.status === 404) {
// IFSC doesn't exist, skip
} else {
failed.push(ifsc);
}
}
// Small delay to not overwhelm the API
await new Promise(resolve => setTimeout(resolve, 100));
}
return { inserted: inserted.length, failed: failed.length };
}
async function main() {
console.log('🚀 Starting comprehensive IFSC import...\n');
let totalInserted = 0;
for (const bank of BANK_PREFIXES) {
console.log(`\n📦 Fetching ${bank.name} (${bank.prefix})...`);
const result = await fetchAndInsertIFSC(bank.prefix, bank.name, bank.count);
totalInserted += result.inserted;
console.log(`\n ✅ Inserted: ${result.inserted} codes`);
}
console.log(`\n==================================================`);
console.log(`🎉 Total IFSC codes imported: ${totalInserted}`);
console.log(`==================================================\n`);
await pool.end();
}
main().catch(console.error);

View File

@ -0,0 +1,92 @@
/**
* Import missing metro city pincodes from India Post API
*/
const axios = require('axios');
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:fXlNbqBpxhgchmrvPtlMKgwttWVdMDlO@shuttle.proxy.rlwy.net:23879/railway'
});
// Major metro pincodes that need to be added
const METRO_PINCODES = [
// Mumbai
'400001', '400002', '400003', '400004', '400005', '400006', '400007', '400008', '400009', '400010',
'400011', '400012', '400013', '400015', '400016', '400017', '400018', '400019', '400020', '400021',
'400022', '400023', '400024', '400025', '400026', '400027', '400028', '400029', '400030', '400031',
'400032', '400033', '400034', '400036', '400037', '400038', '400039', '400042', '400043', '400049',
'400050', '400051', '400052', '400053', '400054', '400055', '400056', '400057', '400058', '400059',
'400060', '400061', '400062', '400063', '400064', '400065', '400066', '400067', '400068', '400069',
'400070', '400071', '400072', '400074', '400075', '400076', '400077', '400078', '400079', '400080',
'400081', '400082', '400083', '400084', '400085', '400086', '400087', '400088', '400089', '400091',
'400092', '400093', '400094', '400095', '400096', '400097', '400098', '400099', '400101', '400102',
// Chennai
'600001', '600002', '600003', '600004', '600005', '600006', '600007', '600008', '600009', '600010',
'600011', '600012', '600013', '600014', '600015', '600016', '600017', '600018', '600019', '600020',
'600021', '600022', '600023', '600024', '600025', '600026', '600027', '600028', '600029', '600030',
'600031', '600032', '600033', '600034', '600035', '600036', '600037', '600038', '600039', '600040',
// Pune
'411001', '411002', '411003', '411004', '411005', '411006', '411007', '411008', '411009', '411010',
'411011', '411012', '411013', '411014', '411015', '411016', '411017', '411018', '411019', '411020',
];
async function fetchAndInsertPincode(pincode) {
try {
const response = await axios.get(`https://api.postalpincode.in/pincode/${pincode}`, { timeout: 10000 });
if (response.data && response.data[0] && response.data[0].Status === 'Success') {
const postOffices = response.data[0].PostOffice;
for (const po of postOffices) {
await pool.query(
`INSERT INTO pincodes (pincode, office_name, office_type, district, division, region, state, latitude, longitude, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
ON CONFLICT (pincode, office_name) DO UPDATE SET
district = EXCLUDED.district,
state = EXCLUDED.state,
updated_at = NOW()`,
[
pincode,
po.Name || '',
po.BranchType || 'PO',
po.District || '',
po.Division || '',
po.Region || '',
po.State || '',
'0',
'0'
]
);
}
return postOffices.length;
}
return 0;
} catch (error) {
return 0;
}
}
async function main() {
console.log('🚀 Starting metro pincode import...\n');
let totalInserted = 0;
for (const pincode of METRO_PINCODES) {
const count = await fetchAndInsertPincode(pincode);
if (count > 0) {
process.stdout.write(`${pincode} (${count}) `);
totalInserted += count;
}
// Delay to respect API rate limits
await new Promise(resolve => setTimeout(resolve, 200));
}
console.log(`\n\n==================================================`);
console.log(`🎉 Total post offices imported: ${totalInserted}`);
console.log(`==================================================\n`);
await pool.end();
}
main().catch(console.error);

View File

@ -118,6 +118,7 @@ CREATE TABLE IF NOT EXISTS pincodes (
division VARCHAR(100),
region VARCHAR(100),
state VARCHAR(100),
block VARCHAR(100),
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
updated_at TIMESTAMP DEFAULT NOW()

View File

@ -25,7 +25,7 @@ function generateApiKey() {
*/
router.post('/signup', async (req, res, next) => {
try {
const { email, password, company_name, phone } = req.body;
const { email, password, name, phone } = req.body;
if (!email || !password) {
throw new ApiError(400, 'MISSING_FIELDS', 'Email and password required');
@ -49,7 +49,7 @@ router.post('/signup', async (req, res, next) => {
`INSERT INTO users (email, password_hash, company_name, phone, plan, monthly_quota, quota_reset_date)
VALUES ($1, $2, $3, $4, 'free', 100, DATE(NOW() + INTERVAL '1 month'))
RETURNING id, email, company_name, plan`,
[email.toLowerCase(), passwordHash, company_name, phone]
[email.toLowerCase(), passwordHash, name, phone]
);
const user = result.rows[0];
@ -79,7 +79,7 @@ router.post('/signup', async (req, res, next) => {
user: {
id: user.id,
email: user.email,
company_name: user.company_name,
name: user.company_name,
plan: user.plan
},
api_key: apiKey,
@ -139,7 +139,7 @@ router.post('/login', async (req, res, next) => {
user: {
id: user.id,
email: user.email,
company_name: user.company_name,
name: user.company_name,
plan: user.plan,
quota: user.monthly_quota,
used: user.calls_this_month

View File

@ -1,16 +1,94 @@
/**
* IFSC Lookup Route
* Returns bank details for a given IFSC code
* Supports local database + Razorpay API fallback
*/
const express = require('express');
const router = express.Router();
const axios = require('axios');
const { authenticateApiKey } = require('../middleware/auth');
const { rateLimit } = require('../middleware/rateLimit');
const { query } = require('../database/connection');
const { cacheGet, cacheSet } = require('../cache/redis');
const { logApiCall } = require('../services/analytics');
/**
* Fetch IFSC from Razorpay API and save to database
*/
async function fetchFromRazorpayAPI(ifsc) {
try {
console.log(`🌐 Fetching IFSC ${ifsc} from Razorpay API...`);
const response = await axios.get(`https://ifsc.razorpay.com/${ifsc}`, {
timeout: 10000
});
if (response.data && response.data.BANK) {
const data = response.data;
try {
// Save to database for future lookups
await query(
`INSERT INTO ifsc_codes (ifsc, bank_name, branch, address, city, district, state, contact, upi_enabled, rtgs_enabled, neft_enabled, imps_enabled, micr_code, swift_code, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW())`,
[
ifsc,
data.BANK || '',
data.BRANCH || '',
data.ADDRESS || '',
data.CITY || '',
data.DISTRICT || '',
data.STATE || '',
data.CONTACT || '',
data.UPI || false,
data.RTGS || false,
data.NEFT || false,
data.IMPS || false,
data.MICR || '',
data.SWIFT || null
]
);
console.log(`✅ Saved IFSC ${ifsc} to database`);
} catch (insertError) {
// Ignore duplicate errors
console.log(`⚠️ Could not save to DB (might already exist):`, insertError.message);
}
// Return normalized data
return {
success: true,
data: {
ifsc: ifsc,
bank_name: data.BANK || '',
branch: data.BRANCH || '',
address: data.ADDRESS || '',
city: data.CITY || '',
district: data.DISTRICT || '',
state: data.STATE || '',
contact: data.CONTACT || '',
upi_enabled: data.UPI || false,
rtgs_enabled: data.RTGS || false,
neft_enabled: data.NEFT || false,
imps_enabled: data.IMPS || false,
micr_code: data.MICR || '',
swift_code: data.SWIFT || null
},
source: 'RAZORPAY_API'
};
}
return { success: false, error: 'Invalid IFSC code' };
} catch (error) {
if (error.response && error.response.status === 404) {
console.log(`❌ IFSC ${ifsc} not found in Razorpay API`);
return { success: false, error: 'IFSC code not found' };
}
console.error(`❌ Razorpay API error for ${ifsc}:`, error.message);
return { success: false, error: error.message };
}
}
router.use(authenticateApiKey);
router.use(rateLimit);
@ -133,6 +211,7 @@ router.get('/state/:stateName', async (req, res, next) => {
router.get('/:ifsc_code', async (req, res, next) => {
const startTime = Date.now();
let success = false;
let source = 'LOCAL_DB';
try {
const { ifsc_code } = req.params;
@ -149,17 +228,30 @@ router.get('/:ifsc_code', async (req, res, next) => {
const cacheKey = `ifsc:${ifsc}`;
let data = await cacheGet(cacheKey);
if (!data) {
if (data) {
source = 'CACHE';
} else {
// Try local database first
const result = await query('SELECT * FROM ifsc_codes WHERE ifsc = $1', [ifsc]);
if (result.rows.length === 0) {
// Fallback to Razorpay API
console.log(`📦 IFSC ${ifsc} not in DB, trying Razorpay API...`);
const apiResult = await fetchFromRazorpayAPI(ifsc);
if (!apiResult.success) {
return res.status(404).json({
success: false,
error: { code: 'IFSC_NOT_FOUND', message: 'IFSC not found' }
error: { code: 'IFSC_NOT_FOUND', message: 'IFSC not found in any source' }
});
}
data = apiResult.data;
source = 'RAZORPAY_API';
} else {
data = result.rows[0];
}
await cacheSet(cacheKey, data, 86400); // Cache for 24 hours
}
@ -169,6 +261,7 @@ router.get('/:ifsc_code', async (req, res, next) => {
success: true,
data: {
ifsc: data.ifsc,
source,
bank: data.bank_name,
branch: data.branch,
address: data.address,

View File

@ -1,16 +1,112 @@
/**
* Pincode Lookup Route
* Returns location details for a given pincode
* Supports local database + India Post API fallback
*/
const express = require('express');
const router = express.Router();
const axios = require('axios');
const { authenticateApiKey } = require('../middleware/auth');
const { rateLimit } = require('../middleware/rateLimit');
const { query } = require('../database/connection');
const { cacheGet, cacheSet } = require('../cache/redis');
const { logApiCall } = require('../services/analytics');
/**
* Fetch pincode from India Post API and save to database
*/
async function fetchFromIndiaPostAPI(pincode) {
try {
console.log(`🌐 Fetching pincode ${pincode} from India Post API...`);
const response = await axios.get(`https://api.postalpincode.in/pincode/${pincode}`, {
timeout: 10000
});
if (response.data && response.data[0] && response.data[0].Status === 'Success') {
const postOffices = response.data[0].PostOffice;
const savedRecords = [];
for (const po of postOffices) {
try {
// Save to database for future lookups (UPSERT for self-healing)
await query(
`INSERT INTO pincodes (pincode, office_name, office_type, district, division, region, state, block, latitude, longitude, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
ON CONFLICT (id)
DO UPDATE SET
office_name = EXCLUDED.office_name,
office_type = EXCLUDED.office_type,
district = EXCLUDED.district,
division = EXCLUDED.division,
region = EXCLUDED.region,
state = EXCLUDED.state,
block = EXCLUDED.block,
latitude = EXCLUDED.latitude,
longitude = EXCLUDED.longitude,
updated_at = NOW()`,
[
pincode,
po.Name || '',
po.BranchType || 'PO',
po.District || '',
po.Division || '',
po.Region || '',
po.State || '',
po.Block || '',
'0',
'0'
]
);
} catch (insertError) {
// Try to update if insert fails
try {
await query(
`UPDATE pincodes
SET office_type = $3, district = $4, division = $5, region = $6, state = $7, block = $8, updated_at = NOW()
WHERE pincode = $1 AND office_name = $2`,
[
pincode,
po.Name || '',
po.BranchType || 'PO',
po.District || '',
po.Division || '',
po.Region || '',
po.State || '',
po.Block || ''
]
);
} catch (updateError) {
// Ignore errors
}
}
savedRecords.push({
pincode: pincode,
office_name: po.Name || '',
office_type: po.BranchType || 'PO',
district: po.District || '',
division: po.Division || '',
region: po.Region || '',
state: po.State || '',
block: po.Block || '',
latitude: '0',
longitude: '0'
});
}
console.log(`✅ Fetched and saved ${savedRecords.length} offices for pincode ${pincode}`);
return { success: true, data: savedRecords, source: 'INDIA_POST_API' };
}
return { success: false, error: 'Invalid pincode' };
} catch (error) {
console.error(`❌ India Post API error for ${pincode}:`, error.message);
return { success: false, error: error.message };
}
}
const STATE_CODES = {
'Andhra Pradesh': 'AP', 'Arunachal Pradesh': 'AR', 'Assam': 'AS', 'Bihar': 'BR',
'Chhattisgarh': 'CG', 'Delhi': 'DL', 'Goa': 'GA', 'Gujarat': 'GJ', 'Haryana': 'HR',
@ -112,11 +208,12 @@ router.get('/search', async (req, res, next) => {
/**
* GET /v1/pincode/:pincode
* Lookup pincode details
* Lookup pincode details with India Post API fallback
*/
router.get('/:pincode', async (req, res, next) => {
const startTime = Date.now();
let success = false;
let source = 'LOCAL_DB';
try {
const { pincode } = req.params;
@ -130,18 +227,55 @@ router.get('/:pincode', async (req, res, next) => {
const cacheKey = `pincode:${pincode}`;
let data = await cacheGet(cacheKey);
let needsRefresh = false;
if (data) {
source = 'CACHE';
// Self-healing: Check if taluk is missing
if (data.length > 0 && (!data[0].block || data[0].block === '')) {
console.log(`🔄 Self-healing: Taluk missing for pincode ${pincode}, refetching...`);
needsRefresh = true;
data = null; // Force refetch
}
}
if (!data) {
// Try local database first
const result = await query('SELECT * FROM pincodes WHERE pincode = $1', [pincode]);
if (result.rows.length === 0) {
if (result.rows.length === 0 || needsRefresh) {
// Fallback to India Post API (or forced refresh for self-healing)
if (needsRefresh) {
console.log(`🔄 Refreshing pincode ${pincode} with India Post API...`);
} else {
console.log(`📦 Pincode ${pincode} not in DB, trying India Post API...`);
}
const apiResult = await fetchFromIndiaPostAPI(pincode);
if (!apiResult.success) {
return res.status(404).json({
success: false,
error: { code: 'PINCODE_NOT_FOUND', message: 'Pincode not found' }
error: { code: 'PINCODE_NOT_FOUND', message: 'Pincode not found in any source' }
});
}
data = apiResult.data;
source = 'INDIA_POST_API';
} else {
data = result.rows;
// Double-check: if DB data also has empty taluk, force refresh
if (data.length > 0 && (!data[0].block || data[0].block === '')) {
console.log(`🔄 Self-healing: DB data also missing taluk for ${pincode}, refetching...`);
const apiResult = await fetchFromIndiaPostAPI(pincode);
if (apiResult.success) {
data = apiResult.data;
source = 'INDIA_POST_API';
}
}
}
await cacheSet(cacheKey, data, 604800); // Cache for 7 days
}
@ -152,19 +286,17 @@ router.get('/:pincode', async (req, res, next) => {
success: true,
data: {
pincode,
locations: data.map(row => ({
office_name: row.office_name,
office_type: row.office_type,
district: row.district,
state: row.state,
latitude: parseFloat(row.latitude) || null,
longitude: parseFloat(row.longitude) || null
})),
primary: {
source,
district: primary.district,
state: primary.state,
state_code: STATE_CODES[primary.state] || ''
}
state_code: STATE_CODES[primary.state] || '',
latitude: parseFloat(primary.latitude) || null,
longitude: parseFloat(primary.longitude) || null,
offices: data.map(office => ({
name: office.office_name,
type: office.office_type,
taluk: office.block || ''
}))
},
meta: {
request_id: `req_pin_${Date.now()}`,