PocketBuy 门店端 H5 前后端接口契约(MVP)

版本:V1.2
日期:2026-08-11
API 前缀:/api/h5/v1
时区:Africa/Lagos
金额:NGN 十进制字符串
原则:H5 展示服务端财务结果,不计算订单佣金、绩效系数或结算金额。

1. 契约原则

  1. 所有受保护接口通过访问令牌识别 user_idrolestore_id,禁止由查询参数覆盖授权范围。
  2. 金额字段使用十进制字符串,例如 "18650.00",禁止 JSON 浮点数。
  3. 时间使用 RFC 3339 且包含偏移,例如 2026-08-05T10:45:00+01:00;月份使用 YYYY-MM
  4. 列表使用游标分页;汇总值由后端针对完整筛选范围计算。
  5. 所有写接口支持 Idempotency-Key,同键同请求返回原结果,同键不同请求返回 409。
  6. 所有响应包含 trace_id;错误不透传上游原始报文。
  7. 财务数据必须返回 calculation_version/policy_snapshot_id/data_as_of 等可追溯信息。

2. 通用协议

2.1 请求头

Header必填说明
Authorization: Bearer <token>受保护接口是访问令牌
X-Request-Id建议客户端生成 UUID
Idempotency-Key写接口是UUID,24 小时内不可复用到不同请求
Accept-LanguageMVP 默认 en-NG
X-App-VersionH5 发布版本
X-Device-Timezone默认 Africa/Lagos

2.2 成功响应

{
  "success": true,
  "data": {},
  "meta": {
    "trace_id": "trc_01J...",
    "server_time": "2026-08-06T11:20:30+01:00",
    "data_as_of": "2026-08-06T11:20:00+01:00",
    "source": "LIVE"
  }
}

source 枚举:LIVECACHE。H5 本地缓存不是服务端 source;本地缓存展示时由前端额外标记。

2.3 错误响应

{
  "success": false,
  "error": {
    "code": "STORE_RELATION_CONFLICT",
    "message": "This mobile number is already linked to another store.",
    "retryable": false,
    "field_errors": [
      { "field": "manager_mobile", "code": "CONFLICT", "message": "Please contact support." }
    ]
  },
  "meta": { "trace_id": "trc_01J...", "server_time": "2026-08-06T11:20:30+01:00" }
}

2.4 分页

请求:page_size=10&cursor=<opaque>page_size 允许 10–50。

{
  "items": [],
  "page": { "page_size": 10, "next_cursor": "opaque-or-null", "has_more": true },
  "summary": { "total_count": 49 }
}

游标为不透明字符串,前端不得解析。筛选条件变化时必须丢弃旧游标。

3. 公共类型与枚举

3.1 身份

type Role = "CLERK" | "STORE_MANAGER";
type RelationshipStatus = "BOUND" | "PENDING_REVIEW" | "DISABLED";
type BadgeStatus = "NOT_SUBMITTED" | "PENDING_REVIEW" | "APPROVED" | "REJECTED";
type ClerkStatus = "ACTIVE" | "PENDING_REVIEW" | "DISABLED";

3.2 业务状态

type OrderStatus = "PROCESSING" | "QUALIFIED" | "CANCELLED";
type CommissionStatus =
  | "ESTIMATED" | "EARNED" | "WAITING_METRIC" | "AVAILABLE"
  | "IN_SETTLEMENT" | "PAID" | "WITHHELD" | "REVERSED" | "CLAWBACK";
type CommissionPeriodStatus = "OPEN" | "SETTLING" | "SETTLED" | "WITHHELD";
type SettlementStatus = "PROCESSING" | "PAID" | "PARTIAL" | "FAILED" | "WITHHELD";
type Trend = "UP" | "DOWN" | "SAME" | "NEW";
type RiskFactorStatus = "NOT_APPLICABLE" | "PENDING" | "CONFIRMED" | "CORRECTED";
type ReferralCodeStatus = "ACTIVE" | "INACTIVE" | "EXPIRED";

3.3 手机号

  • 前端输入:10 位本地号码,例如 8012345678
  • API 请求:E.164,例如 +2348012345678
  • API 展示:仅返回 mobile_masked,例如 +234 *** *** 5678
  • 后端必须使用 libphonenumber 等可靠组件校验尼日利亚号码,不只校验长度。

