微信小程序调用微信支付接口的实现方法


Posted in Javascript onApril 29, 2019

前言:应项目要求,需要使用微信小程序做支付,写完后告知手续费太高方案不予通过(宝宝心里苦,但宝宝不说)。此次开发在因站在巨人的肩膀上顺利完成。

微信支付文档传送门:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3

1.开发工具:

Eclipse+Tomcat+微信web开发工具

2.开发环境:

java+maven

3.开发前准备:

3.1 所需材料

小程序的appid,APPsecret,支付商户号(mch_id),商户密钥(key),付款用户的openid。

申请接入微信商户地址:https://pay.weixin.qq.com/static/applyment_guide/applyment_detail_miniapp.shtml

3.2 开发模式

本次开发采用的开发模式是:普通模式,适用于有自己开发团队或外包开发商的直连商户收款。开发者申请自己的appid和mch_id,两者需具备绑定关系,以此来使用微信支付提供的开放接口,对用户提供服务。

微信小程序调用微信支付接口的实现方法

4 开发

wx.request({
 
  url: address + 'wxPay',
 
  data: {
 
    openId: openId
  
    // amount: amount,
 
    // openId: openId
 
  },
 
  header: {
 
    'content-type': 'application/x-www-form-urlencoded' // 默认值
  
  },
 
  method: "POST",
 
  success: function (res) {
 
    console.log(res);
 
    that.doWxPay(res.data);
  },
 
  fail: function (err) {
 
    wx.showToast({
 
      icon: "none",
 
      title: '服务器异常,清稍候再试'
 
    })
 
  },
 
});
 
 
 
doWxPay(param) {
 
//小程序发起微信支付
 
wx.requestPayment({
 
timeStamp: param.data.timeStamp,//记住,这边的timeStamp一定要是字符串类型的,不然会报错
 
nonceStr: param.data.nonceStr,
 
package: param.data.package,
 
signType: 'MD5',
 
paySign: param.data.paySign,
 
success: function (event) {
 
// success
 
console.log(event);
 
 
 
wx.showToast({
 
title: '支付成功',
 
icon: 'success',
 
duration: 2000
 
});
 
},
 
fail: function (error) {
 
// fail
 
console.log("支付失败")
 
console.log(error)
 
},
 
complete: function () {
 
// complete
 
console.log("pay complete")
 
}
 
});
 
},

4.2 java后台

4.2.1 PayUtil.java

