微信小程序支付是实现商业变现的核心能力,但接入过程涉及微信支付商户平台配置、前后端交互、签名验证等多环节,新手易踩坑。本文提供可直接复用的代码片段、分阶段操作步骤及避坑技巧,助你快速完成对接。
一、前期准备(核心前置条件)
接入前必须完成以下配置,否则后续开发无法推进:
- 小程序资质:已完成微信小程序注册(个人主体暂不支持支付,需企业 / 个体工商户主体),且通过微信认证。
- 微信支付商户号:登录微信支付商户平台注册,将商户号与小程序绑定(路径:商户平台 – 产品中心 – AppID 授权管理)。
- 密钥与证书:
- 登录商户平台,在 “账户中心 – API 安全” 中设置 “APIv2 密钥”(32 位字母数字组合,需牢记)。
- 申请并下载 “API 证书”(含 cert.p12、apiclient_cert.pem、apiclient_key.pem),用于敏感接口加密。
- 开发工具:安装微信开发者工具(用于小程序前端调试)、后端开发工具(如 IDEA、VS Code)及接口测试工具(如 Postman)。
二、核心对接步骤(附现成代码)
微信小程序支付遵循 “前端发起请求 – 后端生成支付参数 – 前端调起支付 – 后端接收回调” 的流程,以下分前后端详细说明。
阶段 1:后端开发(以 Java 为例,其他语言逻辑通用)
后端负责生成预支付订单、签名验证、接收支付结果回调,是支付安全的核心。
1. 引入依赖(Maven)
在pom.xml中添加微信支付 SDK 依赖,避免重复开发加密、签名等工具类:
<!– 微信支付Java SDK –>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<!– 工具类依赖 –>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
2. 配置类(统一管理参数)
创建WxPayConfig.java,存储商户号、密钥等核心参数(建议通过配置文件读取,避免硬编码):
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = “wx.pay”)
public class WxPayConfig {
// 小程序AppID
private String appId;
// 微信支付商户号
private String mchId;
// APIv2密钥
private String apiV2Key;
// 证书路径(绝对路径,如D:/cert/apiclient_cert.pem)
private String certPath;
// 支付结果回调地址(需外网可访问,微信服务器会POST请求该地址)
private String notifyUrl;
}
在application.yml中配置参数:
wx:
pay:
appId: wx1234567890abcdef # 替换为你的小程序AppID
mchId: 1600000000 # 替换为你的商户号
apiV2Key: abcdef1234567890abcdef1234567890 # 替换为你的APIv2密钥
certPath: /usr/local/wxpay/cert/ # 替换为你的证书存放路径
notifyUrl: https://www.xxx.com/api/pay/notify # 替换为你的回调地址
3. 核心支付工具类(生成预支付参数)
创建WxPayService.java,实现 “统一下单” 接口调用,生成前端需的支付参数:
import com.alibaba.fastjson.JSONObject;
import com.github.wechatpay.apiv3.core.WechatPayHttpClientBuilder;
import com.github.wechatpay.apiv3.core.util.PemUtil;
import com.github.wechatpay.apiv3.service.payments.jsapi.model.PrepayRequest;
import com.github.wechatpay.apiv3.service.payments.jsapi.model.PrepayResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@Service
public class WxPayService {
@Autowired
private WxPayConfig wxPayConfig;
/**
* 生成JSAPI支付参数(供小程序前端调用)
* @param openid 支付用户的小程序openid(需通过小程序登录接口获取)
* @param outTradeNo 商户订单号(唯一,如UUID生成)
* @param totalFee 订单金额(单位:分,如1元=100)
* @param description 订单描述
* @return 支付参数(含timeStamp、nonceStr、package、signType、paySign)
* @throws Exception
*/
public Map<String, String> createJsapiPayParams(String openid, String outTradeNo, Integer totalFee, String description) throws Exception {
// 1. 加载商户证书和私钥
X509Certificate cert = PemUtil.loadCertificate(new FileInputStream(wxPayConfig.getCertPath() + “apiclient_cert.pem”));
PrivateKey privateKey = PemUtil.loadPrivateKey(new FileInputStream(wxPayConfig.getCertPath() + “apiclient_key.pem”));
// 2. 构建HTTP客户端
SSLContext sslContext = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayConfig.getMchId(), privateKey, cert)
.build().getSslContext();
// 3. 构造统一下单请求参数
PrepayRequest request = new PrepayRequest();
request.setAppid(wxPayConfig.getAppId());
request.setMchid(wxPayConfig.getMchId());
request.setDescription(description);
request.setOutTradeNo(outTradeNo);
request.setNotifyUrl(wxPayConfig.getNotifyUrl());
// 订单金额(单位分)
PrepayRequest.Amount amount = new PrepayRequest.Amount();
amount.setTotal(totalFee);
request.setAmount(amount);
// 支付者信息(openid必传)
PrepayRequest.Payer payer = new PrepayRequest.Payer();
payer.setOpenid(openid);
request.setPayer(payer);
com.github.wechatpay.apiv3.service.payments.jsapi.JsapiService service = new com.github.wechatpay.apiv3.service.payments.jsapi.JsapiServiceBuilder()
.withSslContext(sslContext)
.build();
PrepayResponse response = service.prepay(request);
// 5. 生成前端调起支付的签名(关键步骤)
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = UUID.randomUUID().toString().replace(“-“, “”);
String packageStr = “prepay_id=” + response.getPrepayId(); // 格式固定
String signType = “RSA”;
// 签名原串:appId\n时间戳\n随机字符串\nprepay_id\n
String signRaw = wxPayConfig.getAppId() + “\n” + timeStamp + “\n” + nonceStr + “\n” + packageStr + “\n”;
String paySign = PemUtil.sign(privateKey, signRaw.getBytes(“UTF-8”));
// 6. 组装返回给前端的参数
Map<String, String> payParams = new HashMap<>();
payParams.put(“timeStamp”, timeStamp);
payParams.put(“nonceStr”, nonceStr);
payParams.put(“package”, packageStr);
payParams.put(“signType”, signType);
payParams.put(“paySign”, paySign);
log.info(“生成支付参数成功,订单号:{}”, outTradeNo);
return payParams;
}
/**
* 支付结果回调验证与处理
* @param notifyData 微信服务器POST的回调数据(XML格式)
* @return 处理结果(微信要求返回”success”或”fail”)
*/
public String handlePayNotify(String notifyData) {
try {
// 1. 验证签名(防止伪造回调)
// 此处需使用APIv2密钥验证XML签名,具体实现可参考微信支付SDK文档
// 验证通过后解析XML数据
JSONObject notifyObj = parseXmlToJson(notifyData);
String outTradeNo = notifyObj.getString(“out_trade_no”); // 商户订单号
String tradeState = notifyObj.getString(“trade_state”); // 支付状态(SUCCESS为成功)
// 2. 处理订单(如更新订单状态为”已支付”,记录支付时间等)
if (“SUCCESS”.equals(tradeState)) {
log.info(“订单支付成功,订单号:{}”, outTradeNo);
// TODO: 此处添加你的订单处理逻辑(如更新数据库、发送通知等)
}
// 3. 返回成功响应(微信收到后不再重试回调)
return “<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>”;
} catch (Exception e) {
log.error(“处理支付回调失败”, e);
// 4. 返回失败响应(微信会重试回调)
return “<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>”;
}
}
// 辅助方法:XML转JSON(可使用dom4j等工具实现)
private JSONObject parseXmlToJson(String xml) {
// TODO: 实现XML解析逻辑,此处省略具体代码
return new JSONObject();
}
}
4. 接口控制器(提供前端调用入口)
创建PayController.java,暴露生成支付参数和接收回调的接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping(“/api/pay”)
public class PayController {
@Autowired
private WxPayService wxPayService;
/**
* 生成支付参数(前端调用)
* @param openid 用户openid
* @param totalFee 订单金额(分)
* @param description 订单描述
* @return 支付参数
* @throws Exception
*/
@PostMapping(“/createOrder”)
public Map<String, String> createOrder(@RequestParam String openid,
@RequestParam Integer totalFee,
@RequestParam String description) throws Exception {
// 生成唯一商户订单号(避免重复)
String outTradeNo = “ORDER_” + UUID.randomUUID().toString().replace(“-“, “”).substring(0, 16);
return wxPayService.createJsapiPayParams(openid, outTradeNo, totalFee, description);
}
/**
* 接收微信支付回调(微信服务器调用)
* @param notifyData 回调XML数据
* @return 响应结果
*/
@PostMapping(“/notify”)
public String payNotify(@RequestBody String notifyData) {
return wxPayService.handlePayNotify(notifyData);
}
}
阶段 2:前端开发(小程序端)
前端负责发起支付请求、调起微信支付面板、处理支付结果。
1. 发起支付请求(页面 js 文件)
在需要支付的页面(如订单确认页),调用后端接口获取支付参数,再调起微信支付:
// pages/order/confirm.js
Page({
data: {
totalFee: 100, // 订单金额(分,1元=100)
description: “测试商品” // 订单描述
},
// 点击”立即支付”按钮触发
onPayTap: function() {
const that = this;
// 1. 获取用户openid(需提前通过wx.login+后端接口获取,并存入本地缓存)
const openid = wx.getStorageSync(‘openid’);
if (!openid) {
wx.showToast({
title: ‘请先登录’,
icon: ‘none’
});
return;
}
// 2. 调用后端接口生成支付参数
wx.request({
url: ‘https://www.xxx.com/api/pay/createOrder’, // 替换为你的后端接口地址
method: ‘POST’,
data: {
openid: openid,
totalFee: that.data.totalFee,
description: that.data.description
},
success: function(res) {
const payParams = res.data;
// 3. 调起微信支付面板
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
// 支付成功回调
success: function(payRes) {
wx.showToast({
title: ‘支付成功’,
icon: ‘success’
});
// TODO: 跳转至支付成功页面,更新订单状态
wx.navigateTo({
url: ‘/pages/pay/success?outTradeNo=’ + payParams.outTradeNo
});
},
// 支付失败回调
fail: function(payRes) {
wx.showToast({
title: ‘支付失败:’ + payRes.errMsg,
icon: ‘none’
});
}
});
},
fail: function(err) {
wx.showToast({
title: ‘生成订单失败’,
icon: ‘none’
});
console.error(‘生成支付参数失败:’, err);
}
});
}
});
2. 支付结果页面(可选)
创建支付成功 / 失败页面,展示订单状态,引导用户后续操作(如查看订单、继续购物)。
阶段 3:联调测试
- 后端接口测试:用 Postman 调用/api/pay/createOrder接口,检查是否能正常返回支付参数。
- 前端调起测试:在微信开发者工具中运行小程序,点击支付按钮,检查是否能正常弹出微信支付面板。
- 支付流程测试:使用微信 “沙箱环境” 或小额真实支付(如 1 分钱)测试完整流程,确认支付成功后后端能收到回调并更新订单状态。
- 异常场景测试:测试 “支付取消”“网络中断”“重复支付” 等场景,确保系统能正常处理。
三、避坑指南(新手高频问题解决方案)
1. 签名错误(最常见)
- 原因:签名原串格式错误、参数缺失、密钥不匹配、大小写错误。
- 严格按照微信规范拼接签名原串(JSAPI 支付签名原串为appId\n时间戳\n随机字符串\nprepay_id\n,注意换行符和顺序)。
- 确认使用的是 “APIv2 密钥” 还是 “APIv3 密钥”(本文示例用 APIv2,若用 APIv3 需调整加密方式)。
- 检查参数大小写(如timeStamp首字母大写,nonceStr驼峰式)。
2. 回调接收不到
- 原因:回调地址不可外网访问、端口被屏蔽、接口返回格式错误。
- 用 “内网穿透工具”(如 ngrok、花生壳)将本地接口暴露至外网(测试环境)。
- 回调地址需以https开头(微信要求),且端口为 443(默认)。
- 回调接口必须返回 XML 格式数据,且需包含return_code字段(不可返回 JSON)。
3. openid 获取失败
- 原因:小程序登录流程错误、AppID 与商户号未绑定。
- 正确实现登录流程:wx.login()获取 code → 后端用 code+AppID+AppSecret 调用微信接口获取 openid。
- 在商户平台 “AppID 授权管理” 中确认已绑定小程序 AppID。
4. 订单金额错误
- 原因:单位混淆(微信支付金额单位为 “分”,而非 “元”)