คู่มือพาร์ทเนอร์ SMEDNA — เปิดตัวแทนจำหน่าย ขายผ่านร้านโชห่วย เชื่อมระบบด้วย API

พาร์ทเนอร์ (partner) คือตัวแทนจำหน่ายที่ขาย SMEDNA ต่อให้ร้านค้าในเครือของตัวเอง ภายใต้ผลิตภัณฑ์ของตัวเองที่เรียกว่า Edition คู่มือเล่มนี้เดินตามเรื่องสมมติเรื่องเดียวตั้งแต่ต้นจนจบ: บริษัท ส่งดี โฮลเซล จำกัด ผู้ค้าส่งใน จ. นครราชสีมา ที่ขาย SMEDNA ให้ร้านโชห่วยในเครือประมาณ 40 ร้าน ทุกภาพถ่ายจากระบบจริงบนสภาพแวดล้อมทดสอบภายใน ไม่ใช่ภาพจำลอง (ดูกรอบ “ภาพในเล่มนี้กับ prod” ในบทที่ 0)

4 บทบาท: ผู้ดูแลแพลตฟอร์ม · พาร์ทเนอร์ · เจ้าของร้าน · นักพัฒนา ภาพจากระบบจริง 42 จุด ตัวอย่างข้อมูลจริงที่ถูกปิดบังค่าอ่อนไหวแล้ว มีชุดข้อมูลตัวอย่างกดสร้าง/ลบได้ (บทที่ 9)

ภาพประกอบทุกภาพถ่ายอัตโนมัติด้วย qa/partner-guide/run-e2e.mjs ซึ่งตรวจข้อความบนหน้าจอก่อนถ่ายทุกภาพ ตามเรื่องใน docs/partner/guide/DESIGN.md · จัดทำ 24 กันยายน 2026

เริ่มที่นี่: เส้นทางพาร์ทเนอร์และสิ่งที่ปรับแต่งได้

ภาพรวมทั้งเล่มในบทเดียว: ใครทำอะไร แก้แล้วร้านเจออะไร และปรับแต่งได้แค่ไหน — ขั้นตอนละเอียดพร้อมภาพหน้าจออยู่ในบทที่ลิงก์ไว้ในแต่ละหัวข้อ

คุณนำ SMEDNA ไปขายต่อให้ร้านในเครือ ภายใต้ชุดเมนูและช่องกรอกของคุณเองที่เรียกว่า Edition ทุกร้านใช้แอปเดียวกัน คุณจึงปรับได้เองโดยไม่ต้องเขียนโค้ด และเมื่อ SMEDNA อัปเดตระบบ ทุกร้านได้พร้อมกัน

แก้ Edition แล้ว ร้านยังไม่เห็น จนกว่าคุณกดเผยแพร่
ย้อนกลับได้เสมอ ทุกฉบับเก็บไว้ในประวัติ
ร้านใช้เมนูได้ ไม่เกินแผนที่ร้านซื้อ

ภาพรวม: ใครทำอะไร

SMEDNA เปิดบัญชีให้ก่อน จากนั้นคุณจัด Edition เซลส์เปิดร้าน แล้วร้านใช้งานจริง หลังเปิดร้านแล้วมีงานต่อเนื่องอีกสามอย่าง

  • ทีม SMEDNA
  • พาร์ทเนอร์
  • ร้านค้า
  1. ทีม SMEDNA SMEDNA เปิดบัญชีพาร์ทเนอร์

    ทำสัญญาและลงวันเซ็น DPA · ตั้งราคาส่งของแต่ละแผน · เพิ่มคนในทีมของคุณ ทั้งเจ้าของและเซลส์

    ดูละเอียด: บทที่ 1
  2. พาร์ทเนอร์ คุณจัด Edition

    เลือกเมนูที่ร้านจะเห็น · เพิ่มช่องกรอกของคุณเอง เช่น “วันรับของประจำ” · กด “ดูผลกระทบแล้วเผยแพร่”

    ดูละเอียด: บทที่ 3
  3. พาร์ทเนอร์ เซลส์เปิดร้านให้ลูกค้า

    ที่เมนู “เปิดร้านใหม่” กรอกชื่อร้าน อีเมลเจ้าของร้าน เลือก Edition แผน และประเภทร้าน แล้วส่งลิงก์เชิญให้เจ้าของร้าน หรือส่ง “ลิงก์ทดลองใช้” ในหน้าภาพรวมให้ร้านสมัครเอง

    ดูละเอียด: บทที่ 4
  4. ร้านค้า ร้านใช้งานจริง

    ร้านเห็นเมนูและช่องกรอกตาม Edition ของคุณ · ข้อมูลของร้านเป็นของร้าน

    ดูละเอียด: บทที่ 6

เวลาคุณแก้ Edition

Edition มีสองฉบับเสมอ คือฉบับที่คุณกำลังแก้ กับฉบับที่ร้านใช้อยู่ ร้านจะได้ของใหม่ก็ต่อเมื่อคุณกดเผยแพร่

  1. แก้ใน Studio

    บันทึกได้เรื่อย ๆ ระหว่างนี้ร้านยังเห็นฉบับเดิม

  2. ดูผลกระทบ

    กด “ดูผลกระทบแล้วเผยแพร่” ระบบบอกว่ามีกี่ร้านที่จะเปลี่ยน

    ดูละเอียด: บทที่ 3 (ขั้นตอน P04)
  3. เผยแพร่

    ส่งให้ทุกร้าน หรือให้เฉพาะร้านทดลองก่อนก็ได้

  4. ร้านเห็นของใหม่

    ร้านไม่ต้องทำอะไร อาจใช้เวลาราวครึ่งนาที

แก้แล้วร้านเจออะไร

การเพิ่มหรือลบช่องกรอก และการเปิดปิดเมนู มีผลหลังคุณกดเผยแพร่

สิ่งที่คุณทำร้านเห็นอะไรข้อมูลเดิมของร้าน
เพิ่มช่องกรอกช่องใหม่ในฟอร์มลูกค้าลูกค้าเก่ายังว่างในช่องนี้ รอร้านกรอกดูละเอียด: บทที่ 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)

ข้อจำกัดตัดได้อย่างเดียว เพิ่มเกินแผนและประเภทร้านไม่ได้ · เมนูพื้นฐานบางตัวซ่อนไม่ได้ · การซ่อนเมนูแค่ไม่แสดง ไม่ใช่การปิดสิทธิ์

เพิ่มช่องในฟอร์มเดิม

ทำเองได้วันนี้

ทำอย่างไร

  1. ที่เมนู “Edition” เปิด Edition ของคุณ แล้วเลือกแท็บ “ช่องกรอก”
  2. กด “เพิ่มฟิลด์ในหน้าลูกค้า” หรือ “เพิ่มฟิลด์ในหน้าสินค้า”
  3. ตั้ง “ชื่อที่ร้านเห็น” เช่น วันรับของประจำ · “รหัสฟิลด์” เป็นภาษาอังกฤษตัวเล็ก เช่น pickup_day · เลือก “ชนิด” ได้แก่ ข้อความ ตัวเลข วันที่ ตัวเลือก หรือติ๊กถูก · ติ๊ก “ต้องกรอก” ถ้าบังคับ
  4. กด “ดูผลกระทบแล้วเผยแพร่” แล้วร้านจะเห็นช่องใหม่ในหน้าเพิ่มและแก้ลูกค้า
หน้า Edition ที่ขายในคอนโซลพาร์ทเนอร์ มีรายการความสามารถที่ร้านจะเห็น ช่อง ร้านจะเห็นแบบนี้ และประวัติการแก้ไข
ฝั่งพาร์ทเนอร์: หน้า “Edition ที่ขาย” (ตัวอย่าง “โชห่วยส่งดี”) ติ๊ก “ความสามารถที่ร้านจะเห็น” ทางซ้าย ทางขวามี “ร้านจะเห็นแบบนี้” และ “ประวัติการแก้ไข” · ดูขั้นตอนเต็มในบทที่ 3
กล่องแก้ไขลูกค้าที่ร้านเห็น มีช่องวันรับของประจำและระดับสมาชิกที่ Edition เพิ่มให้
ฝั่งร้าน: กล่อง “แก้ไขลูกค้า” มีช่อง “วันรับของประจำ” และ “ระดับสมาชิก” เพิ่มมาจาก Edition · ดูเพิ่มในบทที่ 6
ข้อจำกัดเพิ่มได้ที่หน้าลูกค้าและหน้าสินค้าเท่านั้น · ไม่เกิน 20 ช่องต่อหน้า · ระบบไม่ให้สร้างช่องสำหรับข้อมูลอ่อนไหว เช่น เลขบัตรประชาชน ข้อมูลสุขภาพ ลายนิ้วมือหรือใบหน้า

เริ่มและเผยแพร่อย่างปลอดภัย

ทำเองได้วันนี้

เริ่มจากต้นแบบ 132 แบบ หรือคัดลอก Edition เดิมในหน้าเลือกต้นแบบ · เก็บเป็นฉบับร่าง ดูตัวอย่าง ตรวจผลกระทบก่อนเผยแพร่ · ปล่อย beta ก่อน แล้วค่อยเป็น stable · ย้อนกลับไปฉบับก่อนได้

ข้อจำกัดเผยแพร่แล้วร้านเห็นทันที ควรตรวจผลกระทบทุกครั้ง

ฟีเจอร์ใหม่ที่ SMEDNA ยังไม่มี

มีสามทาง เลือกตามลักษณะงาน

ใช้ได้วันนี้

ระบบของคุณ + Partner API

เหมาะกับงานหลังบ้านของเครือ เช่น สรุปยอดทั้งเครือหรือ CRM · อ่านข้อมูลร้าน ลูกค้า และยอดขาย · สร้างลูกค้าใหม่ได้ถ้าคีย์มีสิทธิ์เขียน · รับ webhook ต้องมีโปรแกรมเมอร์ฝั่งคุณ

ดูละเอียด: บทที่ 5 และ บทที่ 7
ยังไม่เปิด

Extension

สร้างข้อมูลและฟอร์มชนิดใหม่ให้โผล่ในแอปร้าน เช่น ตารางรอบส่งของ · จะให้ AI ช่วยร่างแล้วคุณอนุมัติก่อนใช้ · ร้านกดติดตั้งและยินยอมเอง

ขอ SMEDNA

ให้ SMEDNA ทำในแกน

งานที่แตะเงิน สต็อก ใบเสร็จ ภาษี หรือหน้าขาย ต้องทำในแกนเพื่อความถูกต้อง · เสร็จแล้วทุกพาร์ทเนอร์เปิดใช้ผ่าน Edition ได้

ตั้งค่าที่มีผลทันที

สามเรื่องนี้ไม่ต้องเผยแพร่ บันทึกแล้วมีผลเลย

