SDK 下载
代付接口 std1 有六种语言的 SDK:Java、PHP、Python、Go、Node.js、.NET。六种行为完全一样:同一组参数发出同样的字节,同一个应答判成同样的结果、给出同样的提示。都没有写死的地址,接口地址、商户号、密钥由您配置。代收接口 compat1(老协议)没有 SDK。
下载
| 语言 | 最低版本 | 文件 | 大小 | SHA-256 |
|---|---|---|---|---|
| Java | Java 8 | std1-sdk-java-1.0.0.zip | 89 KB | 6e5c3980a03f04cd48968b6425159f5110b1a0171fd89769fef7b342d91e8c4c |
| PHP | PHP 7.4 | std1-sdk-php-1.0.0.zip | 93 KB | 340dfd49cc9c89e90aea527fbe91eaa5de39b5c694ee20bc6fce0902a589ee12 |
| Python | Python 3.7 | std1-sdk-python-1.0.0.zip | 63 KB | dec28311467ae757b22299e6f9d4e40db9cf0134f41c0b95f3ad84d2ad836d47 |
| Go | Go 1.18 | std1-sdk-go-1.0.0.zip | 82 KB | d3e680e4fe1f85d511319d90016e7c233caab934f72377753b15b2eadff628fc |
| Node.js(TypeScript) | Node.js 16 | std1-sdk-node-1.0.0.zip | 120 KB | 9dafafaafaf666cc404149596e56d3ca6bb561e9344f2f696472fbf5ac86d327 |
| .NET | .NET Standard 2.0 | std1-sdk-dotnet-1.0.0.zip | 99 KB | 3e322b359bcb2800303166022d21c924cc72f7690e3a0078b626ac001ca99fe8 |
| 全部六种 | — | std1-sdk-all-1.0.0.zip | 398 KB | 79097c0dd230e8638c732f309049bc3612ba3ab5ef4868795d2ee896a29f2fc1 |
- 每个包里有源码、中英文 README(安装、配置、三个接口、回调、重试规则、常见错误)、测试脚本
run-tests.sh和黄金样本vectors/std1.json。 - 下载后可以用上表的 SHA-256 核对文件完整。
- 不用 SDK、自己写的:拿黄金样本逐条核对签名、请求正文和结果判断,全部对上再上线。
配置
- 网关地址:
https://api.xx2system.xyz - 商户号:您的商户号(新开的形如
MCH58001) - 密钥:商户后台「安全中心」里的 API私钥(代收代付通用的那一把;新开商户是 64 位十六进制文本),原样复制,首尾不能带空格或换行
结果只有五种
| 结果 | 意思 | 您该做什么 |
|---|---|---|
SUCCESS | 提代付=已受理、钱已冻结,不是已出款;查单=查到了,看 status;查余额=拿到了 | 等回调或查单 |
NOT_PROCESSED | 本次没被处理(参数错、签名错、IP 未授权、限流、商户号不对) | 改正后用原单号重发 |
REJECTED | 订单已拒绝(终态),该商户订单号已占用(MERCHANT_DISABLED「商户未开通代付接口」不占用,开通后可使用原商户订单号重新提交) | 如需代付,请使用新的商户订单号提交 |
UNKNOWN | 结果不明(超时、断线、HTTP 不是 200、验签不过、系统繁忙、单号冲突) | 用同一单号、完全相同的参数重试或查单 |
NOT_FOUND | 查单查不到 | 不能据此判断没建单 |
示例
下面的示例取自各语言包里的 README。
Java
提代付
ApplyRequest request = ApplyRequest.builder()
.mchOrderNo("ORDER-20260917-0001") // 你的单号,同商户唯一;结果不明时重试只能用它
.amountCents(Amounts.yuanToCents("19.99")) // "1999";也可以 amountCents(1999L)
.accountName("张三")
.accountNo("6222021234567890123")
.bankName("中国工商银行")
.notifyUrl("https://你的回调地址/std1/notify") // 可选;不给就只能靠查单
.remark("备注") // 可选,查单和回调原样带回
.extra("myTag", "vip") // 可选:自带字段,照签照发、网关忽略;同一个名字只能设一次
.extra("productId", "2") // 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
.build();
Std1Result result = client.applyPayout(request);
switch (result.getOutcome()) {
case SUCCESS: // 已受理、钱已冻结,还不是已出款;记下 result.getPayoutNo(),等回调或查单
break;
case REJECTED: // 订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
break;
case NOT_PROCESSED: // 本次没处理:按 result.getHint() 改正后,用原单号重发
break;
default: // UNKNOWN:结果不明——用同一单号、完全相同的参数重试或查单,不要换单号重下
break;
}
System.out.println(result.getOutcome() + " " + result.getHint()); // 提示有中文 getHint() 和英文 getHintEn()
收回调
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
byte[] body = readAll(req.getInputStream()); // 只读正文字节,别碰 getParameter*
try {
PayoutNotification n = client.parseNotification(body);
saveIfNewer(n.getPayoutNo(), n.getStatus(), n); // 你的落库逻辑(按 payoutNo+status 去重)
resp.setStatus(200);
resp.getWriter().write(Std1Client.SUCCESS_REPLY);
} catch (InvalidNotificationException e) {
resp.setStatus(400); // e.getReason(): malformed / signature / merchant / fields
resp.getWriter().write("fail");
}
}
PHP
提代付
use Std1\Payout\Amounts;
use Std1\Payout\ApplyRequest;
use Std1\Payout\Outcome;
$request = new ApplyRequest([
'mchOrderNo' => 'A20260917000001', // 商户单号,同一商户唯一,就是防重复的键
'amount' => Amounts::yuanToCents('100.00'), // '10000' 分。不要写 100.00 * 100
'accountName' => '张三',
'accountNo' => '6222021234567890123',
'bankName' => '中国工商银行',
'notifyUrl' => 'https://商户回调地址/std1/notify', // 可选;不传就不发回调,只能查单
'remark' => '九月工资', // 可选;查单、回调原样带回
], [
'myBatch' => '0917', // 可选:你自己的字段,会签名、网关忽略
'productId' => '2', // 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
]);
// 发之前先把这组参数和单号一起存库:结果不明时要原样重发
$result = $client->applyPayout($request);
switch ($result->getOutcome()) {
case Outcome::SUCCESS: // 已受理、钱已冻结,还没出款;等回调或查单
saveAccepted($request->getMchOrderNo(), $result->getPayoutNo());
break;
case Outcome::REJECTED: // 订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
markRejected($request->getMchOrderNo(), $result->getCode(), $result->getMessage());
break;
case Outcome::NOT_PROCESSED: // 本次没处理:按提示改正后,用原单号重发
logWarning($result->getHint());
break;
default: // UNKNOWN 结果不明:用同一单号、完全相同的参数重试或查单,绝不换单号
scheduleRetryWithSameParams($request->getMchOrderNo(), $result->getHint());
}
收回调
use Std1\Payout\Client;
use Std1\Payout\InvalidNotificationException;
use Std1\Payout\Statuses;
$raw = file_get_contents('php://input'); // 只用正文原文。绝不用 $_POST、$_REQUEST
try {
$notification = $client->parseNotification($raw);
} catch (InvalidNotificationException $e) {
http_response_code(400); // 不合法:不要回 success
echo 'fail';
exit;
}
// 先落库,再回 success:按 payoutNo + status 去重;只接受往后走的状态
$previous = findStatus($notification->getPayoutNo()); // 没记过为 null
if (Statuses::isNewer($previous, $notification->getStatus())) {
saveStatus($notification->getPayoutNo(), $notification->getStatus(), $notification->getFailMsg());
}
echo Client::SUCCESS_REPLY; // 正文只能是 success,前后不要有任何输出
Python
提代付
amount = std1.yuan_to_cents("19.99") # "1999":主单位转最小单位只收字符串,不经过浮点
request = std1.ApplyRequest(
mch_order_no="ORDER-20260917-0001", # 你的单号:同一笔钱永远用同一个单号
amount=amount, # 最小单位整数:str 或 int,浮点直接报错
account_name="张三",
account_no="6222021234567890123",
bank_name="中国工商银行",
notify_url="https://你的域名/std1/notify", # 可选
remark="备注", # 可选;空串和 None 等于没传
extra={"productId": "2"}, # 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
)
result = client.apply_payout(request)
if result.outcome == std1.Outcome.SUCCESS:
save_payout_no(result.field("payoutNo")) # 已受理、钱已冻结,还不是已出款
elif result.outcome == std1.Outcome.REJECTED:
mark_rejected(result.code, result.message) # 订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
elif result.outcome == std1.Outcome.NOT_PROCESSED:
fix_and_retry_same_order_no(result.hint) # 本次没处理:改正后用原单号重发
else: # UNKNOWN
retry_later_with_same_params(result.hint) # 结果不明:同一单号、完全相同的参数重试或查单
收回调
# Flask
@app.post("/std1/notify")
def std1_notify():
try:
notification = client.parse_notification(request.get_data()) # 原始正文字节
except std1.InvalidNotification:
return "fail", 400 # 不合法:不要回 success
save_once(notification.payout_no, notification.status, notification) # 先落库(按 payoutNo+status 去重)
return std1.SUCCESS_REPLY, 200 # 再回 success
Go
提代付
cents, err := std1payout.YuanToCents("19.99") // "1999"
if err != nil { /* 金额写法不对,最多两位小数 */ }
result, err := client.ApplyPayout(ctx, std1payout.ApplyRequest{
MchOrderNo: "ORDER-20260917-0001", // 你的单号,同一商户唯一
Amount: cents,
AccountName: "张三",
AccountNo: "6222021234567890123",
BankName: "中国工商银行",
NotifyURL: "https://你的回调地址/std1/notify", // 可选
Remark: "备注", // 可选
Extra: []std1payout.Param{
{Name: "myTag", Value: "vip"}, // 可选:你自己的字段,会参与签名
{Name: "productId", Value: "2"}, // 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
},
})
if err != nil {
// 只有参数本身不对才会走到这里(缺必填、金额不是最小单位整数、自带字段重名),这时什么都没发出去
}
switch result.Outcome {
case std1payout.OutcomeSuccess:
// 已受理、钱已冻结,还不是已出款;结果等回调或查单。result.Field("payoutNo") 是平台代付单号
case std1payout.OutcomeRejected:
// 已拒绝(result.Code 说明原因):订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
case std1payout.OutcomeNotProcessed:
// 本次没处理(参数、IP、签名、限流):按 result.Hint 改正后用原单号重发
default: // OutcomeUnknown
// 结果不明:用同一单号、完全相同的参数重试或查单,不要换单号重下
}
收回调
http.Handle("/std1/notify", client.NotificationHandler(func(n *std1payout.Notification) error {
// 1. 按 n.PayoutNo + n.Status 去重;只接受往后走的状态:std1payout.IsNewerStatus(已记录的状态, n.Status)
// 2. 先把结果落库
// 3. 返回 nil,SDK 回 HTTP 200「success」;返回 error,SDK 回 500,网关稍后重发
return saveResult(n)
}))
Node.js(TypeScript)
提代付
const result = await client.applyPayout({
mchOrderNo: 'ORDER-20260917-0001', // 商户单号:重试必须同一个单号、完全相同的参数
amount: yuanToCents('100.00'), // 金额只收最小单位整数:'10000'、10000 或 10000n
accountName: '张三',
accountNo: '6222021234567890123',
bankName: '中国工商银行',
bankBranch: null, // 可选:bankBranch、bankProvince、bankCity、currency(只收本部署币种,现在是 CNY)
notifyUrl: 'https://你的回调地址/std1/notify',
remark: '工资',
extra: [
['myTraceId', 'T-1'], // 可选:自带字段(参与签名,网关验签后忽略)
['productId', '2'], // 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
],
});
switch (result.outcome) {
case Outcome.SUCCESS: // 已受理、钱已冻结,还不是已出款;等回调或查单
save(result.field('payoutNo'));
break;
case Outcome.REJECTED: // 订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
markRejected(result.code, result.message);
break;
case Outcome.NOT_PROCESSED: // 本次没处理(参数、IP、签名、限流);改正后用原单号重发
fixAndRetrySameOrderNo(result.code, result.message);
break;
default: // UNKNOWN:结果不明;用同一单号、完全相同的参数重试或查单,绝不换单号
retryLaterSameParams();
}
console.log(result.hint); // 中文提示;result.hintEn 是英文
收回调
const express = require('express');
const app = express();
// 只给回调这个路由用 raw,拿原始 Buffer;不要用 express.urlencoded() 解析后的对象,更不要把 req.query 合进来
app.post('/std1/notify', express.raw({ type: '*/*', limit: '64kb' }), async (req, res) => {
let notification;
try {
notification = client.parseNotification(req.body); // Buffer
} catch (error) {
// InvalidNotificationError:error.reason 是 malformed / signature / merchant / fields
return res.status(400).type('text/plain').send('fail'); // 不要回 success,网关会重发
}
// 先落库再回 success;按 payoutNo + status 去重;只接受往后走的状态
await saveOnce(notification.payoutNo, notification.status, notification);
res.status(200).type('text/plain').send(SUCCESS_REPLY);
});
.NET
提代付
var request = new ApplyRequest
{
MchOrderNo = "ORDER-20260917-0001", // 你系统里的单号:同一商户唯一,就是幂等键
Amount = Amounts.YuanToCents("100.00"), // "10000",金额一律是最小单位整数;最小单位整数也可以用 request.SetAmountCents(10000)
AccountName = "张三",
AccountNo = "6222021234567890123",
BankName = "中国工商银行",
NotifyUrl = "https://回调地址/std1/notify", // 可选;不传就不发回调,只能查单
Remark = "工资", // 可选;查单、回调原样带回
};
request.AddExtra("myTag", "vip"); // 可选:自带字段参与签名,网关验签后忽略
request.AddExtra("productId", "2"); // 可选:代付产品编号,用自带字段带;不带走这户的默认代付产品
// 先把单号和这组参数存库:结果不明时要原样重发
Std1Result result = await client.ApplyPayoutAsync(request);
switch (result.Outcome)
{
case Outcome.Success: // 已受理、钱已冻结,还不是已出款:等回调或查单
SavePayoutNo(request.MchOrderNo, result.PayoutNo);
break;
case Outcome.Rejected: // 订单已拒绝(终态),该商户订单号已占用;如需代付,请使用新的商户订单号
MarkRejected(request.MchOrderNo, result.Code, result.Message);
break;
case Outcome.NotProcessed: // 本次没被处理:改正原因后用原单号重发
Alert(result.Hint);
break;
default: // Unknown:结果不明:用原单号、原参数重发或查单,绝不换单号
ScheduleRetryOrQuery(request.MchOrderNo);
break;
}
收回调
app.MapPost("/std1/notify", async (HttpRequest http) =>
{
byte[] body;
using (var buffer = new MemoryStream())
{
await http.Body.CopyToAsync(buffer); // 原始正文字节
body = buffer.ToArray();
}
PayoutNotification n;
try
{
n = client.ParseNotification(body);
}
catch (InvalidNotificationException e) // e.Reason:malformed/signature/merchant/fields
{
Console.Error.WriteLine("std1 回调不合法:" + e.Reason);
return Results.Text("fail", statusCode: 400);
}
await SaveIfNewerAsync(n); // 按 PayoutNo+Status 去重,只收往后走的状态
return Results.Text(Std1Client.SuccessReply);
});