private static Logger logger = Logger.getLogger(PayUtil.class);
public static JSONObject wxPay(String openid,HttpServletRequest request){
JSONObject json = new JSONObject();
        try{
            //生成的随机字符串
            String nonce_str = Util.getRandomStringByLength(32);
            //商品名称 
            String body = new String(WXConst.title.getBytes("ISO-8859-1"),"UTF-8");
            //获取本机的ip地址
            String spbill_create_ip = Util.getIpAddr(request);
            String orderNo = WXConst.orderNo;
            String money = "1";//支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败
 
            Map<String, String> packageParams = new HashMap<String, String>();
            packageParams.put("appid", WXConst.appId);
            packageParams.put("mch_id", WXConst.mch_id);
            packageParams.put("nonce_str", nonce_str);
            packageParams.put("body", body);
            packageParams.put("out_trade_no", orderNo);//商户订单号
            packageParams.put("total_fee", money);
            packageParams.put("spbill_create_ip", spbill_create_ip);
            packageParams.put("notify_url", WXConst.notify_url);
            packageParams.put("trade_type", WXConst.TRADETYPE);
            packageParams.put("openid", openid);
 
 
            // 除去数组中的空值和签名参数
            packageParams = PayUtil.paraFilter(packageParams);
            String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
 
 
            //MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
            String mysign = PayUtil.sign(prestr, WXConst.key, "utf-8").toUpperCase();
            logger.info("=======================第一次签名:" + mysign + "=====================");
 
 
            //拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
            String xml = "<xml version='1.0' encoding='gbk'>" + "<appid>" + WXConst.appId + "</appid>"
                    + "<body><![CDATA[" + body + "]]></body>"
                    + "<mch_id>" + WXConst.mch_id + "</mch_id>"
                    + "<nonce_str>" + nonce_str + "</nonce_str>"
                    + "<notify_url>" + WXConst.notify_url + "</notify_url>"
                    + "<openid>" + openid + "</openid>"
                    + "<out_trade_no>" + orderNo + "</out_trade_no>"
                    + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
                    + "<total_fee>" + money + "</total_fee>"
                    + "<trade_type>" + WXConst.TRADETYPE + "</trade_type>"
                    + "<sign>" + mysign + "</sign>"
                    + "</xml>";
 
 
            System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);
 
 
            //调用统一下单接口,并接受返回的结果
            String result = PayUtil.httpRequest(WXConst.pay_url, "POST", xml);
 
 
            System.out.println("调试模式_统一下单接口 返回XML数据:" + result);
 
 
            // 将解析结果存储在HashMap中
            Map map = PayUtil.doXMLParse(result);
 
 
            String return_code = (String) map.get("return_code");//返回状态码
 
 
            //返回给移动端需要的参数
            Map<String, Object> response = new HashMap<String, Object>();
            if(return_code == "SUCCESS" || return_code.equals(return_code)){
                // 业务结果
                String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
                response.put("nonceStr", nonce_str);
                response.put("package", "prepay_id=" + prepay_id);
                Long timeStamp = System.currentTimeMillis() / 1000;
                response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
 
 
                String stringSignTemp = "appId=" + WXConst.appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id+ "&signType=" + WXConst.SIGNTYPE + "&timeStamp=" + timeStamp;
                //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PayUtil.sign(stringSignTemp, WXConst.key, "utf-8").toUpperCase();
                logger.info("=======================第二次签名:" + paySign + "=====================");
 
 
                response.put("paySign", paySign);
 
 
                //更新订单信息
                //业务逻辑代码
            }
 
 
            response.put("appid", WXConst.appId);
            json.put("errMsg", "OK");
            //json.setSuccess(true);
            json.put("data", response);
            //json.setData(response);
        }catch(Exception e){
            e.printStackTrace();
            json.put("errMsg", "Failed");
            //json.setSuccess(false);
            //json.setMsg("发起失败");
        }
        return json;
    }
 
 
 
/**  
     * 签名字符串  
     * @param text需要签名的字符串  
     * @param key 密钥  
     * @param input_charset编码格式  
     * @return 签名结果  
     */   
    public static String sign(String text, String key, String input_charset) {   
        text = text + "&key=" + key;   
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));   
    }   
    /**  
     * 签名字符串  
     * @param text需要签名的字符串  
     * @param sign 签名结果  
     * @param key密钥  
     * @param input_charset 编码格式  
     * @return 签名结果  
     */   
    public static boolean verify(String text, String sign, String key, String input_charset) {   
        text = text + key;   
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));   
        if (mysign.equals(sign)) {   
            return true;   
        } else {   
            return false;   
        }   
    }   
    /**  
     * @param content  
     * @param charset  
     * @return  
     * @throws SignatureException  
     * @throws UnsupportedEncodingException  
     */   
    public static byte[] getContentBytes(String content, String charset) {   
        if (charset == null || "".equals(charset)) {   
            return content.getBytes();   
        }   
        try {   
            return content.getBytes(charset);   
        } catch (UnsupportedEncodingException e) {   
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);   
        }   
    }   
    /**  
     * 生成6位或10位随机数 param codeLength(多少位)  
     * @return  
     */   
    public static String createCode(int codeLength) {   
        String code = "";   
        for (int i = 0; i < codeLength; i++) {   
            code += (int) (Math.random() * 9);   
        }   
        return code;   
    }   
    @SuppressWarnings("unused")