คีย์ API เมนู “การเชื่อมต่อ”
  • สร้างคีย์ได้เอง รหัสคีย์แสดงครั้งเดียวตอนสร้าง ให้เก็บไว้ทันที
  • ทุกคีย์มีวันหมดอายุ ตั้งได้ไม่เกินที่ SMEDNA กำหนด พอหมดอายุก็ใช้ไม่ได้ ให้สร้างคีย์ใหม่
  • การอ่านเบอร์โทรและอีเมลลูกค้า ต้องมีสัญญา DPA และได้รับอนุมัติก่อน
ดูละเอียด: บทที่ 5
webhook เมนู “การเชื่อมต่อ”
  • URL ต้องขึ้นต้นด้วย https://
  • เลือกเหตุการณ์ที่ต้องการรับได้ แต่ customer.erased (ลูกค้าขอลบข้อมูล) ปิดไม่ได้ ระบบของคุณจะได้ลบตาม
  • ทุกข้อความมีลายเซ็นใน X-Partner-Signature ให้ตรวจว่ามาจาก SMEDNA จริง
ดูละเอียด: บทที่ 5 และ บทที่ 7
ราคา เมนู “ราคา”
  • SMEDNA ตั้งราคาส่งของแต่ละแผนให้คุณ
  • คุณตั้งราคาขายปลีกเอง
  • แผนที่ยังไม่มีราคาส่ง ยังเปิดร้านบนแผนนั้นไม่ได้

ใครในทีมทำอะไรได้

เจ้าของพาร์ทเนอร์

ใช้ได้ทุกเมนูในคอนโซล รวมการจัด Edition และเมนู “การเชื่อมต่อ” (คีย์ API และ webhook)

เซลส์

เปิดร้านใหม่ และขอช่วยดูแลร้าน · ไม่เห็นเมนู “การเชื่อมต่อ”

ต้องการเพิ่มหรือถอดคนในทีม ติดต่อทีม SMEDNA · สิทธิ์ของเซลส์เทียบกับเจ้าของ ดูบทที่ 3 (ขั้นตอน P02)

0 · เรื่องของเรา — ส่งดี โฮลเซล กับร้านโชห่วยในเครือ

อ่านบทนี้ก่อนบทอื่นทุกครั้ง — ชื่อคนและร้านในภาพทุกภาพของเล่มนี้มาจากตารางเดียวกันนี้

ใครเป็นใคร

ส่งดี โฮลเซล เปิดบัญชีพาร์ทเนอร์กับ 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) ยังไม่กดรับคำเชิญ
ภาพในเล่มนี้กับ prod (24 กันยายน 2026) prod (production) คือระบบจริงที่ลูกค้าใช้งาน ส่วนทุกภาพและตัวอย่างข้อมูลในเล่มนี้ทำบนสภาพแวดล้อมทดสอบภายในที่รันโค้ดรุ่นเดียวกัน ด้วยข้อมูลสมมติตามตารางข้างบน ความต่างสำคัญคือสวิตช์ (feature flag — ตัวเปิด/ปิดฟีเจอร์ที่ผู้ดูแลแพลตฟอร์มคุมที่หน้า /flags) 7 ตัวนี้ ซึ่งบน prod ยังปิดอยู่ทั้งหมด (ดูบทที่ 2 และบทที่ 10):
  • สวิตช์กลุ่ม “⚖️ ต้องมีคำยืนยันจากทนาย” ทั้ง 6 ตัว — ปิดจนกว่าฝ่ายกฎหมายจะตอบเป็นลายลักษณ์อักษร ในเล่มนี้เปิดตัวเดียวคือ partner_api_contact_enabled (ให้คีย์ API อ่านเบอร์โทร/อีเมลลูกค้าได้)
  • partner_detach_purge_enabled (ลบข้อมูลถาวรอัตโนมัติหลังคืนร้าน) — ในเล่มนี้เปิดไว้
สองเรื่องนี้จึงยังใช้บน prod ไม่ได้: การอ่านเบอร์โทร/อีเมลลูกค้าผ่าน API (บทที่ 5 ขั้นตอน P07 และบทที่ 7 ข้อ 7.3) และการลบถาวรอัตโนมัติ (บทที่ 6 ขั้นตอน S08)

1 · ผู้ดูแลแพลตฟอร์ม: เปิดบัญชีพาร์ทเนอร์

บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) · หน้า: /admin/partners

A01

เปิดบัญชีพาร์ทเนอร์ให้ส่งดี โฮลเซล

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีผู้ดูแลแพลตฟอร์ม ไปที่หน้า /admin/partners
  2. กดปุ่ม “จัดการ” ที่แถวขององค์กร “บริษัท ส่งดี โฮลเซล จำกัด”
หน้า /admin/partners แสดงองค์กรบริษัท ส่งดี โฮลเซล จำกัด

แถวองค์กร “บริษัท ส่งดี โฮลเซล จำกัด” พร้อมปุ่มจัดการ

แผงรายละเอียดขององค์กรเปิดขึ้น เห็นชื่อเต็มและรหัสองค์กร (slug — ชื่อย่อภาษาอังกฤษที่ระบบใช้อ้างถึงองค์กร) songdee

A02

ยืนยันว่าเซ็นสัญญาประมวลผลข้อมูลส่วนบุคคล (DPA) แล้ว

ทำอย่างไร

  1. ในแผงองค์กรเดียวกัน เลื่อนไปหัวข้อ “เงื่อนไขสัญญา”
หัวข้อเงื่อนไขสัญญา แสดงวันที่เซ็น DPA

หัวข้อเงื่อนไขสัญญา (โหมดสว่าง)

หัวข้อเงื่อนไขสัญญาในโหมดมืด

หน้าจอเดียวกันในโหมดมืด

ช่อง “วันที่เซ็น DPA” มีค่า 2026-09-01 ยืนยันว่าส่งดีเซ็น DPA (Data Processing Agreement — สัญญาประมวลผลข้อมูลส่วนบุคคล) แล้ว

ทำไมต้องมีขั้นตอนนี้องค์กรที่ยังไม่กรอกวันเซ็น DPA จะออกคีย์ API ขอบเขตข้อมูลติดต่อลูกค้าไม่ได้เลย ไม่ว่าจะเปิดสวิตช์กฎหมายไว้แล้วหรือไม่ก็ตาม (เห็นตัวอย่างจริงในบทที่ 5 ขั้นตอน P09 — องค์กรของประภาที่ยังไม่เซ็น)
A03

เพิ่มพนักงานขายเป็นสมาชิกขององค์กร

ทำอย่างไร

  1. กรอกอีเมล [email protected] ในช่อง “อีเมลของบัญชีที่มีอยู่แล้ว”
  2. ในช่อง “บทบาท” เลือกฝ่ายขาย (sales)
  3. กดปุ่ม “เพิ่ม”
ฟอร์มเพิ่มสมาชิกกรอกอีเมลวิชัยและบทบาท sales

ฟอร์มเพิ่มสมาชิก กรอกครบก่อนกดเพิ่ม (บนมือถือ ฟอร์มอยู่ในหน้าเดียวกัน เลื่อนลงมาเจอ)

ข้อความ “เพิ่ม [email protected] เป็น … แล้ว” ปรากฏ และวิชัยขึ้นในรายชื่อสมาชิกขององค์กรทันที

ผูกได้เฉพาะบัญชีที่มีอยู่แล้วฟอร์มนี้ไม่สร้างบัญชีใหม่ พนักงานต้องสมัครบัญชี SMEDNA ไว้ก่อน (แบบวิชัยในบทที่ 0)

2 · ผู้ดูแลแพลตฟอร์ม: รั้วความปลอดภัย

บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) · หน้า: /admin/plans /flags และ /admin — สิ่งที่ตั้งในบทนี้คือขอบเขตสูงสุด (เพดาน) ของทั้งระบบ Edition ของพาร์ทเนอร์ (บทที่ 3) ทำได้แค่ตัดให้แคบลงจากเพดานนี้ ขยายเกินไม่ได้

A04

ดูเพดานพื้นฐานของแผนที่ขายให้ร้าน

ทำอย่างไร

  1. ไปที่หน้า /admin/plans
  2. เลื่อนไปหัวข้อ “พื้นฐานทั้งระบบ (floor)”
หัวข้อพื้นฐานทั้งระบบ floor พร้อมแผน guide_starter

หน้า “เพดานแผน”: หัวข้อพื้นฐานทั้งระบบ (floor) และรายการแผนด้านล่าง รวมถึง “Starter ร้านโชห่วย” (guide_starter)

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

หัวข้อ “พื้นฐานทั้งระบบ (floor)” แสดงรหัสสิทธิ์ทั้งหมด ตัวที่ไฮไลต์คือสิทธิ์ที่ทุกแผนต้องมีอย่างน้อย (ในภาพคือ AUDIT_LOG BACKUP และ PDP_PRIVACY) คำอธิบายใต้หัวข้อบอกอีกกติกาหนึ่ง: ร้านที่จด VAT แล้วจะมี E_TAX ติดตัวเสมอไม่ว่าจะอยู่แผนไหน ด้านล่างเป็นรายการแผน รวมถึงแผน guide_starter ของเรื่องนี้

floor กับเพดาน ต่างกันอย่างไรfloor คือชุดสิทธิ์ขั้นต่ำที่ทุกแผนต้องมี ส่วนเพดานคือสิทธิ์สูงสุดที่แผนนั้นขายได้ Edition ของพาร์ทเนอร์ (บทที่ 3) ตัดสิทธิ์ที่ร้านเห็นให้แคบลงจากเพดานของแผนที่ร้านใช้อยู่ได้ แต่ขยายเกินเพดานไม่ได้
A05

ก่อนเปลี่ยนสิทธิ์ในแผน ระบบให้ดูผลกระทบก่อนเสมอ

ทำอย่างไร

  1. กดที่แถวแผน “Starter ร้านโชห่วย” (guide_starter) ให้ขยายออก
  2. กดที่รหัส “POS” เพื่อเพิ่มเข้าไปในแผน
  3. กดปุ่ม “บันทึก”
กล่องยืนยันการเปลี่ยนแปลงแผน แสดงจำนวนร้านที่อยู่บนแผนนี้

กล่องยืนยัน “ยืนยันการเปลี่ยนแปลงนี้?” ก่อนบันทึกจริง

กล่องโต้ตอบ “ยืนยันการเปลี่ยนแปลงนี้?” ขึ้นมาก่อนเสมอ พร้อมบอกว่ามีกี่ร้านอยู่บนแผนนี้ (“… ร้านอยู่บนแผนนี้”) กด “ยืนยันบันทึก” เพื่อบันทึกจริง หรือ “ยกเลิก” เพื่อปิดโดยไม่เปลี่ยนอะไร

ภาพนี้ไม่ได้บันทึกจริงขั้นตอนนี้กดยกเลิกเพื่อปิดกล่องโต้ตอบ ไม่ได้กดยืนยันบันทึก แผน guide_starter ในเรื่องนี้จึงไม่ถูกแก้จริง — ภาพที่เห็นคือหน้าจอเตือนก่อนตัดสินใจเท่านั้น
A06

