stall-qualification-app-api.md 10 KB

摊位资质认证(034):App 端接入说明

日期:2026-10-10 分支:test-202609v2(后端已实现并过定向测试;测试库 stall_qualification 两张表已建) 一句话:摊位(userType=4 账号)在 App 里提交认证资料、查看审核进度、驳回后重提;审核通过前摊位不能营业和接新单。


1. 功能与角色

  • 认证归属摊位(storeId),不归属登录账号。一个摊位可以有多个管理账号,共用同一套认证资料;换账号、停用账号都不影响已通过的资质。
  • App 端只做摊主侧:填表、传图、存草稿、提交、看进度、看驳回原因、重提。
  • 平台审核(通过/驳回)在平台管理 PC 端操作,App 不涉及。夜市主看进度是 PC 端功能(接口见第 8 节,App 如需也可用)。
  • 所有摊位同一张表单、同一套必填规则,没有个人经营/公司经营的分类分支。

2. 通用约定

项 说明
鉴权 请求头 token: <登录令牌>,账号必须是有效的 userType=4(摊主)且已绑定 isStall=1 的摊位;否则返回 merchant.store.access.denied
响应包裹 { "code": 200, "msg": "...", "data": ... };业务失败 code=500,msg 为服务端按语言下发的本地化文案(zh/tw/en/vi/th),App 直接展示 msg 即可,不要自己映射错误文案
图片 BASE 新上传图片是公开路径,直接 BASE_URL + 路径 展示,无需登录

3. 状态模型

application.status:

值 含义 App 表现
0 草稿 可继续编辑、可提交
1 待审核 只读,不能改不能重复提交(服务端拒绝 stall.qualification.pending)
2 通过 当前有效资质;重新提交期间保持有效,新申请通过才切换
3 驳回 展示 rejectReason,按新版本重新填写提交

GET /stall/qualification 里 currentApplication 为 null 表示从未创建过申请(此时 version=0)。

版本并发:view.version 是乐观锁版本号。每次保存/提交/审核都会 +1。保存请求必须带当前 version;不匹配返回 stall.qualification.conflict(提示用户刷新重新加载,不要自动重试覆盖——可能是另一个管理账号刚改过)。提交成功后从响应里取新的 version 存本地。

4. 接口

4.1 查询资质视图

GET /stall/qualification
token: <摊主登录令牌>
{
  "code": 200,
  "data": {
    "storeId": 194,
    "stallName": "阿美蚵仔煎",
    "stallAddress": "台北市士林夜市 108 摊",
    "nightMarketId": 12,
    "version": 3,
    "currentApplication": { "...": "完整申请对象,见 4.4" },
    "approvedApplication": { "...": "当前有效版本,可能为 null" },
    "history": [
      {
        "id": 88,
        "storeId": 194,
        "applicationVersion": 2,
        "status": 3,
        "stallNameSnapshot": "阿美蚵仔煎",
        "stallAddressSnapshot": "台北市士林夜市 108 摊",
        "nightMarketIdSnapshot": 12,
        "submittedAt": "2026-10-10T09:00:00+08:00",
        "reviewedAt": "2026-10-10T11:00:00+08:00",
        "rejectReason": "证件照片不清晰"
      }
    ]
  }
}
  • 读操作不会创建草稿;从未提交过时 version=0、两个 application 都是 null、history=[]。
  • history 只有摘要(无姓名/证件号/电话/附件),按 applicationVersion 倒序。
  • 摊位名称、地址、所属夜市由服务端从 pos_store 固化快照,App 不提供修改入口。

4.2 保存草稿 / 提交审核

POST /stall/qualification/draft     # 存草稿,允许不完整
POST /stall/qualification/submit    # 提交审核,四组资料必填
Content-Type: application/json
token: <摊主登录令牌>

请求体(两个接口相同):

{
  "version": 3,
  "responsibleName": "王大明",
  "contactPhone": "0912345678",
  "identityNumber": "A123456789",
  "identityImageUrls": ["/profile/upload/2026/10/10/xxx.jpg"],
  "stallImageUrls": ["/profile/upload/2026/10/10/yyy.png"],
  "foodRegistrationNumber": "F-200000000-00000",
  "foodRegistrationImageUrls": ["/profile/upload/2026/10/10/zzz.jpg"]
}
字段 必填(提交时) 限制
version 是(草稿也要) 必须等于当前 view.version,否则冲突错误
responsibleName 是 ≤100 字符
contactPhone 是 ≤32 字符;与账号登录手机号独立,可以不同
identityNumber 是 ≤64 字符(身份证/护照号)
identityImageUrls 是 1~5 张
stallImageUrls 是 1~5 张
foodRegistrationNumber 是 ≤64 字符
foodRegistrationImageUrls 是 1~5 张

