Account Reconciliation
Account reconciliation ช่วยให้มั่นใจว่าบันทึก transaction ของ Omise ตรงกับรายงานธน าคารและระบบบัญชีของคุณ คู่มือนี้ครอบคลุมกระบวนการ reconciliation, การจับคู่อัตโนมัติ และแนวทางปฏิบัติที่ดีที่สุด
ภาพรวม
Reconciliation มีความสำคัญสำหรับ:
- การบัญชีที่ถูกต้อง: รับประกันบันทึกทางการเงินที่ถูกต้อง
- การตรวจจับการฉ้อโกง: ระบุ transaction ที่ไม่ได้รับอนุญาต
- การปฏิบัติตามข้อบังคับ: ตอบสนองความต้องการด้านกฎระเบียบ
- การจัดการกระแสเงินสด: ติดตามเงินทุนที่เกิดขึ้นจริงกับที่คาดไว้
- การระบุข้อผิดพลาด: จับปัญหาการประมวลผลตั้งแต่เนิ่นๆ
- บันทึกการตรวจสอบ: รักษาบันทึกทางการเงินที่สมบูรณ์
แนวคิดหลัก
- Settlement: ชุดของ transaction ที่จ่ายเข้าบัญชีธนาคารของคุณ
- Settlement Date: เมื่อเงินทุนมาถึงธนาคารของคุณ
- Transaction Date: เมื่อการชำระเงินของลูกค้าเกิดขึ้น
- Net Amount: จำนวน settlement หลังหักค่าธรรมเนียม
- Gross Amount: จำนวนการชำระเงินทั้งหมดก่อนหักค่าธรรมเนียม
ทำความเข้าใจ Settlement
วงจร Settlement
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
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
});
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];
const amountDiff = Math.abs(settlement.amount - entry.amount);
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;
confidence -= (amountDiff / 100) * 5;
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"""
settlements = self.get_omise_settlements(start_date, end_date)
transactions = self.get_omise_transactions(start_date, end_date)
match_results = self.match_settlements(settlements, bank_statement)
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,
'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:
amount_diff = abs(settlement['amount'] - entry['amount'])
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
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)
# 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)
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)
settlements = get_omise_settlements(start_date, end_date)
transactions = get_omise_transactions(start_date, end_date)
match_results = match_settlements(settlements, bank_statement)
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,
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|
amount_diff = (settlement[:amount] - entry[:amount]).abs
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
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)