[← หน้ารวม](../index.html)

# affiliate — เอกสารส่งต่อ

> อ่านไฟล์นี้ก่อนเริ่มงาน ทั้งฝั่งดีไซน์และฝั่งเดฟ
> ของที่ต้องอ่านต่อ: [`affiliate.md`](affiliate.md) (สเปก) · [`affiliate-flow.html`](affiliate-flow.html) (ผัง) · [`../prototype/affiliate.html`](../prototype/affiliate.html) (กดเล่นได้)

---

> **งานชุดนี้เป็นฟีเจอร์ที่ต่อเข้ากับแอป dailyMu ที่มีอยู่แล้ว** — ระบบล็อกอิน บัญชีผู้ใช้ อีเมล มีอยู่แล้วทั้งหมด
> ไม่ต้องออกแบบใหม่ · เอกสารนี้ครอบเฉพาะส่วน affiliate ที่เพิ่มเข้าไป

## 🚧 เรื่องที่ทีมเดฟต้องเคลียร์กับทีมพอร์ทัลกลาง

สองข้อนี้ **ไม่ใช่การตัดสินใจเชิงธุรกิจ** — เป็นของที่เดฟคุยกับเดฟ แต่ต้องได้คำตอบก่อนเขียนโค้ด

### 1. การล็อกอินข้ามไปพอร์ทัลกลาง

ชุดงานนี้ตั้งอยู่บนกฎว่า *"ของละเอียดให้กดลิงก์ไปพอร์ทัล"* มีปุ่มแบบนั้นอยู่ **6 จุด**
ถ้าผู้ใช้กดแล้วเจอหน้า login ที่ต้องกรอกใหม่ = ทางตัน และ**กฎข้อนี้ใช้ไม่ได้ทั้งข้อ**
สิ่งที่ต้องได้คือ **one-time login link + return URL** จากพอร์ทัล

นอกจากนี้ต้องเคลียร์ว่า **แอปยิง API ด้วย token อะไร อายุเท่าไหร่ refresh ยังไง เจอ 401 ทำยังไง**

> ✅ **ไม่ต้องห่วงเรื่องอีเมลและรหัสผ่าน** — แอปเก็บอีเมลตั้งแต่ตอนสมัครใช้แอปอยู่แล้ว (แก้ในแอปไม่ได้ แต่ทีมมีวิธีรับมือ)
> และมีระบบล็อกอินอยู่แล้ว · หน้าแก้บัญชีรับเงินให้เดฟเชื่อมกับกลไกยืนยันตัวตนที่แอปมีอยู่

### 2. สัญญา API ทั้งชุด

ตอนนี้ทั้งงานอ้างถึง API แค่ 2 บรรทัด — `GET /affiliate/me · /commissions · /wallet` และ `POST /affiliate/kyc/documents`
ไม่มี schema ไม่มีชื่อฟิลด์ ไม่มีชนิดข้อมูล ไม่มี error code

**endpoint ที่ flow ต้องใช้แต่ยังไม่เคยถูกเอ่ยถึงเลย:** สมัครเป็นผู้แนะนำ · ดึงค่า config ·
สร้าง/ลิสต์/ดูรายละเอียดคำขอถอน · อ่านและแก้บัญชีรับเงิน · ยืนยันรหัสผ่าน · ดึงลิงก์และโค้ด ·
รายการเดินบัญชี · สรุปกราฟ 6 เดือน

**ต้องขอจากทีมพอร์ทัล** — OpenAPI spec หรืออย่างน้อยตาราง endpoint × field × type × ตัวอย่าง JSON

> ⚠️ **สิ่งที่ต้องยืนยันเป็นข้อแรก** — endpoint ต้องส่งมาแต่**จำนวนเงิน** ไม่มี % หรือ pool structure ติดมา
> ถ้าส่งมา = ละเมิดกฎ No rate exposure ตั้งแต่ต้นทาง ไม่ใช่แค่แอปแสดงผิด