规则:

  • 文本两端 trim,trim 后为空视为缺失;超长返回 stall.qualification.input.invalid。
  • 草稿也校验已填图片引用的合法性(数量上限、文件存在),只是允许字段不齐。
  • 不接收 storeId、状态、审核人等字段——归属由 token 决定。
  • 草稿重复保存是覆盖同一条草稿(applicationVersion 不变);从已提交/已审核状态再保存(提交后想改、驳回重提)会创建新版本申请,旧记录不可变。
  • 响应为保存后的完整 QualificationView(同 4.1 结构,currentApplication 即刚保存的申请),从响应更新本地 version。

4.3 图片上传(商品同款通用上传)

POST /utils/Upload
Content-Type: multipart/form-data
(字段名 file;接口匿名,可不带 token)

响应:

{ "code": 200, "msg": "上传成功", "data": "/profile/upload/2026/10/10/20261010xxxx.jpg" }
  • data 就是可保存进 xxxImageUrls 的路径,直接使用。
  • 后端会校验真实图片格式并规范文件名特殊字符,App 无需预处理文件名。

展示规则(重要):

4.4 历史申请详情 / 旧私有附件

GET /stall/qualification/applications/{id}
token: <摊主登录令牌>

返回完整 StallQualificationApplication(含材料、提交/审核时间、驳回原因)。只能看本摊位的,看别人的返回 merchant.store.access.denied。用于点开 history 记录查看当时提交的材料。

GET /stall/qualification/files/{storeId}/{fileName}
token: <摊主登录令牌>

旧私有附件读取(stall-qualification 目录的历史文件),返回图片二进制(Cache-Control: no-store),App 需带 token 请求并转 blob 展示;只能读本人摊位的文件。新上传不要用 /stall/qualification/files(POST),那是旧版兼容接口。

5. 营业门禁(App 需要处理的联动)

isStall=1 且无通过记录时(含从未提交):

引用形态 判别 展示方式
新公共路径 以 /profile/upload/ 开头 BASE_URL + 引用,公开访问,无需登录
旧私有引用 `"/.jpg .png"` 形态
场景 行为
摊主 App 创建订单(代客/堂食/外送) 服务端拒绝,msg = 摊位材料尚未审核通过,暂不能营业或建立新订单
用户 App 向该摊位下单 同上被拒
门店状态 对外按闭店(state=1)展示
已有订单 出餐、配送、支付后续不受影响

建议:摊主端遇到 stall.qualification.approval.required 文案(或进店页发现未认证)时,引导跳转认证页;认证页根据 4.1 的状态渲染对应操作。

6. 错误文案(服务端已本地化,直接展示 msg)

场景 zh-TW 文案示例
版本冲突/已被他人操作 資料已變更或已審核,請重新整理後重試
缺 version 請重新整理後再儲存,缺少資料版本
待审核中重复提交/修改 資料正在審核,暫不可修改或重複提交
提交时材料不齐 請補齊負責人姓名電話、證件、攤位照片及食品業者登錄資料…
参数/超长/图片超5张 審核資料參數不正確
图片无效/不存在/非本摊位 審核圖片無效、超過限制或不屬於目前攤位
上传处理失败 審核材料檔案處理失敗,請稍後重試
营业门禁 攤位材料尚未審核通過,暫不能營業或建立新訂單
非本摊位/账号无效 (merchant.store.access.denied 对应文案)

7. 建议页面流程

进入认证页
  └─ GET /stall/qualification
      ├─ currentApplication = null        → 空表单(version=0),填完可存草稿/提交
      ├─ status = 0 (草稿)                → 回填表单,继续编辑或提交
      ├─ status = 1 (待审核)              → 只读展示材料 + "审核中"
      ├─ status = 3 (驳回)                → 展示 rejectReason;重填提交走新版本
      └─ status = 2 (通过)                → 展示有效资料;approvedApplication 为当前有效版本
  • 图片选择后先 POST /utils/Upload,成功再把返回路径塞进对应数组;上传中禁止点提交。
  • 待审核状态下隐藏保存/提交按钮(服务端同样会拦)。
  • 提交成功后用响应刷新整个视图和本地 version。

8. 夜市主进度(可选,type=3 账号)

GET /stall/qualification/market
token: <夜市主登录令牌>

返回本人旗下摊位数组:{ storeId, status(-1=未创建), approved, ... },不含姓名、证件号、电话和附件。App 若不做夜市主进度页可忽略。

9. 注意事项

  • userType 对照:0 用户、1 商家、2 骑手、3 夜市主、4 摊主。本功能仅 4。
  • 平台审核接口(/system/stallQualification*)走平台管理端 Authorization 登录,与 App 无关,不要在 App 调用。
  • 提交后的材料不可变:想改只能等驳回后重提,或(已通过后)提交新版本申请;已通过资质在新申请审核期间保持有效。
  • 证件图片链接为公开访问(用户已确认);但姓名、证件号、电话不出现在任何列表/进度接口,App 同样不要在日志里打印这些字段。