原生行動應用程式指南:多商業帳戶存取與 OAuth 流程

OAuth Android iOS 行動 API 多商業帳戶 PKCE

ReceiptRoller 常有 1 位使用者所屬於多個商業帳戶的情況。若想在原生行動應用程式(Android / iOS)實作「商業帳戶切換」UI,使用本指南的模式,只需登入 1 次即可存取所有商業帳戶的資料。

運作機制

組合 3 個機制。

  1. user-scoped 存取權杖 — 權杖不關聯至特定的商業帳戶(AuthorizedOrganizationId = null,僅設定 AuthorizedUserId
  2. GET /api/v1/me/organizations — 回傳使用者所屬的商業帳戶一覽
  3. 在各 API 加上 ?organizationId= — 伺服器端每次執行成員資格驗證

藉此,使用者只需進行 1 次 Web 瀏覽器基礎的 OAuth 登入,即可切換參照、操作所屬的所有商業帳戶。

應用程式登錄時的設定

要使用此模式,請在應用程式登錄時將 「授權時將權杖關聯至 1 個商業帳戶」的勾選關閉。可在開發者儀表板的應用程式建立畫面設定。

  • 維持開啟(預設)→ 在授權畫面會顯示商業帳戶的選擇對話框,權杖會關聯至所選擇的 1 個帳戶(適用於一般的 Web 應用程式、伺服器串接)
  • 關閉 → 授權畫面不出現帳戶選擇,發行 user-scoped 權杖(本指南的對象)

在應用程式一覽的詳細畫面,旗標為關閉的應用程式會顯示「支援多帳戶」徽章。

OAuth 授權流程(PKCE + Chrome Custom Tabs / ASWebAuthenticationSession)

遵循 RFC 8252(OAuth 2.0 for Native Apps)與 OAuth 2.1 建議的形式。請避免應用程式內 WebView,使用系統瀏覽器元件。

  • Android: androidx.browser.customtabs.CustomTabsIntent
  • iOS: AuthenticationServices.ASWebAuthenticationSession

步驟

  1. 登錄 OAuth 用戶端
    • 從開發者儀表板手動建立: 建立新的應用程式
    • 將「授權時將權杖關聯至 1 個商業帳戶」的勾選關閉
    • redirect_uri 指定自訂 scheme(例:com.example.rrstore://oauth/callback)或 HTTPS 的 App Links / Universal Links
  2. 產生 PKCE 參數
    • code_verifier: 43~128 字元的隨機字串
    • code_challenge: BASE64URL(SHA256(code_verifier))
    • state: CSRF 對策的隨機值
  3. 以系統瀏覽器開啟 /oauth/authorize
    https://receiptroller.io/oauth/authorize
      ?response_type=code
      &client_id={your_client_id}
      &redirect_uri={your_redirect_uri}
      &scope=sales.read sns.content.read sns.audience.read analytics.read store.customers.read store.flyers.read
      &state={state}
      &code_challenge={code_challenge}
      &code_challenge_method=S256

    使用者若未登入會被導引至 /Identity/Account/Login,登入後經由同意畫面(僅顯示所要求的權限範圍、無商業帳戶選擇)回到應用程式的 redirect_uri

    注: 若未將「授權時將權杖關聯至 1 個商業帳戶」關閉,此處會額外顯示選擇「要存取哪個商業帳戶」的下拉選單。要支援多帳戶請務必關閉。

  4. 將授權碼交換為存取權杖
    POST https://receiptroller.io/api/v1/auth/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &code={code_from_redirect}
    &redirect_uri={your_redirect_uri}
    &client_id={your_client_id}
    &client_secret={your_client_secret}
    &code_verifier={code_verifier}

    回應為符合 RFC 6749 §5.1 的 snake_case JSON。

    {
      "access_token": "...",
      "refresh_token": "...",
      "token_type": "Bearer",
      "expires_in": 3600,
      "scope": "sales.read sns.content.read sns.audience.read analytics.read store.customers.read store.flyers.read"
    }

取得使用者所屬的商業帳戶

使用存取權杖取得商業帳戶一覽。

GET https://receiptroller.io/api/v1/me/organizations
Authorization: Bearer {access_token}

回應:

{
  "organizations": [
    { "id": "9f0dfdc3-77ac-4bf7-9c9e-a5d1285edd88", "name": "AB", "role": "SuperUser,StoreStaff" },
    { "id": "55a5b9bf-287c-4618-a973-66b67004f292", "name": "モスフードサービス", "role": "Admin" }
  ]
}

將此回應在應用程式內顯示為「商業帳戶切換」UI。將使用者選擇的商業帳戶的 id 保存於本地,在之後的 API 呼叫使用。

在各 API 傳入 ?organizationId=

將選擇中的商業帳戶 ID 每次作為查詢參數傳入。伺服器端每次驗證「呼叫來源使用者是否為該商業帳戶的成員」(防止 cross-org 資料外洩)。

GET https://receiptroller.io/api/v1/sales/kpis?organizationId={selected_org_id}&storeId={store_id}
Authorization: Bearer {access_token}

支援多商業帳戶存取的 API 一覽

截至 2026 年 5 月,以下端點群支援 user-scoped 權杖與 ?organizationId= 模式。

共通

  • GET /api/v1/me/organizations — 所屬的商業帳戶一覽

營業額 / 分析

  • GET /api/v1/sales/kpis
  • GET /api/v1/sales/trend
  • GET /api/v1/sales/advice
  • GET /api/v1/sales/forecast
  • GET /api/v1/sales/products — 商品 + ABC 分析
  • GET /api/v1/sales/visitors — 來店客分析
  • GET /api/v1/sales/churn — 流失風險分數
  • GET /api/v1/sales/recommendations — 商品推薦
  • GET /api/v1/sales/multi-trend — 多店鋪比較

SNS

  • GET /api/v1/sns/accounts / /accounts/{id}
  • GET /api/v1/sns/posts / /posts/{id} / /posts/{id}/analytics
  • GET /api/v1/sns/comments

顧客 (CRM)

  • GET /api/v1/store/customers / /customers/{id}
  • POST /api/v1/store/customers (upsert)
  • DELETE /api/v1/store/customers/{id}
  • GET /api/v1/store/customers/{id}/purchases

傳單

  • GET /api/v1/organizations/{orgId}/stores/{storeId}/flyers
  • GET /api/v1/organizations/{orgId}/stores/{storeId}/flyers/{flyerId}
  • POST / PUT / DELETE / publish / unpublish / expire

傳單系因 URL 路徑中已包含 {orgId},因此不需要加上 ?organizationId=。伺服器端每次驗證「URL 路徑的 orgId 使用者是否可存取」。

範例: 在 Android 取得營業額 KPI

// 1. 登入後 → 以 /api/v1/me/organizations 取得商業帳戶一覽
val orgs = api.getMyOrganizations()  // 會附上 Authorization: Bearer ...
showOrgSwitcher(orgs)

// 2. 使用者選擇商業帳戶
val selectedOrgId = "55a5b9bf-287c-4618-a973-66b67004f292"

// 3. 取得營業額 KPI
val kpis = api.getSalesKpis(organizationId = selectedOrgId, storeId = "abc123")
showDashboard(kpis)

// 4. 切換商業帳戶時不需重新取得權杖 — 只需改變 selectedOrgId 再重新請求
val newOrgId = "9f0dfdc3-77ac-4bf7-9c9e-a5d1285edd88"
val newKpis = api.getSalesKpis(organizationId = newOrgId, storeId = "xyz789")

安全性考量事項

  • client_secret 的處理 — 若為 Public client(行動應用程式單體),建議採用不將 client_secret 含入應用程式二進位、僅以 PKCE 驗證的 token_endpoint_auth_method=none 組態。
  • 權杖的保存位置 — 請使用 Android Keystore / iOS Keychain 等 OS 的安全區域。
  • 有效期限 — access_token 為 1 小時,refresh_token 為 30 天。refresh_token 以用完即棄(輪替方式)發行。
  • 權限範圍最小化 — 請只要求必要的權限範圍。OAuth 權限範圍一覽
  • 伺服器端的成員資格驗證 — 若指定使用者不所屬的商業帳戶的 organizationId,API 會回傳 403 Forbidden。

相關文章

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