Bootstrap

手把手教你搞定 微信支付 跳出微信支付的坑 (公众号支付,核心代码可以用于小程序支付)

1. 准备工作

 设置支付目录

  请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。

  在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台-->产品中心-->开发配置,如下图所示。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。

设置授权域名

开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面下图所示:

* 注:这里的服务器路径和网页授权的路径要特别,因为路径的相对深浅,在调取微信支付接口的时候,可能会导致微信支付窗口出现闪退情况。

2.那么接下来咱们来了解一下业务流程(我到底需要做什么事情)

 

 

 

其实微信支付我们只需要这几点:

1.将必要路径和配置正好,比如商户key(在商户平台设置),服务器路径,网页授权(在公众号里面)等等,

2.发起支付请求,请求分为预支付(调用预订单接口)请求和支付请求(调取H5支付接口),其中调用预订单接口之后,微信服务器会给我们一些东西,其中有预订单id,和微信支付签名等

3.调用微信支付成功之后,我们需要给微信服务器以反馈 告知微信服务器已成功。

 以上的话 是大体流程  ,那下面我们来看一下具体的实现和应该注意的微信坑

  1. 前端js,调用后台接口,调用微信统一下单接口
  2. 实现统一下单接口,获取微信服务器返回预订单id和签名
  3. 调取微信支付接口,完成支付
  4. 跳转回调函数 ,反馈微信服务器
字段名变量名必填类型示例值描述
公众账号IDappidString(32)wxd678efh567hg6787微信支付分配的公众账号ID(企业号corpid即为此appId)
商户号mch_idString(32)1230000109微信支付分配的商户号
设备号device_infoString(32)013467007045764自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
随机字符串nonce_strString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,长度要求在32位以内。推荐随机数生成算法
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6通过签名算法计算得出的签名值,详见签名生成算法
签名类型sign_typeString(32)MD5签名类型,默认为MD5,支持HMAC-SHA256和MD5。
商品描述bodyString(128)腾讯充值中心-QQ会员充值

商品简单描述,该字段请按照规范传递,具体请见参数规定

商品详情detailString(6000) 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明”
附加数据attachString(127)深圳分店附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
商户订单号out_trade_noString(32)20150806125346商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号
标价币种fee_typeString(16)CNY符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
标价金额total_feeInt88订单总金额,单位为分,详见支付金额
终端IPspbill_create_ipString(16)123.12.12.123APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
交易起始时间time_startString(14)20091225091010订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
交易结束时间time_expireString(14)20091227091010

订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则

建议:最短失效时间间隔大于1分钟

订单优惠标记goods_tagString(32)WXG订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
通知地址notify_urlString(256)http://www.weixin.qq.com/wxpay/pay.php异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
交易类型trade_typeString(16)JSAPI

JSAPI 公众号支付

NATIVE 扫码支付

APP APP支付

说明详见参数规定

商品IDproduct_idString(32)12235413214070356458058trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
指定支付方式limit_payString(32)no_credit上传此参数no_credit--可限制用户不能使用信用卡支付
用户标识openidString(128)oUpF8uMuAJO_M2pxb1Q9zNjWeS6otrade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
+场景信息scene_infoString(256)

{"store_info" : {
"id": "SZTX001",
"name": "腾大餐厅",
"area_code": "440305",
"address": "科技园中一路腾讯大厦" }}

该字段用于上报场景信息,目前支持上报实际门店信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }} ,字段详细说明请点击行前的+展开

前端js,调用后台接口,调用微信统一下单接口

 1. 前端js(ajax)走后端接口  这个就不多说了

 2. 调用微信统一下单接口

Map<String, String> result = wechatAppPayPlugin.getParameterMap(payment.getSn(), new BigDecimal("0.01"), member.getOpendId().toString(), "充值支付", request);

payment.getSn()为自定义生成的订单编号   

new BigDecimal("0.01")  为当前支付的总价格

member.getOpendId().toString()  为当前用户的openid

private static final String WECHAT_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";   //统一下单接口

public Map<String, String> getParameterMap(String sn, BigDecimal amount, String openId, String description,
            HttpServletRequest request) {

        Date now = new Date();
        WechatOrderReturn order = orderSubmit(sn, amount, now, openId, getByteStr(description, 250, "..."));
        String timestamp = String.valueOf(now.getTime()).substring(0, 10);

        Map<String, String> map = new HashMap<String, String>();
        map.put("appId", order.getAppid());
        map.put("nonceStr", order.getNonce_str());// 预支付交易会话标识
        map.put("package", "prepay_id=" + order.getPrepay_id());
        map.put("signType", "MD5");
        map.put("timeStamp", timestamp);

        Setting setting = SettingUtils.get();
        String wechatPayKey = setting.getWxpayPrivateKey();    //微信支付秘钥
        String sign = WechatPayUtil.getSignByMap(map, wechatPayKey);
        map.put("sign", sign);
        return map;
    }

 // 收集统一下单的参数

