小程序接入(基于APIv3进行支付)
需要的参数如下所示
wx:
# 微信小程序appid
app_id: xxx
# 微信小程序app_secret
app_secret: xxx
# 微信小程序获取openid地址
session_key_url: https://api.weixin.qq.com/sns/jscode2session?appid=${wx.app_id}&secret=${wx.app_secret}&js_code=%s&grant_type=authorization_code
pay:
# 商户ID
mch_id: xxx
# API v3密钥
api_V3_key: xxx
# 证书序列号
serial_number: xxx
# 商户证书保存路径
private_key_path: /xxx/xxx/apiclient_key.pem
# 支付成功之后我方接受微信服务器通知地址,必须是Https
notify_url: https://xxx/smart/wx/notify
参数获取地址:
小程序相关:https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN
商户参数相关:
文档地址
https://pay.weixin.qq.com/docs/merchant/development/development-preparation/parameter-application.html
证书获取地址
https://pay.weixin.qq.com/index.php/core/cert/api_cert#/
com.github.wechatpay-apiv3
wechatpay-java
0.2.12
文档地址:
https://github.com/wechatpay-apiv3/wechatpay-java
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.app.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 微信支付
* @author: azhou
* @create: 2024/1/20 14:28
**/
@Component
@Slf4j
public class WxPayService {
private RSAAutoCertificateConfig notificationConfig;
@Value("${wx.session_key_url}")
private String sessionKeyUrl;
/**
* 商户号
*/
@Value("${wx.pay.mch_id}")
public String merchantId;
/**
* 商户API私钥路径
*/
@Value("${wx.pay.private_key_path}")
public String privateKeyPath;
/**
* 商户证书序列号
*/
@Value("${wx.pay.serial_number}")
public String merchantSerialNumber;
/**
* 商户APIV3密钥
*/
@Value("${wx.pay.api_V3_key}")
public String apiV3Key;
/**
* 通知地址
*/
@Value("${wx.pay.notify_url}")
public String notifyUrl;
/**
* 小程序ID
*/
@Value("${wx.app_id}")
public String wxAppid;
/**
* 拉起微信小程序预付款信息
*
* @param orderNo 订单编号
* @param totalPrice 支付的价格(字符串例如:25.66)
* @param openId 微信openId
* @return 预支付ID和openId的map
*/
public Map<String, Object> wxSmartPay(String orderNo, String totalPrice, String openId) {
Map<String, Object> map = new HashMap<>();
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(notificationConfig).build();
PrepayRequest request = new PrepayRequest();
request.setAppid(wxAppid);
request.setMchid(merchantId);
request.setDescription("商品描述");
request.setOutTradeNo(orderNo);
request.setNotifyUrl(notifyUrl);
Amount amount = new Amount();
amount.setTotal(convertToCents(totalPrice));
amount.setCurrency("CNY");
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(openId);
request.setPayer(payer);
log.info("小程序调用微信支付请求参数:{}", JSON.toJSONString(request));
PrepayWithRequestPaymentResponse requestPaymentResponse = service.prepayWithRequestPayment(request);
log.info("小程序调用微信支付响应结果:{}", JSON.toJSONString(requestPaymentResponse));
// requestPaymentResponse对象中包含小程序中拉起微信支付的参数
map.put("data", requestPaymentResponse);
map.put("openId", openId);
return map;
}
/**
* 接受微信通知
*
* @param requestParam 微信服务器请求过来的参数
* @return 解密之后的参数对象
*/
public Transaction wxNotify(RequestParam requestParam) {
NotificationParser parser = new NotificationParser(notificationConfig);
Transaction transaction;
try {
// 以支付通知回调为例,验签、解密并转换成 Transaction
transaction = parser.parse(requestParam, Transaction.class);
} catch (Exception e) {
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
log.info("微信通知,签名校验失败", e);
return null;
}
log.info("微信通知,解密结果:{}", JSON.toJSONString(transaction));
return transaction;
}
/**
* 转换价格
*
* @param totalPrice
* @return
*/
public static Integer convertToCents(String totalPrice) {
BigDecimal bigDecimal = new BigDecimal(totalPrice);
BigDecimal multipliedBy100 = bigDecimal.multiply(new BigDecimal(100));
return multipliedBy100.setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
}
/**
* 获取微信openId
*
* @param code
* @return
*/
public String getOpenId(String code) {
try {
String res = HttpUtil.get(String.format(sessionKeyUrl, code));
log.debug("获取WX_Session_Key结束,结果:{}", res);
JSONObject jsonObject = JSON.parseObject(res);
if (jsonObject.containsKey("openid")) return jsonObject.get("openid").toString();
else return "";
} catch (Exception e) {
log.error("wxCode解密失败:code->{}", code);
return "";
}
}
/**
* 初始化微信支付配置
*/
@PostConstruct
public void init() {
// RSAAutoCertificateConfig对象为自动更新微信平台证书的对象,可发起微信支付调用,也可以接受通知进行解析
notificationConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
}
}
注意:微信支付里面有两个证书,一个是商户证书,就是在商户平台中我们自己获取下载的证书,另外一个是平台证书,用来验证微信服务器通知过来的签名是否正确,两者不同,而平台证书只能通过调用微信官方的接口进行获取,因为我们用的是 wechatpay-java 微信官方推出的sdk,所以里面内置了自动获取平台证书,以及对平台证书的更新操作,所以我们不需要管平台证书。
核心业务处理代码
@Override
public boolean wxNotify(RequestParam requestParam) {
// 获取微信通知之后的参数对象
Transaction transaction = wxPayService.wxNotify(requestParam);
// 是否成功支付
boolean isPay = Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState());
// 订单号
String outTradeNo = transaction.getOutTradeNo();
if (!isPay) log.info("微信通知,订单号:{},尚未支付!", outTradeNo);
else {
// 支付金额
Integer payerTotal = transaction.getAmount().getPayerTotal();
Order order = orderService.getByOrderNo(outTradeNo);
if (order == null) log.info("微信通知,订单号:{} 查询结果为空!", outTradeNo);
String payPrice = order.getPayPrice();
Integer totalPrice = StringUtils.convertToCents(payPrice);
if (!totalPrice.equals(payerTotal)) {
log.info("微信通知,订单号:{} 支付金额不一致!", outTradeNo);
} else {
// 支付成功之后进行的操作
}
}
return isPay;
}
接收微信通知接口
/**
* 接受微信通知接口(只能是post请求)
* @param request
* @return
*/
@PostMapping("/wx/notify")
public ResponseEntity.BodyBuilder wxNotify(HttpServletRequest request) {
String signature = request.getHeader("Wechatpay-Signature");
String serial = request.getHeader("Wechatpay-Serial");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String body = HttpUtils.readData(request);
RequestParam requestParam = new RequestParam.Builder().serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(body)
.build();
boolean wxNotify = goodsCartService.wxNotify(requestParam);
return ResponseEntity.status(wxNotify ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR);
}
其他
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
wxPay(param) {
var that = this;
//小程序发起微信支付
wx.requestPayment({
timeStamp: param.timeStamp, //记住,这边的timeStamp一定要是字符串类型的,不然会报错
nonceStr: param.nonceStr,
package: param.packageVal,
signType: param.signType,
paySign: param.paySign,
success: function(event) {
wx.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
});
},
fail: function(error) {
// fail
console.log("支付失败")
console.log(error)
},
complete: function() {
// complete
console.log("pay complete")
}
});
}
wx.login({
success(res) {
console.log(res.code)
if (res.code) {
// res.code为上方WxPayService.getOpenId()中的code
// 携带订单相关参数以及code进行相关请求
} else {
Util.showMyToast("系统有误,请联系管理员")
}
}
})