微信开发|一文实现微信小程序支付 史上最全版

前言

微信小程序在开发中大部分都是电商类的业务,电商必不可少的是支付环节,实现微信小程序支付看微信官方文档太晦涩(多数人都觉得官方人员写的烂),博主也是在看官方文档的路上迷失去了自己。这篇文章由博主实战采坑后总结的经验构成,尽量带着大家读懂官方文档,绕开前方大坑,哈哈哈哈哈
微信小程序支付必须有商户号,绑定商户平台才有微信支付功能,个人小程序是没有支付功能的。商户平台可使用商户营业执照申请
一、支付流程导读 1.支付系统交互流程
在开发前若时间充足,建议大家通篇阅读一下微信支付官方文档,对业务名称加以熟悉,看清楚接口参数名,是否必传,方便后面的开发。[微信支付文档,点我跳转] (https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_10&index=1)

微信支付业务流程图:
微信开发|一文实现微信小程序支付 史上最全版
文章图片

  1. 调用微信登录API接口获取用户openid。
  2. 将支付金额,回调地址,小程序appid等传入调用统一下单接口API。
  3. 微信官方生成预支付订单数据,包括订单id,支付金额等。
  4. 将返回数据进行签名,防止篡改。
  5. 将5个参数数据放回给前端调用Payment(输密码的面板)。
  6. 用户确认金额,输入密码。
  7. 微信进行密码鉴权。
  8. 微信回调你的业务系统,将支付是否成功通知给你,进行订单支付状态更新。此时你的接口必须放开权限,且公网可访问
二、开发前的准备 1.准备资料
  1. appid :小程序的id
  2. apikey : 可在小程序开发配置找到
  3. mchid : 商户号,商户平台可找到
2.引入SDK 微信支付官方给我们提供了很多工具类。SDK方法中有方法可以也没有业务订单数据的情况下实现跑通微信支付,给大家提供的一个测试类。很友好,里面提供了支付时需要用的工具类方法。
SDK下载地址 点我下载SDK
三、获取用户openid 1. 配置文件
weixin: # 小程序ID appid: wx08huxxxxxxxxxxx # 小程序秘钥 appSecret: 95e2932xxxxxxxxxxxx # 商户秘钥 apiKey: ymruurG3xxxxxxxxxx # 商户ID mchId: 1244xxxxxxxx #支付回调通知地址 必须为公网可访问的域名地址 且权限放开 routine: notify: https:xxxxxx/wx/pay/notify

2. 获取用户openid
若不懂openid的请自行百度

首先前端同学需要请求微信服务获取一个登陆code 。然后带着登陆code请求后端接口
@GetMapping(value = "https://www.it610.com/login/{code}") @OperationLog(name = "获取用户openid", type = OperationLogType.ADD) @ApiOperation(value = "https://www.it610.com/article/获取用户openid" , response = ApiResult.class) @ResponseBody public ApiResult login(@PathVariable String code) throws Exception { Map, String> result= wxAuthApiService.wxLogin(code); if (result.isEmpty()){ return ApiResult.fail("获取用户小程序openid失败"); } return ApiResult.ok(result); }

