Survey API and receipt embed

survey api receipt targeting liff

ReceiptRoller 的問卷功能讓店鋪透過 QR/連結/數位收據嵌入收集回答,並可選擇性地回饋優惠券。本文涵蓋公開消費者 API、收據嵌入酬載,以及店鋪可附加至活動的觀眾鎖定規則。

公開 URL

每個問卷活動都會取得一個 10 字元的 URL-safe 權杖。公開的著陸頁是:

https://{host}/s/{token}

host 來自 config 中的 Survey:UniversalLinkHost;退回時使用請求的 host。原生應用程式透過 Universal Link / App Link 認領 /s/*(項目會被加入 apple-app-site-associationassetlinks.json)。LINE 內建瀏覽器的使用者會被 302 導向附加 ?t={token} 的 LIFF URL;其他所有人則落在內嵌的 web 退回表單。

消費者 API

三個端點,皆位於 /api/v1/surveys 之下。preview 與 submit 是 [AllowAnonymous],因此 LIFF 或 web 退回可在不帶 bearer 權杖的情況下呼叫;伺服器以 auth_required 狀態把關非匿名活動。

GET /api/v1/surveys/campaign/{token}

回傳活動中繼資料與完整的提問清單。用戶端在使用者開始回答前用它渲染表單。

{
  "status": "ok",
  "campaign": {
    "campaignId": "...",
    "storeId": "...",
    "title": "...",
    "description": "...",
    "validFrom": "2026-04-01T00:00:00Z",
    "validUntil": "2026-05-31T23:59:59Z",
    "isAnonymous": true,
    "maxResponsesPerUser": 1,
    "hasReward": true
  },
  "questions": [
    { "questionId": "...", "order": 0, "type": "single_choice",
      "text": "...", "isRequired": true, "optionsJson": "[\"A\",\"B\"]" }
  ]
}

可能的 status 值:okexpiredinactiveunknown_token

POST /api/v1/surveys/responses

提交回答。匿名呼叫者必須送出穩定的 deviceId;非匿名活動需要 bearer 權杖。

POST /api/v1/surveys/responses
Content-Type: application/json

{
  "token": "...",
  "answers": [
    { "questionId": "...", "value": "A" },
    { "questionId": "...", "value": "5" },
    { "questionId": "...", "value": "OptionA,OptionC" }
  ],
  "deviceId": "anon-..."
}

回應使用 status 判別欄位,用戶端據以分歧:

Status意義
recorded儲存成功。當活動有獎勵且優惠券驗證通過時,couponIssuedId 會被填入。
response_limit使用者已提交 maxResponsesPerUser 次。
expired在有效期間之外。
inactive活動已封存。
unknown_token找不到權杖。
validation_error必填欄位為空或超出範圍。validationErrors 字典以 questionId 為鍵。
auth_required活動需要已登入的使用者;用戶端應啟動登入。

複選回答的編碼

對於 multi_choice 提問,用戶端可送出以逗號連接的字串("A,C")或 JSON 陣列字串("[\"A\",\"C\"]")。兩者都被分析彙總器接受。

GET /api/v1/surveys/me

僅限 bearer。回傳呼叫者自己的回答歷史(response id、campaign id、store id、completed-at、coupon-issued id)。

數位收據嵌入

當店鋪有 showOnDigitalReceipt = true 的有效活動,且其鎖定規則符合收據的情境時,交易收據 API 會將 survey 物件附加至收據酬載:

GET /api/v1/rx/{claimToken}

{
  "claimToken": "...",
  "storeName": "...",
  "transaction": { ... },
  "survey": {
    "campaignId": "...",
    "title": "...",
    "description": "...",
    "url": "https://{host}/s/{token}",
    "hasReward": true
  }
}

當沒有活動符合時,survey 欄位為 null — 用戶端只需隱藏該區段。嵌入查找的失敗絕不會破壞收據回應。

觀眾鎖定規則

每個活動可附加一份 JSON 文件,用以過濾哪些收據會取得嵌入。文件是一份扁平的 AND 清單 — 收據只有在每條規則都符合時才符合。空/缺少的文件 = 永遠符合。

{
  "rules": [
    { "field": "total_amount",         "op": "gte",      "value": 1000 },
    { "field": "item_count",           "op": "gte",      "value": 3 },
    { "field": "category",             "op": "eq",       "value": "drinks" },
    { "field": "product_name",         "op": "contains", "value": "coffee" },
    { "field": "time_of_day",          "op": "gte",      "value": 18 },
    { "field": "day_of_week",          "op": "eq",       "value": "fri" },
    { "field": "customer_visit_count", "op": "gte",      "value": 2 }
  ]
}

欄位

Field來源Type
total_amount收據合計decimal
item_count明細行數int
category任一明細行的類別string
product_name任一明細行的商品名string
time_of_day交易的時刻(0-23)int
day_of_week3 字母英文縮寫(mon...sunstring
customer_visit_count此使用者的累計來店次數(延遲載入)int

運算子

  • eq — 完全一致(字串或數字)
  • gt / gte / lt / lte — 數值比較
  • contains — 不分大小寫的子字串(字串欄位與明細行陣列)

Fail-closed 語意

格式錯誤的 JSON、未知欄位、未知運算子或缺少值,會使該規則(因而整個匹配)失敗。這是刻意的:靜默地不嵌入問卷,好過把它嵌入到錯誤的收據上。

優惠券獎勵

在活動上設定 RewardCouponId,會讓 SubmitResponseAsync 在提交時解析優惠券。優惠券必須是有效的且在其自身的有效期間內;若如此,回答列會被蓋上優惠券 id,結果 DTO 會將其作為 couponIssuedId 回傳。ReceiptRoller 的優惠券是店鋪層級的範本 — 沒有依使用者的授予模型。消費者用戶端將回傳的 id 視為可兌換的代碼。

依使用者去重

每個活動有 maxResponsesPerUser 設定(預設 1)。服務以 userId(已登入的提交)或 deviceId(匿名提交)為鍵計數回答來執行上限。web 退回表單在 localStorage 生成穩定的 device id;LIFF/原生用戶端應送出自己的。

Universal Link / App Link

/s/* 路徑與既有的端末 QR(/r/*)、交易 QR(/rx/*)、check-in(/c/*)路徑一起被包含在 apple-app-site-associationassetlinks.json 中。認領這些路徑的原生應用程式會直接從 OS 收到問卷 URL,並可在不經過 web 著陸頁的情況下呼叫消費者 API。

工程參考

完整的設計規格,包含各階段的 PR 清單與已知的 v2 延後項目,位於工程 wiki:Engineering / Survey Feature — Design Spec

發布日: 2026-04-25 更新日: 2026-07-05
標籤
API (22) OAuth (15) Android (10) iOS (9) Webhook (8) api (7) oauth (5) POS串接 (4) getting-started (4) 參考 (4)