private static boolean isValidChar(char ch) {   
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))   
            return true;   
        if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))   
            return true;// 简体中文汉字编码   
        return false;   
    }   
    /**  
     * 除去数组中的空值和签名参数  
     * @param sArray 签名参数组  
     * @return 去掉空值与签名参数后的新签名参数组  
     */   
    public static Map<String, String> paraFilter(Map<String, String> sArray) {   
        Map<String, String> result = new HashMap<String, String>();   
        if (sArray == null || sArray.size() <= 0) {   
            return result;   
        }   
        for (String key : sArray.keySet()) {   
            String value = sArray.get(key);   
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")   
                    || key.equalsIgnoreCase("sign_type")) {   
                continue;   
            }   
            result.put(key, value);   
        }   
        return result;   
    }   
    /**  
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串  
     * @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 (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符   
                prestr = prestr + key + "=" + value;   
            } else {   
                prestr = prestr + key + "=" + value + "&";   
            }   
        }   
        return prestr;   
    }   
    /**  
     *  
     * @param requestUrl请求地址  
     * @param requestMethod请求方法  
     * @param outputStr参数  
     */   
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){   
        // 创建SSLContext   
        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);   
        }   
        br.close();
        }catch(Exception e){   
            e.printStackTrace();   
        }
        return buffer.toString();
    }     
    public static String urlEncodeUTF8(String source){   
        String result=source;   
        try {   
            result=java.net.URLEncoder.encode(source, "UTF-8");   
        } catch (UnsupportedEncodingException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        }   
        return result;   
    } 
    /**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
  if(null == strxml || "".equals(strxml)) {
    return null;
    }
 
  Map m = new HashMap();
  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) {
  return new ByteArrayInputStream(str.getBytes());
}
 
 
 
 
 
public static void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{  
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while((line = br.readLine())!=null){
            sb.append(line);
        }
        br.close();
        //sb为微信返回的xml
        String notityXml = sb.toString();
        String resXml = "";
        System.out.println("接收到的报文:" + notityXml);
 
 
        Map map = PayUtil.doXMLParse(notityXml);
 
 
        String returnCode = (String) map.get("return_code");
        if("SUCCESS".equals(returnCode)){
            //验证签名是否正确
            if(PayUtil.verify(PayUtil.createLinkString(map), (String)map.get("sign"), WXConst.key, "utf-8")){
                /**此处添加自己的业务逻辑代码start**/
 
 
 
 
                /**此处添加自己的业务逻辑代码end**/
 
 
                //通知微信服务器已经支付成功
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            }
        }else{
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        System.out.println(resXml);
        System.out.println("微信支付回调数据结束");
 
 
        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();  
    }

4.2.2 Util.java

