Java接入微信支付详细教程——提供资料

news2024/9/20 16:47:22

目录

1 前提

2 准备环境

2.1 基本依赖

2.2 准备配置

2.3 测试环境

3 交易类型说明

4 使用

支付下单

支付通知

查询支付

取消支付

申请退款

退款通知

查询退款

5 前端页面

6 前后端联调

6.1 点击支付

6.2 支付通知

6.3 查询支付

6.4 退款


说明:微信支付有两种版本:V3和V2,本文重点讲V3,V2大家有需要的话可以在评论区说出来

V2版接口和V3版接口实际上是基于两种接口标准设计的两套接口。目前大部分接口已升级为V3接口,其余V2接口后续也将逐步升级为V3接口。

1 前提

1.获取商户号

微信商户平台:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式 步骤:申请成为商户 => 提交资料 => 签署协议 => 获取商户号

2.获取AppID

微信公众平台:微信公众平台 步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号

3.申请商户证书

步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥

4.获取微信的证书

可以预先下载,也可以通过编程的方式获取。

5.获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)

步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 设置APIv3密钥

注意:这些资料本文都有提供(资料在最后面)

2 准备环境

项目采用SpringBoot

jdk本文使用11版本

2.1 基本依赖

<!--微信支付-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.9</version>
</dependency>
<!--实体对象工具类-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--工具类-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
<!--web应用-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 准备配置

配置文件:

wxpay:
   # APIv3密钥
   api-v3-key: UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
   # APPID
   appid: wx74862e0dfcf69954
   # 商户ID
   mch-id: 1558950191
   # 商户API证书序列号
   mch-serial-no: 34345964330B66427E0D3D28826C4993C77E631F
   # 接收结果通知地址
   notify-domain: ​http://r2agtr.natappfree.cc​
   # 商户私钥文件路径
   private-key-path: apiclient_key.pem

注意:上面的接收结果通知地址写你自己的其中http://r2agtr.natappfree.cc是我使用内网穿透的地址。

什么是内网穿透?简单来说就是能让所有人都能访问到你,我明天会出一篇详细的笔记来讲内网穿透。

为什么在微信支付中要用到内网穿透?

因为用户付完钱后,微信这边总得通知你一声吧,把支付信息都告诉你,但是因为你的服务在本地,微信访问不到你这里,这时候内网穿透就能解决这个问题,通过映射到本地的端口,比如我在内网穿透工具上映射的是我本机8080的端口,启动的时候它就会生成:一个url,访问这个路径相当于访问了我本机的8080端口
 

 配置私钥文件路径:

把资料中的apiclient_key.pem放在与src同级目录下

 配置类:

@Configuration
@Slf4j
@Data
public class WechatpayConfig {
    // 商户ID
    @Value("${wxpay.mch-id}")
    private String mchId;

    // 商户API证书序列号
    @Value("${wxpay.mch-serial-no}")
    private String mchSerialNo;

    // 商户私钥文件
    @Value("${wxpay.private-key-path}")
    private String privateKeyPath;

    // APIv3密钥
    @Value("${wxpay.api-v3-key}")
    private String apiV3Key;

    // APPID
    @Value("${wxpay.appid}")
    private String appid;

    // 接收结果通知地址
    @Value("${wxpay.notify-domain}")
    private String notifyDomain;



    /**
     * 获取HttpClient对象
     * @return
     */
    @Bean
    public CloseableHttpClient getVerifier(){
        /**
         * 首先,获取证书管理器的实例,用于管理微信支付平台的证书。
         * 然后,向证书管理器增加需要自动更新平台证书的商户信息,包括商户号、商户私钥和API v3密钥。
         * 接着,创建一个私钥签名器,用于对请求进行签名。
         * 然后,创建一个身份认证对象,用于携带商户号和签名信息。
         * 接着,从证书管理器中获取一个验签器,用于对响应进行验签。
         * 然后,创建一个微信支付HTTP客户端构建器,用于设置商户信息和验签器。
         * 最后,通过构建器构造一个HTTP客户端,用于发送请求和接收响应。
         */
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();

        // 向证书管理器增加需要自动更新平台证书的商户信息
        PrivateKey privateKey = null;
        try {
            // 拿到api v3密钥
            privateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKeyPath));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
        // 创建了一个私钥签名器
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        // 身份认证对象(签名)
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        try {
            certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error("定时更新签名验证器错误:{}",e);
            throw new RuntimeException(e);
        }
        // 从证书管理器中获取verifier
        Verifier verifier = null;
        try {
            verifier = certificatesManager.getVerifier(mchId);
        } catch (NotFoundException e) {
            log.error("从证书管理器中获取verifier失败:{}",e);
            throw new RuntimeException(e);
        }
        // 用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo,privateKey )
                .withValidator(new WechatPay2Validator(verifier));

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }
}