ดูสวิตช์ที่ต้องรอทนายตอบก่อนเปิด

ทำอย่างไร

  1. ไปที่หน้า /flags
  2. เลื่อนไปหัวข้อ “⚖️ ต้องมีคำยืนยันจากทนาย”
หัวข้อต้องมีคำยืนยันจากทนาย 6 แถว

6 สวิตช์ที่ต้องมีคำยืนยันจากทนายก่อนเปิด หนึ่งแถวรับรองแล้ว

หน้าเดียวกันในโหมดมืด

โหมดมืด

มีทั้งหมด 6 แถว แถว partner_api_contact_enabled ขึ้นข้อความ “รับรองแล้ว: …” พร้อมเลขอ้างอิง ส่วนอีก 5 แถว ยังขึ้น “ยังไม่มีคำรับรอง”

ค่าจริงบน prodภาพนี้ถ่ายจากสภาพแวดล้อมทดสอบภายในที่เปิด partner_api_contact_enabled ไว้เพื่อทำคู่มือเล่มนี้เท่านั้น (เลขอ้างอิงในภาพคือ GUIDE-LOCAL-ONLY-NOT-PROD) — บน prod จริง ทั้ง 6 แถวยังเป็น “ยังไม่มีคำรับรอง” หมด จนกว่าฝ่ายกฎหมายจะตอบเป็นลายลักษณ์อักษรแยกทีละเรื่อง
A07

เปิดสวิตช์กฎหมายต้องใส่เลขอ้างอิงคำตอบทนายก่อนเสมอ

ทำอย่างไร

  1. กดสวิตช์ที่แถว partner_studio_ai_enabled
กล่องกรอกเลขอ้างอิงคำตอบทนายก่อนเปิดสวิตช์

กล่องกรอก “เลขอ้างอิงคำตอบทนาย” ที่ต้องกรอกก่อนเปิดสวิตช์ได้จริง

กล่อง “เปิด partner_studio_ai_enabled?” ขึ้นมาพร้อมช่อง “เลขอ้างอิงคำตอบทนาย” ต้องกรอกเลขอ้างอิงก่อนจึงจะกด “เปิดใช้” ได้

ภาพนี้ไม่ได้เปิดสวิตช์จริงขั้นตอนนี้กดยกเลิกเพื่อปิดกล่อง ไม่ได้กรอกเลขอ้างอิงจริง — สวิตช์ partner_studio_ai_enabled ยังปิดอยู่เหมือนเดิม
A08

ตั้งเพดานวันหมดอายุของคีย์ API

ทำอย่างไร

  1. ในหน้า /flags เลื่อนไปหัวข้อ “เพดานวันหมดอายุของ partner API key”
ช่องจำนวนวันสูงสุดของคีย์ API ตั้งไว้ที่ 90

เพดานวันหมดอายุของคีย์ API ตั้งไว้ที่ 90 วัน

หน้าเดียวกันบนมือถือ

บนมือถือ

ช่อง “จำนวนวันสูงสุด” ตั้งไว้ที่ 90 วัน — คีย์ API ที่พาร์ทเนอร์ออกเองขออายุเกินนี้ไม่ได้ ถ้าเว้นช่องนี้ว่าง แปลว่าไม่มีเพดาน

เห็นผลจริงในบทที่ 5ขั้นตอน P08 สาธิตสิ่งที่เกิดขึ้นเมื่อพาร์ทเนอร์พยายามออกคีย์อายุเกิน 90 วัน
A09

เพดานพื้นที่เก็บฟิลด์เสริมของ Edition

ทำอย่างไร

  1. เลื่อนไปหัวข้อ “เพดานพื้นที่เก็บฟิลด์เสริม”
ช่องไบต์สูงสุดของฟิลด์เสริม แสดงค่าเริ่มต้น 16000

ค่าเริ่มต้น 16,000 ไบต์ ต่อลูกค้าหรือสินค้าหนึ่งแถว รวมทุกแหล่งที่มา

ช่อง “ไบต์สูงสุด” แสดงค่าเริ่มต้น 16000 ไบต์ — ยังไม่มีใครตั้งแถวนี้เอง จึงเห็นค่าที่มากับระบบ

ฟิลด์เสริมคืออะไรช่องข้อมูลที่ Edition ประกาศเพิ่มให้ลูกค้าหรือสินค้าของร้าน เช่น “วันรับของประจำ” ในบทที่ 6 — เพดานนี้กันไม่ให้ Edition ไหนเก็บข้อมูลเกินตัวจนกระทบร้านอื่น
A10

ดูกำหนดเวลาลบข้อมูลอัตโนมัติหลังร้านถูกคืน

ทำอย่างไร

  1. เลื่อนไปหัวข้อ “การเก็บข้อมูลลูกค้า — กำหนดเวลาลบอัตโนมัติ”
หัวข้อการเก็บข้อมูลลูกค้า แสดงแถว partner_detach และสวิตช์ purge

แถวกำหนดเวลาของ partner_detach และสวิตช์ลบถาวรอัตโนมัติ

หน้าเดียวกันในโหมดมืด

โหมดมืด

เห็นแถวที่เกี่ยวกับ partner_detach (การคืนร้าน) และสวิตช์ partner_detach_purge_enabled ที่คุมว่าจะให้ลบถาวรอัตโนมัติหรือไม่

ปิดอยู่บน prodสวิตช์นี้ปิดอยู่บน prod จริง (ดูบทที่ 0) — บทที่ 6 ตอนท้าย (S08) สาธิตผลลัพธ์หลังลบถาวรบนสภาพแวดล้อมทดสอบภายในเท่านั้น
A11

ตั้งอัตราค่า AI ที่ใช้ประมาณค่าใช้จ่าย

ทำอย่างไร

  1. ไปที่หน้า /admin
  2. กดแท็บ “AI Settings”
  3. เลื่อนไปหัวข้อ “อัตราค่า AI”
แท็บ AI Settings แสดงการ์ดอัตราค่า AI

การ์ดอัตราค่า AI ภายใต้แท็บ AI Settings

การ์ด “อัตราค่า AI” มีปุ่ม “เพิ่มแถว” และแถว “ค่าเริ่มต้น (โมเดลที่ไม่ตรงกับแถวไหนเลย)” — ในภาพยังไม่มีแถวของรุ่นโมเดลใดเลย

ทำไมอยู่ในบทนี้การ์ดนี้ใช้ประมาณค่าใช้จ่าย (บาท) ที่แสดงในหน้า /ai-usage ถ้ายังไม่ตั้งแถวใด ระบบใช้ค่าประมาณจากโค้ด — ก่อนเปิดฟีเจอร์ AI ใด ๆ เช่น partner_studio_ai_enabled (A07) ควรตั้งอัตราให้ใกล้ราคาจริงก่อน

3 · พาร์ทเนอร์: คอนโซลและ Edition ที่ขาย

บทบาท: เจ้าของกิจการพาร์ทเนอร์ (owner) และพนักงานขาย (sales) · หน้า: /partner

P01

เปิดคอนโซลพาร์ทเนอร์ ดูร้านในสังกัด

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีสมชาย (owner)
  2. ไปที่หน้า /partner
หน้า /partner หัวข้อร้านในสังกัด

หัวข้อ “ร้านในสังกัด” พร้อมรายชื่อร้าน

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ แท็บพับเป็นแถว

บนมือถือ — แท็บเมนูพับเรียงเป็นแถว ไม่มีปุ่มแฮมเบอร์เกอร์

เห็นหัวข้อ “ร้านในสังกัด” พร้อมรายชื่อร้านป้าแดงของชำและร้านลุงชัยค้าส่ง

ร้านที่คืนแล้วหายจากรายการทั้งแถวระบบไม่ได้ขึ้นป้ายกำกับไว้ ร้านเจ๊หมวยมินิมาร์ท (ถูกคืนก่อนเรื่องนี้เริ่ม — ดูบทที่ 0) จึงไม่อยู่ในรายการ และภาพนี้เห็นแค่ 2 ร้าน ถ้าพาร์ทเนอร์อยากเห็นประวัติร้านที่เคยอยู่ในสังกัด ทีมงานรับไว้เป็นข้อเสนอปรับปรุงแล้ว แต่ยังไม่มีในรุ่นนี้
P02

สิทธิ์ของพนักงานขายแคบกว่าเจ้าของกิจการ

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีมานี (sales)
  2. ไปที่หน้า /partner
เมนูของบทบาท sales ไม่มีแท็บการเชื่อมต่อ

เมนูของบทบาท sales — มี “ช่วยเหลือร้าน” แต่ไม่มี “การเชื่อมต่อ”

หน้าเดียวกันบนมือถือ

บนมือถือ

เห็นเมนู “ช่วยเหลือร้าน” แต่ไม่มีเมนู “การเชื่อมต่อ”

ซ่อน ไม่ใช่แค่ล็อกการเชื่อมต่อ (คีย์ API และ webhook ในบทที่ 5) เป็นสิทธิ์ของ owner เท่านั้น พนักงานขายไม่เห็นเมนูนี้เลย ไม่ใช่แค่กดแล้วทำอะไรไม่ได้
P03

เปิด Edition ที่ขายอยู่

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีสมชาย ไปที่ /partner/editions
  2. กดปุ่ม “แก้ไข” ที่แถว Edition “โชห่วยส่งดี”
แผงแก้ไข Edition โชห่วยส่งดี พร้อมลิงก์คู่มือ Edition Studio

แผงแก้ไข Edition “โชห่วยส่งดี” พร้อมลิงก์ไปคู่มือ Edition Studio

แผงแก้ไขเปิดขึ้น ช่องชื่อ Edition แสดง “โชห่วยส่งดี”

วิธีตัดแต่ง Edition อยู่ในคู่มืออีกเล่มการเลือกความสามารถ เมนู คำเรียก และแบรนด์ของ Edition สอนละเอียดแล้วในคู่มือ Edition Studio — เล่มนี้ไม่สอนซ้ำ อ่านต่อได้ที่ คู่มือ Edition Studio
P04

ก่อนเผยแพร่ Edition ระบบบอกก่อนว่ากระทบร้านไหนบ้าง

ทำอย่างไร

  1. ในแผงแก้ไข Edition กดปุ่ม “ดูผลกระทบแล้วเผยแพร่”
หน้าเผยแพร่แล้วกระทบใครบ้าง

หน้า “เผยแพร่แล้วกระทบใครบ้าง” ก่อนยืนยันเผยแพร่จริง

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้า “เผยแพร่แล้วกระทบใครบ้าง” แสดงจำนวนร้านที่ใช้ Edition นี้อยู่ก่อนให้ยืนยันเผยแพร่จริง