4. 接口总览

方法路径用途
AuthPOST/auth/otp/send发送 OTP
AuthPOST/auth/otp/verify验证并登录
AgreementGET/agreements/current?scene=LOGIN登录授权协议全文与版本
AgreementGET/me/agreement-acceptances本人协议同意记录
AuthPOST/auth/token/refresh刷新令牌
AuthPOST/auth/logout注销
ProfileGET/me当前身份、门店关系与核验状态(只读)
Payout accountGET/me/payout-account本人唯一默认收款户
Payout accountPUT/me/payout-account新增或更换本人唯一默认收款户
DashboardGET/dashboard首页聚合
PolicyGET/incentive-policies/current当前角色政策
RankingGET/leaderboards排行榜
ProductGET/hot-products热销手机
OrdersGET/orders订单列表与汇总
OrdersGET/orders/{order_id}订单详情
CommissionGET/commission-periods可查询月份
CommissionGET/commissions/monthly-summary月度汇总
CommissionGET/commissions订单佣金列表
CommissionGET/commissions/{commission_id}单笔佣金详情
SettlementGET/settlements结算记录
SettlementGET/settlements/{settlement_id}结算详情
ReferralGET/me/referral-code个人推荐码
ConfigGET/app-config客服电话、功能开关
MessagesGET/messages消息列表/未读数

5. 认证与账号契约

5.1 发送 OTP

POST /auth/otp/send