2.3 测试环境

@RestController
public class TestController {

    @Resource
    private WechatpayConfig wechatpayConfig;
    @GetMapping("/")
    public String index() {
        System.out.println(wechatpayConfig);
        return "成功";
    }
}

访问:localhost:8080

控制台出现:

 到这里的话环境已经没问题了。

3 交易类型说明

​ 微信支付的交易类型有好几种方式:官方文档

#1. 付款码支付

付款码支付,即日常所说的被扫支付,这是一种纯用于线下场景的支付方式,由用户出示微信客户端内展示的付款二维码,商户使用扫码设备扫码后完成支付

#2. Native原生支付

Native原生支付,即日常所说的扫码支付,商户根据微信支付协议格式生成的二维码,用户通过微信“扫一扫”扫描二维码后即进入付款确认界面,输入密码即完成支付。

#3. JSAPI网页支付

JSAPI网页支付,即日常所说的公众号支付,可在微信公众号、朋友圈、聊天会话中点击页面链接,或者用微信“扫一扫”扫描页面地址二维码在微信中打开商户HTML5页面,在页面内下单完成支付。

#4. App支付

App支付是指商户已有的App,通过对接微信支付API及SDK,实现从商户App发起交易后跳转到微信App,用户完成支付后跳回商户App的场景。

#5. 小程序支付

小程序支付是指在商户既有的小程序内通过对接微信支付API,实现用户在小程序内完成交易的场景。

本次交易类型为:NATIVE-二维码支付

4 使用

支付下单

官方文档:下单文档

/**
     * Native下单
     *  调用统一下单API,生成支付二维码
     */
    @PostMapping("/native")
    public Map<String,String> nativePay() throws Exception {
        log.info("生成订单");
        //生成订单
        log.info("存入数据库...");

        log.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");

        // 订单号
        String orderNo = IdUtil.simpleUUID();
        // 请求body参数 看官方文档
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wechatpayConfig.getAppid());
        paramsMap.put("mchid", wechatpayConfig.getMchId());
        paramsMap.put("description", "iPhone 15 Pro Max 5G");
        paramsMap.put("out_trade_no", orderNo);
        // 回调的地址
        paramsMap.put("notify_url",wechatpayConfig.getNotifyDomain()+"/native/notify");
        Map amountMap = new HashMap();
        //订单总金额,单位为分。
        amountMap.put("total", 1);
        //CNY:人民币,境内商户号仅支持人民币。
        amountMap.put("currency", "CNY");

        paramsMap.put("amount", amountMap);
        //将参数转换成json字符串
        String jsonParams = JSONUtil.toJsonStr(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) { //处理成功,无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " +
                        bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            Map<String, String> resultMap = JSONUtil.toBean(bodyAsString,HashMap.class);
            //二维码
            String codeUrl = resultMap.get("code_url");
            Map<String, String> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderNo.toString());
            return map;
        } finally {
            response.close();
        }
    }

支付通知

支付通知文档

/**
     * 支付通知
     * 微信支付通过支付通知接口将用户支付成功消息通知给商户
     */
    @PostMapping("/native/notify")
    public Map<String,String> nativeNotify(@RequestBody Map<String,Object> signalRes, HttpServletResponse response){
        Map<String, String> map = new HashMap<>();//应答对象
        log.info("支付通知的完整数据 ===> {}", signalRes);
        try {
            //用密文解密出明文
            Map<String,String> resource=(Map<String,String>)signalRes.get("resource");
            String ciphertext=resource.get("ciphertext");
            String associatedData=resource.get("associated_data");
            String nonce=resource.get("nonce");
            // 拿到明文
            String plainText=new AesUtil(wechatpayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
            //转换
            HashMap<String,Object> data= JSONUtil.toBean(plainText,HashMap.class);
            log.info("解密后的完整数据:{}",data);
            //处理订单
            log.info("处理订单...");
            //成功应答:成功应答必须为200或204,否则就是失败应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
        } catch (GeneralSecurityException e) {
            response.setStatus(500);
            map.put("code", "FAIL");
            map.put("message","失败");
        }
        return map;
    }

查询支付

查询支付官方文档

/**
     * 查询订单
     * @param orderNo
     * @return
     * @throws Exception
     */
    @GetMapping("/query/{orderNo}")
    public String queryOrder(@PathVariable String orderNo) throws Exception {
        log.info("查询订单");
        String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/id/%s", orderNo);
        url = url+"?mchid="+wechatpayConfig.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();
        }

    }