ภาพนี้ไม่ได้เผยแพร่จริงขั้นตอนนี้กดยกเลิกเพื่อปิดหน้าจอ ไม่ได้กดเผยแพร่จริง — Edition ที่ร้านเห็นอยู่ตอนนี้จึงไม่เปลี่ยน

4 · ขายและดูแลร้าน

บทบาท: พนักงานขาย (sales) · เจ้าของร้าน (shop owner) · เจ้าของกิจการพาร์ทเนอร์ (owner)

P05

เปิดร้านใหม่ให้ลูกค้า

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีมานี (sales) ไปที่ /partner/shops/new
  2. กรอกชื่อร้าน ร้านน้องฝนค้าส่ง
  3. กรอกอีเมลเจ้าของร้าน [email protected] และชื่อเจ้าของร้าน ฝน
  4. เลือกประเภทธุรกิจ เลือก Edition “โชห่วยส่งดี” และเลือกแผน guide_starter
  5. กดปุ่ม “เปิดร้าน”
ฟอร์มเปิดร้านใหม่กรอกครบ ปุ่มเปิดร้านกดได้

ฟอร์มเปิดร้านใหม่ กรอกครบทุกช่องแล้วปุ่ม “เปิดร้าน” จึงกดได้

ฟอร์มเดียวกันบนมือถือ

บนมือถือ

หน้า “เปิดร้านเรียบร้อย” ขึ้นมาพร้อมลิงก์เชิญให้เจ้าของร้านไปตั้งรหัสผ่านเอง ลิงก์นี้ใช้ได้ครั้งเดียวและหมดอายุใน 7 วัน

ต้องเลือกประเภทธุรกิจก่อนเสมอปุ่ม “เปิดร้าน” จะกดไม่ได้จนกว่าจะเลือกประเภทธุรกิจ แม้ Edition ที่เลือก (โชห่วยส่งดี) จะใช้ได้กับทุกประเภทธุรกิจในขอบเขตของพาร์ทเนอร์ก็ตาม
P11

ขอสิทธิ์เข้าไปช่วยตั้งค่าให้ร้าน

ทำอย่างไร

  1. ไปที่ /partner/support
  2. เลือกร้าน “ร้านป้าแดงของชำ” ในช่อง “ร้าน”
  3. ช่อง “ระดับสิทธิ์ที่ขอ” — เรื่องนี้ขอแบบ “ดูอย่างเดียว”
  4. พิมพ์เหตุผลในช่อง “เหตุผลที่ขอ” เช่น “ช่วยตั้งค่าฟิลด์ลูกค้าให้ร้าน”
  5. กดปุ่ม “ส่งคำขอให้ร้าน”
ฟอร์มขอสิทธิ์ช่วยเหลือร้าน เลือกร้านและกรอกเหตุผล

ฟอร์มขอสิทธิ์ช่วยเหลือ เลือกร้านและกรอกเหตุผลแล้ว

ฟอร์มเดียวกันบนมือถือ

บนมือถือ

ข้อความ “ส่งคำขอแล้ว รอเจ้าของร้านอนุมัติ” ปรากฏ

ต้องให้ร้านอนุมัติก่อนเสมอพนักงานขายเข้าไปช่วยงานในร้านเองไม่ได้ทันที ต้องรอเจ้าของร้านกดอนุมัติก่อน (ขั้นตอนถัดไป)
S01

เจ้าของร้านอนุมัติคำขอ

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีแดง (เจ้าของร้านป้าแดงของชำ)
  2. ไปที่หน้า /support-access
  3. กดปุ่ม “อนุมัติ … นาที” ที่คำขอของส่งดี (ในภาพคือ “อนุมัติ 60 นาที”)
หน้า support-access ปุ่มอนุมัตินาทีที่ขอ

คำขอจากส่งดีในหัวข้อ “รอคุณอนุมัติ” พร้อมระดับสิทธิ์ “ดูอย่างเดียว” เหตุผล และชื่อคนขอ (มานี ขายเก่ง)

หน้าเดียวกันบนมือถือ

บนมือถือ

คำขอย้ายจากส่วน “รอคุณอนุมัติ” ไปอยู่ใน “กำลังใช้งานอยู่” ทันที

เจ้าของร้านเป็นคนตัดสินใจเสมอก่อนอนุมัติ เจ้าของร้านเห็นว่าใครขอ ขอระดับสิทธิ์ไหน และขอเพราะอะไร จะกด “อนุมัติ” หรือ “ปฏิเสธ” ก็ได้ หน้าเดียวกันยังมีรายการ “สิ่งที่พาร์ทเนอร์ทำแทนไม่ได้” ซึ่งถูกปฏิเสธเสมอแม้อนุมัติแบบทำรายการแทนได้แล้ว
P12

เข้าไปทำงานในร้านจริง พร้อมแถบนับถอยหลัง

ทำอย่างไร

  1. กลับไปที่ /partner/support
  2. กดปุ่ม “เข้าสู่ร้าน” ที่แถวคำขอที่เพิ่งได้รับอนุมัติ
แอปของร้านพร้อมแถบนับถอยหลังเวลาที่เหลือ

แถบโหมดช่วยเหลือบนแอปของร้าน พร้อมเวลานับถอยหลัง (นาที:วินาที)

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

ระบบพาไปยังแอปของร้านจริง พร้อมแถบ “กำลังช่วยร้าน … · ดูอย่างเดียว · เหลือ …” ที่นับถอยหลังเวลาที่เหลือ (นาที:วินาที) อยู่บนหน้าจอตลอด

รู้ตัวตลอดว่ากำลังทำงานในร้านของลูกค้าแถบนี้บอกพนักงานขายตลอดเวลาว่ากำลังช่วยร้านไหน ด้วยสิทธิ์ระดับใด และเหลือเวลาเท่าไร ฝั่งเจ้าของร้านเห็นเซสชันนี้ในหัวข้อ “กำลังใช้งานอยู่” ของหน้า /support-access — หมดเวลาเมื่อไร สิทธิ์นี้หมดเองอัตโนมัติ
P13

จบสิทธิ์ช่วยเหลือก่อนหมดเวลา

ทำอย่างไร

  1. ที่ /partner/support กดปุ่ม “จบสิทธิ์”
ปุ่มจบสิทธิ์ที่แถวเซสชันที่กำลังใช้งาน

ปุ่ม “จบสิทธิ์” ที่แถวเซสชันที่กำลังใช้งานอยู่

ข้อความ “จบสิทธิ์แล้ว ร้านได้รับแจ้งแล้ว” ปรากฏ

จบก่อนได้เสมอไม่ต้องรอหมดเวลาที่ขอไว้ กด “จบสิทธิ์” เมื่อไรก็ได้ และร้านจะได้รับแจ้งทุกครั้งที่จบ
P14

คืนร้านที่เลิกใช้ Edition ของพาร์ทเนอร์นี้แล้ว

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีสมชาย กลับไปที่ /partner
  2. กดปุ่ม “คืนร้าน” ที่แถว “ร้านลุงชัยค้าส่ง”
  3. อ่านกล่องโต้ตอบ แล้วกดปุ่ม “คืนร้าน” อีกครั้งเพื่อยืนยัน
กล่องโต้ตอบคืนร้านนี้ แสดงจำนวนแถวข้อมูลและวันที่ยังดาวน์โหลดได้

กล่องโต้ตอบ “คืนร้านนี้?” ก่อนยืนยันจริง

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

กล่องโต้ตอบ “คืนร้านนี้?” บอกจำนวนแถวข้อมูลลูกค้า/สินค้าที่มีค่าจากฟิลด์ของ Edition และจำนวนวันที่เจ้าของร้านยังดาวน์โหลดข้อมูลได้ก่อนถูกลบถาวร กดยืนยันแล้วขึ้นข้อความ “คืนร้านแล้ว ข้อมูลจะถูกลบถาวรวันที่ …”

GET /api/v1/partner/shops/{ร้านลุงชัยค้าส่ง}/detach-preview — ตัวอย่างผลกระทบก่อนยืนยัน
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
  }
}
คืนร้านแล้วเกิดอะไรขึ้นร้านออกจากสังกัดของส่งดี เลิกใช้ Edition “โชห่วยส่งดี” และหายจากรายการร้านของพาร์ทเนอร์ทันที (แบบเดียวกับร้านเจ๊หมวย — ดูหมายเหตุที่ P01) ค่าที่กรอกในฟิลด์ของ Edition จะถูกลบถาวรเมื่อครบกำหนด ถ้าร้านไม่กลับมาเชื่อมต่อใหม่ก่อน — บน prod การลบอัตโนมัติยังปิดอยู่ (ดูบทที่ 0) ส่วนใน API การคืนร้านเรียกว่า “detach”

5 · เชื่อมระบบ: คีย์ API และ webhook

บทบาท: เจ้าของกิจการพาร์ทเนอร์ (owner) เท่านั้น · หน้า: /partner/integrations (เมนู “การเชื่อมต่อ”) — ซีอาร์เอ็มของส่งดีเชื่อมกับ SMEDNA สองทาง: เรียกข้อมูลเองด้วยคีย์ API (หน้าจอเรียกว่า “กุญแจ API”) และรับข้อความแจ้งเหตุการณ์ที่ SMEDNA ส่งมาเองผ่าน webhook

webhook ในบทนี้ไปถึงเครื่องรับทดสอบภายใน สลับแค่ปลายทางเครือข่ายทุกภาพในบทนี้ตั้ง URL ปลายทางเป็น https://example.com/… ซึ่งระบบรับได้จริง (ต้องขึ้นต้นด้วย https) แต่ตอนส่งจริงเพื่อทำคู่มือเล่มนี้ เราสลับเฉพาะปลายทางเครือข่ายให้ข้อความไปถึงเครื่องรับทดสอบภายในแทน example.com ส่วนเนื้อหาที่ส่ง (payload) ลายเซ็น และโค้ดส่งกับลองส่งซ้ำ (retry) เป็นโค้ดจริงของระบบทั้งหมด ไม่ได้จำลอง (ตัวอย่างเต็มอยู่ในบทที่ 7)
P06

ภาพรวมคีย์ API และปลายทาง webhook

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีสมชาย ไปที่ /partner/integrations
  2. เลื่อนลงเพื่อดูรายการคีย์และหัวข้อ “ประวัติการส่งล่าสุด”
หน้าเชื่อมระบบของคุณ ฟอร์มกุญแจ API ฟอร์มการแจ้งเตือน และปลายทาง example.com

ส่วนบนของหน้า: ฟอร์มออกคีย์ ฟอร์มเพิ่มปลายทาง และปลายทางที่ชี้ไป example.com (สถานะ “ใช้งานอยู่”) — รายการคีย์และประวัติการส่งอยู่ถัดลงไปในหน้าเดียวกัน

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

หน้าเดียวกันมีคีย์ 3 ดวง songdee-pos-sync · songdee-crm-contact · songdee-old-key (ดวงสุดท้ายหมดอายุแล้ว) ปลายทาง webhook ที่ชี้ไป example.com และหัวข้อ “ประวัติการส่งล่าสุด” ซึ่งมีแถว customer.erased สถานะ delivered

