美文网首页
02_微信扫码支付接口开发

02_微信扫码支付接口开发

作者: 明天你好向前奔跑 | 来源:发表于2017-10-17 19:45 被阅读0次

@Author wangwangjie
转载请标明出处~~~

1. 微信扫码支付快速接入

01.png
微信支付接入网址:https://pay.weixin.qq.com/index.php/partner/public/home
1. 与支付宝类似,微信扫码支付开发也需要进行申请,审核通过,微信会下发一个公众号appID与一个商户号mchID。
2. 生成一个32位的随机密码作为微信的秘钥,然后登陆微信公众号商户平台(就是上面的链接)设置API密钥。例如:057B07F6A839420C8110D60F728CB92
3. 有了appid,mchID,及key就可以进行开发了。
    微信支付开发者文档链接:https://pay.weixin.qq.com/wiki/doc/api/index.html

    3.1 下载微信支付的sdk:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1,在上面开发者文档中都能找到.
        将sdk加入到自己的项目中。

    3.2 下面就可以参照sdk中的demo进行开发

这里主要列出支付下单的流程及代码:

微信支付业务流程时序图:
02.png
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。


-------------------------------------------------------------------------------------------------------------

具体代码:

application.properties:
    #微信配置项
    wx_appID=wx123456789
    wx_mchID=xxxxxxx
    #微信支付秘钥,API根据key默认以MD5生成sign,预创建成功返回return_code为success时以key与默认MD5校验key
    wx_key=xxxxxxxxxxxxxxxxxxxxxxxxxx(32位随机数密钥)
    wx_notify_url=http://xx.com.cn/pay/wxNotify
    wx_spbill_create_ip=127.0.0.1
    #微信支付请求的主域名
    wx_domain=api.mch.weixin.qq.com
    #该域名是否为主域名
    wx_primaryDomain=true


自定义WXConfig继承WXPayConfig:
@Component
public class WXconfig extends WXPayConfig {
    @Value("${wx_appID}")
    private String appID;
    @Value("${wx_mchID}")
    private String mchID;
    @Value("${wx_key}")
    private String key;

    //这里要设置domain和primaryDomain,不设置跑起来会报空指针,并且对其源码里面的实现要做一部分变动,后面再详细说明
    @Value("${wx_domain}")
    public String domain;// 支付请求的域名
    @Value("${wx_primaryDomain}")
    private boolean primaryDomain;// 该支付请求域名是否是主域名

    @Override
    protected String getAppID() {
        // TODO Auto-generated method stub
        return appID;
    }
    @Override
    protected String getMchID() {
        // TODO Auto-generated method stub
        return mchID;
    }
    @Override
    protected String getKey() {
        // TODO Auto-generated method stub
        return key;
    }
    public String getDomain() {
        return domain;
    }
    public boolean isPrimaryDomain() {
        return primaryDomain;
    }
    @Override
    protected InputStream getCertStream() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    protected IWXPayDomain getWXPayDomain() {
        return new WXPayDomain();
    }
}



@Autowired
private WXconfig wxconfig;

