-
准备工作
在开发微信小程序支付接口之前,首先得为微信小程序申请接入微信支付,不麻烦,给个连接:微信支付接入指引
跟着指引走,很简单,目前,小程序开发的微信支付接口只支持企业级开发的小程序,需提交相关企业经营相关资质,个人暂时不支持开通支付,过程中要支付300¥,最终会拿到开发需要的需要的“商户号”、“商户api安全key”,这个key是自己在商户后台设置的的32位秘钥,生成方式自己选择,百度一堆;(这两个比较重要,放在后台,不可以放在小程序前台)。
除了获取到“商户号”和“api安全key”之外,我们还需要在商户后台获取到“API证书”,至于什么是“API证书”、如何获取的问题,百度一堆,这里给个参考:什么是API证书?如何获取API证书
最终我们会获取到3个文件,apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem,其中 apiclient_cert.p12 对我们是最重要的,将它放入到我们项目的资源文件目录下;
-
业务流程
在进行开发之前,深刻理解业务流程很重要,这里可以参考官方的:微信小程序支付业务流程时序图
商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,api参见公共api【查询订单API】
上面提到的交互中的第一条,登录接口,上一篇博文已经完成了,这里就可以不用做了,切勿重复调用登录接口,会刷新之前的session_key,造成用户登陆授权流程的混乱,引起不必要的麻烦。
-
开发方式的选择
这里我选择的是使用微信官方提供的SDK开发,可以省去我们很多事情:官方SDK下载地址
可以看到官方java版SDK下载下来共有8个java文件,主要是一些封装好的工具类和配置文件,我们将这些java文件复制到我们的项目下,可以为其创建一个package包;
开发代码
首先,我们在sdk文件的同级目录,创建一个自己的配置类,MyConfig,继承sdk自带的WXPayConfig,重写其中的抽象方法,在构造函数中要读取我们的配置文件 apiclient_cert.p12 ,这个在上面讲过了;
MyConfig.java
package com.wx.video.wxpay.sdk; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.springframework.core.io.ClassPathResource; /** ** 功能描述:自定义的微信支付配置类 * @Package: com.github.wxpay.sdk * @author: jiguiquan * @date: 2019年6月13日 上午11:08:35 */ public class MyConfig extends WXPayConfig { //读取证书文件返回的字节数组 private byte[] certData; public MyConfig() throws Exception { try { ClassPathResource classPathResource = new ClassPathResource("apiclient_cert.p12"); //获取文件流 InputStream certStream = classPathResource.getInputStream(); this.certData = IOUtils.toByteArray(certStream); certStream.read(this.certData); certStream.close(); } catch (IOException e) { e.printStackTrace(); } } //我的appid @Override String getAppID() { return "wx0fb1335345abd2544"; } //我的商户号 @Override String getMchID() { return "15364354501"; } //我的商户api安全key,在商户平台api安全目录下可设置 @Override String getKey() { return "xKLPpyJOORlkVm35435545tx1PuxqqIx"; } @Override InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } @Override public int getHttpConnectTimeoutMs() { return 8000; } @Override public int getHttpReadTimeoutMs() { return 10000; } @Override IWXPayDomain getWXPayDomain() { return new IWXPayDomain() { @Override public void report(String domain, long elapsedTimeMillis, Exception ex) { } @Override public DomainInfo getDomain(WXPayConfig config) { return new DomainInfo("api.mch.weixin.qq.com", false); } }; } }
做完上面的一系列工作后,就可以在Controller中上手开发我们的统一微信支付接口啦,当然更优的做法是将方法都封装起来,或者提取到service层,但是我这里为了直观,就直接将逻辑全部写到controller层了;
/** ** 微信支付接口 * * @param order 实际业务订单详情 * @param request * @return */ @RequestMapping(value = "/wxPay", method = RequestMethod.POST) @ResponseBody public JsonResult wxPay(@RequestBody Vorder order, HttpServletRequest request) { // 从request中解析出当前用户的个人信息 Claims claims = jwtUtils.getUserClaim(request); String uid = claims.get("uid").toString(); String openid = claims.get("openid").toString(); // 判断是否重复购买 List<Integer> list = vorderService.myOrderVidList(Integer.parseInt(uid)); System.out.println(list); if (list.contains(order.getVid())) { return JsonResult.error("当前课程你已经购买过,无需重复购买"); } // 没有购买过的情况下,则记得在数据库中插入订单信息,为了后面的模板消息推送中能获取prepay_id,我需要等到第一次签名结束后,得到prepay_id后,才向数据库新增这条订单数据 Map resultMap = new HashMap(); // 这个resultMap是用来封装最终返回给小程序的数据的 // 开始进入构建微信支付环境 MyConfig config = null; WXPay wxpay = null; try { config = new MyConfig(); wxpay = new WXPay(config); } catch (Exception e) { e.printStackTrace(); } // 使用官方sdk工具生成商户订单号 String out_trade_no = WXPayUtil.generateNonceStr(); // 生成的随机字符串 String nonce_str = WXPayUtil.generateNonceStr(); // 获取客户端的ip地址 // 获取本机的ip地址 InetAddress addr = null; try { addr = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } String spbill_create_ip = addr.getHostAddress(); // 支付金额,需要转成字符串类型,否则后面的签名会失败 int total_fee = (int) (order.getOprice() * 100); System.out.println("付款金额为=" + total_fee); // 商品描述 String body = order.getDescription(); // 统一下单接口参数 HashMap<String, String> data = new HashMap<String, String>(); data.put("appid", "wx0fb1142123bd2544"); data.put("mch_id", "15364312101"); data.put("nonce_str", nonce_str); data.put("body", body); data.put("out_trade_no", out_trade_no); data.put("total_fee", String.valueOf(total_fee)); data.put("spbill_create_ip", spbill_create_ip); data.put("notify_url", "https://www.testtuan.com/wxvideo/api/order/notify"); data.put("trade_type", "JSAPI"); data.put("openid", openid); try { Map<String, String> rMap = wxpay.unifiedOrder(data); //这里是在调用统一下单API,已经被官方的SDK封装好了; System.out.println("统一下单接口返回: " + rMap); String return_code = (String) rMap.get("return_code"); String result_code = (String) rMap.get("result_code"); String nonceStr = WXPayUtil.generateNonceStr(); resultMap.put("nonceStr", nonceStr); Long timeStamp = System.currentTimeMillis() / 1000; if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) { String prepayid = rMap.get("prepay_id"); // 在第一次签名之后,再进行预付订单的创建,这样可以同时将prepay_id一并存入数据库 order.setOutTradeNo(out_trade_no); order.setUid(Integer.parseInt(uid)); order.setOpenid(openid); order.setStatus("0"); order.setOtime(new Date()); order.setOtype("wxPay"); order.setPrepayId(prepayid); int count = vorderService.save(order); if (count != 1) { return JsonResult.error("新增订单失败"); } System.out.println("新增订单成功"); // 开始进行第二次签名 resultMap.put("package", "prepay_id=" + prepayid); resultMap.put("signType", "HMAC-SHA256"); // 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 resultMap.put("timeStamp", timeStamp + ""); // 再次签名,这个签名用于小程序端调用wx.requesetPayment方法 resultMap.put("appId", "wx0fb1dssfsf2544"); String sign = WXPayUtil.generateSignature(resultMap, "xKLPpyJsdffjovptx1PuxqqIx"); resultMap.put("paySign", sign); System.out.println("生成的签名paySign : " + sign); System.out.println("返回结果为" + resultMap); return JsonResult.successs(resultMap); } else { return JsonResult.error("失败了"); } } catch (Exception e) { e.printStackTrace(); return JsonResult.error("出错了"); } }
到这一步,如果第一次签名(统一下单API)和第二次签名都成功的话,小程序前端就可以收到我们返回的5和参数啦,然后调用wx.requestPayment()接口就可以发起支付啦:
示例代码:
wx.requestPayment( { 'timeStamp': '', 'nonceStr': '', 'package': '', 'signType': 'MD5', 'paySign': '', 'success':function(res){}, 'fail':function(res){}, 'complete':function(res){} })
然后用户界面会弹出支付确认,用户确认后,则支付流程就成功啦;这里可参考:小程序调起支付API
注意
1、上面所有的常量,我都是直接写在它应该出现的地方的,实际开发中,注意使用常量类统一管理,这些值都是我复制上来后,随手敲的假值,所以上下文有可能对应不上;
2、支付后微信会异步通知我们支付结果,通知的地址就是我们在统一下单时候,put进去的参数notify_url,这个url必须为外网可访问的url,不难理解;
3、我在支付这里被卡了很久,无论后台代码检查还是前台代码检查都没有问题,用户确认支付后一直显示,“支付签名验证失败”,网上百度很好几天,各种别人遇到的问题都排查了,比如“appid大小写问题”,“二次签名顺序问题”等等,最后不报希望地更换了一下加密算法(官方推荐,网上教程,默认全都是MD5算法),其实还有另外一种加密算法,即“HMAC-SHA256”加密算法,最后竟然生效了,支付过程顺利完成,所以如果有人跟我遇到相同的问题,在百度上面的方法都无效的时候,可以尝试我的方法,将加密算法修改为“HMAC-SHA256”,记住,除了自己的代码里,SDK中也需要修改,小程序前端发起支付时候的signType也需要跟着修改;
4、另外提醒一下:虽然小程序官方要求授权的域名必须是带https证书的,但是在开发过程中,忽略了https域名选项是完全可以支持开发和完成支付的,刚开始我遇到前面失败时候,各种方法尝试无果,一度怀疑是不是https域名没有落实的原因,实际正常,并不是,如果你也遇到了此问题,不要怀疑。
有了上面的代码,小程序微信支付环境就完成啦;由于篇幅原因,至于“异步回调通知”我就留着下篇博文讲述了,如果篇幅不长,我会将异步回调通知和“模板消息”在一篇博文里面讲述。
此致敬礼
1 Comment
跟着楼主的教程顺利搞定了,非常感激,想让楼主请我吃饭