• 学习笔记-微信支付


    微信支付

    • 支付方式

      • 付款码支付
      • JSAPI支付
      • 小程序支付
      • Native支付
        • 商户生产二维码,用户扫一扫支付
      • 刷脸支付
    • 接入

      • 获取商户号
        • https://pay.weixin.qq.com/
        • 提交资料 签署协议 获取商户号
      • 获取api密钥
      • 获取apiv3密钥
      • 申请商户api证书
      • 获取微信平台证书

    项目搭建

    环境准备

    • 依赖
       
        <dependency>
            <groupId>com.github.wechatpay-apiv3groupId>
            <artifactId>wechatpay-apache-httpclientartifactId>
            <version>0.3.0version>
        dependency>
        
        <dependency>
            <groupId>com.google.code.gsongroupId>
            <artifactId>gsonartifactId>
        dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • wxpay.properties
    # 微信支付相关参数
    # 商户号
    wxpay.mch-id=1558950191
    # 商户API证书序列号
    wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F
    
    # 商户私钥文件
    wxpay.private-key-path=apiclient_key.pem
    # APIv3密钥
    wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
    # APPID
    wxpay.appid=wx74862e0dfcf69954
    # 微信服务器地址
    wxpay.domain=https://api.mch.weixin.qq.com
    # 接收结果通知地址
    # 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
    wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io
    
    # APIv2密钥
    wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • WxPayConfig
    
    @Configuration
    @PropertySource("classpath:wxpay.properties") //读取配置文件
    @ConfigurationProperties(prefix="wxpay") //读取wxpay节点
    @Data //使用set方法将wxpay节点中的值填充到当前类的属性中
    @Slf4j
    public class WxPayConfig {
    
        // 商户号
        private String mchId;
    
        // 商户API证书序列号
        private String mchSerialNo;
    
        // 商户私钥文件
        private String privateKeyPath;
    
        // APIv3密钥
        private String apiV3Key;
    
        // APPID
        private String appid;
    
        // 微信服务器地址
        private String domain;
    
        // 接收结果通知地址
        private String notifyDomain;
    
        // APIv2密钥
        private String partnerKey;
    
        /**
         * 获取商户的私钥文件
         * @param filename
         * @return
         */
        private PrivateKey getPrivateKey(String filename){
    
            try {
                return PemUtil.loadPrivateKey(new FileInputStream(filename));
            } catch (FileNotFoundException e) {
                throw new RuntimeException("私钥文件不存在", e);
            }
        }
    
        /**
         * 获取签名验证器
         * @return
         */
        @Bean
        public ScheduledUpdateCertificatesVerifier getVerifier(){
    
            log.info("获取签名验证器");
    
            //获取商户私钥
            PrivateKey privateKey = getPrivateKey(privateKeyPath);
    
            //私钥签名对象
            PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
    
            //身份认证对象
            WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
    
            // 使用定时更新的签名验证器,不需要传入证书
            ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                    wechatPay2Credentials,
                    apiV3Key.getBytes(StandardCharsets.UTF_8));
    
            return verifier;
        }
    
    
        /**
         * 获取http请求对象
         * @param verifier
         * @return
         */
        @Bean(name = "wxPayClient")
        public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
    
            log.info("获取httpClient");
    
            //获取商户私钥
            PrivateKey privateKey = getPrivateKey(privateKeyPath);
    
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(mchId, mchSerialNo, privateKey)
                    .withValidator(new WechatPay2Validator(verifier));
            // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
    
            // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
            CloseableHttpClient httpClient = builder.build();
    
            return httpClient;
        }
    
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    下单

    • 统一下单api
      • native下单api
        • /v3/pay/transactions/native
    // 首先需要生成订单
    log.info("调用统一下单API");
    //调用统一下单API
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
    
    // 请求body参数
    Gson gson = new Gson();
    Map paramsMap = new HashMap();
    paramsMap.put("appid", wxPayConfig.getAppid());
    paramsMap.put("mchid", wxPayConfig.getMchId());
    // 订单描述
    paramsMap.put("description", orderInfo.getTitle());
    // 订单号
    paramsMap.put("out_trade_no", orderInfo.getOrderNo());
    paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
    
    Map amountMap = new HashMap();
    amountMap.put("total", orderInfo.getTotalFee());
    amountMap.put("currency", "CNY");
    
    paramsMap.put("amount", amountMap);
    
    //将参数转换成json字符串
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}" + jsonParams);
    
    StringEntity entity = new StringEntity(jsonParams,"utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 处理响应
      • 获取下单后支付二维码
    String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
    int statusCode = response.getStatusLine().getStatusCode();//响应状态码
    if (statusCode == 200) { //处理成功
        log.info("成功, 返回结果 = " + bodyAsString);
    } else if (statusCode == 204) { //处理成功,无返回Body
        log.info("成功");
    } else {
        log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
        throw new IOException("request failed");
    }
    
    //响应结果
    Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
    //二维码
    codeUrl = resultMap.get("code_url");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 返回
      • 二维码
      • 订单号
    //返回二维码
    Map<String, Object> map = new HashMap<>();
    map.put("codeUrl", codeUrl);
    map.put("orderNo", orderInfo.getOrderNo());
    
    • 1
    • 2
    • 3
    • 4

    支付通知

    • 内网穿透

      • ngrok
    • controller

      • 接收微信通知
     /**
         * 支付通知
         * 微信支付通过支付通知接口将用户支付成功消息通知给商户
         */
        @ApiOperation("支付通知")
        @PostMapping("/native/notify")
        public String nativeNotify(HttpServletRequest request, HttpServletResponse response){
    
            Gson gson = new Gson();
            Map<String, String> map = new HashMap<>();//应答对象
    
            try {
    
                //处理通知参数
                String body = HttpUtils.readData(request);
                Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
                String requestId = (String)bodyMap.get("id");
                log.info("支付通知的id ===> {}", requestId);
                //log.info("支付通知的完整数据 ===> {}", body);
                //int a = 9 / 0;
    
                //签名的验证
                WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                        = new WechatPay2ValidatorForRequest(verifier, requestId, body);
                if(!wechatPay2ValidatorForRequest.validate(request)){
    
                    log.error("通知验签失败");
                    //失败应答
                    response.setStatus(500);
                    map.put("code", "ERROR");
                    map.put("message", "通知验签失败");
                    return gson.toJson(map);
                }
                log.info("通知验签成功");
    
                //处理订单,需要解密
                wxPayService.processOrder(bodyMap);
    
                //应答超时
                //模拟接收微信端的重复通知
                TimeUnit.SECONDS.sleep(5);
    
                //成功应答
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "成功");
                return gson.toJson(map);
    
            } catch (Exception e) {
                e.printStackTrace();
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "失败");
                return gson.toJson(map);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    取消订单

    • /v3/pay/transactions/out-trade-no/%s/close
      //创建远程请求对象
            String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
            url = wxPayConfig.getDomain().concat(url);
            HttpPost httpPost = new HttpPost(url);
    
            //组装json请求体
            Gson gson = new Gson();
            Map<String, String> paramsMap = new HashMap<>();
            paramsMap.put("mchid", wxPayConfig.getMchId());
            String jsonParams = gson.toJson(paramsMap);
            log.info("请求参数 ===> {}", jsonParams);
    
            //将请求参数设置到请求对象中
            StringEntity entity = new StringEntity(jsonParams,"utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");
    
            //完成签名并执行请求
            CloseableHttpResponse response = wxPayClient.execute(httpPost);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    查询微信支付订单

    • /v3/pay/transactions/out-trade-no/%s
    String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
    url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
    
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    
    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功, 返回结果 = " + bodyAsString);
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功");
        } else {
            log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
            throw new IOException("request failed");
        }
    
        return bodyAsString;
    
    } finally {
        response.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    退款

    • /v3/refund/domestic/refunds
      //调用统一下单API
            String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
            HttpPost httpPost = new HttpPost(url);
    
            // 请求body参数
            Gson gson = new Gson();
            Map paramsMap = new HashMap();
            paramsMap.put("out_trade_no", orderNo);//订单编号
            paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
            paramsMap.put("reason",reason);//退款原因
            paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
    
            Map amountMap = new HashMap();
            amountMap.put("refund", refundsInfo.getRefund());//退款金额
            amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
            amountMap.put("currency", "CNY");//退款币种
            paramsMap.put("amount", amountMap);
    
            //将参数转换成json字符串
            String jsonParams = gson.toJson(paramsMap);
            log.info("请求参数 ===> {}" + jsonParams);
    
            StringEntity entity = new StringEntity(jsonParams,"utf-8");
            entity.setContentType("application/json");//设置请求报文格式
            httpPost.setEntity(entity);//将请求报文放入请求对象
            httpPost.setHeader("Accept", "application/json");//设置响应报文格式
    
            //完成签名并执行请求,并完成验签
            CloseableHttpResponse response = wxPayClient.execute(httpPost);
    
            try {
    
                //解析响应结果
                String bodyAsString = EntityUtils.toString(response.getEntity());
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    log.info("成功, 退款返回结果 = " + bodyAsString);
                } else if (statusCode == 204) {
                    log.info("成功");
                } else {
                    throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);
                }
    
            } finally {
                response.close();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    查询退款订单

    • /v3/refund/domestic/refunds/%s
    String url =  String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
    url = wxPayConfig.getDomain().concat(url);
    
    //创建远程Get 请求对象
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    
    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            log.info("成功, 查询退款返回结果 = " + bodyAsString);
        } else if (statusCode == 204) {
            log.info("成功");
        } else {
            throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
        }
    
        return bodyAsString;
    
    } finally {
        response.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    账单查询

    • 账单查询
      • tradebill
        • /v3/bill/tradebill
      • fundflowbill
        • /v3/bill/fundflowbill
            String url = "";
            if("tradebill".equals(type)){
                url =  WxApiType.TRADE_BILLS.getType();
            }else if("fundflowbill".equals(type)){
                url =  WxApiType.FUND_FLOW_BILLS.getType();
            }else{
                throw new RuntimeException("不支持的账单类型");
            }
    
            url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
    
            //创建远程Get 请求对象
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Accept", "application/json");
    
            //使用wxPayClient发送请求得到响应
            CloseableHttpResponse response = wxPayClient.execute(httpGet);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    微软坚持Rust语言重写 Windows 11核心
    云仓是什么?如何选择优质云仓?
    几种简短的数据类型的介绍(主要介绍void)
    XUbuntu22.04之查找进程号pidof、pgrep总结(一百九十)
    IPFS系列 - 答疑
    【MySQL】事务 详解
    人不成熟的特征
    计算机竞赛python区块链实现 - proof of work工作量证明共识算法
    红帽认证笔记2
    数据驱动智能护理:看AI如何塑造医疗领域,为灰暗夕阳带来新的曙光
  • 原文地址:https://blog.csdn.net/weixin0605/article/details/126004789