- 准备工作:注册商户、获取关键参数。
- 核心概念:APIv3、APIv3密钥、签名、证书。
- 开发流程:从下单到查询、退款的全过程。
- 代码示例:使用 Java 和
httpclient/OkHttp进行签名和请求。 - 常见问题与最佳实践。
准备工作
在写任何代码之前,你必须在微信支付商户平台完成以下配置:
-
注册微信支付商户:
- 访问 微信支付商户平台,完成注册和实名认证。
- 获取你的 商户号。
-
获取 API 密钥(APIv3 Key):
- 登录商户平台 -> 账户中心 -> API安全。
- 在 APIv3密钥 栏目,设置并获取你的密钥,这个密钥至关重要,用于生成和验证签名,请务必妥善保管。
-
申请产品并添加支付方式:
- 在 产品中心 -> 我的产品 中,确保你已经开通了 “JSAPI 支付” 或 “Native 支付” 等你需要的支付产品。
- JSAPI 支付通常需要用户的
openid,而 Native 支付则生成一个二维码。
-
获取商户证书:
- 在 API安全 -> 证书管理 中,下载你的 商户API证书(一个
.p12文件)。 - 你还需要一个 API证书序列号,这个在证书管理页面可以找到。
- 注意:证书是双向验证的关键,微信服务器会验证你的身份,你也需要验证微信服务器的身份。
- 在 API安全 -> 证书管理 中,下载你的 商户API证书(一个
核心概念
APIv3
微信支付官方推荐的API版本,相比旧版APIv2,它有诸多优点:
- 更安全:强制使用 HTTPS,签名算法更严谨(使用 HMAC-SHA256),支持双向证书认证。
- 更规范:所有请求和响应都使用 JSON 格式,数据结构清晰。
- 更易用:提供了官方的各语言版本 SDK,但底层原理理解起来也不复杂。
APIv3 Key
一个32位的字符串,用于对请求体进行签名,以及在接收微信回调时验证签名,它不用于数据加密,仅用于签名。
签名
微信支付为了保证请求的完整性和防篡改,要求每个请求都必须带有签名,签名过程如下:
- 拼接:
HTTP方法 + URL路径 + 请求时间戳 + 请求随机串 + 请求体。 - 加密:使用你的 APIv3 Key 对上述拼接的字符串进行 HMAC-SHA256 加密。
- 编码:将加密后的结果进行 Base64 编码,得到最终的签名串。
证书
- 商户证书:用于向微信服务器证明“我是我”,在调用某些敏感接口或接收回调时需要。
- 平台证书:微信服务器的证书,用于解密微信回调通知中的敏感数据(如支付金额),你需要从微信提供的下载地址获取最新的平台证书。
开发流程(以 JSAPI 支付为例)
JSAPI 支付适用于在微信内置浏览器或小程序中发起支付,最终由用户使用微信支付完成。
统一下单
这是支付流程的第一步,你的服务器需要调用微信的统一下单接口,创建一个预支付交易单。
请求参数准备:
appid: 你的应用ID(公众号或小程序的AppID)。mchid: 你的商户号。description: 商品描述。out_trade_no: 你的系统内部订单号,必须唯一。notify_url: 支付结果接收异步通知的URL。amount: 订单金额,单位为分。payer: 支付者信息,对于JSAPI支付,必须包含openid。
发起请求:
- URL:
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi - 方法:
POST - 请求头: 必须包含
Authorization(签名信息) 和Content-Type: application/json。 - 请求体: 上述参数的 JSON 对象。
处理响应:
- 如果下单成功,微信会返回一个预支付交易ID
prepay_id。 - 你的服务器需要使用这个
prepay_id和其他参数,按照微信规定的格式,生成一个最终传递给前端(小程序/H5)的支付参数包。
生成前端支付参数
下单成功后,你需要将以下参数返回给前端:
appId: 你的AppID。timeStamp: 当前时间戳(秒)。nonceStr: 随机字符串。package: 格式为prepay_id=xxxx的字符串。signType: 签名算法,固定为RSA或MD5,根据你的配置来,通常用RSA。
前端拿到这些参数后,调用微信的 wx.requestPayment 方法即可拉起支付。
处理支付结果通知
支付完成后,微信服务器会主动向你在 notify_url 中设置的地址发送一个 POST 请求,通知支付结果。
验证签名:
- 接收到请求后,第一步必须是验证签名,确保请求确实来自微信。
- 签名信息在请求头
Wechatpay-Timestamp、Wechatpay-Nonce和Wechatpay-Signature中。 - 验证逻辑与发起请求时类似,但数据源是请求头和请求体。
解密回调数据:
- 回调的请求体是加密的,解密需要用到 APIv3 Key 和 平台证书。
- 解密步骤:
- 从请求头
Wechatpay-Serial中获取微信平台证书序列号。 - 根据序列号找到对应的平台证书(你需要提前下载并缓存)。
- 使用证书中的公钥,对请求体中的
resource字段进行解密(AES-256-GCM 算法)。 - 解密后得到 JSON 格式的支付结果数据。
- 从请求头
业务处理:
- 验证签名和解密成功后,解析支付结果数据(如
out_trade_no,transaction_id,trade_state等)。 - 根据支付状态(如
SUCCESS)更新你系统中的订单状态。 - 必须向微信服务器返回一个应答,格式为
XML为<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>,微信服务器收到这个应答后,才停止重发通知。
查询订单
由于网络等原因,你可能会收不到异步通知,提供一个主动查询订单状态的接口是必要的。
- 调用
https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}接口。 - 需要提供你的
out_trade_no和mchid。
退款
如果需要退款,调用退款接口。
- URL:
https://api.mch.weixin.qq.com/v3/refund/domestic/refunds - 同样需要签名,并传入
out_trade_no,out_refund_no,amount等参数。
Java 代码示例
下面是一个简化的 Java 示例,展示如何生成签名并发起统一下单请求,这里使用 httpclient 和 commons-codec。
添加 Maven 依赖
<dependencies>
<!-- httpclient for sending requests -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- commons codec for base64 and sha256 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<!-- For json processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
签名工具类
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.UUID;
public class WeChatPayUtil {
// 你的配置
private static final String MCHID = "your_mch_id";
private static final String API_V3_KEY = "your_api_v3_key";
private static final String APPID = "your_app_id";
private static final String CERT_PATH = "path/to/your/apiclient_cert.p12"; // 商户证书路径
private static final String CERT_PASSWORD = "your_p12_password"; // p12文件密码,默认是商户号
/**
* 生成签名
* @param method HTTP方法 (GET, POST等)
* @param url 请求路径 (不包含域名和查询参数)
* @param timestamp 时间戳
* @param nonceStr 随机串
* @param body 请求体
* @return 签名字符串
* @throws Exception
*/
public static String generateSignature(String method, String url, String timestamp, String nonceStr, String body) throws Exception {
// 1. 拼接字符串
String message = method + "\n" +
url + "\n" +
timestamp + "\n" +
nonceStr + "\n" +
body + "\n";
// 2. 使用APIv3 Key进行HMAC-SHA256加密
byte[] digest = DigestUtils.hmacSha256(message.getBytes(StandardCharsets.UTF_8), API_V3_KEY.getBytes(StandardCharsets.UTF_8));
// 3. Base64编码
return Base64.encodeBase64String(digest);
}
/**
* 创建带签名的HTTP请求头
* @param url 请求URL
* @param body 请求体JSON
* @return 包含Authorization的请求头
* @throws Exception
*/
public static String createAuthorizationHeader(String url, String body) throws Exception {
String method = "POST";
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = UUID.randomUUID().toString().replace("-", "");
String signature = generateSignature(method, url, timestamp, nonceStr, body);
// 组装Authorization头
return "WECHATPAY2-SHA256-RSA2048 " +
"mchid=\"" + MCHID + "\"," +
"serial_no=\"your_mch_cert_serial_number\"," + // 你的商户证书序列号
"timestamp=\"" + timestamp + "\"," +
"nonce_str=\"" + nonceStr + "\"," +
"signature=\"" + signature + "\"";
}
/**
* 发起统一下单请求
* @param orderInfo 订单信息对象
* @return 微信响应
* @throws Exception
*/
public static String createOrder(WeChatPayOrderRequest orderInfo) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
String jsonBody = new ObjectMapper().writeValueAsString(orderInfo);
// 创建HTTP客户端 (使用商户证书进行双向认证)
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream instream = new FileInputStream(new File(CERT_PATH))) {
keyStore.load(instream, CERT_PASSWORD.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, CERT_PASSWORD.toCharArray())
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, new String[] {"TLSv1.2"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
try (CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build()) {
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", createAuthorizationHeader(url, jsonBody));
httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
HttpEntity entity = response.getEntity();
String responseString = EntityUtils.toString(entity, StandardCharsets.UTF_8);
EntityUtils.consume(entity); // 确保实体被完全消费
return responseString;
}
}
}
// 示例订单请求对象
public static class WeChatPayOrderRequest {
private String appid;
private String mchid;
private String description;
private String out_trade_no;
private String notify_url;
private Amount amount;
private Payer payer;
// Getters and Setters...
public static class Amount {
private int total; // 单位:分
// Getters and Setters...
}
public static class Payer {
private String openid;
// Getters and Setters...
}
}
public static void main(String[] args) {
try {
WeChatPayOrderRequest order = new WeChatPayOrderRequest();
order.setAppid(APPID);
order.setMchid(MCHID);
order.setDescription("测试商品");
order.setOut_trade_no("ORDER_" + System.currentTimeMillis());
order.setNotify_url("https://your-domain.com/notify");
WeChatPayOrderRequest.Amount amount = new WeChatPayOrderRequest.Amount();
amount.setTotal(1); // 1元 = 100分
order.setAmount(amount);
WeChatPayOrderRequest.Payer payer = new WeChatPayOrderRequest.Payer();
payer.setOpenid("user_openid_here"); // 替换为用户的openid
order.setPayer(payer);
String response = createOrder(order);
System.out.println("下单响应: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:
- 上述代码是一个简化示例,实际项目中你需要更完善的错误处理、日志记录和对象映射(如使用 Lombok 或手动编写 Getters/Setters)。
- 平台证书的下载和缓存逻辑也需要额外实现,用于解密回调。
最佳实践与建议
-
使用官方 SDK:虽然理解原理很重要,但在生产环境中,强烈建议使用微信支付官方提供的 Java SDK,它封装了签名、证书、加解密等所有复杂细节,能让你更专注于业务逻辑,官方 SDK 的 GitHub 地址:wechatpay-java。
-
异步通知是关键:务必正确实现和验证支付回调,这是保证你系统数据一致性的最重要一环。先验证签名,再解密数据,最后处理业务并返回成功应答。
-
幂等性设计:对于下单、退款等操作,你的业务接口必须是幂等的,即使用户重复请求,也应该返回同一个结果,避免重复创建订单或重复退款,可以使用
out_trade_no或out_refund_no作为幂等键。 -
日志记录:详细记录与微信支付交互的请求和响应日志,包括签名、请求体、响应内容等,这对于排查问题至关重要。
-
环境隔离:微信支付有沙箱环境和正式环境,开发时务必使用沙箱环境进行测试,避免产生真实交易,切换环境时,只需要修改请求的 URL(沙箱 URL:
https://api.mch.weixin.qq.com/sandboxnew/...)和 API Key/证书即可。 -
金额单位:微信支付 API 中,金额单位统一为 分,在代码中务必注意转换,避免因单位错误导致严重的资金问题。
希望这份详细的指南能帮助你顺利集成微信支付!