取消支付

取消支付官方文档

/**
     * 用户取消订单
     * @param orderNo   订单id
     */
    @PostMapping("/cancel/{orderNo}")
    public String cancel(@PathVariable String orderNo) throws Exception {
        log.info("用户取消订单");
        //创建远程请求对象
        String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close", orderNo);
        HttpPost httpPost = new HttpPost(url);

        //组装json请求体
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid", wechatpayConfig.getMchId());
        String jsonParams = JSONUtil.toJsonStr(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 {
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功200");
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功204");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode);
                throw new IOException("request failed");
            }
            return "订单取消成功";
        } finally {
            response.close();
        }
    }

申请退款

退款文档

/**
     * 申请退款
     * @param orderNo   订单编号
     */
    @PostMapping("/refunds/{orderNo}")
    public Map<String,Object> refunds(@PathVariable String orderNo) throws Exception {

        log.info("创建退款单记录...");
        //根据订单编号创建退款单

        log.info("调用退款API");

        //调用统一下单API
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");

        // 请求body参数
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号

        // 给它一个退款单号
        paramsMap.put("out_refund_no", "tk202412120001");//退款单编号
        paramsMap.put("reason", "买了不发货");//退款原因
        paramsMap.put("notify_url", wechatpayConfig.getNotifyDomain()+"/refunds/notify");//退款通知地址

        Map amountMap = new HashMap();
        amountMap.put("refund", 1);//退款金额
        amountMap.put("total", 1);//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = JSONUtil.toJsonStr(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);
            }
            log.info("更新订单状态......");
            log.info("更新退款单......");
            return JSONUtil.toBean(bodyAsString,Map.class);
        } finally {
            response.close();
        }
    }

退款通知

/**
     * 退款结果通知
     * 退款状态改变后,微信会把相关退款结果发送给商户。
     */
    @PostMapping("/refunds/notify")
    public String refundsNotify(@RequestBody Map<String,Object> signalRes, HttpServletResponse response){
        log.info("退款通知执行");
        Map<String, String> map = new HashMap<>();//应答对象
        try {
            Map<String,String> resource=(Map<String,String>)signalRes.get("resource");
            String ciphertext=resource.get("ciphertext");
            String associatedData=resource.get("associated_data");
            String nonce=resource.get("nonce");
            // 拿到明文
            String plainText=new AesUtil(wechatpayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);

            //转换
            HashMap<String,Object> data= JSONUtil.toBean(plainText,HashMap.class);
            log.info("解密后的完整数据:{}",data);
            log.info("处理退款单................................");
            log.info("更新订单状态................................");

            //成功应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return JSONUtil.toJsonStr(map);
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return JSONUtil.toJsonStr(map);
        }
    }

查询退款

/**
     * 查询退款
     * @param refundNo  退款订单
     */
    @GetMapping("/query-refund/{refundNo}")
    public String queryRefund(@PathVariable String refundNo) throws Exception {

        log.info("查询退款接口调用 ===> {}", refundNo);
        String url =  String.format("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s", refundNo);
        //创建远程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();
        }
    }

5 前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>微信支付</title>
    <script src="/js/jquery.min.js"></script>
    <script src="/js/axios.js"></script>
    <script src="/js/qrcode.min.js"></script>
    <style>
        .container {
            width: 80%;
            margin: 0 auto;
        }

        .btn {
            display: inline-block;
            padding: 10px 20px;
            background-color: #1AAD19;
            color: white;
            border-radius: 5px;
            cursor: pointer;
        }

        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
        }



        .result {
            display: none;
            margin-top: 20px;
            padding: 10px;
            border: 1px solid #ccc;
        }
        .btn-small {
            padding: 5px 10px;
        }
    </style>