---

## ✅ เคาะแล้ว — ใบสมัครไม่ต้องรออนุมัติ

**เข้าร่วมได้ทันที ไม่ต้องรออนุมัติ** — ขอให้เดฟตั้งพอร์ทัลเป็น **auto-approve** สำหรับ dailyMu
(พอร์ทัลมีระบบอนุมัติอยู่แล้ว ไม่ต้องแก้อะไร แค่ตั้งค่า)

บันไดการเข้าร่วมแบ่งเป็น 3 ขั้น — ดูรายละเอียดที่ [`affiliate.md` ข้อ 3.3](affiliate.md)
และผังในหัวข้อ 1 ของ [`affiliate-flow.html`](affiliate-flow.html)

> หมายเหตุ: prototype **ไม่ได้ทำหน้า "รออนุมัติ"** ไว้ ถ้าวันหลังอยากเปิดโหมดอนุมัติ
> ต้องเพิ่มหน้าจอ 3 แบบ (รออนุมัติ · ไม่อนุมัติ+เหตุผล · ภาพรวมตอนยังไม่มีลิงก์) — ทำทีหลังแพงกว่า

## ⚖️ เรื่องเดียวที่ต้องทำก่อนเปิดใช้จริง — เงื่อนไขโปรแกรมผู้แนะนำ

หน้าเข้าร่วมมี**ติ๊กบังคับยอมรับเงื่อนไข**แล้ว และมีปุ่มกดอ่าน — แต่ **ตัวเอกสารจริงยังไม่มี**
ใน prototype เป็น sheet ที่ลิสต์แค่ *หัวข้อ 8 ข้อที่เอกสารจริงต้องครอบ*

**เป็นประเด็นกฎหมาย ไม่ใช่แค่ UX** — บังคับให้ยอมรับสิ่งที่ผู้ใช้อ่านไม่ได้ · ต้องให้ฝ่ายกฎหมายร่างก่อนเปิดใช้จริง

---

## ✅ เคาะแล้ว — ออกแบบบนนี้ได้เลย ไม่ต้องรอ

| เรื่อง | ข้อสรุป |
|---|---|
| ทางเข้า | แถวเดียวในเมนูโปรไฟล์ · ไม่มีแท็บล่าง ไม่มีการ์ดหน้าแรก |
| ทีม / upline | เฟสแรกไม่มี · ต่อให้อนาคตมี แอปแสดงแค่ค่าคอมของตัวเอง |
| นิติบุคคล / VAT / ใบกำกับภาษี | เฟสนี้ไม่รับ · ลิงก์ไปพอร์ทัล |
| ห้ามโชว์ % ค่าคอม | เด็ดขาด · ใช้ป้าย "ซื้อครั้งแรก / ซื้อซ้ำ ครั้งที่ N จาก M" แทน |
| ยอดขายขั้นต่ำรายรอบ | **เปิดอยู่** → ต้องมีสถานะ "ถูกตัด" และยอดก้อน "รอผ่านเป้ารอบนี้" |
| สถานะการถอน | 7 สถานะจริง → แอปยุบแสดง 4 กลุ่ม · timeline เต็มที่พอร์ทัล |
| Suspended | ขึ้นแบนเนอร์แดง แต่**ถอนเงินได้** ห้ามปิดปุ่มถอน |
| ค่าตั้งทุกตัว | มาจาก API ห้าม hardcode |
| KYC | อัปโหลด 2 ไฟล์ ไม่มีเซลฟี่/liveness · บุคคลธรรมดาเท่านั้น |

---

## 🎨 ฝั่งดีไซน์ — สิ่งที่ยังไม่มีใครออกแบบ

### ต้องทำแน่ๆ