ข้อมูลชุดเดียวกันเรียกผ่าน API ได้ประวัติการส่งนี้คือข้อมูลเดียวกับที่ซีอาร์เอ็มของส่งดีได้เมื่อเรียก GET /partner-api/deliveries — ตัวอย่าง JSON เต็มอยู่ในบทที่ 7 ข้อ 7.6
P06b

เลื่อนดูรายการคีย์และประวัติการส่งให้เต็มตา

ทำอย่างไร

  1. ที่หน้าเดียวกัน เลื่อนลงจนเห็นหัวข้อ “ประวัติการส่งล่าสุด” เต็มจอ
รายการคีย์ API ทั้ง 3 ดวงและประวัติการส่ง customer.erased สถานะ delivered

รายการคีย์ 3 ดวงและประวัติการส่งเต็มจอ (ภาพ P06 ก่อนหน้าตัดที่ขอบจอ ยังไม่เลื่อนลงมาถึงส่วนนี้)

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

เห็นคีย์ครบ 3 ดวง songdee-pos-sync · songdee-crm-contact · songdee-old-key และแถว customer.erased ในประวัติการส่งขึ้นสถานะ delivered

P07

ออกคีย์ใหม่พร้อมขอบเขตข้อมูลติดต่อลูกค้า

ทำอย่างไร

  1. กรอกชื่อคีย์ songdee-guide-ui ในช่อง “ชื่อกุญแจ”
  2. ติ๊กช่อง “ข้อมูลติดต่อลูกค้า”
  3. กรอก 30 ในช่อง “หมดอายุใน (วัน, ไม่บังคับ)”
  4. กดปุ่ม “ออกกุญแจ”
ฟอร์มออกคีย์ใหม่ ติ๊กข้อมูลติดต่อลูกค้า

ฟอร์มออกคีย์ที่กรอกครบแล้ว ก่อนกด “ออกกุญแจ”

ฟอร์มเดียวกันบนมือถือ

บนมือถือ

ข้อความ “กุญแจพร้อมใช้งาน” ปรากฏ พร้อมค่าคีย์ที่เห็นได้เพียงครั้งเดียว

เห็นได้ครั้งเดียวจริง ไม่ใช่แค่ในภาพต้องคัดลอกเก็บไว้ทันที ปิดหน้านี้ไปแล้วเรียกดูค่าคีย์ซ้ำไม่ได้อีกเลย — ระบบเก็บแค่แฮช (hash — ค่าคำนวณทางเดียวที่ย้อนกลับเป็นค่าเดิมไม่ได้) ไว้ตรวจสอบเท่านั้น
P08

เพดานวันหมดอายุปฏิเสธคีย์ที่ขอนานเกินไป

ทำอย่างไร

  1. กรอกชื่อคีย์ too-long
  2. กรอก 365 ในช่อง “หมดอายุใน (วัน, ไม่บังคับ)”
  3. กดปุ่ม “ออกกุญแจ”
ข้อความปฏิเสธวันหมดอายุเกินเพดาน

ข้อความปฏิเสธ “วันหมดอายุเกินเพดานที่บัญชีนี้กำหนด”

ระบบปฏิเสธด้วยข้อความ “วันหมดอายุเกินเพดานที่บัญชีนี้กำหนด” ไม่มีคีย์ชื่อ “too-long” ถูกสร้างขึ้นจริง

POST /api/v1/partner-api/keys — คำขอถูกปฏิเสธ
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"
    }
  }
}
เพดานนี้ตั้งไว้ในบทที่ 2ผู้ดูแลแพลตฟอร์มตั้งเพดาน 90 วันไว้ที่ /flags (ขั้นตอน A08) พาร์ทเนอร์แก้เพดานนี้เองไม่ได้
P09

องค์กรที่ยังไม่เซ็น DPA ออกคีย์ข้อมูลติดต่อลูกค้าไม่ได้

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีประภา (owner ขององค์กร “หจก. ทดลองระบบ” ที่ยังไม่เซ็น DPA)
  2. ไปที่ /partner/integrations
ช่องติ๊กข้อมูลติดต่อลูกค้าถูกล็อกไว้พร้อมเหตุผล

ช่องติ๊ก “ข้อมูลติดต่อลูกค้า” ถูกล็อกไว้ กดไม่ได้

ช่องติ๊ก “ข้อมูลติดต่อลูกค้า” ถูกล็อกไว้ กดไม่ได้ และคำอธิบายใต้ช่องเปลี่ยนเป็นเหตุผลที่ปิดแทนคำอธิบายปกติ

POST /api/v1/partner-api/keys — ถ้าพยายามยิงตรงข้ามหน้าจอ ฝั่งเซิร์ฟเวอร์ก็ปฏิเสธเช่นกัน
ไม่ใช่แค่ปุ่มถูกล็อกในหน้าจอ — เซิร์ฟเวอร์ตรวจซ้ำเองทุกครั้งด้วยรหัส 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"
    }
  }
}
ผูกกับสัญญา ไม่ใช่แค่ตั้งค่าองค์กรที่ยังไม่กรอกวันเซ็น DPA (บทที่ 1 ขั้นตอน A02) ออกคีย์ขอบเขตข้อมูลติดต่อลูกค้าไม่ได้เลย ไม่ว่าฝ่ายกฎหมายจะเปิดสวิตช์ไว้แล้วหรือไม่ก็ตาม
P10

เหตุการณ์ “ลูกค้าขอลบข้อมูล” บังคับส่งทุกปลายทาง ปิดรับไม่ได้

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีสมชาย กรอก URL ปลายทางใหม่ https://example.com/songdee/hook-2 ในช่อง “URL (HTTPS)”
ฟอร์มเพิ่มปลายทาง webhook เหตุการณ์ customer.erased ล็อกไว้เป็นบังคับ

รายการเหตุการณ์ที่จะส่ง — customer.erased ติ๊กค้างไว้พร้อมป้าย “บังคับ”

ช่องติ๊กเหตุการณ์ customer.erased ถูกล็อกให้ติ๊กอยู่เสมอ พร้อมป้าย “บังคับ”

ปิดรับไม่ได้โดยเจตนาcustomer.erased (ลูกค้าขอลบข้อมูล) ต้องส่งไปทุกปลายทางเสมอ เพราะเป็นสัญญาณที่ต้องส่งต่อให้ระบบปลายทางลบข้อมูลตามคำขอเจ้าของข้อมูลด้วย — พาร์ทเนอร์ปิดรับ event นี้เองไม่ได้ไม่ว่าจะตั้งค่าอย่างไร

6 · เจ้าของร้าน: ช่องกรอกของ Edition และกล่องข้อมูล

บทบาท: เจ้าของร้าน (shop owner) · หน้า: /customers และ /data-boxes

S08 ท้ายบทยังทำบน prod ไม่ได้การลบถาวรอัตโนมัติต้องเปิดสวิตช์ partner_detach_purge_enabled ซึ่งบน prod ยังปิดอยู่ (ดูบทที่ 0 และบทที่ 2 ขั้นตอน A10) ขั้นตอน S02–S06 ทำงานเหมือนกันบน prod ส่วนข้อความในแถบแจ้งเตือนของ S07 ขึ้นกับสวิตช์นี้ บน prod จึงอาจไม่ตรงกับภาพทุกคำ
S02

เห็นช่องกรอกที่ Edition เพิ่มให้ลูกค้า

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีแดง (เจ้าของร้านป้าแดงของชำ) ไปที่ /customers
  2. กดปุ่ม “เพิ่มเติม” ที่แถวลูกค้า “สมศรี มีสุข” แล้วเลือก “แก้ไข”
กล่องแก้ไขลูกค้าแสดงช่องวันรับของประจำและระดับสมาชิก

กล่องแก้ไขลูกค้า พร้อมช่องเพิ่มเติมจาก Edition

กล่องเดียวกันบนมือถือ

บนมือถือ

กล่องแก้ไขลูกค้ามีช่องเพิ่มเติมจาก Edition คือ “วันรับของประจำ” = ทุกวันจันทร์ และ “ระดับสมาชิก” = ประจำ

มาจาก Edition ไม่ใช่ของกลาง SMEDNAช่องเหล่านี้ไม่ได้มากับ SMEDNA มาตรฐาน แต่ถูกประกาศเพิ่มเองใน Edition “โชห่วยส่งดี” (ดูวิธีประกาศช่องกรอกในคู่มือ Edition Studio)
S03

แก้ค่าในช่องของ Edition แล้วบันทึก

ทำอย่างไร

  1. ในกล่องแก้ไขเดียวกัน เปลี่ยนช่อง “วันรับของประจำ” เป็น ทุกวันอังคาร
  2. กดปุ่มบันทึก
กล่องแก้ไขลูกค้าเปลี่ยนวันรับของประจำเป็นทุกวันอังคาร

แก้ค่าก่อนกดบันทึก

เปิดกล่องแก้ไขซ้ำแล้วเห็นค่าใหม่ “ทุกวันอังคาร” ยืนยันว่าบันทึกจริงลงฐานข้อมูล ไม่ใช่แค่เปลี่ยนหน้าจอชั่วคราว

ค่านี้เก็บอยู่ที่ไหนค่าที่กรอกในช่องของ Edition เก็บอยู่ใน “กล่องข้อมูล” ของส่งดี (อธิบายในขั้นตอนถัดไป) เจ้าของร้านส่งออกเป็นไฟล์ได้ และค่าชุดนี้จะถูกลบตามกำหนดหลังร้านถูกคืน (ขั้นตอน S07–S08)
S04

ดูกล่องข้อมูลของร้าน แยกตามผู้ให้บริการ

ทำอย่างไร

  1. ไปที่หน้า /data-boxes
ตารางกล่องข้อมูล 2 แถว แยกแหล่งที่มาและสถานะ

2 แถว: กล่องข้อมูลเก่าก่อนแยกกล่อง (legacy) และกล่องของพาร์ทเนอร์ส่งดี

หน้าเดียวกันในโหมดมืด

โหมดมืด

หน้าเดียวกันบนมือถือ

บนมือถือ

ตาราง “กล่องข้อมูลของร้าน” มี 2 แถว พร้อมคอลัมน์ “แหล่งที่มา” และ “สถานะ” — แถวของส่งดีมีสถานะ “เชื่อมต่ออยู่”

GET /api/v1/business/data-boxes — ที่ร้านป้าแดงของชำเห็นจริง
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
  }
}
“กล่องข้อมูล” คืออะไรที่เก็บฟิลด์เสริมของลูกค้าและสินค้า แยกตามผู้ที่เพิ่มฟิลด์นั้น เพื่อลบข้อมูลของผู้ให้บริการรายหนึ่งได้โดยไม่กระทบรายอื่น — ร้านนี้มีทั้งข้อมูลเก่าก่อนแยกกล่อง และข้อมูลจาก Edition ของส่งดี
S05

