项目开发过程中,涉及到了微信支付功能,这里做一个详细的记录。

小程序和H5的后端代码是通用的,前端调用不同的代码实现,这里不是重点,会简单的给出相关的代码。

微信支付,官方给了开发文档,但是其中还是有一部分需要自己去摸索一下,刚开始接触走一点弯路踩一点坑也比较正常。

当然微信支付这边涉及到商户平台,需要进行验证,使用的是公司的账号,个人小程序没有支付的权限。

1.开发之前的认知

先了解一下铺垫的知识,会让开发的思路更加的清楚,当时觉得这些没啥用,现在回想回想这些流程确实会更加的明确开发的流程,这张时序图就很清楚的描述了流程。这里是小程序的支付模式。

这里知道了大概流程就可以了,是在没办法开发时比对自己的代码逻辑,会发现跟这个时序图还是完全吻合的。

需要:appid,商户mchid,商户秘钥key

支付的开发步骤:

这里官方文档罗列了JSAPI与JSSDK之间的整体流程和一些小区别。H5进行支付时需要在后台设置授权域名,当然也都需要设置微信支付通知的回调接口url,微信会向接口发送支付结果的通知,会按照一定的时间间隔进行通知,接收到支付结果的服务器返回微信成功或失败结果,微信接收到成功结果便不会再进行消息的通知。

 2.代码逻辑

了解了基本的流程之后,附上代码为敬。

(1)前端简单的流程就是点击支付按钮

(2)将支付金额等数据传到后台,后台进行处理,并生成预支付订单

