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

ร้านค้า

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

เส้นที่ใช้มีเส้นเดียว: GET /v1/shops

ข้อมูลร้านในเส้นนี้ตั้งใจให้บางที่สุด — มันคือ สารบัญ ไม่ใช่โปรไฟล์ร้าน: มีแค่พอให้ระบุสาขา · รู้ว่าอยู่ใต้แฟรนไชส์ไหน · ชี้ไปที่ฐานสมาชิกของสาขานั้น บอกว่าวันที่ของสาขานี้นับตามนาฬิกาเรือนไหน และบอกชื่อหมวดที่บิลของสาขานี้อ้างถึง

ถ้าคุณต้องการ… ใช้
รู้ว่ากุญแจใบนี้อ่านสาขาไหนได้บ้าง shopId ของทุกแถว
ดึงยอดขายของสาขา shopId → GET /v1/transactions
ดึงฐานสมาชิกที่สาขานั้นใช้ providerId → GET /v1/members
จัดกลุ่มสาขาตามกิจการ franchiseId
ตีความ from / to และ billDate timezone
แปลง items[].category บนบิลเป็นชื่อหมวด categories

ข้อมูลอื่นที่สาขาถืออยู่ — บัญชีพนักงาน · กุญแจระบบส่งข้อความ · การตั้งค่าเกตเวย์รับชำระ · ที่อยู่ · เบอร์ติดต่อ — ไม่ออกจากระบบ ScanFood เลย เพราะเซิร์ฟเวอร์อ่านขึ้นมาเฉพาะช่องเหล่านี้ จากฐานข้อมูล คือชื่อสาขา (2 รูปที่เอกสารร้านใช้เรียกได้) · franchiseId · providerId · timezone · รายชื่อหมวด ⇒ ที่เหลือหลุดออกไปไม่ได้แม้โดยอุบัติเหตุ · และแม้ในรายชื่อหมวด ก็ส่งต่อแค่ 5 ช่องตามหัวข้อ หมวดสินค้า · ส่วน shopId ไม่ได้เป็นช่องที่อ่านขึ้นมา เพราะมันคือรหัสของตัวเอกสารเอง ไม่ใช่ช่องที่เก็บอยู่ข้างใน

กุญแจของคุณเป็นตัวกำหนดว่าเห็นร้านไหน · คำขอขยายขอบเขตเองไม่ได้ — เส้นนี้ไม่มีพารามิเตอร์ shopId ให้ส่ง และไม่มีวิธีขอสาขาที่กุญแจไม่ได้รับสิทธิ์

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

ถ้าสาขาถูกให้สิทธิ์กับกุญแจไว้แต่ข้อมูลสาขานั้นถูกลบไปแล้ว แถวนั้นจะถูกข้ามเฉย ๆ คุณจะได้รายการที่สั้นลง ไม่ใช่ error

รูปของ response:

{
"ok": true,
"data": [ { "…": "หนึ่งก้อนต่อหนึ่งร้าน" } ],
"serverTime": "2026-09-09T13:45:12.310Z"
}

serverTime คือนาฬิกาของเซิร์ฟเวอร์ในรูป UTC (ลงท้าย Z) และมีอยู่ในทุก response ที่สำเร็จ · เส้นนี้ ไม่มี nextCursor — รายชื่อร้านไม่แบ่งหน้า คุณได้ทุกสาขา ในขอบเขตกุญแจครบในคำขอเดียวเสมอ

แต่ละก้อนใน data:

ฟิลด์ ชนิด เป็น null ได้ไหม ความหมาย ตัวอย่าง
shopId string ไม่ รหัสสาขา · ค่านี้คือค่าที่ส่งเป็น shopId ให้เส้นรายการขาย "sh7Kq2mVbN4tRxZ0Lp8W"
shopName string ได้ ชื่อสาขาตามที่เจ้าของร้านพิมพ์ไว้ "ร้านตัวอย่าง สาขาสีลม"
franchiseId string ได้ แฟรนไชส์ที่สาขานี้สังกัด · ร้านเดี่ยว = null "fr5Ns8CtJ4vHqZ2WbY7K"
providerId string ได้ ฐานสมาชิกที่สาขานี้ใช้ · ไม่ได้ทำระบบสมาชิก = null "pv3Yh9DkQ2sLmT6RwX1B"
timezone string ได้ โซนเวลาแบบ IANA ของสาขา · วันแบบปฏิทิน (from · to · billDate) คือวันบนนาฬิกาเรือนนี้ "Asia/Bangkok"
categories array ไม่ (เป็น [] ได้) หมวดสินค้าของสาขาแบบรายการแบน — ดู หมวดสินค้า · สาขาที่ไม่มีหมวด = [] [{ "id": "9369…", "name": "อาหาร", "level": 1, "parentId": null, "hidden": false }]

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

บรรทัดสินค้าบนบิลมี items[].category คือรหัสหมวดที่สินค้าอยู่ ณ เวลาที่ขาย เรียงจากหมวดบนสุดลงไป (ดู รายการขาย) · categories คือที่ที่รหัสเหล่านั้นได้ชื่อ · เป็นรายการแบน เรียงตาม level (หมวดบนสุดก่อน) แล้วตามลำดับที่ร้านจัดหมวดไว้