ส่งออกข้อมูลจากกล่องเก่าก่อนแยกกล่อง

ทำอย่างไร

  1. กดปุ่ม “ส่งออก” ที่แถวข้อมูลเก่า (legacy)
  2. เลือกเมนู “ลูกค้า · CSV”
เมนูส่งออกกล่องข้อมูลเก่า เลือกลูกค้า CSV

เมนูส่งออก เลือกรูปแบบ CSV (ไฟล์ตารางข้อมูลแบบข้อความ เปิดด้วย Excel ได้)

ไฟล์ดาวน์โหลดชื่อ legacy_<uuid>-customer.csv

data-box-legacy-customer.csv — ไฟล์ที่ดาวน์โหลดได้จริง
กล่องเก่านี้มีฟิลด์เสริมฟิลด์เดียวชื่อ “เก่า” ของลูกค้า 1 คน · คอลัมน์ ref ว่างในตัวอย่างนี้
ref,name,เก่า
,สมศรี มีสุข,บันทึกก่อนแยกกล่อง
data-box-partner-customer.csv — เทียบกับไฟล์ที่ส่งออกจากกล่องของส่งดี
กล่องของส่งดีมี 2 แถว (ครูวันดี, สมศรี) และคอลัมน์ตรงกับฟิลด์ของ Edition “โชห่วยส่งดี” (ระดับสมาชิก, วันรับของประจำ) — ไฟล์นี้บันทึกไว้ก่อนขั้นตอน S03 ค่าของสมศรีจึงยังเป็น “ทุกวันจันทร์”
ref,name,ระดับสมาชิก (member_tier),วันรับของประจำ (pickup_day)
,ครูวันดี ศรีสุข,VIP,ทุกวันเสาร์
,สมศรี มีสุข,ประจำ,ทุกวันจันทร์
ชื่อไฟล์ที่ได้ขึ้นต้นด้วย legacy_ ตามด้วยรหัสของกล่อง (UUID — รหัสสุ่มที่ไม่ซ้ำกัน) แล้วต่อท้ายด้วย -customer.csv
S06

ลบกล่องข้อมูลเก่าถาวร ต้องพิมพ์รหัสยืนยัน

ทำอย่างไร

  1. กดปุ่ม “ลบ” ที่แถวข้อมูลเก่า
  2. กล่องยืนยันบอกว่า “พิมพ์ … เพื่อลบกล่องนี้ถาวร กู้คืนไม่ได้” ให้พิมพ์รหัส 8 ตัวอักษรนั้นลงในช่องให้ตรงทุกตัว
  3. กดปุ่ม “ลบ” อีกครั้ง
กล่องยืนยันลบถาวรที่ต้องพิมพ์รหัสยืนยัน

กล่องยืนยัน — ต้องพิมพ์รหัสให้ตรงเป๊ะ

หน้าเดียวกันในโหมดมืด

โหมดมืด

ข้อความ “ลบกล่องแล้ว” ปรากฏ แถวข้อมูลเก่าหายไปจากตารางทันที

ลบแล้วกู้คืนไม่ได้ต้องพิมพ์รหัสยืนยันให้ตรงเป๊ะ ระบบบอกไว้ตรง ๆ ในกล่องโต้ตอบว่ากู้คืนไม่ได้ — ควรกดส่งออก (ขั้นตอนก่อนหน้า) เก็บไว้ก่อนเสมอถ้ายังไม่แน่ใจ ปุ่ม “ลบ” มีเฉพาะแถวที่ลบได้ กล่องของพาร์ทเนอร์ที่ยังเชื่อมต่ออยู่จึงไม่มีปุ่มนี้
S07

ร้านที่ถูกคืนแล้วเห็นแถบนับวันก่อนลบถาวร

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชีหมวย (เจ้าของร้านที่ถูกพาร์ทเนอร์คืนไปแล้ว)
  2. ไปที่หน้า /data-boxes
แถบแจ้งเตือนกล่องที่ตัดการเชื่อมต่อแล้วพร้อมจำนวนวันก่อนลบถาวร

แถบแจ้งเตือน และแถวของส่งดีที่ขึ้นสถานะ “ตัดการเชื่อมต่อแล้ว” เหลืออีก 0 วัน

หน้าเดียวกันบนมือถือ

บนมือถือ

แถบ “มี 1 กล่องที่ตัดการเชื่อมต่อแล้ว ดาวน์โหลดข้อมูลที่ต้องการไว้ก่อน ระบบจะลบถาวรใน 0 วัน” ขึ้นเหนือตาราง — ในภาพเป็น 0 วัน เพราะระบบเก็บข้อมูลไว้ 30 วันนับจากวันคืนร้าน แต่ร้านนี้ถูกคืนมาแล้ว 60 วัน จึงถึงคิวลบในรอบถัดไป (S08)

เจ้าของร้านเลือกได้ก่อนหมดเวลาร้านนี้ถูกพาร์ทเนอร์คืนไปแล้ว (เหมือนขั้นตอน P14 ในบทที่ 4) — หน้าจอนี้ให้เจ้าของร้านเป็นคนตัดสินใจดาวน์โหลดข้อมูลเก็บไว้เองก่อนหมดเวลา เหมือนขั้นตอน S05 ข้างต้น
S08

หลังครบกำหนด ระบบลบข้อมูลที่ตัดการเชื่อมต่อแล้วถาวร

ทำอย่างไร

  1. ไม่มีปุ่มให้กด — งานเบื้องหลัง (scheduler) รันเองอัตโนมัติเมื่อครบกำหนด
หน้ากล่องข้อมูลของร้านที่ถูกคืน หลังลบถาวรแล้วไม่มีสถานะตัดการเชื่อมต่อเหลือ

หน้าเดียวกับ S07 หลังลบถาวรแล้ว

ไม่มีแถวสถานะ “ตัดการเชื่อมต่อแล้ว” เหลืออยู่บนหน้านี้อีกเลย

ขั้นตอนนี้รันบนสภาพแวดล้อมทดสอบภายในเท่านั้นบน prod จริง สวิตช์ partner_detach_purge_enabled ยังปิดอยู่ (ดูบทที่ 0) ดังนั้นการลบถาวรอัตโนมัติแบบนี้ยังไม่เกิดขึ้นจริงบน prod จนกว่าผู้ดูแลแพลตฟอร์มจะเปิดสวิตช์นี้

7 · สำหรับนักพัฒนา: ตัวอย่างจริง

บทบาท: นักพัฒนาฝั่งพาร์ทเนอร์ (developer) — บทนี้รวมตัวอย่างคำขอและผลลัพธ์จริง บันทึกไว้ก่อนเริ่มถ่ายภาพ (ก่อนขั้นตอน P14 ร้านลุงชัยจึงยังอยู่ในสังกัด) เอกสารอ้างอิงเต็มอยู่ที่ docs/partner/PARTNER_API_TH.md ในรีโพของทีม

ค่าทุกตัวในบทนี้ถูกปิดบังแล้วUUID ถูกแทนด้วยค่าปลอมที่คงที่ เวลาทุกจุดถูกเลื่อนไปเท่ากันหมด คีย์ API และลายเซ็นถูกตัดเหลือไม่กี่ตัวแรกตามด้วย … และ JWT ของผู้ใช้ถูกแทนด้วย <JWT ของผู้ใช้> — ชื่อฟิลด์ โครงสร้าง JSON รหัสสถานะ และ reason code เป็นของจริงตามที่เซิร์ฟเวอร์ตอบ

7.1 เรียกดูตัวตนของคีย์ตัวเอง

api-me.json — GET /partner-api/me
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"
  }
}

7.2 รายชื่อร้านที่คีย์นี้เข้าถึงได้

api-businesses.json — GET /partner-api/businesses
มี 2 ร้าน (ร้านป้าแดงของชำ, ร้านลุงชัยค้าส่ง) ร้านเจ๊หมวยที่ถูกคืนแล้วไม่อยู่ในรายการ · 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"
      }
    ]
  }
}

7.3 ข้อมูลติดต่อลูกค้า — เห็นก็ต่อเมื่อคีย์มีขอบเขตนั้น

คีย์สองดวงเรียก endpoint (URL ของ API แต่ละตัว) เดียวกัน ด้วยลูกค้าคนเดียวกัน — ต่างกันแค่ขอบเขต (scope) ของคีย์แต่ละดวง

api-customer-basic.json — คีย์ที่ไม่มีขอบเขตข้อมูลติดต่อลูกค้า
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"
  }
}
api-customer-contact.json — คีย์ songdee-crm-contact ที่มีขอบเขตข้อมูลติดต่อลูกค้า (แบบเดียวกับที่ออกใน P07)
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"
  }
}

7.4 คีย์หมดอายุ — ปฏิเสธทุก endpoint เหมือนกัน

api-key-expired.json — เรียก GET /partner-api/me ด้วยคีย์ที่หมดอายุแล้ว
รหัส 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"
    }
  }
}

7.5 ข้อผิดพลาดตอนออกคีย์ (ย้อนดูจากบทที่ 5)

api-key-expiry-too-long.json — ดูบริบทเต็มที่ขั้นตอน P08
{
  "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"
    }
  }
}
api-contact-not-allowed.json — ดูบริบทเต็มที่ขั้นตอน P09
{
  "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"
    }
  }
}

7.6 ประวัติการส่ง webhook

api-deliveries.json — GET /partner-api/deliveries?limit=10
5 แถว: 1 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"
      }
    ]
  }
}

7.7 เนื้อหาจริงที่ webhook ส่งออกไป

ทุกครั้งที่มีเหตุการณ์ ระบบส่ง HTTP POST ไปยัง URL ปลายทางที่ตั้งไว้ พร้อมชุด header และเนื้อหา (body) ตามนี้:

webhook-customer.created.http — เหตุการณ์ลูกค้าใหม่
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"
}
webhook-customer.erased.http — เหตุการณ์ลูกค้าขอลบข้อมูล (บังคับรับเสมอ ดู P10)
ไม่มี 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"
}

7.8 ตรวจลายเซ็นก่อนเชื่อ payload ทุกครั้ง

ลายเซ็นใน 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 (กันการเทียบแบบรั่วเวลา)
เซ็นจาก raw body เท่านั้นต้องคำนวณจากไบต์ดิบก่อน parse เป็น JSON — parse แล้ว dump ใหม่ ลายเซ็นจะไม่ตรงอีกต่อไป
ปฏิเสธเวลาที่เก่าเกินไปควรปฏิเสธ request ที่ X-Partner-Timestamp เก่าเกินราว 5 นาที เพื่อกันการส่งซ้ำ (replay)
webhook-verify.txt — ผลตรวจลายเซ็นทั้งสองเหตุการณ์ข้างบนจริง
ลายเซ็นที่ได้รับ (received) กับที่คำนวณเอง (recomputed) ต้องตรงกันทุกตัวอักษร ผลในเดโมนี้คือ OK ทั้งคู่ (ตรวจกับค่าเต็ม ไฟล์นี้ตัดเหลือ 8 ตัวแรก)
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

