---
# System prepended metadata

title: Rakmao Payment | Payout Flow

---

# 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 ช่วยให้ได้รับการอัปเดตที่ทันท่วงที
* **ความโปร่งใส:** สถานะต่างๆ ช่วยให้เห็นภาพว่าการจ่ายเงินแต่ละรายการอยู่ในขั้นตอนใด
* **ความยืดหยุ่นในการกู้คืน:** กลไกการลองใหม่และการบันทึกข้อผิดพลาดที่ชัดเจนช่วยในการกู้คืนจากปัญหาชั่วคราวหรือระบุปัญหาสำหรับการดำเนินการด้วยตนเอง

ระบบการจ่ายเงินอัตโนมัตินี้ช่วยให้การดำเนินงานทางการเงินของคุณมีความคล่องตัว และทำให้มั่นใจได้ว่าผู้ขายและพันธมิตรที่มีค่าของเราจะได้รับเงินอย่างถูกต้องและตรงเวลา -->