Account Reconciliation
Account reconciliation ensures your Omise transaction records match your bank statements and accounting systems. This guide covers reconciliation processes, automated matching, and best practices.
Overviewโ
Reconciliation is essential for:
- Accurate Accounting: Ensure financial records are correct
- Fraud Detection: Identify unauthorized transactions
- Compliance: Meet regulatory requirements
- Cash Flow Management: Track actual vs expected funds
- Error Identification: Catch processing issues early
- Audit Trail: Maintain complete financial records
Key Conceptsโ
- Settlement: Batch of transactions paid to your bank account
- Settlement Date: When funds arrive in your bank
- Transaction Date: When customer payment occurred
- Net Amount: Settlement amount after fees
- Gross Amount: Total payment amount before fees
Understanding Settlementsโ
Settlement Cycleโ
const omise = require('omise')({
secretKey: 'skey_test_123456789',
});
// Get recent transfers (settlements)
async function getRecentSettlements(days = 30) {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const transfers = await omise.transfers.list({
from: startDate.toISOString(),
to: endDate.toISOString(),
limit: 100
});
console.log(`Found ${transfers.data.length} settlements`);
transfers.data.forEach(transfer => {
console.log(`Transfer ${transfer.id}:`);
console.log(` Amount: ${transfer.amount / 100} ${transfer.currency.toUpperCase()}`);
console.log(` Fee: ${transfer.fee / 100} ${transfer.currency.toUpperCase()}`);
console.log(` Date: ${new Date(transfer.created).toLocaleDateString()}`);
});
return transfers.data;
}
// Calculate settlement total
function calculateSettlementTotal(settlement) {
return {
gross_amount: settlement.amount,
fee: settlement.fee,
net_amount: settlement.amount - settlement.fee,
currency: settlement.currency,
formatted: {
gross: `${settlement.amount / 100} ${settlement.currency.toUpperCase()}`,
fee: `${settlement.fee / 100} ${settlement.currency.toUpperCase()}`,
net: `${(settlement.amount - settlement.fee) / 100} ${settlement.currency.toUpperCase()}`
}
};
}
// Example usage
getRecentSettlements(30);
Reconciliation Processโ
Basic Reconciliationโ
- Node.js
- Python
- Ruby
class ReconciliationManager {
constructor() {
this.discrepancies = [];
}
async reconcileWithBankStatement(bankStatement, startDate, endDate) {
// Get Omise settlements
const settlements = await this.getOmiseSettlements(startDate, endDate);
// Get Omise transactions
const transactions = await this.getOmiseTransactions(startDate, endDate);
// Match settlements with bank entries
const matchResults = this.matchSettlements(settlements, bankStatement);
// Generate reconciliation report
const report = this.generateReconciliationReport(
settlements,
transactions,
bankStatement,
matchResults
);
return report;
}
async getOmiseSettlements(startDate, endDate) {
const transfers = await omise.transfers.list({
from: startDate.toISOString(),
to: endDate.toISOString(),
limit: 100
});
return transfers.data.map(t => ({
id: t.id,
date: new Date(t.created),
amount: t.amount - t.fee, // Net amount
gross: t.amount,
fee: t.fee,
currency: t.currency,
status: t.paid ? 'paid' : 'pending'
}));
}
async getOmiseTransactions(startDate, endDate) {
const transactions = await omise.transactions.list({
from: startDate.toISOString(),
to: endDate.toISOString(),
limit: 1000
});
return transactions.data;
}
matchSettlements(settlements, bankStatement) {
const matched = [];
const unmatchedOmise = [];
const unmatchedBank = [];
const bankEntries = [...bankStatement];
settlements.forEach(settlement => {
const match = this.findMatchingBankEntry(settlement, bankEntries);
if (match) {
matched.push({
settlement: settlement,
bankEntry: match.entry,
confidence: match.confidence
});
// Remove matched entry
bankEntries.splice(match.index, 1);
} else {
unmatchedOmise.push(settlement);
}
});
unmatchedBank.push(...bankEntries);
return {
matched,
unmatchedOmise,
unmatchedBank,
matchRate: (matched.length / settlements.length * 100).toFixed(2)
};
}
findMatchingBankEntry(settlement, bankEntries) {
for (let i = 0; i < bankEntries.length; i++) {
const entry = bankEntries[i];
// Match by amount (allowing small difference for fees)
const amountDiff = Math.abs(settlement.amount - entry.amount);
// Match by date (allowing a few days difference)
const daysDiff = Math.abs(
(settlement.date - new Date(entry.date)) / (1000 * 60 * 60 * 24)
);
if (amountDiff < 100 && daysDiff <= 3) {
const confidence = this.calculateMatchConfidence(
amountDiff,
daysDiff
);
return {
entry: entry,
index: i,
confidence: confidence
};
}
}
return null;
}
calculateMatchConfidence(amountDiff, daysDiff) {
let confidence = 100;
// Reduce confidence based on amount difference
confidence -= (amountDiff / 100) * 5;
// Reduce confidence based on date difference
confidence -= daysDiff * 10;
return Math.max(0, confidence);
}
generateReconciliationReport(settlements, transactions, bankStatement, matchResults) {
return {
period: {
start: settlements[0]?.date,
end: settlements[settlements.length - 1]?.date
},
summary: {
total_settlements: settlements.length,
total_bank_entries: bankStatement.length,
matched: matchResults.matched.length,
unmatched_omise: matchResults.unmatchedOmise.length,
unmatched_bank: matchResults.unmatchedBank.length,
match_rate: matchResults.matchRate + '%'
},
totals: {
omise_total: settlements.reduce((sum, s) => sum + s.amount, 0),
bank_total: bankStatement.reduce((sum, e) => sum + e.amount, 0),
difference: 0
},
matched: matchResults.matched,
discrepancies: {
unmatched_omise: matchResults.unmatchedOmise,
unmatched_bank: matchResults.unmatchedBank
}
};
}
printReconciliationReport(report) {
console.log('\n' + '='.repeat(60));
console.log('RECONCILIATION REPORT');
console.log('='.repeat(60));
console.log(`Period: ${report.period.start.toLocaleDateString()} - ${report.period.end.toLocaleDateString()}`);
console.log('\nSummary:');
console.log(` Omise Settlements: ${report.summary.total_settlements}`);
console.log(` Bank Entries: ${report.summary.total_bank_entries}`);
console.log(` Matched: ${report.summary.matched}`);
console.log(` Match Rate: ${report.summary.match_rate}`);
console.log('\nDiscrepancies:');
console.log(` Unmatched Omise: ${report.summary.unmatched_omise}`);
console.log(` Unmatched Bank: ${report.summary.unmatched_bank}`);
console.log('='.repeat(60));
}
}
// Example usage
const reconciler = new ReconciliationManager();
const bankStatement = [
{ date: '2024-01-15', amount: 97500, reference: 'OMISE-001' },
{ date: '2024-01-22', amount: 195000, reference: 'OMISE-002' }
];
reconciler.reconcileWithBankStatement(
bankStatement,
new Date('2024-01-01'),
new Date('2024-01-31')
).then(report => {
reconciler.printReconciliationReport(report);
});
import omise
from datetime import datetime, timedelta
from collections import defaultdict
omise.api_secret = 'skey_test_123456789'
class ReconciliationManager:
def __init__(self):
self.discrepancies = []
def reconcile_with_bank_statement(self, bank_statement, start_date, end_date):
"""Reconcile Omise records with bank statement"""
# Get Omise settlements
settlements = self.get_omise_settlements(start_date, end_date)
# Get Omise transactions
transactions = self.get_omise_transactions(start_date, end_date)
# Match settlements with bank entries
match_results = self.match_settlements(settlements, bank_statement)
# Generate report
report = self.generate_reconciliation_report(
settlements,
transactions,
bank_statement,
match_results
)
return report
def get_omise_settlements(self, start_date, end_date):
"""Get Omise settlements (transfers)"""
transfers = omise.Transfer.retrieve(
created={
'gte': start_date.isoformat(),
'lte': end_date.isoformat()
},
limit=100
)
return [
{
'id': t.id,
'date': datetime.fromtimestamp(t.created),
'amount': t.amount - t.fee, # Net amount
'gross': t.amount,
'fee': t.fee,
'currency': t.currency,
'status': 'paid' if t.paid else 'pending'
}
for t in transfers.data
]
def get_omise_transactions(self, start_date, end_date):
"""Get Omise transactions"""
transactions = omise.Transaction.retrieve(
created={
'gte': start_date.isoformat(),
'lte': end_date.isoformat()
},
limit=1000
)
return transactions.data
def match_settlements(self, settlements, bank_statement):
"""Match settlements with bank entries"""
matched = []
unmatched_omise = []
unmatched_bank = list(bank_statement)
for settlement in settlements:
match = self.find_matching_bank_entry(settlement, unmatched_bank)
if match:
matched.append({
'settlement': settlement,
'bank_entry': match['entry'],
'confidence': match['confidence']
})
unmatched_bank.remove(match['entry'])
else:
unmatched_omise.append(settlement)
match_rate = (len(matched) / len(settlements) * 100) if settlements else 0
return {
'matched': matched,
'unmatched_omise': unmatched_omise,
'unmatched_bank': unmatched_bank,
'match_rate': f"{match_rate:.2f}"
}
def find_matching_bank_entry(self, settlement, bank_entries):
"""Find matching bank entry for settlement"""
for entry in bank_entries:
# Match by amount (allowing small difference)
amount_diff = abs(settlement['amount'] - entry['amount'])
# Match by date (allowing a few days difference)
if isinstance(entry['date'], str):
entry_date = datetime.strptime(entry['date'], '%Y-%m-%d')
else:
entry_date = entry['date']
days_diff = abs((settlement['date'] - entry_date).days)
if amount_diff < 100 and days_diff <= 3:
confidence = self.calculate_match_confidence(amount_diff, days_diff)
return {
'entry': entry,
'confidence': confidence
}
return None
def calculate_match_confidence(self, amount_diff, days_diff):
"""Calculate match confidence score"""
confidence = 100
# Reduce confidence based on differences
confidence -= (amount_diff / 100) * 5
confidence -= days_diff * 10
return max(0, confidence)
def generate_reconciliation_report(self, settlements, transactions, bank_statement, match_results):
"""Generate comprehensive reconciliation report"""
omise_total = sum(s['amount'] for s in settlements)
bank_total = sum(e['amount'] for e in bank_statement)
return {
'period': {
'start': settlements[0]['date'] if settlements else None,
'end': settlements[-1]['date'] if settlements else None
},
'summary': {
'total_settlements': len(settlements),
'total_bank_entries': len(bank_statement),
'matched': len(match_results['matched']),
'unmatched_omise': len(match_results['unmatched_omise']),
'unmatched_bank': len(match_results['unmatched_bank']),
'match_rate': match_results['match_rate'] + '%'
},
'totals': {
'omise_total': omise_total,
'bank_total': bank_total,
'difference': omise_total - bank_total
},
'matched': match_results['matched'],
'discrepancies': {
'unmatched_omise': match_results['unmatched_omise'],
'unmatched_bank': match_results['unmatched_bank']
}
}
def print_reconciliation_report(self, report):
"""Print formatted reconciliation report"""
print('\n' + '='*60)
print('RECONCILIATION REPORT')
print('='*60)
if report['period']['start']:
print(f"Period: {report['period']['start'].strftime('%Y-%m-%d')} - {report['period']['end'].strftime('%Y-%m-%d')}")
print('\nSummary:')
print(f" Omise Settlements: {report['summary']['total_settlements']}")
print(f" Bank Entries: {report['summary']['total_bank_entries']}")
print(f" Matched: {report['summary']['matched']}")
print(f" Match Rate: {report['summary']['match_rate']}")
print('\nTotals:')
print(f" Omise Total: {report['totals']['omise_total'] / 100:,.2f} THB")
print(f" Bank Total: {report['totals']['bank_total'] / 100:,.2f} THB")
print(f" Difference: {report['totals']['difference'] / 100:,.2f} THB")
print('\nDiscrepancies:')
print(f" Unmatched Omise: {report['summary']['unmatched_omise']}")
print(f" Unmatched Bank: {report['summary']['unmatched_bank']}")
print('='*60)
def export_reconciliation_csv(self, report, filename='reconciliation_report.csv'):
"""Export reconciliation report to CSV"""
import csv
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
# Write matched entries
writer.writerow(['MATCHED ENTRIES'])
writer.writerow(['Omise ID', 'Omise Date', 'Omise Amount', 'Bank Date', 'Bank Amount', 'Confidence'])
for match in report['matched']:
writer.writerow([
match['settlement']['id'],
match['settlement']['date'].strftime('%Y-%m-%d'),
match['settlement']['amount'] / 100,
match['bank_entry']['date'],
match['bank_entry']['amount'] / 100,
f"{match['confidence']:.1f}%"
])
writer.writerow([])
# Write unmatched Omise
writer.writerow(['UNMATCHED OMISE SETTLEMENTS'])
writer.writerow(['ID', 'Date', 'Amount'])
for settlement in report['discrepancies']['unmatched_omise']:
writer.writerow([
settlement['id'],
settlement['date'].strftime('%Y-%m-%d'),
settlement['amount'] / 100
])
writer.writerow([])
# Write unmatched bank
writer.writerow(['UNMATCHED BANK ENTRIES'])
writer.writerow(['Date', 'Amount', 'Reference'])
for entry in report['discrepancies']['unmatched_bank']:
writer.writerow([
entry['date'],
entry['amount'] / 100,
entry.get('reference', 'N/A')
])
print(f"Report exported to {filename}")
# Example usage
reconciler = ReconciliationManager()
bank_statement = [
{'date': '2024-01-15', 'amount': 97500, 'reference': 'OMISE-001'},
{'date': '2024-01-22', 'amount': 195000, 'reference': 'OMISE-002'}
]
start_date = datetime(2024, 1, 1)
end_date = datetime(2024, 1, 31)
report = reconciler.reconcile_with_bank_statement(bank_statement, start_date, end_date)
reconciler.print_reconciliation_report(report)
reconciler.export_reconciliation_csv(report)
require 'omise'
require 'date'
require 'csv'
Omise.api_key = 'skey_test_123456789'
class ReconciliationManager
attr_reader :discrepancies
def initialize
@discrepancies = []
end
def reconcile_with_bank_statement(bank_statement, start_date, end_date)
# Get Omise settlements
settlements = get_omise_settlements(start_date, end_date)
# Get Omise transactions
transactions = get_omise_transactions(start_date, end_date)
# Match settlements with bank entries
match_results = match_settlements(settlements, bank_statement)
# Generate report
generate_reconciliation_report(
settlements,
transactions,
bank_statement,
match_results
)
end
def get_omise_settlements(start_date, end_date)
transfers = Omise::Transfer.retrieve(
created: {
gte: start_date.iso8601,
lte: end_date.iso8601
},
limit: 100
)
transfers.data.map do |t|
{
id: t.id,
date: Time.at(t.created),
amount: t.amount - t.fee, # Net amount
gross: t.amount,
fee: t.fee,
currency: t.currency,
status: t.paid ? 'paid' : 'pending'
}
end
end
def get_omise_transactions(start_date, end_date)
transactions = Omise::Transaction.retrieve(
created: {
gte: start_date.iso8601,
lte: end_date.iso8601
},
limit: 1000
)
transactions.data
end
def match_settlements(settlements, bank_statement)
matched = []
unmatched_omise = []
unmatched_bank = bank_statement.dup
settlements.each do |settlement|
match = find_matching_bank_entry(settlement, unmatched_bank)
if match
matched << {
settlement: settlement,
bank_entry: match[:entry],
confidence: match[:confidence]
}
unmatched_bank.delete(match[:entry])
else
unmatched_omise << settlement
end
end
match_rate = settlements.empty? ? 0 : (matched.length.to_f / settlements.length * 100)
{
matched: matched,
unmatched_omise: unmatched_omise,
unmatched_bank: unmatched_bank,
match_rate: format('%.2f', match_rate)
}
end
def find_matching_bank_entry(settlement, bank_entries)
bank_entries.each do |entry|
# Match by amount
amount_diff = (settlement[:amount] - entry[:amount]).abs
# Match by date
entry_date = entry[:date].is_a?(String) ? Date.parse(entry[:date]) : entry[:date]
days_diff = (settlement[:date].to_date - entry_date).abs
if amount_diff < 100 && days_diff <= 3
confidence = calculate_match_confidence(amount_diff, days_diff)
return {
entry: entry,
confidence: confidence
}
end
end
nil
end
def calculate_match_confidence(amount_diff, days_diff)
confidence = 100
confidence -= (amount_diff.to_f / 100) * 5
confidence -= days_diff * 10
[0, confidence].max
end
def generate_reconciliation_report(settlements, transactions, bank_statement, match_results)
omise_total = settlements.sum { |s| s[:amount] }
bank_total = bank_statement.sum { |e| e[:amount] }
{
period: {
start: settlements.first&.dig(:date),
end: settlements.last&.dig(:date)
},
summary: {
total_settlements: settlements.length,
total_bank_entries: bank_statement.length,
matched: match_results[:matched].length,
unmatched_omise: match_results[:unmatched_omise].length,
unmatched_bank: match_results[:unmatched_bank].length,
match_rate: "#{match_results[:match_rate]}%"
},
totals: {
omise_total: omise_total,
bank_total: bank_total,
difference: omise_total - bank_total
},
matched: match_results[:matched],
discrepancies: {
unmatched_omise: match_results[:unmatched_omise],
unmatched_bank: match_results[:unmatched_bank]
}
}
end
def print_reconciliation_report(report)
puts "\n#{'='*60}"
puts 'RECONCILIATION REPORT'
puts '='*60
if report[:period][:start]
puts "Period: #{report[:period][:start].strftime('%Y-%m-%d')} - #{report[:period][:end].strftime('%Y-%m-%d')}"
end
puts "\nSummary:"
puts " Omise Settlements: #{report[:summary][:total_settlements]}"
puts " Bank Entries: #{report[:summary][:total_bank_entries]}"
puts " Matched: #{report[:summary][:matched]}"
puts " Match Rate: #{report[:summary][:match_rate]}"
puts "\nTotals:"
puts " Omise Total: #{format('%.2f', report[:totals][:omise_total] / 100.0)} THB"
puts " Bank Total: #{format('%.2f', report[:totals][:bank_total] / 100.0)} THB"
puts " Difference: #{format('%.2f', report[:totals][:difference] / 100.0)} THB"
puts "\nDiscrepancies:"
puts " Unmatched Omise: #{report[:summary][:unmatched_omise]}"
puts " Unmatched Bank: #{report[:summary][:unmatched_bank]}"
puts '='*60
end
def export_reconciliation_csv(report, filename = 'reconciliation_report.csv')
CSV.open(filename, 'w') do |csv|
# Matched entries
csv << ['MATCHED ENTRIES']
csv << ['Omise ID', 'Omise Date', 'Omise Amount', 'Bank Date', 'Bank Amount', 'Confidence']
report[:matched].each do |match|
csv << [
match[:settlement][:id],
match[:settlement][:date].strftime('%Y-%m-%d'),
match[:settlement][:amount] / 100.0,
match[:bank_entry][:date],
match[:bank_entry][:amount] / 100.0,
format('%.1f%%', match[:confidence])
]
end
csv << []
# Unmatched Omise
csv << ['UNMATCHED OMISE SETTLEMENTS']
csv << ['ID', 'Date', 'Amount']
report[:discrepancies][:unmatched_omise].each do |settlement|
csv << [
settlement[:id],
settlement[:date].strftime('%Y-%m-%d'),
settlement[:amount] / 100.0
]
end
csv << []
# Unmatched bank
csv << ['UNMATCHED BANK ENTRIES']
csv << ['Date', 'Amount', 'Reference']
report[:discrepancies][:unmatched_bank].each do |entry|
csv << [
entry[:date],
entry[:amount] / 100.0,
entry[:reference] || 'N/A'
]
end
end
puts "Report exported to #{filename}"
end
end
# Example usage
reconciler = ReconciliationManager.new
bank_statement = [
{ date: '2024-01-15', amount: 97500, reference: 'OMISE-001' },
{ date: '2024-01-22', amount: 195000, reference: 'OMISE-002' }
]
start_date = Date.new(2024, 1, 1)
end_date = Date.new(2024, 1, 31)
report = reconciler.reconcile_with_bank_statement(bank_statement, start_date, end_date)
reconciler.print_reconciliation_report(report)
reconciler.export_reconciliation_csv(report)
Automated Reconciliationโ
Daily Reconciliation Scriptโ
<?php
require_once 'vendor/autoload.php';
class DailyReconciliation {
private $omiseKey;
private $emailRecipients;
public function __construct($omiseKey, $emailRecipients) {
$this->omiseKey = $omiseKey;
$this->emailRecipients = $emailRecipients;
}
public function runDailyReconciliation() {
// Get yesterday's data
$yesterday = date('Y-m-d', strtotime('-1 day'));
// Fetch bank statement (from your banking system)
$bankStatement = $this->fetchBankStatement($yesterday);
// Fetch Omise settlements
$omiseSettlements = $this->fetchOmiseSettlements($yesterday);
// Reconcile
$report = $this->reconcile($omiseSettlements, $bankStatement);
// Check for discrepancies
if ($report['discrepancy_count'] > 0) {
$this->sendAlertEmail($report);
}
// Save report
$this->saveReport($report);
return $report;
}
private function fetchOmiseSettlements($date) {
$startDate = date('c', strtotime($date . ' 00:00:00'));
$endDate = date('c', strtotime($date . ' 23:59:59'));
$transfers = OmiseTransfer::retrieve([
'from' => $startDate,
'to' => $endDate,
'limit' => 100
]);
return array_map(function($t) {
return [
'id' => $t['id'],
'date' => date('Y-m-d', $t['created']),
'amount' => $t['amount'] - $t['fee'],
'gross' => $t['amount'],
'fee' => $t['fee']
];
}, $transfers['data']);
}
private function fetchBankStatement($date) {
// Implement your bank API integration here
// This is a placeholder
return [];
}
private function reconcile($omiseSettlements, $bankStatement) {
$matched = 0;
$unmatched = [];
foreach ($omiseSettlements as $settlement) {
$found = false;
foreach ($bankStatement as $entry) {
if ($this->isMatch($settlement, $entry)) {
$matched++;
$found = true;
break;
}
}
if (!$found) {
$unmatched[] = $settlement;
}
}
return [
'date' => date('Y-m-d'),
'total_settlements' => count($omiseSettlements),
'matched' => $matched,
'discrepancy_count' => count($unmatched),
'unmatched' => $unmatched
];
}
private function isMatch($settlement, $bankEntry) {
$amountMatch = abs($settlement['amount'] - $bankEntry['amount']) < 100;
$dateMatch = $settlement['date'] === $bankEntry['date'];
return $amountMatch && $dateMatch;
}
private function sendAlertEmail($report) {
$subject = "โ ๏ธ Reconciliation Discrepancies - " . $report['date'];
$message = "Found {$report['discrepancy_count']} unmatched settlements.\n\n";
foreach ($report['unmatched'] as $settlement) {
$message .= "Settlement {$settlement['id']}: " .
number_format($settlement['amount'] / 100, 2) . " THB\n";
}
foreach ($this->emailRecipients as $recipient) {
mail($recipient, $subject, $message);
}
}
private function saveReport($report) {
$filename = 'reconciliation_' . $report['date'] . '.json';
file_put_contents($filename, json_encode($report, JSON_PRETTY_PRINT));
}
}
// Run daily reconciliation
$reconciliation = new DailyReconciliation(
'skey_test_123456789',
['finance@company.com', 'accounting@company.com']
);
$report = $reconciliation->runDailyReconciliation();
echo "Reconciliation complete. Matched: {$report['matched']}/{$report['total_settlements']}\n";
?>
Best Practicesโ
1. Automate Daily Reconciliationโ
// Schedule daily reconciliation
const cron = require('node-cron');
class AutomatedReconciliation {
constructor() {
this.reconciler = new ReconciliationManager();
}
scheduleDailyReconciliation() {
// Run every day at 9 AM
cron.schedule('0 9 * * *', async () => {
console.log('Starting daily reconciliation...');
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
try {
const report = await this.runReconciliation(yesterday);
if (report.summary.unmatched_omise > 0 || report.summary.unmatched_bank > 0) {
await this.sendAlertNotification(report);
}
await this.saveReport(report);
console.log('Daily reconciliation complete');
} catch (error) {
console.error('Reconciliation failed:', error);
await this.sendErrorNotification(error);
}
});
}
async runReconciliation(date) {
const bankStatement = await this.fetchBankStatement(date);
return await this.reconciler.reconcileWithBankStatement(
bankStatement,
date,
date
);
}
async sendAlertNotification(report) {
// Send email or Slack notification
console.log('โ ๏ธ Discrepancies found in reconciliation');
}
}
const automated = new AutomatedReconciliation();
automated.scheduleDailyReconciliation();
2. Track Fee Differencesโ
def analyze_fee_discrepancies(omise_settlements, bank_statement):
"""Analyze fee-related discrepancies"""
discrepancies = []
for settlement in omise_settlements:
# Calculate expected bank amount (settlement amount - Omise fee)
expected_bank_amount = settlement['gross'] - settlement['fee']
# Find corresponding bank entry
bank_entry = find_bank_entry_by_date(settlement['date'], bank_statement)
if bank_entry:
actual_bank_amount = bank_entry['amount']
difference = expected_bank_amount - actual_bank_amount
if abs(difference) > 10: # More than 0.10 THB difference
discrepancies.append({
'settlement_id': settlement['id'],
'expected': expected_bank_amount / 100,
'actual': actual_bank_amount / 100,
'difference': difference / 100,
'possible_cause': analyze_difference(difference)
})
return discrepancies
def analyze_difference(difference):
"""Determine possible cause of difference"""
if abs(difference) < 500: # Less than 5 THB
return 'Rounding difference'
elif difference > 0:
return 'Missing bank fees or adjustment'
else:
return 'Additional bank fees charged'
3. Maintain Audit Trailโ
class ReconciliationAuditLog
def self.log_reconciliation(report, discrepancies = [])
log_entry = {
timestamp: Time.now.iso8601,
report_date: report[:period][:start].to_s,
match_rate: report[:summary][:match_rate],
total_settlements: report[:summary][:total_settlements],
matched: report[:summary][:matched],
unmatched: report[:summary][:unmatched_omise] + report[:summary][:unmatched_bank],
discrepancies: discrepancies.map { |d| d[:settlement_id] }
}
# Save to audit log file
File.open('reconciliation_audit.log', 'a') do |f|
f.puts JSON.generate(log_entry)
end
# Also save to database for reporting
save_to_database(log_entry)
end
def self.save_to_database(entry)
# Implement database storage
end
end
FAQโ
How often should I reconcile my account?โ
Daily reconciliation is recommended for businesses with high transaction volumes. Smaller businesses may reconcile weekly or monthly. Automated daily reconciliation helps catch issues early.
What causes reconciliation discrepancies?โ
Common causes include:
- Timing differences (transaction dates vs settlement dates)
- Fee mismatches
- Bank processing delays
- Failed transactions
- Manual adjustments
- Currency conversion differences
Should I match gross or net amounts?โ
Match net amounts (after fees) since that's what appears in your bank account. However, track gross amounts separately to verify fee calculations are correct.
How do I handle multi-day settlement delays?โ
Allow a 2-3 day window when matching transactions to bank entries. Settlements typically take 1-2 business days but can vary by bank and payment method.
What if I can't match a settlement?โ
Investigate by:
- Checking if it's still pending
- Verifying the date range (may have settled in different period)
- Checking for failed or reversed transactions
- Contacting Omise support for assistance
Can I automate bank statement imports?โ
Yes, most banks offer APIs or file exports (CSV, OFX) that you can automate. Integrate these with your reconciliation system for fully automated reconciliation.
How long should I keep reconciliation records?โ
Keep reconciliation records for at least 7 years for tax and audit purposes. Store them securely with proper access controls.
What's the best way to handle discrepancies?โ
Document all discrepancies immediately, investigate within 24 hours, and maintain a log of resolutions. Set up alerts for discrepancies above certain thresholds.
Related Resourcesโ
- Balance Overview - Understanding account balance
- Transaction History - View all transactions
- Creating Transfers - Settlement transfers
- Settlements API - Settlement details
- [Accounting Integration](/business-tools/reporting/balance-reports - Integration guides
Next Stepsโ
- Set up automated daily reconciliation
- Integrate with your [accounting software](/business-tools/reporting/balance-reports
- Configure alert notifications for discrepancies
- Review settlement schedules
- Implement audit logging for compliance