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