渠道开放接口对接文档 v2.0

本系统向签约渠道提供 H5 实物电商的下单、支付、退款、查询能力。支付产品为支付宝手机网站支付(alipay.trade.wap.pay)。

接入红线(服务端强制):
① 下单方式二选一:
  方式A(金额模式,推荐):不传 sku_id,只传 target_amount(应付金额,单位:元)。实付金额即 target_amount,平台自动匹配一个售价最接近的在售商品,仅用于订单展示与支付宝商品说明(数量按 1 件记录);不收运费、不扣库存、不校验商品授权范围,匹配不到商品也照常下单;
  方式B(商品模式):传 sku_id 指定商品,实付金额 = 商品售价 × quantity + 商户后台配置的运费,校验商品归属/授权并真实扣减库存;
② 收货人姓名/手机号/地址为可选项:可随下单一起传;也可先不传,支付完成后联系平台在后台补录后发货;
③ 走方式B的商品单对应真实库存扣减与真实发货,渠道须确保交易背景真实;
④ 密钥 app_secret 只允许保存在渠道服务端,严禁下发 App/H5/小程序前端。

签名规则

除异步通知由我方发起外,所有请求均需签名。请求格式 application/x-www-form-urlencoded 或 JSON。

  1. 剔除 sign、sign_type 及值为空的参数;
  2. 参数名按 ASCII 升序排序,以 key=value 用 & 拼接;
  3. 末尾直接追加密钥 APP_SECRET(不带 &key= 前缀,与彩虹易支付一致);
  4. 对整串做 MD5,取 32 位小写十六进制,放入 sign 字段。
签名重要提醒:签名串自动包含本次请求所有非空参数(含新增的 target_amount 等)。任何新传入的参数,都必须同步加入签名计算,否则验签失败、下单不成功。

示例待签名串:

attach=test&partner_request_no=REQ20240520001&pid=DEMO20240001&target_amount=50.00&key=渠道密钥

1. 统一下单

POST /api/partner/order/create

参数必填说明
pid是平台签发的渠道商户ID
partner_request_no是渠道侧唯一请求号,最长64;重复请求返回原订单(幂等)
partner_user_id是终端用户标识,用于风控溯源
sku_id否商品ID;传了则走方式B,按商品售价×数量+运费计价
target_amount否应付金额(元),不传 sku_id 时必传;实付即该金额,平台仅按金额就近匹配商品用于展示,不收运费、不扣库存
quantity否数量,默认1
receiver_name否收货人姓名(可选项,可下单时传,也可支付后由平台后台补录)
receiver_phone否收货人手机号(可选项)
receiver_address否收货地址(可选项,可下单时传,也可支付后由平台后台补录)
notify_url否支付/退款结果异步通知地址,不传则用后台配置的默认地址
return_url否支付完成后浏览器跳转地址
attach否透传字段,原样返回/通知,最长255
user_ip否终端用户IP,用于风控与限流
sign是MD5签名

返回 data

字段说明
order_no平台订单号(同时作为支付宝 out_trade_no)
cashier_url平台收银台地址(展示商品/运费明细,推荐跳转此地址)
pay_url支付宝官方H5收银台直跳链接(可直接302)
product_amount / shipping_fee / pay_amount商品金额 / 运费 / 应付总额
order_statusPENDING_PAYMENT / PAID / CLOSED / REFUNDED

2. 订单查询

POST /api/partner/order/query

入参:pid、sign,以及 partner_request_no 与 order_no 二选一。

返回订单状态、商品/运费/总额、支付宝交易号、支付时间、退款状态与累计退款额、物流公司/单号、履约状态。建议异步通知与主动轮询并用。

3. 退款申请

POST /api/partner/order/refund(原路退回,需平台后台审核后执行)

参数必填说明
pid / sign是同通用规则
partner_refund_no是渠道退款请求号,唯一,幂等
order_no 或 partner_request_no是定位原订单
refund_amount是本次退款金额,支持部分退款;不得超过订单可退余额
reason否退款原因

提交后返回 refund_no 与 refund_status=PENDING;审核结果通过异步通知下发,也可按退款单号在平台后台跟进。

4. 可售商品列表

POST /api/partner/product/list,入参 pid、sign。

返回 list: [{ sku_id, name, price, stock_quantity }]。分销渠道返回被授权的平台商品;独立收款商户返回归属本商户的商品。

5. 订单列表

POST /api/partner/order/list

入参:pid、sign、可选 page(默认1)、page_size(默认20,最大50)。返回分页订单与费用/退款/物流字段。

6. 默认通知地址配置