8 · บนมือถือ

รวมภาพหน้าจอบนมือถือ (390 พิกเซล) จากบททั้งหมดไว้ในที่เดียว ให้เห็นภาพรวมว่าแต่ละบทบาทใช้งานบนมือถือหน้าตาเป็นอย่างไร — เนื้อหาละเอียดของแต่ละภาพอยู่ในบทของมันแล้ว

A01 บนมือถือ

A01 · /admin/partners

P01 บนมือถือ

P01 · /partner

P02 บนมือถือ

P02 · /partner (sales)

P05 บนมือถือ

P05 · /partner/shops/new

P06 บนมือถือ

P06 · /partner/integrations

P07 บนมือถือ

P07 · ออกคีย์

P11 บนมือถือ

P11 · /partner/support

P12 บนมือถือ

P12 · โหมดช่วยเหลือ

P14 บนมือถือ

P14 · คืนร้าน

S02 บนมือถือ

S02 · /customers

S04 บนมือถือ

S04 · /data-boxes

S07 บนมือถือ

S07 · ร้านที่ถูกคืน

9 · เดโมบนระบบจริง (prod): ปุ่มสร้างข้อมูลตัวอย่าง

บทบาท: ผู้ดูแลแพลตฟอร์ม (admin) กดสร้างและลบ · หน้า: /admin การ์ด “คู่มือพาร์ทเนอร์ (ข้อมูลตัวอย่าง)” — จากนั้นใช้บัญชีตัวอย่าง 4 บัญชีดูแต่ละมุมมองบน prod ได้

ใช้ได้หลังรุ่นนี้ขึ้น prodการ์ดนี้มากับโค้ดรุ่นเดียวกับคู่มือเล่มนี้ จะเห็นบน prod หลังเจ้าของระบบสั่ง deploy (นำโค้ดขึ้นระบบจริง) แล้วเท่านั้น และรุ่นนี้ไม่เพิ่มตารางในฐานข้อมูล (ไม่มี migration)
9.1

เปิดการ์ดก่อนกดสร้าง

ทำอย่างไร

  1. เข้าสู่ระบบด้วยบัญชี admin ไปที่หน้า /admin
  2. เปิดแท็บ “บัญชีตัวอย่าง”
  3. หาการ์ด “คู่มือพาร์ทเนอร์ (ข้อมูลตัวอย่าง)”
การ์ดคู่มือพาร์ทเนอร์ก่อนกดสร้าง แสดงข้อความยังไม่ได้สร้างข้อมูลตัวอย่าง

ก่อนกดสร้าง — การ์ดว่าง พร้อมช่องรหัสผ่านร่วม (ปิดบังค่าไว้)

เห็นการ์ดเปล่า ข้อความ “ยังไม่ได้สร้างข้อมูลตัวอย่าง” และปุ่ม “สร้างข้อมูลตัวอย่างคู่มือพาร์ทเนอร์”

9.2

กดสร้างข้อมูลตัวอย่างทั้งชุด

ทำอย่างไร

  1. กดปุ่ม “สร้างข้อมูลตัวอย่างคู่มือพาร์ทเนอร์”
การ์ดหลังสร้างข้อมูลตัวอย่าง แสดงองค์กรบัญชีและร้านค้า ช่องรหัสผ่านถูกปิดบัง

หลังสร้างสำเร็จ — การ์ดแสดงองค์กร บัญชี และร้านค้า (ช่องรหัสผ่านปิดบังไว้ด้วยแถบทึบ)

การ์ดเดียวกันบนมือถือ

บนมือถือ

ข้อความ “สร้างใหม่ 17 รายการ · มีอยู่แล้ว 0 รายการ” ปรากฏ การ์ดแสดงพาร์ทเนอร์ตัวอย่าง ตาราง “บัญชี” 4 บัญชี ตาราง “ร้านค้า” 2 ร้าน (“เชื่อมอยู่” 1 · “ถูกคืนแล้ว” 1) คีย์ API 2 ดวง และ webhook 1 จุดที่ขึ้นว่า “หยุดไว้ — ไม่ยิงออกจริง” — กดซ้ำได้ ของที่มีอยู่แล้วนับเป็น “มีอยู่แล้ว” ไม่สร้างซ้ำ

สิ่งที่ปุ่มนี้สร้างองค์กร “บริษัท ส่งดี โฮลเซล จำกัด (ตัวอย่าง)” (slug songdee-demo) ที่เซ็น DPA แล้ว · Edition “โชห่วยส่งดี” ที่เผยแพร่แล้ว · 4 บัญชี: สมชาย (เจ้าขององค์กรพาร์ทเนอร์) มานี (ฝ่ายขายพาร์ทเนอร์) แดงและหมวย (เจ้าของร้าน) · 2 ร้าน: ร้านป้าแดงของชำ (เชื่อมอยู่) และร้านเจ๊หมวยมินิมาร์ท (ถูกคืนย้อนหลัง 10 วัน) · ลูกค้าตัวอย่าง 4 คนที่มีแค่ชื่อ ไม่มีเบอร์โทรหรืออีเมล · คีย์ API แบบอ่านอย่างเดียว 2 ดวง (ดวงหนึ่งหมดอายุแล้วโดยตั้งใจ) · webhook 1 จุดที่ชี้ไป hooks.songdee.example (โดเมนสำหรับตัวอย่าง ไม่มีเครื่องจริงรับ) และหยุดไว้ตั้งแต่สร้าง
สิ่งที่ปุ่มนี้ไม่แตะเลยไม่เปลี่ยนสวิตช์ใด ๆ ไม่เปลี่ยนแผนหรือราคา ไม่ผูกแผนให้ร้านตัวอย่าง ไม่ออกใบแจ้งหนี้ ไม่ส่งอีเมล และไม่ส่ง webhook ออกไปไหน
คีย์ API ของเดโมใช้เรียก API ไม่ได้คีย์ทั้งสองดวงมีอยู่จริงในระบบ แต่ปุ่มนี้ทิ้งค่าคีย์เต็ม (plaintext) ทันทีหลังสร้าง ไม่เก็บไว้ที่ไหนเลย เหมือนที่พาร์ทเนอร์เห็นค่าคีย์ได้ครั้งเดียวในบทที่ 5 (P07) — จึงไม่มีใครนำคีย์ของเดโมไปเรียก API ได้
ถ้าขึ้นรหัส SLUG_TAKENระบบตอบ 409 SLUG_TAKEN เมื่อมีองค์กรอื่นที่ไม่ได้สร้างจากปุ่มนี้ใช้ slug songdee-demo อยู่แล้ว ปุ่มจะไม่ยึดองค์กรนั้นมาใช้ ให้ตรวจองค์กรนั้นที่ /admin/partners ก่อน
9.3

เข้าสู่ระบบด้วยบัญชีตัวอย่าง

ทำอย่างไร

  1. ที่การ์ดเดียวกัน ช่อง “รหัสผ่านร่วม (เดโมเท่านั้น)” แสดงรหัสผ่านเดโม กดปุ่มคัดลอกข้างช่องได้ — ใช้รหัสนี้กับบัญชีตัวอย่างทั้ง 4 บัญชี
  2. เข้าสู่ระบบด้วยอีเมลของบัญชีที่ต้องการ เช่น [email protected] พร้อมรหัสผ่านนั้น
  3. บัญชีสมชายและมานีไม่มีร้านของตัวเอง หลังเข้าสู่ระบบอาจเจอหน้าที่ยังไม่มีร้าน ให้เปิด /partner ตรง ๆ

เข้าสู่ระบบได้ในบทบาทของบัญชีนั้น เหมือนบัญชีจริงทุกอย่าง แต่ข้อมูลทั้งหมดเป็นข้อมูลตัวอย่าง

เปลี่ยนรหัสผ่านเดโมทีหลัง บัญชีชุดนี้ไม่เปลี่ยนตามบัญชีตัวอย่างได้รหัสผ่านเดโม ณ ตอนที่กดสร้าง ถ้าภายหลังมีการเปลี่ยนรหัสผ่านเดโม บัญชีชุดนี้ยังใช้รหัสเดิม จนกว่าจะกดลบข้อมูลตัวอย่างแล้วสร้างใหม่
9.4

เจ้าขององค์กรพาร์ทเนอร์: คอนโซล /partner

ทำอย่างไร

  1. เข้าเป็นสมชาย (somchai.owner@…) แล้วเปิด /partner
คอนโซลพาร์ทเนอร์ของสมชาย เห็นร้านป้าแดงของชำ

คอนโซล /partner ของสมชาย — เห็นร้านป้าแดงของชำ (ร้านเจ๊หมวยถูกคืนแล้วจึงไม่อยู่ในรายการ)

หน้าเดียวกันบนมือถือ

บนมือถือ

หน้าตาเหมือนบทที่ 4 (P01) ทุกอย่าง เห็นร้านป้าแดงของชำ 1 ร้าน

9.5

เจ้าขององค์กรพาร์ทเนอร์: การเชื่อมระบบ

ทำอย่างไร

  1. ที่บัญชีเดียวกัน เปิด /partner/integrations
หน้าการเชื่อมระบบของสมชาย เห็นคีย์ API 2 ดวง

/partner/integrations ของสมชาย — คีย์ API 2 ดวง ไม่มีคีย์ขอบเขตข้อมูลติดต่อลูกค้า

เหมือนบทที่ 5 (P06) แต่มีคีย์แค่ 2 ดวง (ไม่มี songdee-crm-contact) และ webhook หยุดไว้ จึงไม่มีประวัติการส่ง

9.6

เจ้าของร้าน: ร้านที่ยังเชื่อมอยู่

ทำอย่างไร

  1. เข้าเป็นแดง (pa.daeng@…) แล้วเปิด /data-boxes
กล่องข้อมูลของร้านป้าแดง แสดงฟิลด์จาก Edition

/data-boxes ของร้านป้าแดง — เห็นกล่องข้อมูลจาก Edition โชห่วยส่งดี

เหมือนบทที่ 6 (S04) ทุกอย่าง เห็นค่า “วันรับของประจำ” และ “ระดับสมาชิก” ของลูกค้าตัวอย่าง

9.7

เจ้าของร้าน: ร้านที่ถูกคืนแล้ว

ทำอย่างไร

  1. เข้าเป็นหมวย (jae.muay@… ร้านที่ถูกคืนแล้ว) แล้วเปิด /data-boxes
กล่องข้อมูลของร้านเจ๊หมวย แสดงแถบแจ้งเตือนว่าตัดการเชื่อมต่อแล้ว

/data-boxes ของร้านเจ๊หมวย — แถบแจ้งเตือนว่ากล่องนี้ตัดการเชื่อมต่อแล้ว

