本篇文章只展示了对接微信支付的部分内容,主要参考了官方文档以及其SDK
https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
和绅士1993博主的内容 http://t.csdn.cn/HHLZs
并且是在该博主的内容上面二次开发,去掉了三方的包(IJPay),删除了多余代码,需要源码可以@我或者你可以直接使用该博主的代码,验证过有效!
我对其代码做了稍微调整。目录如下:主要是替换了html的位置,因为申请域名导致的问题
例如我申请的域名地址为 baidu.com/wxpay
添加依赖官方依赖
cn.hutool
hutool-all
5.4.0
com.google.code.gson
gson
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.4.7
当你拿到了这些如下配置后,可参考博主绅士1993的方法,需要注意的点是 appId mchId apiKey3 和domain(微信申请的域名)
通用工具类和方法:我抽取了三方包的一些代码,和官方的部分代码:
放在CommonUtil中,这里代码过多就不展示了,有需要可以私信
另外引用官方SDK中的代码,构建通用httpClient,之后根据httpClient完成发送请求功能
private WechatPayHttpClientBuilder getWechatPayHttpClientBuilder() throws Exception{
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream(wxPayV3Bean.getKeyPath()));
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(wxPayV3Bean.getMchId(), new WechatPay2Credentials(wxPayV3Bean.getMchId(),
new PrivateKeySigner(CommonUtil.getSerialNumber(wxPayV3Bean.getCertPath()), merchantPrivateKey)), wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(wxPayV3Bean.getMchId());
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
X509Certificate certificate = verifier.getValidCertificate();
List certificates = new ArrayList<>();
certificates.add(certificate);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(wxPayV3Bean.getMchId(), CommonUtil.getSerialNumber(wxPayV3Bean.getCertPath()), merchantPrivateKey)
.withWechatPay(certificates);
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
return builder;
}
H5下单支付(外部浏览器)
H5支付是最简单的,不需要繁琐的配置,根据官方需要的参数构建入参URl即可
@RequestMapping("/wxpay/v3/h5Pay")
@ResponseBody
public ResponseInfo myselfH5(HttpServletRequest request) throws Exception{
WechatPayHttpClientBuilder builder = getWechatPayHttpClientBuilder();
CloseableHttpClient httpClient = builder.build();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/h5");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
String outTradeNo = CommonUtil.generateStr();
log.info("订单号是:{}",outTradeNo);
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid",wxPayV3Bean.getMchId())
.put("out_trade_no", outTradeNo)
.put("appid", wxPayV3Bean.getAppId())
.put("description", "Image形象店-深圳腾大-QQ公仔")
.put("notify_url", wxPayV3Bean.getDomain().concat("/v3/payNotify"));
rootNode.putObject("amount")
.put("total", 1)
.put("currency", "CNY");
rootNode.putObject("scene_info")
.put("payer_client_ip", CommonUtil.getIpAddress(request))
.putObject("h5_info")
.put("type","Wap");
System.out.println("rootNode是:" + rootNode.toString());
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String result =EntityUtils.toString(response.getEntity());
log.info("reuslt是:" + result);
return new ResponseInfo(result);
}
JsApi下单支付(微信内部浏览器)
1.根据微信用户获取code
2.根据用户code获取OpenId
3.获取openId后调用下单方法
package com.example.wxpay.controller.wxpay;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.wxpay.domain.WxPayV3Bean;
import com.example.wxpay.utils.CommonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;
import java.util.Map;
@Controller
public class WxGZHController {
private static final Logger log = LoggerFactory.getLogger(WxGZHController.class);
@Resource
WxPayV3Bean wxPayV3Bean;
private static final String stateCashout = "pay";
private static final String weixinGzhSecret = "60b4299dde678770b98f1xxxxxxxxx";//开发者密码(AppSecret) 根据实际替换
private static final String jsApiPayUrl = "/wxpay/jsApiPay.html";//使用openId的html页面
//1.先查询code
@RequestMapping("/wxgzh/redirecttocashout")
public String redirectToCashout() {
log.info("准备获取code");
String urlFir = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=";
// 微信申请的域名 也就是 wxPayV3Bean.getDomain() 根据实际替换 这里是baidu.com
String domain = "http://baidu.com/wxpay";
String redirectMethod = "/wxgzh/weixinoauth";
// (重要!!!) 则拼接地址为: http://baidu.com/wxpay/wxgzh/weixinoauth 编码后为; http%3A%2F%2Fbaidu.com%2Fwxpay%2Fwxgzh%2Fweixinoauth
// 通过该地址,会进入weixinOauth方法,同时会得到微信返回的code 和 自己添加的入参state
String encoderUrl = CommonUtil.getURLEncoderString(domain + redirectMethod);
String state = "pay";
// 静默
String scopeBase = "snsapi_base";
// 需要手动点击 (可根据官网介绍选择)
String scopeUser = "snsapi_userinfo";
log.info(urlFir + wxPayV3Bean.getAppId() + "&redirect_uri=" + encoderUrl +"&response_type=code&scope=" + scopeUser + "&state=" + state + "#wechat_redirect");
return urlFir + wxPayV3Bean.getAppId() + "&redirect_uri=" + encoderUrl +"&response_type=code&scope=" + scopeUser + "&state=" + state + "#wechat_redirect";
}
//2.根据code获取openId
@GetMapping("/wxpay/wxgzh/weixinoauth")
public String weixinOauth(@RequestParam String code,@RequestParam String state) throws Exception {
log.info("获取code:{}",code);
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ wxPayV3Bean.getAppId() + "&secret=" + weixinGzhSecret + "&code=" + code + "&grant_type=authorization_code";
Map paramMap = null;
String res = HttpUtil.get(url, paramMap);
System.out.println(res);
String openid = JSONObject.parseObject(res).getString("openid");
log.info("根据code查询得到openId:{}",openid);
String redirect = "";
switch (state){
case stateCashout:
redirect =jsApiPayUrl + "?openId=" + openid;
break;
}
log.info("准备调起jsApi支付,url:{}",redirect);
return "redirect:" + redirect;
}
}
4.封装url和请求参数,根据前端Js和后端返回的参数唤醒微信支付
@RequestMapping("/wxpay/v3/jsApiPay")
@ResponseBody
public String jsApiPay(@RequestParam(value = "openId", required = false, defaultValue = "oNB9p1BpVJEqu0xvwt2X3i93G1A4") String openId) throws Exception{
WechatPayHttpClientBuilder builder = getWechatPayHttpClientBuilder();
CloseableHttpClient httpClient = builder.build();
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");
// OutputStream流用来保存请求数据
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
// 订单失效时间(可填可不填)
String timeExpire = CommonUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 60);
// 随机生成订单号
String outTradeNo = CommonUtil.generateStr();
log.info("订单号是:{}",outTradeNo);
ObjectNode rootNode = objectMapper.createObjectNode();
// 还有其他字段,例如attach 可填可不填
rootNode.put("mchid",wxPayV3Bean.getMchId())
.put("appid", wxPayV3Bean.getAppId())
.put("description", "Image形象店-深圳腾大-QQ公仔")
.put("notify_url", wxPayV3Bean.getDomain().concat("/v3/payNotify"))
.put("out_trade_no", outTradeNo)
.put("time_expire", timeExpire);
rootNode.putObject("amount")
.put("total", 1);
rootNode.putObject("payer")
.put("openid", openId);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
System.out.println("下单参数是:" + bos.toString("UTF-8"));
// 发出请求得到响应结果
CloseableHttpResponse response = httpClient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSONUtil.parseObj(result);
String prepayId = jsonObject.getStr("prepay_id");
log.info("结果是:{}", result);
Map map = CommonUtil.buildPayMap(wxPayV3Bean.getAppId(),prepayId, wxPayV3Bean.getKeyPath());
log.info("唤起支付参数:{}", map);
return JSONUtil.toJsonStr(map);
}
根据订单号tradeNo 进行订单查询,通用:
@GetMapping("/wxpay/v3/searchJsApi")
@ResponseBody
public String searchJsApi() throws Exception{
WechatPayHttpClientBuilder builder = getWechatPayHttpClientBuilder();
CloseableHttpClient httpClient = builder.build();
String result = "";
String tradeNo = "6d259dd0d9344be1a97c9dc55964591d";
String mchid = wxPayV3Bean.getMchId();
try { // "https://api.mch.weixin.qq.com/v3/pay/transactions/id/4200000889202103303311396384?mchid=1230000109"
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+ tradeNo +"?mchid=" + mchid);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
result = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSONUtil.parseObj(result);
String code = jsonObject.getStr("trade_state");
System.out.println(result);
System.out.println("结果:" + code);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
根据订单号进行退款。通用:
@GetMapping("/v3/refund")
public String refund() throws Exception {
String tradeNo = "6d259dd0d9344be1a97c9dc55964591d";
// 商家内部的退款单号
String myRefundNo ="123456789";
WechatPayHttpClientBuilder builder = getWechatPayHttpClientBuilder();
CloseableHttpClient httpClient = builder.build();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.addHeader("Accept","application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("out_trade_no",tradeNo);
rootNode.put("out_refund_no", myRefundNo);
rootNode.putObject("amount")
.put("refund",1)
.put("total", 1)
.put("currency", "CNY");
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity());
log.info("结果是:{}", result);
return result;
}
下面是下载证书 和 回调通知地址的实现,只是替换了使用官方的SDK
// 获取平台证书certificates
@GetMapping("/getPlatformCert")
@ResponseBody
public void getCa() throws Exception{
WechatPayHttpClientBuilder builder = getWechatPayHttpClientBuilder();
CloseableHttpClient httpClient = builder.build();
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/certificates");
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
System.out.println("证书是: " + bodyAsString);
JSONObject jsonObject = JSONUtil.parseObj(bodyAsString);
JSONArray dataArray = jsonObject.getJSONArray("data");
// 默认认为只有一个平台证书
JSONObject encryptObject = dataArray.getJSONObject(0);
JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getStr("associated_data");
String cipherText = encryptCertificate.getStr("ciphertext");
String nonce = encryptCertificate.getStr("nonce");
String serialNo = encryptObject.getStr("serial_no");
final String platSerialNo = CommonUtil.savePlatformCert(wxPayV3Bean.getApiKey3(), associatedData, nonce, cipherText, wxPayV3Bean.getPlatformCertPath());
log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
}
@RequestMapping(value = "/wxpay/v3/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
@ResponseBody
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("收到支付成功的通知");
Map map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = CommonUtil.readData(request);
log.info("支付通知密文 {}", result);
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
String plainText = CommonUtil.verifyNotify(serialNo, result, signature, nonce, timestamp,
wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath());
log.info("支付通知明文 {}", plainText);
if (StrUtil.isNotEmpty(plainText)) {
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}