ฟิลด์ ชนิด เป็น null ได้ไหม ความหมาย ตัวอย่าง
id string ไม่ รหัสหมวด — ค่าเดียวกับที่อยู่ใน items[].category "eff254c9-3d71-4d6f-9e43-1806ece6a427"
name string ได้ ชื่อหมวดตามที่ร้านตั้ง (ภาษาของร้านเอง) · ร้านเว้นว่าง = null "ก๋วยเตี๋ยว"
level integer ได้ ความลึก: 1 = หมวดบนสุด · 2 = อยู่ใต้หมวดบนสุดโดยตรง ไล่ลงไปเรื่อย ๆ 2
parentId string ได้ รหัสหมวดที่อยู่เหนือขึ้นไปหนึ่งชั้น · หมวดบนสุด = null "9369601f-fef5-4a61-bfb8-26f25eab3dad"
hidden boolean ไม่ true เมื่อร้านซ่อนหมวดนี้จากทุกช่องทางขาย false

สิ่งที่ต้องรู้ตอนใช้:

  • ส่งทุกหมวด รวมหมวดที่ซ่อน — ร้านซ่อนหมวดวันนี้ได้ แต่บิลเก่ายังอ้างรหัสนั้นอยู่ ⇒ หมวดที่ซ่อน ยังอยู่ในรายการพร้อม hidden: true · ถ้าต้องการเฉพาะหมวดที่ขายอยู่ตอนนี้ ให้กรอง hidden เอง
  • หมวดที่ถูกลบแล้วคือหายไปแล้ว — รหัสบนบิลที่ไม่มีใน categories คือหมวดที่ร้านลบไปแล้ว และกู้ชื่อคืนไม่ได้ · parentId ที่ชี้ไปรหัสที่ไม่อยู่ในรายการก็เช่นกัน ⇒ รายงานเป็น “ไม่ระบุหมวด” (หรือเก็บรหัสดิบไว้) อย่าให้ระบบล้ม
  • ชื่อเป็นของปัจจุบัน รหัสเป็นของ ณ วันขาย — categories คือรายการของวันนี้ ⇒ หมวดที่เปลี่ยนชื่อ จะโชว์ชื่อใหม่แม้บนบิลเก่า · ซึ่งมักเป็นสิ่งที่ต้องการสำหรับรายงาน · ถ้าต้องการชื่อตามวันที่ขาย ให้เก็บชื่อไว้คู่กับบิลเอง
  • ชั้นโปรโมชันไม่ใช่หมวด — ชั้น “ขายดี” “โปรโมชัน” “แนะนำ” บนเมนูของร้านไม่เคยอยู่ใน items[].category และไม่อยู่ในรายการนี้
  • ส่งเฉพาะชื่อที่ร้านพิมพ์ไว้ · ไม่ส่งชื่อแปลภาษาอื่นและรูปของหมวด

การแปลงบรรทัดบิลเป็นชื่อ = เอาแต่ละรหัสไปหาในรายการของสาขานั้น · รหัสตัวท้ายใน items[].category คือหมวดที่เจาะจงที่สุด · ทั้ง array คือเส้นทางจากหมวดบนสุดลงมา

// shops = data จาก GET /v1/shops
const categoryName = new Map();
for (const shop of shops) {
for (const c of shop.categories) categoryName.set(`${shop.shopId}:${c.id}`, c.name);
}
function categoryPath(bill, item) {
// เช่น ["อาหาร", "ก๋วยเตี๋ยว"] — หมวดที่ถูกลบไปแล้วได้ null
return (item.category || []).map((id) => categoryName.get(`${bill.shopId}:${id}`) ?? null);
}

รหัสหมวดไม่ซ้ำกันภายในร้าน แต่ให้หาแยกตาม shopId แบบข้างบน — สาขาในแฟรนไชส์ถือรายชื่อหมวดของตัวเอง

ดึงสารบัญมาก่อน แล้วค่อยกระจายงานต่อ

Terminal window
curl -s https://api.scanfood.co/ext/v1/shops \
-H "Authorization: Bearer sf_live_4b8f2c1e9d07_3f9a2c1b7e4d8506a1b2c3d4e5f60718"

ตัวอย่าง response ของกุญแจแฟรนไชส์ที่มี 2 สาขา:

{
"ok": true,
"data": [
{
"shopId": "sh7Kq2mVbN4tRxZ0Lp8W",
"shopName": "ร้านตัวอย่าง สาขาสีลม",
"franchiseId": "fr5Ns8CtJ4vHqZ2WbY7K",
"providerId": "pv3Yh9DkQ2sLmT6RwX1B",
"timezone": "Asia/Bangkok",
"categories": [
{ "id": "9369601f-fef5-4a61-bfb8-26f25eab3dad", "name": "อาหาร", "level": 1, "parentId": null, "hidden": false },
{ "id": "3c1d7e52-8a0b-4f6e-9d21-5b7a0c4e9f13", "name": "เครื่องดื่ม", "level": 1, "parentId": null, "hidden": true },
{ "id": "eff254c9-3d71-4d6f-9e43-1806ece6a427", "name": "ก๋วยเตี๋ยว", "level": 2, "parentId": "9369601f-fef5-4a61-bfb8-26f25eab3dad", "hidden": false }
]
},
{
"shopId": "shB3xW9pL2knT6ZqR4Vd",
"shopName": "ร้านตัวอย่าง สาขาอโศก",
"franchiseId": "fr5Ns8CtJ4vHqZ2WbY7K",
"providerId": "pv3Yh9DkQ2sLmT6RwX1B",
"timezone": "Asia/Bangkok",
"categories": []
}
],
"serverTime": "2026-09-09T13:45:12.310Z"
}

2 สาขานี้ชี้ไปที่ providerId เดียวกัน ⇒ ซิงก์ฐานสมาชิกนั้น ครั้งเดียว ไม่ใช่ 2 รอบ