微信支付核心功能
1. 配置
yaml
wechat:
program:
# 小程序AppID - 在微信公众平台(https://mp.weixin.qq.com)注册小程序后获取
appid: YOUR_APPID
# 商户号 - 在微信支付商户平台(https://pay.weixin.qq.com)入驻后获取
mchId: YOUR_MCH_ID
# API证书序列号 - 在微信支付商户平台下载API证书后,使用证书工具查看序列号
certSerialNo: YOUR_CERT_SERIAL_NO
# API证书私钥路径 - 商户平台下载的apiclient_key.pem 文件绝对路径
privateKeyPath: /path/to/apiclient_key.pem
# API证书公钥路径 - 从商户平台下载的微信平台证书,用于验签
publicKeyPath: /path/to/pub_key.pem
# APIv3密钥 - 商户平台API安全中设置的32位随机密钥
apiV3Key: YOUR_API_V3_KEY2. 初始化微信支付服务
java
@Service
public class WxPayServiceConfig {
@Value("${wechat.program.appid:}")
private String appId;
@Value("${wechat.program.mchid:}")
private String mchId;
@Value("${wechat.program.certSerialNo:}")
private String certSerialNo;
@Value("${wechat.program.privateKeyPath:}")
private String privateKeyPath;
@Value("${wechat.program.apiV3Key:}")
private String apiV3Key;
private WxPayService wxPayService;
@PostConstruct
public void init() {
WxPayConfig config = new WxPayConfig();
config.setAppId(appId);
config.setMchId(mchId);
config.setCertSerialNo(certSerialNo);
config.setPrivateKeyPath(privateKeyPath);
config.setApiV3Key(apiV3Key);
wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(config);
}
public WxPayService getWxPayService() {
return wxPayService;
}
}3. JSAPI 统一下单
java
@Service
@RequiredArgsConstructor
public class PayService {
private final WxPayService wxPayService;
private final SnowflakeIdGenerator idGenerator;
/**
* 创建微信支付订单
*
* @param amount 订单金额(元)
* @param openid 用户openid
* @param notifyUrl 回调地址
* @return 前端唤起支付所需参数
*/
public Map<String, String> createWxPay(BigDecimal amount, String openid, String notifyUrl) {
// 1. 生成商户订单号
String outTradeNo = idGenerator.nextStringId();
// 2. 构建请求参数
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setAppid(appId);
request.setMchid(mchId);
request.setOutTradeNo(outTradeNo);
request.setDescription("订单支付");
request.setNotifyUrl(notifyUrl);
// 金额转换为分
request.setAmount(new WxPayUnifiedOrderV3Request.Amount()
.setTotal(amount.multiply(new BigDecimal(100)).intValue()));
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openid));
try {
// 3. 调用统一下单接口
WxPayUnifiedOrderV3Result.JsapiResult result =
wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
// 4. 返回前端所需参数
Map<String, String> payParams = new HashMap<>();
payParams.put("outTradeNo", outTradeNo);
payParams.put("timeStamp", result.getTimeStamp());
payParams.put("nonceStr", result.getNonceStr());
payParams.put("package", result.getPackageValue());
payParams.put("signType", result.getSignType());
payParams.put("paySign", result.getPaySign());
return payParams;
} catch (WxPayException e) {
throw new RuntimeException("创建支付订单失败: " + e.getMessage(), e);
}
}
}4. 前端唤起支付
javascript
function onWxPay(payParams) {
return new Promise((resolve, reject) => {
WeChatJSBridge.call('chooseWXPay', {
timestamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: resolve,
fail: reject
});
});
}
// 或使用 wx.config + wx.chooseWXPay5. 支付回调处理
java
@PostMapping("/notify/pay")
public ResponseEntity<String> parseNotifyResult(@RequestBody String notifyData,
HttpServletRequest request) {
try {
// 1. 获取请求头签名信息
SignatureHeader header = new SignatureHeader();
header.setSignature(request.getHeader("Wechatpay-Signature"));
header.setNonce(request.getHeader("Wechatpay-Nonce"));
header.setSerial(request.getHeader("Wechatpay-Serial"));
header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
// 2. 解析并验证回调通知
WxPayNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyData, header);
WxPayNotifyV3Result.DecryptNotifyResult data = result.getResult();
// 3. 获取支付结果
String outTradeNo = data.getOutTradeNo();
String transactionId = data.getTransactionId();
String tradeState = data.getTradeState();
// 4. 更新本地订单状态
if ("SUCCESS".equals(tradeState)) {
orderService.updatePaymentStatus(outTradeNo, "PAID");
paymentTransactionService.updateWxTransactionNo(outTradeNo, transactionId);
}
// 5. 成功返回200
return ResponseEntity.status(200).body("");
} catch (WxPayException e) {
return ResponseEntity.status(500).body("fail");
}
}6. 申请退款
java
@Service
@RequiredArgsConstructor
public class RefundService {
private final WxPayService wxPayService;
/**
* 申请微信支付退款
*
* @param outTradeNo 原商户订单号
* @param outRefundNo 商户退款单号
* @param totalFee 订单总金额(分)
* @param refundFee 退款金额(分)
* @param refundReason 退款原因
*/
public void wxRefund(String outTradeNo, String outRefundNo,
Integer totalFee, Integer refundFee,
String refundReason) {
WxPayRefundV3Request request = new WxPayRefundV3Request();
request.setOutTradeNo(outTradeNo);
request.setOutRefundNo(outRefundNo);
request.setReason(refundReason);
request.setNotifyUrl("/notify/refund");
WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
amount.setTotal(totalFee);
amount.setRefund(refundFee);
amount.setCurrency("CNY");
request.setAmount(amount);
try {
WxPayRefundV3Result result = wxPayService.refundV3(request);
log.info("退款成功,退款单号: {}", result.getRefundId());
} catch (WxPayException e) {
throw new RuntimeException("退款失败: " + e.getMessage(), e);
}
}
}7. 退款回调处理
java
@PostMapping("/notify/refund")
public ResponseEntity<String> parseRefundNotifyResult(@RequestBody String notifyData,
HttpServletRequest request) {
try {
SignatureHeader header = new SignatureHeader();
header.setSignature(request.getHeader("Wechatpay-Signature"));
header.setNonce(request.getHeader("Wechatpay-Nonce"));
header.setSerial(request.getHeader("Wechatpay-Serial"));
header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
// 解析退款回调
WxPayRefundNotifyV3Result result = wxPayService.parseRefundNotifyV3Result(notifyData, header);
WxPayRefundNotifyV3Result.DecryptNotifyResult data = result.getResult();
String outRefundNo = data.getOutRefundNo();
String refundStatus = data.getRefundStatus();
String refundId = data.getRefundId();
// 更新退款状态
if ("SUCCESS".equals(refundStatus)) {
refundService.updateRefundStatus(outRefundNo, "REFUND_SUCCESS", refundId);
} else if ("CLOSE".equals(refundStatus)) {
refundService.updateRefundStatus(outRefundNo, "REFUND_CLOSE", refundId);
}
return ResponseEntity.status(200).body("");
} catch (WxPayException e) {
return ResponseEntity.status(500).body("fail");
}
}8. 查询订单状态
java
public String queryOrderStatus(String outTradeNo) {
WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request();
request.setOutTradeNo(outTradeNo);
request.setMchid(mchId);
try {
WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(request);
return result.getTradeState().name();
} catch (WxPayException e) {
throw new RuntimeException("查询订单失败: " + e.getMessage(), e);
}
}9. 手续费计算
java
public class WechatPayFeeUtils {
private static final BigDecimal FEE_RATE = new BigDecimal("0.006");
private static final BigDecimal MIN_CHARGE = new BigDecimal("0.01");
/**
* 计算手续费(元)
*/
public static BigDecimal calculateFee(BigDecimal amount) {
BigDecimal fee = amount.multiply(FEE_RATE)
.setScale(2, RoundingMode.HALF_UP);
return fee.compareTo(MIN_CHARGE) < 0 ? BigDecimal.ZERO : fee;
}
/**
* 计算手续费(分)
*/
public static int calculateFeeFen(int amountInFen) {
BigDecimal amount = new BigDecimal(amountInFen).divide(new BigDecimal(100));
BigDecimal fee = calculateFee(amount);
return fee.multiply(new BigDecimal(100)).intValue();
}
}10. 支付流水表设计
sql
CREATE TABLE payment_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
transaction_no VARCHAR(64) COMMENT '商户交易流水号',
wx_transaction_no VARCHAR(64) COMMENT '微信交易单号',
business_type INT COMMENT '业务类型: 1-订单 2-退款',
business_no BIGINT COMMENT '业务编号',
amount INT COMMENT '交易金额(分)',
fee INT COMMENT '手续费(分)',
direction INT COMMENT '1-支出 2-收入',
pay_status VARCHAR(20) COMMENT '支付状态',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME ON UPDATE CURRENT_TIMESTAMP
);流程图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户下单 │ -> │ 统一下单 │ -> │ 前端唤起支付 │
└─────────────┘ └─────────────┘ └─────────────┘
│
v
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 更新订单 │ <- │ 支付回调 │ <- │ 微信通知 │
│ 状态 │ └─────────────┘ └─────────────┘
└─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 申请退款 │ -> │ 微信退款 │ -> │ 退款回调 │
└─────────────┘ └─────────────┘ └─────────────┘
│
v
┌─────────────┐ ┌─────────────┐
│ 更新退款 │ <- │ 处理结果 │
│ 状态 │ └─────────────┘
└─────────────┘关键依赖
xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.5.0</version>
</dependency>