# Rakmao Payment/Payout Workflow [T] = Table (In database) [F] = Function (In C#) ## Payment Flow ```mermaid --- title: Payment Flow --- flowchart TD subgraph BA ["Buyer Actor"] A(Start) B[/"Approve PO"/] C{"Select payment method"} OPN["OPN (QR Code)"] CREDIT["KPG (CreditCard)"] PARTNER["Partner"] OPN_2["Scan QR Code"] CREDIT_2["Enter card detail"] CREDIT_3["3DS authorize"] ACP["Accept Delivery"] end subgraph SYSA ["System Actor"] OPN_3["Cash transafer from <br>customer bank to <br>OPN payment gateway <br> account"] CREDIT_4["Credit deduct on <br> customer CC account"] CREDIT_5["Credit deduct on <br> KPG payment gateway <br> for merchant account"] end subgraph RSA ["Rakmao System Actor"] CB["Callback to Rakmao"] CB_1["Update transaction <br> paid status"] CB_2["Callback to Seller"] AFTER_CB["Create payout transaction<br>([F]AddPendingPayoutTransaction)"] DEC{"Payment via OPN"} end rsa["Rakmao System Actor"] subgraph SLA ["Seller Actor"] SL["Accept payment <br> transaction"] SL2["Update delivery date"] SL3["Delivery"] end sla["Seller Actor"] subgraph ROA ["Rakmao Officer Actor"] OPN_4["End of day transfer cash<br> from OPN account to <br> KGP bank account for<br> Rakmao (002)"] end CB --> CB_1 --> CB_2 SL --> SL2 --> SL3 C --> OPN --> OPN_2 --> OPN_3 --> CB C --> CREDIT --> CREDIT_2 --> CREDIT_3 --> CREDIT_4 --> CREDIT_5 --> rsa CB_2 --> sla --> ACP --> AFTER_CB --> DEC DEC --> YES --> ROA --> END1 DEC --> NO --> END A --> B --> C --> PARTNER --> END END["END"] END1["END <br>(with cash on rakmao <br>bank account)"] ``` ## Payout Flow ```mermaid --- title: Payout Flow --- flowchart TD subgraph "System Actor" A(Start) end subgraph "Schedule" SCH1["Everyday at 07.15 UTC+7"] SCH2["Everyday at 10.01 UTC+7"] end subgraph CDB ["Create Draft batchs"] CDB1["Get payout config <br> from DB"] CDB2["Get PayoutTransaction <br>not assign to Batch"] CDB3["Group transaction <br> with AppId <br> (TransGroup)"] CDB4["Get Payment transactions "] CDB5["Get App Profile "] CDB6["Remap model with <br> group of seller <br>combine key<br>(SellerGroup)"] CDB7{"Have SellerGroup"} CDB8{"Any BatchId is null <br> in TransGroup"} CDB9{"Have batchId is null <br> in TransGroup"} CDB10{"Payout amount <br> over config"} CDB11{"Check payout amount with accumulate"} CDB12["Update accumulate value"] CDB13["Update this seller batch"] CDB14{"noRequireCashIn and <br> iskgpMethod and<br> payoutTransactionAlreadyCreate"} CDB15["Insert PayoutPayment line <br> with trnsaction data"] CDB16{"Check split batch"} CDB17["Update batch are split"] CDB18["insert batch"] CDB19{"cashIn > 0 "} CDB20["insert PayoutPayment <br> with transaction amount "] CDB21{"subsidize > 0 "} CDB22["insert PayoutPayment <br> with discount amount "] CDB23["save to DB"] END_CDB["END"] end cdb_node('Create Draft batchs') A --> SCH1 --> cdb_node --> END CDB1 --> CDB2 --> CDB3 --> CDB4 -->CDB5 -->CDB6 --> CDB7 --> CDB8 CDB8 --> CDB9 --> CDB10 --> CDB11 --> CDB12 --> CDB13 --> CDB14 CDB14 --> CDB15 --> CDB16 --> CDB17 --> CDB18 --> CDB19 --> CDB20 CDB20 --> CDB21 --> CDB22 --> CDB9 CDB9 --> CDB8 CDB11 --> CDB9 CDB14 --> CDB16 CDB16 --> CDB18 CDB19 --> CDB21 CDB21 --> CDB9 CDB7 --> END_CDB CDB8 --> CDB23 --> END_CDB CDB10 --> END_CDB END("End") ``` ## Summary After Payment Flow ```mermaid --- title: "After payment" --- flowchart TD A(Start) --> B[\Seller deilvered order/] B --> C[\Transaction payment <br> success/] C --> D[[Update Table Transactions]] D --> E[/"Create rows with<br> [F]AddPendingPayoutTransaction"/] E --> F[["Insert [T] <br> TransactionPayout"]] F --> G(End) ``` ## Function flow ### AddPendingPayoutTransaction #### TODO : Update to lastest ```mermaid --- title: "[F]AddPendingPayoutTransaction" --- flowchart TD A[Start] --> B{"Find eligible transaction <br>(Completed, Not yet paid)"}; B -- Not Found --> C[End]; B -- Found --> D[Get Merchant, Campaigns, & Payout Config]; D --> E["Calculate Amounts: - PayoutAmount = GrandTotal of PaymentTransaction without seller discount - CashoutAmount = GrandTotal of PaymentTransaction via none KGP Payment gateway without seller discount and Platform discount - SubsidizeAmount = Platform discount "]; E --> F{PayoutAmount <= BatchLimit?}; subgraph "Simple Payout <br>(Amount under or equal limit)" F -- Yes --> G[Create a single PayoutTransaction]; G --> Z[Save to Database]; end subgraph "Split Payout" F -- No --> H[Calculate Multiples & Fractions<br/>for Cashout and Subsidize amounts]; H --> I[Create new List for PayoutTransactions]; I --> J{Cashout batches to create > 0?}; J -- Yes --> K[Loop & Create PayoutTransactions for full Cashout batches]; K --> L; J -- No --> L; L{Remaining cashout fraction > 0?}; L -- Yes --> M[Create PayoutTransaction for remaining Cashout fraction]; M --> N; L -- No --> N; N{Subsidize batches to create > 0?}; N -- Yes --> O[Loop & Create PayoutTransactions for full Subsidize batches]; O --> P; N -- No --> P; P{Remaining subsidize fraction > 0?}; P -- Yes --> Q[Create PayoutTransaction for remaining Subsidize fraction]; Q --> R; P -- No --> R; R[Add the list of new transactions] --> Z; end Z --> C; ``` ### ApproveBatchDraft ```mermaid --- title: "Create Batch Draft" --- flowchart TD A[Start] --> B["Get Payout Configurations"]; B --> C{"Ratio > 100%"}; C -- Yes --> D[Throw Error]; C -- No --> E["Get Unbatched [T]PayoutTransactions"]; E --> F{"Any Transactions Found?"}; F -- No --> G[End]; F -- Yes --> H["Group Transactions by Seller-CombineKey"]; H --> I[Get Seller Details]; I --> J["Initialize empty lists for [T]PayoutBatch & [T]PayoutPayment"]; J --> K[For each <br> Transaction Group]; subgraph "Batch Creation Loop" K --> L{"While Group has <br> unbatched transactions"}; L -- No --> M[Next Group]; M --> K; L -- Yes --> N["Initialize batch variables<br/>(accumulators,<br> counters, new IDs)"]; N --> O["For each unbatched <br> transaction <br> 'trans' in the group"]; O --> P{"trans.PayoutAmount > <br> batchLimit?"}; P -- Yes --> Q[Throw ArgumentException]; P -- No --> R{"Accumulated Payout + <br> trans.PayoutAmount <= <br>batchLimit?"}; R -- No --> S[Process next transaction]; S --> O; R -- Yes --> T["Add 'trans' amount to <br>accumulators<br/>Update 'trans'<br> with batch/payment IDs"]; T --> U{trans.IsDocumentSplit?}; U -- Yes --> V[Set isContainSplit = true]; V --> S; U -- No --> S; end subgraph "Finalize and Save" O -- Loop Finished --> W[Create new PayoutBatch <br> object from <br>accumulated values]; W --> X{transactionCashoutAmount > 0?}; X -- Yes --> Y[Create PayoutPayment <br>for Transaction cashout]; Y --> Z; X -- No --> Z; Z{subsidizeCashoutAmount > 0?}; Z -- Yes --> AA[Create PayoutPayment <br>for Subsidize cashout]; AA --> L; Z -- No --> L; end K -- All Groups Processed --> AB{Any Batches Created?}; AB -- No --> G; AB -- Yes --> AC[Add Batches & <br>Payments to context]; AC --> AD[Save Changes to Database]; AD --> G; ``` <!-- # ****[AI Autogenerate]**** ## ทำความเข้าใจระบบการจ่ายเงินอัตโนมัติของเรา: จากยอดขายสู่การชำระเงินให้ผู้ขาย (ฉบับปรับปรุง) เอกสารนี้อธิบายขั้นตอนการทำงานอัตโนมัติของระบบที่เราใช้เพื่อให้มั่นใจว่าผู้ขายและพันธมิตรได้รับเงินอย่างถูกต้องและมีประสิทธิภาพหลังจากลูกค้าชำระเงิน ### เป้าหมายหลัก: การจ่ายเงินที่ตรงเวลาและแม่นยำ ระบบการจ่ายเงินของเราออกแบบมาเพื่อ: * **คำนวณยอดเงินค้างจ่าย** สำหรับแต่ละคำสั่งซื้อที่เสร็จสมบูรณ์อย่างแม่นยำ โดยคำนึงถึงส่วนลดต่างๆ * **จัดกลุ่มการชำระเงิน** อย่างมีตรรกะเพื่อการประมวลผลที่มีประสิทธิภาพ * **โต้ตอบอย่างปลอดภัย** กับพันธมิตรผู้ให้บริการประมวลผลการชำระเงินของเรา (ซึ่งในทางเทคนิคเรียกว่า KApi) เพื่อโอนเงิน * **ให้ข้อมูลการติดตามและสถานะที่ชัดเจน** ในแต่ละขั้นตอน โดยใช้การผสมผสานระหว่างการตรวจสอบตามกำหนดเวลาและการอัปเดตตามเวลาจริงจากพันธมิตรการชำระเงินของเรา * **ช่วยให้สามารถดำเนินการทางธุรการได้** เช่น การแบ่งชุดรายการร่าง * **เปิดใช้งานการจัดการและการลองใหม่** ในกรณีที่เกิดปัญหา ### เส้นทางการจ่ายเงิน: ขั้นตอนทีละขั้นตอน (ข้อมูล ณ วันที่ 14 พฤษภาคม 2025) นี่คือภาพรวมการเดินทางของยอดเงินที่ต้องจ่ายผ่านระบบของเรา: --- ### ขั้นตอนที่ 1: การบันทึกรายการรอจ่าย (หลังจากการชำระเงินของลูกค้า) * **ฟังก์ชันที่เกี่ยวข้อง:** `AddPendingPayoutTransaction` * **ตัวกระตุ้น (Trigger):** ทันทีหลังจากที่การชำระเงินของลูกค้าสำหรับคำสั่งซื้อได้รับการประมวลผลสำเร็จ ระบบงานอัตโนมัติ (worker - กระบวนการภายในระบบ) จะเริ่มขั้นตอนนี้ **สิ่งที่เกิดขึ้น:** 1. **ระบุคำสั่งซื้อ:** ระบบจะค้นหารายละเอียดของคำสั่งซื้อของลูกค้าที่เสร็จสมบูรณ์ 2. **คำนวณยอดสุทธิ:** ระบบจะพิจารณา: * ยอดรวม (`GrandTotal`) ของคำสั่งซื้อ * ส่วนลดใดๆ ที่ใช้โดย **ผู้ขาย** (จาก "แคมเปญของผู้ขาย" - Seller Campaign) * ส่วนลดใดๆ ที่ใช้โดย **แพลตฟอร์มของเรา** (จาก "แคมเปญของแพลตฟอร์ม" - Platform Campaign) 3. **จัดการการจ่ายเงินจำนวนมาก:** หาก `PayoutAmount` (ยอดรวมหลังหักส่วนลดผู้ขาย) ที่ต้องจ่ายสำหรับคำสั่งซื้อเดียวมีขนาดใหญ่มาก (เกินกว่า `PayoutBatchAmountSize` ที่ตั้งไว้ล่วงหน้า) ระบบจะแบ่งภาระการจ่ายเงินครั้งเดียวนี้ออกเป็นหลายรายการ `PayoutTransaction` ที่มีขนาดเล็กลงโดยอัตโนมัติ เพื่อให้การประมวลผลในภายหลังราบรื่นยิ่งขึ้น 4. **สร้าง "รายการรอการจ่ายเงิน (หลายรายการได้)":** ระบบจะสร้างรายการโดยละเอียด (`PayoutTransaction`) หนึ่งรายการหรือมากกว่า ซึ่งแต่ละรายการรวมถึง: * `PayoutAmount` สำหรับรายการนั้น (อาจเป็นยอดเต็มหรือส่วนที่แบ่งย่อย) * `TransactionCashoutAmount` (จำนวนเงินจริงที่เราจะจ่ายออกสำหรับรายการนั้น หลังจากคำนวณส่วนลดทั้งหมดแล้ว) * `SubsidizeCashoutAmount` (ส่วนของส่วนลดที่แพลตฟอร์มของเรารับผิดชอบสำหรับรายการนั้น) * รายละเอียดคำสั่งซื้อและผู้ขาย (เช่น `DocumentId`, `PoCode`, `VendorTaxId`) * รายการเหล่านี้จะถูกทำเครื่องหมายภายในว่า `PayoutSuccess = false` (ยังไม่ได้จ่ายเงิน) และพร้อมสำหรับขั้นตอนต่อไป **เหตุผล:** เพื่อสร้างรายการทางการเงิน (line item) ที่ถูกต้องและเป็นรายบุคคล (หรือแบ่งย่อยอย่างตั้งใจ) สำหรับทุกยอดขายที่เสร็จสมบูรณ์ซึ่งต้องการการจ่ายเงิน --- ### ขั้นตอนที่ 2: การจัดกลุ่มยอดที่ต้องจ่ายเป็นชุดร่าง (รายวัน) * **ฟังก์ชันที่เกี่ยวข้อง:** `CreateBatchDraft` * **ตัวกระตุ้น (Trigger):** ระบบงานอัตโนมัติ (worker) ที่ตั้งกำหนดการทำงานรายวัน **สิ่งที่เกิดขึ้น:** 1. **รวบรวมรายการที่รอดำเนินการ:** ระบบจะรวบรวมรายการ `PayoutTransaction` ทั้งหมดที่ยังไม่ได้จัดกลุ่มเข้าชุดและยังไม่ได้ถูกทำเครื่องหมายว่าจ่ายสำเร็จ 2. **จัดระเบียบตามผู้ขาย/แพลตฟอร์ม:** การจ่ายเงินจะถูกจัดกลุ่มตามผู้ขายเป็นหลัก (`SellerCombineKey`) นอกจากนี้ยังพิจารณาถึงแอปพลิเคชันหรือแพลตฟอร์ม (`AppOwner`, `PlatformProfile`) ที่เกิดการขาย 3. **ใช้อัตราส่วนการจ่ายเงิน:** สำหรับแต่ละกลุ่มการจ่ายเงิน ระบบจะตรวจสอบอัตราส่วนที่กำหนดไว้ล่วงหน้า: * `PayoutShopRatio`: เปอร์เซ็นต์ของยอดจ่ายรวมของชุดรายการที่จัดสรรให้กับร้านค้า/ผู้ขาย * `PayoutPartnerRatio`: เปอร์เซ็นต์ที่จัดสรรให้กับนิติบุคคลที่เป็นพันธมิตร (เช่น พันธมิตรการขาย, ผู้ให้บริการแพลตฟอร์ม) * *(การตรวจสอบความปลอดภัย: เพื่อให้แน่ใจว่าอัตราส่วนเหล่านี้รวมกันไม่เกิน 100%)* 4. **สร้าง "ชุดร่างสำหรับการจ่ายเงิน":** ระบบสร้างรายการ `PayoutBatch` แต่ละชุดประกอบด้วย: * การรวมรายการ `PayoutTransaction` หลายรายการสำหรับผู้ขายรายเดียว * ยอดรวม `PayoutAmount` ถูกจำกัดโดย `PayoutBatchAmountSize` เพื่อให้จัดการชุดรายการได้ หากผู้ขายมีการจ่ายเงินจำนวนมาก อาจมีหลายชุดรายการ * ถูกทำเครื่องหมายด้วยสถานะ `Draft` (ฉบับร่าง) * บันทึกว่ามีรายการ `PayoutTransaction` ใดๆ ภายในชุดนั้นที่เดิมถูกแบ่งย่อยมาจากคำสั่งซื้อขนาดใหญ่หรือไม่ (`IsContainSplit`) * มีการกำหนด `PayoutLevel` (เช่น "M" สำหรับ Merchant Level, "S" สำหรับ Shop Level) จากการกำหนดค่า ซึ่งอาจมีผลต่อตรรกะการลองใหม่ในภายหลัง 5. **รายละเอียดการชำระเงินในชุด:** สำหรับแต่ละชุด จะมีการสร้างรายการ `PayoutPayment` ที่ระบุ: * `TotalAmount` (ยอดรวม) ของชุดนั้น (ผลรวมของ `PayoutAmount` จากธุรกรรมในชุด) * `ShopAmount` (ยอดที่คำนวณได้ที่ต้องจ่ายให้ผู้ขายตามอัตราส่วน) * `PartnerAmount` (ยอดที่คำนวณได้ที่ต้องจ่ายให้พันธมิตรตามอัตราส่วน) * ยอดรวม `TransactionCashoutAmount` และ `SubsidizeCashoutAmount` สำหรับธุรกรรมทั้งหมดในชุดนั้น * รายการ `PayoutPayment` นี้ถูกทำเครื่องหมายด้วยสถานะ `Create` (สร้าง) **เหตุผล:** เพื่อรวบรวมการจ่ายเงินแต่ละรายการเป็นชุดที่จัดการได้สำหรับผู้ขาย, ใช้กฎการแบ่งรายได้กับพันธมิตร (ถ้ามี), และเตรียมพร้อมสำหรับขั้นตอนการอนุมัติและการเริ่มต้นการชำระเงิน --- ### ขั้นตอนที่ 3: ผู้ดูแลระบบอนุมัติชุดรายการ & เริ่มต้นคำขอเคลื่อนย้ายเงินทุน * **ฟังก์ชันที่เกี่ยวข้อง:** `ApproveBatchDraft`, `RequestCashOut` (ฟังก์ชันช่วยเหลือภายใน) * **ตัวกระตุ้น (Trigger):** ผู้ดูแลระบบ (เช่น จากทีมการเงิน) ตรวจสอบและอนุมัติชุดรายการสถานะ `Draft` ด้วยตนเองผ่านระบบภายในของเรา **สิ่งที่เกิดขึ้น:** 1. **ผู้ดูแลระบบเลือกรายการ:** ผู้ดูแลระบบเลือกชุดรายการ `Draft` หนึ่งชุดหรือมากกว่าเพื่ออนุมัติ * *(การตรวจสอบความปลอดภัย: ระบบใช้การล็อกหน่วยความจำชั่วคราว (`IMemoryCache`) เพื่อป้องกันไม่ให้ชุดรายการเดียวกัน (หรือหลายชุด) ถูกอนุมัติหลายครั้งพร้อมกัน)* 2. **บันทึกการอนุมัติ:** ระบบบันทึกชื่อผู้ดูแลระบบ (`ApproveUserName`) และเวลาที่อนุมัติ 3. **เริ่มต้นคำขอ Cash Out:** สำหรับแต่ละ `PayoutPayment` ภายในชุดที่ได้รับการอนุมัติ (โดยเฉพาะรายการที่มุ่งหมายสำหรับร้านค้า/ผู้ขาย): * ระบบจะเรียก API ของ **พันธมิตรผู้ให้บริการประมวลผลการชำระเงิน (KApi)** ของเราผ่านฟังก์ชัน `RequestCashOut` * ขั้นตอนนี้เป็นการแจ้งให้ KApi เตรียมพร้อมที่จะเคลื่อนย้ายเงินทุน อาจเกี่ยวข้องกับการเรียก "direct credit" สองครั้งแยกกัน หากมียอด `TransactionCashoutAmount` หลักและยอด `SubsidizeCashoutAmount` ที่แพลตฟอร์มสนับสนุน โดยใช้รหัสเงินสดเฉพาะที่กำหนดค่าไว้ (`TransactionCashID`, `SubsidizeCashID`) * ระบบจะสร้าง `OrderId` (เช่น `Order000...123`) และ `PaymentId` (เช่น `Payment00...123`) ที่ไม่ซ้ำกันจากหมายเลขที่รันต่อเนื่อง และส่งไปยัง KApi เพื่อติดตามคำขอเฉพาะเหล่านี้ 4. **อัปเดตสถานะ (เบื้องต้น):** * หาก KApi ตอบรับคำขอเคลื่อนย้ายเงินทุนเริ่มต้นเหล่านี้ *ทั้งหมด* สำหรับชุดรายการสำเร็จ สถานะ `PayoutBatch` จะอัปเดตเป็น `Pending` (บ่งชี้ว่ากำลังรอการยืนยันการเคลื่อนย้ายเงินทุนจาก KApi) รายการ `PayoutPayment` แต่ละรายการจะถูกทำเครื่องหมายเป็น `Pending` เช่นกัน * หากคำขอใดๆ ไปยัง KApi ล้มเหลวสำหรับรายการชำระเงินภายในชุดนั้น `PayoutBatch` นั้นจะถูกทำเครื่องหมายเป็น `CashoutFail` ส่วน `PayoutPayment` ที่ล้มเหลวจะถูกทำเครื่องหมาย `Failed` พร้อมบันทึกข้อผิดพลาด กระบวนการอนุมัติสำหรับชุดนั้นจะหยุดเมื่อเกิดความล้มเหลวครั้งแรก **เหตุผล:** เพื่อให้มีการอนุมัติอย่างเป็นทางการก่อนที่จะมีการร้องขอการเคลื่อนย้ายเงินใดๆ และเพื่อส่งคำแนะนำเบื้องต้นไปยังพันธมิตรผู้ให้บริการประมวลผลการชำระเงินของเราเพื่อเริ่มกระบวนการจัดหาเงินทุนสำหรับการจ่ายเงิน --- ### ขั้นตอนที่ 4: ระบบยืนยันเงินทุน, เตรียมการสำหรับการแจกจ่าย, และรอการอัปเดตจาก KApi เกี่ยวกับการตั้งค่าชุดรายการ * **ฟังก์ชันที่เกี่ยวข้อง:** `WorkspacePaymentStatusAll()` (งานที่ตั้งกำหนดเวลาในระบบของคุณ), `WorkspacePaymentStatus()` (ฟังก์ชันช่วยเหลือ), `KApiRepository.InquiryPayment()`, `RequestBatchPayment()`, `KApiRepository.PayoutShop()`, และตัวจัดการ Callback `UpdatePayoutBatchStatus()` * **ตัวกระตุ้น (Trigger) & กระบวนการ:** 1. **การตรวจสอบโดยระบบของคุณ (`WorkspacePaymentStatusAll`):** หลังจากผู้ดูแลระบบอนุมัติ (ขั้นตอนที่ 3) งานที่ตั้งกำหนดเวลาในระบบของคุณ (`WorkspacePaymentStatusAll`) จะทำงานเป็นระยะ (เช่น ทุกชั่วโมง) โดยจะค้นหารายการ `PayoutPayment` ทั้งหมดที่อยู่ในสถานะ `Pending` และสำหรับแต่ละรายการ จะเรียก `WorkspacePaymentStatus` 2. **การสอบถาม KApi (`WorkspacePaymentStatus`):** `WorkspacePaymentStatus` จะสอบถาม **KApi** (โดยใช้ `InquiryPayment`) เพื่อรับสถานะล่าสุดของคำขอเคลื่อนย้ายเงินทุนเฉพาะนั้น 3. **การอัปเดตภายใน & การกระตุ้นการตั้งค่าการแจกจ่าย:** * หาก KApi ยืนยันว่าการชำระเงินแต่ละรายการเป็น **"paid"** (หมายถึงเงินทุนได้รับการจัดการ/ประมวลผลทางฝั่งของพวกเขาแล้ว) และ KApi ยังระบุว่าการชำระเงินนี้ **"ready"** (พร้อม) สำหรับการจัดสรรขั้นสุดท้าย: * ระบบของคุณจะอัปเดตสถานะ `PayoutPayment` เป็น `Paid` (ชำระแล้ว) * จากนั้นจะกระตุ้น `RequestBatchPayment()` โดยอัตโนมัติสำหรับ `PayoutBatch` หลัก * `RequestBatchPayment()` จะส่งคำสั่งอีกชุดไปยัง **KApi** (การเรียก `PayoutShop`) ซึ่งจะแจ้งรายละเอียดการแจกจ่ายขั้นสุดท้ายสำหรับทั้งชุด (เช่น จำนวนเงินสำหรับร้านค้า/ผู้ขายผ่าน `ShopAmount` และจำนวนเงินสำหรับพันธมิตรผ่าน `PartnerAmount`) * หาก KApi รายงานว่าการชำระเงินแต่ละรายการยังคง "pending", "failed", "expired", หรือ "cancelled" ระบบของคุณจะอัปเดตสถานะ `PayoutPayment` ตามนั้น หากล้มเหลว `PayoutBatch` หลักอาจถูกทำเครื่องหมายเป็น `CashoutFail` ด้วย 4. **การรอ KApi Callback (สำหรับผลลัพธ์ `PayoutShop`):** หลังจาก `RequestBatchPayment` ส่งคำสั่ง `PayoutShop` ไปยัง KApi โดยทั่วไปแล้วระบบของคุณจะรอการแจ้งเตือนภายนอก (a **callback**) จาก KApi เพื่อทราบผลลัพธ์ของการตั้งค่าชุดรายการนี้ 5. **การประมวลผล KApi Callback (`UpdatePayoutBatchStatus`):** เมื่อ KApi ประมวลผลคำสั่ง `PayoutShop` เสร็จสิ้น คาดว่าจะมีการเรียก callback มายัง endpoint ในระบบของคุณ ซึ่งจะเรียกใช้ฟังก์ชัน `UpdatePayoutBatchStatus` ฟังก์ชันนี้จะ: * สอบถาม KApi อีกครั้ง (โดยใช้ `InquiryPayout`) เพื่อรับผลลัพธ์ที่ชัดเจนของการดำเนินการ `PayoutShop` สำหรับชุดรายการนั้น * หาก KApi ระบุว่าสำเร็จ (เช่น สถานะ "processed" และไม่มี `FailedPayments`) ระบบของคุณจะอัปเดตสถานะ `PayoutBatch` เป็น `ReadyPayout` (พร้อมจ่าย) รายการ `PayoutTransaction` ภายในชุดนั้นก็จะถูกทำเครื่องหมายว่าประมวลผลสำเร็จถึงขั้นตอนนี้แล้ว (แต่ยังไม่ได้จ่ายเงินให้ผู้ค้าเต็มจำนวน) * หาก KApi ระบุว่าล้มเหลว (เช่น สถานะ "failed" หรือ "processed" แต่มี `FailedPayments`) `PayoutBatch` จะถูกทำเครื่องหมาย `CreateBatchFail` (หรือสถานะข้อผิดพลาดที่คล้ายกัน) รายละเอียดข้อผิดพลาดจาก KApi จะถูกบันทึกไว้ **เหตุผล:** กระบวนการหลายขั้นตอนนี้ช่วยให้มั่นใจได้ว่า: * ระบบของคุณตรวจสอบอย่างแข็งขันว่าการเคลื่อนย้ายเงินทุนเริ่มต้นประสบความสำเร็จหรือไม่ (การตรวจสอบเป็นระยะ) * คำสั่งการแจกจ่ายขั้นสุดท้าย (`PayoutShop`) จะถูกส่งหลังจากที่การชำระเงินแต่ละรายการได้รับการยืนยันจาก KApi แล้วเท่านั้น * ระบบของคุณอาศัย callback จาก KApi สำหรับผลลัพธ์สุดท้ายของการตั้งค่าการแจกจ่ายชุดรายการ ทำให้เป็นแบบ event-driven ในขั้นตอนนี้ และทำให้มั่นใจได้ว่าสถานะ (`ReadyPayout` หรือข้อผิดพลาด) สะท้อนการประมวลผลของ KApi อย่างถูกต้อง --- ### ขั้นตอนที่ 5: ผู้ดูแลระบบเริ่มการจ่ายเงินขั้นสุดท้ายไปยังผู้ค้า * **ฟังก์ชันที่เกี่ยวข้อง:** `MakePayout()` (เวอร์ชันที่ไม่รับอาร์กิวเมนต์) * **ตัวกระตุ้น (Trigger):** ผู้ดูแลระบบตรวจสอบชุดรายการที่มีสถานะ `ReadyPayout` และเริ่มขั้นตอนนี้ โดยทั่วไปสำหรับชุดรายการทั้งหมดที่พร้อมในขณะนั้น **สิ่งที่เกิดขึ้น:** 1. **รวบรวมชุดรายการที่พร้อม:** ฟังก์ชัน `MakePayout()` ระบุรายการ `PayoutBatch` ทั้งหมดที่อยู่ในสถานะ `ReadyPayout` 2. **จัดกลุ่มตามผู้ค้า:** ชุดรายการเหล่านี้ถูกจัดกลุ่มตาม `MerchantSellerId` (รหัสเฉพาะสำหรับนิติบุคคลทางธุรกิจขั้นสุดท้ายที่รับการชำระเงิน) ซึ่งช่วยให้สามารถรวมคำสั่งจ่ายเงินได้หากผู้ค้ามีหลายชุดรายการที่พร้อม 3. **สั่งการจ่ายเงินให้ผู้ค้า:** สำหรับแต่ละกลุ่ม `MerchantSellerId`: * ระบบจะเรียก `PayoutMerchant` ไปยัง KApi * การเรียกนี้รวมถึงรหัสอ้างอิงเฉพาะใหม่ (`PartnerPayoutMerchantID`) สำหรับการดำเนินการจ่ายเงินให้ผู้ค้ารวมนี้ และรายการรหัส `PayoutBatch` เฉพาะทั้งหมดของพวกเขาที่กำลังจะจ่าย 4. **อัปเดตสถานะชุดรายการ (เบื้องต้น):** * หาก KApi รับทราบคำสั่งการจ่ายเงินขั้นสุดท้ายนี้ (โดยปกติจะตอบกลับด้วยสถานะ "pending" ซึ่งบ่งชี้ว่าพวกเขากำลังประมวลผลการเบิกจ่ายจริงไปยังบัญชีของผู้ค้า): * รายการ `PayoutBatch` ทั้งหมดที่รวมอยู่ในการเรียก KApi นี้จะได้รับการอัปเดตในระบบของคุณเป็น `PendingPayout` (รอการจ่ายเงิน) * รหัสอ้างอิงเฉพาะสำหรับการเรียก `PayoutMerchant` นี้ไปยัง KApi จะถูกเก็บไว้ในแต่ละชุด (`PayoutRefId`) * หากคำสั่ง `PayoutMerchant` ไปยัง KApi ล้มเหลวทันที ชุดรายการจะถูกทำเครื่องหมาย `PayoutFail` พร้อมข้อความแสดงข้อผิดพลาดจาก KApi **เหตุผล:** นี่คือขั้นตอนที่ผู้ดูแลระบบสั่งการอย่างเป็นทางการให้ KApi ดำเนินการชำระเงินครั้งสุดท้ายแบบรวมศูนย์ไปยังบัญชีของผู้ค้าสำหรับชุดรายการทั้งหมดที่ได้รับอนุมัติและเตรียมการอย่างสมบูรณ์แล้ว --- ### ขั้นตอนที่ 6: KApi Callback ยืนยันการจ่ายเงินสำเร็จไปยังผู้ค้า * **ตัวกระตุ้น (Trigger):** หลังจากผู้ดูแลระบบเริ่ม `MakePayout` (ขั้นตอนที่ 5) และ KApi ได้ประมวลผลคำสั่ง `PayoutMerchant` แล้ว KApi คาดว่าจะเรียก **callback** มายังระบบของคุณ ซึ่งสอดคล้องกับโฟลว์ของคุณ: "ในขั้นตอนสุดท้าย ระบบภายนอกจะเรียก API เพื่ออัปเดตสถานะการจ่ายเงิน" * **ฟังก์ชันที่เกี่ยวข้อง:** ตัวจัดการ Callback `UpdatePayoutMerchantStatus()`, `KApiRepository.InquiryPayoutMerchant()` **สิ่งที่เกิดขึ้น:** 1. **การประมวลผล KApi Callback (`UpdatePayoutMerchantStatus`):** เมื่อ KApi เรียก callback ระบบของคุณจะเรียกใช้ฟังก์ชัน `UpdatePayoutMerchantStatus` สำหรับ `sellerMerchantIdStr` ที่กำหนด (ซึ่งคือ `PartnerPayoutMerchantID` จากขั้นตอนที่ 5) ฟังก์ชันนี้จะ: * สอบถาม KApi (โดยใช้ `InquiryPayoutMerchant`) เพื่อรับผลลัพธ์ที่ชัดเจนของการดำเนินการ `PayoutMerchant` ทั้งหมด * ตามการตอบสนองของ KApi ที่ให้รายละเอียด `PassedPayouts` (การจ่ายเงินที่สำเร็จ) และ `FailedPayouts` (การจ่ายเงินที่ล้มเหลว): * สำหรับชุดรายการที่ KApi ยืนยันว่าประมวลผลสำเร็จ (`PassedPayouts`): * สถานะ `PayoutBatch` ในระบบของคุณจะได้รับการอัปเดตเป็นสถานะสำเร็จขั้นสุดท้าย: **`Paid`** (จ่ายแล้ว) * รายการ `PayoutTransaction` ภายในชุดรายการ `Paid` เหล่านี้จะมีแฟล็ก `PayoutSuccess` ถูกตั้งค่าเป็น `true` * มีการบันทึก `PayoutTimestamp` (เวลาที่จ่ายเงินจริง) บนชุดรายการ * สำหรับชุดรายการที่ KApi รายงานว่าล้มเหลว (`FailedPayouts`): สถานะ `PayoutBatch` ที่เกี่ยวข้องจะได้รับการอัปเดตเป็น `PayoutFail` และรายละเอียดข้อผิดพลาดจาก KApi จะถูกบันทึกไว้ **เหตุผล:** เพื่อรับการยืนยันที่ชัดเจนจากพันธมิตรผู้ให้บริการประมวลผลการชำระเงินว่าเงินทุนได้รับการประมวลผลสำเร็จเพื่อส่งมอบให้ผู้ค้า (หรือหากล้มเหลว) และเพื่ออัปเดตบันทึกทั้งหมดในระบบเพื่อสะท้อนสถานะ `Paid` (หรือล้มเหลว) ขั้นสุดท้ายนี้สำหรับแต่ละชุดรายการ --- ### การดำเนินการทางธุรการ & การจัดการข้อผิดพลาด ### การแบ่งชุดรายการร่าง * **ฟังก์ชันที่เกี่ยวข้อง:** `SplitPayoutBatch` * **การดำเนินการ:** ผู้ดูแลระบบสามารถเลือกที่จะ **แบ่งชุดรายการ Payout Batch ที่มีสถานะ `Draft`** ได้ หากชุดรายการมีหลายธุรกรรมและจำเป็นต้องจัดระเบียบใหม่ (เช่น เพื่อจ่ายบางธุรกรรมตอนนี้และรายการอื่นในภายหลัง) ฟังก์ชันนี้ช่วยให้สามารถย้ายธุรกรรมที่เลือกไปยังชุดรายการ `Draft` ใหม่ที่แยกจากกันได้ ### การจัดการความล้มเหลวและการลองใหม่ * **ฟังก์ชันที่เกี่ยวข้อง:** `RetryPayout` * ระบบของเราออกแบบมาเพื่อจัดการกับสถานการณ์ที่การจ่ายเงินอาจล้มเหลวในขั้นตอนต่างๆ 1. **การระบุ & การตรวจสอบ:** ชุดรายการที่ล้มเหลวจะถูกทำเครื่องหมายอย่างชัดเจนด้วยสถานะข้อผิดพลาด (เช่น `CashoutFail`, `CreateBatchFail`, `PayoutFail`) ทีมงานของเราสามารถตรวจสอบรายการเหล่านี้ได้ 2. **การลองใหม่ (เริ่มโดยผู้ดูแลระบบ):** ผู้ดูแลระบบสามารถใช้ `RetryPayout` เพื่อประมวลผลชุดรายการที่ล้มเหลวอีกครั้ง การดำเนินการของระบบขึ้นอยู่กับขั้นตอนที่เกิดความล้มเหลวและ `PayoutLevel` ของชุดรายการ (เช่น "M" สำหรับ Merchant Level, "S" สำหรับ Shop Level ซึ่งตั้งค่าระหว่างการสร้างชุดรายการ): * **หากเป็น `CashoutFail`** (คำขอเคลื่อนย้ายเงินทุนเริ่มต้นไปยัง KApi ล้มเหลว): * หากรายละเอียดการเรียก KApi เริ่มต้นหายไปหรือการชำระเงินล้มเหลวตั้งแต่เนิ่นๆ: ระบบจะพยายาม `RequestCashOut` อีกครั้งหลังจากตั้งค่ารายละเอียดผู้ Sนุมัติ * มิฉะนั้น (หากมีการเรียก KApi แล้วแต่สถานะไม่แน่นอนหรือต้องการรีเฟรช): ระบบจะเรียก `UpdatePaymentStatus` (ซึ่งภายในใช้ `WorkspacePaymentStatus` เพื่อสอบถาม KApi และอัปเดต จากนั้นอาจกระตุ้น `RequestBatchPayment` หากสำเร็จ) * **หากเป็น `CreateBatchFail`** (ความล้มเหลวระหว่างคำสั่ง `PayoutShop` ไปยัง KApi สำหรับการตั้งค่าการแจกจ่าย): * หาก `PayoutLevel` เป็น "M" (Merchant Level): ระบบจะลอง `RequestBatchPayment(batch.Id)` อีกครั้งโดยตรง * หาก `PayoutLevel` แตกต่างออกไป (เช่น "S" - Shop Level): ระบบจะเรียก `UpdatePayoutBatchStatus(batch.Id)` (ตัวจัดการ callback ซึ่งจะสอบถาม KApi สำหรับสถานะล่าสุดของการดำเนินการ `PayoutShop`) * **หากเป็น `PayoutFail`** (ความล้มเหลวระหว่างคำสั่ง `PayoutMerchant` ของ `MakePayout` ขั้นสุดท้ายไปยัง KApi): * หาก `PayoutLevel` เป็น "M": ระบบจะลองเรียก `MakePayout(new List<Guid> { batch.Id })` อีกครั้งโดยตรงสำหรับชุดรายการเฉพาะนั้น * หาก `PayoutLevel` แตกต่างออกไป (เช่น "S"): ระบบจะเรียก `UpdatePayoutBatchStatus(batch.Id)` (ตัวจัดการ callback ซึ่งในทางเทคนิคแล้ว `UpdatePayoutMerchantStatus` จะเป็นตัวที่เกี่ยวข้องกับความล้มเหลวของ `PayoutMerchant` มากกว่า แต่อาจมีพฤติกรรมเฉพาะของ KApi หรือการกำหนดเส้นทางภายในที่ `UpdatePayoutBatchStatus` จัดการสำหรับความล้มเหลวในการจ่ายเงินขั้นสุดท้ายระดับ "S" บางอย่างโดยการตรวจสอบสถานะชุดรายการอีกครั้ง) 3. **นัยทางธุรกิจของ Payout Levels สำหรับการลองใหม่:** การจ่ายเงินระดับ "Merchant Level" (M) มักจะอนุญาตให้มีการลองคำสั่ง KApi เดิมซ้ำอีกครั้งโดยตรงและทันที ส่วนระดับอื่นๆ (เช่น "S" - Shop Level) อาจให้ความสำคัญกับการตรวจสอบสถานะล่าสุดกับ KApi ก่อนผ่านตรรกะการสอบถาม/callback ของระบบ ก่อนที่จะตัดสินใจดำเนินการลองคำสั่งซ้ำ **เหตุผล:** เพื่อให้มีกลไกที่ยืดหยุ่น รับรู้ขั้นตอน และบางครั้งแยกตามระดับ เพื่อแก้ไขปัญหาการชำระเงิน โดยมีเป้าหมายเพื่อให้การจ่ายเงินสำเร็จในที่สุด ฟังก์ชัน `InquiryPayment` ยังช่วยให้สามารถตรวจสอบสถานะการชำระเงินแต่ละรายการกับ KApi ได้โดยตรงเมื่อต้องการ --- ### ความสามารถอื่นๆ ของระบบ * **ข้อมูลสำหรับรายงาน:** ระบบมีฟังก์ชัน (`GetPayoutSummary`, `GetPayoutBatch`, `GetPayoutBatchDetail`) เพื่อดึงข้อมูลการจ่ายเงินแบบสรุปและแบบละเอียด ซึ่งน่าจะใช้สำหรับแดชบอร์ดของผู้ดูแลระบบและการรายงานทางการเงิน * **รายงานกระทบยอดรายวัน:** กระบวนการแยกต่างหาก (`GetDailyPayoutReport`) สามารถดึงรายงานการจ่ายเงินรายวันจาก SFTP ของ KBank, แปลงเป็น Excel, และจัดเก็บไว้ใน Google Cloud Platform เพื่อช่วยในการกระทบยอด ### บทบาทและระบบที่สำคัญ * **ระบบงานอัตโนมัติ/งานที่ตั้งกำหนดการ (Automated Workers/Scheduled Tasks):** จัดการการประมวลผลตามปกติ เช่น การสร้างรายการรอจ่ายเริ่มต้น (`AddPendingPayoutTransaction`), การสร้างชุดร่างสำหรับการจ่ายเงินรายวัน (`CreateBatchDraft`), และการตรวจสอบสถานะการชำระเงินกับ KApi เป็นระยะ (`WorkspacePaymentStatusAll`) * **ผู้ดูแลระบบ (ทีมการเงิน/ปฏิบัติการ):** ตรวจสอบและอนุมัติชุดรายการ (`ApproveBatchDraft`), เริ่มการจ่ายเงินขั้นสุดท้ายให้ผู้ค้า (`MakePayout`), จัดการการลองใหม่สำหรับการชำระเงินที่ล้มเหลว (`RetryPayout`), และอาจมีการแบ่งชุดรายการร่าง (`SplitPayoutBatch`) * **ระบบของคุณ (แอปพลิเคชันนี้):** ควบคุมกระบวนการทั้งหมด, ดูแลรักษาบันทึก, ใช้ตรรกะทางธุรกิจ, และเริ่มต้นการสื่อสารกับ KApi * **พันธมิตรผู้ให้บริการประมวลผลการชำระเงิน (KApi - ระบบภายนอก):** บริการภายนอกที่รับผิดชอบในการเคลื่อนย้ายเงินทุนจริง, ยืนยันสถานะการชำระเงิน, ประมวลผลการจ่ายเงินไปยังผู้ค้าและพันธมิตร, และเรียก callback กลับมายังระบบของคุณเพื่อรายงานผลลัพธ์ของการดำเนินการบางอย่าง ### ประโยชน์ของระบบนี้ * **ความแม่นยำ:** การคำนวณและการแบ่งส่วนต่างๆ เป็นไปโดยอัตโนมัติ ช่วยลดข้อผิดพลาดจากคน จำนวนเงินทั้งหมดจะถูกปัดเศษอย่างระมัดระวัง (`RoundDownToSecondPoint`) * **ประสิทธิภาพ:** การจัดกลุ่มเป็นชุดและระบบอัตโนมัติช่วยประหยัดเวลาและความพยายามได้อย่างมาก * **การควบคุม:** ขั้นตอนการอนุมัติช่วยให้มั่นใจได้ว่ามีการกำกับดูแลก่อนที่จะมีการผูกมัดเงินทุน กลไก Callback ช่วยให้ได้รับการอัปเดตที่ทันท่วงที * **ความโปร่งใส:** สถานะต่างๆ ช่วยให้เห็นภาพว่าการจ่ายเงินแต่ละรายการอยู่ในขั้นตอนใด * **ความยืดหยุ่นในการกู้คืน:** กลไกการลองใหม่และการบันทึกข้อผิดพลาดที่ชัดเจนช่วยในการกู้คืนจากปัญหาชั่วคราวหรือระบุปัญหาสำหรับการดำเนินการด้วยตนเอง ระบบการจ่ายเงินอัตโนมัตินี้ช่วยให้การดำเนินงานทางการเงินของคุณมีความคล่องตัว และทำให้มั่นใจได้ว่าผู้ขายและพันธมิตรที่มีค่าของเราจะได้รับเงินอย่างถูกต้องและตรงเวลา -->