微信H5支付接口

发布于 2021-01-26 10:48

H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。

主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。

微信支付流程:

  1. 将参数封装为xml格式。

  2. 调用MD5加密算法签名

  3. 发送请求

  4. 返回中间页面

  5. 调起微信支付

  6. 支付成功后,回调

生成签名:

1.第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

特别注意以下重要规则:

  1. ◆ 参数名ASCII码从小到大排序(字典序);

  2. ◆ 如果参数的值为空不参与签名;

  3. ◆ 参数名区分大小写;

  4. ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。

  5. ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

2.第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

举例:

假设传送的参数如下:

appid:wxd930ea5d5a258f4f

mch_id:10000100

device_info:1000

body:test

nonce_str:ibuaiVcKdpRxkhJA

第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下

stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";

第二步:拼接API密钥:

MD5签名方式:

stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key

sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式

<xml>

<appid>wxd930ea5d5a258f4f</appid>

<mch_id>10000100</mch_id>

<device_info>1000</device_info>

<body>test</body>

<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>

<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>

</xml>

发送完数据后,并不代表调起了微信支付。微信支付相比于支付宝支付,这里多了一个请求。返回的数据是一个xml格式的一堆参数,其中mweu_url是调起微信支付的中间页。

支付成功后,如果如果想要在前端跳转地址,需要在mweu_url中加入跳转地址:

正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面回调地址需要进行urlencode处理。

微信的回调

官方文档:支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。

注意:

1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。

3、在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单API】确认订单状态。

特别提醒:

如果不在回调方法中给微信通知,微信会疯狂回调。

支付:

@RequestMapping(value = "weixinPayRechargeGold",method = RequestMethod.POST)    @ResponseBody    public Result  weixinPayWap(InfoBean bean,HttpServletRequest request) throws Exception {        Result result=new Result();        result.setCode(200);        result.setMsg("success");        String mweb_url = "";        try {            TreeMap<String, String> paraMap = new TreeMap<>();            paraMap.put("appid", "wx1d84563a6ac34711"); //商户ID            paraMap.put("body", "xxxx"); //商品名称            paraMap.put("mch_id", "1491796222");            paraMap.put("nonce_str", WXPayUtil.generateNonceStr()); //String 随机字符串            String time = String.valueOf(new Date().getTime());            paraMap.put("out_trade_no", bean.getOut_trade_no());//订单号            paraMap.put("spbill_create_ip", getIpAddr(request));//请求IP            paraMap.put("total_fee", bean.getTotal_amount()); //总金额            paraMap.put("trade_type", "MWEB"); //类型            paraMap.put("scene_info", "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"http://m.jnbat.com\",\"wap_name\": \"shangcheng\"}}");            paraMap.put("notify_url",bean.getNotify_url());// 此路径是微信服务器调用支付结果带了一大批参数多次请求            String stringA=SignUtil.map_to_string(paraMap);            String stringSignTemp=stringA+"&key=ggy101600ggy101600ggy101600ggy10";            String sign = SignUtil.MD5(stringSignTemp);            paraMap.put("sign", sign);            String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式            // 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder            String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//            String xmlStr = sendPost(unifiedorder_url,xml);            String xmlStr = HttpClientUtils.doPostByXml(unifiedorder_url, xml);            //以下内容是返回前端页面的json数据//            String xmlStr2 = HttpClientUtils.doPostString(unifiedorder_url, xml);            System.out.println(xmlStr);            if (xmlStr.indexOf("SUCCESS") != -1) {                Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);                mweb_url = map.get("mweb_url");                //支付完返回浏览器跳转的地址,如跳到查看订单页面//                String redirect_url = "http://413f2a199c79.ngrok.io/paysuccess.jsp";//                String redirect_urlEncode = URLEncoder.encode(redirect_url,"GBK");//对上面地址urlencode                mweb_url=mweb_url+"&redirect_url="+URLEncoder.encode("http://m.jnbat.com","utf-8");//拼接返回地址                System.out.println(mweb_url);                String s = HttpClientUtils.doPostXml2(mweb_url);//                String s = HttpClientUtils.doGet(mweb_url);                System.out.println(s);                result.setData(s);//              response.sendRedirect(mweb_url);                return result;            }        } catch (Exception e) {            e.printStackTrace();        }//        response.sendRedirect(mweb_url);//        request.getRequestDispatcher(mweb_url).forward(request,response);        return null;    }

回调:

@RequestMapping("weixinRechargeGoldResult")    public void weixinResult(HttpServletRequest request, HttpServletResponse response) {        BufferedReader reader;        try {            reader = request.getReader();            String line = "";            StringBuffer inputString = new StringBuffer();            while ((line = reader.readLine()) != null) {                inputString.append(line);            }            request.getReader().close();            Map<String, String> notifyMap = WXPayUtil.xmlToMap(inputString.toString());            //验签            if (notifyMap.get("return_code").equals("SUCCESS")) {                if (notifyMap.get("result_code").equals("SUCCESS")) {                    String out_trade_no = notifyMap.get("out_trade_no"); //商户订单号                    String amountpaid = notifyMap.get("total_fee");//实际支付的订单金额:单位 分                    BigDecimal amountPay = (new BigDecimal(amountpaid).divide(new BigDecimal("100"))).setScale(2);//将分转换成元-实际支付金额:元                    String openid = notifyMap.get("openid"); //如果有需要可以获取                    orderService.updateOrderStatus(out_trade_no, "Paid");                }            }            //告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可            response.setContentType("text/xml; charset=utf-8");            response.getWriter().write("<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>");        } catch (Exception e) {            e.printStackTrace();        }    }

工具类:

//WXPayUtil在这里就不写了/**     * 获取用户实际ip     * @param request     * @return     */    public static String getIpAddr(HttpServletRequest request) {        String ipAddress = request.getHeader("x-forwarded-for");        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = request.getHeader("Proxy-Client-IP");        }        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = request.getHeader("WL-Proxy-Client-IP");        }        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = request.getRemoteAddr();            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {                //根据网卡取本机配置的IP                InetAddress inet = null;                try {                    inet = InetAddress.getLocalHost();                } catch (UnknownHostException e) {                    e.printStackTrace();                }                ipAddress = inet.getHostAddress();            }        }        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割        if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15            if (ipAddress.indexOf(",") > 0) {                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));            }        }        return ipAddress;    }        
package com.gouwuche.Util;//在向微信发起请求时,用到的这个类,出现在这里不是很合适。但是也用到了//等哪天再单独写一篇手写HttpLcient的笔记import org.apache.http.HttpEntity;import org.apache.http.NameValuePair;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.client.utils.URIBuilder;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.conn.ssl.TrustStrategy;import org.apache.http.entity.ContentType;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.message.BasicNameValuePair;import org.apache.http.ssl.SSLContextBuilder;import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext;import java.io.IOException;import java.net.URI;import java.security.KeyManagementException;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.cert.X509Certificate;import java.util.ArrayList;import java.util.List;import java.util.Map;public class HttpClientUtils {    public static String doGet(String url, Map<String, String> param) {        // 创建Httpclient对象        CloseableHttpClient httpclient = HttpClients.createDefault();        String resultString = "";        CloseableHttpResponse response = null;        try {            // 创建uri            URIBuilder builder = new URIBuilder(url);            if (param != null) {                for (String key : param.keySet()) {                    builder.addParameter(key, param.get(key));                }            }            URI uri = builder.build();            // 创建http GET请求            HttpGet httpGet = new HttpGet(uri);            // 执行请求            response = httpclient.execute(httpGet);            // 判断返回状态是否为200            if (response.getStatusLine().getStatusCode() == 200) {                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");            }        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (response != null) {                    response.close();                }                httpclient.close();            } catch (IOException e) {                e.printStackTrace();            }        }        return resultString;    }    public static String doGet(String url) {        return doGet(url, null);    }    public static String doPostXml2(String url){        CloseableHttpClient httpClient = HttpClients.createDefault();        HttpPost httpPost = new HttpPost(url);    /*    RequestConfig config = RequestConfig.custom()                .setConnectionRequestTimeout(15000) //连接请求超时时间                .setConnectTimeout(60000)   //连接服务器超时时间                .setSocketTimeout(60000).build();//设置读取响应数据超时时间        httpPost.setConfig(config);*/        httpPost.addHeader("Content-Type","application/x-www-form-urlencoded ");        httpPost.addHeader("Referer","m.jnbat.com");        String result =null;        try {            CloseableHttpResponse response = httpClient.execute(httpPost);            HttpEntity entity = response.getEntity();            result = EntityUtils.toString(entity);        } catch (IOException e) {            e.printStackTrace();            throw new RuntimeException("post error");        }        return result;    }    public static String doPost(String url, Map<String, String> param) {        // 创建Httpclient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        CloseableHttpResponse response = null;        String resultString = "";        try {            // 创建Http Post请求            HttpPost httpPost = new HttpPost(url);            // 创建参数列表            if (param != null) {                List<NameValuePair> paramList = new ArrayList<>();                for (String key : param.keySet()) {                    paramList.add(new BasicNameValuePair(key, param.get(key)));                }                // 模拟表单                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);                httpPost.setEntity(entity);            }            // 执行http请求            response = httpClient.execute(httpPost);            resultString = EntityUtils.toString(response.getEntity(), "utf-8");        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                response.close();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        return resultString;    }    public static String doPost(String url) {        return doPost(url, null);    }    public static String doPostJson(String url, String json) {        // 创建Httpclient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        CloseableHttpResponse response = null;        String resultString = "";        try {            // 创建Http Post请求            HttpPost httpPost = new HttpPost(url);            // 创建请求内容            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);            httpPost.setEntity(entity);            // 执行http请求            response = httpClient.execute(httpPost);            resultString = EntityUtils.toString(response.getEntity(), "utf-8");        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                response.close();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        return resultString;    }    /**     * @Description post请求发送xml     * @Param [url, requestDataXml]     * @return java.lang.String     **/    public static String doPostByXml(String url,String requestDataXml){        CloseableHttpClient httpClient=null;        CloseableHttpResponse httpResponse=null;        //创建httpClient连接对象        //httpClient = HttpClients.createDefault();        httpClient = HttpClientUtils.createSSLClientDefault();        //创建post请求连接对象        HttpPost httpPost = new HttpPost(url);        //创建连接请求参数对象,并设置连接参数        RequestConfig requestConfig = RequestConfig.custom()                .setConnectTimeout(15000)    //连接服务器主机超时时间                .setConnectionRequestTimeout(60000)  //连接请求超时时间                .setSocketTimeout(6000)        //设置读取响应数据超时时间                .build();        //为httpPost请求设置参数        httpPost.setConfig(requestConfig);        //将上传参数存放到entity属性中        httpPost.setEntity(new StringEntity(requestDataXml,"UTF-8"));        //添加头信息        httpPost.setHeader("Content-Type","text/xml");        String result="";        try {            //发送请求            httpResponse = httpClient.execute(httpPost);            //获取返回内容            HttpEntity httpEntity = httpResponse.getEntity();            result = EntityUtils.toString(httpEntity, "UTF-8");        } catch (IOException e) {            e.printStackTrace();        }        return result;    }    /**     * 创建一个SSL信任所有证书的httpClient对象     *     * @return     */    public static CloseableHttpClient createSSLClientDefault() {        try {            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {                // 信任所有                @Override                public boolean isTrusted(X509Certificate[] chain, String authType){                    return true;                }            }).build();            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);            return HttpClients.custom().setSSLSocketFactory(sslsf).build();        } catch (KeyManagementException e) {            e.printStackTrace();        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (KeyStoreException e) {            e.printStackTrace();        }        return HttpClients.createDefault();    }}

吐槽:微信的支付api坑比支付宝api多。

调侃:微信支付就是图一热闹,真要支付,害的看支付宝

官方文档地址

https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1

https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7

本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。

相关素材