Service
public interface WxAuthApiService { // 获取获取用户小程序openid Map,String> wxLogin(String code) throws Exception; }

实现类
/** * @Author 公众号:真香号 * @Version 1.0 * @Description */ @Slf4j @Service public class WxAuthApiServiceImpl implements WxAuthApiService { // 小程序ID @Value("${weixin.appid}") private String APPID; // 小程序秘钥 @Value("${weixin.appSecret}") private String SECRET; @Transactional(rollbackFor = Exception.class) @Override public Map, String> wxLogin(String code) throws Exception { Map, String> param = new ConcurrentHashMap<>(); if (StringUtils.isEmpty(code)) { throw new BusinessException("获取用户openid失败,code 不能为空"); } String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", APPID, SECRET, code); String result = HttpUtils.sendGet(url, null); log.info(result); JSONObject object = JSON.parseObject(result); if (object.getInteger("errcode") != null) { log.info("获取用户openid失败code:{},msg:{}", object.getInteger("errcode"), object.getString("errmsg")); throw new BusinessException("获取用户openid失败,errcode:" + object.getInteger("errcode") + "errmsg:" + object.getString("errmsg")); } String openId = object.getString("openid"); // 会话密钥 String sessionKey = object.getString("session_key"); // 根据业务将openid与sessionKey 持久化存入数据库 param.put("openid", openId); param.put("sessionKey", sessionKey); return param; }

顺利拿到openid 以后就可以请求支付,因为微信支付必须确定订单的支付人openid。
四、支付流程 1. 支付接口
/** * 微信小程序 统一下单支付 * @param request * @param response * @return */ @PostMapping("/unifiedOrder") @ResponseBody @ApiOperation(value = "https://www.it610.com/article/微信统一下单", response = ApiResult.class) public ApiResult unifiedOrder(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("---------统一下单--------"); String openId = request.getParameter("openId"); if (StringUtils.isEmpty(openId)) { thrownew BusinessException("openid 不能为空"); } log.info("---------openId--------" + openId); Map, String> map = wxPayService.unifiedOrder(openId, IpUtil.getIpAddress(request), request); return ApiResult.ok(map); }

service
/** *统一下单接口 * @param openid 用户标识 * @param ip请求ip * @param request 订单数据 * @return */ Map,String> unifiedOrder(String openid, String ip, HttpServletRequest request) throws Exception;

【微信开发|一文实现微信小程序支付 史上最全版】实现类
/** * @Author 公众号:真香号 * @Version 1.0 * @Description */ @Service @Slf4j public class WxPayServiceImpl implements WxPayService {@Value("${weixin.appid}") private String APPID; @Value("${weixin.appSecret}") private String SECRET; @Value("${weixin.apiKey}") private String APIKEY; @Value("${weixin.mchId}") private String MCHID; //交易类型 public static final String TRADE_TYPE_JS = "JSAPI"; public static final String FAIL = "FAIL"; public static final String SUCCESS = "SUCCESS"; // 微信统一下单url public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 支付回调地址 @Value("${routine.notify}") public String NOTIFY_URL; /** 统一下单 * @param openid 用户唯一标识 * @param ip 请求方IP地址 * @param request 请求数据 * @return 已生成订单信息 */ @Override public Map, String> unifiedOrder(String openid, String ip, HttpServletRequest request) throws Exception { // 1. 拼接统?下单地址参数 log.info("进入下单流程"); Map, String> paramMap = new ConcurrentHashMap, String>(10); paramMap.put("appid", APPID); paramMap.put("mch_id", MCHID); // 商品信息 paramMap.put("body", "test"); paramMap.put("openid", openid); // 订单号 paramMap.put("out_trade_no", request.getParameter("orderId")); // 随机字符串,长度要求在32位以内。 paramMap.put("nonce_str", StringHelpUtils.generateNonceStr()); // 以分为单位,1代表0.01元 paramMap.put("total_fee", "1"); paramMap.put("spbill_create_ip", ip); paramMap.put("notify_url", NOTIFY_URL); // 支付类型 paramMap.put("trade_type", TRADE_TYPE_JS); // 微信官方SDK中生成签名方法 String paramXml = WxPayUtil.generateSignedXml(paramMap, APIKEY, WxPayUtil.SignType.MD5); log.info("签名参数:{}", paramXml); Map, String> resultMap = new ConcurrentHashMap<>(10); // 请求微信服务发起支付 生成预支付订单ID String returnXml = HttpClientUtil.httpsRequest(UNIFIED_ORDER_URL, "POST", paramXml); log.info("返回参数:{}", returnXml); resultMap = WxPayUtil.xmlToMap(returnXml); Map, String> returnMap = new ConcurrentHashMap<>(); // 微信SDK方法验证签名 再次签名 if (WxPayUtil.isSignatureValid(resultMap, APIKEY, WxPayUtil.SignType.MD5)) { // 签名成功后返给前端 returnMap.put("appId", APPID); // 时间戳 returnMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + ""); // 随机字符串 returnMap.put("nonceStr", WXPayUtil.generateNonceStr()); // 签名类型 returnMap.put("signType", WXPayConstants.MD5); // 微信预订单ID ,package参数名在微信文档中已指定必填 returnMap.put("package", "prepay_id=" + resultMap.get("prepay_id")); // 5. 通过appId, timeStamp, nonceStr, signType, package及商户密钥进?key=value形式拼接并加密 String paySign = WXPayUtil.generateSignature(returnMap, APIKEY, WXPayConstants.SignType.MD5); returnMap.put("paySign", paySign); returnMap.put("mchId", MCHID); returnMap.put("resultCode", SUCCESS); log.info("returnMap:{}", returnMap); return returnMap; } else { returnMap.put("resultCode", FAIL); log.info("签名验证错误"); } return returnMap; } }

https 请求方法
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try {URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 当outputStr不为null时向输出流写数据 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意编码格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 释放资源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); return buffer.toString(); } catch (ConnectException ce) { System.out.println("连接超时:{}" + ce); } catch (Exception e) { System.out.println("https请求异常:{}" + e); } return null; }

前端同学拿到以上参数就能唤起Payment 支付密码输入面板啦。
2. 注意事项
  • 微信支付金额以“分”为单位,需将"元"转换为"分"。
  • 微信支付金额不能为"0"元。0元将导致签名失败,无法唤起Payment。
    以上给我记牢了,我曾经一次又一次在这翻车,一步步看日志的那种,唉不说了,一把辛酸泪。
五 、支付回调
何所谓支付回调:订单支付钱到了微信,究竟是否支付成功,业务系统无从知道。微信官方提供一个消息推送接口,下单后,将会请求你订单支付时设置的回调地址接口地址。微信会传送订单号、支付是否成功、支付金额、时间等信息。根据信息我们对相应的订单进行业务处理。
上代码。此处请自行脑补抖音“三支花”大妈 “上才艺”视频场景。哈哈哈哈哈
接口:
/** * 通知回调 notify * @param request 订单信息 * @param response * @returnSUCCESS/FAIL * @throws Exception */ @RequestMapping(value = "https://www.it610.com/notify") @ApiOperation(value = "https://www.it610.com/article/微信支付回调", response = ApiResult.class) public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("微信开始回调"); StringresXml = wxPayService.notify(request,response); log.info("微信回调成功,返回值:{}",resXml); }

