快速开始
10 分钟跑通第一笔支付订单
前置条件
- 已联系商务,拿到
apiKey/apiSecret/baseUrl - 已在微盈云后台为本平台开通
paymentsservice - 已完成 platform 自身的子商户进件,
sub_mchid状态为finished
1. 安装 / 准备签名工具
SMP 不绑定 SDK,按规则手动签名即可。下面是 Node.js 的最小工具:
ts
// smp-client.ts
import crypto from "crypto";
const SMP_BASE_URL = process.env.SMP_BASE_URL!;
const SMP_API_KEY = process.env.SMP_API_KEY!;
const SMP_API_SECRET = process.env.SMP_API_SECRET!;
const sign = (serviceCode: string, body: unknown) => {
const signingKey = crypto
.createHash("sha256")
.update(SMP_API_SECRET)
.digest("hex");
const ts = Date.now().toString();
const payload = ts + serviceCode.toLowerCase() + JSON.stringify(body ?? {});
const sig = crypto.createHmac("sha256", signingKey).update(payload).digest("hex");
return { ts, sig };
};
export async function smp<T>(
method: "GET" | "POST" | "PUT" | "PATCH",
path: string,
serviceCode: "payments" | "activation" | "miniprogram",
body?: unknown,
): Promise<T> {
const { ts, sig } = sign(serviceCode, body);
const res = await fetch(`${SMP_BASE_URL}${path}`, {
method,
headers: {
"Content-Type": "application/json",
"X-Api-Key": SMP_API_KEY,
"X-Timestamp": ts,
"X-Signature": sig,
"X-Service-Code": serviceCode,
},
body: body == null ? undefined : JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw Object.assign(new Error(data.error || res.statusText), {
statusCode: res.status,
...data,
});
}
return data as T;
}2. 第一笔订单(微信 Native 收款)
ts
const order = await smp("POST", "/v1/payments/orders", "payments", {
outTradeNo: `hello_${Date.now()}`,
amount: 0.01, // 1 分
description: "测试订单",
channel: "wechat",
notifyUrl: "https://your.domain/notify",
});
console.log("二维码:", order.payParams.codeUrl);把 codeUrl 用任意二维码组件渲染,扫码即可付款。
3. 处理支付成功通知
SMP 收到微信回调后会向 notifyUrl POST:
ts
// 接收端示例(Express)
app.post("/notify", async (req, res) => {
const { headers, body } = req;
const ts = String(headers["x-timestamp"]);
const sig = String(headers["x-signature"]);
const expected = sign("payments", body).sig; // 用上面同一个 sign() 工具
if (sig !== expected) {
return res.status(401).end("invalid sig");
}
if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) {
return res.status(401).end("expired");
}
if (body.status === "paid") {
await fulfill(body.outTradeNo, body.transactionId);
}
res.status(204).end();
});⚠️ 收到通知务必做幂等(按 outTradeNo + status 去重)。SMP 转发失败时只 log 不重试。
4. 主动查询(兜底)
如果 webhook 长时间未到,可以主动查:
ts
const status = await smp("GET", `/v1/payments/orders/${order.id}`, "payments");
// pending 时 SMP 会顺带向上游 query 一次最新状态再返回