พาร์ทเนอร์ (partner) คือตัวแทนจำหน่ายที่ขาย SMEDNA ต่อให้ร้านค้าในเครือของตัวเอง ภายใต้ผลิตภัณฑ์ของตัวเองที่เรียกว่า Edition คู่มือเล่มนี้เดินตามเรื่องสมมติเรื่องเดียวตั้งแต่ต้นจนจบ: บริษัท ส่งดี โฮลเซล จำกัด ผู้ค้าส่งใน จ. นครราชสีมา ที่ขาย SMEDNA ให้ร้านโชห่วยในเครือประมาณ 40 ร้าน ทุกภาพถ่ายจากระบบจริงบนสภาพแวดล้อมทดสอบภายใน ไม่ใช่ภาพจำลอง (ดูกรอบ “ภาพในเล่มนี้กับ prod” ในบทที่ 0)
ภาพรวมทั้งเล่มในบทเดียว: ใครทำอะไร แก้แล้วร้านเจออะไร และปรับแต่งได้แค่ไหน — ขั้นตอนละเอียดพร้อมภาพหน้าจออยู่ในบทที่ลิงก์ไว้ในแต่ละหัวข้อ
คุณนำ SMEDNA ไปขายต่อให้ร้านในเครือ ภายใต้ชุดเมนูและช่องกรอกของคุณเองที่เรียกว่า Edition ทุกร้านใช้แอปเดียวกัน คุณจึงปรับได้เองโดยไม่ต้องเขียนโค้ด และเมื่อ SMEDNA อัปเดตระบบ ทุกร้านได้พร้อมกัน
SMEDNA เปิดบัญชีให้ก่อน จากนั้นคุณจัด Edition เซลส์เปิดร้าน แล้วร้านใช้งานจริง หลังเปิดร้านแล้วมีงานต่อเนื่องอีกสามอย่าง
ทำสัญญาและลงวันเซ็น DPA · ตั้งราคาส่งของแต่ละแผน · เพิ่มคนในทีมของคุณ ทั้งเจ้าของและเซลส์
ดูละเอียด: บทที่ 1เลือกเมนูที่ร้านจะเห็น · เพิ่มช่องกรอกของคุณเอง เช่น “วันรับของประจำ” · กด “ดูผลกระทบแล้วเผยแพร่”
ดูละเอียด: บทที่ 3ที่เมนู “เปิดร้านใหม่” กรอกชื่อร้าน อีเมลเจ้าของร้าน เลือก Edition แผน และประเภทร้าน แล้วส่งลิงก์เชิญให้เจ้าของร้าน หรือส่ง “ลิงก์ทดลองใช้” ในหน้าภาพรวมให้ร้านสมัครเอง
ดูละเอียด: บทที่ 4ร้านเห็นเมนูและช่องกรอกตาม Edition ของคุณ · ข้อมูลของร้านเป็นของร้าน
ดูละเอียด: บทที่ 6Edition มีสองฉบับเสมอ คือฉบับที่คุณกำลังแก้ กับฉบับที่ร้านใช้อยู่ ร้านจะได้ของใหม่ก็ต่อเมื่อคุณกดเผยแพร่
บันทึกได้เรื่อย ๆ ระหว่างนี้ร้านยังเห็นฉบับเดิม
กด “ดูผลกระทบแล้วเผยแพร่” ระบบบอกว่ามีกี่ร้านที่จะเปลี่ยน
ดูละเอียด: บทที่ 3 (ขั้นตอน P04)ส่งให้ทุกร้าน หรือให้เฉพาะร้านทดลองก่อนก็ได้
ร้านไม่ต้องทำอะไร อาจใช้เวลาราวครึ่งนาที
การเพิ่มหรือลบช่องกรอก และการเปิดปิดเมนู มีผลหลังคุณกดเผยแพร่
| สิ่งที่คุณทำ | ร้านเห็นอะไร | ข้อมูลเดิมของร้าน |
|---|---|---|
| เพิ่มช่องกรอก | ช่องใหม่ในฟอร์มลูกค้า | ลูกค้าเก่ายังว่างในช่องนี้ รอร้านกรอกดูละเอียด: บทที่ 6 |
| ลบช่องกรอก | ช่องหายจากฟอร์ม | ค่าเดิมไม่แสดง และจะหายเมื่อร้านบันทึกลูกค้าคนนั้นครั้งถัดไป ถ้าต้องการเก็บ ให้ร้าน export ที่หน้า “กล่องข้อมูลของร้าน” ก่อนดูละเอียด: บทที่ 6 |
| ปิดหรือเปิดเมนู | เมนูหายหรือกลับมา | ไม่หาย แค่ซ่อนไว้ |
| ใส่เมนูเกินแผนของร้าน | ไม่มีผล | Edition ตัดเมนูออกได้ แต่เพิ่มเกินแผนที่ร้านซื้อไม่ได้ |
| ย้อนเวอร์ชัน | กลับไปเหมือนฉบับเก่า | ประวัติทุกฉบับยังอยู่ |
| เลิกใช้ Edition | เมนูครบตามแผนที่ร้านซื้อ | ไม่หาย |
| คืนร้าน | ร้านไม่อยู่ใต้ Edition ของคุณแล้ว | ข้อมูลในช่องกรอกของคุณเก็บไว้อย่างน้อย 30 วันก่อนลบ ถ้าลูกค้าขอลบข้อมูลภายหลัง ระบบยังแจ้งคุณผ่าน webhookดูละเอียด: บทที่ 4 และ บทที่ 6 |
Edition Studio ปรับได้มากกว่าการเพิ่มช่อง มี 9 อย่างที่ทำเองได้วันนี้ โดยไม่ต้องเขียนโค้ด แบ่งเป็น 4 กลุ่มด้านล่าง ทำทั้งหมดที่เมนู “Edition” (/partner/editions) แล้วเปิด Edition ของคุณ
ลำดับงาน เปิด /partner/editions · เลือกต้นแบบหรือ Edition เดิม · แก้ตามแท็บ · ดูตัวอย่างและผลกระทบ · เผยแพร่ beta · เมื่อมั่นใจเลื่อนเป็น stable
แท็บ “แบรนด์/ธีม” ตั้งชื่อผลิตภัณฑ์ โลโก้ สี หรือเลือกธีมสำเร็จรูป · แท็บ “คำเรียก” เปลี่ยนคำที่ขึ้นบนหน้าจอ แยกตามภาษา
แท็บ “ความสามารถ” ตัดฟีเจอร์ที่ร้านไม่ต้องใช้ · แท็บ “เมนู” ซ่อนหรือปักหมุดเมนู · แท็บ “wizard” ตั้งขั้นตอนเริ่มใช้งานครั้งแรก แต่ละขั้นเป็นลิงก์ที่มีชื่อไปหน้าที่มีอยู่แล้ว · แท็บ “คู่มือ” เลือกบทคู่มือที่จะแสดงดูละเอียด: บทที่ 3 (ขั้นตอน P03)
pickup_day · เลือก “ชนิด” ได้แก่ ข้อความ ตัวเลข วันที่ ตัวเลือก หรือติ๊กถูก · ติ๊ก “ต้องกรอก” ถ้าบังคับเริ่มจากต้นแบบ 132 แบบ หรือคัดลอก Edition เดิมในหน้าเลือกต้นแบบ · เก็บเป็นฉบับร่าง ดูตัวอย่าง ตรวจผลกระทบก่อนเผยแพร่ · ปล่อย beta ก่อน แล้วค่อยเป็น stable · ย้อนกลับไปฉบับก่อนได้
มีสามทาง เลือกตามลักษณะงาน
ระบบของคุณ + Partner API
เหมาะกับงานหลังบ้านของเครือ เช่น สรุปยอดทั้งเครือหรือ CRM · อ่านข้อมูลร้าน ลูกค้า และยอดขาย · สร้างลูกค้าใหม่ได้ถ้าคีย์มีสิทธิ์เขียน · รับ webhook ต้องมีโปรแกรมเมอร์ฝั่งคุณ
ดูละเอียด: บทที่ 5 และ บทที่ 7Extension
สร้างข้อมูลและฟอร์มชนิดใหม่ให้โผล่ในแอปร้าน เช่น ตารางรอบส่งของ · จะให้ AI ช่วยร่างแล้วคุณอนุมัติก่อนใช้ · ร้านกดติดตั้งและยินยอมเอง
ให้ SMEDNA ทำในแกน
งานที่แตะเงิน สต็อก ใบเสร็จ ภาษี หรือหน้าขาย ต้องทำในแกนเพื่อความถูกต้อง · เสร็จแล้วทุกพาร์ทเนอร์เปิดใช้ผ่าน Edition ได้
สามเรื่องนี้ไม่ต้องเผยแพร่ บันทึกแล้วมีผลเลย
https://customer.erased (ลูกค้าขอลบข้อมูล) ปิดไม่ได้ ระบบของคุณจะได้ลบตามX-Partner-Signature ให้ตรวจว่ามาจาก SMEDNA จริงใช้ได้ทุกเมนูในคอนโซล รวมการจัด Edition และเมนู “การเชื่อมต่อ” (คีย์ API และ webhook)
เปิดร้านใหม่ และขอช่วยดูแลร้าน · ไม่เห็นเมนู “การเชื่อมต่อ”
อ่านบทนี้ก่อนบทอื่นทุกครั้ง — ชื่อคนและร้านในภาพทุกภาพของเล่มนี้มาจากตารางเดียวกันนี้
ส่งดี โฮลเซล เปิดบัญชีพาร์ทเนอร์กับ SMEDNA แล้วขาย SMEDNA ต่อให้ร้านโชห่วยในเครือ ภายใต้ผลิตภัณฑ์ของตัวเองชื่อ “โชห่วยส่งดี” (Edition — ผลิตภัณฑ์ที่พาร์ทเนอร์ตัดแต่งเมนูและช่องกรอกเองจากของกลาง SMEDNA) พนักงานขายของส่งดีเป็นคนดูแลร้านแต่ละร้าน ส่วนซีอาร์เอ็ม (CRM — โปรแกรมบริหารลูกค้า) ของส่งดีอ่านข้อมูลผ่าน Partner API (ช่องทางให้โปรแกรมของพาร์ทเนอร์ดึงข้อมูลจาก SMEDNA) ด้วยคีย์ API (รหัสลับที่โปรแกรมใช้แทนรหัสผ่าน หน้าจอเรียกว่า “กุญแจ API”) และรับการแจ้งเตือนอัตโนมัติผ่าน webhook (SMEDNA ส่งข้อความไปยัง URL ของพาร์ทเนอร์ทันทีที่มีเหตุการณ์)
| บทบาท | ชื่อ | อีเมล | หมายเหตุ |
|---|---|---|---|
| ผู้ดูแลแพลตฟอร์ม (admin) | ทีมแพลตฟอร์ม SMEDNA | [email protected] | เปิดบัญชีพาร์ทเนอร์และคุมรั้วความปลอดภัย |
| เจ้าของกิจการพาร์ทเนอร์ (owner) | สมชาย ส่งดี | [email protected] | แก้ Edition ออกคีย์ API และคืนร้านได้ |
| พนักงานขายพาร์ทเนอร์ (sales) | มานี ขายเก่ง | [email protected] | เปิดร้านใหม่และขอสิทธิ์ช่วยเหลือร้านได้ ไม่เห็นเมนู “การเชื่อมต่อ” (คีย์ API และ webhook) |
| พนักงานขายพาร์ทเนอร์ (sales) | วิชัย ใจบริการ | [email protected] | สมัครบัญชีไว้ก่อน แล้วผู้ดูแลแพลตฟอร์มเพิ่มเข้าองค์กรในบทที่ 1 |
| เจ้าของกิจการพาร์ทเนอร์อีกราย (owner, ไม่มี DPA) | ประภา ทดลอง | [email protected] | องค์กร “หจก. ทดลองระบบ” ยังไม่เซ็นสัญญาประมวลผลข้อมูลส่วนบุคคล (DPA) จึงออกคีย์อ่านข้อมูลติดต่อลูกค้าไม่ได้ |
| เจ้าของร้าน (shop owner) | แดง ใจดี | [email protected] | ร้านป้าแดงของชำ — ร้านที่ยังเชื่อมต่อกับส่งดีอยู่ |
| เจ้าของร้าน (shop owner) | หมวย แซ่ลี้ | [email protected] | ร้านเจ๊หมวยมินิมาร์ท — ถูกคืนไปแล้วก่อนเรื่องนี้เริ่ม (ย้อนหลัง 60 วัน) |
| เจ้าของร้าน (shop owner, ไม่มีในภาพ) | ชัย มั่นคง | [email protected] | ร้านลุงชัยค้าส่ง — ถูกคืนสด ๆ ในบทที่ 4 (ขั้นตอน P14) |
| เจ้าของร้าน (ยังไม่ได้เข้าระบบ) | ฝน | [email protected] | ร้านน้องฝนค้าส่ง — เพิ่งถูกเปิดในบทที่ 4 (P05) ยังไม่กดรับคำเชิญ |
partner_api_contact_enabled (ให้คีย์ API อ่านเบอร์โทร/อีเมลลูกค้าได้)partner_detach_purge_enabled (ลบข้อมูลถาวรอัตโนมัติหลังคืนร้าน) — ในเล่มนี้เปิดไว้บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) · หน้า: /admin/partners
แถวองค์กร “บริษัท ส่งดี โฮลเซล จำกัด” พร้อมปุ่มจัดการ
แผงรายละเอียดขององค์กรเปิดขึ้น เห็นชื่อเต็มและรหัสองค์กร (slug — ชื่อย่อภาษาอังกฤษที่ระบบใช้อ้างถึงองค์กร) songdee
หัวข้อเงื่อนไขสัญญา (โหมดสว่าง)
หน้าจอเดียวกันในโหมดมืด
ช่อง “วันที่เซ็น DPA” มีค่า 2026-09-01 ยืนยันว่าส่งดีเซ็น DPA (Data Processing Agreement — สัญญาประมวลผลข้อมูลส่วนบุคคล) แล้ว
[email protected] ในช่อง “อีเมลของบัญชีที่มีอยู่แล้ว”sales)ฟอร์มเพิ่มสมาชิก กรอกครบก่อนกดเพิ่ม (บนมือถือ ฟอร์มอยู่ในหน้าเดียวกัน เลื่อนลงมาเจอ)
ข้อความ “เพิ่ม [email protected] เป็น … แล้ว” ปรากฏ และวิชัยขึ้นในรายชื่อสมาชิกขององค์กรทันที
บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) · หน้า: /admin/plans /flags และ /admin — สิ่งที่ตั้งในบทนี้คือขอบเขตสูงสุด (เพดาน) ของทั้งระบบ Edition ของพาร์ทเนอร์ (บทที่ 3) ทำได้แค่ตัดให้แคบลงจากเพดานนี้ ขยายเกินไม่ได้
หน้า “เพดานแผน”: หัวข้อพื้นฐานทั้งระบบ (floor) และรายการแผนด้านล่าง รวมถึง “Starter ร้านโชห่วย” (guide_starter)
โหมดมืด
บนมือถือ
หัวข้อ “พื้นฐานทั้งระบบ (floor)” แสดงรหัสสิทธิ์ทั้งหมด ตัวที่ไฮไลต์คือสิทธิ์ที่ทุกแผนต้องมีอย่างน้อย (ในภาพคือ AUDIT_LOG BACKUP และ PDP_PRIVACY) คำอธิบายใต้หัวข้อบอกอีกกติกาหนึ่ง: ร้านที่จด VAT แล้วจะมี E_TAX ติดตัวเสมอไม่ว่าจะอยู่แผนไหน ด้านล่างเป็นรายการแผน รวมถึงแผน guide_starter ของเรื่องนี้
กล่องยืนยัน “ยืนยันการเปลี่ยนแปลงนี้?” ก่อนบันทึกจริง
กล่องโต้ตอบ “ยืนยันการเปลี่ยนแปลงนี้?” ขึ้นมาก่อนเสมอ พร้อมบอกว่ามีกี่ร้านอยู่บนแผนนี้ (“… ร้านอยู่บนแผนนี้”) กด “ยืนยันบันทึก” เพื่อบันทึกจริง หรือ “ยกเลิก” เพื่อปิดโดยไม่เปลี่ยนอะไร
6 สวิตช์ที่ต้องมีคำยืนยันจากทนายก่อนเปิด หนึ่งแถวรับรองแล้ว
โหมดมืด
มีทั้งหมด 6 แถว แถว partner_api_contact_enabled ขึ้นข้อความ “รับรองแล้ว: …” พร้อมเลขอ้างอิง ส่วนอีก 5 แถว ยังขึ้น “ยังไม่มีคำรับรอง”
partner_api_contact_enabled ไว้เพื่อทำคู่มือเล่มนี้เท่านั้น (เลขอ้างอิงในภาพคือ GUIDE-LOCAL-ONLY-NOT-PROD) — บน prod จริง ทั้ง 6 แถวยังเป็น “ยังไม่มีคำรับรอง” หมด จนกว่าฝ่ายกฎหมายจะตอบเป็นลายลักษณ์อักษรแยกทีละเรื่องpartner_studio_ai_enabledกล่องกรอก “เลขอ้างอิงคำตอบทนาย” ที่ต้องกรอกก่อนเปิดสวิตช์ได้จริง
กล่อง “เปิด partner_studio_ai_enabled?” ขึ้นมาพร้อมช่อง “เลขอ้างอิงคำตอบทนาย” ต้องกรอกเลขอ้างอิงก่อนจึงจะกด “เปิดใช้” ได้
partner_studio_ai_enabled ยังปิดอยู่เหมือนเดิมเพดานวันหมดอายุของคีย์ API ตั้งไว้ที่ 90 วัน
บนมือถือ
ช่อง “จำนวนวันสูงสุด” ตั้งไว้ที่ 90 วัน — คีย์ API ที่พาร์ทเนอร์ออกเองขออายุเกินนี้ไม่ได้ ถ้าเว้นช่องนี้ว่าง แปลว่าไม่มีเพดาน
ค่าเริ่มต้น 16,000 ไบต์ ต่อลูกค้าหรือสินค้าหนึ่งแถว รวมทุกแหล่งที่มา
ช่อง “ไบต์สูงสุด” แสดงค่าเริ่มต้น 16000 ไบต์ — ยังไม่มีใครตั้งแถวนี้เอง จึงเห็นค่าที่มากับระบบ
แถวกำหนดเวลาของ partner_detach และสวิตช์ลบถาวรอัตโนมัติ
โหมดมืด
เห็นแถวที่เกี่ยวกับ partner_detach (การคืนร้าน) และสวิตช์ partner_detach_purge_enabled ที่คุมว่าจะให้ลบถาวรอัตโนมัติหรือไม่
การ์ดอัตราค่า AI ภายใต้แท็บ AI Settings
การ์ด “อัตราค่า AI” มีปุ่ม “เพิ่มแถว” และแถว “ค่าเริ่มต้น (โมเดลที่ไม่ตรงกับแถวไหนเลย)” — ในภาพยังไม่มีแถวของรุ่นโมเดลใดเลย
/ai-usage ถ้ายังไม่ตั้งแถวใด ระบบใช้ค่าประมาณจากโค้ด — ก่อนเปิดฟีเจอร์ AI ใด ๆ เช่น partner_studio_ai_enabled (A07) ควรตั้งอัตราให้ใกล้ราคาจริงก่อนบทบาท: เจ้าของกิจการพาร์ทเนอร์ (owner) และพนักงานขาย (sales) · หน้า: /partner
หัวข้อ “ร้านในสังกัด” พร้อมรายชื่อร้าน
โหมดมืด
บนมือถือ — แท็บเมนูพับเรียงเป็นแถว ไม่มีปุ่มแฮมเบอร์เกอร์
เห็นหัวข้อ “ร้านในสังกัด” พร้อมรายชื่อร้านป้าแดงของชำและร้านลุงชัยค้าส่ง
เมนูของบทบาท sales — มี “ช่วยเหลือร้าน” แต่ไม่มี “การเชื่อมต่อ”
บนมือถือ
เห็นเมนู “ช่วยเหลือร้าน” แต่ไม่มีเมนู “การเชื่อมต่อ”
แผงแก้ไข Edition “โชห่วยส่งดี” พร้อมลิงก์ไปคู่มือ Edition Studio
แผงแก้ไขเปิดขึ้น ช่องชื่อ Edition แสดง “โชห่วยส่งดี”
หน้า “เผยแพร่แล้วกระทบใครบ้าง” ก่อนยืนยันเผยแพร่จริง
โหมดมืด
หน้า “เผยแพร่แล้วกระทบใครบ้าง” แสดงจำนวนร้านที่ใช้ Edition นี้อยู่ก่อนให้ยืนยันเผยแพร่จริง
บทบาท: พนักงานขาย (sales) · เจ้าของร้าน (shop owner) · เจ้าของกิจการพาร์ทเนอร์ (owner)
ร้านน้องฝนค้าส่ง[email protected] และชื่อเจ้าของร้าน ฝนฟอร์มเปิดร้านใหม่ กรอกครบทุกช่องแล้วปุ่ม “เปิดร้าน” จึงกดได้
บนมือถือ
หน้า “เปิดร้านเรียบร้อย” ขึ้นมาพร้อมลิงก์เชิญให้เจ้าของร้านไปตั้งรหัสผ่านเอง ลิงก์นี้ใช้ได้ครั้งเดียวและหมดอายุใน 7 วัน
ฟอร์มขอสิทธิ์ช่วยเหลือ เลือกร้านและกรอกเหตุผลแล้ว
บนมือถือ
ข้อความ “ส่งคำขอแล้ว รอเจ้าของร้านอนุมัติ” ปรากฏ
คำขอจากส่งดีในหัวข้อ “รอคุณอนุมัติ” พร้อมระดับสิทธิ์ “ดูอย่างเดียว” เหตุผล และชื่อคนขอ (มานี ขายเก่ง)
บนมือถือ
คำขอย้ายจากส่วน “รอคุณอนุมัติ” ไปอยู่ใน “กำลังใช้งานอยู่” ทันที
แถบโหมดช่วยเหลือบนแอปของร้าน พร้อมเวลานับถอยหลัง (นาที:วินาที)
โหมดมืด
บนมือถือ
ระบบพาไปยังแอปของร้านจริง พร้อมแถบ “กำลังช่วยร้าน … · ดูอย่างเดียว · เหลือ …” ที่นับถอยหลังเวลาที่เหลือ (นาที:วินาที) อยู่บนหน้าจอตลอด
ปุ่ม “จบสิทธิ์” ที่แถวเซสชันที่กำลังใช้งานอยู่
ข้อความ “จบสิทธิ์แล้ว ร้านได้รับแจ้งแล้ว” ปรากฏ
กล่องโต้ตอบ “คืนร้านนี้?” ก่อนยืนยันจริง
โหมดมืด
บนมือถือ
กล่องโต้ตอบ “คืนร้านนี้?” บอกจำนวนแถวข้อมูลลูกค้า/สินค้าที่มีค่าจากฟิลด์ของ Edition และจำนวนวันที่เจ้าของร้านยังดาวน์โหลดข้อมูลได้ก่อนถูกลบถาวร กดยืนยันแล้วขึ้นข้อความ “คืนร้านแล้ว ข้อมูลจะถูกลบถาวรวันที่ …”
rows_with_values = จำนวนแถวลูกค้า/สินค้าที่มีค่าจริงในฟิลด์ที่ Edition นี้เพิ่ม · retention_days = จำนวนวันที่เจ้าของร้านยังดาวน์โหลดได้ก่อนลบถาวร · purge_enabled = สวิตช์ลบถาวรอัตโนมัติเปิดอยู่ไหมตอนนั้น{
"request": "GET /api/v1/partner/shops/{C}/detach-preview",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 200,
"body": {
"rows_with_values": {
"customer": 1,
"product": 0
},
"retention_days": 30,
"purge_enabled": true
}
}
บทบาท: เจ้าของกิจการพาร์ทเนอร์ (owner) เท่านั้น · หน้า: /partner/integrations (เมนู “การเชื่อมต่อ”) — ซีอาร์เอ็มของส่งดีเชื่อมกับ SMEDNA สองทาง: เรียกข้อมูลเองด้วยคีย์ API (หน้าจอเรียกว่า “กุญแจ API”) และรับข้อความแจ้งเหตุการณ์ที่ SMEDNA ส่งมาเองผ่าน webhook
https://example.com/… ซึ่งระบบรับได้จริง (ต้องขึ้นต้นด้วย https) แต่ตอนส่งจริงเพื่อทำคู่มือเล่มนี้ เราสลับเฉพาะปลายทางเครือข่ายให้ข้อความไปถึงเครื่องรับทดสอบภายในแทน example.com ส่วนเนื้อหาที่ส่ง (payload) ลายเซ็น และโค้ดส่งกับลองส่งซ้ำ (retry) เป็นโค้ดจริงของระบบทั้งหมด ไม่ได้จำลอง (ตัวอย่างเต็มอยู่ในบทที่ 7)ส่วนบนของหน้า: ฟอร์มออกคีย์ ฟอร์มเพิ่มปลายทาง และปลายทางที่ชี้ไป example.com (สถานะ “ใช้งานอยู่”) — รายการคีย์และประวัติการส่งอยู่ถัดลงไปในหน้าเดียวกัน
โหมดมืด
บนมือถือ
หน้าเดียวกันมีคีย์ 3 ดวง songdee-pos-sync · songdee-crm-contact · songdee-old-key (ดวงสุดท้ายหมดอายุแล้ว) ปลายทาง webhook ที่ชี้ไป example.com และหัวข้อ “ประวัติการส่งล่าสุด” ซึ่งมีแถว customer.erased สถานะ delivered
GET /partner-api/deliveries — ตัวอย่าง JSON เต็มอยู่ในบทที่ 7 ข้อ 7.6รายการคีย์ 3 ดวงและประวัติการส่งเต็มจอ (ภาพ P06 ก่อนหน้าตัดที่ขอบจอ ยังไม่เลื่อนลงมาถึงส่วนนี้)
โหมดมืด
บนมือถือ
เห็นคีย์ครบ 3 ดวง songdee-pos-sync · songdee-crm-contact · songdee-old-key และแถว customer.erased ในประวัติการส่งขึ้นสถานะ delivered
songdee-guide-ui ในช่อง “ชื่อกุญแจ”ฟอร์มออกคีย์ที่กรอกครบแล้ว ก่อนกด “ออกกุญแจ”
บนมือถือ
ข้อความ “กุญแจพร้อมใช้งาน” ปรากฏ พร้อมค่าคีย์ที่เห็นได้เพียงครั้งเดียว
too-longข้อความปฏิเสธ “วันหมดอายุเกินเพดานที่บัญชีนี้กำหนด”
ระบบปฏิเสธด้วยข้อความ “วันหมดอายุเกินเพดานที่บัญชีนี้กำหนด” ไม่มีคีย์ชื่อ “too-long” ถูกสร้างขึ้นจริง
reason_code เป็นรหัสคงที่ที่โปรแกรมอ่านได้ ไม่เปลี่ยนตามภาษา — ใช้ตัดสินใจในโค้ดแทนการอ่านข้อความ message{
"request": "POST /api/v1/partner-api/keys",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 422,
"body": {
"detail": {
"message": "expires_in_days may be at most 90",
"reason_code": "KEY_EXPIRY_TOO_LONG"
}
}
}
ช่องติ๊ก “ข้อมูลติดต่อลูกค้า” ถูกล็อกไว้ กดไม่ได้
ช่องติ๊ก “ข้อมูลติดต่อลูกค้า” ถูกล็อกไว้ กดไม่ได้ และคำอธิบายใต้ช่องเปลี่ยนเป็นเหตุผลที่ปิดแทนคำอธิบายปกติ
CONTACT_SCOPE_NOT_ALLOWED{
"request": "POST /api/v1/partner-api/keys",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 422,
"body": {
"detail": {
"message": "customers.contact:read needs this partner's signed DPA (contract.dpa_signed_at)",
"reason_code": "CONTACT_SCOPE_NOT_ALLOWED"
}
}
}
https://example.com/songdee/hook-2 ในช่อง “URL (HTTPS)”รายการเหตุการณ์ที่จะส่ง — customer.erased ติ๊กค้างไว้พร้อมป้าย “บังคับ”
ช่องติ๊กเหตุการณ์ customer.erased ถูกล็อกให้ติ๊กอยู่เสมอ พร้อมป้าย “บังคับ”
customer.erased (ลูกค้าขอลบข้อมูล) ต้องส่งไปทุกปลายทางเสมอ เพราะเป็นสัญญาณที่ต้องส่งต่อให้ระบบปลายทางลบข้อมูลตามคำขอเจ้าของข้อมูลด้วย — พาร์ทเนอร์ปิดรับ event นี้เองไม่ได้ไม่ว่าจะตั้งค่าอย่างไรบทบาท: เจ้าของร้าน (shop owner) · หน้า: /customers และ /data-boxes
partner_detach_purge_enabled ซึ่งบน prod ยังปิดอยู่ (ดูบทที่ 0 และบทที่ 2 ขั้นตอน A10) ขั้นตอน S02–S06 ทำงานเหมือนกันบน prod ส่วนข้อความในแถบแจ้งเตือนของ S07 ขึ้นกับสวิตช์นี้ บน prod จึงอาจไม่ตรงกับภาพทุกคำกล่องแก้ไขลูกค้า พร้อมช่องเพิ่มเติมจาก Edition
บนมือถือ
กล่องแก้ไขลูกค้ามีช่องเพิ่มเติมจาก Edition คือ “วันรับของประจำ” = ทุกวันจันทร์ และ “ระดับสมาชิก” = ประจำ
ทุกวันอังคารแก้ค่าก่อนกดบันทึก
เปิดกล่องแก้ไขซ้ำแล้วเห็นค่าใหม่ “ทุกวันอังคาร” ยืนยันว่าบันทึกจริงลงฐานข้อมูล ไม่ใช่แค่เปลี่ยนหน้าจอชั่วคราว
2 แถว: กล่องข้อมูลเก่าก่อนแยกกล่อง (legacy) และกล่องของพาร์ทเนอร์ส่งดี
โหมดมืด
บนมือถือ
ตาราง “กล่องข้อมูลของร้าน” มี 2 แถว พร้อมคอลัมน์ “แหล่งที่มา” และ “สถานะ” — แถวของส่งดีมีสถานะ “เชื่อมต่ออยู่”
kind:"legacy" = ข้อมูลเก่าก่อนระบบแยกกล่อง (deletable:true, ลบได้) · kind:"partner" = กล่องของพาร์ทเนอร์ส่งดี (deletable:false, ลบเองไม่ได้เพราะยังเชื่อมต่ออยู่) · rows = จำนวนแถวลูกค้า/สินค้าที่มีค่าจากกล่องนั้น{
"request": "GET /api/v1/business/data-boxes",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 200,
"body": {
"boxes": [
{
"box": "legacy:00000000-0000-4000-a000-0000000000a1",
"kind": "legacy",
"source_id": "00000000-0000-4000-a000-0000000000a1",
"source_name": null,
"status": "detached",
"orphan": true,
"clock_start": null,
"purge_at": null,
"days_left": null,
"rows": {
"customer": 1,
"product": 0
},
"deletable": true,
"confirm_code": "6ea5c564"
},
{
"box": "partner:00000000-0000-4000-a000-000000000001",
"kind": "partner",
"source_id": "00000000-0000-4000-a000-000000000001",
"source_name": "บริษัท ส่งดี โฮลเซล จำกัด",
"status": "enabled",
"orphan": false,
"clock_start": null,
"purge_at": null,
"days_left": null,
"rows": {
"customer": 2,
"product": 0
},
"deletable": false,
"confirm_code": "6faa1148"
}
],
"retention_days": 30,
"purge_enabled": true
}
}
เมนูส่งออก เลือกรูปแบบ CSV (ไฟล์ตารางข้อมูลแบบข้อความ เปิดด้วย Excel ได้)
ไฟล์ดาวน์โหลดชื่อ legacy_<uuid>-customer.csv
ref ว่างในตัวอย่างนี้ref,name,เก่า ,สมศรี มีสุข,บันทึกก่อนแยกกล่อง
ref,name,ระดับสมาชิก (member_tier),วันรับของประจำ (pickup_day) ,ครูวันดี ศรีสุข,VIP,ทุกวันเสาร์ ,สมศรี มีสุข,ประจำ,ทุกวันจันทร์
legacy_ ตามด้วยรหัสของกล่อง (UUID — รหัสสุ่มที่ไม่ซ้ำกัน) แล้วต่อท้ายด้วย -customer.csvกล่องยืนยัน — ต้องพิมพ์รหัสให้ตรงเป๊ะ
โหมดมืด
ข้อความ “ลบกล่องแล้ว” ปรากฏ แถวข้อมูลเก่าหายไปจากตารางทันที
แถบแจ้งเตือน และแถวของส่งดีที่ขึ้นสถานะ “ตัดการเชื่อมต่อแล้ว” เหลืออีก 0 วัน
บนมือถือ
แถบ “มี 1 กล่องที่ตัดการเชื่อมต่อแล้ว ดาวน์โหลดข้อมูลที่ต้องการไว้ก่อน ระบบจะลบถาวรใน 0 วัน” ขึ้นเหนือตาราง — ในภาพเป็น 0 วัน เพราะระบบเก็บข้อมูลไว้ 30 วันนับจากวันคืนร้าน แต่ร้านนี้ถูกคืนมาแล้ว 60 วัน จึงถึงคิวลบในรอบถัดไป (S08)
หน้าเดียวกับ S07 หลังลบถาวรแล้ว
ไม่มีแถวสถานะ “ตัดการเชื่อมต่อแล้ว” เหลืออยู่บนหน้านี้อีกเลย
partner_detach_purge_enabled ยังปิดอยู่ (ดูบทที่ 0) ดังนั้นการลบถาวรอัตโนมัติแบบนี้ยังไม่เกิดขึ้นจริงบน prod จนกว่าผู้ดูแลแพลตฟอร์มจะเปิดสวิตช์นี้บทบาท: นักพัฒนาฝั่งพาร์ทเนอร์ (developer) — บทนี้รวมตัวอย่างคำขอและผลลัพธ์จริง บันทึกไว้ก่อนเริ่มถ่ายภาพ (ก่อนขั้นตอน P14 ร้านลุงชัยจึงยังอยู่ในสังกัด) เอกสารอ้างอิงเต็มอยู่ที่ docs/partner/PARTNER_API_TH.md ในรีโพของทีม
<JWT ของผู้ใช้> — ชื่อฟิลด์ โครงสร้าง JSON รหัสสถานะ และ reason code เป็นของจริงตามที่เซิร์ฟเวอร์ตอบscopes = ขอบเขตที่คีย์นี้ทำได้ (read อ่านอย่างเดียว) · shops_in_scope = จำนวนร้านที่คีย์นี้เข้าถึงได้ (2 ร้าน — ร้านที่ถูกคืนแล้วไม่นับรวม เหมือนที่เห็นในบทที่ 3 P01) · rate_limit_per_minute = จำนวนครั้งสูงสุดที่เรียกได้ต่อนาที{
"request": "GET /api/v1/partner-api/me",
"auth": "Bearer pk_zgPzieERv…(ตัดทอน)",
"status": 200,
"body": {
"partner_id": "00000000-0000-4000-a000-000000000001",
"key_id": "00000000-0000-4000-b000-000000000101",
"scopes": [
"read"
],
"shops_in_scope": 2,
"rate_limit_per_minute": 120,
"expires_at": "2026-10-31T08:59:00+07:00"
}
}
business_type เป็น null ได้ (ร้านตัวอย่างทั้งสองร้านเป็นแบบนั้น) โค้ดฝั่งพาร์ทเนอร์จึงต้องรับค่า null ได้{
"request": "GET /api/v1/partner-api/businesses",
"auth": "Bearer pk_zgPzieERv…(ตัดทอน)",
"status": 200,
"body": {
"businesses": [
{
"business_id": "00000000-0000-4000-a000-00000000000a",
"name": "ร้านป้าแดงของชำ",
"business_type": null,
"edition_key": "songdee-chohuay"
},
{
"business_id": "00000000-0000-4000-a000-00000000000c",
"name": "ร้านลุงชัยค้าส่ง",
"business_type": null,
"edition_key": "songdee-chohuay"
}
]
}
}
คีย์สองดวงเรียก endpoint (URL ของ API แต่ละตัว) เดียวกัน ด้วยลูกค้าคนเดียวกัน — ต่างกันแค่ขอบเขต (scope) ของคีย์แต่ละดวง
phone เป็น null — คีย์นี้ไม่มีสิทธิ์อ่านเบอร์โทร ระบบจึงไม่ส่งค่ามาเลย ไม่ใช่ส่งค่าจริงมาแล้วให้ซ่อนเอง{
"request": "GET /api/v1/partner-api/customers/{id}?business_id={A}",
"auth": "Bearer pk_zgPzieERv…(ตัดทอน)",
"status": 200,
"body": {
"id": "00000000-0000-4000-a000-0000000000a1",
"customer_number": null,
"name": "สมศรี มีสุข",
"phone": null,
"email": null,
"segment": null,
"visit_count": 0,
"total_spent": "0.00",
"consent_at": null,
"erased_at": null,
"created_at": "2026-10-01T08:59:00+07:00"
}
}
phone มีค่า ("0811110101" เป็นเบอร์สมมติ) ส่วน email เป็น null ในตัวอย่างนี้ — ต้องครบสามเงื่อนไข: คีย์ออกพร้อมขอบเขต “ข้อมูลติดต่อลูกค้า” · องค์กรเซ็น DPA แล้ว (บทที่ 1 A02) · สวิตช์ partner_api_contact_enabled เปิดอยู่ ซึ่งระบบตรวจทุกครั้งที่เรียก (บน prod ยังปิด — ดูบทที่ 0){
"request": "GET /api/v1/partner-api/customers/{id}?business_id={A}",
"auth": "Bearer pk_k408c4fn9…(ตัดทอน)",
"status": 200,
"body": {
"id": "00000000-0000-4000-a000-0000000000a1",
"customer_number": null,
"name": "สมศรี มีสุข",
"phone": "0811110101",
"email": null,
"segment": null,
"visit_count": 0,
"total_spent": "0.00",
"consent_at": null,
"erased_at": null,
"created_at": "2026-10-01T08:59:00+07:00"
}
}
KEY_EXPIRED ตอบกลับด้วยสถานะ 401 (ไม่ได้รับอนุญาต) — คีย์ที่หมดอายุใช้เรียก endpoint ไหนก็ไม่ได้อีกเลย ต้องออกคีย์ใหม่เท่านั้น ต่ออายุคีย์เดิมไม่ได้{
"request": "GET /api/v1/partner-api/me",
"auth": "Bearer pk_hV9fmMgOW…(ตัดทอน)",
"status": 401,
"body": {
"detail": {
"message": "Partner API key has expired",
"reason_code": "KEY_EXPIRED"
}
}
}
{
"request": "POST /api/v1/partner-api/keys",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 422,
"body": {
"detail": {
"message": "expires_in_days may be at most 90",
"reason_code": "KEY_EXPIRY_TOO_LONG"
}
}
}
{
"request": "POST /api/v1/partner-api/keys",
"auth": "Bearer <JWT ของผู้ใช้>",
"status": 422,
"body": {
"detail": {
"message": "customers.contact:read needs this partner's signed DPA (contract.dpa_signed_at)",
"reason_code": "CONTACT_SCOPE_NOT_ALLOWED"
}
}
}
customer.erased + 4 customer.created ทุกแถวสถานะ delivered รหัสตอบกลับ (response_code) 200 — ข้อมูลชุดเดียวกับหัวข้อ “ประวัติการส่งล่าสุด” ในบทที่ 5 ขั้นตอน P06{
"request": "GET /api/v1/partner-api/deliveries?limit=10",
"auth": "Bearer pk_zgPzieERv…(ตัดทอน)",
"status": 200,
"body": {
"deliveries": [
{
"id": "00000000-0000-4000-b000-000000000102",
"webhook_id": "00000000-0000-4000-a000-000000000009",
"url": "https://example.com/songdee/smedna-hook",
"event_type": "customer.erased",
"event_id": "00000000-0000-4000-b000-000000000103",
"status": "delivered",
"attempt": 1,
"response_code": 200,
"error": null,
"next_attempt_at": null,
"created_at": "2026-10-01T08:59:00+07:00",
"delivered_at": "2026-10-01T08:59:00+07:00"
},
{
"id": "00000000-0000-4000-b000-000000000104",
"webhook_id": "00000000-0000-4000-a000-000000000009",
"url": "https://example.com/songdee/smedna-hook",
"event_type": "customer.created",
"event_id": "00000000-0000-4000-b000-000000000105",
"status": "delivered",
"attempt": 1,
"response_code": 200,
"error": null,
"next_attempt_at": null,
"created_at": "2026-10-01T08:59:00+07:00",
"delivered_at": "2026-10-01T08:59:00+07:00"
},
{
"id": "00000000-0000-4000-b000-000000000106",
"webhook_id": "00000000-0000-4000-a000-000000000009",
"url": "https://example.com/songdee/smedna-hook",
"event_type": "customer.created",
"event_id": "00000000-0000-4000-b000-000000000107",
"status": "delivered",
"attempt": 1,
"response_code": 200,
"error": null,
"next_attempt_at": null,
"created_at": "2026-10-01T08:59:00+07:00",
"delivered_at": "2026-10-01T08:59:00+07:00"
},
{
"id": "00000000-0000-4000-b000-000000000108",
"webhook_id": "00000000-0000-4000-a000-000000000009",
"url": "https://example.com/songdee/smedna-hook",
"event_type": "customer.created",
"event_id": "00000000-0000-4000-b000-000000000109",
"status": "delivered",
"attempt": 1,
"response_code": 200,
"error": null,
"next_attempt_at": null,
"created_at": "2026-10-01T08:59:00+07:00",
"delivered_at": "2026-10-01T08:59:00+07:00"
},
{
"id": "00000000-0000-4000-b000-000000000110",
"webhook_id": "00000000-0000-4000-a000-000000000009",
"url": "https://example.com/songdee/smedna-hook",
"event_type": "customer.created",
"event_id": "00000000-0000-4000-b000-000000000111",
"status": "delivered",
"attempt": 1,
"response_code": 200,
"error": null,
"next_attempt_at": null,
"created_at": "2026-10-01T08:59:00+07:00",
"delivered_at": "2026-10-01T08:59:00+07:00"
}
]
}
}
ทุกครั้งที่มีเหตุการณ์ ระบบส่ง HTTP POST ไปยัง URL ปลายทางที่ตั้งไว้ พร้อมชุด header และเนื้อหา (body) ตามนี้:
X-Partner-Timestamp เป็น Unix seconds (จำนวนวินาทีนับจาก 1 ม.ค. 1970) ไม่ใช่มิลลิวินาที · links.self ชี้กลับไปเรียกรายละเอียดลูกค้าคนนั้นผ่าน API ได้ทันที — payload จึงเป็น “ตัวชี้” ให้ไปเรียกข้อมูลสดเอง ไม่ใช่สำเนาข้อมูลเต็มPOST /webhook HTTP/1.1
X-Partner-Event: customer.created
X-Partner-Delivery: 00000000-0000-4000-b000-000000000110
X-Partner-Timestamp: 1790819940
X-Partner-Signature: cd2ec7f9…
Content-Type: application/json
{
"data": {
"segment": null,
"created_at": "2026-10-01T08:59:00+07:00",
"customer_id": "00000000-0000-4000-a000-0000000000a1"
},
"links": {
"self": "/api/v1/partner-api/customers/00000000-0000-4000-a000-0000000000a1?business_id=00000000-0000-4000-a000-00000000000a"
},
"event_id": "00000000-0000-4000-b000-000000000111",
"event_type": "customer.created",
"business_id": "00000000-0000-4000-a000-00000000000a",
"occurred_at": "2026-10-01T08:59:00+07:00"
}
links.self ชี้ไปหาลูกค้าคนนั้นอีก เพราะลูกค้าถูกลบไปแล้ว — มีแค่ links.businesses ให้ไปเรียกรายชื่อร้านแทนPOST /webhook HTTP/1.1
X-Partner-Event: customer.erased
X-Partner-Delivery: 00000000-0000-4000-b000-000000000102
X-Partner-Timestamp: 1790819940
X-Partner-Signature: cdc16380…
Content-Type: application/json
{
"data": {
"erased_at": "2026-10-01T08:59:00+07:00",
"customer_id": "00000000-0000-4000-a000-0000000000a2"
},
"links": {
"businesses": "/api/v1/partner-api/businesses"
},
"event_id": "00000000-0000-4000-b000-000000000103",
"event_type": "customer.erased",
"business_id": "00000000-0000-4000-a000-00000000000a",
"occurred_at": "2026-10-01T08:59:00+07:00"
}
ลายเซ็นใน X-Partner-Signature คือ HMAC-SHA256 (รหัสตรวจสอบที่คำนวณจาก secret ของปลายทางกับข้อความ) ของ timestamp ตามด้วยจุด แล้วต่อด้วย body ดิบ เขียนเป็นเลขฐานสิบหก:
signed = f"{timestamp}.".encode() + raw_body_bytes
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
# เทียบกับ X-Partner-Signature ด้วย hmac.compare_digest (กันการเทียบแบบรั่วเวลา)
X-Partner-Timestamp เก่าเกินราว 5 นาที เพื่อกันการส่งซ้ำ (replay)algorithm: hex(hmac_sha256(secret, f"{timestamp}." + raw_body))
event: customer.created
timestamp: 1790819940
signature (received): cd2ec7f9…
signature (recomputed): cd2ec7f9…
result: OK
event: customer.erased
timestamp: 1790819940
signature (received): cdc16380…
signature (recomputed): cdc16380…
result: OK
รวมภาพหน้าจอบนมือถือ (390 พิกเซล) จากบททั้งหมดไว้ในที่เดียว ให้เห็นภาพรวมว่าแต่ละบทบาทใช้งานบนมือถือหน้าตาเป็นอย่างไร — เนื้อหาละเอียดของแต่ละภาพอยู่ในบทของมันแล้ว
A01 · /admin/partners
P01 · /partner
P02 · /partner (sales)
P05 · /partner/shops/new
P06 · /partner/integrations
P07 · ออกคีย์
P11 · /partner/support
P12 · โหมดช่วยเหลือ
P14 · คืนร้าน
S02 · /customers
S04 · /data-boxes
S07 · ร้านที่ถูกคืน
บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) กดสร้างและลบ · หน้า: /admin การ์ด “คู่มือพาร์ทเนอร์ (ข้อมูลตัวอย่าง)” — จากนั้นใช้บัญชีตัวอย่าง 4 บัญชีดูแต่ละมุมมองบน prod ได้
ก่อนกดสร้าง — การ์ดว่าง พร้อมช่องรหัสผ่านร่วม (ปิดบังค่าไว้)
เห็นการ์ดเปล่า ข้อความ “ยังไม่ได้สร้างข้อมูลตัวอย่าง” และปุ่ม “สร้างข้อมูลตัวอย่างคู่มือพาร์ทเนอร์”
หลังสร้างสำเร็จ — การ์ดแสดงองค์กร บัญชี และร้านค้า (ช่องรหัสผ่านปิดบังไว้ด้วยแถบทึบ)
บนมือถือ
ข้อความ “สร้างใหม่ 17 รายการ · มีอยู่แล้ว 0 รายการ” ปรากฏ การ์ดแสดงพาร์ทเนอร์ตัวอย่าง ตาราง “บัญชี” 4 บัญชี ตาราง “ร้านค้า” 2 ร้าน (“เชื่อมอยู่” 1 · “ถูกคืนแล้ว” 1) คีย์ API 2 ดวง และ webhook 1 จุดที่ขึ้นว่า “หยุดไว้ — ไม่ยิงออกจริง” — กดซ้ำได้ ของที่มีอยู่แล้วนับเป็น “มีอยู่แล้ว” ไม่สร้างซ้ำ
songdee-demo) ที่เซ็น DPA แล้ว · Edition “โชห่วยส่งดี” ที่เผยแพร่แล้ว · 4 บัญชี: สมชาย (เจ้าขององค์กรพาร์ทเนอร์) มานี (ฝ่ายขายพาร์ทเนอร์) แดงและหมวย (เจ้าของร้าน) · 2 ร้าน: ร้านป้าแดงของชำ (เชื่อมอยู่) และร้านเจ๊หมวยมินิมาร์ท (ถูกคืนย้อนหลัง 10 วัน) · ลูกค้าตัวอย่าง 4 คนที่มีแค่ชื่อ ไม่มีเบอร์โทรหรืออีเมล · คีย์ API แบบอ่านอย่างเดียว 2 ดวง (ดวงหนึ่งหมดอายุแล้วโดยตั้งใจ) · webhook 1 จุดที่ชี้ไป hooks.songdee.example (โดเมนสำหรับตัวอย่าง ไม่มีเครื่องจริงรับ) และหยุดไว้ตั้งแต่สร้างSLUG_TAKEN เมื่อมีองค์กรอื่นที่ไม่ได้สร้างจากปุ่มนี้ใช้ slug songdee-demo อยู่แล้ว ปุ่มจะไม่ยึดองค์กรนั้นมาใช้ ให้ตรวจองค์กรนั้นที่ /admin/partners ก่อน[email protected] พร้อมรหัสผ่านนั้นเข้าสู่ระบบได้ในบทบาทของบัญชีนั้น เหมือนบัญชีจริงทุกอย่าง แต่ข้อมูลทั้งหมดเป็นข้อมูลตัวอย่าง
somchai.owner@…) แล้วเปิด /partnerคอนโซล /partner ของสมชาย — เห็นร้านป้าแดงของชำ (ร้านเจ๊หมวยถูกคืนแล้วจึงไม่อยู่ในรายการ)
บนมือถือ
หน้าตาเหมือนบทที่ 4 (P01) ทุกอย่าง เห็นร้านป้าแดงของชำ 1 ร้าน
/partner/integrations ของสมชาย — คีย์ API 2 ดวง ไม่มีคีย์ขอบเขตข้อมูลติดต่อลูกค้า
เหมือนบทที่ 5 (P06) แต่มีคีย์แค่ 2 ดวง (ไม่มี songdee-crm-contact) และ webhook หยุดไว้ จึงไม่มีประวัติการส่ง
pa.daeng@…) แล้วเปิด /data-boxes/data-boxes ของร้านป้าแดง — เห็นกล่องข้อมูลจาก Edition โชห่วยส่งดี
เหมือนบทที่ 6 (S04) ทุกอย่าง เห็นค่า “วันรับของประจำ” และ “ระดับสมาชิก” ของลูกค้าตัวอย่าง
jae.muay@… ร้านที่ถูกคืนแล้ว) แล้วเปิด /data-boxes/data-boxes ของร้านเจ๊หมวย — แถบแจ้งเตือนว่ากล่องนี้ตัดการเชื่อมต่อแล้ว
เหมือนบทที่ 6 (S07) แต่ตัวนับวันต่างกัน (ถูกคืนย้อนหลัง 10 วัน ไม่ใช่ 60 วันแบบในภาพบทที่ 6) ข้อความแถบแจ้งเตือนจึงอาจต่างจากภาพนั้น
songdee-demo ในช่อง “พิมพ์ songdee-demo เพื่อยืนยัน”กล่องยืนยัน พิมพ์ songdee-demo ครบแล้ว (ช่องรหัสผ่านของการ์ดด้านหลังยังปิดบังไว้)
หลังลบสำเร็จ — การ์ดกลับไปว่างเหมือนขั้นตอน 9.1
ข้อความ “ลบไปแล้ว 14 รายการ” ปรากฏ ระบบลบเฉพาะสิ่งที่ปุ่มสร้างสร้างไว้ (องค์กร บัญชี ร้าน คีย์ webhook และลูกค้าตัวอย่าง) ไม่แตะข้อมูลอื่นในระบบ — ตรวจแล้วว่าไม่มีผู้ใช้ องค์กร หรือ registry ของเดโมนี้เหลือค้างในฐานข้อมูลเลย
อ่านรหัสเหล่านี้ในโค้ดของคุณแทนข้อความ message เพราะ message เปลี่ยนได้ตามเวลา ส่วนรหัสคงที่เสมอ
| reason_code | สถานะ HTTP | ความหมาย |
|---|---|---|
invalid_partner_key | 401 | คีย์ไม่ถูกต้อง ไม่มีอยู่จริง หรือถูกเพิกถอนแล้ว — ตอบเหมือนกันทุกกรณีโดยเจตนา เพื่อไม่ให้รู้ว่าคีย์ที่เดามาถูกเพิกถอนไปแล้วหรือไม่เคยมีอยู่ |
KEY_EXPIRED | 401 | คีย์หมดอายุแล้ว (ดูตัวอย่างเต็มในบทที่ 7.4) ต้องออกคีย์ใหม่เท่านั้น |
PARTNER_LANE_DISABLED | 404 | เลนพาร์ทเนอร์ทั้งระบบปิดอยู่ — ตอบ 404 แทน 401 โดยเจตนา เพื่อไม่ยืนยันว่า Partner API มีอยู่จริง |
business_not_found | 404 | คีย์นี้เข้าถึงร้านนั้นไม่ได้ — ตอบเหมือนร้านไม่มีอยู่จริง แม้ร้านนั้นจะมีอยู่แต่เป็นของพาร์ทเนอร์รายอื่น |
path_not_allowlisted | 403 | เส้นทาง (endpoint) นี้ไม่เปิดให้คีย์ API เรียก — เปิดเฉพาะเส้นทางที่อยู่ในเอกสาร Partner API เท่านั้น |
write_scope_required | 403 | คีย์นี้อ่านได้อย่างเดียว (read) แต่พยายามเรียกคำสั่งที่ต้องเขียนข้อมูล ต้องออกคีย์ใหม่พร้อมขอบเขต write |
business_id_required | 422 | คีย์นี้เข้าถึงได้หลายร้าน ต้องระบุ business_id มาด้วยเสมอ — ดูรายชื่อร้านทั้งหมดที่ GET /partner-api/businesses |
rate_limited | 429 | เรียกเกินโควตาต่อนาทีของคีย์นี้แล้ว — รอตามเวลาใน header Retry-After แล้วค่อยเรียกใหม่ |
CONTACT_SCOPE_NOT_ALLOWED | 422 | องค์กรนี้ยังไม่เซ็น DPA จึงออกคีย์ขอบเขตข้อมูลติดต่อลูกค้าไม่ได้ (ดูบทที่ 5 P09) |
KEY_EXPIRY_TOO_LONG | 422 | ขอวันหมดอายุเกินเพดานที่ผู้ดูแลแพลตฟอร์มตั้งไว้ (ดูบทที่ 5 P08 และบทที่ 2 A08) |
key_not_found | 404 | คีย์นี้ไม่ใช่ของพาร์ทเนอร์ที่ล็อกอินอยู่ หรือไม่มีอยู่จริง — ตอบเหมือนกันทั้งสองกรณี |
CONSENT_NOTICE_REQUIRED | 422 | ต้องแสดงประกาศขอความยินยอมให้ลูกค้าเห็นก่อน ระบบจึงจะยอมสร้างข้อมูลลูกค้าใหม่ผ่าน API |
webhook_not_found | 404 | ปลายทาง webhook นี้ไม่ใช่ของพาร์ทเนอร์ที่ล็อกอินอยู่ หรือไม่มีอยู่จริง |
| สวิตช์ / เพดาน | บน prod | ในคู่มือเล่มนี้ (สภาพแวดล้อมทดสอบภายใน) |
|---|---|---|
6 สวิตช์ “ต้องมีคำยืนยันจากทนาย” (รวม partner_api_contact_enabled) | ปิดทั้ง 6 | เปิดเฉพาะ partner_api_contact_enabled เพื่อทำตัวอย่างข้อมูลติดต่อลูกค้า (บทที่ 5 และ 7) |
partner_detach_purge_enabled | ปิด | เปิด เพื่อถ่ายภาพบทที่ 6 (S08) |
| เพดานวันหมดอายุคีย์ API | ตามที่ผู้ดูแลแพลตฟอร์มตั้งที่ /flags (เว้นว่าง = ไม่มีเพดาน) | 90 วัน |
| เพดานพื้นที่เก็บฟิลด์เสริม | 16,000 ไบต์ ถ้ายังไม่มีใครตั้ง (ค่าเริ่มต้น) | 16,000 ไบต์ (ค่าเริ่มต้น) |
เป็นพฤติกรรมจริงของระบบตอนนี้ (ดูหมายเหตุบทที่ 3 ขั้นตอน P01) — ทีมงานบันทึกไว้เป็นข้อเสนอปรับปรุงผลิตภัณฑ์ว่าพาร์ทเนอร์ควรเห็นประวัติร้านที่เคยอยู่ในสังกัดด้วยหรือไม่ ยังไม่มีการแก้ไข ณ วันที่จัดทำคู่มือ
เรียกดูซ้ำไม่ได้แล้ว ต้องเพิกถอน (revoke) คีย์ดวงนั้นแล้วออกคีย์ใหม่แทน ระบบเก็บแค่แฮชไว้ตรวจสอบ ไม่เก็บค่าจริงไว้ที่ไหนเลยหลังสร้างเสร็จ (เหมือนที่ปุ่มเดโมในบทที่ 9 ทิ้งค่าลับทันทีเช่นกัน)
ปิดไม่ได้ (ดูบทที่ 5 P10) เพราะเป็นสัญญาณที่ต้องส่งต่อให้ระบบปลายทางลบข้อมูลของลูกค้าตามคำขอเจ้าของข้อมูลด้วย ทุกปลายทางที่ตั้งไว้จะได้รับเหตุการณ์นี้เสมอ
ต้องครบสามเงื่อนไข: องค์กรเซ็น DPA แล้ว (บทที่ 1 A02) · คีย์ที่ใช้ออกพร้อมขอบเขต “ข้อมูลติดต่อลูกค้า” (บทที่ 5 P07) · สวิตช์ partner_api_contact_enabled เปิดอยู่ (บทที่ 2 A06 — บน prod ยังปิด) ขาดข้อใดข้อหนึ่งก็อ่านไม่ได้ ดูกรณีไม่มี DPA ที่ P09
ใช้เป็นตัวอย่างโครงสร้างข้อมูลได้เลย แต่ค่าทุกตัว (UUID เวลา คีย์ ลายเซ็น) ถูกปิดบังแล้วตามที่บอกไว้ในบทที่ 7 ไม่ใช่ค่าที่เรียกได้จริงบนระบบจริง
ปุ่มสร้างและปุ่มลบทำงานกับข้อมูลตัวอย่างของตัวเองเท่านั้น ปุ่มลบลบตามรายการที่ปุ่มสร้างบันทึกไว้ (องค์กร บัญชี ร้าน คีย์ webhook และลูกค้าตัวอย่าง) ไม่แตะสวิตช์ แผน ราคา หรือร้านจริงรายอื่นเลย และกดลบซ้ำได้อย่างปลอดภัย (ดูบทที่ 9 ข้อ 9.4)