</head>
<body>
<div class="container">
    <button class="btn" id="pay">支付</button>
    <button class="btn" id="query">查询支付</button>
    <button class="btn" id="refund">申请退款</button>
    <button class="btn-small" id="cancelOrder">取消订单</button>
    <button class="btn-small" id="queryRefund">查询退款状态</button>
    <div class="qrcode" id="qrcode"></div>
    <div class="result" id="result"></div>
</div>
<script>
    var order_id = "";
    var pay_url = "";

    //二维码
    var qRCode='';
    //  支付
    $("#pay").on("click", function () {
        axios.post("/native")
            .then(function (response) {
                order_id = response.data.orderNo;
                pay_url = response.data.codeUrl;
                //生成二维码放入”Orcode_div“ div
                qRCode = new QRCode(document.getElementById("qrcode"),pay_url);
            })
            .catch(function (error) {
                alert(error.message);
            });
    });

    // 查询订单
    $("#query").on("click", function () {
        if (order_id === "") {
            alert("请先支付");
        } else {
            axios.get("/query/" + order_id)
                .then(function (response) {
                    $("#result").text(response.data)
                })
                .catch(function (error) {
                    alert(error.message);
                });
        }
    });

    // 取消订单
    $("#cancelOrder").on("click", function () {
        // 取消订单
        if (order_id === "") {
            alert("请先支付");
        } else {
            axios.post("/cancel/" + order_id)
                .then(function (response) {
                    //清除二维码
                    document.getElementById("qrcode").innerHTML = '';
                    // 处理取消订单成功的逻辑
                    alert(response.data);
                })
                .catch(function (error) {
                    // 处理取消订单失败的逻辑
                    alert(error.message);
                });
        }
    });

    // 查询退款
    $("#queryRefund").on("click", function () {
        // 查询退款状态
        if (order_id === "") {
            alert("请先支付");
        } else {
            // 假设这里使用退款单号作为参数,您可以根据实际情况调整
            axios.get("/query-refund/" + order_id)
                .then(function (response) {
                    // 处理查询退款状态的逻辑
                    alert(response.data);
                })
                .catch(function (error) {
                    // 处理查询退款状态失败的逻辑
                    alert(error.message);
                });
        }
    });

    // 退款
    $("#refund").on("click", function () {
        if (order_id === "") {
            alert("请先支付");
        } else {
            axios.post("/refunds/" + order_id)
                .then(function (response) {
                    $("#result").text(response.data)
                })
                .catch(function (error) {
                    alert(error.message);
                });
        }
    });
</script>
</body>
</html>

6 前后端联调

6.1 点击支付

控制台

6.2 支付通知

返回:

{
  "transaction_id": "4200002111202312128239200217",
  "amount": {
    "total": 1,
    "payer_total": 1,
    "currency": "CNY",
    "payer_currency": "CNY"
  },
  "mchid": 1558950191,
  "out_trade_no": 202412120001,
  "trade_state": "SUCCESS",
  "bank_type": "OTHERS",
  "appid": "wx74862e0dfcf69954",
  "trade_state_desc": "支付成功",
  "trade_type": "NATIVE",
  "attach": null,
  "success_time": "2023-12-12T21:36:09+08:00",
  "payer": {
    "openid": "oHwsHuKAElSTZ37f5F-Zv4Jg5eIo"
  }
}

6.3 查询支付

返回:

{
  "amount": {
    "currency": "CNY",
    "payer_currency": "CNY",
    "payer_total": 1,
    "total": 1
  },
  "appid": "wx74862e0dfcf69954",
  "attach": "",
  "bank_type": "OTHERS",
  "mchid": "1558950191",
  "out_trade_no": "202412120001",
  "payer": {
    "openid": "oHwsHuKAElSTZ37f5F-Zv4Jg5eIo"
  },
  "promotion_detail": [],
  "success_time": "2023-12-12T21:36:09+08:00",
  "trade_state": "SUCCESS",
  "trade_state_desc": "支付成功",
  "trade_type": "NATIVE",
  "transaction_id": "4200002111202312128239200217"
}

6.4 退款

返回:
 