{
  "mobile": "+2348012345678",
  "purpose": "LOGIN",
  "device_id": "dev_opaque"
}
{
  "success": true,
  "data": {
    "challenge_id": "otp_01J...",
    "expires_in_seconds": 300,
    "resend_after_seconds": 60,
    "mobile_masked": "+234 *** *** 5678"
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:20:30+01:00" }
}

错误:INVALID_MOBILEACCOUNT_NOT_REGISTEREDACCOUNT_DISABLEDOTP_RATE_LIMITEDSMS_PROVIDER_UNAVAILABLE

5.2 验证 OTP

GET /agreements/current?scene=LOGIN&locale=en-NG 返回当前 agreement_idversiontitlecontent_urlcontent_hasheffective_atacceptance_required。登录页在发送 OTP 前即可打开协议全文。

POST /auth/otp/verify

{
  "challenge_id": "otp_01J...",
  "otp": "123456",
  "device_id": "dev_opaque",
  "agreement_acceptance": {
    "agreement_id": "agr_login_01",
    "version": "2026-08-11",
    "accepted": true,
    "content_hash": "sha256:..."
  }
}
{
  "success": true,
  "data": {
    "access_token": "opaque",
    "access_token_expires_in": 900,
    "refresh_token": "opaque",
    "refresh_token_expires_in": 2592000,
    "user": {
      "user_id": "usr_01J...",
      "display_name": "Amina Yusuf",
      "role": "CLERK",
      "mobile_masked": "+234 *** *** 5678",
      "relationship_status": "BOUND",
      "badge_status": "APPROVED"
    }
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:21:00+01:00" }
}

只有 SA/SM/RM 已录入且满足登录条件、并同意当前必需协议的店长/店员账号返回令牌,验证成功后直接进入 Earnings。服务端原子保存用户、角色、协议版本、内容 hash、同意时间、IP/设备和 trace ID;协议升级后可要求重新确认。GET /me/agreement-acceptances 只返回当前用户的协议版本和同意时间,不返回 IP 等内部证据。

错误:OTP_INVALIDOTP_EXPIREDOTP_ATTEMPTS_EXCEEDEDAGREEMENT_REQUIREDAGREEMENT_VERSION_MISMATCHACCOUNT_NOT_READYRELATIONSHIP_NOT_READYACCOUNT_DISABLEDACCOUNT_NOT_READY / RELATIONSHIP_NOT_READY 的用户文案引导联系 SA 或客服。

5.3 刷新、注销与当前用户

POST /auth/token/refresh

{ "refresh_token": "opaque", "device_id": "dev_opaque" }

成功时轮换并返回新的 access_tokenrefresh_token 及各自有效期;旧刷新令牌立即失效。重复使用已轮换令牌返回 REFRESH_TOKEN_REUSED 并撤销该会话族。

POST /auth/logout 请求体 { "refresh_token":"opaque", "all_devices":false },幂等返回 { "logged_out":true }

GET /me

{
  "user_id": "usr_01J...",
  "display_name": "Amina Yusuf",
  "role": "CLERK",
  "mobile_masked": "+234 *** *** 0184",
  "relationship_status": "BOUND",
  "store": { "store_id": "store_018", "store_name": "Ikeja Mobile Hub", "store_code": "STORE-018" },
  "badge": { "status": "APPROVED", "rejection_reason": null, "latest_submission_id": "badgesub_..." },
  "payout_account": { "status": "VERIFIED", "bank_name": "Example Bank", "account_name": "Amina Yusuf", "nuban_masked": "******0184", "is_default": true },
  "permissions": ["VIEW_OWN_ORDERS", "VIEW_OWN_COMMISSION", "VIEW_REFERRAL_CODE", "MANAGE_OWN_PAYOUT_ACCOUNT"]
}

permissions 只用于控制页面可见性,后端每次请求仍须独立鉴权。

5.4 人员与关系数据边界

  • 店长、店员及门店关系由 SA App 的 Stores H5 供 SA/SM/RM 按授权范围录入/维护,经服务端审核和生效后供门店端 H5 只读查询。
  • 门店端 H5 的人员信息来自 GET /me 和订单快照字段,门店角色令牌不具有人员主数据写权限;仅可通过专用接口维护本人唯一收款户。

5.5 本人唯一收款户

GET /me/payout-account 返回当前账户状态、银行、账户名、脱敏 NUBAN、默认标记、校验时间和版本;无账户时返回 account=null

PUT /me/payout-account

{ "bank_code": "058", "nuban": "0123456789", "change_reason": "FIRST_SETUP" }

服务端必须调用权威银行账户解析/校验能力,账户名匹配并通过风控后保存。每个用户同一时刻最多一条有效账户,首次成功自动 is_default=true;再次调用表示更换而非新增第二户,需关闭旧版本并保留历史。响应只返回银行名、账户名、脱敏 NUBAN、VERIFIED 和版本。错误:BANK_NOT_SUPPORTEDACCOUNT_RESOLVE_FAILEDACCOUNT_NAME_MISMATCHPAYOUT_ACCOUNT_UNDER_REVIEWIDEMPOTENCY_CONFLICT。付款批次必须引用不可变 payout_account_snapshot_id

6. 首页聚合

GET /dashboard?period=2026-08

{
  "success": true,
  "data": {
    "period": "2026-08",
    "user": { "display_name": "Amina Yusuf", "role": "CLERK", "store_name": "Ikeja Mobile Hub" },
    "monthly_summary": {
      "period_status": "OPEN",
      "order_count": 49,
      "estimated_commission": "18650.00",
      "earned_base_amount": "12400.00",
      "waiting_amount": "6250.00",
      "settled_amount": "0.00",
      "currency": "NGN",
      "updated_at": "2026-08-06T11:15:00+01:00",
      "calculation_version": "calc_2026_08_03"
    },
    "policy_teaser": {
      "policy_id": "pol_aug_2026_clerk",
      "title": "August incentive policy",
      "headline": "Know your reward before your next sale",
      "subtitle": "ACH targets · ₦400–₦600 per unit"
    },
    "leaderboard_preview": { "default_tab": "CLERKS", "clerks": [], "stores": [] },
    "hot_products": []
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:20:30+01:00", "data_as_of": "2026-08-06T11:15:00+01:00", "source": "LIVE" }
}

首页聚合内的 monthly_summary/commissions/monthly-summary?period=同月 字段和值必须一致。

7. 政策、排行榜与商品

7.1 激励政策

GET /incentive-policies/current?period=2026-08

{
  "success": true,
  "data": {
    "policy_id": "pol_aug_2026_clerk",
    "policy_snapshot_id": "polsnap_01J...",
    "period": "2026-08",
    "role": "CLERK",
    "title": "PocketBuy Aug. 2026 Incentive Policy",
    "effective_from": "2026-08-01T00:00:00+01:00",
    "effective_to": "2026-09-01T00:00:00+01:00",
    "base_tiers": [
      { "metric": "ACH", "min": null, "min_inclusive": false, "max": "50", "max_inclusive": false, "reward_per_unit": "400.00" },
      { "metric": "ACH", "min": "50", "min_inclusive": true, "max": "80", "max_inclusive": false, "reward_per_unit": "500.00" },
      { "metric": "ACH", "min": "80", "min_inclusive": true, "max": null, "max_inclusive": false, "reward_per_unit": "600.00" }
    ],
    "observation_window": { "metric": "CPD7", "days": 90, "minimum_orders_for_positive_adjustment": 10 },
    "performance_tiers": [
      { "metric": "CPD7", "min": null, "min_inclusive": false, "max": "5", "max_inclusive": false, "adjustment_rate": "0.20", "settlement_action": "CURRENT_PERIOD_UPLIFT" },
      { "metric": "CPD7", "min": "5", "min_inclusive": true, "max": "10", "max_inclusive": false, "adjustment_rate": "0.10", "settlement_action": "CURRENT_PERIOD_UPLIFT" },
      { "metric": "CPD7", "min": "10", "min_inclusive": true, "max": "15", "max_inclusive": true, "adjustment_rate": "0.00", "settlement_action": "NO_ADJUSTMENT" },
      { "metric": "CPD7", "min": "15", "min_inclusive": false, "max": null, "max_inclusive": false, "adjustment_rate": "-0.20", "settlement_action": "NEXT_PAYOUT_DEDUCTION" }
    ],
    "notes": ["Bonuses for the previous month are paid on the 25th. A confirmed -0.2 adjustment from two months earlier is deducted in the same payout."],
    "visible_related_roles": []
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:20:30+01:00" }
}

店长响应 role=STORE_MANAGER,并可在 visible_related_roles 中包含完整店员展示政策;店员响应不得包含店长政策。CPD7>15% 返回 adjustment_rate=-0.20settlement_action=NEXT_PAYOUT_DEDUCTION,表示在下一发放月扣减原月基础奖励的 20%,不是当期 0.8 乘数。90 天观察窗内 observation_order_count<10 时,服务端将正向 effective_adjustment_rate 封顶为 0.00,并返回 small_sample_cap_applied=true;负向 -0.20 不被封顶。此接口只用于展示政策,不作为 H5 计算输入。

7.2 排行榜

GET /leaderboards?period=2026-08&type=CLERKS|STORES&limit=20

{
  "period": "2026-08",
  "type": "STORES",
  "items": [
    { "rank": 1, "display_name_masked": "Vi**** Mobile", "qualified_sales": 418, "trend": "UP", "is_my_store": false },
    { "rank": 3, "display_name_masked": "Ik**** Hub (Your store)", "qualified_sales": 372, "trend": "UP", "is_my_store": true }
  ],
  "metric": "QUALIFIED_SALES",
  "updated_at": "2026-08-06T10:30:00+01:00"
}

Stores 不返回 manager 字段;Clerks 使用 is_me,且不返回佣金、手机号或完整姓名。limit 最大 100,首页预览建议取 3,完整页取 20。

7.3 热销手机

GET /hot-products?period=2026-08&cursor=&page_size=20

{
  "items": [
    {
      "product_id": "sku-tecno-spark-30",
      "rank": 1,
      "brand": "Tecno",
      "model": "Spark 30",
      "sku": "SP30-8-128-SIL",
      "qualified_sales": 46,
      "commission_display": { "type": "FIXED", "min": "400.00", "max": "400.00", "currency": "NGN" },
      "image_url": "https://cdn.../spark30.webp",
      "effective_at": "2026-08-01T00:00:00+01:00"
    }
  ],
  "page": { "page_size": 20, "next_cursor": null, "has_more": false }
}

commission_display.typeFIXEDRANGECONTACT_MANAGER。该字段只用于预估展示。

8. 订单契约

8.1 列表

GET /orders?query=&date_from=2026-08-01&date_to=2026-09-01&status=QUALIFIED&cursor=&page_size=10

{
  "success": true,
  "data": {
    "items": [
      {
        "order_id": "ORD-20260805-4821",
        "short_id": "4821",
        "brand": "Tecno",
        "model": "Spark 30",
        "sku": "SP30-8-128-SIL",
        "imei_masked": "******7314",
        "customer_masked": "Customer ***1208",
        "created_at": "2026-08-05T09:26:00+01:00",
        "qualified_at": "2026-08-05T09:54:00+01:00",
        "status": "QUALIFIED",
        "commission_status": "EARNED",
        "display_reward_amount": "400.00",
        "currency": "NGN",
        "commission_id": "com_4821"
      }
    ],
    "page": { "page_size": 10, "next_cursor": "cur_...", "has_more": true },
    "summary": { "total_count": 49, "estimated_reward_total": "18650.00", "currency": "NGN" }
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:20:30+01:00", "data_as_of": "2026-08-06T11:15:00+01:00", "source": "LIVE" }
}

筛选结束时间采用左闭右开区间 [date_from, date_to)。搜索由后端在当前授权范围内执行。取消订单不计入 estimated_reward_total;具体统计口径需由佣金服务返回展示金额,订单服务不得重复计算佣金规则。

8.2 详情

GET /orders/{order_id} 返回列表字段及订单状态时间线、完整业务订单号、可选 commission_id。仍只返回脱敏客户与 IMEI。

错误:ORDER_NOT_FOUND(跨数据范围也统一 404)、INVALID_DATE_RANGEINVALID_CURSOR

9. 佣金契约

9.1 可查询月份

GET /commission-periods?limit=12

{
  "success": true,
  "data": {
    "current_period": "2026-08",
    "periods": [
      { "period": "2026-08", "status": "OPEN" },
      { "period": "2026-07", "status": "SETTLING" },
      { "period": "2026-06", "status": "SETTLED" }
    ]
  },
  "meta": { "trace_id": "trc_...", "server_time": "2026-08-06T11:20:30+01:00" }
}

9.2 月度汇总

GET /commissions/monthly-summary?period=2026-07

统一结构:

{
  "period": "2026-07",
  "period_status": "SETTLING",
  "order_count": 46,
  "estimated_commission": "10420.00",
  "earned_base_amount": "9320.00",
  "waiting_amount": "1100.00",
  "estimated_settlement_total": "10420.00",
  "settled_amount": "9320.00",
  "estimated_remaining_amount": "1100.00",
  "actual_settled_amount": null,
  "currency": "NGN",
  "risk_factor_status": "PENDING",
  "risk_factor": null,
  "calculation_version": "calc_2026_07_08",
  "policy_snapshot_id": "polsnap_...",
  "updated_at": "2026-08-06T10:30:00+01:00"
}

字段使用规则:

状态必须有值必须为空/为零
OPENorder_count/estimated_commission/earned_base_amount/waiting_amountsettled_amount=0,无结算记录
SETTLINGestimated_settlement_total/order_count/settled_amount/estimated_remaining_amountactual_settled_amount=null
SETTLEDactual_settled_amount/order_count预计字段可保留审计值,但前端不作为主展示
WITHHELDorder_count/withheld_reason按实际情况

9.3 订单佣金列表

GET /commissions?period=2026-08&status=&cursor=&page_size=10

{
  "items": [
    {
      "commission_id": "com_4821",
      "order_id": "ORD-20260805-4821",
      "order_short_id": "4821",
      "phone_name": "Tecno Spark 30",
      "status": "EARNED",
      "display_amount": "400.00",
      "paid_amount": "0.00",
      "pending_amount": "400.00",
      "qualified_at": "2026-08-05T09:54:00+01:00",
      "explanation": "Fixed clerk commission. Qualified device activation.",
      "currency": "NGN"
    }
  ],
  "page": { "page_size": 10, "next_cursor": null, "has_more": false }
}

9.4 单笔佣金详情

GET /commissions/{commission_id}

{
  "commission_id": "com_6214",
  "beneficiary_role": "CLERK",
  "order": { "order_id": "ORD-20260803-6214", "short_id": "6214", "phone_name": "Samsung Galaxy A16" },
  "status": "WAITING_METRIC",
  "base_amount": "400.00",
  "performance": {
    "period": "2026-08",
    "metric_name": "CPD7",
    "metric_status": "PENDING",
    "metric_value": null,
    "observation_days": 90,
    "observation_order_count": null,
    "adjustment_rate": null,
    "effective_adjustment_rate": null,
    "small_sample_cap_applied": null,
    "adjustment_amount": null,
    "order_scope_hash": null
  },
  "total_payable": null,
  "settlement_progress": { "scheduled_pay_date": "2026-09-25", "paid_amount": "0.00", "pending_amount": "400.00" },
  "explanation": "Base earned. Waiting for the risk factor result.",
  "policy_snapshot_id": "polsnap_...",
  "calculation_version": "calc_...",
  "qualified_at": "2026-08-03T13:06:00+01:00",
  "updated_at": "2026-08-06T10:30:00+01:00",
  "currency": "NGN"
}

total_payable 在风险系数未确认时为 null,不能用基础金额冒充最终应发。常规发放日为次月 25 日;若该绩效月产生 -0.2,扣减进入再下一个月的 25 日批次并引用本佣金/绩效快照。

10. 结算契约

10.1 列表

GET /settlements?period=2026-07&cursor=&page_size=10

{
  "items": [
    {
      "settlement_id": "set_202607_8842",
      "period": "2026-07",
      "batch_name": "August 25 Payout - July 2026",
      "status": "PAID",
      "net_payable": "9320.00",
      "scheduled_pay_date": "2026-08-25",
      "paid_at": "2026-08-25T15:20:00+01:00",
      "reference_masked": "PMT-***7732",
      "currency": "NGN"
    }
  ],
  "page": { "page_size": 10, "next_cursor": null, "has_more": false }
}

10.2 详情

GET /settlements/{settlement_id}

{
  "settlement_id": "set_202607_8842",
  "period": "2026-07",
  "batch_name": "August 25 Payout - July 2026",
  "status": "PAID",
  "gross_amount": "8700.00",
  "current_period_positive_adjustment": "800.00",
  "prior_period_negative_adjustment": "-180.00",
  "deduction_source_period": "2026-06",
  "manual_adjustment": "0.00",
  "clawback_amount": "0.00",
  "net_payable": "9320.00",
  "scheduled_pay_date": "2026-08-25",
  "paid_at": "2026-08-25T15:20:00+01:00",
  "reference_masked": "PMT-***7732",
  "currency": "NGN",
  "line_count": 46
}

period 表示本次发放的基础奖励月;deduction_source_period 表示负绩效来源月。净额小于 0 时状态返回 WITHHELD,不发起负数付款并转人工复核。若产品需要展示结算包含哪些订单,可后续增加 /settlements/{id}/lines,MVP 不强制。

11. 推荐码契约

GET /me/referral-code

{
  "referral_code": "PB-018-A7Q9",
  "status": "ACTIVE",
  "qr_payload": "https://pocketbuy.ng/r/tk_unguessable",
  "display_purpose": "For SA use when a customer buys goods with PocketBuy installments.",
  "store_name": "Ikeja Mobile Hub",
  "expires_at": null,
  "updated_at": "2026-08-06T09:00:00+01:00"
}

statusACTIVEINACTIVEEXPIRED。二维码图片可由前端根据 qr_payload 生成并保存品牌卡片;qr_payload 必须是可撤销的不可猜测令牌,不得拼接手机号。

店长响应必须由服务端将签发主体记录为 referrer_role=STORE_MANAGER。该二维码可作为推荐证据,但佣金侧只生成一次店长奖励,不得再生成独立推荐奖励或第二条店长奖励;H5 不自行判断叠加。

12. 店员数据访问边界

  • 门店端 H5 仅从订单和收益接口读取必要的店员归属信息,不存在独立人员管理接口。
  • 店长在订单/Earnings 中查看的店员名称、脱敏手机号、归属状态和同单收益,必须作为订单快照的只读字段返回,不可由前端写回人员主数据。
  • 店员新增/变更使用 SA App 的 Stores H5 及其后端契约,操作人必须为有权限的 SA/SM/RM,并保留操作人角色、授权范围、审核、版本和生效时间。

13. 配置与消息

GET /app-config

{
  "support_phone_e164": "+2347000000000",
  "support_phone_display": "+234 700 000 0000",
  "messages_enabled": true,
  "policy_banner_enabled": true,
  "minimum_supported_version": "1.0.0",
  "maintenance": { "enabled": false, "message": null }
}

GET /messages?cursor=&page_size=20

{
  "unread_count": 1,
  "items": [
    {
      "message_id": "msg_01J...",
      "title": "Your account verification is complete",
      "body": "Your SA-maintained profile is now active.",
      "category": "ACCOUNT",
      "read": false,
      "created_at": "2026-08-06T09:15:00+01:00",
      "deep_link": "pocketbuy://profile"
    }
  ],
  "page": { "page_size": 20, "next_cursor": null, "has_more": false }
}

categoryACCOUNTORDERCOMMISSIONSETTLEMENTPOLICYSYSTEM。MVP 若关闭,messages_enabled=false,前端隐藏入口。若支持标记已读,使用 POST /messages/{message_id}/read,幂等返回 { "read":true }

14. 审计、版本与数据来源

  • 写接口审计字段:操作者、角色、门店、请求 ID、幂等键、前后状态、业务原因、时间和 trace ID。
  • 财务查询至少关联 policy_snapshot_idcalculation_version;绩效确认后增加 metric_versionorder_scope_hash
  • 排行榜和热销商品返回聚合更新时间;前端不能把榜单行用作财务真值。
  • 订单、佣金、绩效和结算由不同服务提供时,BFF 负责统一字段、权限和 trace,不允许前端拼接多个未对齐的金额。
  • 字段语义变更必须新增 API/Schema 版本或保持向后兼容,不能只改同名字段含义。

15. HTTP 状态码与错误码

HTTP场景
200/201成功/创建成功
400格式、枚举、日期范围错误
401令牌缺失、过期、刷新失败
403角色无权限或账号停用
404资源不存在或不在数据范围
409关系冲突、幂等键负载冲突、状态不允许
413文件过大
422字段业务校验失败
429OTP/查询频控
500内部错误
503依赖暂不可用

通用错误码:VALIDATION_ERRORUNAUTHORIZEDFORBIDDENRESOURCE_NOT_FOUNDIDEMPOTENCY_CONFLICTRATE_LIMITEDSERVICE_UNAVAILABLEINTERNAL_ERROR

16. 缓存与一致性

  • /app-config:5 分钟;政策:按 policy_snapshot_id 缓存至失效;热销商品:5–15 分钟。
  • Dashboard、订单、佣金和结算属于用户敏感数据,只能使用用户隔离的加密缓存;注销时清除。
  • SA/SM/RM 提交的人员/关系变更生效后,后端失效受影响用户的 /me、Dashboard 和后续订单归属缓存;已固化历史订单/佣金/结算快照不失效。
  • 聚合页与详情可能存在短暂延迟时,响应 data_as_ofupdated_at 必须可见;财务结果以详情/账本服务为准。

17. 契约验收清单

  • OpenAPI 3.1 文件通过 lint,所有枚举、nullability、金额字符串和示例与本文一致。
  • 前后端针对每个接口各保存一组成功、空、权限失败、业务冲突和 5xx 示例。
  • Dashboard 当月汇总与 Commission 当月汇总自动契约测试相等。
  • 订单第一页和加载更多汇总值相等;筛选变化后游标失效。
  • 店员无法取得店长政策;店长无法取得其他门店店员。
  • 门店角色令牌仅具有当前契约定义的只读人员数据权限和本人唯一收款户维护权限;前端产物中无其他人员主数据写入 API client 和页面。
  • 佣金详情的 total_payable=null 在风险系数待确认时不会被前端转为 0 或基础金额。
  • CPD7 政策返回 90 天观察窗、10 单阈值和小样本标记;>15% 返回 -0.20/NEXT_PAYOUT_DEDUCTION,不得再按当期 80% 折减建模。
  • 每月 25 日结算详情分别展示上月奖励和上上月负绩效扣减;净额为负时不发起付款。
  • 首次登录/协议升级可查看并同意授权协议,版本化同意证据可回查;拒绝或版本不匹配不能登录。
  • 收款户接口确保每人同一时刻最多一条有效账户且自动为默认户,更换保留历史并固化付款快照。
  • BVN、完整手机号、完整 IMEI、客户信息不出现在响应、日志和埋点。
  • 推荐码令牌不可由店员 ID、手机号或门店码直接推导。