| # | เรื่อง | ทำไมสำคัญ |
|---|---|---|
| D1 | **สถานะ loading / API ล่ม / ไม่มีเน็ต** ของ 7 หน้าที่ดึง API | ทุกหน้าเขียนเหมือนข้อมูลมาถึงแล้วเสมอ · เรื่องเงิน + จอว่าง = ผู้ใช้คิดว่าเงินหาย |
| D2 | **สถานะ "กำลังส่ง" ของปุ่มธุรกรรม** (ขอถอน · ส่งเอกสาร · บันทึกบัญชี) | กดซ้ำตอนเน็ตช้า = คำขอถอนซ้อน 2 ใบ |
| D3 | **pattern refresh** — pull-to-refresh หรือปุ่ม เลือกอันเดียวใช้ทั้งระบบ | ตอนนี้ผู้ใช้ไม่มีทางอัปเดตยอดนอกจากเดินออกแล้วเข้าใหม่ |
| D4 | **ยอดก้อนที่ 3 "รอผ่านเป้ารอบนี้" ต้องกดดูได้** ว่าประกอบด้วยรายการไหน | ก้อนนี้กำลังจะกลายเป็น "ถูกตัด" ถ้าอธิบายไม่ได้ = เรื่องร้องเรียน |
| D5 | **empty state แยกต่อแท็บ** | แก้ไปแล้วใน prototype — เอาไปใช้ต่อได้ · ที่ยังขาดคือหน้าภาพรวม "วันแรก" ที่ยอด ฿0 (ตอนนี้อ่านเหมือนระบบพัง) |
| D6 | **type scale / spacing scale** | ตอนนี้มีขนาดฟอนต์ **26 ค่า** และ spacing **21 ค่า** ที่ไม่อยู่บน grid เดียวกัน · ยุบเหลือ ~8 ขั้น + 4pt grid ก่อนตั้ง Figma library |
| D7 | **เคส "ข้อความยาวสุด"** | ชื่อธนาคารยาว ("ธนาคารเพื่อการเกษตรและสหกรณ์การเกษตร") · ชื่อบัญชี 2 บรรทัด · ยอด 7 หลัก · เหตุผล KYC 4 บรรทัด — ยังไม่เคยวาด |
| D8 | **dark mode เอาหรือไม่เอา** | ถ้าแอปหลักมี แล้ว affiliate ขาวโพลน = แปลกแยก · เติมทีหลัง = ทำ Figma สองรอบ |
| D9 | **icon inventory** | ตอนนี้เป็น emoji ล้วน ~25 ตัว ใช้บอกสถานะ · เรนเดอร์ไม่เหมือนกันข้าม iOS/Android |
| D10 | **copy deck** | ข้อความสำคัญกระจายอยู่ในโค้ด (ปุ่ม 5 แบบ · แบนเนอร์ 8 · toast · hint · empty) ไม่มีที่ให้ฝ่ายกฎหมายรีวิว |

### แก้ไปแล้วใน prototype — ยกไปใช้ได้เลย

- **contrast ผ่าน WCAG AA ทุกคู่** (เดิมไม่ผ่าน 5 คู่) — `--ink-3` `--gold` `--warn` ปรับใหม่แล้ว
- **ปุ่ม disabled ไม่ใช้ opacity แล้ว** เพราะข้อความบนปุ่มคือ "เหตุผลที่ถอนไม่ได้" ซึ่งเป็นข้อมูลสำคัญที่สุดในหน้า
- **ป้าย `ซื้อครั้งแรก / ซื้อซ้ำ` ขยายเป็น 11px** — มันคือข้อมูลที่ใช้แทนการโชว์ % ไม่ควรเป็นตัวเล็กที่สุดในจอ
- `env(safe-area-inset-bottom)` ที่ footer · `:focus-visible` · `prefers-reduced-motion`

### ยังไม่ได้แก้ — ฝากดีไซเนอร์ตัดสิน

