Skip to main content

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.