public WechatOrderReturn orderSubmit(String sn, BigDecimal amount, Date now, String openId, String description) {

        Setting setting = SettingUtils.get();
        String wechatPayKey = setting.getWxpayPrivateKey();
        String appid = setting.getWxAppId();      //公众号的appid
        String mchId = setting.getWxMchId();     //商户号id  

        // 构造微信统一下单API
        WechatOrder wechatOrder = new WechatOrder();
        wechatOrder.setAppid(appid);
        wechatOrder.setMch_id(mchId);

        String nonce_str = UUID.randomUUID().toString().substring(0, 32);// 随机字符串,不长于32位

        int total_fee = amount.multiply(new BigDecimal(100)).intValue();// 总额,以分为单位
        String spbill_create_ip = "xx.xx.xx.xx";// 终端ip
        wechatOrder.setNonce_str(nonce_str);// 随机字符串 不长于32位

        // 商品描述125
        if (description == null) {
            wechatOrder.setBody("xxxxx");
        } else if (description.length() > 124) {
            wechatOrder.setBody(description.substring(0, 124));
        } else {
            wechatOrder.setBody(description);
        }

        wechatOrder.setOut_trade_no(sn);// 商户订单号(必须唯一)
        wechatOrder.setTotal_fee(total_fee);// 总额 以分为单位
        wechatOrder.setSpbill_create_ip(spbill_create_ip);// 终端ip
        wechatOrder.setNotify_url(getNtifyUrl(sn));// 接收微信支付异步通知回调地址
        wechatOrder.setTrade_type("JSAPI");
        wechatOrder.setOpenid(openId);

        String sign = WechatPayUtil.getSign(wechatOrder, wechatPayKey);// 生成签名

        wechatOrder.setSign(sign);
        logger.info("【微信下单参数】" + new Gson().toJson(wechatOrder));
        String xml = JaxbUtil.convertToXml(wechatOrder);
        xml = xml.replace("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", "");
        logger.info("【微信下单参数】" + xml);
        String result = WechatPayUtil.httpsRequest(WECHAT_PAY_URL, "POST", xml);
        logger.info("【微信下单结果】" + result);
        WechatOrderReturn wechatOrderReturn = JaxbUtil.converyToJavaBean(result, WechatOrderReturn.class);
        wechatOrderReturn.setSign(sign);
        System.out.println(sign);
        return wechatOrderReturn;
    }

    //生成签名工具类

    public static String getSign(Object bean, String wechatPayKey) {
        List<String> list = new ArrayList<String>();
        Class<? extends Object> c = bean.getClass();
        Field[] fields = c.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true); // 设置些属性是可以访问的
            Object val = null;
            try {
                val = field.get(bean);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }// 得到此属性的值
            String name = field.getName();// 得到此属性的名字
            if (val != null && "sign".equals(name) == false) {
                list.add(name + "=" + val.toString());
            }
        }
        Object[] os = list.toArray();
        Arrays.sort(os);
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < os.length; i++) {
            if (i > 0) {
                stringBuffer.append("&");
            }
            stringBuffer.append(os[i]);
        }
        stringBuffer.append("&key=");
        stringBuffer.append(wechatPayKey);
        String content = stringBuffer.toString();
        logger.info("【微信签名字符串】" + content);

        String sign = null;
        try {
            sign = DigestUtils.md5DigestAsHex(content.getBytes("utf-8")).toString();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return sign.toUpperCase();
    }

// 发送请求工具类

private static String httpsRequest(String requestUrl, String requestMethod, String outputStr, Integer count) {
        String result = null;
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);

            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            result = buffer.toString();
        } catch (ConnectException ce) {
            //判断类型为超时重新调用
            if(count < 3 && "Connection timed out: connect".equals(ce.getMessage())){
                //超时调用3次
                count++;
                return httpsRequest(requestUrl, requestMethod, outputStr, count);
            }else{
                ce.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(result);
        return result;
    }

//回调函数

private String getNtifyUrl(String sn) {
        Setting setting = SettingUtils.get();
        StringBuffer sb = new StringBuffer();
        sb.append(setting.getSiteUrl());
        sb.append("/api/order/notify/wechat/");
        sb.append(sn);
        sb.append(".jhtml");
        return sb.toString();
    }

    /**
     * 
     * @param str
     *            被截取字符串
     * @param length
     *            截取长度
     * @param more
     *            更多代替字符
     * @return
     */
    protected String getByteStr(String str, Integer length, String more) {
        StringBuilder sb = new StringBuilder();
        int currentLength = 0;
        char[] _moreChar = more.toCharArray();
        length = length - _moreChar.length;
        for (char c : str.toCharArray()) {
            try {
                currentLength += String.valueOf(c).getBytes("UTF8").length;
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            if (currentLength <= length) {
                sb.append(c);
            } else {
                str = sb.toString() + more;
                break;
            }
        }
        return str;
    }

 

 

调用统一下单接口之后  微信服务器会返回预订单id

之后返给手机端  调取微信支付接口

官方有代码实例  我就不写了

当调用成功之后,服务器会自动给您转到回调函数中

遇见这样的情况,说明调取成功

同时  我们要验证一下签名,并反馈服务器

return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";

一句话就可以了

 

 

;