ข้ามไปยังเนื้อหา

สมาชิก

สมาชิก คือคน 1 คนที่สมัครเข้าระบบสมาชิกของร้าน — ระดับสมาชิก · แต้มและเครดิตที่ใช้ได้ · คูปองในมือ และสรุปพฤติกรรมเล็กน้อย (มากี่ครั้ง · มาล่าสุดเมื่อไหร่) · นี่คือทรัพยากรที่ระบบ CRM · ระบบสะสมแต้ม หรือคลังข้อมูลวิเคราะห์ลูกค้า ใช้อ่าน

มี 3 เส้น:

  • GET /v1/members — สมาชิกของฐานสมาชิก 1 ฐาน ทีละหน้า
  • GET /v1/members/{memberToken} — สมาชิก 1 คนจากโทเคน
  • GET /v1/members/lookup — โทเคนสมาชิกที่อยู่เบื้องหลัง LINE user id ที่คุณถืออยู่แล้ว

ทั้ง 3 เส้นเป็นการอ่านอย่างเดียว

ระบบสมาชิกของ ScanFood ไม่ได้ผูกกับร้าน แต่ผูกกับ ฐานสมาชิก ที่ระบุด้วย providerId · หลายสาขาใช้ฐานเดียวกันเป็นเรื่องปกติ ⇒ ลูกค้าที่สมัครที่สาขาหนึ่ง จะถูกจำได้ที่ทุกสาขาที่ใช้ฐานนั้นร่วมกัน

นี่คือเหตุผลที่เส้นนี้กำหนดขอบเขตด้วย providerId ไม่ใช่ shopId

ระดับกุญแจ ฐานสมาชิกที่อ่านได้
shop providerId เดียวของร้านนั้น
franchise ทุก providerId ที่ไม่ซ้ำใต้แฟรนไชส์

providerId เป็นค่า บังคับ ของเส้นลิสต์ และต้องเป็นค่าที่กุญแจของคุณครอบถึง ไม่งั้นได้ 403 provider_not_in_scope · ค่าที่ใช้ได้ดูจาก GET /v1/shops

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

สมาชิกถูกระบุด้วย โทเคนแบบทึบ ที่ขึ้นต้นด้วย m1_ เสมอ:

m1_ZmQ3YjJkNGE1ZTZmNzg5MGExYjJjM2Q0ZTVmNjc4OTBhYmNkZWYxMjM

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

สิ่งที่คุณพึ่งพาได้:

คุณสมบัติ รายละเอียด
เสถียร คนเดิมได้โทเคนเดิมเสมอ ⇒ ใช้เป็นคีย์หลัก · คีย์ join หรือ cursor ได้
ทั้งระบบ โทเคนไม่ขึ้นกับว่ามองจากกุญแจใบไหน ร้านไหน หรือแฟรนไชส์ไหน — 2 กุญแจที่อ่านคนเดียวกันเห็นโทเคนเดียวกัน
ย้อนกลับไม่ได้ ถอดกลับไปเป็นตัวตนของลูกค้าไม่ได้
มีรุ่น m1_ คือเครื่องหมายรุ่น · ถ้าวันไหนมีการหมุนกุญแจเข้ารหัส โทเคนใหม่จะขึ้นต้น m2_

memberId บนบิลคือโทเคนตัวนี้เป๊ะ ⇒ การ join บิลกับสมาชิกไม่ต้องแปลงอะไรเลย:

// บิลจาก /v1/transactions
if (bill.memberId) {
const member = await getMember(bill.memberId); // /v1/members/{memberToken}
}

ถ้าคุณมี LINE user id ของลูกค้าอยู่แล้ว — เพราะเขากำลังคุยกับบอทของคุณ หรือเปิด LIFF ของคุณอยู่ — คุณแลกมันเป็นโทเคนสมาชิกของคนนั้นได้เลย โดยไม่ต้องถามอะไรลูกค้าเพิ่ม:

  • GET /v1/members/lookup — โทเคนสมาชิก 1 ตัว จาก LINE user id ที่คุณถืออยู่
