CHOOSE A ROUTE
先选择你的接入方式
DuoCai 同时提供原生码支付 API和易支付兼容协议。两者都能使用,但配置方式不同,请只按选中的路线操作。
已有易支付插件直接填三项配置接口地址填 https://pay.1kexiu.xyz/(保留末尾 /),再填商户中心显示的 PID 和支付对接密钥 KEY。不要把地址填成 /mapi.php。
自主开发接码支付原生 API服务端按本文签名后 POST /mapi.php下单,用 /api.php 查单,并在 notify_url 处验签、幂等入账。
网页收银台服务端签名后跳转需要让用户浏览器直接进收银台时,由服务端生成已签名的表单并提交到 /submit.php。
上述域名是生产环境。先运行离线签名测试,再用独立测试订单号创建 0.01 元订单;未经确认不要在生产订单上发货。
QUICK START
接入流程
推荐由你的服务端完成签名和下单。浏览器、APP、小程序只接收服务端返回的收银台地址,绝不能持有支付密钥。
- 1获取凭证
复制商户 PID 与支付对接密钥,启用套餐和支付通道。
- 2生成签名
服务端按 ASCII 键名排序,拼接参数与密钥并计算 MD5。
- 3创建订单
原生 API 请求
mapi.php;易支付插件只需配置网关根地址、PID 和 KEY。 - 4处理回调
验签、核对金额和订单状态,幂等更新后返回纯文本
success。
- 协议:HTTPS
- 编码:UTF-8
- 请求:
application/x-www-form-urlencoded - 金额单位:人民币元
- 签名:MD5(32 位小写)
AUTHENTICATION
商户凭证
pid商户 PID平台分配的对接标识,以后台显示值为准,当前通常为 M 开头。
pay_key支付对接密钥只用于支付签名和回调验签。密钥重置后,旧签名立即失效。
不要写进 HTML、JavaScript、APP 安装包、公开仓库或访问日志;不要将 key 返回给前端。生产环境请通过环境变量或密钥管理服务读取。
SIGNATURE
MD5 签名算法
- 排序
把本次实际发送的全部参数按参数名 ASCII 升序排列。
- 过滤
排除
sign、sign_type、路由参数a/c/m/s以及值为空字符串的参数。 - 拼接
按
key=value用&连接;值保持原样,签名前不要 URL 编码。 - 加密
在参数串末尾直接追加支付对接密钥,计算 32 位小写 MD5。
服务端以 application/x-www-form-urlencoded 解析后的值计算签名。验签前不会执行 HTML 转义、去除首尾空格、大小写转换、金额格式重写、JSON 转义或二次 URL 编解码。notify_url、return_url 可以携带查询参数,其中的原始 & 必须参与签名;不要改成 &。
- 排除
sign、sign_type、a、c、m、s。 - 排除值为''或
null的字段,其他已发送字段全部参与。 - 按参数名升序排列,按
参数名=原始参数值拼接并以&连接。 - 参数串末尾直接追加
pay_key,计算 32 位小写 MD5。 - 客户端必须先计算签名,再用
http_build_query或等价方法编码并发送。
money=0.01&name=测试商品&...&type=alipayYOUR_PAY_KEY末尾密钥前不增加 &key=,而是直接拼接密钥。可选参数只要非空且被发送,就必须参与签名。请求金额用什么字符串签名就发送什么字符串;回调中的 money 固定为两位小数,例如 1.00。
固定签名测试向量
先用下面的假凭证验证你的签名函数。只有得到相同结果,再替换成真实 PID 和密钥发起请求;该测试向量不能用于真实下单。
pay_key = test_pay_key_20260813
pid = M1234567890
type = alipay
out_trade_no = ORDER202608130001
notify_url = https://shop.example.com/pay/notify?channel=alipay&version=2
return_url = https://shop.example.com/pay/return?order=ORDER202608130001&source=pay
name = 测试订单
money = 1.00
expected_sign = beddd35092c3afea54c5218de0872502PAGE PAYMENT
页面跳转支付
https://pay.1kexiu.xyz/submit.php适合网页商城和发卡系统。建议用服务端生成的自动提交表单将用户浏览器跳转到此接口;成功后进入平台收银台,响应是 HTML,不是 JSON。
请求参数
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
pid | 必填 | String | 商户 PID,以后台 API 配置页显示值为准。 |
type | 必填 | String | 支付分类:alipay、wxpay、qqpay;需有对应启用通道。 |
out_trade_no | 必填 | String(128) | 商户订单号,只允许字母、数字及 . _ - |;同一 PID 内必须唯一。为避免跨系统碰撞,建议使用“项目标识 + 时间戳 + 随机串”生成全局高概率唯一值,不要只用简单自增数字。 |
notify_url | 必填 | String(288) | 服务端异步通知地址,必须可被公网 HTTPS 访问;允许携带查询参数,原始 & 参与签名。 |
return_url | 必填 | String(288) | 付款后的浏览器跳转地址;允许携带查询参数,不能代替异步通知。 |
name | 必填 | String(188) | 商品或订单标题。 |
money | 必填 | String | 单位元,范围 0.01~99999999.99,最多两位小数;建议始终使用两位小数字符串。 |
param | 选填 | String(300) | 业务扩展参数,付款回调时原样返回;不要放敏感信息。 |
sign | 必填 | String | 按本文算法生成的 32 位小写 MD5。 |
sign_type | 必填 | String | 固定为 MD5。 |
<form id="payForm" action="https://pay.1kexiu.xyz/submit.php" method="post">
<input type="hidden" name="pid" value="YOUR_PID">
<input type="hidden" name="type" value="alipay">
<input type="hidden" name="out_trade_no" value="ORDER_202608130001">
<input type="hidden" name="notify_url" value="https://your-site.example/pay/notify">
<input type="hidden" name="return_url" value="https://your-site.example/pay/return">
<input type="hidden" name="name" value="测试商品">
<input type="hidden" name="money" value="0.01">
<input type="hidden" name="sign" value="SERVER_GENERATED_SIGN">
<input type="hidden" name="sign_type" value="MD5">
</form>
<script>document.getElementById('payForm').submit();</script>SERVER_GENERATED_SIGN 只是占位符不能把它原样提交。必须先在你的服务端按本文算法计算真实 sign,再渲染自动提交表单;可直接复用后文 PHP/Node.js/Python 的 makeSign 函数。
API PAYMENT
API 下单
https://pay.1kexiu.xyz/mapi.php适合服务端请求后自行处理支付链接或二维码。请求参数与 页面跳转支付一致,推荐使用 POST 表单编码。
return_url 在新版中必须传旧版文档曾把它标为选填,但新版核心下单服务会校验该字段;缺少时会返回“回调地址(return_url)不能为空”。
先使用原 out_trade_no 调用 act=order 查单;查到订单就继续使用该平台订单。如暂时查不到,等待数秒后再查,不要立即更换订单号;当前接口不承诺下单请求幂等。
兼容参数
| 参数 | 必填 | 说明 |
|---|---|---|
clientip | 选填 | 付款用户 IP,兼容易支付客户端;发送非空值时必须参与签名。 |
device | 选填 | pc、mobile、wechat、alipay 或 qq;发送时参与签名。 |
成功响应
“商户中心 → 通道管理 → 通道配置 → API 返回模式”决定主要字段:payurl 为支付/收银台地址,qrcode 为可生成二维码的内容。接入端应兼容两者,优先处理实际非空字段。
{
"code": 1,
"msg": "下单成功",
"trade_no": "5090058146920009728",
"payurl": "https://pay.1kexiu.xyz/gateway/Submit/pay?trade_no=..."
}{
"code": 1,
"msg": "下单成功",
"trade_no": "5090058146920009728",
"qrcode": "https://qr.alipay.com/..."
}{
"code": -1,
"msg": "签名校验未通过,请检查签名算法、参与签名的参数原文和支付对接密钥(请求编号:xxxxxxxxxxxxxxxx)"
}签名失败时请保留响应中的请求编号,并向技术支持提供 PID、请求时间和脱敏后的参数名称列表。请勿发送支付对接密钥或完整回调 Token。
NOTIFICATION
支付回调
支付成功后,平台会向下单时的 notify_url 发送异步通知,并向 return_url 携带同步跳转参数。为兼容不同通道版本,接收端应同时读取 URL 查询参数和表单参数;只有异步通知可以作为可靠到账依据。
notify_url 不能填 localhost 或内网地址必须使用平台服务器可直接访问的公网 HTTPS 地址。本地开发时请使用受信任的内网穿透/反向代理,并确认该路由不会 301/302 跳转。
PHP 可使用 array_replace($_GET, $_POST) 合并回调字段,同名字段以 POST 为准。无论参数来自 GET 还是 POST,都必须使用框架解析后的原始字段值验签,不要先做 HTML 转义、trim 或金额格式化。
| 参数 | 类型 | 说明 |
|---|---|---|
pid | String | 商户 PID。 |
trade_no | String | 平台支付订单号。 |
out_trade_no | String | 你的商户订单号。 |
type | String | 支付分类。 |
name | String | 商品名称。 |
money | String | 商户订单金额,固定两位小数,例如 1.00。 |
trade_status | String | 只有精确等于 TRADE_SUCCESS 才表示成功。 |
param | String | 下单扩展参数;下单未传时可能不存在。 |
sign | String | 使用支付对接密钥按相同算法验签。 |
sign_type | String | 固定为 MD5。 |
使用收到的原始参数验签,失败立即拒绝。
核对 PID、订单号、订单金额与本地待支付记录。
以 trade_no 或 out_trade_no 加唯一约束,重复通知不能重复发货。
业务处理成功后返回纯文本小写 success,不要附加 HTML 或 JSON。
return_url 可能被用户关闭、重复访问或伪造。同步页只用于展示结果,必须以已验签的异步通知或主动查单结果更新订单。
<?php
function makeSign(array $params, string $key): string {
unset($params['sign'], $params['sign_type'],
$params['a'], $params['c'], $params['m'], $params['s']);
$params = array_filter($params, fn($v) => $v !== '' && $v !== null);
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $name => $value) $pairs[] = $name . '=' . $value;
return md5(implode('&', $pairs) . $key);
}
$params = array_replace($_GET, $_POST); // POST 覆盖同名 GET
$clientSign = (string)($params['sign'] ?? '');
if ($clientSign === '' || !hash_equals(makeSign($params, getenv('DUOCAI_PAY_KEY')), $clientSign)) {
http_response_code(400);
exit('fail');
}
if (($params['trade_status'] ?? '') !== 'TRADE_SUCCESS') {
http_response_code(400);
exit('fail');
}
// TODO:接下来必须在事务中锁定本地订单,核对 pid/out_trade_no/money/trade_no,
// 仅把“未支付”更新为“已支付”;重复通知可返回 success,但禁止重复发货。
// 在 updateLocalOrderOnce($params) 尚未真正实现并成功提交事务前,必须保持 fail。
http_response_code(501);
exit('fail');
// 实现业务处理后,删除上面两行;仅在事务提交成功后返回:exit('success');ORDER QUERY
订单查询
https://pay.1kexiu.xyz/api.php查询接口使用 pid 与支付对接密钥 key 鉴权,并只返回该 PID 自己的订单。推荐服务端使用 POST 表单,避免密钥出现在 URL、浏览器历史和代理日志中;GET 仅为兼容旧客户端保留。
act=query&pid=YOUR_PID&key=YOUR_PAY_KEY成功时返回 code=1、开通状态、余额、用户名及套餐允许的通道驱动标识列表 type;可用于服务器端连通性检查。
act=order&pid=YOUR_PID&key=YOUR_PAY_KEY&out_trade_no=ORDER_NO可传 out_trade_no(商户订单号)或 trade_no(平台订单号),只会查询当前 PID 的订单。
act=orders&pid=YOUR_PID&key=YOUR_PAY_KEY&limit=20&page=1limit 范围 1–50,page 从 1 开始。不要用高频轮询替代异步回调。
act=query 返回 code,pid,key,active,money,username,type;act=order 返回 code,trade_no,out_trade_no,type,money,price,status,pay_time;act=orders 返回 code,total,data,其中 data 是订单数组。pay_time 为 Unix 秒级时间戳,未支付时为 null。
curl --request POST 'https://pay.1kexiu.xyz/api.php' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'act=order' \
--data-urlencode 'pid=YOUR_PID' \
--data-urlencode 'key=YOUR_PAY_KEY' \
--data-urlencode 'out_trade_no=ORDER_NO'单笔订单成功响应示例
{
"code": 1,
"trade_no": "5090058146920009728",
"out_trade_no": "ORDER_202608130001",
"type": "alipay",
"money": "0.01",
"price": "0.01",
"status": 1,
"pay_time": 1787716800
}act=query 的兼容响应包含 key这是为旧协议兼容保留的字段。查询接口只能由你的服务端调用,禁止把完整响应转发给浏览器、APP 或第三方日志平台。
TROUBLESHOOTING
错误处理与重试
接口的业务失败可能仍返回 HTTP 200。mapi.php 和 api.php 必须解析 JSON 并判断 code;submit.php 失败时可能返回带错误说明的 HTML 页面。
签名校验未通过最常见
确认使用支付对接密钥;按 ASCII 排序;空值、sign、sign_type 不参与;所有实际发送的非空参数都参与签名。重点检查回调 URL 中的 & 是否在签名前被错误转成 &,或是否发生 trim、金额格式化及二次 URL 编解码。
商户 PID 或支付对接密钥错误
仅用于 api.php 查单鉴权。确认传的是商户中心“API 配置”中的 PID 和“对接密钥(KEY)”,不是登录密码、软件密钥或下单签名 sign。
回调地址(return_url)不能为空
新版页面支付和 API 下单都需要传 return_url。它与 notify_url 是两个不同地址,且都应使用绝对 HTTPS URL。
当前商户无通道可用
检查套餐是否有效、所选 type 是否存在已启用通道、通道配置是否保存完整,以及通道限额/轮询配置。
商户余额不足无法发起支付
当前套餐存在费率且手续费余额不足。先在商户中心充值手续费余额,再重新发起。
订单号格式不正确 / 此订单已支付成功
out_trade_no 只使用字母、数字及 . _ - |,并按“项目标识 + 时间戳 + 随机串”生成全局高概率唯一值;支付状态不确定时先查单,不要立即重试或更换订单号。
- 请求超时或响应无法解析时,保留原
out_trade_no和原始业务参数。 - 调用订单查询接口,数秒内重复查询确认是否已建单或已支付。
- 已查到的订单继续使用原平台订单;始终查不到时进入人工/延迟补偿,避免并发重复建单。
AI ASSISTED INTEGRATION
复制提示词,让 AI 按真实规则接入
把下面整段提示词复制给 ChatGPT、Codex、Claude、Cursor 或其他编程助手。提示词已经写明接口、签名、回调、查单、测试和交付要求,可减少 AI 猜错参数或直接提交占位签名的问题。
OFFICIAL SERVER DEMO
先跑通固定签名,再接进你的项目
一包包含 PHP 8+、Node.js 18+、Python 3.9+ 三套无框架示例,以及下单、查单、回调验签模板和离线签名测试。
f29d733bfd0cd1e55dc1e73ac16069a8a19fb8c51640f1d46cf2036afe3c35f5让 AI 使用 DUOCAI_PID 和 DUOCAI_PAY_KEY 环境变量。你只需在自己的服务器配置真实值,提示词和代码仓库中都不要出现明文密钥。
你是一名资深支付接口集成工程师。请在我现有的项目中完成 DuoCai码支付接入,并以 https://pay.1kexiu.xyz/docs/api 为接口规则来源。先只读检查项目已有的订单、支付、回调、数据库和配置实现,优先复用现有结构,禁止重写无关模块、升级框架或修改第三方依赖。
【先判断接入方式,只选择一条主路径】
A. 如果项目已有“易支付/彩虹易支付/异步支付”兼容插件:优先只配置网关 https://pay.1kexiu.xyz/(必须保留末尾 /)、PID、KEY 和支付类型,由现有插件完成签名、下单及回调;除非插件确实缺少功能,否则不要再写一套 /mapi.php 对接。
B. 如果项目没有兼容插件、需要自行开发服务端:走 DuoCai 原生码支付流程,服务端 POST /mapi.php 下单、POST /api.php 查单,并实现 notify_url 回调验签和幂等入账。
C. 如果只需要网页跳转收银台:由服务端生成签名后 POST /submit.php;该接口返回 HTML,不是 JSON。
先说明你识别出的接入模式和判断理由;除非我明确要求,不要同时实现多条路径。
【我的接入目标】
1. 接口域名:https://pay.1kexiu.xyz
2. 商户 PID:从环境变量 DUOCAI_PID 读取。
3. 支付对接密钥:从环境变量 DUOCAI_PAY_KEY 读取。
4. 需要接入的支付分类:alipay、wxpay;如果项目还需要 qqpay,再一并支持。
5. 按上面的模式判断执行;不能识别时先列出项目中找到的支付插件、下单入口和回调路由,再选择改动最小的方案。
6. 不要要求我把真实 PID 或密钥粘贴到聊天中,不要把密钥写入前端、APP、日志、异常信息或代码仓库。
【必须严格实现的签名规则】
1. 使用 application/x-www-form-urlencoded 解析后的原始字段值。
2. 排除 sign、sign_type、a、c、m、s。
3. 排除值为 null 或空字符串的字段;数字 0 和字符串 0 必须参与签名。
4. 其余实际发送的字段按参数名升序排序。
5. 按 参数名=原始参数值 拼接,字段之间使用 & 连接。
6. 在完整参数串末尾直接追加 DUOCAI_PAY_KEY 的值,不要追加 &key=,然后计算 UTF-8 字节的 32 位小写 MD5。
7. 必须先使用原始值计算签名,再进行表单 URL 编码并发送。
8. 签名前禁止执行 htmlspecialchars、htmlentities、trim、大小写转换、金额格式重写、JSON 转义、URL 重组或二次 urlencode/urldecode。
9. notify_url 和 return_url 可以携带多个查询参数;其中的原始 & 必须参与签名,不能改成 &。
10. 请求中的 money 按实际发送的原始字符串签名;回调中的 money 按平台实际返回值验签,通常为两位小数。
【创建订单】
使用 POST https://pay.1kexiu.xyz/mapi.php,Content-Type 为 application/x-www-form-urlencoded。至少发送:
pid、type、out_trade_no、notify_url、return_url、name、money、sign、sign_type=MD5。
param、clientip、device 等可选字段只要非空并实际发送,就必须参与签名。
return_url 在当前系统中为必填字段。
out_trade_no 在同一 PID 内必须唯一,最长 128 字符,只允许字母、数字和 . _ - |。为兼容当前系统并避免跨项目碰撞,必须生成全局高概率唯一值,推荐“项目标识_时间戳_随机串”,禁止只用简单自增数字。money 范围是 0.01~99999999.99 且最多两位小数;name 最长 188,notify_url/return_url 最长 288,param 最长 300 字符。
不要把 SERVER_GENERATED_SIGN 当作真实签名,它只是文档占位符。
接口业务失败可能仍返回 HTTP 200,所以必须解析 JSON 并判断 code。code=1 才表示下单成功。成功响应可能返回 payurl,也可能返回 qrcode;代码要兼容两者,优先使用实际非空字段,并保存平台 trade_no。不要只根据 HTTP 状态码判断成功。
如果使用 /submit.php,它返回 HTML 收银台页面,不是 JSON。签名必须仍在服务端生成,浏览器只能接收已经签好的自动提交表单,不能接触支付密钥。
【异步回调】
1. notify_url 接收端同时兼容 URL 查询参数和 POST 表单;PHP 可用 array_replace($_GET, $_POST),同名字段以 POST 为准,其他语言实现同等逻辑。
2. 使用收到的原始字段重新计算签名,并用安全比较函数核对 sign。
3. 验证 trade_status 必须等于 TRADE_SUCCESS。
4. 按 out_trade_no 查询本地订单,核对 pid、money、订单状态和平台 trade_no。
5. 使用数据库事务、行锁或唯一约束实现幂等:同一订单重复通知不能重复加款、发货或执行权益。
6. 只有本地订单核验、幂等业务处理和数据库事务全部成功提交后,才返回纯文本 success;失败返回 fail 或非 2xx。任何带 TODO、占位函数或尚未实现入账逻辑的回调示例都必须默认返回 fail,严禁在占位代码后直接返回 success。
7. return_url 只用于浏览器展示,禁止仅凭同步跳转更新付款状态或发货。
【主动查单】
使用服务端 POST https://pay.1kexiu.xyz/api.php,表单参数使用 pid 和 key 鉴权,key 的值来自 DUOCAI_PAY_KEY,不使用下单 sign 代替 key。
单笔查单:act=order,加 out_trade_no 或 trade_no。
订单列表:act=orders,加 page 和 limit,limit 范围 1 至 50。
商户状态:act=query。
成功响应字段必须按真实结构处理:act=query 返回 code,pid,key,active,money,username,type;act=order 返回 code,trade_no,out_trade_no,type,money,price,status,pay_time;act=orders 返回 code,total,data,其中 data 是订单数组。
status=0 表示未支付,status=1 表示支付成功,status=2 表示订单已超时;pay_time 是 Unix 秒级时间戳,未支付时可能为 null。只能在 code=1 且 status=1 时按“已支付”处理。
act=query 为兼容旧协议会在响应中包含 key,该字段等同敏感密钥;禁止把完整响应返回浏览器、APP、第三方日志或错误监控。
GET 查询仅为兼容旧客户端,不要在新代码中把密钥放进 URL。
【固定签名测试向量】
先为签名函数编写自动化测试,下面参数必须得到指定 MD5:
pay_key = test_pay_key_20260813
pid = M1234567890
type = alipay
out_trade_no = ORDER202608130001
notify_url = https://shop.example.com/pay/notify?channel=alipay&version=2
return_url = https://shop.example.com/pay/return?order=ORDER202608130001&source=pay
name = 测试订单
money = 1.00
expected_sign = beddd35092c3afea54c5218de0872502
测试还必须覆盖:空字符串、null、数字 0、money 分别为 1/1.0/1.00、中文商品名、空格、+、%、引号,以及 notify_url/return_url 含多个 &。同时验证签名后再做表单编码,确保实际发送字段与参与签名字段完全一致。
【实现与安全要求】
1. 先找到项目已有的订单模型、支付服务、回调路由和配置方式,做最小修改并保持现有代码风格。
2. PID 和密钥只从服务端环境变量或安全配置读取;日志不得记录密钥、完整签名原文或完整回调 Token。
3. 不关闭 TLS 证书验证,不使用浮点数直接比较金额;金额使用 decimal 或整数分,并按业务规则精确比较。
4. 请求超时或响应不确定时保留原 out_trade_no 和全部原始业务参数,在数秒内重复查单;当前平台不承诺下单请求幂等,禁止并发重试或立即更换订单号。
5. 当前接口域名是生产环境,没有独立沙箱。未经我明确确认,不调用会创建订单或产生资金行为的 /mapi.php、/submit.php,不进行真实付款、退款、发货或生产数据写入测试;默认只做固定签名向量、Mock 回调、Mock 查单和本地自动化测试。
6. 如果现有项目规则与接口要求冲突,先指出具体文件和冲突原因,再给出保持兼容的最小修复,不要自行猜测。
【完成后请交付】
1. 说明修改了哪些文件、每个文件做了什么。
2. 给出需要配置的环境变量和部署步骤。
3. 给出签名、下单响应、回调幂等和主动查单的自动化测试结果。
4. 明确哪些内容已经实际验证、哪些因为没有真实付款而尚未验证。
5. 提供一个安全的联调检查清单;如果失败,输出脱敏后的参数名称、排序结果和请求编号,但绝不输出真实密钥。
现在请先检查我的项目结构和现有支付实现,然后直接完成最小范围接入。如果你不能直接读取或修改项目,就按我的技术栈输出可复制的完整文件,并明确每个文件的放置路径。CODE EXAMPLES
可直接改造的服务端示例
示例均完成“过滤 → 排序 → 拼接 → MD5”。请把凭证放入环境变量,生产环境不要关闭 TLS 证书校验。
<?php
$apiBase = 'https://pay.1kexiu.xyz';
$payKey = getenv('DUOCAI_PAY_KEY');
function makeSign(array $params, string $key): string {
unset($params['sign'], $params['sign_type'],
$params['a'], $params['c'], $params['m'], $params['s']);
$params = array_filter($params, fn($v) => $v !== '' && $v !== null);
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $name => $value) {
$pairs[] = $name . '=' . $value;
}
return md5(implode('&', $pairs) . $key);
}
$params = [
'pid' => getenv('DUOCAI_PID'),
'type' => 'alipay',
'out_trade_no' => 'ORDER_' . date('YmdHis'),
'notify_url' => 'https://shop.example.com/pay/notify?channel=alipay&version=2',
'return_url' => 'https://shop.example.com/pay/return?order=ORDER_202608130001&source=pay',
'name' => '测试商品',
'money' => '0.01',
];
$params['sign'] = makeSign($params, $payKey);
$params['sign_type'] = 'MD5';
$ch = curl_init($apiBase . '/mapi.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
// 先用原始字段值签名,再编码发送;不要先对 URL 做 htmlspecialchars。
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$result = json_decode(curl_exec($ch), true, 512, JSON_THROW_ON_ERROR);
curl_close($ch);
if (($result['code'] ?? -1) !== 1) {
throw new RuntimeException($result['msg'] ?? '下单失败');
}
// 优先使用实际存在的字段:payurl 或 qrcode
$paymentValue = $result['payurl'] ?? $result['qrcode'] ?? null;import crypto from 'node:crypto';
const apiBase = 'https://pay.1kexiu.xyz';
const payKey = process.env.DUOCAI_PAY_KEY;
function makeSign(params, key) {
const ignored = new Set(['sign', 'sign_type', 'a', 'c', 'm', 's']);
const source = Object.entries(params)
.filter(([name, value]) => !ignored.has(name) && value !== '' && value !== null && value !== undefined)
.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
.map(([name, value]) => `${name}=${value}`)
.join('&') + key;
return crypto.createHash('md5').update(source, 'utf8').digest('hex');
}
const params = {
pid: process.env.DUOCAI_PID,
type: 'alipay',
out_trade_no: `ORDER_${Date.now()}`,
notify_url: 'https://shop.example.com/pay/notify?channel=alipay&version=2',
return_url: 'https://shop.example.com/pay/return?order=ORDER_202608130001&source=pay',
name: '测试商品',
money: '0.01'
};
params.sign = makeSign(params, payKey);
params.sign_type = 'MD5';
const response = await fetch(`${apiBase}/mapi.php`, {
method: 'POST',
headers: {'content-type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams(params)
});
const result = await response.json();
if (result.code !== 1) throw new Error(result.msg || '下单失败');
const paymentValue = result.payurl ?? result.qrcode;import hashlib
import json
import os
import time
from urllib import parse, request
API_BASE = 'https://pay.1kexiu.xyz'
PAY_KEY = os.environ['DUOCAI_PAY_KEY']
def make_sign(params: dict, key: str) -> str:
ignored = {'sign', 'sign_type', 'a', 'c', 'm', 's'}
pairs = [f'{name}={value}' for name, value in sorted(params.items())
if name not in ignored and value is not None and value != '']
source = '&'.join(pairs) + key
return hashlib.md5(source.encode('utf-8')).hexdigest()
params = {
'pid': os.environ['DUOCAI_PID'],
'type': 'alipay',
'out_trade_no': f'ORDER_{int(time.time() * 1000)}',
'notify_url': 'https://shop.example.com/pay/notify?channel=alipay&version=2',
'return_url': 'https://shop.example.com/pay/return?order=ORDER_202608130001&source=pay',
'name': '测试商品',
'money': '0.01',
}
params['sign'] = make_sign(params, PAY_KEY)
params['sign_type'] = 'MD5'
body = parse.urlencode(params).encode('utf-8')
http_request = request.Request(
f'{API_BASE}/mapi.php', data=body,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
method='POST'
)
with request.urlopen(http_request, timeout=15) as response:
result = json.loads(response.read().decode('utf-8'))
if result.get('code') != 1:
raise RuntimeError(result.get('msg', '下单失败'))
payment_value = result.get('payurl') or result.get('qrcode')下面的 SERVER_GENERATED_SIGN 必须先由你的 PHP/Node.js/Python 服务端代码计算,不能原样发送。
curl --request POST 'https://pay.1kexiu.xyz/mapi.php' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'pid=YOUR_PID' \
--data-urlencode 'type=alipay' \
--data-urlencode 'out_trade_no=ORDER_202608130001' \
--data-urlencode 'notify_url=https://shop.example.com/pay/notify?channel=alipay&version=2' \
--data-urlencode 'return_url=https://shop.example.com/pay/return?order=ORDER_202608130001&source=pay' \
--data-urlencode 'name=测试商品' \
--data-urlencode 'money=0.01' \
--data-urlencode 'sign=SERVER_GENERATED_SIGN' \
--data-urlencode 'sign_type=MD5'回调验签最小逻辑
params = merge_query_and_form_params(request)
if make_sign(params, PAY_KEY) != params.sign:
return "fail"
if params.trade_status != "TRADE_SUCCESS":
return "fail"
order = find_local_order(params.out_trade_no)
if order is null or order.pid != params.pid or order.money != params.money:
return "fail"
mark_paid_once(order, params.trade_no) // 必须幂等
return "success" // 纯文本GO LIVE