小程序微信支付java(来自技术小菜的分享)

本文目的为了让更多像我一样的小白了解微信支付的整个流程,希望能够给你们提供一点帮忙,有不足的地方也希望各位大神能够指正,第一次发博勿喷。

微信支付交互流程图

微信支付整体流程
- 1.统一下单(为了得到预支付id)
- 2.再次签名(将得到的预支付id再次和相关参数进行签名)
- 3.返回给前端xml(封装前端所需相关字段数据)
- 4.支付成功后的回调(进行相关逻辑处理)


总概:

调用微信提供的统一下单接口,得到预支付id - prepay_id
具体参照微信官方文档:微信支付统一下单接口文档

接口写完之后调试可能会用到的工具:微信接口调试工具
此处注意部分浏览器可能打开显示不完整,推荐使用谷歌浏览器。

ASCII码升序排序加密签名类也在最后补充贴出。

-此处需要注意:签名为本步骤中较为麻烦的一点,微信要求比较严格,严格遵守文档要求进行传参,与参数处理。

-以下指出几点这个操作中可能会遇到的情况:
1.body 字段,参数如果为中文可能会导致签名错误。
2.total-fee 字段,进行处理以分为单位传递,微信是以分为单位。
3.传递参数和签名加密等相关数据与测试工具中一致表面代码正确。
4.若代码正确,还是返回签名错误的情况可尝试修改API密钥(32位key)。

微信接口调试工具使用示例

具体怎么调试我想不需要介绍,总的来说就是自己的参数和生成的东西需要与这里自动生成的一致

参数填写的截图
参数填写部分
填写完成后点击下方生成签名………

签名生成截图
签名生成的截图

服务端完整代码

贴出自己微信支付代码供参考,代码处理不好的地方请勿喷:

//商户号
String mchId = "";
//支付密钥
String key = "&key=自己的商户密钥";
//交易类型
String tradeType = "JSAPI";
//随机字符串
String nonceStr = WxPayUtil.getNonceStr();
//微信支付完成后给该链接发送消息,判断订单是否完成
String notifyUrl = "外网能够访问的url,支付成功后的回调";
//微信用户唯一id
        if(param.getOpenid()==null){
            return result(500 ,"支付失败,openid is null");
        }
        String openId = param.getOpenid();
        //小程序id
        if(param.getAppid()==null){
            return result(500 ,"支付失败,appid is null");
        }
        String appid  = param.getAppid();  
        //商品订单号(保持唯一性)
        String outTradeNo = mchId+WxPayUtil.getNonceStr();
        //支付金额
        if(param.getTotalFee()==null){
            return result(500 ,"支付失败,totalfee is null");
        }
        String fee = param.getTotalFee();
        String totalFee = WxPayUtil.getMoney(fee);
        //发起支付设备ip
        String spbillCreateIp = param.getSpbillCreateIp();
        //商品描述
        if(param.getBody()==null){
            return result(500 ,"支付失败,body is null");
        }
        String body = param.getBody();
        //附加数据,商户携带的订单的自定义数据 (原样返回到通知中,这类我们需要系统中订单的id 方便对订单进行处理)
        String attach = param.getAttach();

        //我们后面需要键值对的形式,所以先装入map
 Map<String, String> sParaTemp = new HashMap<String, String>();
 sParaTemp.put("appid", appid);
 sParaTemp.put("attach", attach);
 sParaTemp.put("body",  body);
 sParaTemp.put("mch_id", mchId);
 sParaTemp.put("nonce_str", nonceStr);
 sParaTemp.put("notify_url",notifyUrl);
 sParaTemp.put("openid", openId);
 sParaTemp.put("out_trade_no", outTradeNo);
 sParaTemp.put("spbill_create_ip", spbillCreateIp);
 sParaTemp.put("total_fee",totalFee);
 sParaTemp.put("trade_type", tradeType);

 //去掉空值 跟 签名参数(空值不参与签名,所以需要去掉)