GET /ext/v1/members/lookup?providerId=pv3Yh9DkQ2sLmT6RwX1B&lineUserId=U4af4980629…

คำตอบมีแค่โทเคน ไม่มีอย่างอื่น:

{
"ok": true,
"data": {
"memberId": "m1_ZmQ3YjJkNGE1ZTZmNzg5MGExYjJjM2Q0ZTVmNjc4OTBhYmNkZWYxMjM"
},
"serverTime": "2026-09-18T04:10:00.000Z"
}

เป็นโทเคนตัวเดียวกับที่ใช้ทุกที่ ⇒ เอาไปเรียก GET /v1/members/{memberToken} ต่อเพื่อดูระดับ แต้ม และคูปอง · และบิลทุกใบใน GET /v1/transactions ที่มี memberId ค่านั้นคือการซื้อของคนคนเดียวกัน · นี่คือเคส “ลูกค้าเพิ่งทักเข้ามา — เป็นสมาชิกไหม แล้วเป็นใครกับเรา”

สังเกตทิศทางของข้อมูล: คุณส่ง LINE user id ที่คุณมีอยู่แล้วเข้ามา แล้วได้โทเคนกลับไป · API ยังไม่ปล่อย LINE user id ออกไปเหมือนเดิม — ช่อง hasLine ยังเป็นสิ่งเดียวที่ response ของสมาชิกบอกเกี่ยวกับช่องทางนี้

การค้นนี้ ปิดไว้เป็นค่าตั้งต้น และเปิดให้ทีละกุญแจ แบบเดียวกับ tel และ email (ดูหัวข้อ “ความเป็นส่วนตัว”) · กุญแจที่ยังไม่ได้เปิดจะได้ 403 lookup_not_enabled ทุกครั้งที่เรียก · การเปิดเป็นการตัดสินใจเชิงพาณิชย์และเชิงกฎหมายเรื่องฐานการประมวลผล และความยินยอมตาม พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล ไม่ใช่แค่การกดสวิตช์ ⇒ ติดต่อผู้ดูแลของ ScanFood และเตรียมชี้แจงว่าจะเอาการค้นนี้ไปใช้ทำอะไร

พารามิเตอร์ บังคับ หมายเหตุ
providerId ใช่ ฐานสมาชิกที่จะค้น · ต้องอยู่ในขอบเขตกุญแจ ไม่งั้น 403 provider_not_in_scope
lineUserId ใช่ LINE user id ของลูกค้า · รูป U ตามด้วยเลขฐานสิบหก 32 ตัว
สถานะ error เกิดเมื่อ
400 bad_query providerId ว่าง หรือ lineUserId ไม่ได้อยู่ในรูป U + เลขฐานสิบหก 32 ตัว
403 provider_not_in_scope ฐานสมาชิกอยู่นอกขอบเขตที่กุญแจครอบ
403 lookup_not_enabled กุญแจของคุณยังไม่ได้เปิดสิทธิ์ค้น
404 not_found ไม่พบสมาชิกที่ตรงกัน
Terminal window
curl -s -G https://api.scanfood.co/ext/v1/members/lookup \
-H "Authorization: Bearer sf_live_4b8f2c1e9d07_3f9a2c1b7e4d8506a1b2c3d4e5f60718" \
--data-urlencode "providerId=pv3Yh9DkQ2sLmT6RwX1B" \
--data-urlencode "lineUserId=U4af4980629304a1b8c2d3e4f5a6b7c8d"

สมาชิกที่สมัครซ้ำหลายรอบจนมีมากกว่า 1 ระเบียนในฐานเดียวกัน — การค้นจะคืน ระเบียนที่สมัครล่าสุด

รูป response ของเส้นลิสต์:

{
"ok": true,
"data": [ { "…": "หนึ่งก้อนต่อหนึ่งคน" } ],
"nextCursor": "m1_…",
"serverTime": "2026-09-10T01:45:00.000Z"
}

เส้นสมาชิกรายคนคืนก้อนเดียวกันใต้ data และไม่มี nextCursor

