วิธีใช้ In-Store Media Display API (Display API)
วัตถุประสงค์ของคู่มือนี้
รวบรวมขั้นตอนการสร้างแอปที่เล่นมีเดียลูป (ภาพ・วิดีโอ) บนเครื่อง Android หน้าร้าน โดยใช้ In-Store Media Display API (Display API) ของ ReceiptRoller คู่มือนี้สำหรับการเผยแพร่รีเทลมีเดียของ ReceiptRoller หรือวิดีโอแคมเปญ บนเครื่องแบบ POS ควบคู่ (kiosk) หรือเครื่องไซเนจเฉพาะทาง (แท็บเล็ตแขวนผนัง ฯลฯ)
เงื่อนไขเบื้องต้น
- ไม่ได้ออกแบบให้เปิดเบราว์เซอร์เพื่อทำโฟลว์ OAuth ที่ฝั่งเครื่อง Android แต่ละเครื่องจะยืนยันตัวตนด้วยโทเคนอุปกรณ์ที่มีอายุยาว
- โทเคนอุปกรณ์รับได้จากกระบวนการ "จับคู่ (pairing)" เมื่อป้อนโค้ด 6 หลักที่เจ้าของร้านสร้างในหน้าจัดการลงในแอป Android จะมีการออกโทเคนของแต่ละเครื่อง
- Base URL:
https://receiptroller.io
รายการเอนด์พอยต์
| เอนด์พอยต์ | วัตถุประสงค์ | การยืนยันตัวตน |
|---|---|---|
POST /api/v1/displays/pair | แลกโค้ดจับคู่ 6 หลักเป็นโทเคนอุปกรณ์ | ไม่จำเป็น (โค้ดคือการยืนยันตัวตน) |
POST /api/v1/displays/{displayId}/heartbeat | แจ้งการมีชีวิตของเครื่อง (สำหรับตัดสินออนไลน์/ออฟไลน์) | โทเคนอุปกรณ์ |
GET /api/v1/displays/{displayId}/playlist | ดึงรายการมีเดียที่ควรเล่นพร้อมลำดับ | โทเคนอุปกรณ์ |
POST /api/v1/displays/{displayId}/impressions | รายงานผลการเล่น (เพลย์) แบบแบตช์ | โทเคนอุปกรณ์ |
โฟลว์การจับคู่
แต่ละเครื่องจับคู่เพียงครั้งเดียว โทเคนจะถูกเก็บถาวรที่ฝั่ง Android และแนบในเฮดเดอร์ Authorization: Bearer {token} ในทุกคำขอต่อจากนั้น
- เมื่อเจ้าของร้านลงทะเบียนดิสเพลย์ใหม่ในหน้าจัดการ จะแสดงโค้ดจับคู่ 6 หลัก (อายุ 10 นาที・ใช้ได้ครั้งเดียว)
- ป้อนโค้ดในหน้าจอเปิดใช้งานครั้งแรกของแอป Android
- Android เรียก
POST /api/v1/displays/pairและรับโทเคนกับdisplayId - Android เก็บโทเคนและ displayId ลงในเครื่องแบบถาวร (แนะนำให้เข้ารหัส)
ตัวอย่างคำขอ:
POST /api/v1/displays/pair
Content-Type: application/json
User-Agent: RRDisplay/1.0 (Android 14; Pixel Tablet)
{
"code": "123456",
"screenWidth": 1920,
"screenHeight": 1080,
"supportsVideo": true,
"supportsAudio": false
}
ตัวอย่างเรสปอนส์ (200):
{
"displayId": "a1b2c3d4-...",
"organizationId": "7d325d1d-...",
"deviceToken": "dvc_3f9c1a8b7e2d6f4a1b8c5d2e9f7a3b6c"
}
เรสปอนส์ข้อผิดพลาด:
400 missing_code— ไม่ได้ระบุฟิลด์ code401 invalid_or_expired_code— โค้ดผิด หรือเกินอายุ 10 นาที หรือถูกใช้ไปแล้ว
สำคัญ:
- โทเคนรับได้จากเรสปอนส์เท่านั้น เนื่องจากฝั่งเซิร์ฟเวอร์เก็บเพียงแฮชไว้ หากทำสูญหาย ให้ลบดิสเพลย์ในหน้าจัดการและจับคู่ใหม่
- โค้ดจับคู่หนึ่งอันใช้ได้เพียงครั้งเดียว เมื่อจับคู่ใหม่ กรุณาออกโค้ดใหม่
Heartbeat
แจ้งเซิร์ฟเวอร์ว่าเครื่องออนไลน์อยู่ การแสดง "ออนไลน์/ออฟไลน์" ในหน้าจัดการอิงตามไทม์สแตมป์นี้ (หากไม่เกิน 5 นาทีนับจาก heartbeat ล่าสุด ถือว่าออนไลน์)
POST /api/v1/displays/{displayId}/heartbeat
Authorization: Bearer dvc_3f9c1a8b...
User-Agent: RRDisplay/1.0 (Android 14; Pixel Tablet)
ตัวอย่างเรสปอนส์ (200):
{
"ok": true,
"serverTimeUtc": "2026-06-03T05:21:30Z",
"nextHeartbeatSeconds": 60
}
ช่วงเวลาเรียกที่แนะนำ: กรุณาทำตาม nextHeartbeatSeconds ในเรสปอนส์ (ปัจจุบัน 60 วินาที) เนื่องจากฝั่งเซิร์ฟเวอร์อาจเปลี่ยนค่าตามภาระการโพลลิง จึงแนะนำให้อ้างอิงเรสปอนส์แทนการฮาร์ดโค้ด
การดึงเพลย์ลิสต์
เครื่องดึงรายการมีเดียที่ควรเล่นพร้อมลำดับ แต่ละไอเทมมี URL เข้าถึงโดยตรงที่เซ็น SAS แล้ว (อายุ 1 ชั่วโมง)
GET /api/v1/displays/{displayId}/playlist
Authorization: Bearer dvc_3f9c1a8b...
ตัวอย่างเรสปอนส์ (200):
{
"displayId": "a1b2c3d4-...",
"generatedAt": "2026-06-03T05:21:30Z",
"pollIntervalSeconds": 60,
"items": [
{
"creativeId": "creative-001",
"name": "夏の新商品キャンペーン",
"type": "Image",
"mediaUrl": "https://strprdomnicon.blob.core.windows.net/creatives/...?sv=...&sig=...",
"durationSeconds": 10,
"hashHint": "creative-001_638549812340000000"
},
{
"creativeId": "creative-002",
"name": "新メニュー紹介動画",
"type": "Video",
"mediaUrl": "https://strprdomnicon.blob.core.windows.net/creatives/...?sv=...&sig=...",
"durationSeconds": 30,
"hashHint": "creative-002_638549812350000000"
}
]
}
คู่มือการใช้งาน:
- ลำดับการเล่น — กรุณาเล่นตามลำดับในอาร์เรย์
itemsและเมื่อเล่นถึงตัวสุดท้ายแล้วให้วนกลับไปเล่นตัวแรก - แคช — ตราบใดที่
hashHintไม่เปลี่ยน ไฟล์มีเดียไม่จำเป็นต้องดาวน์โหลดซ้ำ แนะนำให้แคชในเครื่อง เมื่อhashHintเปลี่ยน จึงดึงใหม่ - อายุของ SAS URL —
mediaUrlมีอายุ 1 ชั่วโมง หากพยายามดาวน์โหลดด้วย URL ที่หมดอายุจะได้ 403 กรุณาดึงเพลย์ลิสต์ใหม่ทุกช่วงpollIntervalSecondsเพื่อรับ URL ใหม่ - durationSeconds — ภาพคือ 10 วินาที (ค่าเริ่มต้น) วิดีโอคือความยาวจริง แอปควรสลับไปไอเทมถัดไปตามจำนวนวินาทีที่ระบุ
- เพลย์ลิสต์ว่าง — หากฝั่งเจ้าของยังไม่อนุมัติครีเอทีฟ
itemsจะเป็นอาร์เรย์ว่าง เมื่อว่าง แนะนำให้แสดงจอดำหรือพเลสโฮลเดอร์ "กำลังเตรียมการ"
การรายงานผลการเล่น (Impression)
ส่งข้อมูล "เล่นครีเอทีฟไหน・เมื่อไหร่・นานเท่าไร" จาก Android ไปยังเซิร์ฟเวอร์แบบแบตช์ ต่อ 1 สล็อตที่เล่น Phase 3 / t-e9a2f501
POST /api/v1/displays/{displayId}/impressions
Authorization: Bearer dvc_3f9c1a8b...
Content-Type: application/json
{
"events": [
{
"eventId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"creativeId": "creative-001",
"campaignId": "camp-001",
"placementId": "place-001",
"playedAtUtc": "2026-06-03T05:20:00Z",
"durationPlayedMs": 10000,
"completed": true
},
{
"eventId": "b2c3d4e5-f678-9012-3456-7890abcdef01",
"creativeId": "creative-002",
"campaignId": "camp-001",
"placementId": "place-001",
"playedAtUtc": "2026-06-03T05:20:10Z",
"durationPlayedMs": 30000,
"completed": true
}
]
}
ตัวอย่างเรสปอนส์ (200):
{
"accepted": 2,
"duplicates": 0,
"rejected": 0,
"errors": []
}
คู่มือการใช้งาน:
- ความถี่ในการส่ง — แนะนำให้ส่งทุก 60 วินาที การส่งต่อทุกสล็อตที่เล่นจะทำให้เกิดภาระเครือข่ายมากเกินไป
- ขีดจำกัดแบตช์ — สูงสุด 500 รายการต่อคำขอ หากเกินจะได้
400 batch_too_large - eventId สร้างโดยไคลเอนต์ — แนะนำ UUID v4 ฝั่งเซิร์ฟเวอร์ใช้ ID นี้เพื่อรับประกันความเป็นไอเดมโพเทนต์ (idempotency) แม้ส่งแบตช์เดิมซ้ำหลังออฟไลน์ เซิร์ฟเวอร์จะนับครั้งที่สองเป็น
duplicatesอย่างเงียบ ๆ - แฟลก completed — หากเล่นสล็อตจนจบเป็น
trueหากสลับไปตัวถัดไปกลางคันเป็นfalseใช้สำหรับวิเคราะห์อัตราการเล่นจบ - รองรับออฟไลน์ — เมื่อเครือข่ายล้มเหลว ให้เก็บไว้ในคิวในเครื่องและส่งซ้ำหลังกู้คืน เนื่องจากไอเดมโพเทนต์ การส่งซ้ำจึงปลอดภัย
- placementId / campaignId เป็นตัวเลือก — ไม่ได้รวมอยู่ในไอเทมของเพลย์ลิสต์โดยตรง แต่หากแอป Android เข้าใจโครงสร้างเพลย์ลิสต์ของ Phase 2 การใส่จะเพิ่มความละเอียดของการวิเคราะห์ แม้ว่างก็ถูก accepted
ฟิลด์ในเรสปอนส์:
accepted— จำนวนที่บันทึกใหม่duplicates— จำนวนที่ประมวลผลไปแล้ว (ซ้ำจากการส่งซ้ำ)rejected— จำนวนที่ประมวลผลไม่ได้ เช่น ขาดฟิลด์ที่จำเป็นerrors— อาร์เรย์ของเหตุผลการปฏิเสธ (สำหรับดีบัก เนื้อหาอาจเปลี่ยนตามเวอร์ชัน)
โฟลว์การนำไปใช้ทั่วไป (Android)
- เปิดใช้งานครั้งแรก — หากไม่มีโทเคนในเครื่อง ให้แสดงหน้าจอจับคู่ ป้อนโค้ด →
POST /pair→ รับโทเคน → บันทึก - ลูปทำงานปกติ
- เรียก
GET /playlistตอนเริ่มและทุก 60 วินาทีหลังจากนั้น - ไอเทมที่
hashHintต่างจากครั้งก่อนให้ดาวน์โหลดใหม่ ไอเทมเดิมอ่านจากแคช - เล่นภาพ/วิดีโอเต็มจอตามลำดับที่ได้ และไปตัวถัดไปทุก
durationSeconds - เมื่อเล่นถึงไอเทมสุดท้ายแล้วให้วนกลับไปต้น
- เรียก
- ขนาน — เรียก
POST /heartbeatทุก 60 วินาทีเพื่อแจ้งการมีชีวิตแก่เซิร์ฟเวอร์ - ขนาน — บันทึกลงคิวในเครื่องต่อทุกสล็อตที่เล่น และส่งแบตช์ด้วย
POST /impressionsทุก 60 วินาที - การจัดการข้อผิดพลาด
401— โทเคนไม่ถูกต้อง กลับไปหน้าจอจับคู่ใหม่403— โทเคนกับ displayId บนพาธไม่ตรงกัน (บั๊ก) เก็บล็อกแล้วจับคู่ใหม่- เครือข่ายล้มเหลว — เล่นเพลย์ลิสต์ที่ดึงมาล่าสุดต่อไปตามเดิม เก็บคิว impression ไว้และลองใหม่แบบแบ็กกราวด์
ความปลอดภัย
- โทเคนอุปกรณ์มีอายุยาว กรุณาเก็บในสตอเรจเข้ารหัสของเครื่อง เช่น Android Keystore
- ฝั่งเซิร์ฟเวอร์เก็บเพียงแฮช SHA-256 ของโทเคน โทเคนที่ทำสูญหายจะยกเลิกได้โดยลบดิสเพลย์ในหน้าจัดการแล้วจับคู่ใหม่
- 1 โทเคน = 1 ดิสเพลย์ หากใช้โทเคนเดิมซ้ำในหลายเครื่อง "heartbeat ล่าสุด" และ "ความละเอียดปัจจุบัน" ในหน้าจัดการจะถูกเขียนทับกันไปมาระหว่างเครื่องและสับสน
อนาคตของ API นี้
เพลย์ลิสต์ปัจจุบันรองรับ"การกำหนดเป้าหมายตามช่วงเวลา・ร้านค้า และลูปถ่วงน้ำหนักด้วยแคมเปญ × เพลสเมนต์" ฟีเจอร์ในอนาคตจะเพิ่มดังนี้:
- แดชบอร์ดผลการเล่นสำหรับเจ้าของร้าน (จำนวน impression, อัตราการเล่นจบ, ยอดวิวแยกตามดิสเพลย์)
- การบังคับใช้ขีดจำกัดความถี่ให้มีผลจริง (
frequencyCapPerHourของเพลสเมนต์ตั้งค่าได้แล้วในปัจจุบัน แต่จะเป็น no-op จนกว่าจะมีการนำ Phase 5 ไปใช้)
API สคีมาจะรักษาความเข้ากันได้แบบไปข้างหน้า — รูปแบบเรสปอนส์ของ 4 เอนด์พอยต์ที่มีอยู่จะไม่เปลี่ยนในอนาคต (แต่อาจมีฟิลด์เพิ่มเติมได้)
ข้อมูลที่เกี่ยวข้อง
-
วิธีใช้ Reservations APIคู่มือ Reservations API ของ ReceiptRoller (/api/v1/reservations) อธิบายการทำ CRUD และอัปเดตสถานะการจอง การดึง・สร้าง・สร้างชุดของช่องจอง (สล็อต) การดึงข้อมูลโต๊ะ (โต๊ะ・ห้องส่วนตัว・เคาน์เตอร์) ที่ลงทะเบียนไว้ในเลย์เอาต์ร้าน และสถิติการจอง
-
การเชื่อมต่อข้อมูลการซื้อและใบเสร็จอธิบายโครงสร้างข้อมูลการซื้อและใบเสร็จที่ ReceiptRoller จัดการ วิธีการดึงข้อมูล สโคปที่เกี่ยวข้อง Webhook และกรณีการใช้งานที่พบบ่อย
-
สารบัญความช่วยเหลือสำหรับนักพัฒนาสารบัญความช่วยเหลือสำหรับนักพัฒนา ReceiptRoller รวบรวมตั้งแต่การสมัครเป็นนักพัฒนา การลงทะเบียนแอปพลิเคชัน การยืนยันตัวตน OAuth และสโคป คู่มือการพัฒนา (แอปวอลเล็ต, Webhook สำหรับร้านค้า, Survey API) คู่มือแยกตามโดเมนข้อมูล การใช้งานและความปลอดภัย คอมมูนิตี ไปจนถึงการแก้ปัญหา
-
ดึงรายการบัญชีธุรกิจ・ร้านค้า・เครื่อง POSคู่มือสรุปขั้นตอนการดึงรายการบัญชีธุรกิจที่ผู้ใช้สังกัด ร้านค้าภายใต้บัญชีนั้น และเครื่อง POS ของแต่ละร้านผ่าน REST API จากแอปมือถือ/แอปเซิร์ฟเวอร์ของ ReceiptRoller
-
พื้นฐานของรีเควสต์และเรสปอนส์ (JSON)อธิบายรูปแบบรีเควสต์ของ API ReceiptRoller เฮดเดอร์ที่จำเป็น โครงสร้างเรสปอนส์ การแบ่งหน้า การกรอง และกฎของรูปแบบวันที่-เวลา