//Controller方法 返回给小程序端需要的参数Map集合
Map<String, Object> resultMap = new HashMap<String, Object>();
public class IpUtil {/** * IpUtils工具类方法 * 获取真实的ip地址 * @param request * @return */  public static String getIpAddr(HttpServletRequest request) {  String ip = request.getHeader("X-Forwarded-For");  if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  //多次反向代理后会有多个ip值,第一个ip才是真实ip  int index = ip.indexOf(",");  if(index != -1){  return ip.substring(0,index);  }else{  return ip;  }  }  ip = request.getHeader("X-Real-IP");  if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  return ip;  }  return request.getRemoteAddr();  }
}
public static Map<String, Object> wxPay(String ip,UserInfo user,int inviteAnswerId) throws Exception{  InviteAnswer answer = seekHelpAnswerService.getInviteAnswer(inviteAnswerId);Question question = seekHelpQuestionService.getQuestion(answer.questionId);//直接调固定的方法Account myAccount = seekHelpPayService.getUserAccount(user.id);BigDecimal qMoney = new BigDecimal(question.money.toString());BigDecimal totalMoney = new BigDecimal("0");if(null != answer.helperId && null != question.extraAward && question.extraAward != QuestionExtraAwardType.暂无.code){totalMoney = SeniorBigDecimalUtil.add(qMoney, question.extraAwardMoney);}else{totalMoney = qMoney;}BigDecimal needPayMoney = SeniorBigDecimalUtil.sub(myAccount.money, totalMoney).abs();BigDecimal needPayMoneyFormatCent = SeniorBigDecimalUtil.mul(needPayMoney, new BigDecimal("100"));//将需要支付的金额转换为以分为单位的金额Integer totalFee = needPayMoneyFormatCent.intValue();//Integer totalFee = 1;String nonce_str = WxPayOperate.getRandomStringByLength(32);  //生成的随机字符串        String body = "求助支付";   //商品名称            String out_trade_no = DateTimeOper.getStrForDate(new Date()) + inviteAnswerId;//充值订单号Map<String, String> packageParams = new HashMap<String, String>();  //组装参数,用户生成统一下单接口的签名  packageParams.put("appid", WxPayConfig.appid);  packageParams.put("mch_id", WxPayConfig.mch_id);  packageParams.put("nonce_str", nonce_str);  packageParams.put("body", body);  packageParams.put("out_trade_no", out_trade_no);//商户订单号  packageParams.put("total_fee", String.valueOf(totalFee));//支付金额,这边需要转成字符串类型,否则后面的签名会失败  packageParams.put("ip", ip);  packageParams.put("notify_url", WxPayConfig.notify_url);//支付成功后的回调地址  packageParams.put("trade_type", WxPayConfig.TRADETYPE);//支付方式  packageParams.put("openid", user.openId);String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串   String mysign = PayUtil.sign(prestr, WxPayConfig.key, "utf-8").toUpperCase();//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口  String xml = "<xml>" + "<appid>" + WxPayConfig.appid + "</appid>"     //拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去  + "<body><![CDATA[" + body + "]]></body>"   + "<mch_id>" + WxPayConfig.mch_id + "</mch_id>"   + "<nonce_str>" + nonce_str + "</nonce_str>"   + "<notify_url>" + WxPayConfig.notify_url + "</notify_url>"   + "<openid>" + user.openId + "</openid>"   + "<out_trade_no>" + out_trade_no + "</out_trade_no>"   + "<ip>" + ip+ "</ip>"   + "<total_fee>" + String.valueOf(totalFee) + "</total_fee>"  + "<trade_type>" + WxPayConfig.TRADETYPE + "</trade_type>"   + "<sign>" + mysign + "</sign>"  + "</xml>";String result = PayUtil.httpRequest(WxPayConfig.pay_url, "POST", xml);//调用统一下单接口,并接受返回的结果        Map<?, ?> map = PayUtil.doXMLParse(result);// 将解析结果存储在HashMap中   String return_code = (String) map.get("return_code");//返回状态码Map<String, Object> resultMap = new HashMap<String, Object>();//返回给小程序端需要的参数 if(return_code.equals("SUCCESS")){     String result_code = (String)map.get("result_code");       if(result_code.equals("SUCCESS")){String prepay_id = (String) map.get("prepay_id");//返回的预付单信息                   resultMap.put("nonceStr", nonce_str);                           resultMap.put("package", "prepay_id=" + prepay_id);  Long timeStamp = System.currentTimeMillis() / 1000;     resultMap.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误  String stringSignTemp = "appId=" + WxPayConfig.appid + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id+ "&signType=MD5&timeStamp=" + timeStamp;  //拼接签名需要的参数     String paySign = PayUtil.sign(stringSignTemp, WxPayConfig.key, "utf-8").toUpperCase();   //再次签名,这个签名用于小程序端调用wx.requesetPayment方法  resultMap.put("paySign", paySign);             }else{return_code = "FAIL";//result_code决定最终请求结果,result_code为FAIL,则表示请求支付失败,return_code也置为FAILString err_code = (String)map.get("err_code");String err_code_des = (String) map.get("err_code_des");order.err_code = err_code;order.err_code_des = err_code_des;}}  resultMap.put("appid", WxPayConfig.appid); resultMap.put("flag", return_code);resultMap.put("tradeNo", out_trade_no);return resultMap;}

不好的习惯,代码的注释写的很少。大致的流程就是就官方要求的参加添加到Map中,然后将元素转换成“参数=参数值”的模式用“&”字符拼接成字符串,使用PayUtil.sign方法与商户秘钥key进行参数第一次签名,后面统一下单接口调用需要使用。

拼接统一下单接口使用的xml数据,post请求统一下单接口,https://api.mch.weixin.qq.com/pay/unifiedorder,获取统一下单接口返回的xml格式数据,解析返回的xml数据,保存在map中,根据返回的状态码判断下单是否成功。如果成功,将成功的信息返回给小程序端准备调用微信app的支付功能。

这部分多看看官方的文档,会有比较详细的流程,下单请求需要的参数,以及返回的数据字段等等。

  • 元素转换成“参数=参数值”的模式用“&”字符拼接成字符串
/**   * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串   * @param params 需要排序并参与字符拼接的参数组   * @return 拼接后字符串   */     public static String createLinkString(Map<String, String> params) {     List<String> keys = new ArrayList<String>(params.keySet());     Collections.sort(keys);     String prestr = "";     for (int i = 0; i < keys.size(); i++) {     String key = keys.get(i);     String value = params.get(key);if(value != null && (!key.equals("sign"))){if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符     prestr = prestr + key + "=" + value;     } else {     prestr = prestr + key + "=" + value + "&";     } }}     return prestr;     } 
  • MD5运算签名
    /**   * 签名字符串   * @param text需要签名的字符串   * @param key 密钥   * @param input_charset编码格式   * @return 签名结果   * @throws Exception */     public static String sign(String text, String key, String input_charset) throws Exception {     text = text + "&key=" + key;        return MD5(text).toUpperCase();}
    /*** 生成 MD5** @param data 待处理数据* @return MD5结果*/public static String MD5(String data) throws Exception {java.security.MessageDigest md = MessageDigest.getInstance("MD5");byte[] array = md.digest(data.getBytes("UTF-8"));StringBuilder sb = new StringBuilder();for (byte item : array) {sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));}return sb.toString().toUpperCase();}
  • 拼接xml格式数据
  • post请求统一下单地址