- **touch target < 44px** หลายจุด: ปุ่ม "ลบ" ไฟล์แนบ (~21×30) · ปุ่ม "เปลี่ยน" บัญชี (~30) · ปุ่มย้อนกลับ (38×38) · ปุ่มลัดจำนวนเงิน (~33) · แท็บ (~40)
  — "ลบ" เป็นปุ่มทำลายข้อมูลที่เล็กที่สุดในแอป และ "เปลี่ยนบัญชี" เป็น flow ความปลอดภัย
- **ฟอนต์ 9.5–10.5px** ยังเหลืออยู่บ้าง (label กราฟ) · ทุกอย่าง fix เป็น px ไม่ขยายตาม Dynamic Type

---

## 🗺 ผังหน้าจอ — prototype มี 12 หน้า ไม่ใช่ 7

สเปกข้อ 3.1 ลิสต์ไว้ 7 หน้า แต่ของจริงมีมากกว่านั้น **ใช้ตารางนี้ประเมินขนาดงาน**

| หน้า | id | เข้าจากไหนได้บ้าง |
|---|---|---|
| โปรไฟล์ (ทางเข้า) | `s-profile` | หน้าแรกของ prototype |
| ชวนสมัคร | `s-join` | โปรไฟล์ (เมื่อยังไม่สมัคร) |
| ภาพรวม affiliate | `s-aff` | โปรไฟล์ (เมื่อสมัครแล้ว) · หลังสมัคร · หลังถอนสำเร็จ |
| แชร์ลิงก์ · QR | `s-link` | ภาพรวม |
| รายได้ของฉัน | `s-earn` | ภาพรวม |
| รายละเอียดส่วนแบ่ง | `s-earn-item` | รายได้ (กดรายการ) |
| กระเป๋าเงิน | `s-wallet` | ภาพรวม · หลังถอนสำเร็จ |
| ขอถอนเงิน | `s-withdraw` | ภาพรวม · กระเป๋า · **และย้อนกลับจากหน้าแก้บัญชี** ← ทางนี้เคยเป็นบั๊ก |
| ขอถอนสำเร็จ | `s-wd-ok` | ขอถอน |
| ยืนยันตัวตน | `s-kyc` | ภาพรวม · แบนเนอร์ · ด่านก่อนถอน |
| บัญชีรับเงิน | `s-bank` | ภาพรวม · หลังบันทึกบัญชี |
| แก้บัญชีรับเงิน | `s-bank-edit` | บัญชีรับเงิน · แบนเนอร์ · ด่านก่อนถอน · **ขอถอน (ปุ่ม "เปลี่ยน")** |

**บวก bottom sheet อีก 3 แบบ** — ยืนยันการถอน · รายละเอียดคำขอถอน · เลือกวิธีแนบไฟล์
**บวก toast 3 แบบ** — ปกติ / สำเร็จ / เตือน

> 💡 สังเกตว่า `s-withdraw` กับ `s-bank-edit` เข้าได้หลายทาง — เป็นต้นเหตุของบั๊กที่เคยเจอ
> (เข้าหน้าขอถอนโดยไม่ผ่านด่านตรวจ) **ทุกหน้าที่เข้าได้หลายทางต้องตรวจเงื่อนไขซ้ำตอน render เสมอ**

---

## 🔧 ฝั่งเดฟ — สิ่งที่ต้องได้คำตอบก่อนเขียนโค้ด