{
  "amount": {
    "currency": "CNY",
    "discount_refund": 0,
    "from": [],
    "payer_refund": 1,
    "payer_total": 1,
    "refund": 1,
    "refund_fee": 0,
    "settlement_refund": 1,
    "settlement_total": 1,
    "total": 1
  },
  "channel": "ORIGINAL",
  "create_time": "2023-12-12T21:51:55+08:00",
  "funds_account": "UNAVAILABLE",
  "out_refund_no": "tk202412120001",
  "out_trade_no": "202412120001",
  "promotion_detail": [],
  "refund_id": "50310208212023121204787113276",
  "status": "PROCESSING",
  "transaction_id": "4200002111202312128239200217",
  "user_received_account": "支付用户零钱"
}

apiclient_key.pem文件的内容:

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDnSAKI8sea8p+d
OBVPWlZmxqJfPbdhzZxdI5Kx1j5SJNZwXWtr43/giw38pwzSlBI+bubBcYlkFTI0
guigMZO/yueb1mZChaY/JG1vsT02Ubj0xkVvBwKNbYS48NEpZhK61Mia09R4n1iH
1vip9kt8J6Zrx+xIqwmuCNWigyivGrvY9AdevCNlNSVdHVOZUJiJ6UGtvVmgZb0u
RTwBzfkjnwTgEcsrZMmF15nFubFsyJLyF/zY4NhrISc8H/rbjgleqa8ybYL26iTS
gfPCXe4U9f8fNFF2bSA06GTiB2R93q2B0zHeUYrpgF4XOGlIAqH+Ea4Vn+aOj6I0
pduh03idAgMBAAECggEBAJ+4SB/hYd1szrPZhkXtwhtp87pIObtuLhzYMzdjGFjM
HdctfMDeNHKSNU+U4bMPFOZO2kcfLF2Ukb5X5WSzuDBMZNRnJOmtuJiEhJsM0JQR
reREhLDfK3EWAAFkNV4corSpu/vIbEP87zuoRsPBVnHgQ/rM7y1kCORKL5bycwcw
5BI4xhULKAu14LEcDL3+xDJo39w+WCFlxuP+6Bs7+vIeavs+AC3TJkA4kg2nyWd3
W07xPjHl64f17icqsFhuFZ+VuSf5CAgQGWDbC7BHqRkDStUDSiiUiFushouKCLdK
MpA0x4ogb2ZwfZDRhZHiLNAGe4QovYCcXWBydzuT0WECgYEA828Bo1JAHE5kdnsO
E9+enH/yMcOKTRnuYPiXsFXNvqofc5tZiXJmVE/+EKv7LFmtUA6qqKC7FDek8TpP
SkfXmSDAgfM6AdzT0YoHH23FRVewnFMEYumtogXsXJTyI5siBSJp16s9Rn/YwESt
JqjW5+9Ck1dkU+UJCZ4lOw4HeGkCgYEA8zho2BKQTh3P/xcFcoTcunVZpRayVkHM
g8Ef6RGGo4vM1oshQLvXyPqCmhAIf6j71I9WPqUwjmeGyaR7Hir0dbgTCm2fJPFW
lxAvgbCISxEPz10RYBcR2umMSlJLfZfhqv1CyfU4vfCTbdOimgsz2039E3oLTbzg
eDe/mdzu2BUCgYEAleKjf4wFLWiXMtxRrqrhXjrpRPrBDPgKbmqh+1DZfawB8YyV
dKublg4qwNkjrgsJS2G8cleE2M3qIR1l9LaHaSFhZqH79WmigkIaYJ+V9zwm4hm7
eaun3TsIbXjIHmRGbiLiSIiHEgFl0/x1IHiU2fnXZCFLBNzg06ssAVCCCQECgYA1
4BfxTONkOlxZgAr33BBcySPLWuS0EK0xvjTIVtaBIbWFDJqYEUPyQ/NsFwMa7B6k
bf/HrqW71ZjYz7Np8k/mR5kIJVIsR71Lhw1O6AC4yBW9dDsmEtYkrLkjuWj5cAxP
6PvDaqtf/4tYt5l8D+Ezwem+R7l7RcxfNNIfTf4mJQKBgE57dnRx+Ijx7VHjJvjl
X2jB/VSVGpK5OADykmmZ/wvHPlQcyzd+5kAIoJhSuY48CFeI1DOogR2p01LEFQEL
j4AI5FqOOQwRJvNmfoKcKwO36tSxSEGSM8POKOsa21PG/gvDpJjVFo2hn5QcMHWn
z5SjsgA/1YbXejubdLxT/3pl
-----END PRIVATE KEY-----

