ข้ามไปยังเนื้อหาหลัก

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 พื้นฐาน

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);
});

Reconciliation อัตโนมัติ

สคริปต์ Reconciliation รายวัน

<?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() {
$yesterday = date('Y-m-d', strtotime('-1 day'));
$bankStatement = $this->fetchBankStatement($yesterday);
$omiseSettlements = $this->fetchOmiseSettlements($yesterday);
$report = $this->reconcile($omiseSettlements, $bankStatement);

if ($report['discrepancy_count'] > 0) {
$this->sendAlertEmail($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 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;
}
}

$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";
?>

แนวทางปฏิบัติที่ดีที่สุด

1. ทำ Reconciliation รายวันอัตโนมัติ

const cron = require('node-cron');

class AutomatedReconciliation {
constructor() {
this.reconciler = new ReconciliationManager();
}

scheduleDailyReconciliation() {
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) {
console.log('⚠️ Discrepancies found in reconciliation');
}
}

const automated = new AutomatedReconciliation();
automated.scheduleDailyReconciliation();

2. ติดตามความแตกต่างของค่าธรรมเนียม

def analyze_fee_discrepancies(omise_settlements, bank_statement):
"""Analyze fee-related discrepancies"""

discrepancies = []

for settlement in omise_settlements:
expected_bank_amount = settlement['gross'] - settlement['fee']
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:
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:
return 'ความแตกต่างจากการปัดเศษ'
elif difference > 0:
return 'ค่าธรรมเนียมธนาคารหรือการปรับที่ขาดหาย'
else:
return 'มีค่าธรรมเนียมธนาคารเพิ่มเติม'

คำถามที่พบบ่อย

ฉันควรทำ reconciliation บัญชีบ่อยแค่ไหน?

แนะนำให้ทำ reconciliation รายวันสำหรับธุรกิจที่มี transaction ปริมาณสูง ธุรกิจขนาดเล็กอาจทำรายสัปดาห์หรือรายเดือน Reconciliation รายวันอัตโนมัติช่วยตรวจจับปัญหาตั้งแต่เนิ่นๆ

อะไรทำให้เกิดความไม่สอดคล้องใน reconciliation?

สาเหตุทั่วไป:

  • ความแตกต่างของเวลา (transaction date กับ settlement date)
  • ค่าธรรมเนียมที่ไม่ตรงกัน
  • ความล่าช้าในการประมวลผลของธนาคาร
  • Transaction ที่ล้มเหลว
  • การปรับแต่งด้วยตนเอง
  • ความแตกต่างจากการแปลงสกุลเงิน

ฉันควรจับคู่จำนวน gross หรือ net?

จับคู่จำนวน net (หลังหักค่าธรรมเนียม) เนื่องจากนั่นคือสิ่งที่ปรากฏในบัญชีธนาคารของคุณ อย่างไรก็ตาม ติดตามจำนวน gross แยกต่างหากเพื่อยืนยันว่าการคำนวณค่าธรรมเนียมถูกต้อง

ฉันจะจัดการกับความล่าช้าของ settlement หลายวันได้อย่างไร?

อนุญาตช่วงเวลา 2-3 วันเมื่อจับคู่ transaction กับรายการธนาคาร Settlement โดยทั่วไปใช้เวลา 1-2 วันทำการ แต่อาจแตกต่างกันไปตามธนาคารและวิธีการชำระเงิน

ถ้าฉันไม่สามารถจับคู่ settlement ได้ ควรทำอย่างไร?

ตรวจสอบโดย:

  1. ตรวจสอบว่ายัง pending อยู่หรือไม่
  2. ยืนยันช่วงวันที่ (อาจ settle ในช่วงเวลาอื่น)
  3. ตรวจสอบ transaction ที่ล้มเหลวหรือถูก reverse
  4. ติดต่อฝ่ายสนับสนุน Omise เพื่อขอความช่วยเหลือ

ฉันสามารถทำ import รายงานธนาคารอัตโนมัติได้หรือไม่?

ได้ ธนาคารส่วนใหญ่เสนอ API หรือการส่งออกไฟล์ (CSV, OFX) ที่คุณสามารถทำอัตโนมัติได้ รวมสิ่งเหล่านี้เข้ากับระบบ reconciliation ของคุณเพื่อ reconciliation อัตโนมัติแบบเต็มรูปแบบ

ฉันควรเก็บบันทึก reconciliation ไว้นานแค่ไหน?

เก็บบันทึก reconciliation ไว้อย่างน้อย 7 ปีเพื่อวัตถุประสงค์ด้านภาษีและการตรวจสอบ จัดเก็บอย่างปลอดภัยด้วยการควบคุมการเข้าถึงที่เหมาะสม

วิธีที่ดีที่สุดในการจัดการความไม่สอดคล้องคืออะไร?

บันทึกความไม่สอดคล้องทั้งหมดทันที สอบสวนภายใน 24 ชั่วโมง และรักษาบันทึกของการแก้ไข ตั้งค่าการแจ้งเตือนสำหรับความไม่สอดคล้องที่เกินเกณฑ์ที่กำหนด

แหล่งข้อมูลที่เกี่ยวข้อง

ขั้นตอนถัดไป