【微信支付】微信支付之 Native 支付

  • Post author:
  • Post category:其他




概述

Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。详情见

开发文档



前期准备

  1. 注册微信支付商户号,获取商户号 mch_id、key;
  2. 申请微信认证的服务号、政府或媒体类订阅号、小程序、APP、企业微信其中之一,并与商户号绑定。获取 appid。



开发步骤

Native支付可分为两种模式,商户根据支付场景选择相应模式。本文仅介绍模式二。



业务流程时序图

原生支付模式二时序图

业务流程说明:

(1)商户后台系统根据用户选购的商品生成订单。

(2)用户确认支付后调用微信支付【

统一下单API

】生成预支付交易;

(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

(4)商户后台系统根据返回的code_url生成二维码。

(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

(8)微信支付系统根据用户授权完成支付交易。

(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

(11)未收到支付通知的情况,商户后台系统调用【

查询订单API

】。

(12)商户确认订单已支付后给用户发货。



统一下单



SDK

API 详见

统一下单API



下载官方 SDK ,或通过 maven 获取依赖。推荐使用后者。

<!-- wxpay-sdk -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

SDK 中集成了一个 WXPayConfig.java 的接口,我们需要写一个实现类,存放配置信息。

public class WXPayConfigImpl implements WXPayConfig {
    // 设置证书,没有的话注释掉
    // private byte[] certData;
    // public WXPayConfigImpl() throws Exception {
    //    String certPath = "/path/to/apiclient_cert.p12";
    //    File file = new File(certPath);
    //    InputStream certStream = new FileInputStream(file);
    //    this.certData = new byte[(int) file.length()];
    //    certStream.read(this.certData);
    //    certStream.close();
    // }
    // set appid
    public static void setAppId(String appId) {
        WXPayConfigImpl.appId = "";
    }
    // set mch_id
    public static void setMchId(String mchId) {
        WXPayConfigImpl.mchId = "";
    }
    // set key
    public static void setKey(String key) {
        WXPayConfigImpl.key = "";
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }
    // 异步通知地址
    public String getNotifyUrl() {
        return "http://www.xxx.com/wxNotify";
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

//    IWXPayDomain getWXPayDomain() {
//        return null;
//    }
}



获取 code_url

核心代码如下:

public class WXPayService {
    /**
     * 微信扫码下单 native
     * @param outTradeNo
     * @param totalFee
     * @param body
     * @param productId
     * @param attach 自定义参数,通知中原样返回。
     * @return
     * @throws Exception
     */
    public static String wxUnifiedOrder(String outTradeNo, 
                                        String totalFee, 
                                        String body,
                                        String productId, 
                                        String attach) throws Exception {
        WXPayConfigImpl config = null;
        WXPay wxpay = null;
        config = new WXPayConfigImpl();
        // 异步通知地址
        String notifyUrl = config.getNotifyUrl();
        // 使用沙箱环境
        // wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true);
        // 默认使用MD5,不使用沙箱环境
        wxpay = new WXPay(config); 
        Map<String, String> data = new HashMap<String, String>();
        // 商品描述
        data.put("body", body); 
        // 商户订单号
        data.put("out_trade_no", outTradeNo); 
        // 标价金额
        data.put("total_fee", totalFee); 
        // 产品id
        data.put("product_id", productId);
        // 终端IP:调用微信支付API的机器IP
        data.put("spbill_create_ip", "221.12.4.52"); 
        // 交易类型:此处指定为扫码支付
        data.put("trade_type", "NATIVE"); 
        // 异步通知 url
        data.put("notify_url", notifyUrl);
        // 自定义参数
        data.put("attach", attach);
        Map<String, String> resp = null;
        try {
            resp = wxpay.unifiedOrder(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        String codeUrl = resp.get("code_url");
        System.out.println("============= 微信返回结果 =============");
        System.out.println(resp);
        return codeUrl;
    }
}

将获取得到的 codeUrl 通过第三方库转为二维码,供用户扫码。



异步通知


异步通知

的请求路径在统一下单接口中设置。

@RequestMapping("wxNotify")
public class WXController {
    public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 拿到微信回调信息
        InputStream inputStream = request.getInputStream();
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        StringBuffer sb = new StringBuffer();
        // 将微信回调信息转为字符串
        String line;
        while ((line = in.readLine()) != null) {
            sb.append(line);
        }
        in.close();
        inputStream.close();
        String strXml = sb.toString();
        WXPayConfigImpl config = new WXPayConfigImpl();
        Map<String, String> map = WXPayUtil.xmlToMap(strXml);
        System.out.println(strXml);

        // 获取业务信息
        String outTradeNo = map.get("out_trade_no");
        String totalFee = map.get("total_fee");
        String appId = map.get("appid");
        String mchId = map.get("mch_id");
        String transactionId = map.get("transaction_id");
        String resultCode = map.get("result_code");
        String attach = map.get("attach");
        
        // 验签
        boolean signatureValid = WXPayUtil.isSignatureValid(strXml, config.getKey());
        // todo 这里写代码
        /* 实际验证过程建议商户务必添加以下校验:
            1、需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
            2、判断 total_fee 是否确实为该订单的实际金额(即商户订单创建时的金额)
        */
        PrintWriter writer = response.getWriter();
        // 判断签名是否正确
        if (signatureValid) {
            // 判断回调信息是否成功
            if ("SUCCESS".equals(map.get("result_code"))) {
                // todo 根据不同业务类型处理不同业务
                
                // 通知微信订单处理成功
                String noticeStr = setXML("SUCCESS", "");
                writer.write(noticeStr);
                writer.flush();
            }
        } else {
            // 通知微信订单处理失败
            String noticeStr = setXML("FAIL", "");
            writer.write(noticeStr);
            writer.flush();
        }
    }

    private static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }
}



沙箱测试

编写完代码,我们就要开始沙箱测试了。

微信的流程是,写完代码之后,必须使用沙箱测试,对测试结果进行验收,验收通过才可以使用正式的微信商户号,否则会返回“商户无此接口权限”。

沙箱验收过程可查看

支付验收指引

  1. 使用正式的 mch_id,商户key 来获取沙箱密钥 sandbox_signkey。

    传送门


    沙箱密钥获取

  2. 通过调用接口

    https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey

    ,获取验签密钥。请求方式为 POST,发送 json 字符串。可以使用 Postman 调用此接口,获取验签密钥。

    通过Postman获取验签密钥

    返回的结果如下

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <retmsg><![CDATA[ALSDFJASLDFKJASLDFKJASDLFJ]]></retmsg>
  <retcode><![CDATA[0]]></retcode>
</xml>
  1. 使用沙箱环境
// 使用沙箱环境
wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true);
  1. 测试结果

    • 微信的沙箱测试

      不会

      发送异步通知,等待5s左右后,会在控制台打印出模拟异步通知的结果。若无通知,调用

      查询订单

      接口进行查询。
    • 必须按照微信的

      支付验收用例

      来设置金额,否则会返回错误信息,“金额不合法”。
    • 各项支付验收用例测试结束后,按照要求调用【

      查询订单

      】进行查询,查询结束后验收结束。
    • 在官方微信号查询验收结果。验收通过后,即可使用正式账号。



查询订单

controller 层

public class WXController {
    public void orderQuery(String key, String appid, String mchId, String outTradeNo, String transactionId) throws Exception {
        WXPayConfigImpl.setKey(key);
        WXPayConfigImpl.setAppId(appid);
        WXPayConfigImpl.setMchId(mchId);
        WXPayConfigImpl config = new WXPayConfigImpl();
        
        Map<String, String> map = WXPayService.orderQuery(outTradeNo, transactionId);
        String attach = map.get("attach");
        String appId = map.get("appid");
        String totalFee = map.get("total_fee");
        String strXml = WXPayUtil.mapToXml(map);
        String resultCode = map.get("result_code");
        transactionId = map.get("transaction_id");
        System.out.println("strXml: " + strXml);
    }
}

service 层

    /**
     * 微信订单查询
     *
     * @param outTradeNo
     * @param transactionId
     * @return
     * @throws Exception
     */
    public static Map<String, String> orderQuery(String outTradeNo, String transactionId) throws Exception {
        WXPayConfigImpl config = null;
        WXPay wxpay = null;
        config = new WXPayConfigImpl();
        // wxpay = new WXPay(config, WXPayConstants.SignType.MD5, true); // 使用沙箱
        wxpay = new WXPay(config);// 默认使用MD5,不使用沙箱环境

        Map<String, String> data = new HashMap<String, String>();
        if (!StringUtils.isEmpty(outTradeNo)) {
            data.put("out_trade_no", outTradeNo);
        }
        if (!StringUtils.isEmpty(transactionId)) {
            data.put("transaction_id", transactionId);
        }
        return wxpay.orderQuery(data);
    }



遇到的问题

  1. appid 与商户号绑定:


    问题描述

    :调用微信统一下单接口时,返回失败。错误信息为:appid 与 mch_id 不匹配。


    原因分析

    :appid 与 mch_id 不匹配,在 mch_id 没错的情况下,自然是 appid 出了问题。如果 appid 不合法,或者没有与 mch_id 绑定,就会报这个错。在微信商户账号界面绑定一个 appid 到 mch_id 下。


    解决方法

    :在微信商户账号界面绑定一个 appid 到 mch_id 下。

    “申请微信认证的服务号、政府或媒体类订阅号、小程序、APP、企业微信其中之一,并与商户号绑定。”
  2. 商户订单号重复:


    问题描述

    :在微信支付页面,用户获取了二维码之后并没有付款,或是关闭了页面,或是暂时不想支付等原因,这很常见。此时再次对同一订单进行微信支付,统一下单接口可能会报“INVALID_REQUEST 商户订单号重复”的错误。


    原因分析

    :同一订单号调用微信的统一下单接口时,若其他参数与首次不同,则微信认为是两个订单。两个订单使用了同一个订单号,这是不被允许的。


    解决方法

    :订单重入时,要求参数值与原请求一致,请确认参数问题。包括 attach 这种商户自定义参数,都必须一致。

[end]



版权声明:本文为JSlingzhi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。