Map<String, String> map = WxPayUtil.paraFilter(sParaTemp);
/**
按照 参数=参数值&参数2=参数值2 这样的形式拼接(拼接需要按照ASCII码升序排列)
/
String mapStr = WxPayUtil.createLinkString(map);
//MD5运算生成签名
 String sign = 
 WxPayUtil.sign(mapStr, key, "utf-8").toUpperCase();
   sParaTemp.put("sign", sign);
/** 
组装成xml参数,此处偷懒使用手动组装,严格代码可封装一个方法,XML标排序需要注意,ASCII码升序排列 
*/
        String xml = "<xml>" + "<appid>" + appid + "</appid>" 
                    + "<attach>" + attach + "</attach>" 
                    + "<body>" + body + "</body>" 
                    + "<mch_id>" + mchId + "</mch_id>" 
                    + "<nonce_str>" + nonceStr + "</nonce_str>" 
                    + "<notify_url>" + notifyUrl + "</notify_url>" 
                    + "<openid>" + openId + "</openid>" 
                    + "<out_trade_no>" + outTradeNo + "</out_trade_no>" 
                    + "<spbill_create_ip>" + spbillCreateIp + "</spbill_create_ip>" 
                    + "<total_fee>" + totalFee + "</total_fee>"
                    + "<trade_type>" + tradeType + "</trade_type>" 
                    + "<sign>" + sign + "</sign>"
                    + "</xml>";
        //统一下单url,生成预付id
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String result =WxPayUtil.httpRequest(url, "POST", xml);

        Map<String, String> paramMap = new HashMap<String, String>();
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        //得到预支付id
        String prepay_id = "";
        try {
            prepay_id = WxPayUtil.getPayNo(result);
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        String packages = "prepay_id="+prepay_id;
        String nonceStr1 = WxPayUtil.getNonceStr();
        //开始第二次签名
        String mapStr1 = "appId="+appid+"&nonceStr=" + nonceStr1 + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
        String paySign = WxPayUtil.sign(mapStr1, key, "utf-8").toUpperCase();
        //前端所需各项参数拼接
        String finaPackage = "\"appId\":\"" + appid + "\",\"timeStamp\":\"" + timeStamp
                + "\",\"nonceStr\":\"" + nonceStr1 + "\",\"package\":\""
                + packages + "\",\"signType\" : \"MD5" + "\",\"paySign\":\""
                + paySign + "\"";

        return result(200,finaPackage);
    }
