Files
atlas-seller-kit/docs/api/pricing.md
T
2026-09-28 17:34:33 +08:00

10 KiB
Raw Blame History

定价试算与门店方案 API

2026-09-28:维护者批准 SKU 就地试算、详情弹窗、随商品草稿原子确认。本契约替换原规划的独立 pricing-confirms 入口。 稳定 DTO 位于 packages/contracts/src/pricing.ts;金额、汇率和比例均使用十进制字符串。

1. 目录

GET /pricing/catalog?store_id={uuid}

需要已认证工作空间。返回 {data: PricingCatalog}:

  • store:门店 ID、名称及报价币种(CNY / RUB)。
  • rate_cards[]:该门店平台、RU / rFBS 的有效费率卡及运输方式。正式环境必须 active、在有效期内、有审核时间及审核人。development_only=true 表示本地开发模式临时可选的 draft 卡。
  • pending_rate_cards[]:该平台 RU / rFBS 待审核草稿的名称、版本和状态,仅用于说明阻断原因,不作为方案或报价可选项。
  • policies[]:该门店活动方案,默认方案优先,包含完整参数和 revision。
  • exchange_rate:CBR 最新 CNY→RUB 官方参考汇率,包含 ID、日期和 stale;缺少数据时为 null。超过 72 小时提示过期参考,不自动代填其他来源。

GET /pricing/categories?store_id={uuid}&q={text}

检索门店平台下活动商品类目,返回 {data:[{id,name,path}]},最多 50 条。q 最长 200 字;继续输入名称缩小范围。

2. POST /pricing/quote-previews

要求 Idempotency-Key。成功返回 201;同键同输入重放原响应,同键不同输入返回 409。

{
  "store_id": "<store UUID>",
  "platform_product_category_id": "<category UUID>",
  "fulfillment": "rfbs",
  "binding": {"profile_id":"<profile UUID>","variant_id":"<variant UUID>","client_key":null},
  "purchase_cost_cny": "28.00",
  "package": {"shipping_weight_g":"380","length_mm":"220","width_mm":"160","height_mm":"90","status":"confirmed"},
  "pricing": {
    "pricing_policy_id": "<policy UUID>",
    "rate_card_id": "<rate-card UUID>",
    "transport_method_ids": ["<transport UUID>"],
    "default_transport_method_id": "<transport UUID>",
    "domestic_shipping_and_label_fee_cny":"3",
    "ad_rate":"0.03","last_mile_rate":"0.02","withdrawal_rate":"0.01","other_rate":"0.02",
    "desired_markup_rate":"0.50","list_price_discount_rate":"0.20",
    "mode":"target_markup","selling_price":null,
    "fx":{"mode":"policy_fixed"},"temporary_commission_rate":null
  }
}
  • 独立试算 binding=null;SKU 必须提供 profile,已有 variant_id 与新建 client_key 二选一。验证资源归属,已上架 SKU 不接受新报价。
  • 包装输入是编辑草稿合并后的有效 g/mm 值,必须已确认;尺寸最多三位小数,重量至少 1 g。保存时从实际持久化草稿重新合并并核对,不能仅信任客户端摘要。
  • mode=target_markup:目标利润=采购成本×加价率;求解销售价。fixed_sale:selling_price 必填,按该售价核算利润。
  • 百分比费用以销售额为基数;除加价率外每项小于 1、最多六位小数。引擎拒绝总扣费率≥1。划线价=销售价÷(1−折扣率)。
  • 枚举运输方式×产品类型×佣金档,按最终币种舍入后回验货值、佣金、重量和体积重边界。默认运输不可用时 recommended_option_id=null,不替换为另一方式。
  • fx.mode=system 读取 PostgreSQL 最新 CBR;policy_fixed 必须绑定有固定汇率的方案;one_time_override 必须明确 rate 和 reason。固定汇率取方案值,不信任客户端覆盖。采用的汇率固定在本次预览中,15 分钟内确认不重新拉取外部汇率。
  • 只有不存在有效正式佣金规则且无方案或方案允许时,才能 temporary_commission_rate。已有正式规则禁止覆盖。

响应包含 preview_id,expires_at,input_hash,recommended_option_id,options,unavailable_options,context。 每个 option 包含目标币种销售/划线价、CNY/RUB 利润、计费重、佣金/物流引用、费用明细、warnings 和 can_confirm。 亏损可查看,但 can_confirm=false;判定使用未舍入利润,显示为 0.00 的微亏也不能确认。 context 保存规则版本、实际汇率与来源,以及系统汇率记录 ID / 日期。参数/规则快照由服务端持久化。

预览按用户、工作空间、门店、SKU、规范化输入与规则摘要绑定,15 分钟有效。Worker 删除过期两天以上的预览。

3. 商品保存时确认

PUT /products/{id}/editor-draft 沿用商品/profile revision 的 If-Match 和 Idempotency-Key,每个 variant 增加:

{
  "pricing_draft": "本次 PricingParameters 对象或 null",
  "pricing_selection": {"preview_id":"<UUID>","option_id":"<UUID>","input_hash":"<64-char SHA256>"}
}

两个字段均可省略以兼容既有客户端;读取 listing 返回 pricing_draft,pricing_result,pricing_valid。

