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

การแบ่งหน้าและการซิงก์

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

ช่อง ทิศทาง คืออะไร
limit คำขอ จำนวนแถวต่อหน้า ค่าตั้งต้น 100 สูงสุด 200
cursor คำขอ ค่า nextCursor ของหน้าก่อน หน้าแรกไม่ต้องส่ง
nextCursor ผลลัพธ์ cursor ของหน้าถัดไป หรือ null เมื่อได้ครบแล้ว

กติกาที่ตามมาจากตารางนี้

  • limit ถูกแก้ให้ ไม่ถูกปฏิเสธ ค่าที่เกิน 200 จะถูกลดเหลือ 200 และค่าที่ไม่ใช่จำนวนบวกจะตกกลับไปเป็น 100 คุณจะไม่ได้ 400 จากเรื่องนี้ จึงควรตรวจว่าได้มากี่แถวจริง
  • cursor เป็นค่าทึบ เก็บเป็นสตริงแล้วส่งกลับไปเหมือนเดิม อย่าแกะและอย่าสร้างเอง ในเส้นฝั่งสมาชิก cursor คือ token รูป m1_… การส่งรหัสดิบไปจะได้ 400 bad_cursor
  • “หน้าเต็ม” แปลว่า “ถามต่อ” ระบบจะยื่น cursor ถัดไปให้ทุกครั้งที่หน้านั้นเต็มพอดี ถ้าหน้าสุดท้ายบังเอิญเต็มพอดี คุณจะได้อีกหนึ่งคำขอที่คืน "data": [] พร้อม "nextCursor": null ซึ่งเป็นเรื่องปกติ ไม่ใช่ข้อผิดพลาด
  • ไม่มียอดรวม ไม่มี hasMore ไม่มีจำนวนแถวทั้งหมด ไม่มี page และไม่มี offset เงื่อนไขหยุดมีอย่างเดียวคือ nextCursor === null
  • GET /v1/shops ไม่แบ่งหน้าเลย คืนทุกสาขาที่กุญแจอ่านได้ในคำตอบเดียว

ลูปเดียวกันใช้ได้ทั้งฝั่งบิลและฝั่งสมาชิก ต่างกันแค่พารามิเตอร์

Terminal window
# หน้าแรก
curl -s -G https://api.scanfood.co/ext/v1/transactions \
-H "Authorization: Bearer $SCANFOOD_API_KEY" \
--data-urlencode "shopId=aK3mP9xQ2bR7cT4dV6wY" \
--data-urlencode "from=20260801" --data-urlencode "to=20260831" \
--data-urlencode "limit=200"
# หน้าถัดไป ส่ง nextCursor ที่เพิ่งได้กลับไป
curl -s -G https://api.scanfood.co/ext/v1/transactions \
-H "Authorization: Bearer $SCANFOOD_API_KEY" \
--data-urlencode "shopId=aK3mP9xQ2bR7cT4dV6wY" \
--data-urlencode "from=20260801" --data-urlencode "to=20260831" \
--data-urlencode "limit=200" \
--data-urlencode "cursor=dN6pS2aT5eU0fW7gY9zB"

ให้พารามิเตอร์ทุกตัวเหมือนเดิมตลอดการไล่หน้าของผลลัพธ์ชุดหนึ่ง ถ้าเปลี่ยนช่วงวันหรือเปลี่ยน limit กลางทาง cursor ที่ถืออยู่จะกลายเป็นของคำขอคนละชุด

เมื่อมีสำเนาครบชุดแล้ว ให้ถามเฉพาะสิ่งที่เปลี่ยน

ฝั่งบิล รับ updatedSince ซึ่งเป็นเวลาแบบ ISO-8601 แถวจะเรียงตามเวลาที่ถูกแก้ล่าสุด ดังนั้นใบกำกับภาษีที่ออกใหม่ บิลที่ถูกยกเลิก และช่องที่ถูกแก้ จะกลับมาให้เห็นทั้งหมด