| # | เรื่อง | สาระ |
|---|---|---|
| E1 | **ค่าไหนมาจาก API ค่าไหนคำนวณในแอป** | prototype คำนวณเอง 5 ค่าที่**จะผิดทันทีเมื่อต่อของจริง**: ยอด "รอปล่อย" · "เพื่อนที่ซื้อแล้ว N คน" · กราฟ 6 เดือน · รายการเดินบัญชี · ยอดสุทธิหลังหักภาษี — ทั้งหมดคำนวณจาก "ลิสต์ที่โหลดมา" ซึ่งของจริงมี pagination |
| E2 | **ห้ามคำนวณภาษีฝั่ง client** | ด้วยเหตุผลเดียวกับที่ห้ามคำนวณ rate — ถ้าพอร์ทัลปัดเศษคนละแบบ ตัวเลขที่ผู้ใช้เห็น**ก่อน**กดยืนยันจะไม่ตรงกับที่โอนจริง · ให้ API ส่ง `gross / wht / net` มาครบ · เก็บเงินเป็นจำนวนเต็มสตางค์ |
| E3 | **idempotency key ของคำขอถอน** | ยังไม่มี · กดซ้ำหรือเน็ตหลุดหลังส่ง = ถอนซ้อน |
| E4 | **ด่านตรวจต้องมีฝั่งเซิร์ฟเวอร์ด้วย** | prototype ตรวจ 3 จุดแต่**อยู่ฝั่ง client ทั้งหมด** กัน UX ได้ กัน attacker ไม่ได้ · ทุกด่าน (KYC / บัญชี / ขั้นต่ำ / สถานะบัญชี) ต้อง validate ซ้ำที่เซิร์ฟเวอร์ |
| E5 | **timezone** | ต้องตัดตาม **Asia/Bangkok** เสมอ ไม่ใช่ timezone ของเครื่อง · มี 4 จุดที่ผูกกับเส้นแบ่งวัน: วันพ้น hold · วันสรุปรอบ · รอบโอน · วันที่เกิดรายการ · และต้องล็อกว่าใช้ พ.ศ. หรือ ค.ศ. |
| E6 | **pagination** | ยังไม่ถูกพูดถึงเลย · ผู้แนะนำจริงจะมีรายการเป็นพัน · ตัวเลขนับบนแท็บต้องมาจาก API ไม่ใช่นับฝั่ง client |
| E7 | **fallback ของ enum ที่ไม่รู้จัก** | ถ้าพอร์ทัลเพิ่มสถานะใหม่ โค้ดปัจจุบัน `CM_STATUS[c.status].tab` จะพังทันที · ต้องมีกลุ่ม "อื่นๆ" + log |
| E8 | **error taxonomy + retry policy** | ไม่มี error code list ไม่มี backoff · ต้องมีข้อความไทยต่อ code |
| E9 | **อัปโหลดไฟล์** | multipart ตรง vs signed URL · **บีบอัดรูปฝั่งแอปไหม** (รูปบัตรจากกล้องมือถือ 8–12MB เกิน 5MB ที่ UI เขียนไว้เป็นประจำ) · HEIC ของ iPhone · permission ถูกปฏิเสธ · prototype ไม่มี `<input type="file">` จริงสักตัว |
| E10 | **attribution** | `dailymu.com/r/CODE` ยังเป็นกล่องเดียวในผัง · ยังไม่มีสเปก: กดแล้วเจอหน้าอะไร · **ถ้ายังไม่ติดตั้งแอปทำยังไง (deferred deep link)** ← เคสที่เกิดบ่อยที่สุดและยากที่สุด · 30 วัน last-click เก็บที่ไหน · ข้ามเครื่องนับไหม |
| E11 | **ยกเลิกคำขอถอนได้ไหม** | ผังบอกว่า `Processing` คือจุดย้อนกลับไม่ได้ = นัยว่าก่อนหน้านั้นย้อนได้ แต่แอปไม่มีปุ่มยกเลิก · ถ้ายกเลิกไม่ได้ต้องบอก**ก่อน**กด |
| E12 | **แจ้งเตือน** | ยังไม่มีสเปกว่าใครยิง ช่องทางไหน กดแล้วไปไหน · สถานะที่**ควร**แจ้งที่สุดคือ `Failed`/`Returned` (เงินค้าง ต้องลงมือแก้) |
| E13 | **i18n เอาหรือไม่เอา** | ถ้าไทยอย่างเดียว ให้เขียนลงไปว่าไม่ทำ · ถ้าจะทำ ต้องวางโครง key ตั้งแต่ตอนนี้ เพราะข้อความประกอบจากชิ้นส่วน แยกทีหลังแพงกว่ามาก |
| E14 | **QR และปุ่มแชร์ยังเป็นของปลอม** | `drawQR()` สร้างลายสุ่มที่สแกนไม่ได้ · ปุ่ม LINE/เฟซบุ๊กเป็นแค่ toast · ต้องเคาะไลบรารี + fallback เมื่อไม่มีแอปปลายทาง |