确认在现有商品事务中执行:先保存草稿和新 SKU,再核对实际有效输入、用户绑定、有效期、审核规则摘要、币种及候选资格,最后创建不可变报价/候选快照并切换 listing 售价引用。 新 SKU 的 client_key 在同一事务映射到真实 ID。独立预览、其他用户或其他 SKU 的预览不能用于确认。

  • 携带失效确认意图:409 PRICING_PREVIEW_STALE,details 定位具体 SKU,整笔保存回滚。
  • 不携带确认意图:允许保存未定价草稿;输入/币种变化使报价失效。用户可在冲突弹窗明确选择“仅保存草稿,稍后定价”。
  • 未变化的已确认报价继续使用原快照,无时间过期,不重新读规则或汇率。方案更新/归档不改变其价格。
  • 发布校验要求 pricing_valid=true,无有效报价返回 PRICING_REQUIRED;已导入 SKU 沿用既有不可再次首次上架边界。
  • 数据库复合外键保证 quote/profile/product/variant/store/platform 归属,RLS 隔离租户。确认快照拒绝 UPDATE;成本、包装、类目、店铺或币种变化触发失效。

4. 门店方案

方法 路径 约束
GET /stores/{id}/pricing-policies 返回当前门店活动方案数组
POST /stores/{id}/pricing-policies Idempotency-Key;成功 201
PATCH /pricing-policies/{id} Idempotency-Key + If-Match(带引号的 revision);冲突 409
POST /pricing-policies/{id}/set-default Idempotency-Key;门店锁保证唯一默认;成功 200
DELETE /pricing-policies/{id} Idempotency-Key;归档并清除默认标志,返回 {id,archived:true}

写入 {name,note,parameters,allow_commission_override}。只保存费用、加价、折扣、固定/系统汇率与运输偏好;采购价、包装、手动售价及临时佣金不属于方案持久字段。 方案的默认和比较运输必须属于同一可用费率卡;正式环境仅允许已审核卡。历史快照不会被方案修改;尚未确认的预览因方案版本改变要求重算。

5. 数据启用与错误

迁移中的 CEL 目录为 draft,覆盖六种产品类型、三种运输方式,香港空运不纳入。真实账单复核、有效日期/审核人以及平台类目佣金映射仍是正式使用前置条件,接口不会回退到示例费率或固定佣金。

商品编辑联调期间,API 在 NODE_ENV=development 时默认允许 draft 费率卡参与方案保存、预览与草稿报价确认;可设 PRICING_ALLOW_DRAFT_RATES=false 关闭。测试环境只有显式设 PRICING_ALLOW_DRAFT_RATES=true 才开启;生产环境始终关闭。该模式不修改费率卡状态、不生成审核记录。目录的 rate_cards[].development_only 和预览的 context.development_only 标识试算性质;基于此类预览确认的 SKU 报价可在编辑页查看,但首次上架校验必须返回 PRICING_RATE_REVIEW_REQUIRED,直至使用已审核费率重新试算确认。佣金和汇率仍按现有输入规则处理。

系统管理员审核 CEL 费率

系统管理「物流费率审核」使用以下接口;全部要求登录且具备实时有效的 system_admin_grants 授权,普通门店用户返回 403 SYSTEM_ADMIN_REQUIRED:

方法 路径 行为
GET /system/pricing/rate-cards?platform_id={uuid} 查看该平台 RU/rFBS 草稿及已启用费率,含产品类型、规则、审核记录与 rules_hash
GET /system/pricing/rate-cards/{id} 重读单个费率版本
POST /system/pricing/rate-cards/{id}/reviews Idempotency-Key UUID 必填;原子保存审核证据并启用费率,成功 201

审核请求包含 rules_hash、source_reference(资费合同/实际账单批次)、至少 20 字的 review_note、effective_from(含时区 ISO 时间)和 samples。每条样本包含 rule_id、唯一的 invoice_reference、merchandise_value_cny、包装重量 g / 长宽高 mm、billed_logistics_cny。每种 CEL 产品类型至少一条,合计不少于六条。服务端使用与正式报价相同的包装适用性和运费函数复算;样本货值须命中对应类型,账单运费与规则计算差额不得超过 0.01 CNY。真实账单来源和口径仍由审核人负责确认,系统不会凭示例数字自行审核。

仅支持 Ozon/CEL 草稿版本。规则必须仍为 16 条 draft,类型为指定六种,且 rules_hash 与管理员查看时一致;否则返回 409/422。通过后在同一数据库事务保存不可修改的审核记录、把规则与费率卡改为 active、设置审核人与生效日期。若生效日期在未来,直到该时间才出现在卖家端可用费率中。数据库拒绝改写已审核的费率卡、产品类型、运输方式和规则;修改资费应新建版本重新审核,不覆盖已确认 SKU 报价。已启用费率不提供再次审核入口。

系统管理不会自动配置平台类目佣金映射;缺少正式佣金规则时,卖家试算仍需满足临时佣金覆盖条件。当前 CEL 种子保持 draft,尚无真实账单证据时不得提交启用。

HTTP / code 用户动作
422 VALIDATION_ERROR 修正输入格式或缺项
422 PRICING_RULES_UNAVAILABLE 配置并审核物流费率
422 PRICING_COMMISSION_UNAVAILABLE 配置正式映射,或使用允许临时佣金的方案
422 PRICING_FX_UNAVAILABLE 等待 CBR 同步,或明确采用临时/方案固定汇率
409 PRICING_PREVIEW_STALE 重算、比较并重新应用,或显式仅保存草稿
409 VERSION_CONFLICT 重新读取版本后修改

独立上下文、汇率信息整合在 catalog;本期不开放原规划的独立 context、exchange-rate、pricing-confirms 路由和报价历史列表。