เหมือนบทที่ 6 (S07) แต่ตัวนับวันต่างกัน (ถูกคืนย้อนหลัง 10 วัน ไม่ใช่ 60 วันแบบในภาพบทที่ 6) ข้อความแถบแจ้งเตือนจึงอาจต่างจากภาพนั้น

เดโมบน prod ต่างจากภาพในเล่มนี้อย่างไร
  • ชื่อองค์กร ร้าน และคน มีคำว่า “(ตัวอย่าง)” ต่อท้ายทุกชื่อ
  • ไม่มีบัญชีวิชัย ประภา ลุงชัย และน้องฝน — ขั้นตอน A03, P05, P09 และ P14 จึงดูได้จากภาพในเล่มนี้เท่านั้น
  • /partner เห็นแค่ร้านป้าแดง เพราะร้านเจ๊หมวยถูกคืนแล้ว (ย้อนหลัง 10 วัน ไม่ใช่ 60 วันแบบในภาพ)
  • /partner/integrations มีคีย์ 2 ดวง ไม่มีคีย์ที่อ่านข้อมูลติดต่อลูกค้า และ webhook หยุดไว้ จึงไม่มีประวัติการส่ง
  • สวิตช์ทนายทั้ง 6 ตัวและสวิตช์ลบถาวรอัตโนมัติยังปิด (บทที่ 0) ข้อความแถบแจ้งเตือนของหมวยจึงอาจต่างจากภาพ S07
อย่าเปิดร้านใหม่หรือคืนร้านบนเดโมนี้ปุ่มลบข้อมูลตัวอย่างลบเฉพาะรายการที่ปุ่มสร้างบันทึกไว้ ร้าน คีย์ หรือ webhook ที่สร้างเพิ่มเองระหว่างลองอาจไม่ถูกลบไปด้วย
9.8

ลบข้อมูลตัวอย่างทั้งชุดเมื่อไม่ใช้แล้ว

ทำอย่างไร

  1. กลับมาเป็น admin ที่การ์ดเดียวกัน กดปุ่ม “ลบข้อมูลตัวอย่าง”
  2. ในกล่อง “ลบข้อมูลตัวอย่างคู่มือพาร์ทเนอร์?” พิมพ์ songdee-demo ในช่อง “พิมพ์ songdee-demo เพื่อยืนยัน”
  3. กดปุ่ม “ลบข้อมูลตัวอย่าง” ในกล่องอีกครั้ง
กล่องยืนยันการลบข้อมูลตัวอย่าง พิมพ์ songdee-demo แล้ว ช่องรหัสผ่านถูกปิดบัง

กล่องยืนยัน พิมพ์ songdee-demo ครบแล้ว (ช่องรหัสผ่านของการ์ดด้านหลังยังปิดบังไว้)

การ์ดว่างหลังลบข้อมูลตัวอย่างสำเร็จ

หลังลบสำเร็จ — การ์ดกลับไปว่างเหมือนขั้นตอน 9.1

ข้อความ “ลบไปแล้ว 14 รายการ” ปรากฏ ระบบลบเฉพาะสิ่งที่ปุ่มสร้างสร้างไว้ (องค์กร บัญชี ร้าน คีย์ webhook และลูกค้าตัวอย่าง) ไม่แตะข้อมูลอื่นในระบบ — ตรวจแล้วว่าไม่มีผู้ใช้ องค์กร หรือ registry ของเดโมนี้เหลือค้างในฐานข้อมูลเลย

ลบแล้วย้อนกลับไม่ได้ แต่พิมพ์ผิดไม่มีอะไรหายพิมพ์คำยืนยันไม่ตรงแม้ตัวเดียว ระบบขึ้น “พิมพ์ไม่ตรง ลองพิมพ์ตามที่แสดงอีกครั้ง” และไม่ลบอะไรเลย ถ้าไม่มีอะไรให้ลบแล้ว กดซ้ำก็ไม่ error

10 · ภาคผนวก

10.1 รหัสเหตุผล (reason code) ที่ Partner API ตอบกลับ

อ่านรหัสเหล่านี้ในโค้ดของคุณแทนข้อความ message เพราะ message เปลี่ยนได้ตามเวลา ส่วนรหัสคงที่เสมอ

reason_codeสถานะ HTTPความหมาย
invalid_partner_key401คีย์ไม่ถูกต้อง ไม่มีอยู่จริง หรือถูกเพิกถอนแล้ว — ตอบเหมือนกันทุกกรณีโดยเจตนา เพื่อไม่ให้รู้ว่าคีย์ที่เดามาถูกเพิกถอนไปแล้วหรือไม่เคยมีอยู่
KEY_EXPIRED401คีย์หมดอายุแล้ว (ดูตัวอย่างเต็มในบทที่ 7.4) ต้องออกคีย์ใหม่เท่านั้น
PARTNER_LANE_DISABLED404เลนพาร์ทเนอร์ทั้งระบบปิดอยู่ — ตอบ 404 แทน 401 โดยเจตนา เพื่อไม่ยืนยันว่า Partner API มีอยู่จริง
business_not_found404คีย์นี้เข้าถึงร้านนั้นไม่ได้ — ตอบเหมือนร้านไม่มีอยู่จริง แม้ร้านนั้นจะมีอยู่แต่เป็นของพาร์ทเนอร์รายอื่น
path_not_allowlisted403เส้นทาง (endpoint) นี้ไม่เปิดให้คีย์ API เรียก — เปิดเฉพาะเส้นทางที่อยู่ในเอกสาร Partner API เท่านั้น
write_scope_required403คีย์นี้อ่านได้อย่างเดียว (read) แต่พยายามเรียกคำสั่งที่ต้องเขียนข้อมูล ต้องออกคีย์ใหม่พร้อมขอบเขต write
business_id_required422คีย์นี้เข้าถึงได้หลายร้าน ต้องระบุ business_id มาด้วยเสมอ — ดูรายชื่อร้านทั้งหมดที่ GET /partner-api/businesses
rate_limited429เรียกเกินโควตาต่อนาทีของคีย์นี้แล้ว — รอตามเวลาใน header Retry-After แล้วค่อยเรียกใหม่
CONTACT_SCOPE_NOT_ALLOWED422องค์กรนี้ยังไม่เซ็น DPA จึงออกคีย์ขอบเขตข้อมูลติดต่อลูกค้าไม่ได้ (ดูบทที่ 5 P09)
KEY_EXPIRY_TOO_LONG422ขอวันหมดอายุเกินเพดานที่ผู้ดูแลแพลตฟอร์มตั้งไว้ (ดูบทที่ 5 P08 และบทที่ 2 A08)
key_not_found404คีย์นี้ไม่ใช่ของพาร์ทเนอร์ที่ล็อกอินอยู่ หรือไม่มีอยู่จริง — ตอบเหมือนกันทั้งสองกรณี
CONSENT_NOTICE_REQUIRED422ต้องแสดงประกาศขอความยินยอมให้ลูกค้าเห็นก่อน ระบบจึงจะยอมสร้างข้อมูลลูกค้าใหม่ผ่าน API
webhook_not_found404ปลายทาง webhook นี้ไม่ใช่ของพาร์ทเนอร์ที่ล็อกอินอยู่ หรือไม่มีอยู่จริง

10.2 อะไรเปิด อะไรปิด บน prod ตอนนี้

สวิตช์ / เพดานบน 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 ไบต์ (ค่าเริ่มต้น)

10.3 คำถามที่ถามบ่อย

ทำไมร้านที่ถูกคืนหายจากรายการร้านของพาร์ทเนอร์ทันที ไม่เหลือร่องรอยอะไรเลย

เป็นพฤติกรรมจริงของระบบตอนนี้ (ดูหมายเหตุบทที่ 3 ขั้นตอน P01) — ทีมงานบันทึกไว้เป็นข้อเสนอปรับปรุงผลิตภัณฑ์ว่าพาร์ทเนอร์ควรเห็นประวัติร้านที่เคยอยู่ในสังกัดด้วยหรือไม่ ยังไม่มีการแก้ไข ณ วันที่จัดทำคู่มือ

ลืมคัดลอกค่าคีย์ API ตอนออกคีย์ใหม่ ต้องทำอย่างไร

เรียกดูซ้ำไม่ได้แล้ว ต้องเพิกถอน (revoke) คีย์ดวงนั้นแล้วออกคีย์ใหม่แทน ระบบเก็บแค่แฮชไว้ตรวจสอบ ไม่เก็บค่าจริงไว้ที่ไหนเลยหลังสร้างเสร็จ (เหมือนที่ปุ่มเดโมในบทที่ 9 ทิ้งค่าลับทันทีเช่นกัน)

เหตุการณ์ customer.erased ปิดรับได้ไหม ไม่อยากรับ

ปิดไม่ได้ (ดูบทที่ 5 P10) เพราะเป็นสัญญาณที่ต้องส่งต่อให้ระบบปลายทางลบข้อมูลของลูกค้าตามคำขอเจ้าของข้อมูลด้วย ทุกปลายทางที่ตั้งไว้จะได้รับเหตุการณ์นี้เสมอ

ทำไมองค์กรบางรายอ่านเบอร์โทร/อีเมลลูกค้าผ่าน API ไม่ได้ ทั้งที่ตั้งค่าครบแล้ว

ต้องครบสามเงื่อนไข: องค์กรเซ็น DPA แล้ว (บทที่ 1 A02) · คีย์ที่ใช้ออกพร้อมขอบเขต “ข้อมูลติดต่อลูกค้า” (บทที่ 5 P07) · สวิตช์ partner_api_contact_enabled เปิดอยู่ (บทที่ 2 A06 — บน prod ยังปิด) ขาดข้อใดข้อหนึ่งก็อ่านไม่ได้ ดูกรณีไม่มี DPA ที่ P09

ตัวอย่าง JSON/CSV/webhook ในคู่มือนี้ปลอดภัยแค่ไหน เอาไปใช้จริงได้ไหม

ใช้เป็นตัวอย่างโครงสร้างข้อมูลได้เลย แต่ค่าทุกตัว (UUID เวลา คีย์ ลายเซ็น) ถูกปิดบังแล้วตามที่บอกไว้ในบทที่ 7 ไม่ใช่ค่าที่เรียกได้จริงบนระบบจริง

เดโมบน prod (บทที่ 9) ปลอดภัยแค่ไหน กดพลาดแล้วกระทบร้านค้าจริงไหม

ปุ่มสร้างและปุ่มลบทำงานกับข้อมูลตัวอย่างของตัวเองเท่านั้น ปุ่มลบลบตามรายการที่ปุ่มสร้างบันทึกไว้ (องค์กร บัญชี ร้าน คีย์ webhook และลูกค้าตัวอย่าง) ไม่แตะสวิตช์ แผน ราคา หรือร้านจริงรายอื่นเลย และกดลบซ้ำได้อย่างปลอดภัย (ดูบทที่ 9 ข้อ 9.4)