... 
'''

代码中使用到的工具类

public class WxPayUtil{

    /**
     * 获取随机字符串 (采用截取8位当前日期数  + 4位随机整数)
     * @return 
     */
    public static String getNonceStr() {
        //获得当前日期
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String currTime = outFormat.format(now);
        //截取8位
        String strTime = currTime.substring(8, currTime.length());
        //得到4位随机整数
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < 4; i++) {
            num = num * 10;
        }
        num = (int)random * num;
        return strTime + num;
    }

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @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 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;
    }

    /**
     * MD5 加密,转为指定类型
     * @param text
     * @param key
     * @param input_charset
     * @return
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

    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);
        }
    }


    /**
     * 元转换成分
     * @param money
     * @return
     */
    public static String getMoney(String amount) {
        if(amount==null){
            return "";
        }
        // 金额转化为分为单位
        String currency =  amount.replaceAll("\\$|\\¥|\\,", "");  //处理包含, ¥ 或者$的金额  
        int index = currency.indexOf(".");  
        int length = currency.length();  
        Long amLong = 0l;  
        if(index == -1){  
            amLong = Long.valueOf(currency+"00");  
        }else if(length - index >= 3){  
            amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));  
        }else if(length - index == 2){  
            amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);  
        }else{  
            amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");  
        }  
        return amLong.toString(); 
    }

    /**
     * 解析xml得到 prepay_id 预支付id
     * @param result
     * @return
     * @throws DocumentException 
     */
    public static String getPayNo(String result) throws DocumentException{
        Map<String, String> map = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(result.getBytes());
        SAXReader read = new SAXReader();
        Document doc = read.read(in);
        //得到xml根元素
        Element root = doc.getRootElement();
        //遍历  得到根元素的所有子节点 
        @SuppressWarnings("unchecked")
        List<Element> list =root.elements();
        for(Element element:list){
            //装进map
            map.put(element.getName(), element.getText());
        }
        //返回码
        String return_code = map.get("return_code");
        //返回信息
        String result_code = map.get("result_code");
        //预支付id
        String prepay_id = "";
        //return_code 和result_code 都为SUCCESS 的时候返回 预支付id
        if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")){
            prepay_id = map.get("prepay_id");
        }
        return prepay_id;
    }
    /**
     * 解析 回调时的xml装进map 返回
     * @param result 
     * @return
     * @throws DocumentException 
     */
    public static Map<String, String> getNotifyUrl(String result) throws DocumentException{
        Map<String, String> map = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(result.getBytes());
        SAXReader read = new SAXReader();
        Document doc = read.read(in);
        //得到xml根元素
        Element root = doc.getRootElement();
        //遍历  得到根元素的所有子节点 
        @SuppressWarnings("unchecked")
        List<Element> list =root.elements();
        for(Element element:list){
            //装进map
            map.put(element.getName().toString(), element.getText().toString());
        }
        return map;
    }

    /**
     * 验证签名,判断是否是从微信发过来
     * 验证方法:接收微信服务器回调我们url的时候传递的xml中的参数 然后再次加密,看是否与传递过来的sign签名相同
     * @param map
     * @return
     */
    public static boolean verifyWeixinNotify(Map<String, String> map,String key) {  
        //根据微信服务端传来的各项参数 进行再一次加密后  与传过来的 sign 签名对比
        String mapStr = createLinkString(map);
        String signOwn = WxPayUtil.sign(mapStr, key, "utf-8").toUpperCase();         //根据微信端参数进行加密的签名
        String signWx = map.get("sign");                //微信端传过来的签名
        if(signOwn.equals(signWx)){
            //如果两个签名一致,验证成功
            return true;
        }
        return false;
    }  

    /**
     * 
     * @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);
        }
        }catch(Exception e){
            e.printStackTrace();
        }
        return buffer.toString();
        }   


ASCII码升序排列加密签名

**看许多朋友再找这个工具方法,贴出来供使用**
    /**
     * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     */
    public String createSign(SortedMap<String, String> packageParams) {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k)
                    && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + this.getKey());
        String sign = DigestUtils.md5Hex(getContentBytes(text, "utf-8"))
                .toUpperCase();
        return sign;

    }

支付通知(回调url)

请参考官方文档:微信支付通知官方文档

最后一步就是支付完成后的通知了(回调同意下单时候填写的notify_url)
在此步环节中重要的是 验证(介绍在下面) ,验证通知是否是从微信端发送过来的,为了防止资金出现问题。

验证过程:

就是把微信端发送过来的报文解析,得到相关参数(具体可以查看自己第一步签名时使用了哪些参数)进行再次签名加密,然后与报文发送过来的sign签名进行对比。

注意:微信会循环回调该url,直到确认支付完成才回停止,所以我们需要在该url中返指定 xml让它停止调用。

支付通知代码片段

        //用于处理结束后返回的xml
        String resXml = "";
        String key = "&key=自己的密钥";
        try {
            InputStream in = request.getInputStream();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int len = 0;
            byte[] b = new byte[1024];
            while((len = in.read(b)) != -1){
                out.write(b, 0, len);
            }
            out.close();
            in.close();
            //将流 转为字符串
            String result = new String(out.toByteArray(), "utf-8");
            Map<String, String> map = WxPayUtil.getNotifyUrl(result);
            String return_code = map.get("return_code").toString().toUpperCase();
            if(return_code.equals("SUCCESS")){
                //进行签名验证,看是否是从微信发送过来的,防止资金被盗
                if(WxPayUtil.verifyWeixinNotify(map, key)){
                    //签名验证成功后按照微信要求返回的xml
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    return resXml;
                }
            }else{
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
                        + "<return_msg><![CDATA[sign check error]]></return_msg>" + "</xml> ";
                return resXml;
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
                + "<return_msg><![CDATA[xml error]]></return_msg>" + "</xml> ";
        return resXml;

服务端的支付接口相关以及处理完毕了,现在需要前端上场了

-

前端操作

没错,在我们的处理之下,前端只需要这一个东西就搞定!
wx.requestPayment(OBJECT)

具体操作参照官方文档:微信小程序官方API文档

到这里小程序微信支付所有操作已经完成了,当自己做完了之后是不是觉得很简单了。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值