---

## ⚠️ เรื่องที่ต้องเคาะนอกทีม

**นโยบายคืนเงินของ dailyMu** — สเปกบอกว่า `Hold Period` ต้องครอบระยะขอคืนเงิน
แต่ `docs/spec.md` **ไม่มีนโยบายคืนเงินแพลน/เครดิตเลย** (`planId` ระบุว่านอกสโคป)

ถ้าขายผ่าน App Store / Play การคืนเงินเป็นสิทธิ์ของสโตร์ ย้อนได้นานกว่า Hold มาก
และเกิดได้**หลัง**ส่วนแบ่งถูกถอนออกไปแล้ว ซึ่งสถานะ `Reversed` ไม่ครอบเคสนี้

**ต้องตอบให้ได้** — ถ้าสโตร์คืนเงินหลังส่วนแบ่งถูกถอนไปแล้ว ใครรับภาระ

---

## 📋 checklist ก่อนกดส่ง

**เรื่อง business ที่ต้องเคาะ** — ดูรายละเอียดที่ [`affiliate.md` ข้อ 12](affiliate.md)

- [ ] **นโยบายคืนเงิน** → ถึงจะตั้ง `Hold Period` ได้
- [ ] **ถ้าสโตร์คืนเงินหลังจ่ายคอมไปแล้ว ใครรับภาระ**
- [ ] **ยอดขายขั้นต่ำรายรอบตั้งเท่าไหร่** — `0` = ปิด · `1 บาท` ≠ ปิด (ตรรกะยังทำงาน คนที่รอบนั้นขายไม่ได้เลยจะโดนตัดคอมจริง)
- [ ] **เอกสารเงื่อนไขโปรแกรมผู้แนะนำ** — ฝ่ายกฎหมายร่าง (prototype มีปุ่มกดอ่านแล้ว แต่เนื้อหายังเป็นแค่รายการหัวข้อ) · ต้องครอบ 2 เคสที่ยังไม่เคาะ: **เลิกเข้าร่วมได้ไหม** · **เพื่อนที่เป็นผู้ใช้เดิมกดลิงก์แล้วนับไหม** และเขียนเรื่อง **ซื้อเองไม่ได้คอม** ให้ชัด

**เคาะแล้ว ไม่ต้องทำอะไรต่อ**

- ✅ **ใช้ลิงก์ตัวเองซื้อเอง** — กดได้แต่ไม่ได้ค่าคอม · ตรรกะหลังบ้านล้วน **แอปไม่ต้องมีหน้าจอหรือข้อความอะไรเพิ่ม**
- ✅ **ล็อกอิน** — แอปมีระบบล็อกอินอยู่แล้ว งาน affiliate นี้เป็นฟีเจอร์ที่ต่อเข้าไป ไม่ต้องออกแบบ auth ใหม่ · หน้าแก้บัญชีรับเงินให้เดฟเชื่อมกับกลไกยืนยันตัวตนที่แอปมีอยู่

**เรื่องที่ทีมรับต่อจัดการเอง ไม่ต้องรอเคาะ**

- [ ] ขอ API spec จากทีมพอร์ทัล (เดฟคุยกับเดฟ)
- [ ] เคาะ dark mode ก่อนเข้า Figma (ดีไซเนอร์)
- [ ] ตรวจว่าไม่มีตัวเลข config หลุดในเอกสารก่อน deploy — `grep -nE '[0-9]+%' docs/affiliate*.*`