ฟิลด์ ชนิด เป็น null ได้ไหม ความหมาย ตัวอย่าง
memberId string ได้ โทเคนสมาชิกแบบทึบ · ค่าเดียวกับ memberId บนบิล "m1_ZmQ3YjJkNGE1…"
rank string ได้ รหัสระดับที่บันทึกไว้บนตัวสมาชิก · ไม่มีบันทึก = null "GOLD"
rankLevel number ได้ ชั้นของสมาชิกในลิสต์ระดับของฐาน เป็นจำนวนเต็มนับจาก 0 · มีความหมายเฉพาะเมื่อฐานเปิดเลื่อนระดับอัตโนมัติ · ไม่มีชั้นที่ใช้ได้บันทึกไว้ = null 2
rankName string ได้ ชื่อระดับที่สมาชิกอยู่ตอนนี้ เทียบกับลิสต์ระดับปัจจุบันของฐาน · ไม่มีระดับ = null · ใช้ช่องนี้แสดงผล ไม่ใช่ rank "โกลด์"
points number ไม่ แต้มที่ใช้ได้ หลังคิดวันหมดอายุแล้ว — ไม่ใช่แต้มสะสมทั้งชีวิต 14097
credit number ไม่ เครดิตร้านที่ใช้ได้ หลังคิดอายุแล้ว · ฐานที่ไม่ได้เปิดระบบเครดิต = 0 200
creditEnabled boolean ไม่ ฐานสมาชิกนี้เปิดระบบเครดิตร้านไหม · อ่านช่องนี้ก่อนแสดง credit ให้ใครดู true
coupons array ไม่ (เป็น [] ได้) คูปองในมือสมาชิก (ดูหัวข้อ “คูปอง”) —
labels string[] ไม่ (เป็น [] ได้) ป้ายกำกับที่ร้านใส่ให้สมาชิก ["vip", "แพ้ถั่ว"]
persona string[] ไม่ (เป็น [] ได้) ป้ายบุคลิกที่ร้านใส่ให้ ["สายหวาน"]
createdDate string ได้ วันที่สมัครสมาชิก · ISO-8601 พร้อม offset +07:00 (เวลาไทย) — ฐานสมาชิกไม่มีเขตเวลาของตัวเอง เวลาของสมาชิกจึงเป็นเวลาไทยเสมอ "2026-03-04T16:15:00.000+07:00"
visitCount integer ไม่ จำนวนครั้งที่มาใช้บริการ · ตัวรายการไม่ถูกส่งออก 57
lastVisit string ได้ วันที่มาล่าสุด รูป YYYY-MM-DD · ไม่เคยมา = null "2026-09-07"
hasTel boolean ไม่ มีเบอร์โทรในระบบไหม — ไม่ใช่ตัวเบอร์ true
hasEmail boolean ไม่ มีอีเมลในระบบไหม false
hasLine boolean ไม่ ผูกบัญชีแชตไว้ไหม true

อีก 2 ฟิลด์จะโผล่ เฉพาะกุญแจที่ได้รับสิทธิ์ข้อมูลส่วนบุคคลไว้ชัดเจน (ดูหัวข้อ “ความเป็นส่วนตัว”):

ฟิลด์ ชนิด เป็น null ได้ไหม
tel string ได้
email string ได้

ช่องที่ จงใจไม่มี: ชื่อและชื่อเล่นของสมาชิก · เบอร์โทรและอีเมล (ถ้าไม่ได้รับสิทธิ์) · รหัสบัญชีแชต · วันเกิด · เพศ · รูปภาพ · ที่อยู่ · โน้ตข้อความอิสระ · บัตรที่บันทึกไว้ · ประวัติการมาใช้บริการแบบดิบ และรายการเคลื่อนไหวของแต้ม/เครดิตแบบดิบ · และ ไม่มีกลุ่มลูกค้าสำเร็จรูป (ลูกค้าประจำ · เสี่ยงหาย · ลูกค้าใหม่ ฯลฯ) เพราะกลุ่มพวกนี้ คำนวณเป็นรอบที่อื่น ไม่ได้ติดอยู่กับตัวสมาชิก ⇒ คำนวณเองจาก visitCount · lastVisit และประวัติการซื้อ