使用了最基本的HttpURLConnection方式,这里可以自己更好的实现,目的是获取返回的结果

    /**   *   * @param requestUrl请求地址   * @param requestMethod请求方法   * @param outputStr参数   */     public static String httpRequest(String requestUrl,String requestMethod,String outputStr){        StringBuffer buffer = null;     try{     URL url = new URL(requestUrl);     HttpURLConnection conn = (HttpURLConnection) url.openConnection();     conn.setRequestMethod(requestMethod);     conn.setDoOutput(true);     conn.setDoInput(true);     conn.connect();     //往服务器端写内容     if(null !=outputStr){     OutputStream os=conn.getOutputStream();     os.write(outputStr.getBytes("utf-8"));     os.close();     }     // 读取服务器端返回的内容     InputStream is = conn.getInputStream();     InputStreamReader isr = new InputStreamReader(is, "utf-8");     BufferedReader br = new BufferedReader(isr);     buffer = new StringBuffer();     String line = null;     while ((line = br.readLine()) != null) {     buffer.append(line);     }     }catch(Exception e){     e.printStackTrace();     }  return buffer.toString();  
  • 解析xml数据
    /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */  public static Map<String, String> doXMLParse(String strxml) throws Exception {  if(null == strxml || "".equals(strxml)) {  return null;  }  Map<String, String> m = new HashMap<String, String>();  InputStream in = String2Inputstream(strxml);  SAXBuilder builder = new SAXBuilder();  Document doc = builder.build(in);  Element root = doc.getRootElement();  List<?> list = root.getChildren();  Iterator<?> it = list.iterator();  while(it.hasNext()) {  Element e = (Element) it.next();  String k = e.getName();  String v = "";  List<?> children = e.getChildren();  if(children.isEmpty()) {  v = e.getTextNormalize();  } else {  v = getChildrenText(children);  }  m.put(k, v);  }  //关闭流  in.close();  return m;  }  /** * 获取子结点的xml * @param children * @return String */  public static String getChildrenText(List<?> children) {  StringBuffer sb = new StringBuffer();  if(!children.isEmpty()) {  Iterator<?> it = children.iterator();  while(it.hasNext()) {  Element e = (Element) it.next();  String name = e.getName();  String value = e.getTextNormalize();  List<?> list = e.getChildren();  sb.append("<" + name + ">");  if(!list.isEmpty()) {  sb.append(getChildrenText(list));  }  sb.append(value);  sb.append("</" + name + ">");  }  }  return sb.toString();  }  public static InputStream String2Inputstream(String str) throws UnsupportedEncodingException {  return new ByteArrayInputStream(str.getBytes("UTF-8"));  }
  • 重要的地方,二次签名
//拼接签名需要的参数
String stringSignTemp = "appId=" + WxPayConfig.appid + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id+ "&signType=MD5&timeStamp=" + timeStamp;       //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
String paySign = PayUtil.sign(stringSignTemp, WxPayConfig.key, "utf-8").toUpperCase();     

3.小程序端获取预支付订单数据,调用支付组件

/*** 充值并支付成功回调*/
function rechargeAndPaySuc(data) {if (data.flag === "SUCCESS") {var timeStamp = data.timeStamp;var nonceStr = data.nonceStr;var pack = data.package;var paySign = data.paySign;tradeNo = data.tradeNo;wx.requestPayment({timeStamp: timeStamp,nonceStr: nonceStr,package: pack,signType: 'MD5',paySign: paySign,success: function (re) {//支付成功 },fail: function (re) {//  cancalPay();},complete: function (re) {if (re.errMsg == "requestPayment:fail cancel") {cancalPay();} else if (re.errMsg == "requestPayment:ok") {//进行一些页面逻辑的处理} else {        cancalPay();}}})} else {//调用微信支付接口生成预付订单失败cancalPay();}
}

这里选择在complete回调函数进行页面的处理,但是真正的业务逻辑确认需要在微信通知结果获取后进行处理,这里的返回结果并不一定准确。

4.微信通知支付结果,根据设置的结果回调url进行处理

    /*** @Title: wxPayNotify* @Description: 支付回调通知  微信异步通知* 订单待确认状态* * 如果订单确认,则进行账户金额的划转* * 如果订单没有支付成功,则返回给失败状态给客户端* * @param @param request* @param @param response* @param @throws Exception    * @return String    * @throws*/ @RequestMapping("/wxPayNotify")@ResponseBodypublic String wxPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{InputStream inputStream = null;inputStream = request.getInputStream();StringBuffer sb = new StringBuffer();BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));String line = null;while((line = in.readLine()) != null){sb.append(line);}in.close();inputStream.close();String result = seekHelpPayService.processWxPayNotifyInfoToDB(sb.toString());String xml = null;if(result.equals("success")){xml = "<xml>" + "<return_code><![CDATA[" + "SUCCESS" + "]]></return_code>"   + "<return_msg><![CDATA[" + "OK" + "]]></return_msg>"  + "</xml>";}return xml;}

这里就是对获取的微信支付通知结果进行的处理。

整个单机系统的微信支付流程就完成了,首先最重要的还是要看微信的官方文档,明白开发的流程,理解了流程,剩下就是业务逻辑的处理了,对于开发人员并没有太大的难度。

H5页面与小程序端对比,前端调用有所不同,这里的H5支付是在微信环境中的H5

H5页面js:

        $.ajax({url:urlHeader + "/xxx/xxxx",type:"post",dataType:"json",data:{xxxxxx      },success:function(data){console.log("预支付订单请求成功:");if(data.flag == "SUCCESS"){appId = data.appid;timeStamp = data.timeStamp;nonceStr = data.nonceStr;packageStr = data.packageStr;signType = data.signType;paySign = data.paySign;out_trade_no = data.tradeNo;}        callPay();},error:function(data){console.log(data);alert("支付失败");}})

下面的两个js方法是官方提供的,大致意思是调用微信内置的js

function onBridgeReady(){console.log("申请支付页面");    WeixinJSBridge.invoke('getBrandWCPayRequest', {"appId":appId,"timeStamp":timeStamp,"nonceStr":nonceStr,"package":packageStr,"signType":signType,"paySign":paySign},function(res){console.log("支付请求返回结果");console.log(res);if(res.err_msg == "get_brand_wcpay_request:ok" ){// 使用以上方式判断前端返回,微信团队郑重提示://res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。//执行成功后的页面逻辑}else if(res.err_msg == "get_brand_wcpay_request:fail"){alert("支付失败");} });
}function callPay(){if (typeof WeixinJSBridge == "undefined"){if( document.addEventListener ){document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);}else if (document.attachEvent){document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);}}else{onBridgeReady();}
}

基本的流程就是这样了,代码写的比较差,持续努力中。

如有问题,欢迎添加我的微信:llbbaa

一起交流,共同学习。。。

微信支付总结--小程序与H5页面微信支付相关推荐

  1. uni-app | 小程序嵌入H5页面实现支付功能

    文章目录

  2. 微信小程序嵌套h5页面怎么实现小程序支付

    微信小程序嵌套h5页面怎么实现小程序支付 小程序嵌套h5页面怎么实现小程序支付小程序中嵌套h5页面,但是不能再h5页面拉起小程序支付,这时是需要小程序方拉起支付 目前的流程,外链发送订单请求拿到预支付 ...

  3. 微信小程序内含H5页面实现方式

    微信小程序内含H5页面实现方式 一.背景 众所周知,我们写完小程序,发到线上,是需要经历设置版本.提交代码审核.等待审核等步骤,放一个版本下来可能花费很多的时间,甚至很可能等待了很长时间,审核未通过, ...

  4. 微信小程序嵌入 H5 页面(web-view)

    在开发微信小程序的时候,我们有时候会遇到将 H5 页面嵌入到小程序页面中的情况:微信小程序自带的 web-view 组件相当于 HTML 页面中的 iframe ,方便我们在微信小程序中打开一个 H5 ...

  5. 2021最新外卖霸王餐小程序、H5、微信公众号版外系统源码|霸王餐美团/饿了么系统 粉丝裂变玩源码下载

    2021年了,你还在用淘宝客吗?赶紧跟上互联网的大势吧,外卖cps就是cps人群趋势! 个人.个体.企业均可使用 外卖霸王餐小程序.H5.微信公众号版外系统源码|霸王餐美团/饿了么系统 粉丝裂变玩 2 ...

  6. uni-app中,小程序或h5页面背景音乐的播放与暂停

    在uni-app中写小程序或h5页面时,用到背景音乐,以及图标的旋转动画 一.创建music.js 放在static或新建文件夹 const bgm = uni.createInnerAudioCon ...

  7. 微信点餐小程序怎么做?微信小程序点餐系统制作

    顾客不用排队等待,打开微信扫一扫二维码,就能通过微信点餐支付,对于大部分餐厅来说,都已经成为常见的现象,小程序彻底融入了我们的生活,小程序也已经成为餐饮行业必备的工具.通过餐饮小程序,不仅可以让用户获 ...

  8. 小程序实现h5页面的微信支付

    项目需求: 由于小程序的热度高涨,公司为了引流,又想降低研发成本,所以将原先的H5项目嵌套在现在的小程序中. 之前的h5项目虽然实现了微信支付,但是嵌套之后小程序无法直接使用之前的h5支付功能.为了达 ...

  9. 微信小程序webview(H5页面)调用微信小程序支付

    1.业务描述:微信小程序商城入口进入的页面是商城H5页面,在H5页面进行微信支付如何实现: 2.微信小程序(webview访问H5页面)必须使用微信小程序支付: 如何实现以及实现方式以及支付后页面返回 ...

最新文章

  1. 创建文档库时指定文件夹(路径)
  2. MIPS(loongson)linux 中添加系统调用
  3. 报错:ModuleNotFoundError: No module named ‘cv_bridge‘,以及在ROS是如何安装cv_bridge库包
  4. mysql场景测试_【干货】不同场景下 如何进行MySQL迁移
  5. 配置syslog发送_nginx简单配置优化概述
  6. flask返回json数据到前端_小白学Flask第六天| abort函数、自定义错误方法、视图函数的返回值...
  7. java ftp 连接超时时间_ftpClient的连接超时设置(setConnectTimeout,setSoTimeout) | 学步园...
  8. 作者:鄂世嘉,男,同济大学博士生,CCF学生会员。
  9. maven编译:target/surefire-reports for the individual test results
  10. 重学前端学习笔记(十三)--浏览器工作解析(三)
  11. const和readonly区别
  12. 如果Linux从未出现,我们的生活会变成怎样?
  13. .NET简谈观察者模式
  14. 第三届Apache Flink 极客挑战赛暨AAIG CUP电商推荐“抱大腿”攻击识别
  15. 浩辰3D设计软件中如何进行弹簧设计?
  16. appcan ajax mysql_APPCAN MAS接口之AJAX
  17. ABC-Endless Walk-(缩点+拓扑dp)
  18. 《极限竞速地平线4》Teredo不合格解决
  19. yolov4中的mosaic数据增强
  20. Linux使用tar命令进行磁带备份

热门文章

  1. JS原型与原型链(面试题)
  2. [自助建站系列]1.如何制作自己的网站-总体建设篇
  3. 在拼多多上班,是一种什么样的体验?
  4. 英特尔oneAPI———神奇的oneAPI
  5. November 1t h Thursday
  6. JS-学生信息表录入删除功能(本地存储)
  7. 8个免费高质量视频、音频素材网
  8. “跳一跳”小游戏这么火爆,它是如何做的设计和开发?
  9. 为什么正数的反码与原码相同
  10. 《通信网》专题一:通信网的概念、构成与业务分类