1.背景
实际开发中用到微信支付的概率非常大,
至于为什么这里不必要我多少......
微信支付大体需要对接的核心接口有
其实大部分支付都是这些,就像上一节我们讲的支付宝支付一样
这里以常用的H5支付为例,其他的都是差不多的....
值得注意的时候,微信支付最常用的就是H5支付和JSAPI支付,这两者的主要区别在于
H5支付必须在非微信浏览器打开;
JSAPI支付只能在微信的浏览器打开;
微信支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
1.统一下单
2.查询订单
3.支付结果通知
4.申请退款
5.查询退款
6.下载对账单
2.需要准备的环境
微信支付目前还没有沙箱测试环境,要开开发支付功能必须要有企业资质申请公众号和商户号才可以
主要的是:
公众号appid
商户号
商户号的apiSercet密码
商户号的证书
商户号上绑定好的回调域名
3.开发步骤
3.1.统一下单
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ldp.user.service.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.ldp.user.common.constant.PayConstant; import com.ldp.user.common.constant.PayEnum; import com.ldp.user.common.constant.TestData; import com.ldp.user.common.constant.WeChatConstant; import com.ldp.user.common.exception.ParamException; import com.ldp.user.common.exception.WeChatException; import com.ldp.user.common.util.wechat.WXPayUtil; import com.ldp.user.entity.bo.PayBO; import com.ldp.user.entity.vo.PayVO; import com.ldp.user.service.IPayService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Copyright (C) XXXXXX科技股份技有限公司 * @Author: lidongping * @Date: 2021-01-07 18:47 * @Description: */ @Service @Slf4j public class WeChatPayService implements IPayService { @Override public String getPayType() { return PayConstant.WECHAT; } @Override public PayVO getPayInfo(PayBO payBO) throws Exception { // 根据订单号查询订单数据 和 支付账号数据 // 这里测试 直接将数据写在代码里 payBO.setAppId(TestData.WX_APP_ID) .setMerchantId(TestData.WX_MERCHANT_ID) .setApiSecretKey(TestData.WX_SECRET) .setPayTitle("测试支付") .setPayMoney(0.01) .setProductNo("P001") .setAsynchronousNotifyUrl("http://lidongping.free.idcfengye.com/api/async/wechat/payment") .setOpenId("ozwLvwah3xtA93csvZTWr0wON_IU"); // 以下是封装微信支付数据 Map dic = new HashMap<String, String>(); dic.put("appid", payBO.getAppId()); dic.put("mch_id", payBO.getMerchantId()); String nonceStr = RandomUtil.randomString(30); dic.put("nonce_str", nonceStr); dic.put("body", payBO.getPayTitle()); dic.put("out_trade_no", payBO.getOrderNo()); String amount = NumberUtil.roundStr(payBO.getPayMoney() * 100, 0); dic.put("total_fee", amount); dic.put("spbill_create_ip", payBO.getUserIp()); dic.put("notify_url", payBO.getAsynchronousNotifyUrl()); // 生成的支付信息一小时内有效 dic.put("time_expire", DateUtil.format(DateUtil.offsetHour(new Date(), 1), "yyyyMMddHHmmss")); // 根据类型请求支付 PayEnum payType = PayEnum.valueOf(payBO.getPayType()); switch (payType) { case WECHAT_QR: dic.put("trade_type", "NATIVE"); dic.put("product_id", payBO.getProductNo()); break; case WECHAT_JSAPI: dic.put("trade_type", "JSAPI"); if (StrUtil.isEmpty(payBO.getOpenId())) { throw new ParamException("openid 为空"); } dic.put("openid", payBO.getOpenId()); break; case WECHAT_H5: dic.put("trade_type", "MWEB"); break; default: throw new ParamException("没有对应的支付方式"); } log.info("签名前:" + JSON.toJSONString(dic)); dic.put("sign", WXPayUtil.generateSignature(dic, payBO.getApiSecretKey())); String dataXml = WXPayUtil.mapToXml(dic); log.info("微信请求下单url:" + WeChatConstant.PAY_URL); log.info("微信请求下单参数:" + dataXml); String resp = HttpUtil.post(WeChatConstant.PAY_URL, dataXml, 60 * 1000); log.info("微信请求下单返回:" + resp); Map<String, String> resMap = WXPayUtil.xmlToMap(resp); // 请求失败 if (!resMap.get("return_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) { throw new WeChatException(resMap.get("return_msg")); } // 下单失败 if (!resMap.get("result_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) { throw new WeChatException(resMap.get("err_code_des")); } // 校验支付返回结果签名 if (!checkSign(resMap, payBO.getApiSecretKey())) { throw new WeChatException("验证签名失败"); } // 处理返回结果 PayVO payVO = new PayVO(); payVO.setOrderNo(payBO.getOrderNo()); switch (payType) { case WECHAT_QR: payVO.setPayInfo(resMap.get("code_url")); break; case WECHAT_JSAPI: Map jsapi = new HashMap<String, String>(); jsapi.put("appId", payBO.getAppId()); // 当前时间秒 jsapi.put("timeStamp", System.currentTimeMillis() / 1000 + ""); jsapi.put("nonceStr", nonceStr); jsapi.put("package", "prepay_id=" + resMap.get("prepay_id")); jsapi.put("signType", "MD5"); jsapi.put("paySign", WXPayUtil.generateSignature(jsapi, payBO.getApiSecretKey())); payVO.setPayInfo(jsapi); break; default: payVO.setPayInfo(resMap.get("mweb_url")); } return payVO; } /** * 微信响应数据签名检查 * * @param map * @param key * @return * @throws Exception */ private static boolean checkSign(Map<String, String> map, String key) throws Exception { String sign = map.get("sign"); map.remove("sign"); String orign = WXPayUtil.generateSignature(map, key); return orign.equalsIgnoreCase(sign); } }
3.2.异步通知
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ldp.user.controller; import cn.hutool.extra.servlet.ServletUtil; import com.alibaba.fastjson.JSON; import com.ldp.user.common.constant.TestData; import com.ldp.user.common.constant.WeChatConstant; import com.ldp.user.common.util.RedisUtil; import com.ldp.user.common.util.wechat.WXPayUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/10 11:18 * @description */ @RestController @Slf4j public class WeChatController { /** * 注意: * 1.这里是只支付宝微信xml的参数接收,微信最近也提供了json的方式,本质上都是一样的 * 2.xml参数回调文档:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8 * * @param request * @return * @throws Exception */ @PostMapping("/async/wechat/payment") public String notifyPaymentResult(HttpServletRequest request) throws Exception { log.info("wechat异步回调...."); String paramXml = ServletUtil.getBody(request); Map<String, String> paramsMap = WXPayUtil.xmlToMap(paramXml); //解析参数 String returnCode = paramsMap.get("return_code"); String tradeStatus = paramsMap.get("result_code"); String appid = paramsMap.get("appid"); String totalFee = paramsMap.get("total_fee"); String orderNo = paramsMap.get("out_trade_no"); if (!WeChatConstant.SUCCESS.equals(returnCode)) { log.error("回调状态错误:returnCode={}", returnCode); return WeChatConstant.RETURN_FAIL; } // TODO 这里实际开发时根据订单号获取apiSecret秘钥 String apiSecret = TestData.WX_SECRET; // 验签 if (!WXPayUtil.isSignatureValid(paramsMap, apiSecret)) { log.error("验签失败"); return WeChatConstant.RETURN_FAIL; } if (!WeChatConstant.SUCCESS.equals(tradeStatus)) { log.error("支付状态失败:tradeStatus={}", tradeStatus); return WeChatConstant.RETURN_FAIL; } // TODO 更新数据库订单数据和缓存 ... 这里略 // 这里只是模拟方放入缓存,便于前端查询订单 60 * 60 * 2L 表示缓存2小时 RedisUtil.set(orderNo, JSON.toJSON(paramsMap), 60 * 60 * 2L); log.info("异步处理成功............."); return WeChatConstant.RETURN_SUCCESS; } }
3.3.测试
获取微信支付信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@Test void getPayInfo() { String url = urlLocal + "/userOrder/payInfo"; System.out.println("请求地址:" + url); HttpRequest request = HttpUtil.createRequest(Method.GET, url); Map<String, Object> map = new HashMap<>(); map.put("orderNo", "NO" + System.currentTimeMillis()); // 微信支付 wechat 支付宝 alipay map.put("payCategory", "wechat"); // 201 jsapi支付 , 202 微信H5 101支付宝H5 map.put("payType", "202"); request.form(map); System.out.println("请求参数:" + map); request.header("X-Real_IP", "192.168.5.195"); request.setConnectionTimeout(60 * 1000); String respone = request.execute().body(); System.out.println("响应结果:" + respone); }
测试结果
请求地址:http://localhost:8080/api/userOrder/payInfo 请求参数:{payCategory=wechat, orderNo=NO1610250837438, payType=202} 响应结果:{"message":"success","code":100,"data":{"orderNo":"NO1610250837438","payInfo":"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx10115509234051b333adbe01d032b80000&package=2821154835"}}
从响应的结果看,我们已经拿到了payInfo,也就是跳转到微信的url
正常情况下,只要在非微信浏览器打开就可以了,但是微信那边对请求来源的域名做了校验,我们这里测试不是很方便,就不继续演示下去了
因为实际开发中只要走到这一步,对于开发人员来说代码就已经合格了,如果只会支付不了,那多半是微信商户号上的配置有问题....
学习我们就到这里,如果在实际开发中有问题可以咨询我,共同交流技术!
4.总结
1.这里只是给大家演示了统一下单和支付回调的代码,也是微信支付的核心代码,相信只要这调通了,其他的接口类似!
2.为了更好让大家快速掌握微信支付,这篇博客已经做了配套的视频讲解,大家可以结合视频讲解学习,或者单独问我!