coupons[] คือคูปองที่อยู่ในมือสมาชิก · แต่ละใบ:

ฟิลด์ ชนิด เป็น null ได้ไหม ความหมาย
code string ได้ รหัสสั้นที่สมาชิกโชว์ที่เคาน์เตอร์ · ไม่ซ้ำต่อใบ
status "available" | "used" ได้ "available" = ยังใช้ได้ · "used" = ใช้ไปแล้ว
templateId string ได้ แคตตาล็อกคูปองที่ใบนี้ออกมาจาก · หลายใบใช้ templateId เดียวกันได้
name string ได้ ชื่อคูปองตามที่สมาชิกเห็น
expireAt string ได้ วันหมดอายุ · ISO-8601 พร้อม offset +07:00 (เวลาไทย) เหมือนเวลาอื่นของสมาชิก · ไม่มีวันหมดอายุ = null

2 เรื่องที่ควรวางแผนไว้:

  • คูปองที่ status: "used" ยังอยู่ในรายการ ⇒ กรองด้วย status ก่อนแสดงคำว่า “คูปองที่ใช้ได้”
  • status บอกเรื่อง การใช้ไปแล้วหรือยัง ไม่ได้บอกเรื่องหมดอายุ ⇒ ให้ถือ expireAt เป็นอีกเงื่อนไขหนึ่ง และเทียบกับ serverTime ไม่ใช่นาฬิกาบนเครื่องของคุณเอง

คูปองถูกอ่านภายในฐานสมาชิกที่คุณขอเสมอ ⇒ คูปองของแบรนด์อื่นไม่มีทางหลุดเข้ามา

ฝั่งสมาชิก ไม่มีโหมดดึงเฉพาะส่วนที่เปลี่ยน — updatedSince ถูกปัดด้วย 400 not_supported

⇒ ให้ไล่อ่านทั้งฐานด้วย cursor

พารามิเตอร์ บังคับ ค่าตั้งต้น หมายเหตุ
providerId ใช่ — ต้องอยู่ในขอบเขตกุญแจ ไม่งั้น 403 provider_not_in_scope
limit ไม่ 100 เกิน 200 ถูกลดเหลือ 200 · ค่าที่ไม่ถูกต้องตกกลับไปที่ 100
cursor ไม่ — ค่า nextCursor ของหน้าก่อน — เป็นโทเคน m1_…
updatedSince — — ไม่รองรับ — 400 not_supported

สมาชิกถูกเรียงตาม วันสมัคร จากเก่าไปใหม่ ⇒ การไล่อ่านทั้งฐานจึงนิ่ง: แถวที่ส่งไปแล้ว จะไม่กระโดดแซงหน้าคุณระหว่างไล่ · หน้าสุดท้าย nextCursor = null

Terminal window
# หน้าแรก
curl -s -G https://api.scanfood.co/ext/v1/members \
-H "Authorization: Bearer sf_live_4b8f2c1e9d07_3f9a2c1b7e4d8506a1b2c3d4e5f60718" \
--data-urlencode "providerId=pv3Yh9DkQ2sLmT6RwX1B" \
--data-urlencode "limit=200"
# หน้าถัดไป — ส่ง nextCursor ที่ได้จาก response ข้างบน
curl -s -G https://api.scanfood.co/ext/v1/members \
-H "Authorization: Bearer sf_live_4b8f2c1e9d07_3f9a2c1b7e4d8506a1b2c3d4e5f60718" \
--data-urlencode "providerId=pv3Yh9DkQ2sLmT6RwX1B" \
--data-urlencode "limit=200" \
--data-urlencode "cursor=m1_ZmQ3YjJkNGE1ZTZmNzg5MGExYjJjM2Q0ZTVmNjc4OTBhYmNkZWYxMjM"

ถ้า cursor ชี้ไปที่สมาชิกที่ถูกลบไปแล้ว คุณจะได้ 400 bad_cursor ⇒ ให้เริ่มหน้านั้นใหม่ โดยไม่ส่ง cursor แทนที่จะถือว่าเป็น error ร้ายแรง

