# 商家 / 骑手 App 三方登录接入文档(Apple / Google / LINE) > 依据 `specs/017-oauth-login`(含 2026-10-08 业务端账号密码绑定增量)整理。 > 适用端:骑手 App、商家 App(含商家子账号)。用户端 App 的手机号+短信流程不在本文范围。 ## 1. 渠道与 provider | 端 | Apple | Google | LINE | |---|---|---|---| | 骑手 | `apple_rider` | `google_rider` | `line_rider` | | 商家(含子账号) | `apple_merchant` | `google_merchant` | `line_merchant` | - 三方账号只做**已有业务账号的快捷登录入口**:骑手渠道只能绑已有有效骑手账号,商家渠道只能绑已有有效商家/子账号,**不会自动创建业务账号**。 - 一个三方账号在同一 provider 下只能绑定一个业务账号;换绑需联系平台处理。 ## 2. 总体流程 ``` ① App 取三方凭证 Apple :Sign in with Apple SDK → identityToken Google :Google Sign-In SDK → idToken LINE :先调 /auth/line/authorize 拿授权地址和 state,打开授权页 ② POST /infouser/user/oauthLogin 提交 provider + credential ├─ 已绑定 → 直接返回 { data: 用户信息, token },登录完成 └─ 未绑定 → 返回 { data: { status:"needPhone", tempKey } } ③ 首次绑定:App 弹【账号 + 密码】表单 POST /infouser/user/oauthBindPhone 提交 tempKey + userName + password(RSA密文) → 成功返回 { data: 用户信息, token },绑定并登录完成 ``` - `token` 与现有账号密码登录(`syslodeing` / `shanglodeing`)签发的是同类会话 token(骑手端 / 商家端各自独立),后续请求照旧放 `token` 请求头即可,无需其它接入。 - LINE 走服务端回调时第②步由后端回调完成,App 从回跳 scheme 参数里直接拿到 token 或 tempKey,见第 6 节。 ## 3. 接口:签发 LINE 授权地址 `POST /auth/line/authorize`(免登录) 业务端 LINE 登录**必须**先从服务端拿授权地址(内含服务端签发的 state,5 分钟有效、单次消费),不要自行拼接 LINE 授权 URL。 **请求体:** ```json { "provider": "line_rider" } // 商家端传 line_merchant ``` **响应:** ```json { "code": 200, "data": { "authorizationUrl": "https://access.line.me/oauth2/v2.1/authorize?...", "state": "32位hex" } } ``` App 打开 `authorizationUrl`(系统浏览器或 WebView),并**保存 state** 用于回跳比对。 ## 4. 接口:三方登录第一步 `POST /infouser/user/oauthLogin`(免登录) **请求体:** | 字段 | 必填 | 说明 | |---|---|---| | `provider` | 是 | 见第 1 节 | | `credential` | 是 | Apple=identityToken;Google=idToken;LINE=授权 code | | `state` | LINE 直连提交 code 时必填 | `/auth/line/authorize` 返回的 state;Apple/Google 不传 | | `cid` `cidType` `deviceToken` `voIPToken` | 否 | 推送字段,与现有登录一致 | | `fullName` | 否 | 仅 Apple:首次授权时用户共享的姓名,从 ASAuthorization 凭证取;后续登录不再传 | **响应 A(已绑定,直接登录):** ```json { "code": 200, "msg": "...", "data": { ...用户信息... }, "token": "xxx" } ``` **响应 B(未绑定,首次):** ```json { "code": 200, "msg": "...", "data": { "status": "needPhone", "tempKey": "xxx" } } ``` - `tempKey` **5 分钟有效、一次性**:过期或任何一步失败,都要重新从 `oauthLogin` 开始,不能重试旧 tempKey。 - 注意:字段名 `needPhone` 是历史契约(用户端为手机号+短信),**业务端拿到该状态后展示的是「账号 + 密码」表单**,不走短信。 - 响应 `data` 不含三方渠道字段(骑手返回账号实体、商家返回 `MerchantLoginView`,均只有账号资料);登录渠道 App 自己发起时已知,需要展示时按第 7 节查询。 **失败:** `code != 200`(凭证无效/过期、provider 不支持、账号已停用、state 无效等),直接提示 `msg`。 ## 5. 接口:首次绑定 `POST /infouser/user/oauthBindPhone`(免登录) 业务端绑定 = 校验业务账号密码后写入三方绑定,成功即登录。 **请求体:** | 字段 | 必填 | 说明 | |---|---|---| | `tempKey` | 是 | `oauthLogin` 返回 | | `userName` | 是 | 业务账号登录名(与 `syslodeing` / `shanglodeing` 登录用的账号一致) | | `password` | 是 | **RSA 公钥加密后的密码密文**,加密方式与现有账号密码登录完全一致(同一公钥、同一格式,直接复用现有加密函数) | | `cid` `cidType` `deviceToken` `voIPToken` | 否 | 推送字段 | 业务端**不传** `phone` / `code`(那是用户端 App 的短信绑定参数)。 **响应(成功):** ```json { "code": 200, "msg": "...", "data": { ...用户信息... }, "token": "xxx" } ``` 绑定成功后即完成登录,无需再调登录接口;该三方账号下次登录走 `oauthLogin` 直接拿 token。 **失败(不写绑定、不签 token):** | 场景 | 处理建议 | |---|---| | tempKey 缺失/过期 | 重新发起 `oauthLogin` | | 缺 userName / password | 表单校验拦截 | | 账号不存在 | 提示 `msg`(账号名或角色不符、已删除均按不存在处理) | | 密码错误 | 提示 `msg`,可重输 | | 账号停用 / 子账号被禁用或归属失效 | 提示 `msg`,联系平台 | ## 6. LINE 服务端回调 `GET /auth/line/callback`(LINE 服务器调用,App 不直接请求) LINE 授权完成后,后端处理回调并 **302 跳回对应 App 的 scheme**,App 按查询参数分流: | 回跳参数 | 含义 | App 动作 | |---|---|---| | `?token=...&state=...` | 已绑定,登录成功 | 比对 state 后保存 token,进入首页 | | `?needPhone=1&tempKey=...&state=...` | 未绑定,首次 | 比对 state 后弹**账号+密码**表单 → 调 `oauthBindPhone` | | `?error=no_code\|user_stopped\|fail&state=...` | 失败 | 提示失败,可重试 | - App 必须比对回跳 `state` 与第 3 节保存的发起值,不一致视为异常放弃本次登录。 - 若业务端在 H5/webview 内能自己拿到授权 code,也可不走回调,改为直接调 `oauthLogin` 提交 `provider + credential(code) + state`。 ## 7. 登录渠道与第三方名称展示(与用户端同一契约) 与用户端 App 一致:登录接口响应不带渠道字段,需要展示「本次登录渠道 + 第三方名称」时调用: ### `GET /infouser/user/getuserinfo`(`token` 请求头;骑手/商家 OAuth 登录签发的 token 均可直接调用) 响应 `data` 中与渠道相关的两个字段(017 2026-09-24 增量,三端共用同一契约): | 字段 | 说明 | |---|---| | `oauthProvider` | 本次会话登录渠道:`apple_rider` / `google_rider` / `line_rider` / `apple_merchant` / `google_merchant` / `line_merchant`;账号密码登录的会话该字段为空 | | `providerDisplayName` | 该渠道的第三方显示名称(LINE 的 displayName / Apple 首次授权共享的 fullName),**可空** | **展示规则(与用户端一致):** - 第三方名称与账号昵称 `nickName` 分开显示;`providerDisplayName` 为空时只展示渠道(如「Apple 登录」),不要用昵称冒充第三方名称。 - LINE 名称由服务端每次成功登录自动刷新,App 无需额外操作。 - 这两个字段反映的是**本次会话**的登录方式,不是账号维度的「最后一次登录方式」。 ## 8. 注意事项汇总 1. Apple / Google:App 用各自 SDK 在本地取凭证后直接调 `oauthLogin`,无需服务端授权地址。 2. LINE:必须走 `/auth/line/authorize` 拿授权地址与 state(服务端校验单次消费),state 过期需重新获取。 3. `tempKey` 一次性、5 分钟 TTL;绑定任何失败都从 `oauthLogin` 重来。 4. 密码只接受 RSA 密文,与现有登录接口同公钥同格式。 5. 各端 audience/clientId 独立:Apple/Google 凭证必须用对应业务端 App 的 bundleId/clientId 获取,用户端凭证在业务端会被拒绝。