项目要求需要在小程序中添加微信支付的功能(微信小程序就不要想支付宝的事情了),折腾了半天,也在网上看了各位先行者的各种文章(强烈谴责一下摘取别人文章一半,当作自己文章的人,越看越头疼),记录一下这次的实验
这个需要公司的一些材料(营业执照,法人,银行卡之类的),这一步是必须的,你要做微信支付,最起码得开通别人的服务才行
点击产品中心-查看我的产品 这里能看到所有产品,找到你需要的就可以了,我这边只是做小程序的支付,默认开通的就够用
要在微信小程序中使用微信商户的支付功能,就必须把小程序和某个商户关联起来,进入小程序管理——功能——微信支付 (进去后如果没有开通微信支付就要申请,不过我的默认开启的,网上有的文章说是要自己申请开通),点击关联更多商户号 注意,这里会跳回到你的微信商户的网站去,跳回的目录是产品中心-AppId账户管理 (我这里是已经关联了),点击关联AppId 在这个页面输入小程序的AppId(在小程序——开发——开发设置中找)号,输入正确的话,会要求你输入小程序的注册认证主体(小程序注册时候企业主体的名字)
这里点击确认之后,在小程序那边确认绑定,这样就完成了关联,就能做下一步的开发了
微信为了方便开发者开发,提供了自己的SDK(就是封装了一些使用工具,不用sdk也可以,但是你就得手工去写一些方法),但是这个SDK没有上传到maven中央仓库去(但是在maven中央仓库能搜索到微信支付的sdk,一个是2017年的,还有个最新的,想一想算了,用微信自己的sdk比较保险,毕竟是钱相关的)点击位置支付SDK 选择需要的版本下载,我这边选择的是java版本。下载完成后,你会得到一个zip包(WxPayAPI_JAVA.zip),点击解压缩,你会得到它的一个工程 其中reademe.md中包含了里边常用方法的demo(不过写的不清不楚,真的!~该你被飞车三人组骗),进去看看
这里是提供的所有的微信支付相关的类了。 我们的工程一般都是maven工程,所以我们需要对这个进行转换一下 1:打开编译器,将下载的这个工程转换成jar包 2:用maven命令将得到的jar包安装到maven本地仓库去
mvn install:install-file -Dfile=wxpay.jar -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dversion=3.0.9 -Dpackaging=jar
注意上边-Dfile后边jar包所在的文件的路径 如果dos不再当前文件的当前目录,需要把文件路径补全了 在当前工程中使用mvn引入wx的jar包
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>3.0.9</version> </dependency>这样你的工程中就能正常使用微信支付的sdk的功能了
01:微信支付要求必须在https的请求中进行,所以第一步在服务器请求地址中去设置一下https证书(这个自己百度一下),http请求是不能访问的 02:需要在微信商户 账户中心-api安全去设置两个东西 分别为:设置API密钥和 api证书,其中密钥记录下来(程序中用),证书也下载下来(程序中需要读取) 下载下来的证书如下 解压缩包,得到三个文件,最重要的是一本apiclient_cert.p12的证书(别的两本我没用到。。。。),把这个文件丢到spring boot工程的resources 目录下边去 这样我们的证书问题就解决了
要开始使用了,首先我们要为接入做配置,第一个需要配置的类为WXPayConfig 这个类是支付SDK提供的,但是我们需要在其中写入我们自己的参数 创建类: public class MyConfig extends WXPayConfig
这里让我头疼的是,有的文章写的是 implements WXPayConfig这是比较老的使用了,微信这里WXPayConfig这个改成了抽象类,所以要继承。
这里还有个小细节,其实sdk使用有两种办法,一种是把下载sdk的类全部拷贝到你的工程中,第二种就是我刚才jar包的方式。
如果是jar的方式,自己写的这个配置类一定要放在和WXPayConfig这个类一样的路径下边(如果路径不一致,就会报错),所以我只能在自己的程序中创建一个一模一样的路径存放这个配置类,最终是这样的 com下创建了两个包,一个是以git开头的,另外一个是自己的工程的 全类如下:
package com.github.wxpay.sdk; import com.github.wxpay.sdk.IWXPayDomain; import com.github.wxpay.sdk.WXPayConfig; import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.InputStream; @Component("myConfig") public class MyConfig extends WXPayConfig { private byte[] certData; public MyConfig() throws Exception { InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("apiclient_cert.p12");//读取方才存放在resource西边的证书 this.certData = IOUtils.toByteArray(certStream); certStream.close(); } @Override public String getAppID() { return "你自己的小程序Id号"; } public String getMchID() { return "你自己的商户ID号"; } public String getKey() { return "刚才在商户证书位置设置的密钥"; } public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } public int getHttpConnectTimeoutMs() { return 8000; } public int getHttpReadTimeoutMs() { return 10000; } @Override IWXPayDomain getWXPayDomain() { //必须实例化,否则WxPay初始化失败 IWXPayDomain iwxPayDomain = new IWXPayDomain() { public void report(String domain, long elapsedTimeMillis, Exception ex) { } public DomainInfo getDomain(WXPayConfig config) { return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true); } }; return iwxPayDomain; } }修改这几个参数之后就能使用了
现在基础配置已经完成了,我们开始做开发(距离胜利就一步了) 首先打开小程序支付API 文档,点击API列表,查看统一下单 这里的流程简单的来说就是 商户在小程序中先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易后调起支付。 1:在后台生成一个订单信息(这个是下给微信支付服务器的) 2:将生成订单信息的返回给前台(调用之后返回来信息,把返回的信息发送给前端) 3:前台发起支付(前端获取信息后发起支付) 首先发送订单准备代码如下:
借鉴了这位老哥的代码,十分感谢参考代码地址 最开始修改sdk中的一个东西,要不然会说你的签名错误 直接复制sdk中WXPay.java这个类到你之前新建的包路径中(还是要和sdk保持包路径统一)。找到这个方法
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception { this.config = config; this.notifyUrl = notifyUrl; this.autoReport = autoReport; this.useSandbox = useSandbox; if (useSandbox) { this.signType = SignType.MD5; // 沙箱环境 } else { //this.signType = SignType.HMACSHA256; //位置1 this.signType = SignType.MD5; } this.wxPayRequest = new WXPayRequest(config); }位置1——原来这一行没注释,注释掉这一行代码,拷贝它下边的一行代码,因为我们的加密方式使用的是md5模式,如果是默认的这一行,加密方式不对签名就错误了
首先编写方法获取openid
//获取openId public JSONObject getOpenIdAndsessionKey(String code) { String url = "https://api.weixin.qq.com/sns/jscode2session?appid=你的小程序ID&secret=小程序密钥&js_code=" + code + "&grant_type=authorization_code"; RestTemplate restTemplate = new RestTemplate(); String result = restTemplate.getForObject(url, String.class); JSONObject jsonObject = JSONObject.parseObject(result); // String openId = jsonObject.getString("openid"); return jsonObject; }尤其注意上边的url,这种是亲测可用的,别的两种在这里没用
下单代码
@RequestMapping(value = "/createOrder") BaseResponse<Map> createOrder(@RequestParam("code") String code) throws Exception { //位置1 String appId = "你自己的小程序id"; BaseResponse<Map> response = new BaseResponse<>(); JSONObject jsonObject = getOpenIdAndsessionKey(code); String openid = jsonObject.getString("openid"); Map resultMap = new HashMap(); MyConfig conf = new MyConfig(); WXPay wxPay = new WXPay(conf); Map<String, String> data = new HashMap<String, String>(); data.put("appid", appId);//小程序号 data.put("mch_id", "你自己的商户号");//商户号 data.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串 data.put("body", "会员充值");//商品描述 //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号 String outTradeNo= getRandomStringByLength(16); data.put("out_trade_no",outTradeNo);//商户订单号 位置2 System.out.println("商户订单号------------------------------------------"+outTradeNo); data.put("fee_type", "CNY");//标价币种 data.put("total_fee", "1");//金额 位置3 data.put("spbill_create_ip", "服务器ip地址就可以");//支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP data.put("notify_url", "https://www.你的服务器.cn/wx/notifyWeiXinPay");//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 data.put("trade_type", "JSAPI"); // 此处指定为扫码支付 位置4 data.put("product_id", "1");//此参数为二维码中包含的商品ID,商户自行定义。 data.put("openid", openid); try { Map<String, String> rMap = wxPay.unifiedOrder(data); //位置5 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"); resultMap.put("package", "prepay_id=" + prepayid); resultMap.put("signType", "MD5"); //这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 resultMap.put("timeStamp", timeStamp + ""); //再次签名,这个签名用于小程序端调用wx.requesetPayment方法 resultMap.put("appId", appId); String sign = WXPayUtil.generateSignature(resultMap, "xxxxqqwqwqwqwqxqxqxqxq"); resultMap.put("paySign", sign); System.out.println("生成的签名paySign : " + sign); response.setData(resultMap); return response; //位置6 } else { return response; } } catch (Exception e) { e.printStackTrace(); return response; } }位置1——code值是前端授权得到的值,需要前端人员动态传递到后台 位置2——订单商户号,保存在我们自己的服务器,用来区分哪个订单是哪个 位置3——这里的金额必须是整数,单位是分 位置4——小程序用就写这种 位置5——调用sdk方法,完成下单 位置6——将下单成功后的信息返回给前端用于支付 上边代码是个demo什么金额啊都应该是前端参数,你自己补全就好了 这样前端就能完成支付了,最后,当支付成功后后端需要知道支付成功信息,最后编写支付成功API
@RequestMapping(value = "/notifyWeiXinPay") @ResponseBody String notifyWeiXinPay(HttpServletRequest res, HttpServletResponse rsp) throws Exception { System.out.println("微信支付回调"); InputStream inStream = res.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } String resultxml = new String(outSteam.toByteArray(), "utf-8"); Map<String, String> params = WXPayUtil.xmlToMap(resultxml); outSteam.close(); inStream.close(); //支付结果返回,最后需要返回xml格式的数据 Map<String, String> return_data = new HashMap<String, String>(); if (!"SUCCESS".equals(params.get("return_code"))) { //没有支付成功 return_data.put("return_code", "FAIL"); return_data.put("return_msg", "return_code不正确"); return WXPayUtil.mapToXml(return_data); } else { //返回成功了 //商户订单号 String outTradeNo = params.get("out_trade_no"); //订单金额 String totalFee = params.get("total_fee"); //支付完成时间 String timeEnd =params.get("time_end"); //微信支付订单号 String tradeNo = params.get("transaction_id"); System.out.println("商户订单号"+outTradeNo+"tradeNo"+tradeNo); return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WXPayUtil.mapToXml(return_data); } }这样当支付成功后,微信商户服务器会主动推送到你这个地址(这个地址是前边下单地址写的notify_url),我们接受信息,就能知道支付成功了(里边outTradeNo商户订单号,就是我们在下单时候随机生成的商户订单号) 这样整个过程就完成了