到这里就结束,制作不宜,喜欢博主的可以点点关注,每日分享编程知识!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1305212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【性能测试】Jmeter 配置元件(一):计数器

Jmeter 配置元件&#xff08;一&#xff09;&#xff1a;计数器 在 Jmeter 中&#xff0c;通过函数 ${__counter(,)} 可以实现每次加 1 1 1 的计数效果。但如果步长不为 1 1 1&#xff0c;则要利用到我们的计数器。 函数作用${__counter(,)}计数器&#xff0c;每次加 1${__d…

jmeter不精通?来看这套很全的jmeter教程,ant批量执行Jmeter脚本

JDK&#xff0c;Jmeter默认已经装了ANT下载&#xff1a;Apache Ant - Binary Distributions ant环境变量需要配置 ant_home&#xff0c;你解压之后的地址 然后PATH环境变量里加上bin目录&#xff0c;D:\work\apache_ant\bin 最后打开命令行窗口&#xff0c;输入 ant -v&…

湖南大学-电路与电子学-2021期末A卷★(不含解析)

【写在前面】 电路与电子学好像是从2020级开设的课程&#xff0c;故实际上目前只有2020与2021两个年级考过期末考试。 本份卷子的参考性很高&#xff0c;这是2020级的期末考卷。题目都是很典型的&#xff0c;每一道题都值得仔细研究透。 特别注意&#xff1a;看得懂答案跟写得…

10天玩转Python第3天:python循环语句和字符串、列表全面详解与代码示例

目录 1 循环1.1 for 循环1.2 break 和 continue 2 容器3 字符串3.1 定义3.2 下标3.3 切片3.4 字符串的查找方法 find3.5 字符串的替换方法 replace3.6 字符串的拆分 split3.7 字符串的链接 join 4 列表4.1 定义4.1 列表支持下标和切片, 长度4.3 查找 - 查找列表中数据下标的方法…

C语言 二叉树详解(自我理解版)!!!二叉树的实现

目录 1.树的概念和结构&#xff08;了解&#xff09; 1.1树的概念 1.2关于树的每个组成结构的叫法 1.3树的结构体表示 1.4树在实际中的运用 2.二叉树的概念和结构和实现&#xff08;本期偏重点之一&#xff09; 二叉树的概念 ​编辑 特殊的二叉树 1.完全二叉树 2.满二…

MIT18.06线性代数 笔记2

文章目录 正交向量与子空间子空间投影投影矩阵和最小二乘正交矩阵和Gram-Schmidt正交化行列式及其性质行列式公式和代数余子式克拉默法则、逆矩阵、体积特征值和特征向量对角化和A的幂微分方程和exp(At)马尔科夫矩阵 傅里叶级数复习二 正交向量与子空间 向量正交&#xff1a;x…

Python中的并发编程(1)并发相关概念

并发和并行 并发和并行 并发指逻辑上同时处理多件事情&#xff0c;并行指实际上同时做多件事情。 并发不一定通过并行实现&#xff0c;也可以通过多任务实现。例如&#xff1a;现代操作系统都可以同时执行多个任务&#xff0c;比如同时听歌和玩游戏&#xff0c;但歌曲播放和游…

【模块化】 js 模块化(CommonJS, AMD, UMD, CMD, ES6)

目录 js 的演变模块化1. CommonJS 规范commonJs伪代码⭐CommonJS优缺点 2. AMD 规范⭐AMD 优缺点 3. UMD 规范⭐UMD AMD CommonJS 4. CMD 规范⭐CMD 优缺点 5. ES6 模块化符号绑定⭐ESM 优缺点 AMD 和 CMD 的区别ES6 模块与 CommonJS 模块的差异参考 将介绍几种 js 模块化的规…

Mirrors and reflections for VR

专为虚拟现实而建,但也非常适合非虚拟现实桌面和移动项目 这是URP管道,从Unity2019.4.16一直测试到2023年。 完全工作场景预览,轻松修改着色器材质。着色器支持折射,可以制作很酷的效果。 镜子/反射可以互相反射,而不仅仅是2...想象一下一个电梯,3面镜子都互相反射,直到…

【清晰明了】Jenkins邮件发送配置

