目录
目录
一、微信支付参数
1.商户号
微信支付平台->账号中心->登录账号。
2.公众号ID
微信公众号平台->设置与开发->基本配置->开发者ID。
3.商户号绑定公众号
微信支付平台->产品中心->AppID账号管理->关联AppID。
AppID:公众号ID。
AppID主体:微信公众号平台->设置与开发->公众号设置->主体信息。
4.证书和秘钥
微信支付平台->账号中心->API安全。
使用v2就申请APIv2秘钥,使用v3就申请APIv3秘钥,随机密码生成的网站在这,秘钥申请完毕。
点下载证书工具,安装打开,证书保存路径,填入商户号和商户名称,刚刚你点下载证书工具的那个页面就有这两个参数,下一步,复制出现的请求串,换到微信支付平台,粘贴复制的请求串,下一步,输入操作密码,复制出来的证书串,打开证书生成工具,下一步,这个会自动粘贴上去,下一步,证书生成成功。
5.注意事件
证书文件后面是要加入到项目解密的,在微信SDK中已经做好了解密的代码,可能会出现解密失败,是因为jdk不支持256的解密,是需要修改两个配置文件的,jdk1.8.0_131\jre\lib\security中的local_policy.jar和US_export_policy.jar。去官方下载支持256位解密的,这里我已经提供了,出现解密失败自行下载。
二、微信SDK
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
三、获取openID
1.获取code
官方示例如下:
appid->上面获取到的公众号id,
redirect_uri->用 urlEncode 对链接进行处理,URLEncoder.encode(redirect_uri),我推荐是用前端页面地址,我就是用了我公众号版本的首页的地址,用户每次进入时就会获取code。
2.获取openid
官方示例如下:
appid->公众号id, SECRET ->公众号密钥
前端获取到的code发送到后端,然后发送请求,获取到openId。
四、下载微信支付证书
CertificateDownloader.jar下载后,名字带版本号,我这里没写,把jar包名字改一下或者命令里面名字改一下都可再cmd。
java -jar CertificateDownloader.jar
-k 证书解密的密钥
-m 商户号
-s 商户证书的序列号
-o 保存证书的路径
-f 商户的私钥文件路径
把命令执行,即可下载微信证书,在把证书放到项目根目录里面,运行项目一次后,即可删除。
五、代码
1.配置微信支付参数
resources - > application.properties。
# 商户号
wxpay.mch-id=
# 证书号,申请api证书里面
wxpay.mch-serial-no=
# 商户私钥文件 申请的证书文件里面的
wxpay.private-key-path=apiclient_key.pem
# Apiv3 秘钥 上面你申请的证书时设置的
wxpay.api-v3-key=
# Appid
wxpay.appid=
2.配置微信支付配置类
config -> WxpayConfig。
package com.dingyutx.config;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data
public class WxPayConfig {
/**
* 商户号
*/
private String mchId;
/**
* 商户API证书序列号
*/
private String mchSerialNo;
/**
* 商户私钥文件地址
*/
private String privateKeyPath;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* APPID
*/
private String appid;
/**
*获取商户秘钥
*@return PrivateKey
*/
public PrivateKey getPrivateKey(String filename){
try{
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e){
throw new RuntimeException("秘钥文件不存在",e);
}
}
/**
*获取微信平台签名认证器
*@return Verifier
*/
@Bean
public Verifier getVerifier() throws Exception {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//获取签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo,privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials =new WechatPay2Credentials(mchId ,privateKeySigner);
//定时刷新微信平台的签名认证
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
return certificatesManager.getVerifier(mchId);
}
/**
*获取http请求对象
*@return CloseableHttpClient
*/
@Bean
public CloseableHttpClient getWxpayClient(Verifier verifier){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
//CloseableHttpResponse response = httpClient.execute();
return builder.build();
}
}
3.封装返回请求
entity->vo->R
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.HashMap;
import java.util.Map;
@Data
@Accessors(chain = true)
public class R {
private Integer code; //响应码
private String message; //响应消息
private Map<String,Object> data = new HashMap<>(); //响应数据
public static R ok(){
R r =new R();
r.setCode(0);
r.setMessage("success");
return r;
}
public static R error(){
R r =new R();
r.setCode(-1);
r.setMessage("defeat");
return r;
}
public R data(String key, Object value){
this.data.put(key, value);
return this;
}
}
4.微信统一下单
controller -> WxPayController。
注意事件:Verifier 导入需要配置完WxpayConfig重启一下,下载证书。
package com.dingyutx.controller;
import com.dingyutx.config.WxPayConfig;
import com.dingyutx.entity.OrderInfo;
import com.dingyutx.service.WxPayService;
import com.dingyutx.util.HttpUtils;
import com.dingyutx.util.WechatPay2ValidatorForRequest;
import com.dingyutx.vo.R;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API")
@Slf4j
public class WxPayController{
@Resource
private WxPayService wxPayService;
@Resource
private WxPayConfig wxPayConfig;
@Resource
private Verifier verifier;
@ApiOperation("调用统一下单API,生成预支付交易会话标识")
@PostMapping("/jsapi")
public R jsapiPay(OrderInfo orderInfo, String openid) throws Exception{
log.info("发起支付请求");
Map<String,Object> map = wxPayService.jsapiPay(orderInfo,openid);
return R.ok().setData(map);
}
@ApiOperation("微信支付回调")
@PostMapping("/jsapi/notify")
public R jsapiNotify(HttpServletRequest req, HttpServletResponse res){
try {
String body = readData(req);
// 构建request,传入必要参数
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(req.getHeader("Wechatpay-Serial"))
.withNonce(req.getHeader("Wechatpay-Nonce"))
.withTimestamp(req.getHeader("Wechatpay-Timestamp"))
.withSignature(req.getHeader("Wechatpay-Signature"))
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(request);
// 从notification中获取解密报文
String decryptData = notification.getDecryptData();
//开始你的业务逻辑
res.setStatus(200);
return R.ok();
}catch (Exception e){
throw new RuntimeException("解密失败===>"+e.getMessage());
}
}
/**
* 读取微信支付信息
* @param request 信息
* @return 结果
*/
private 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();
}
}
}
}
}
5.方法实现
package com.dingyutx.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.dingyutx.config.WxPayConfig;
import com.dingyutx.entity.OrderInfo;
import com.dingyutx.service.WxPayService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class WxPayServiceImpl implements WxPayService {
@Resource
private WxPayConfig wxPayConfig;
@Resource
private CloseableHttpClient httpClient;
/**
* jsapi用户下单
* @param orderInfo 订单信息
* @param openid 微信用户标识
* @return 预支付标识
*/
@Override
public Map<String, Object> jsapiPay(OrderInfo orderInfo,String openid) {
//调用统一下单API
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
try {
//微信支付参数
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid",wxPayConfig.getMchId())
.put("appid", wxPayConfig.getAppid())
.put("description", orderInfo.getTitle())
.put("notify_url", "回调链接自己写")
.put("out_trade_no", orderInfo.getOrderNo());
rootNode.putObject("amount")
.put("total", orderInfo.getTotalFee());
rootNode.putObject("payer")
.put("openid", openid);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//获取对于的微信支付认证并发送请求
CloseableHttpResponse response = httpClient.execute(httpPost);
//微信支付返回信息
String bodyAsString = EntityUtils.toString(response.getEntity());
String prepayId = JSONObject.parseObject(bodyAsString).getString("prepay_id");
//返回调起支付的信息
return getPayment("prepay_id="+prepayId, wxPayConfig.getAppid(),wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath()));
} catch (Exception e) {
throw new RuntimeException("支付失败"+e.getMessage());
}
}
/** 获取微信支付参数信息 */
private Map<String,Object> getPayment(String prepayId, String appId, PrivateKey privateKey) {
Map<String,Object> map = new HashMap<>();
String nonceStr = UUID.randomUUID().toString().toUpperCase();
long timeStamp = System.currentTimeMillis()/1000;
String source=appId+"\n"+timeStamp+"\n"+nonceStr+"\n"+prepayId+"\n";
String sign = getSign(source.getBytes(StandardCharsets.UTF_8), privateKey);
map.put("appId", appId);
map.put("timeStamp", timeStamp);
map.put("nonceStr", nonceStr);
map.put("package", prepayId);
map.put("signType", "RSA");
map.put("paySign", sign);
return map;
}
/** 获取微信微信支付签名 */
private String getSign(byte[] message, PrivateKey yourPrivateKey) {
try {
// 签名方式(固定SHA256withRSA)
Signature sign = Signature.getInstance("SHA256withRSA");
// 使用私钥进行初始化签名(私钥需要从私钥文件【证书】中读取)
sign.initSign(yourPrivateKey);
// 签名更新
sign.update(message);
// 对签名结果进行Base64编码
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
throw new RuntimeException("获取微信支付签名失败");
}
}
}
总结
花费三天时间把微信jsapi支付搞完了,记录一下,0407更新的更简单了。