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.