1、maven引入依赖
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.4.5
2、配置文件application.yml
#微信支付相关参数
wx-pay:
#商户id(微信支付商户平台获取)
mch-id: xxxxxxxxx
#公众号appid(和商户id绑定过后,微信支付商户平台或者微信公众平台获取)
appid: xxxxxxxxx
#商户证书序列号(从微信支付商户平台-API安全获取)
mch-serial-no: xxxxxxxxx
#商户私钥(从微信支付商户平台-API安全获取,放在项目根目录与src同级)
private-key-path: apiclient_key.pem
#APIv3密钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
api-v3-key: xxxxxxxxx
#微信服务器地址
domain: https://api.mch.weixin.qq.com
#接收结果通知地址(自己项目的域名,发布到服务器上就是服务器的域名,必须域名不支持ip,本地调试需要使用内网穿透-推荐使用花生壳来做内网穿透,但会收取少量费用,也可使用ngrok,免费但每次启动域名会变化,需要修改此处地址)
notify-domain: https://xxxxxxxxx
3、支付配置类WxPayConfig(GitHub - wechatpay-apiv3/wechatpay-apache-httpclient: 微信支付 APIv3 Apache HttpClient装饰器(decorator))
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@Configuration
@Data
public class WxPayConfig {
@Value("${wx-pay.mch-id}")
private String mchId;
@Value("${wx-pay.appid}")
private String appid;
@Value("${wx-pay.mch-serial-no}")
private String mchSerialNo;
@Value("${wx-pay.private-key-path}")
private String privateKeyPath;
@Value("${wx-pay.api-v3-key}")
private String apiV3Key;
@Value("${wx-pay.domain}")
private String domain;
@Value("${wx-pay.notify-domain}")
private String notifyDomain;
/**
* 获取商户私钥
* */
private PrivateKey getPrivateKey(String filename){
try {
return PemUtil.loadPrivateKey(
new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在",e);
}
}
@Bean(name = "wxPayHttpClientOne")
public CloseableHttpClient wxPayHttpClient(){
//私钥
PrivateKey mchPrivateKey= getPrivateKey(privateKeyPath);
//微信证书校验器
Verifier verifier=null;
try {
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, mchPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
verifier = certificatesManager.getVerifier(mchId);
} catch (Exception e) {
new RuntimeException("微信证书校验器配置失败",e);
}
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, mchPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
4、返回前台的实体类(微信支付文档有要求JSAPI调起支付_JSAPI支付|微信支付商户文档中心)
@Data
public class PaymentDto {
private String appId;
private String timeStamp;
private String nonceStr;
private String prepayId;
private String signType;
private String paySign;
}
5、后台获取小程序发起支付需要的参数
@TableName("order_info")
@Data
public class OrderInfo {
//根据自己项目
private String outTradeNo;
}
@PostMapping({"/createPay"})
public JsonResult createPay(@RequestBody OrderInfo entity) {
PayMentDto response = orderInfoService.createPay(entity);
return JsonResult.OK().data(response);
}
@SaIgnore
@PostMapping("/callback")
public JsonResult paySignal(HttpServletRequest request, HttpServletResponse response) throws IOException {
try{
Gson gson = new Gson();
Map map = gson.fromJson(request.getReader(), Map.class);
log.info(map.toString());
log.info("支付id:{}", map.get("id"));
}catch (Exception e){
}
return JsonResult.OK().data("444");
}
@Service
@Slf4j
public class OrderInfoService {
@Value("${wx-pay.mch-id}")
private String mchId;
@Value("${wx-pay.appid}")
private String appid;
@Value("${wx-pay.mch-serial-no}")
private String mchSerialNo;
@Value("${wx-pay.private-key-path}")
private String privateKeyPath;
@Value("${wx-pay.api-v3-key}")
private String apiV3Key;
@Value("${wx-pay.domain}")
private String domain;
@Value("${wx-pay.notify-domain}")
private String notifyDomain;
@Resource(name = "wxPayHttpClientOne")
private CloseableHttpClient wxPayHttpClient;
//
public PaymentDto createPay(OrderInfo entity) throws IOException {
//请求构造
HttpPost httpPost=new HttpPost(domain+WxApiType.JSAPI_PAY.getType());
//构造数据,根据官方文档需求构造
HashMap reqData=new HashMap<>();
reqData.put("appid",appid);
reqData.put("mchid",mchId);
reqData.put("description","订单详情");
reqData.put("out_trade_no","商户系统生成的订单号");
reqData.put("notify_url",notifyDomain+"/callback");
HashMap amount=new HashMap<>();
//单位是分
amount.put("total",1);
reqData.put("amount",amount);
HashMap payer=new HashMap<>();
payer.put("openid",xxxxxxxxx);
reqData.put("payer",payer);
String jsonReqData=new Gson().toJson(reqData);
StringEntity entity=new StringEntity(jsonReqData,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
//请求头
httpPost.setHeader("Accept","application/json");
//完成签名并执行请求
CloseableHttpResponse response= wxPayHttpClient.execute(httpPost);
Map dataMap=null;
PaymentDto paymentDto = new PaymentDto();
try{
int statusCode=response.getStatusLine()
.getStatusCode();
//成功
if(statusCode==200){
String body= EntityUtils.toString(response.getEntity());
dataMap=new Gson().fromJson(body,HashMap.class);
String timeStamp = String.valueOf(DateUtil.current());
String nonceStr = RandomUtil.randomString(RandomUtil.randomInt(1, 33));
//拿到prepare_id存入数据库订单,把订单信息传回前端提示用户发起支付,这个data根据官方文档要求需要自己拼接,注意:\n是必须要的。
String data = "wxkjdjjfjkhjhjhj\n"+ timeStamp+"\n"+nonceStr+"\nprepay_id="+dataMap.get("prepay_id")+"\n";
RSA rsa = new RSA(PemUtil.loadPrivateKey(
Files.newInputStream(Paths.get("apiclient_key.pem"))), null);
// 创建签名对象并指定算法
Sign sign = new Sign(SignAlgorithm.SHA256withRSA, rsa.getPrivateKey(), null);
// 对数据进行签名
byte[] signedData = sign.sign(data.getBytes(StandardCharsets.UTF_8));
String base64Signature = Base64.getEncoder().encodeToString(signedData);
paymentDto.setAppId(appid);
paymentDto.setTimeStamp(timeStamp);
paymentDto.setNonceStr(nonceStr);
paymentDto.setPrepayId(dataMap.get("prepay_id"));
paymentDto.setSignType("RSA");
paymentDto.setPaySign(base64Signature);
}
//失败
else{
if(statusCode!=204){
String body=EntityUtils.toString(response.getEntity());
log.error(body);
}
}
}
finally{
response.close();
}
return paymentDto;
}
}
6、前台发起支付
发起支付