易览云全域旅游系统
渠道开放 API — 第三方对接开发文档
面向对象:渠道方 / 供应商自有网站或小程序的开发人员
版本:v1.3(2026-09-24)
模式:贵方收款 → 调本站锁单与支付确认 → 本站按结算价扣商家预充值或占授信并履约
在线阅读:https://www.elanyun.cn/doc/commerce-channel-api.html
覆盖品类:commerce 已接入的全部来源(mall / hotel / ticket / venue / travel / carrent / rent / pointsmall)
1. 业务说明(先读)
| 角色 | 做什么 |
|---|---|
| 贵方(渠道) | 展示商品、向用户收款/退款、调用本站 API |
| 本站(平台) | 商品/库存/订单履约;按结算价扣商家预充值或记授信 |
| 商家(供应商) | 在本站配置预充值或授信额度;不直接对接本 API(除非自己也是渠道) |
钱怎么流
- 用户付钱给贵方(微信/支付宝等,贵方自己的商户号)
- 贵方调本站
order_pay_notify - 本站从商家账户扣结算价(预充值余额减少,或授信已用增加)
- 本站负责发货/核销等履约
贵方零售价可自定;本站只认 channel_settle_amount(结算价)。
推荐主流程
products / product_detail(标价与基础信息)
↓
price_quote(日期类:日历 / 连住 / 场次询价)← hotel/ticket/venue/travel 必调
↓
merchant_quota(可选,下单前看额度)
↓
order_hold(锁库存,校验额度,不扣款)
↓
贵方向用户收款
↓
order_pay_notify(成功)──→ 本站履约
↓ 失败(如额度不足)
贵方立即退款给用户
退款:
贵方先退用户 → order_refund_notify → 本站回滚额度与库存
关于价格(重要)
| 字段 | 含义 |
|---|---|
product_detail.price |
标价/底价,来自商品基础信息,不含日历日期价 |
product_detail.channel_settle_price |
未带日期时多为估算;settle_is_estimate=true 时不能当成交价 |
price_quote.channel_settle_price |
指定日期后的渠道结算价,与 hold 扣款一致 |
order_hold.channel_settle_amount |
最终以锁单返回为准 |
日期类(hotel / ticket / venue / travel / carrent):详情看介绍 → price_quote 定价 → hold。
固定价(mall / rent / pointsmall)可直接用详情结算价。
限制(请遵守)
- 单笔订单暂不支持多个供应商商品,请拆单
- 锁单默认约 30 分钟超时,超时自动取消并恢复库存
hold不扣商家额度;真正扣/占在pay_notify- 结算价为
0的商品不会出现在列表,也无法 hold(如纯积分商品) - 酒店/门票/场地等日期类商品,锁单须在
items[].extra带齐业务字段(见 4.4 extra 总表) - 开放渠道仅能售预充值商家商品(见 2.1)
1.1 支持的商品来源(source)
| source | 说明 | product_id 含义 | sku_id | 结算价规则 | 锁单 extra 要点 |
|---|---|---|---|---|---|
mall |
商城 | 商品 ID | SKU ID,无规格传 0 | cost_price>0 用成本,否则用售价 |
一般无需 |
hotel |
酒店房型 | 房型 ID | 0 | 须 price_quote(check_in/check_out) 连住总价 |
必填 check_in/check_out;quantity=间数 |
ticket |
门票 | 票种 ID | price_quote 返回的日库存行 ID | 按 booking_date 日报价 |
传询价得到的 sku_id |
venue |
场地 | 场地 ID | 当日时段 ID | price_quote(booking_date) 时段价 |
sku_id + extra.booking_date |
travel |
旅游套餐 | 套餐 ID | 套餐 SKU | price_quote 日期价 |
sku_id + extra.booking_date |
carrent |
租车 | 车辆 ID | 0 | 按 rent_type 单价 |
可选 rent_type(日/周/月);quantity=租期数 |
rent |
租赁商品 | 商品 ID | 0 | 日租/套餐价 | 可选 use_plan |
pointsmall |
积分商城 | 商品 ID | 0 | 仅现金价;纯积分不可渠道售 | 无 |
先调 sources 接口查看当前租户实际已开通(registered=true)的来源。
2. 接入准备
向平台运营索取:
| 配置项 | 说明 |
|---|---|
tenant_id |
租户 ID(请求头 X-Tenant-Id) |
app_key |
渠道应用 Key |
app_secret |
渠道应用 Secret(仅线下交付,勿写进前端) |
| API 域名 | 如 https://www.example.com |
可选:IP 白名单(后台配置后,非白名单 IP 将被拒绝)。
2.1 渠道类型与结算模式(必读)
后台创建「渠道应用」时有两类:
| channel_type | 名称 | 商家结算限制 |
|---|---|---|
1 签约合作 |
平台签约渠道 | 允许预充值与/或授信(以后台 allow_bill_modes 为准,默认 1,2) |
2 开放接入 |
开放 API | 仅允许预充值商家(channel_bill_mode=1);授信商家商品 lock 会失败 |
商家账户:
| channel_bill_mode | 含义 | pay_notify 行为 |
|---|---|---|
1 |
预充值 | 扣减 prepaid_balance(ledger:consume) |
2 |
授信 | 增加 credit_used(ledger:credit_occupy) |
账户 status=0 冻结时不可 hold / pay。
3. 通用约定
3.0 金额与精度
- 所有金额字段(请求与响应)一律为 两位小数字符串,如
"36.00" - 本站用字符串精确比较(
bccomp);禁止用浮点直接比对 channel_settle_amount必须与锁单返回值 完全一致(含两位小数)- 数量
quantity为正整数;酒店=间数,租车=租期数
3.1 Base URL
https://{域名}/plugin.php/commerce/channel_api/{动作}.html
示例:
GET https://www.example.com/plugin.php/commerce/channel_api/products.html?source=mall&page=1&limit=20
3.2 请求头(必填)
| Header | 说明 |
|---|---|
X-Tenant-Id |
租户 ID |
X-App-Key |
AppKey |
X-Timestamp |
当前 Unix 时间戳(秒) |
X-Nonce |
随机字符串(建议 UUID) |
X-Sign |
签名(见下) |
Content-Type |
POST 时用 application/json |
也可用 Query 传 app_key / timestamp / nonce / sign(不推荐,优先 Header)。
3.3 签名算法
待签字符串 = app_key + timestamp + nonce + body
X-Sign = UPPERCASE( HEX( HMAC-SHA256( app_secret, 待签字符串 ) ) )
body 怎么取
| 方法 | body |
|---|---|
| GET | 业务 Query 参数按 key 字典序 拼成 a=1&b=2(不含 app_key/timestamp/nonce/sign/s) |
| POST | HTTP 原始 Body(一般是 JSON 原文,须与发送内容完全一致) |
时间戳与服务器相差超过约 300 秒(可按应用配置 notify_tol,最小 60)会判过期。
空业务参数的 GET(如 sources)
无业务 Query 时,签名用的 body 为 空字符串:
待签 = app_key + timestamp + nonce + ""
PHP:$body = ''; 再算 HMAC。不要传 null,也不要拼多余的 =。
3.4 统一响应
{
"code": 1,
"info": "ok",
"data": { }
}
| code | 含义 |
|---|---|
1 |
成功 |
0 |
失败(看 info;data 可能带附加信息) |
3.5 PHP 签名示例
function channelSign(string $secret, string $appKey, string $timestamp, string $nonce, string $body): string
{
return strtoupper(hash_hmac('sha256', $appKey . $timestamp . $nonce . $body, $secret));
}
// GET
$query = ['source' => 'mall', 'page' => '1', 'limit' => '20'];
ksort($query);
$body = http_build_query($query); // a=1&b=2
$sign = channelSign($secret, $appKey, $ts, $nonce, $body);
// POST
$json = json_encode($payload, JSON_UNESCAPED_UNICODE);
$sign = channelSign($secret, $appKey, $ts, $nonce, $json);
// 请求时必须发送同一份 $json 作为 Body
3.6 Node.js 签名示例
const crypto = require('crypto');
function channelSign(secret, appKey, timestamp, nonce, body) {
return crypto
.createHmac('sha256', secret)
.update(appKey + timestamp + nonce + body)
.digest('hex')
.toUpperCase();
}
4. 接口一览
| 动作 | Method | 说明 |
|---|---|---|
| sources | GET | 查询已支持/已注册来源 |
| products | GET | 商品列表(可全源聚合) |
| product_detail | GET | 商品详情 + 标价(日期价见下) |
| price_quote | GET | 日期/日历询价(酒店连住、门票日价、场地时段等) |
| merchant_quota | GET | 查询可用额度 |
| order_hold | POST | 锁库存、创建待支付订单 |
| order_pay_notify | POST | 用户已付款,确认成交 |
| order_query | GET | 查单 |
| order_refund_notify | POST | 退款回滚 |
| — | — | 另见 幂等与状态机、常见错误 |
4.0 商品来源 sources
GET .../sources.html
无业务 Query。用于发现当前租户开通了哪些 commerce 品类。
成功 data
{
"list": [
{"source": "mall", "name": "商城实物/虚拟", "registered": true},
{"source": "hotel", "name": "酒店房型", "registered": true},
{"source": "ticket", "name": "门票票种", "registered": true},
{"source": "venue", "name": "场地预订", "registered": true},
{"source": "travel", "name": "旅游套餐", "registered": true},
{"source": "carrent", "name": "租车车辆", "registered": true},
{"source": "rent", "name": "租赁商品", "registered": false},
{"source": "pointsmall", "name": "积分商城", "registered": true}
]
}
仅 registered=true 的来源可拉商品与下单。
4.1 商品列表 products
GET .../products.html
Query
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| source | string | 否 | 留空=聚合全部已注册来源;指定则只查该源(如 mall/hotel) |
| keyword | string | 否 | 关键词 |
| page | int | 否 | 默认 1 |
| limit | int | 否 | 默认 20,最大 50 |
成功 data 示例
{
"list": [
{
"source": "mall",
"product_id": 1001,
"title": "海南椰子糖",
"cover": "https://cdn.example.com/p.jpg",
"price": "29.90",
"channel_settle_price": "18.00",
"stock": 200,
"supplier_id": 12,
"channel_bill_mode": 2,
"quota_available": "50000.00",
"need_shipping": true,
"need_verify": false,
"sort": 100
},
{
"source": "hotel",
"product_id": 88,
"title": "海景大床房",
"cover": "https://cdn.example.com/room.jpg",
"price": "388.00",
"channel_settle_price": "388.00",
"stock": 0,
"supplier_id": 5,
"channel_bill_mode": 2,
"quota_available": "80000.00",
"need_shipping": false,
"need_verify": true,
"sort": 10
}
],
"total": 2,
"page": 1,
"limit": 20
}
| 字段 | 说明 |
|---|---|
| source | 来源编码,下单必须原样回传 |
| price | 本站参考标价;贵方可另设售价 |
| channel_settle_price | 结算单价(成交按此×数量扣商家) |
| channel_bill_mode | 1 预充值 / 2 授信 |
| quota_available | 所属商家当前可用额度 |
列表价注意
- 结算价为 0 的商品会被过滤,不出现在列表中。
- 日期类(hotel / ticket / venue / travel 等)列表里的
price/channel_settle_price仅为标价估算,与真实成交价可能不一致;下单前必须再调price_quote。 - 固定价品类(mall / rent / pointsmall)列表结算价一般可直接用于 hold。
4.2 商品详情 product_detail
GET .../product_detail.html
注意:本接口复用各品类基础
getProductInfo,返回的是标价/底价。
酒店/门票/场地/旅游等有日历价的品类,请用 price_quote 询价后再 hold,不要直接用这里的channel_settle_price当成交价。
Query
| 参数 | 必填 | 说明 |
|---|---|---|
| source | 是 | 来源编码 |
| product_id | 是 | 商品/房型/票种等 ID |
| check_in / check_out | 否 | 酒店:若传入则 channel_settle_price 按连住估算(仍推荐专用询价) |
| booking_date | 否 | 旅游:日期价估算 |
| rent_type | 否 | 租车:租期类型 |
成功 data(节选)
{
"source": "hotel",
"source_name": "酒店房型",
"product_id": 88,
"title": "海景大床房",
"cover": "https://cdn.example.com/room.jpg",
"price": "388.00",
"price_mode": "list_only",
"channel_settle_price": "388.00",
"settle_is_estimate": true,
"supplier_id": 5,
"channel_bill_mode": 2,
"need_shipping": false,
"need_verify": true,
"extra_hint": {
"required": ["check_in", "check_out"],
"note": "详情 price 仅为标价。请先调 price_quote(check_in/check_out) 再 hold;quantity=间数"
},
"specs": [],
"skus": [],
"stores": [],
"quota_available": "80000.00"
}
| 字段 | 说明 |
|---|---|
price |
标价/底价 |
price_mode |
list_only=未带日期的日期商品;dated=已带日期;fixed=固定价品类 |
settle_is_estimate |
true 时结算价仅为估算,须再调 price_quote |
请阅读返回的 extra_hint。有 skus 时下单传对应 sku_id,无规格传 0。
4.2.1 日期询价 price_quote
GET .../price_quote.html
日期类商品的正式成交结算价与可售库存由此接口给出。
酒店 source=hotel
| 模式 | Query | 返回要点 |
|---|---|---|
| 连住询价 | product_id + check_in + check_out |
channel_settle_price(1 间整段)、nights、available、daily_prices、extra_for_hold |
| 月日历 | product_id + month=YYYY-MM |
daily_prices 每晚价 |
| 区间日历 | product_id + start + end |
同上(跨度≤93 天) |
{
"source": "hotel",
"product_id": 88,
"mode": "range",
"check_in": "2026-10-01",
"check_out": "2026-10-03",
"nights": 2,
"available": 3,
"retail_total": "880.00",
"channel_settle_price": "776.00",
"daily_prices": [
{"date": "2026-10-01", "price": "388.00", "available": 3},
{"date": "2026-10-02", "price": "492.00", "available": 3}
],
"extra_for_hold": {"check_in": "2026-10-01", "check_out": "2026-10-03"}
}
门票 source=ticket
| 模式 | Query | 返回要点 |
|---|---|---|
| 单日 | product_id + booking_date(或 date) |
sku_id(日库存行,可直接 hold)、结算价、余量 |
| 日历 | product_id + start + end |
daily_prices;其中 sku_id=0 表示尚未物化,不可直接 hold;选定日期后必须再调单日询价拿正式 sku_id |
旅游 source=travel
| 模式 | Query | 返回要点 |
|---|---|---|
| 单日 | product_id + sku_id + booking_date |
当日结算价 |
| 日历 | product_id + sku_id + start + end |
daily_prices |
场地 source=venue
product_id + booking_date → slots[](含各时段 sku_id 与结算价)。
租车 source=carrent
product_id + rent_type(1 日 / 2 周 / 3 月)→ 单价结算价。
固定价
mall / rent / pointsmall 也可调,返回 mode=fixed 与结算价(与详情一致)。
4.3 商家额度 merchant_quota
GET .../merchant_quota.html
Query
| 参数 | 必填 | 说明 |
|---|---|---|
| supplier_id | 否 | 供应商 ID;0 或不传表示平台自营账户 |
成功 data
{
"subject_type": "supplier",
"subject_id": 12,
"channel_bill_mode": 2,
"channel_bill_mode_text": "credit",
"prepaid_balance": "0.00",
"credit_limit": "100000.00",
"credit_used": "50000.00",
"quota_available": "50000.00",
"status": 1,
"status_text": "normal",
"warnings": [],
"low_balance": false
}
status=0 表示账户冻结,不可成交。warnings 可能含「余额偏低」「额度将尽」等提示。
4.4 锁单 order_hold
POST .../order_hold.htmlContent-Type: application/json
Body
{
"out_order_no": "CH202609240001",
"contact_name": "张三",
"contact_phone": "13800138000",
"address": {
"province": "海南省",
"city": "海口市",
"district": "龙华区",
"detail": "某某路1号"
},
"items": [
{
"source": "mall",
"product_id": 1001,
"sku_id": 2001,
"quantity": 2
}
],
"remark": "渠道锁单"
}
酒店示例 item
{
"source": "hotel",
"product_id": 88,
"sku_id": 0,
"quantity": 1,
"extra": {
"check_in": "2026-10-01",
"check_out": "2026-10-03",
"guest_name": "张三"
}
}
门票示例 item(sku_id 来自 price_quote 单日询价)
{
"source": "ticket",
"product_id": 15,
"sku_id": 9001,
"quantity": 2,
"extra": {
"booking_date": "2026-10-01"
}
}
旅游示例 item
{
"source": "travel",
"product_id": 20,
"sku_id": 301,
"quantity": 2,
"extra": {
"booking_date": "2026-10-05",
"guest_name": "张三",
"guest_phone": "13800138000"
}
}
场地示例 item
{
"source": "venue",
"product_id": 7,
"sku_id": 5501,
"quantity": 1,
"extra": {
"booking_date": "2026-10-02"
}
}
租车示例 item
{
"source": "carrent",
"product_id": 3,
"sku_id": 0,
"quantity": 2,
"extra": {
"rent_type": 1,
"customer_name": "张三",
"customer_phone": "13800138000",
"pickup_time": "2026-10-01 10:00:00",
"expect_return_time": "2026-10-03 10:00:00"
}
}
锁单 items[].extra 字段总表
| source | 必填 extra / 关键字段 | 建议 extra | 说明 |
|---|---|---|---|
mall |
无 | — | 有规格必传 sku_id |
hotel |
check_in、check_out(YYYY-MM-DD) |
guest_name、guest_phone、guest_count |
quantity=间数;结算价=连住单价×间数 |
ticket |
sku_id=询价返回的日库存行 |
booking_date(建议与询价日一致) |
日历接口里 sku_id=0 不可 hold |
venue |
sku_id=询价 slots[].sku_id |
booking_date |
时段物化行 ID,不是模板 slot_id |
travel |
sku_id + booking_date |
guest_name、guest_phone |
有日期价时必须带 booking_date |
carrent |
建议 rent_type(1 日/2 周/3 月) |
customer_name、customer_phone、customer_id_card、pickup_time、expect_return_time、pickup_store_id |
quantity=租期数 |
rent |
无 | use_plan、rent_type、start_at、store_id、customer_name、customer_phone |
套餐租可设 use_plan=1 |
pointsmall |
无 | — | 仅现金结算价>0 可售 |
另:顶层 contact_name / contact_phone 建议始终填写(写入渠道虚拟会员与取货联系人)。需要快递时传 address;纯核销类可不传。
可选顶层字段:distributor_id(绑定分销员;默认可用渠道应用上的绑定分销员)。
| 字段 | 必填 | 说明 |
|---|---|---|
| items | 是 | 商品行,同单须同一供应商;可混合同一供应商下不同 source(不推荐) |
| items[].source | 是 | 来源编码 |
| items[].product_id | 是 | 见第 1.1 节 |
| items[].sku_id | 视品类 | 无规格传 0;门票/场地/旅游见上表 |
| items[].quantity | 是 | 数量;酒店=间数;租车=租期数 |
| items[].extra | 视品类 | 见上表 |
| out_order_no | 建议 | 贵方单号,防重复锁单 |
| contact_name / contact_phone | 建议 | 联系人 |
| address | 否 | 有则按快递地址写入;虚拟/核销类可不传 |
成功 data(节选)
{
"order_id": 90001,
"order_no": "C20260924153000001",
"out_order_no": "CH202609240001",
"status": 0,
"status_text": "待支付",
"retail_amount": "59.80",
"channel_settle_amount": "36.00",
"channel_bill_mode": 2,
"hold_expire_at": "2026-09-24 16:00:00",
"quota_available_after_hold": "49964.00",
"items": [
{
"product_id": 1001,
"sku_id": 2001,
"quantity": 2,
"price": "29.90",
"channel_settle_price": "18.00",
"channel_settle_amount": "36.00"
}
]
}
请保存 order_no 与 channel_settle_amount,支付通知必须一致。hold 只校验额度、不扣额度;quota_available_after_hold 为校验后可用额度快照。
常见失败
- 商家可用额度不足
- 库存不足 / 未配置结算价
- 多供应商混单
out_order_no已存在(同渠道下待支付/已支付/已发货/已完成)- 开放渠道遇到授信商家:
开放渠道仅支持预充值模式商家商品
4.5 支付通知 order_pay_notify
POST .../order_pay_notify.html
在贵方确认用户支付成功后调用。
{
"order_no": "C20260924153000001",
"out_order_no": "CH202609240001",
"partner_trade_no": "P20260924WX998877",
"paid_amount": "59.80",
"channel_settle_amount": "36.00",
"paid_at": "2026-09-24 15:35:10"
}
| 字段 | 必填 | 说明 |
|---|---|---|
| order_no 或 out_order_no | 二选一 | 定位订单 |
| partner_trade_no | 是 | 贵方支付流水号(幂等键) |
| channel_settle_amount | 是 | 必须等于锁单返回值(两位小数字符串) |
| paid_amount | 建议 | 贵方实收用户金额(仅快照) |
成功 data(节选)
{
"order_id": 90001,
"order_no": "C20260924153000001",
"status": 1,
"status_text": "已支付",
"channel_settle_amount": "36.00",
"partner_trade_no": "P20260924WX998877",
"payment_time": "2026-09-24 15:35:12",
"hold_expire_at": "2026-09-24 16:00:00",
"items": [ ],
"ledger": {
"change_type": "consume",
"amount": "36.00",
"quota_available": "49964.00"
}
}
| ledger.change_type | 含义 |
|---|---|
consume |
预充值扣减 |
credit_occupy |
授信占用 |
重复通知:同一订单已支付时幂等返回成功(ledger 可能带历史扣款信息)。
失败且用户已付款时(重要)
若返回如「商家可用额度不足」,订单不会变成已支付。贵方必须:
- 立即退款给用户
- 联系平台给商家充值/提高授信后,可重新 hold 或协商处理
若履约回调失败,本站会尝试回滚已扣额度,订单保持待支付,可用新 partner_trade_no 重试或取消后重新 hold。
响应可能带:
{
"code": 0,
"info": "商家可用额度不足,请退款给用户后联系平台充值或回款",
"data": { "suggest": "refund_user" }
}
4.6 订单查询 order_query
GET .../order_query.html
Query(三选一)
| 参数 | 说明 |
|---|---|
| order_no | 本站订单号 |
| out_order_no | 贵方订单号 |
| partner_trade_no | 贵方支付单号 |
成功 data 示例
{
"order_id": 90001,
"order_no": "C20260924153000001",
"out_order_no": "CH202609240001",
"partner_trade_no": "P20260924WX998877",
"status": 1,
"status_text": "已支付",
"retail_amount": "59.80",
"pay_amount": "59.80",
"channel_settle_amount": "36.00",
"channel_bill_mode": 1,
"payment_type": "channel_partner",
"payment_time": "2026-09-24 15:35:12",
"hold_expire_at": "2026-09-24 16:00:00",
"items": [
{
"source": "mall",
"product_id": 1001,
"sku_id": 2001,
"title": "海南椰子糖",
"quantity": 2,
"price": "29.90",
"channel_settle_price": "18.00",
"channel_settle_amount": "36.00"
}
]
}
订单状态 status
| 值 | 含义 | 渠道侧建议动作 |
|---|---|---|
| 0 | 待支付(锁单中) | 可 pay_notify;超时后会变取消,需重新 hold |
| 1 | 已支付 | 可对账;退款走 refund_notify |
| 2 | 已发货 | 履约中 |
| 3 | 已完成 | 终态(仍可能支持退款视业务) |
| 4 | 已退款 | 终态;refund 幂等 |
| 5 | 已取消 | 锁单超时或取消;不可再 pay |
| 6 | 退款申请中 | 处理中 |
本查询不返回核销码、物流轨迹等履约明细;如需展示请联系平台另议或走运营后台。
详情/列表也不提供富文本content/ 多图相册;当前开放接口以交易字段为主。
4.7 退款通知 order_refund_notify
POST .../order_refund_notify.html
请先完成对用户的退款,再调本接口。
{
"order_no": "C20260924153000001",
"partner_refund_no": "R20260924WX001",
"refund_settle_amount": "36.00",
"refund_retail_amount": "59.80",
"reason": "用户取消",
"refunded_at": "2026-09-24 18:00:00"
}
| 字段 | 必填 | 说明 |
|---|---|---|
| order_no 或 out_order_no | 二选一 | 定位订单 |
| partner_refund_no | 是 | 贵方退款单号(幂等) |
| refund_settle_amount | 否 | 默认按整单结算价回滚;不可大于锁单结算价 |
成功 data(节选)
{
"order_id": 90001,
"order_no": "C20260924153000001",
"status": 4,
"status_text": "已退款",
"channel_settle_amount": "36.00",
"ledger": {
"change_type": "refund_prepaid",
"amount": "36.00",
"quota_available": "50000.00"
}
}
| ledger.change_type | 含义 |
|---|---|
refund_prepaid |
预充值退回 |
refund_credit |
授信占用释放 |
(具体文案以接口返回为准;同一 partner_refund_no 幂等。)
本站会:回滚商家额度、恢复库存、订单置已退款。
不会给用户打款(用户款由贵方处理)。
4.8 幂等与状态机
hold(status=0) ──超时──→ 取消(5) (须重新 hold)
│
└── pay_notify 成功 ──→ 已支付(1) → 发货(2) → 完成(3)
│ │
│ 失败(额度不足等) └── refund_notify ──→ 已退款(4)
└── 订单仍为 0;用户已付款则贵方立即退用户
| 场景 | 行为 |
|---|---|
同一 out_order_no 再次 hold |
若已有有效单 → 报「已存在」 |
同一 partner_trade_no 重复 pay |
已支付则幂等成功 |
| 锁单过期后再 pay | 失败「锁单已过期,请重新 hold」 |
| 已取消/已退款再 pay | 失败「订单状态不可支付」 |
同一 partner_refund_no 重复退 |
已退款则幂等成功 |
| pay 履约失败 | 额度回滚,订单保持待支付,可换 trade_no 重试 |
4.9 常见错误 info(节选)
| info(或包含) | 阶段 | 处理建议 |
|---|---|---|
| 缺少渠道鉴权参数 | 鉴权 | 检查 Header |
| 渠道应用无效或已禁用 | 鉴权 | 联系平台开通 |
| 请求已过期,请校准时间戳 | 鉴权 | NTP;核对 notify_tol |
| IP 不在白名单 | 鉴权 | 加白或去掉白名单 |
| 签名校验失败 | 鉴权 | 核对 body 原文/GET 排序/空 body |
| 商家可用额度不足 | hold/pay | 充值或授信;pay 失败则退用户 |
| 商家渠道账户已冻结 | hold/pay | 联系平台解冻 |
| 开放渠道仅支持预充值模式商家商品 | hold | 换预充值商家,或改渠道类型 |
| 单笔渠道单暂不支持多个供应商 | hold | 拆单 |
| out_order_no 已存在 | hold | 改单号或 order_query |
| channel_settle_amount 与锁单不一致 | pay | 用 hold 返回值,勿重算 |
| 锁单已过期,请重新 hold | pay | 重新 hold → 收款 → notify |
| partner_trade_no / partner_refund_no 必填 | pay/refund | 补流水号 |
| 订单不存在 | 查/付/退 | 核对租户与渠道 App |
| 退款结算金额不能超过锁单结算价 | refund | 调低金额 |
失败统一:{ "code": 0, "info": "…", "data": … }。data.suggest=refund_user 时表示用户侧应退款。
5. 联调检查清单(给开发)
- 先调
sources,只对接registered=true的品类 - 确认本渠道是「签约」还是「开放」;开放渠道勿选授信商家商品
- 日期类商品:必须先
price_quote再order_hold;门票日历里sku_id=0不可 hold - 签名:用同一份 POST JSON 算签并发送;
sources等无参 GET 的 body 为空串 - GET 参数按 key 字典序拼串再签
- 时钟同步(NTP),避免 timestamp 过期
- 金额一律两位小数字符串;
pay_notify.channel_settle_amount必须等于 hold 返回值 - hold → 再收款 → pay_notify;不要未 hold 就 notify
- pay_notify 失败且用户已付:立即退用户;见
data.suggest=refund_user partner_trade_no/partner_refund_no全局唯一;用order_query对账- Secret 只放服务端,勿进小程序/前端包
- 本 API 不返回富文本详情与核销码/物流;展示页字段以交易字段为准
6. 责任边界(合同建议引用)
| 事项 | 责任方 |
|---|---|
| 对用户收款、退款、客诉(支付相关) | 渠道 |
| 商品信息、库存、履约、核销 | 平台 |
| 商家预充值/授信额度 | 平台 + 商家 |
| AppSecret 保管 | 渠道(泄露须立即联系平台重置) |
7. 联系与变更
- 密钥重置、额度调整、IP 白名单:联系平台运营后台处理
- 接口变更以本文档版本号为准;重大变更会另行通知