/**
     * Util工具类方法
     * 获取一定长度的随机字符串,范围0-9,a-z
     * @param length:指定字符串长度
     * @return 一定长度的随机字符串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }
    
    
    /**
     * Util工具类方法
     * 获取真实的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();
 
    }

4.2.3 WXConst.java

//微信小程序appid
public static String appId = "";
//微信小程序appsecret
public static String appSecret = "";
//微信支付主体
public static String title = "";
public static String orderNo = "";
//微信商户号
public static String mch_id="";
//微信支付的商户密钥
public static final String key = "";
//获取微信Openid的请求地址
public static String WxGetOpenIdUrl = "";
//支付成功后的服务器回调url
public static final String notify_url="https://api.weixin.qq.com/sns/jscode2session";
//签名方式
public static final String SIGNTYPE = "MD5";
//交易类型
public static final String TRADETYPE = "JSAPI";
//微信统一下单接口地址
public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

5 可能出现的问题

5.1 商户号

会出现一个什么异常我忘了,重置一下微信商户密钥就好了

5.2 中文参数

String body = new String(WXConst.title.getBytes("ISO-8859-1"),"UTF-8");

这行很重要,如果报参数索引-2异常,很可能是出现了中文,对中文进行如上处理即可通过。

5.3 invalid spbill_create_ip

使用微信web开发工具直接测试的,出现了这个问题,调试记得用真机哦。

整个小程序前后端一个人开发,测试成功上线前夕又嫌弃微信支付收取的手续费(0.6%)太高,结算周期(T+7)太长,所以就被无情抛弃了,这个月项目重启(2018-11)和工商银行达成一致,直接转账到对公账户,目前项目进展顺利已上线。改需求请先扫码(小声bb)

以上所述是小编给大家介绍的微信小程序调用微信支付接口详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jquery控制左右箭头滚动图片列表的实例
May 20 Javascript
js中replace的用法总结
Dec 27 Javascript
js获取指定的cookie的具体实现
Feb 20 Javascript
Jquery中的层次选择器与find()的区别示例介绍
Feb 20 Javascript
jQuery $命名冲突解决方案汇总
Nov 13 Javascript
node+express+jade制作简单网站指南
Nov 26 Javascript
javascript结合canvas实现图片旋转效果
May 03 Javascript
微信小程序选择图片和放大预览图片功能
Nov 02 Javascript
Node.js搭建小程序后台服务
Jan 03 Javascript
解决vue单页路由跳转后scrollTop的问题
Sep 03 Javascript
Vue 刷新当前路由的实现代码
Sep 26 Javascript
vue iview 隐藏Table组件里的某一列操作
Nov 13 Javascript
vue实现微信分享链接添加动态参数的方法
Apr 29 #Javascript
VueJs里利用CryptoJs实现加密及解密的方法示例
Apr 29 #Javascript
微信小程序地图导航功能实现完整源代码附效果图(推荐)
Apr 28 #Javascript
详解vue 路由跳转四种方式 (带参数)
Apr 28 #Javascript
使用vue完成微信公众号网页小记(推荐)
Apr 28 #Javascript
详解VUE项目中安装和使用vant组件
Apr 28 #Javascript
详解JavaScript对数组操作(添加/删除/截取/排序/倒序)
Apr 28 #Javascript
You might like
Thinkphp单字母函数使用指南
2016/05/08 PHP
PHP实现的无限分类类库定义与用法示例【基于thinkPHP】
2018/08/06 PHP
javascript 动态数据下的锚点错位问题解决方法
2008/12/24 Javascript
javascript 对象比较实现代码
2009/04/27 Javascript
jQuery基本选择器选择元素使用介绍
2013/04/18 Javascript
你必须知道的Javascript知识点之&quot;单线程事件驱动&quot;的使用
2013/04/23 Javascript
js判断手机端(Android手机还是iPhone手机)
2015/07/22 Javascript
使用Node.js处理前端代码文件的编码问题
2016/02/16 Javascript
JavaScript Array对象详解
2016/03/01 Javascript
js实现带农历和八字等信息的日历特效
2016/05/16 Javascript
AnjularJS中$scope和$rootScope的区别小结
2016/09/18 Javascript
JavaScript基于自定义函数判断变量类型的实现方法
2016/11/23 Javascript
nodejs和php实现图片访问实时处理
2017/01/05 NodeJs
JavaScript监听手机物理返回键的两种解决方法
2017/08/14 Javascript
JavaScript时间戳与时间日期间相互转换
2017/12/11 Javascript
web前端vue实现插值文本和输出原始html
2018/01/19 Javascript
vue-cli3环境变量与分环境打包的方法示例
2019/02/18 Javascript
[41:11]完美世界DOTA2联赛PWL S2 Inki vs Magma 第一场 11.22
2020/11/24 DOTA
python中的字典使用分享
2016/07/31 Python
Python获取SQLite查询结果表列名的方法
2017/06/21 Python
pandas将DataFrame的列变成行索引的方法
2018/04/10 Python
Python 16进制与中文相互转换的实现方法
2018/07/09 Python
Pytorch 多维数组运算过程的索引处理方式
2019/12/27 Python
python能做哪些生活有趣的事情
2020/09/09 Python
Python本地及虚拟解释器配置过程解析
2020/10/13 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
意大利奢华内衣制造商:Cosabella
2017/08/29 全球购物
HomeAway澳大利亚:预订你的度假屋,公寓、度假村、别墅等
2019/02/20 全球购物
写出SQL四条最基本的数据操作语句(DML)
2012/12/12 面试题
公积金单位接收函
2014/01/11 职场文书
诚信承诺书范文
2014/03/27 职场文书
工商局副局长个人对照检查材料
2014/09/25 职场文书
2014年班务工作总结
2014/12/02 职场文书
2014年校务公开工作总结
2014/12/18 职场文书
2015年新学期寄语
2015/02/26 职场文书
2016国庆节67周年寄语
2015/12/07 职场文书