Terminal window
curl -s -G https://api.scanfood.co/ext/v1/transactions \
-H "Authorization: Bearer $SCANFOOD_API_KEY" \
--data-urlencode "shopId=aK3mP9xQ2bR7cT4dV6wY" \
--data-urlencode "updatedSince=2026-09-09T04:00:00.000Z" \
--data-urlencode "limit=200"

ใช้ค่า serverTime ของรอบที่สำเร็จครั้งล่าสุดเป็น updatedSince ของรอบถัดไป และเลื่อนค่านี้ต่อเมื่อเก็บข้อมูลครบทุกหน้าของรอบนั้นแล้ว การเผื่อย้อนหลังสักหนึ่งนาที ไม่มีต้นทุนอะไร เพราะทุกแถวมี id ที่เสถียร การอ่านซ้ำจึงเป็นการอัปเดต ไม่ใช่ข้อมูลซ้ำ

from/to กับ updatedSince เป็นสองโหมดของเส้นเดียวกัน และต้องเลือกโหมดเดียวต่อคำขอ ส่งมาทั้งคู่หรือไม่ส่งเลย จะได้ 400 bad_query

ทำครั้งเดียวต่อร้าน แล้วสลับไปโหมดเพิ่มทีละรอบ

  1. ดูรายชื่อสาขา GET /v1/shops ให้ shopId ทุกตัวที่กุญแจอ่านได้
  2. ไล่ปฏิทินเป็นช่วงละไม่เกิน 31 วัน ช่วงที่กว้างกว่านั้นจะถูกปฏิเสธด้วย 400 bad_query (range too wide) ช่วงละหนึ่งเดือนคิดตามได้ง่ายที่สุด
  3. ไล่หน้าของแต่ละช่วงให้ครบ ด้วย limit=200 แล้วตาม nextCursor ไปเรื่อย ๆ
  4. บันทึกค่า serverTime ของช่วงสุดท้ายที่สำเร็จ เวลานั้นคือจุดเริ่มต้นของรอบเพิ่มทีละรอบครั้งแรก
  5. ฝั่งสมาชิกทำแยก ไล่หน้า GET /v1/members ทีละ providerId หนึ่งแบรนด์อาจถูกใช้ร่วมกันหลายสาขา จึงควรตัดรหัสซ้ำจากขั้นที่ 1 ออกก่อนเริ่ม

API รับประกันว่าการอ่านไม่เปลี่ยนอะไร แต่ที่เก็บข้อมูลฝั่งคุณต้องทนได้เมื่อแถวเดิมมาถึงสองครั้ง

  • บันทึกแบบ upsert ด้วย id สำหรับบิล และด้วย memberId สำหรับสมาชิก ทั้งคู่เสถียรและไม่ถูกนำกลับมาใช้ซ้ำ
  • ถือว่า status: "void" คือการอัปเดต ไม่ใช่การลบ บิลที่คุณเก็บไว้แล้วอาจกลับมาเป็นบิลที่ถูกยกเลิก ให้เก็บแถวนั้นไว้แล้วทำเครื่องหมาย ยอดของคุณกับยอดของร้านจะได้ตรงกัน
  • เก็บ updatedAt ไว้ข้างบิลด้วย ถ้ามีแถวมาถึงพร้อม updatedAt ที่เก่ากว่าที่คุณถืออยู่ แปลว่าเป็นข้อมูลซ้ำ ให้ข้ามไป
  • เลื่อนหมุดเวลาต่อเมื่อรอบนั้นสำเร็จ การเก็บแถวและค่า updatedSince ใหม่ ในธุรกรรมเดียวกัน ช่วยไม่ให้เกิดช่องโหว่เมื่องานตายกลางคัน
  • เตรียมรับค่า null ไม่ใช่ช่องที่หายไป ช่องที่ไม่เกี่ยวกับบิลนั้นจะมาเป็น null คอลัมน์ที่อยู่ ๆ ว่างจึงเป็นข้อมูล ไม่ใช่การเปลี่ยนโครงสร้าง