Reconcile Your Daily Transactions
Learn how to reconcile daily transactions with NextAPI to ensure accurate financial records and identify any discrepancies.
Overview
Transaction reconciliation is crucial for:
- Financial accuracy and compliance
- Identifying failed or pending transactions
- Maintaining proper audit trails
- Detecting fraud or unusual activity
Daily Reconciliation Process
1. Fetch Daily Transactions
async function getDailyTransactions(date) {
const startDate = new Date(date);
const endDate = new Date(date);
endDate.setDate(endDate.getDate() + 1);
const response = await fetch(`https://api.nextpay.world/v2/transactions`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
limit: 1000
})
});
return await response.json();
}
2. Categorize Transactions
function categorizeTransactions(transactions) {
return {
successful: transactions.filter(t => t.status === 'COMPLETED'),
failed: transactions.filter(t => t.status === 'FAILED'),
pending: transactions.filter(t => t.status === 'PENDING'),
refunds: transactions.filter(t => t.type === 'REFUND'),
payouts: transactions.filter(t => t.type === 'PAYOUT')
};
}
3. Calculate Daily Totals
function calculateDailyTotals(transactions) {
const totals = {
totalAmount: 0,
successfulAmount: 0,
failedAmount: 0,
refundAmount: 0,
payoutAmount: 0,
transactionCount: 0,
successRate: 0
};
transactions.forEach(transaction => {
totals.totalAmount += transaction.amount;
totals.transactionCount++;
if (transaction.status === 'COMPLETED') {
totals.successfulAmount += transaction.amount;
} else if (transaction.status === 'FAILED') {
totals.failedAmount += transaction.amount;
}
if (transaction.type === 'REFUND') {
totals.refundAmount += transaction.amount;
} else if (transaction.type === 'PAYOUT') {
totals.payoutAmount += transaction.amount;
}
});
totals.successRate = (totals.successfulAmount / totals.totalAmount) * 100;
return totals;
}
Automated Reconciliation Script
Daily Reconciliation Report
class DailyReconciler {
constructor(apiKey) {
this.apiKey = apiKey;
this.alertThresholds = {
failureRate: 5, // 5% failure rate threshold
pendingTransactions: 10, // Max 10 pending transactions
largeTransaction: 100000 // Alert on transactions > 100k
};
}
async runDailyReconciliation(date = new Date()) {
console.log(`Starting daily reconciliation for ${date.toDateString()}`);
try {
// 1. Fetch transactions
const transactions = await this.getDailyTransactions(date);
// 2. Categorize and analyze
const categorized = this.categorizeTransactions(transactions);
const totals = this.calculateDailyTotals(transactions);
// 3. Generate report
const report = this.generateReport(date, categorized, totals);
// 4. Check for alerts
await this.checkAlerts(categorized, totals);
// 5. Store reconciliation record
await this.storeReconciliationRecord(date, report);
console.log('Daily reconciliation completed successfully');
return report;
} catch (error) {
console.error('Daily reconciliation failed:', error);
await this.sendAlert('Reconciliation Failed', error);
throw error;
}
}
async getDailyTransactions(date) {
const startDate = new Date(date);
const endDate = new Date(date);
endDate.setDate(endDate.getDate() + 1);
const response = await fetch('https://api.nextpay.world/v2/transactions', {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
return data.transactions || [];
}
categorizeTransactions(transactions) {
return {
successful: transactions.filter(t => t.status === 'COMPLETED'),
failed: transactions.filter(t => t.status === 'FAILED'),
pending: transactions.filter(t => t.status === 'PENDING'),
refunds: transactions.filter(t => t.type === 'REFUND'),
payouts: transactions.filter(t => t.type === 'PAYOUT')
};
}
calculateDailyTotals(transactions) {
const totals = {
totalAmount: 0,
successfulAmount: 0,
failedAmount: 0,
refundAmount: 0,
payoutAmount: 0,
transactionCount: transactions.length,
successRate: 0
};
transactions.forEach(transaction => {
totals.totalAmount += transaction.amount;
if (transaction.status === 'COMPLETED') {
totals.successfulAmount += transaction.amount;
} else if (transaction.status === 'FAILED') {
totals.failedAmount += transaction.amount;
}
if (transaction.type === 'REFUND') {
totals.refundAmount += transaction.amount;
} else if (transaction.type === 'PAYOUT') {
totals.payoutAmount += transaction.amount;
}
});
if (totals.totalAmount > 0) {
totals.successRate = (totals.successfulAmount / totals.totalAmount) * 100;
}
return totals;
}
generateReport(date, categorized, totals) {
return {
date: date.toISOString(),
summary: {
totalTransactions: totals.transactionCount,
totalAmount: totals.totalAmount,
successRate: totals.successRate.toFixed(2) + '%'
},
breakdown: {
successful: {
count: categorized.successful.length,
amount: categorized.successful.reduce((sum, t) => sum + t.amount, 0)
},
failed: {
count: categorized.failed.length,
amount: categorized.failed.reduce((sum, t) => sum + t.amount, 0)
},
pending: {
count: categorized.pending.length,
amount: categorized.pending.reduce((sum, t) => sum + t.amount, 0)
}
},
alerts: []
};
}
async checkAlerts(categorized, totals) {
const alerts = [];
// Check failure rate
if (totals.successRate < (100 - this.alertThresholds.failureRate)) {
alerts.push({
type: 'HIGH_FAILURE_RATE',
message: `Failure rate is ${(100 - totals.successRate).toFixed(2)}%`,
severity: 'HIGH'
});
}
// Check pending transactions
if (categorized.pending.length > this.alertThresholds.pendingTransactions) {
alerts.push({
type: 'HIGH_PENDING_COUNT',
message: `${categorized.pending.length} transactions pending`,
severity: 'MEDIUM'
});
}
// Check for large transactions
const largeTransactions = categorized.successful.filter(t => t.amount > this.alertThresholds.largeTransaction);
if (largeTransactions.length > 0) {
alerts.push({
type: 'LARGE_TRANSACTIONS',
message: `${largeTransactions.length} large transactions detected`,
severity: 'MEDIUM',
data: largeTransactions
});
}
// Send alerts if any
for (const alert of alerts) {
await this.sendAlert(alert.type, alert.message, alert.severity);
}
return alerts;
}
async sendAlert(type, message, severity = 'MEDIUM') {
console.log(`ALERT [${severity}]: ${type} - ${message}`);
// Send to your monitoring system
await fetch('https://api.nextpay.world/v2/alerts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: type,
message: message,
severity: severity,
timestamp: new Date().toISOString()
})
});
}
async storeReconciliationRecord(date, report) {
// Store in your database or logging system
await fetch('https://api.nextpay.world/v2/reconciliation/records', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
date: date.toISOString(),
report: report,
reconciledAt: new Date().toISOString()
})
});
}
}
Manual Reconciliation
1. Export Transaction Data
async function exportTransactionsForReconciliation(startDate, endDate) {
const response = await fetch('https://api.nextpay.world/v2/transactions/export', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
format: 'csv'
})
});
return await response.blob(); // CSV file for download
}
2. Compare with Internal Records
async function reconcileWithInternalRecords(nextpayTransactions, internalRecords) {
const discrepancies = [];
// Match transactions by ID or reference
for (const nextpayTx of nextpayTransactions) {
const internalTx = internalRecords.find(r => r.reference === nextpayTx.reference);
if (!internalTx) {
discrepancies.push({
type: 'MISSING_INTERNAL_RECORD',
nextpayTransaction: nextpayTx,
message: 'Transaction exists in NextAPI but not in internal records'
});
} else if (internalTx.amount !== nextpayTx.amount) {
discrepancies.push({
type: 'AMOUNT_MISMATCH',
nextpayTransaction: nextpayTx,
internalTransaction: internalTx,
message: `Amount mismatch: NextAPI ${nextpayTx.amount} vs Internal ${internalTx.amount}`
});
} else if (internalTx.status !== nextpayTx.status) {
discrepancies.push({
type: 'STATUS_MISMATCH',
nextpayTransaction: nextpayTx,
internalTransaction: internalTx,
message: `Status mismatch: NextAPI ${nextpayTx.status} vs Internal ${internalTx.status}`
});
}
}
// Check for transactions in internal records but not in NextAPI
for (const internalTx of internalRecords) {
const nextpayTx = nextpayTransactions.find(t => t.reference === internalTx.reference);
if (!nextpayTx) {
discrepancies.push({
type: 'MISSING_NEXTPAY_RECORD',
internalTransaction: internalTx,
message: 'Transaction exists in internal records but not in NextAPI'
});
}
}
return discrepancies;
}
Best Practices
1. Schedule Regular Reconciliation
// Set up daily reconciliation job
const cron = require('node-cron');
const reconciler = new DailyReconciler('YOUR_API_KEY');
// Run every day at 2 AM
cron.schedule('0 2 * * *', async () => {
try {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await reconciler.runDailyReconciliation(yesterday);
} catch (error) {
console.error('Scheduled reconciliation failed:', error);
}
});
2. Maintain Audit Trail
async function logReconciliationActivity(activity) {
await fetch('https://api.nextpay.world/v2/audit-log', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
activity: 'RECONCILIATION',
details: activity,
timestamp: new Date().toISOString(),
userId: 'system'
})
});
}
3. Handle Time Zones
function getBusinessDayDate(date, timeZone = 'Asia/Manila') {
return new Date(date.toLocaleString('en-US', { timeZone }));
}
Testing Reconciliation
1. Test Data Validation
async function testReconciliation() {
// Create test transactions
const testTransactions = [
{ id: '1', amount: 1000, status: 'COMPLETED', type: 'PAYMENT' },
{ id: '2', amount: 500, status: 'FAILED', type: 'PAYMENT' },
{ id: '3', amount: 2000, status: 'PENDING', type: 'PAYOUT' }
];
const reconciler = new DailyReconciler('TEST_API_KEY');
const totals = reconciler.calculateDailyTotals(testTransactions);
console.log('Test totals:', totals);
// Verify calculations
console.assert(totals.totalAmount === 3500, 'Total amount mismatch');
console.assert(totals.successRate === (1000/3500)*100, 'Success rate mismatch');
}
Conclusion
Implementing daily transaction reconciliation ensures:
- ✅ Financial accuracy and compliance
- ✅ Early detection of issues
- ✅ Comprehensive audit trails
- ✅ Automated monitoring and alerting
- ✅ Better financial control and visibility
Ready to implement reconciliation? Start with our transaction API guide and add these reconciliation patterns to your daily operations.