自带邮件插件 首先要知道的是jenkins是自带邮件插件的&#xff0c;且不支持卸载。 下面开始配置自带邮件插件。 配置默认邮件管理员 系统管理 --> 系统配置&#xff0c;进行如下配置&#xff1a; 不配置管理员邮件地址报错如下 jakarta.mail.internet.AddressException:…

论文阅读二——基于全脸外观的凝视估计

论文阅读二——基于全脸外观的凝视估计 基础知识主要内容文章中需要学习的架构AlexNet 代码复现 该论文是2017年在CVPR中发表的一篇关于 “gaze estimation” 的文章&#xff0c;其论文地址与代码地址如下&#xff1a; 论文地址 代码地址 论文特点&#xff1a;文章提出了一种…

Java进阶 1-1 枚举

目录 枚举的基本特性 枚举类型中的自定义方法 switch语句中的枚举 编译器创建的values()方法 使用实现代替继承 构建工具&#xff1a;生成随机的枚举 组织枚举 EnumSet EnumMap 本笔记参考自&#xff1a; 《On Java 中文版》 枚举类型通过enum关键字定义&#xff0c;其…

【Spring进阶系列丨第五篇】详解Spring中的依赖注入

文章目录 一、说明二、构造函数注入2.1、方式一【index索引方式】2.1.1、定义Bean2.1.2、主配置文件中配置Bean2.1.3、测试 2.2、方式二【indextype组合方式】2.2.1、定义Bean2.2.2、主配置文件配置Bean2.2.3、测试2.2.4、解决方案 2.3、方式三【name方式】2.3.1、定义Bean2.3.…

5. PyTorch——数据处理模块

1.数据加载 在PyTorch中&#xff0c;数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset类&#xff0c;实现自定义的数据集需要继承Dataset&#xff0c;并实现两个Python魔法方法&#xff1a; __getitem__&#xff1a;返回一条数据&#xff0c;或一个样本。obj[in…

unity Mesh Simplify 1.10(模型优化工具:查看面数,降低面数灯)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、面板参数详解说明二、使用方法总结 前言 有时候想对模型优化一下&#xff0c;奈何又不会建模方面的。虽然我感觉它的数值不大对&#xff0c;但是不影响我们优化顶点数嘛。 Me…

LeetCode 1631. 最小体力消耗路径:广度优先搜索BFS

【LetMeFly】1631.最小体力消耗路径&#xff1a;广度优先搜索BFS 力扣题目链接&#xff1a;https://leetcode.cn/problems/path-with-minimum-effort/ 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights &#xff0c;其中 heights[row][col] 表示格子 (ro…

【玩转TableAgent数据智能分析】TableAgent全功能详解及多领域数据分析实践(中)不同领域数据分析实践

3 电影点评数据分析实践 利用本身自带的电影点评数据&#xff0c;来具体看一下TableAgent的分析能力&#xff0c;选择电影点评数据&#xff0c;智能体会自动导入该数据DMSC20000.csv&#xff0c;大小为3.3 MB。在数据信息展示区&#xff0c;就会显示出该数据&#xff0c;并提供…

你一定要知道的Fiddler过滤器 Filters 详解

如果要对当前Fiddler的抓包进行过滤&#xff08;如过滤掉与测试项目无关的抓包请求&#xff09;&#xff0c;那功能强大的 Filters 过滤器能帮到你。 进入 Filters 选项页&#xff0c;勾选上 Use Filters&#xff0c;即启用过滤器。 Actions 四个选项说明&#xff1a; Run Fi…

SI24R03 高度集成低功耗SOC 2.4G 收发一体芯片

今天给大家介绍一款Soc 2.4G 收发一体模块-SI24R03 Si24R03是一款高度集成的低功耗无线SOC芯片&#xff0c;芯片为QFN32 5x5mm封装&#xff0c;集成了资源丰富的MCU内核与2.4G收发器模块&#xff0c;最低功耗可达1.6uA&#xff0c;极少外围器件&#xff0c;大幅降低系统应用成本…

(第8天)保姆级 PL/SQL Developer 安装与配置

PL/SQL Developer 安装与配置(第8天) 咱们前面分享了很多 Oracle 数据库的安装,但是还没有正式使用过 Oracle 数据库,怎么连接 Oracle 数据库?今天就来讲讲我学习中比较常用的 Oracle 数据库连接工具:PL/SQL DEVELOPER。 PL/SQL Developer 的安装和配置对于新手来说还是…