public DreamResponse unifiedOrder(IPayDTO dto) {
    DreamResponse res = new DreamResponse();
    CommonDTO real = (CommonDTO) dto;
    if (real.getEpcs() == null || real.getEpcs().size() == 0) {
        res.setMsg("标签列表不能为空!");
        res.setStatus(DreamStatus.FAIL);
        return res;
    }
    if (StringUtils.isEmpty(real.getMac())) {
        res.setMsg("设备地址不能为空!");
        res.setStatus(DreamStatus.FAIL);
        return res;
    }
    Map<String, String> resmap = new HashMap<String, String>();

    ===============================================微信支付部分================================================
    try {
        //构建WXPay的时候调用其构造方法,会将微信配置项wxconfig及签名类型sign_type给赋值,默认本地签名类型sign_type为:HMACSHA256
        WXPay wxpay = new WXPay(wxconfig);

        //Map集合用于存储请求参数执行微信支付,下面的value根据自己的业务设定,这里就不加以更改了
        Map<String, String> data = new HashMap<String, String>();
        StockVO stockVo = priceSearchService.getSKUPrice(real);//这里是商品集合
        data.put("body", stockVo.getListStock().get(0).getWarehouseName());
        data.put("out_trade_no", OrderUtil.generateOrderNo());
        data.put("device_info", real.getMac());
        data.put("fee_type", "CNY");

        // 注意:这里去除小数点,total_fee不支持小数点
        Double totalAmt = stockVo.getTotalAmt() * 100;// 订单总金额,单位为分
        String temp = totalAmt.toString();
        temp = temp.substring(0, temp.indexOf('.'));
        data.put("total_fee", temp);

        data.put("spbill_create_ip", wxbean.getSpbill_create_ip());
        data.put("notify_url", wxbean.getNotify_url());
        data.put("trade_type", "NATIVE"); // 此处指定为扫码支付
        // data.put("attach",wxbean.getOpenid());
        data.put("product_id", OrderUtil.generateOrderNo("", 9));// 生成一个id
        data.put("time_start", DateUtils.date2String(new Date(), "yyyyMMddHHmmss"));
        data.put("limit_pay", "no_credit");
        //data.put(WXPayConstants.FIELD_SIGN_TYPE, WXPayConstants.HMACSHA256);
        ========================================微信支付请求参数封装结束=========================================

        Map<String, Object> mapdto = new HashMap<String, Object>();
        mapdto.put("epclist", real.getEpcs());
        mapdto.put("out_trade_no", data.get("out_trade_no"));
        mapdto.put("device_info", real.getMac());
        mapdto.put("payment_type", DreamStatus.WX);
        mapdto.put("stockVo", stockVo);
        // 记录订单流水
        orderService.add(mapdto);

        ========================================发起微信支付===================================================
        logger.info("****微信下订单入参:" + new Gson().toJson(data));
        resmap = wxpay.unifiedOrder(data);
        resmap.put("out_trade_no", data.get("out_trade_no"));
        logger.info("****微信下订单返回结果:" + new Gson().toJson(resmap));
        if (DreamStatus.SUCCESS.equals(resmap.get("return_code"))
                && DreamStatus.SUCCESS.equals(resmap.get("result_code"))) {
            res.setMsg("微信支付下订单成功");
            res.setStatus(DreamStatus.SUCCESS);                 
            ......具体业务实现

        } else {
            res.setExtData(resmap);
            res.setMsg("微信支付下订单失败");
            res.setStatus(DreamStatus.FAIL);
        }
    } catch (Exception e) {
        e.printStackTrace();
        logger.warn("微信支付下订单失败", e);
        res.setExtData(resmap);
        res.setMsg("下单失败");
        res.setStatus(DreamStatus.FAIL);
    }
    return res;
}

以上,domain与primaryDomain是在程序运行起来发现问题后加入的,查看源码发现,必须给定这两个值。
并且在继承WXPayConfig的WxConfig的类中加入重写的方法:(对于getWXPayDomain还得注意修改其实现)
    public String getDomain() {
        return domain;
    }
    public boolean isPrimaryDomain() {
        return primaryDomain;
    }
    @Override
    protected InputStream getCertStream() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    protected IWXPayDomain getWXPayDomain() {
        return new WXPayDomain();
    }


跟入源码发现,wxpay.unifiedOrder(data)底层将我作为构造参数的config内的配置项appid,mchid,key都设置入data了,并且会生成sign存入data,
并且我们无需制定访问路径,调用对应的方法,底层会指定调用的接口。

继续往下走,我们发现这里调用了我们在new WXPay(config)中config.getWXPayDomain().getDomain(config)方法。如果不加以配置就会报空指针。
03.png
继续跟入这个get方法,我们在上面return new WXPayDomain();至于为什么要这样,跟入getDomain(config)研究就可以得到。
04.png
到了这里,具体的原因就不叙述了,看代码体会,我们需要自定义一个类来继承IWXPayDomain重新其getDomain()方法。

public class WXPayDomain implements IWXPayDomain {

  @Override
  public void report(String domain, long elapsedTimeMillis, Exception ex) {
    // TODO Auto-generated method stub
  }

  @Override
  public DomainInfo getDomain(WXPayConfig config) {
    WXconfig wxConfig = (WXconfig)config;
    return new DomainInfo(wxConfig.getDomain(), wxConfig.isPrimaryDomain());
  }
}

至此,微信扫码支付开发就完成了,最后domain这里是个坑,需要看源码自己去体会。

总结:

1. 自定义WXConfig继承WXPayConfig,读取微信配置项
2. 给定支付接口所需的请求参数,用wxpay.unifiedOrder(data)进行支付
3. 对于domain要做一下几个地方的改动。
    1. 配置项的改动:这里为什么这么配通过看源码可以得到
        domain=api.mch.weixin.qq.com 
        primaryDomain=true

    2. WXConfig中读取domain,primaryDomain配置项,并重写getDomain(),isPrimaryDomain(),getWXPayDomain()方法
    3. 自定义类(我这里叫WXPayDomain继承IWXPayDomain),重写getDomain()方法