实现类:
/** 支付回调 * @param request * @param response * @return */ @Override public String notify(HttpServletRequest request, HttpServletResponse response) throws IOException { log.info("---------------微信支付回调----------------------"); // 调用返回 xml String resXml = ""; try { // 解析request中xml 转换成map Map, String> map = WxPayUtil.parseXml(request); log.info("---------------微信支付回调订单号为:{}--------", map.get("out_trade_no")); // 检验签名是否成功 if (WxPayUtil.isSignatureValid(map, APIKEY, WxPayUtil.SignType.MD5)) { log.info("微信支付return_code:{}", map.get("return_code")); //log.info("判断结果:{}", "SUCCESS".equals(map.get("return_code"))); if ("SUCCESS".equals(map.get("return_code"))) { Boolean result = orderService.orderCallback(map).getData(); log.info("支付回调得到order更新数据库结果:{}", result); if (result) { // 返回success给微信服务器 剔除推送队列中的本次交易 resXml = " " + ""; } else { log.info("----------------微信回调成功 更新数据库状态失败---------------"); } } else { log.info("微信回调失败:{}", map.get("return_code")); } } else { resXml = "" + "" + "" + " "; } } catch (Exception e) { log.error("微信回调失败,抛出异常:{}", e); } PrintWriter printWriter = new PrintWriter(response.getOutputStream()); log.info("回调返回给微信服务器:{}", resXml); printWriter.write(resXml); printWriter.flush(); printWriter.close(); return resXml; }

一定要放开支付回调的权限啊,不然微信想回调你,也进不了你家大门啊
以上就是微信小程序支付实现的大致过程,下期讲 微信退款
微信开发|一文实现微信小程序支付 史上最全版
文章图片

关注个人微信公众号,与作者交流,第一时间接收更新通知:
微信开发|一文实现微信小程序支付 史上最全版
文章图片

    推荐阅读