{
"ok": true,
"data": [
{
"memberId": "m1_ZmQ3YjJkNGE1ZTZmNzg5MGExYjJjM2Q0ZTVmNjc4OTBhYmNkZWYxMjM",
"rank": "GOLD",
"rankLevel": 2,
"rankName": "โกลด์",
"points": 14097,
"credit": 200,
"creditEnabled": true,
"coupons": [
{
"code": "ABC123",
"status": "available",
"templateId": "cp7Vb2NqZ5tMx8LwR3Kd",
"name": "ส่วนลด 50 บาท",
"expireAt": "2026-12-31T23:59:59.000+07:00"
},
{
"code": "XYZ789",
"status": "used",
"templateId": "cpQ4mL8vT2zNb6RwK9Ys",
"name": "ฟรีชาเย็น",
"expireAt": null
}
],
"labels": ["vip", "แพ้ถั่ว"],
"persona": ["สายหวาน"],
"createdDate": "2026-03-04T16:15:00.000+07:00",
"visitCount": 57,
"lastVisit": "2026-09-07",
"hasTel": true,
"hasEmail": false,
"hasLine": true
}
],
"nextCursor": null,
"serverTime": "2026-09-10T01:45:00.000Z"
}

ข้อมูลสมาชิกคือข้อมูลส่วนบุคคล · API นี้ถูกออกแบบให้กุญแจ ได้ข้อมูลเกินกว่าที่ให้ไว้ ไม่ได้แม้โดยอุบัติเหตุ

ค่าตั้งต้นคือไม่ส่งข้อมูลติดต่อออกเลย — คุณได้ hasTel · hasEmail · hasLine ซึ่งพอให้รู้ว่ามีช่องทางติดต่อไหมและแบ่งกลุ่มได้ แต่ไม่ได้ตัวค่า

เบอร์โทรและอีเมลเปิดได้ รายกุญแจ และเปิดได้แค่ 2 ช่องนี้เท่านั้น ไม่มีสวิตช์ไหน ที่เปิดของอย่างอื่นได้ · มีอีกธงหนึ่งแยกต่างหากที่เปิดการ ค้นสมาชิกจาก LINE user id ⇒ ดูให้ชัดว่าธงนั้นทำอะไร และไม่ทำอะไร: มันให้กุญแจ ส่งเข้ามา ซึ่ง LINE user id ที่ผู้เรียกมีอยู่แล้ว แล้วรับโทเคนสมาชิกกลับไป · มันไม่ได้เพิ่มรหัสบัญชีแชตเข้าไปใน response ใด ๆ ⇒ ไม่มีกุญแจระดับไหนอ่านรหัสบัญชีแชตออกจาก ScanFood ได้ · การเปิดเป็นการตัดสินใจเชิงพาณิชย์และเชิงกฎหมายเรื่องฐานการ ประมวลผลและความยินยอมตาม พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล ไม่ใช่แค่การกดสวิตช์ ⇒ ติดต่อผู้ดูแลของ ScanFood และเตรียมชี้แจงว่าจะใช้ข้อมูลทำอะไร

ที่เหลือทั้งหมดเกี่ยวกับตัวบุคคล อยู่ในระบบ ScanFood ถาวร:

กลุ่ม ไม่มีให้ไม่ว่ากุญแจระดับไหน
ตัวตน ชื่อ · ชื่อเล่น · รหัสบัญชีแชต · วันเกิด · เพศ · รูปภาพ · ที่อยู่
ข้อความอิสระ โน้ตที่พนักงานเขียนถึงสมาชิก
ประวัติดิบ ประวัติการมาใช้บริการเต็ม (พร้อมรหัสบิลและยอดรายครั้ง) · การเคลื่อนไหวแต้ม · การเคลื่อนไหวเครดิต · ประวัติโปรโมชัน
เครื่องมือชำระเงิน บัตรที่บันทึกไว้
ภายใน ข้อมูลการแก้ไข · ตัวนับยอดใช้จ่าย · ข้อมูลการเลื่อนระดับ