2. 微信扫码支付成功回调接口开发

与支付宝支付成功后的异步回调一样,需要登录商户平台找到开发配置,设置notify_url回调ip即可。这里要注意的是:微信支付的notify_url只支持域名。
如:http://wwj.com.cn/pay/wxNotify,不像支付宝ip也可以

注意点:
1. 微信支付回调返回的xml数据,并且是以流的方式返回的,因此我们收取数据的时候也必须以流的方式获取:
2. 微信支付返回的xml数据中没有sign_type这一字段,而在本地验签时,我们需要采用HMACSHA256(与下单时一致)进行验签,因此要手动设置到map中去进行验签。
    跟入源码发现,map有sign_type为我们需要采用HMACSHA256时验签类型才为HMACSHA256,否则就是MD5.
    但如果只是这样处理,最后会出现验签失败,之前支付宝支付也说明了验签的原理,是通过私钥与request中的参数进行签名后和原本的签名sign进行对比,
    如今你手动多加了一个字段,那么签名后肯定不同,因此要在源码中,sign_type确定后,将sign_type再重新移除掉即可(这是我想到的做法,可能不是妥当)

微信支付成功的回调代码:
public String notify() {
    Map<String, Object> map = new HashMap<String, Object>();
    Map<String, String> res = new HashMap<String, String>();

    =======================================微信支付回调======================================
    String xml = "";
    String notifyXml = "";
    // 微信支付结果参数
    Map<String, String> paramMap = null;
    WxPayDTO real = null;

    try {
        // 获取微信支付成功后回调POST反馈的信息
        logger.info("微信支付回调获取数据开始...");

        //以流的方式读取Request中的数据
        notifyXml = RequestUtil.getRequestBodyByReader(request);
        logger.debug("微信支付回调参数xml格式:" + notifyXml);

        if (StringUtils.isEmpty(notifyXml)) {
            logger.error("微信支付回调参数xml为空");
        }

        //下面这一段是将获取到的xml数据封装到实体类中,实体类WxPayDTO中对应的字段与官方API支付成功后的响应参数必须一致
        paramMap = WXPayUtil.xmlToMap(notifyXml);
        String paramJson = new Gson().toJson(paramMap);
        logger.info("微信支付回调参数json格式" + paramJson);
        real = new Gson().fromJson(paramJson, WxPayDTO.class);

        WXPay wxpay = new WXPay(wxconfig);
        logger.info("微信通知成功:" + paramJson);

        if (isPay(real.getOut_trade_no()) || null == real) {// 已通知过,无参数无需通知
            res.put("return_code", DreamStatus.SUCCESS);
            res.put("return_msg", "OK");
            xml = WXPayUtil.mapToXml(res);
            return xml;
        }

        
        //指定验签方式与下单时的提交到微信服务端的sign_type一致,为HMACSHA256
        paramMap.put(WXPayConstants.FIELD_SIGN_TYPE, WXPayConstants.HMACSHA256);
        if (wxpay.isPayResultNotifySignatureValid(paramMap)) {
            =========================微信回调基本代码到此结束,下面是具体业务的实现====================

            // 签名正确
            logger.info("微信验签成功:" + new Gson().toJson(paramMap));
            if (DreamStatus.SUCCESS.equals(real.getResult_code())
                    && DreamStatus.SUCCESS.equals(real.getReturn_code())) {
                //更新订单状态
                map.put("out_trade_no", real.getOut_trade_no());
                map.put("status", DreamStatus.SUCCESS);
                map.put("type", MessageType.PAY_OVER);
                orderService.update(map);

                res.put("return_code", DreamStatus.SUCCESS);
                res.put("return_msg", "OK");
            } else {
                res.put("return_code", DreamStatus.FAIL);
                res.put("return_msg", real.getErr_code_des());
            }
        } else {
            // 签名失败
            logger.info("微信验签失败:" + new Gson().toJson(paramMap));
            map.put("out_trade_no", real.getOut_trade_no());
            map.put("status", DreamStatus.FAIL);
            orderService.update(map);
            res.put("return_code", DreamStatus.FAIL);
            res.put("return_msg", "fail");
        }
    } catch (Exception e) {
        e.printStackTrace();
        res.put("return_code", DreamStatus.FAIL);
        res.put("return_msg", "fail");
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        logger.error("微信消息通知异常", e);
    }
    try {
        xml = WXPayUtil.mapToXml(res);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return xml;
}


/**   
 * @Title: getRequestBodyByReader   
 * @Description: TODO(获取微信回调函数参数,返回xml)   
 * @param: @param request
 * @param: @return
 * @param: @throws IOException      
 * @return: String      
 * @throws   
 */ 
public static String getRequestBodyByReader(HttpServletRequest request) throws IOException {
    String tempLine;
    String result = "";
    try {
        if(request != null) {
            while ((tempLine = request.getReader().readLine()) != null) {
                result += tempLine;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw e;
    } finally {
        try {
            if(request.getReader() != null) {
                request.getReader().close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return result;
}


用于封装Request返回参数的实体类,与API响应参数保持一致:
public class WxPayDTO implements IPayDTO, Serializable {
    private String device_info;// 设备号

    private String openid;// 用户标识

    private String appid;// 公众账号ID

    private String mch_id;// 商户号

    private String nonce_str;// 随机字符串

    private String sign;// 签名

    private String sign_type;// 签名类型

    private String result_code;// 业务结果

    private String err_code;// 错误代码

    private String err_code_des;// 错误代码描述

    private String is_subscribe;// 是否关注公众账号

    private String trade_type;// 交易类型

    private String bank_type;// 付款银行

    private int total_fee;// 订单金额

    private int settlement_total_fee;// 应结订单金额

    private int cash_fee;// 现金支付金额

    private String transaction_id;// 微信支付订单号

    private String out_trade_no;// 商户订单号

    private String time_end;// 支付完成时间

    private String return_code;// 返回状态码

    private String return_msg;// 返回信息

    ............
}   


跟入wxpay.isPayResultNotifySignatureValid(paramMap)验签方法,修改isSignatureValid()方法,移除map中的sign_type字段

/**
 * 判断签名是否正确,必须包含sign字段,否则返回false。
 *
 * @param data
 *            Map类型数据
 * @param key
 *            API密钥
 * @param signType
 *            签名方式
 * @return 签名是否正确
 * @throws Exception
 */
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
    if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
        return false;
    }
    String sign = data.get(WXPayConstants.FIELD_SIGN);
    logger.info("原签名为:" + sign + ",本地验签类型为:" + signType);
    //为了保证签名类型是HMACSHA256手动设置了sign_type,为了保证验签通过,移除sign_type
    data.remove(WXPayConstants.FIELD_SIGN_TYPE);
    String generateSignature = generateSignature(data, key, signType);
    logger.info("验签后>>>" + generateSignature);
    return generateSignature.equals(sign);
}

到此,微信支付成功回调接口开发完成,开发途中会遇到很多坑,慢慢解决就好了~~~

相关文章

  • 02_微信扫码支付接口开发

    @Author wangwangjie转载请标明出处~~~ 1. 微信扫码支付快速接入 至此,微信扫码支付开发就完...

  • 一个扫码支付满减活动方案

    背景 某行开发的聚合支付系统,聚合了微信扫码支付、支付宝扫码支付以及本行手机银行扫码支付。通过对本行手机银行扫码支...

  • 第三方支付招代理

    免开户费免开户费 支持行业:棋牌/BC/SSC/理财等、 支付接口:微信wap支付宝,微信扫码、QQ钱包扫码 结算...

  • 探索:CodeIgniter集成微信扫码支付方案

    微信扫码支付API文档下载地址:微信扫码支付API文档下载 1. 集成微信扫码支付的CI目录结构 ├─config...

  • 微信公众号开发之现金红包

    欢迎留言、转发 微信极速开发系列文章:点击这里 前几篇文章介绍了微信支付。 公众号支付、微信扫码支付、刷卡支付、微...

  • 免费第三方支付平台公司

    通道已全面开通啦 微信扫码,支付宝扫码,公众号,支付宝wap, 通道已全面开通啦 微信扫码,支付宝扫码,公众号,支...

  • 前端-获取微信openid

    第一步 登录 微信扫码登录微信开发者平台,扫码地址:微信公众平台 确定网页授权权限已开:点击左侧菜单栏 开发-接口...

  • Laravel中获取真实ip

    起因 最近在开发微信支付,微信扫码付以及微信公众号支付对接都是比较顺利,因为 laravel 中 laravel-...

  • 微信支付sdk调用记录

    微信sdk提供了五种支付场景(公众号支付、app支付、扫码支付、刷卡支付、微信买单)。作为后台开发实际工作中主要接...

  • iOS 高仿微信扫一扫

    日常项目开发中,经常会出现类似扫码加好友、扫码登录或者扫码支付等功能。SWQRCode 高仿微信扫一扫功能,支持二...

网友评论

      本文标题:02_微信扫码支付接口开发

      本文链接:https://www.haomeiwen.com/subject/lmcjuxtx.html