POST /api/partner/config/notify-url,入参 pid、notify_url、sign。保存后未在下单时传 notify_url 的订单使用该地址。

7. 平台异步通知(平台 → 渠道)

平台以 application/x-www-form-urlencoded POST 到渠道 notify_url,参数同样按 MD5 规则签名,渠道侧必须验签。

支付成功 trade_status=TRADE_SUCCESS:含 pid、order_no、partner_request_no、total_amount、alipay_trade_no、attach、timestamp、sign。

退款结果:trade_status=REFUND_SUCCESS / REFUND_REJECTED,含 refund_no、partner_refund_no、refund_amount、result_msg、sign。

渠道处理成功须响应纯文本 success;否则平台会重试。

8. 常见失败提示

msg(示例)含义/处理
签名校验失败检查排序、拼接、密钥是否一致,编码是否 UTF-8
来源IP不在白名单渠道服务端出口IP需在后台白名单内(未配置则不限制)
该渠道无权销售此商品 / 该商品不属于当前商户sku_id 不在授权目录或不属于该商户,先调商品列表接口
商品已下架 / 商品库存不足更换 sku_id 或联系平台补货
操作过于频繁同 pid/IP/用户 60秒内超过后台配置上限(默认5笔)
退款金额超过可退余额核对已累计退款金额后重提

9. 签名与下单示例

PHP

<?php
function sign(array $params, string $secret): string {
    unset($params['sign'], $params['sign_type']);
    $params = array_filter($params, fn($v) => $v !== null && $v !== '');
    ksort($params);                       // ASCII 升序
    $str = urldecode(http_build_query($params)); // k=v&k2=v2
    return md5($str . $secret);
}

$params = [
  'pid' => 'DEMO20240001',
  'partner_request_no' => 'REQ' . date('YmdHis'),
  'partner_user_id' => 'U10086',
  // 方式A:不指定商品,由平台按金额匹配——target_amount 必须参与签名
  'target_amount' => '50.00',
  // 方式B:也可改用 'sku_id' => '1' 精确指定商品
  'quantity' => '1',
  'attach' => 'campaign_a',
];
$params['sign'] = sign($params, '渠道密钥');

$ch = curl_init('https://你的域名/api/partner/order/create');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => http_build_query($params),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);
header('Location: ' . $res['data']['cashier_url']); // 或 pay_url

Java

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

public class PartnerSign {
    static String sign(Map<String,String> p, String secret) throws Exception {
        var keys = new TreeSet<>(p.keySet());          // ASCII 升序
        var sb = new StringBuilder();
        for (String k : keys) {
            if (k.equals("sign") || k.equals("sign_type")) continue;
            String v = p.get(k);
            if (v == null || v.isEmpty()) continue;
            if (sb.length() > 0) sb.append('&');
            sb.append(k).append('=').append(v);        // 值不做URLEncode
        }
        sb.append(secret);
        var md = MessageDigest.getInstance("MD5");
        byte[] b = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
        var hex = new StringBuilder();
        for (byte x : b) hex.append(String.format("%02x", x));
        return hex.toString();
    }
}

Python

import hashlib, time, requests

def sign(params: dict, secret: str) -> str:
    items = [(k, v) for k, v in params.items()
             if k not in ("sign", "sign_type") and v not in (None, "")]
    items.sort(key=lambda x: x[0])               # ASCII 升序
    raw = "&".join(f"{k}={v}" for k, v in items)
    return hashlib.md5((raw + secret).encode("utf-8")).hexdigest()

params = {
    "pid": "DEMO20240001",
    "partner_request_no": f"REQ{int(time.time())}",
    "partner_user_id": "U10086",
    # 不指定商品,平台按金额匹配最接近的商品;target_amount 必须参与签名
    "target_amount": "50.00",
}
params["sign"] = sign(params, "渠道密钥")

r = requests.post("https://你的域名/api/partner/order/create", data=params, timeout=10)
print(r.json()["data"]["cashier_url"])

验签(渠道收到异步通知时,以 Node.js 为例)

const crypto = require('crypto');
function verifyNotify(params, secret) {
  const given = params.sign;
  const items = Object.keys(params)
    .filter(k => !['sign', 'sign_type'].includes(k) && params[k] !== '')
    .sort().map(k => `${k}=${params[k]}`);
  const expected = crypto.createHash('md5')
    .update(items.join('&') + secret).digest('hex');
  return given === expected;
}
// 验签通过且 trade_status === 'TRADE_SUCCESS' 后,响应字符串 success
限流:同一 pid / user_ip / partner_user_id 每 60 秒最多 5 笔(以后台配置为准)。 数据留存:订单、支付流水、回调原文、物流记录保留不少于 3 年。