ข้ามไปยังเนื้อหา
แก่น Kaen
ค้นหา
Ctrl
K
ยกเลิก
เลือกธีม
มืด
สว่าง
อัตโนมัติ
ออกแบบระบบเงินและ Ledger ด้วย DDD
วางระบบเงินที่ตรวจสอบได้ทุกบาท — 8 บทเรียน double-entry ledger, wallet, PromptPay, escrow, settlement, payout, refund และ reconciliation บน domain Order เดิม
เริ่มบทที่ 1
ความคืบหน้า
0 / 8 บทเรียน
เส้นทางการเรียน
หัวข้อที่มีชื่อว่า “เส้นทางการเรียน”
●
01
ทำไมเงินต้องใช้ double-entry
เริ่มตรงนี้
Double-entry Bookkeeping
วิธีบันทึกธุรกรรมการเงินที่ทุกรายการต้องมีสองด้านเสมอ — เดบิตด้านหนึ่ง เครดิตอีกด้านหนึ่ง เท่ากันทุกครั้ง เช่น ลูกค้าจ่ายค่าอาหาร 200 บาท เดบิตบัญชีเงินสด เครดิตบัญชีเจ้าหนี้ร้านค้า 200 บาทเท่ากัน — ทำให้เงินไม่มีวันหายเพราะทุกบาทมีที่มาที่ไปเสมอ
Debit and Credit
สองด้านของทุก Journal Entry ในระบบ double-entry — เดบิตกับเครดิตไม่ได้แปลว่า 'บวก' หรือ 'ลบ' ตรง ๆ แต่ขึ้นกับประเภทบัญชี สิ่งที่ต้องจริงเสมอคือผลรวมเดบิตของรายการหนึ่งต้องเท่ากับผลรวมเครดิตพอดี
○
02
model Ledger
Ledger
แหล่งความจริงเดียวที่รักษาประวัติ Journal Entry ทุกรายการของระบบไว้เรียงตามเวลา แบบ append-only ไม่มีการแก้ของเก่า ยอดเงินของบัญชีไหนก็ตามคำนวณได้จากการไล่ผลรวมรายการใน ledger เท่านั้น ไม่ใช่ตัวเลขที่เก็บแยกไว้ต่างหาก
Account
หน่วยที่ใช้แยกแยะว่า 'เงินอยู่ตรงไหน' ในระบบ เช่น บัญชีเงินสด platform, บัญชีลูกหนี้ร้านอาหาร, บัญชี wallet ของลูกค้า, บัญชี escrow ของออเดอร์ — ทุก Journal Entry ต้องระบุว่าเดบิต/เครดิตบัญชีไหน
Journal Entry
หน่วยที่เล็กที่สุดของการบันทึกใน ledger หนึ่งรายการมีอย่างน้อยสองบรรทัด (เดบิตกับเครดิต) ที่ยอดรวมเท่ากัน เช่น ตอนลูกค้าจ่ายค่าอาหาร เกิด journal entry เดียวที่กระทบทั้งบัญชีเงินสดและบัญชี escrow — เขียนแล้วห้ามแก้ ถ้าผิดต้องเขียน Reversing Entry มาหักล้างแทน
Append-only Log
โครงสร้างการเก็บข้อมูลที่ 'เขียนเพิ่มได้อย่างเดียว' ห้าม UPDATE หรือ DELETE รายการเก่า — ledger ของระบบการเงินต้องเป็นแบบนี้เสมอ เพราะประวัติเงินต้องตรวจสอบย้อนหลังได้ทุกบรรทัด ต่างจากตารางทั่วไปที่แก้ทับค่าเดิมได้
Immutability
คุณสมบัติที่ข้อมูลเปลี่ยนแปลงไม่ได้หลังถูกบันทึก — Journal Entry ที่เขียนลง ledger แล้วต้อง immutable ตลอดไป ถ้าธุรกรรมผิดพลาดก็เขียนรายการใหม่มาหักล้าง ไม่ใช่แก้ของเดิม เพราะการแก้ของเดิมทำให้ตรวจสอบ (audit) เชื่อถือไม่ได้อีกต่อไป
Balance as a Fold
แนวคิดที่ว่ายอดคงเหลือของบัญชีไม่ใช่ตัวเลขที่เก็บไว้ตรง ๆ แต่เป็นผลลัพธ์จากการ 'พับ' (fold) ไล่บวก-ลบทุก Journal Entry ของบัญชีนั้นตั้งแต่ต้นจนถึงปัจจุบัน คล้ายการ replay event ใน Event Sourcing — บัญชีจึงตรวจสอบย้อนกลับได้เสมอว่ายอดเงินมาจากไหนบ้าง
○
03
ย้ายเงินแบบ idempotent
Idempotency Key
รหัสเฉพาะที่แนบไปกับคำขอทำธุรกรรม (เช่น เรียกเก็บเงินผ่าน PromptPay) เพื่อให้ระบบรู้ว่าคำขอที่ถูกส่งซ้ำ (retry) คือคำขอเดิม ไม่ใช่ธุรกรรมใหม่ — กันไม่ให้ลูกค้าโดนหักเงินซ้ำเวลาเครือข่ายมีปัญหาแล้ว client ยิงคำขอเดิมซ้ำ
Idempotent Consumer
ตัวรับ event หรือ message ที่ประมวลผลข้อความเดิมซ้ำกี่ครั้งก็ได้ผลลัพธ์เหมือนเดิม ไม่สร้างผลข้างเคียงซ้ำ เช่น รับ PaymentCaptured ซ้ำสองครั้งจาก message broker ต้องไม่บันทึก journal entry สองรอบ — ตรวจด้วย Idempotency Key หรือเช็คว่าเคยประมวลผลรายการนี้ไปแล้วหรือยัง
○
04
Top-up ด้วย PromptPay/QR
Clearing Account
บัญชีพักเงินชั่วคราวระหว่างสองขั้นตอนของธุรกรรม เช่น ตอนลูกค้าจ่ายเงินผ่าน PromptPay เงินจะเข้าบัญชี clearing ก่อน แล้วค่อยกระจายไปบัญชีร้านอาหาร/ไรเดอร์/ค่าคอมมิชชัน platform ทีหลัง — แยกขั้นตอน 'รับเงินเข้า' ออกจาก 'จัดสรรเงิน' ให้ตรวจสอบได้ทีละขั้น
Integration Event
เหตุการณ์ที่สื่อสารข้าม Bounded Context หรือข้าม service ผ่าน message broker เช่น PaymentCaptured, OrderDelivered ที่ Payment context ต้องรับฟังจาก Ordering context เพื่อรู้ว่าเมื่อไหร่ควรปล่อยเงินจาก escrow — ต่างจาก Domain Event ที่ทำงานเฉพาะภายใน context เดียว (in-process)
○
05
จ่ายค่าอาหาร (escrow)
Escrow Account
บัญชีที่กันเงินไว้รอเงื่อนไขก่อนปล่อยให้ผู้รับตัวจริง เช่น เงินค่าอาหารที่ลูกค้าจ่ายจะพักอยู่ในบัญชี escrow จนกว่าออเดอร์จะถูกส่งมอบสำเร็จ ค่อยปล่อยเงินให้ร้านอาหารกับไรเดอร์ — รักษาสิทธิ์ทั้งสองฝ่ายไว้จนกว่าเงื่อนไขจะสำเร็จ ถ้าออเดอร์ถูกยกเลิกก่อนส่งมอบ เงินใน escrow จะถูกคืนแทนที่จะจ่ายออกไป
○
06
Settle & payout
Settlement
ขั้นตอนที่เงินซึ่งพักอยู่ (เช่นใน escrow หรือ clearing) ถูก 'ปิดจบ' อย่างเป็นทางการ — กระจายไปยังบัญชีปลายทางจริงตามสัดส่วนที่ตกลงไว้ เช่น ตัดค่าคอมมิชชัน platform แล้วโอนส่วนที่เหลือเข้าบัญชีร้านอาหารและไรเดอร์ มักเกิดเป็นรอบ (batch) ไม่ใช่ทันทีทุกออเดอร์
Payout
การจ่ายเงินที่ settle แล้วออกจากระบบไปให้ร้านอาหารหรือไรเดอร์จริง ๆ เช่น โอนเข้าบัญชีธนาคารหรือ PromptPay ของร้าน — มักรวมหลายออเดอร์เป็นรอบ payout เดียวต่อวันหรือต่อสัปดาห์ แทนที่จะโอนทุกออเดอร์แยกกัน
○
07
Refund, reversal & การแก้ที่ถูกต้อง
Reversing Entry
Journal Entry ใหม่ที่เขียนขึ้นเพื่อหักล้างรายการเดิมทั้งหมดหรือบางส่วน โดยสลับด้านเดบิต/เครดิตของรายการเดิม เช่น ตอนคืนเงินลูกค้า (refund) แทนที่จะลบรายการจ่ายเงินเดิม ระบบเขียน reversing entry ใหม่ทับ — ประวัติเดิมยังอยู่ครบ ตรวจสอบย้อนหลังได้ว่าเกิดอะไรขึ้นบ้าง
Chargeback
การที่ลูกค้าโต้แย้งรายการเรียกเก็บเงินกับธนาคารหรือผู้ให้บริการชำระเงินโดยตรง (ข้ามหัว platform) แล้วเงินถูกดึงกลับ แม้เงินอาจถูกจ่ายให้ร้านอาหาร/ไรเดอร์ไปแล้วก็ตาม — ระบบต้องรองรับกรณีนี้ด้วย Reversing Entry ย้อนหลัง ซึ่งอาจทำให้บัญชีติดลบ
Process Manager
ตัวประสานงานหลายขั้นตอนของธุรกรรมการเงินที่กระจายข้าม Aggregate หรือ Bounded Context เช่น ลำดับ escrow → settlement → payout ของออเดอร์หนึ่งใบ — ฟัง event ที่เข้ามาแต่ละขั้นแล้วสั่ง command ขั้นต่อไป คอยจำสถานะว่าตอนนี้ธุรกรรมไปถึงไหนแล้ว ต่างจาก Policy ที่เป็นกฎเดี่ยว ๆ ตรงที่ process manager ต้องรักษาสถานะของกระบวนการทั้งเส้น
○
08
Reconciliation & ปิดงบ
Reconciliation
กระบวนการเทียบยอดระหว่างสองแหล่งข้อมูลที่ควรตรงกัน เช่น ยอดใน ledger ของ platform กับยอดที่ผู้ให้บริการ PromptPay รายงานมาจริง — ถ้าไม่ตรงกันแปลว่ามีบางธุรกรรมหลุดหายหรือถูกบันทึกผิด ต้องตามหาสาเหตุก่อนปิดบัญชีรอบนั้น รักษาคำสัญญาที่ว่า 'เงินไม่มีวันหาย' ให้เป็นจริง
Trial Balance
รายงานสรุปยอดเดบิตรวมและเครดิตรวมของทุกบัญชีใน ledger ณ เวลาหนึ่ง ใช้ตรวจสุขภาพของระบบบัญชี — ถ้า double-entry ถูกต้องเสมอ ผลรวมเดบิตทั้งหมดต้องเท่ากับผลรวมเครดิตทั้งหมดพอดิบพอดี รักษาสมดุลของระบบไว้ ถ้าไม่เท่ากันแปลว่ามี bug ในระบบบันทึกธุรกรรม
Money
Value Object ที่แทนจำนวนเงินพร้อมสกุลเงิน (เช่น จำนวน + THB) เปรียบเทียบกันด้วยค่า ไม่ใช่ identity, บวก/ลบกันได้แต่ต้องเป็นสกุลเดียวกัน, immutable — ห้ามใช้ decimal เปล่า ๆ แทนเงินเพราะจะทำให้หน่วยเงินและกฎการปัดเศษหล่นหายไป (Primitive Obsession)