SpringBoot | SpringBoot中实现“微信支付“
作者:mmseoamin日期:2024-01-19

SpringBoot中实现"微信支付":

    • 1.“微信支付”产品
    • 2."微信支付"接入流程
    • 3.“微信小程序支付”时序图:
      • 3.1 “商家端JSAPI下单” 接口
      • 3.2 “微信小程序端调起支付” 接口
      • 4.“微信小程序支付”流程-详细讲解
      • 5.微信小程序支付准备工作:
        • 5.1 获得微信支付平台证书、商户私钥文件
        • 5.2 获取临时域名 (内网穿透) :
          • ①下载且安装软件 : cpolar
          • ②获得 “Authtoken” 且配置 cpolar ,生成“内网穿透”工具配置文件
          • ③启动服务,临时获取到一个IP地址 (临时域名)
          • 6.“微信小程序支付”代码:
            • OrderControlle.java (订单Controller)
            • OrderService.java
            • OrderServiceImpl.java (包含 : 微信支付工具类)
            • OrderMapper.java
            • OrderMapper.xml
            • UserMapper.java
            • PayNotifyController.java / 支付回调相关接口
            • application.yml (springboot配置文件)
            • application-dev.xml
            • 微信支付工具类

              1.“微信支付”产品

              微信支付提供了多种产品,即 微信支付多种支付的形式。如:

              • 付款码支付:打开微信展示“微信支付”二维码页面,让商家去扫。

              • JSAPI支付:一般用于在H5页面进行微信支付。 JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。

              • 小程序支付:在微信小程序中调用“微信支付”功能。

              • Native支付:商家提供一个二维码,我们用微信扫一扫功能来进行支付。

              • APP支付:在手机应用中调起微信支付。

              • 刷脸支付:即刷脸完成付款。

              • “微信支付”产品详细介绍

              • 微信支付产品:

                SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第1张

              2."微信支付"接入流程

              • SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第2张
              • SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第3张
              • SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第4张

              3.“微信小程序支付”时序图:

              • 微信小程序支付时序图:

                SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第5张

              • 微信小程序支付 主要内容为以下三个部分:

                • “商家端JSAPI下单” 接口 / 微信下单接口 / 预下单接口
                • “微信小程序端调起支付” 接口 / 调起微信支付
                • 推送支付结果 / notify_url回调请求

              3.1 “商家端JSAPI下单” 接口

              • 商户系统 调用 (微信小程序支付中的) JSAPI接口 /JSAPI预下单接口微信支付服务后台 生成 预支付交易单(即: "prepay_id ) ,同时将该预支付交易单,响应给商户系统

              • “商家端JSAPI下单” 接口-详解

              • 商家端通过 访问 https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi 接口来生成预支付交易单

                商家端JSAPI下单 / 生成预支付交易单 要访问的接口

              • 请求示例

                {
                "mchid": "1900006XXX",
                "out_trade_no": "1217752501201407033233368318",
                "appid": "wxdace645e0bc2cXXX",
                "description": "Image形象店-深圳腾大-QQ公仔",
                "notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
                "amount": {
                	"total": 1,
                	"currency": "CNY"
                },
                "payer": {
                	"openid": "o4GgauInH_RCEdvrrNGrntXDuXXX"
                	}
                }
                
                • 返回示例 (正常示例)
                  {
                  	"prepay_id": "wx26112221580621e9b071c00d9e093b0000"
                  }                             
                  
                  • 适用对象: 直连商户

                    请求URL:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

                    请求方式:POST

                  • 请求参数

                    请求参数-详解

                    参数名变量类型[长度限制]必填描述
                    应用IDappidstring[1,32]body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID 示例值:wxd678efh567hg6787
                    直连商户号mchidstring[1,32]body 直连商户的商户号,由微信支付生成并下发。 示例值:1230000109
                    商品描述descriptionstring[1,127]body 商品描述 示例值:Image形象店-深圳腾大-QQ公仔
                    通知地址notify_urlstring[1,256]body异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http 示例值:https://www.weixin.qq.com/wxpay/pay.php
                    商户订单号out_trade_nostring[6,32]body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 示例值:1217752501201407033233368018

                    SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第6张

                    订单金额amountobjectbody 订单金额信息
                    参数名变量类型[长度限制]必填描述
                    总金额totalint订单总金额,单位为分。 示例值:100
                    货币类型currencystring[1,16]CNY:人民币,境内商户号仅支持人民币。 示例值:CNY

                    SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第7张

                    支付者payerobjectbody 支付者信息
                    参数名变量类型[长度限制]必填描述
                    用户标识openidstring[1,128]用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid,Openid获取详见 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o

                    返回参数

                    参数名变量类型[长度限制]必填描述
                    预支付交易会话标识prepay_idstring[1,64]预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 示例值:wx201410272009395522657a690389285100

              3.2 “微信小程序端调起支付” 接口

              • 通过JSAPI下单接口获取到发起支付的必要参数:prepay_id,然后调用微信支付平台提供的 “微信小程序端调起支付” 接口 / wx.requestPayment(OBJECT) 方法完成微信小程序支付。

                “微信小程序端调起支付” 接口-详解

              • 微信小程序通过调用wx.requestPayment(OBJECT) 发起微信支付

              • 请求示例

                wx.requestPayment
                (
                	{
                		"timeStamp": "1414561699",
                		"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
                		"package": "prepay_id=wx201410272009395522657a690389285100",
                		"signType": "RSA",
                		"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
                		"success":function(res){},
                		"fail":function(res){},
                		"complete":function(res){}
                	}
                )
                
              • 适用对象: 直连商户

              • 接口定义

                此API无后台接口交互,需要将列表中的数据签名。

                参数名变量类型[长度限制]必填描述
                小程序IDappIdstring[1,32]商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看 示例值:wx8888888888888888
                时间戳timeStampstring[1,32]时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 示例值:1414561699
                随机字符串nonceStrstring[1,32]随机字符串,不长于32位。推荐随机数生成算法。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS
                订单详情扩展字符串packagestring[1,128]小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=
                wx201410272009395522657a690389285100
                签名方式signTypestring[1,32]签名类型,默认为RSA,仅支持RSA。 示例值:RSA
                签名paySignstring[1,512]签名,使用字段appId、timeStamp、nonceStr、
                package计算得出的签名值
                oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRA
                Z/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZ
                vI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4
                WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17
                D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYK
                UR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLm
                R9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDm
                XxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcun
                Xt8cqNjKNqZLhLw4jq/xDg==
              • 调用wx.requestPayment(OBJECT)发起微信支付

                接口名称: wx.requestPayment

                Object 参数说明

                ( 这些需要用到的参数全都是后端计算好,返回给微信小程序,然后小程序直接使用这些参数来调用方法来就会弹出“微信支付”的窗口,来完成微信支付。 )

                参数名变量类型[长度限制]必填描述
                时间戳timeStampstring[1,32]当前的时间,其他详见时间戳规则。 示例值:1414561699
                随机字符串nonceStrstring[1,32]随机字符串,不长于32位。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS
                订单详情扩展字符串packagestring[1,128]小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=wx201410272009395522657a690389285100
                签名方式signTypestring[1,32]签名类型,默认为RSA,仅支持RSA。 示例值:RSA
                签名paySignstring[1,512]签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 示例值:oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==
                • 回调结果
                  回调类型errMsg说明
                  successrequestPayment:ok调用支付成功
                  failrequestPayment:fail cancel用户取消支付
                  failrequestPayment:fail (detail message)调用支付失败,其中 detail message 为后台返回的详细失败原因

              4.“微信小程序支付”流程-详细讲解

              微信小程序支付”流程-详细讲解 :

              • 小程序支付页面,点击 “确认支付” 按钮,发送一个 “支付请求”“商户系统”
              • 商户系统根据 “已有“从配置文件中获得的参数” 来调用 “商家端JSAPI预下单”接口

                核心代码操作集中于 OrderServiceImpl.java ,在该.java文件 中会调用 “微信支付工具类”(中的方法) 来根据 “准备好的参数” 来访问JSAPI接口,生成 预支付交易单 ( prepay_id )。 同时在微信支付工具类中 获得小程序端 调用“微信小程序端调起支付”接口 所需要的 一系列参数 ,将这一系列参数封装为JSON对象作为( 微信支付工具类中方法的 )返回值,在微信工具类之外将JSON对象转换为VO对象响应给微信小程序端。

              • 微信小程序端 根据 后端响应来的参数 + wx.requestPayment( )来调用“微信小程序端调起支付”接口,以此来完成“微信支付”
              • 当“小程序端支付”成功时,微信支付后台会根据 notifyUrl (支付成功回调 / 支付成功通知) 设置的 对应的路径发送一个请求,在该请求的方法体中,一般调用商户系统的方法来修改“订单表”中的数据,同时还可给微信做出响应。

              5.微信小程序支付准备工作:

              5.1 获得微信支付平台证书、商户私钥文件

              • 获得微信支付平台证书、商户私钥文件 :这两个文件是从微信的商户平台下载下来的,程序开发过程中会使用到这两个文件。

                ps :要获得这两个文件必须注册成商户。

              • SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第8张

              5.2 获取临时域名 (内网穿透) :

              • 让当前 电脑能获取一个公网的IP地址,让微信后台能调用到当前外卖系统的后端服务,这样我们就需要来 获取临时域名

                (这个临时域名对应的就是一个公网IP)

              ①下载且安装软件 : cpolar
              • 下载链接:https://dashboard.cpolar.com/login

                cpolar软件下载

              • 安装包 (百度网盘下载链接):https://pan.baidu.com/s/10O1mK06ts-l37exniuTquw?pwd=ir23 提取码:ir23

                百度网盘获取cpolar安装包

              ②获得 “Authtoken” 且配置 cpolar ,生成“内网穿透”工具配置文件
              • 登录cpolar官网 : https://dashboard.cpolar.com/login 获得配置cpolar的cmd命令

                SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第9张

              • 在cpolar.exe的目录中敲cmd进入 cmd页面

                在命令行页面中输入 :cpolar.exe authtoken 获得的Authtoken

                该命令生成了一个 .yml文件 : 该文件是 当前“内网穿透工具”的配置文件

                SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第10张

                SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第11张

              ③启动服务,临时获取到一个IP地址 (临时域名)

              输入命令 :cpolar.exe http 8080

              SpringBoot | SpringBoot中实现“微信支付“,在这里插入图片描述,第12张

              6.“微信小程序支付”代码:

              OrderControlle.java (订单Controller)

              • OrderController.java 中的代码 ( 订单Controller) :

                @RestController("userOrderController")  //起别名
                @Slf4j
                @RequestMapping("/user/order")
                @Api(tags = "用户端订单相关接口")
                public class OrderController {
                    @Autowired
                    private OrderService orderService;
                    
                    /**
                     * 订单支付
                     *
                     * 小程序点击去“支付”,请求到该接口,此接口(在商户系统中)先调用JSAPI预下单接口,获得	 *prepay_id ,同时设置好微信小程序端调用 ”微信小程序端调起支付“接口所需的各种参数,且将
                     * 参数封装到 OrderPaymentVO 中响应给微信用户端,微信用户端用这些已经准备好的参数来完
                     * 成“微信小程序支付”
                     *
                     * @param ordersPaymentDTO
                     * @return
                     */
                    @PutMapping("/payment")
                    @ApiOperation("订单支付")
                    public Result payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
                        log.info("订单支付:{}", ordersPaymentDTO);
                    //该方法的返回值为: 小程序端中使用 ”微信小程序端调起支付“ 接口所需的各种参数
                  OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
                  // orderPaymentVO : 包含了小程序端中使用 ”微信小程序端调起支付“ 接口所需的各种参数
                        log.info("生成预支付交易单:{}", orderPaymentVO);
                        //微信小程序端,拿到这个(wx.requestPayment()方法所需的)参数即会进行“微信支付”
                        return Result.success(orderPaymentVO);  
                    }
                }
                
              • OrderPaymentVO.Java :

                @Data
                @Builder
                @NoArgsConstructor
                @AllArgsConstructor
                //这个VO里的属性为: “微信小程序端掉漆支付”接口 所需要的参数
                public class OrderPaymentVO implements Serializable { 
                    private String nonceStr; //随机字符串
                    private String paySign; //签名
                    private String timeStamp; //时间戳
                    private String signType; //签名算法
                    private String packageStr; //统一下单接口返回的 prepay_id 参数值
                }
                
              • OrdersPaymentDTO.java :

                @Data
                 //微信小程序中传来: 订单号、付款方式
                public class OrdersPaymentDTO implements Serializable {
                    //订单号
                    private String orderNumber;
                    //付款方式
                    private Integer payMethod;
                }
                

              OrderService.java

              • OrderService.java

                public interface OrderService {
                    /**
                     * 订单支付
                     * @param ordersPaymentDTO
                     * @return
                     */
                   OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
                    /**
                     * 支付成功,修改订单状态
                     * @param outTradeNo
                     */
                    void paySuccess(String outTradeNo);
                }
                

              OrderServiceImpl.java (包含 : 微信支付工具类)

              • OrderServiceImpl.java :

                @Service //将该类加入到容器中,成为bean
                public class OrderServiceImpl implements OrderService {
                    @Autowired
                    private OrderMapper orderMapper;
                    @Autowired
                    private OrderdetailMapper orderdetailMapper;
                    @Autowired
                    private AddressBookMapper addressBookMapper;
                    @Autowired
                    private ShoppingcartMapper shoppingcartMapper;
                    @Autowired
                    private WeChatPayUtil weChatPayUtil;
                    @Autowired
                    private UserMapper userMapper;
                    /**
                     * 订单支付
                     *
                     * @param ordersPaymentDTO
                     * @return
                     */
                    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
                        // 当前登录用户id
                        Long userId = BaseContext.getCurrentId();
                        User user = userMapper.getById(userId);
                    //调用微信支付接口(“商户系统”调用的“JSAPT预下单接口”),生成“预支付交易单”
                    /*
                     调用微信支付工具类的方法,来进行调用 JSAPI预支付接口 , 同时构造成 微信小程序端调起支付      接口 所以的 JSON参数,将该参数作为pay()方法的返回值 
                     */
                    //jsonObject : 为一个JSON对象,其中包含了 “微信小程序端调起支付”接口所需的JSON参数
                        JSONObject jsonObject = weChatPayUtil.pay(
                                ordersPaymentDTO.getOrderNumber(), //商户订单号
                                new BigDecimal(0.01), //支付金额,单位 元
                                "苍穹外卖订单", //商品描述
                                user.getOpenid() //微信用户的openid
                        );
                        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
                            throw new OrderBusinessException("该订单已支付");
                        }
                        //将JSON参数对象 转换为 Java对象
                        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class); 
                        vo.setPackageStr(jsonObject.getString("package"));
                        //将该OrderPaymentVO 对象作为返回值 ,该对象中包含了 : “微信小程序端调起支付”接口所需的JSON参数
                        return vo;
                    }
                    /**
                     * 支付成功,修改订单状态
                     *
                     * 该方法是在 “支付回调”路径下的方法中 被调用的,用于修改数据库中的“订单表”的数据
                     * (小程序支付成功时,会按照 notifyUrl 设置的回调路径 进行路径访问,在访问到的方法中 会	 * 调用此处的 paySuccess()方法修改数据库
                     * 中“订单表的数据”)
                     *
                     * @param outTradeNo
                     */
                    public void paySuccess(String outTradeNo) {
                        // 根据订单号查询订单
                        Orders ordersDB = orderMapper.getByNumber(outTradeNo);
                        // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
                        //修改“订单表”的数据
                        Orders orders = Orders.builder()
                                .id(ordersDB.getId())
                                .status(Orders.TO_BE_CONFIRMED)
                                .payStatus(Orders.PAID)
                                .checkoutTime(LocalDateTime.now())
                                .build();
                        orderMapper.update(orders); //修改数据库中“订单表”的信息
                    }
                }
                
              • BaseContext.class

                public class BaseContext {  //该类对ThreadLocal对象本身其其下的三个方法进行了封装,方便且更好的调用
                    //创建 ThreadLocal 对象,可在其中设置“线程局部变量”,存储数据该独享的线程中,后“该存入的数据”会被取出来
                    public static ThreadLocal threadLocal = new ThreadLocal<>();
                    /**
                     * 存入请求用户的id
                     * (设置“线程局部变量”)
                     * @param id
                     */
                    public static void setCurrentId(Long id) {
                        //调用ThreadLocal对象的.set(T value) 设置线程局部变量 / 存储数据进该“请求”独享的“线程”中
                        threadLocal.set(id);
                    }
                    /**
                     * 获得请求用户的id
                     * (获得“线程局部变量”)
                     * @return
                     */
                    public static Long getCurrentId() {
                        return threadLocal.get();
                    }
                    /**
                     * 移除请求用户的id
                     * (移除“线程局部变量”)
                     */
                    public static void removeCurrentId() {
                        threadLocal.remove();
                    }
                }
                
              • 微信支付工具类 / WeChatPayUtil.java :

                /**
                 * 微信支付工具类
                 */
                @Component
                public class WeChatPayUtil {
                    //微信支付下单接口地址 (JSAPI"商户系统"预下单接口)
                    public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
                    //申请退款接口地址
                    public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
                    @Autowired
                    private WeChatProperties weChatProperties;
                    /**
                     * 获取调用微信接口的客户端工具对象
                     *
                     * @return
                     */
                    private CloseableHttpClient getClient() {
                        PrivateKey merchantPrivateKey = null;
                        try {
                            //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
                            merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
                            //加载平台证书文件
                            X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
                            //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
                            List wechatPayCertificates = Arrays.asList(x509Certificate);
                            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                                    .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                                    .withWechatPay(wechatPayCertificates);
                   // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
                            CloseableHttpClient httpClient = builder.build();
                            return httpClient;
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                            return null;
                        }
                    }
                    /**
                     * 发送post方式请求
                     *
                     * @param url   请求地址
                     * @param body  请求参数: JSON字符串
                     * @return
                     */
                    private String post(String url, String body) throws Exception {
                        CloseableHttpClient httpClient = getClient();
                        HttpPost httpPost = new HttpPost(url);
                        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
                        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
                        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
                        httpPost.setEntity(new StringEntity(body, "UTF-8"));
                        //该post请求时,返回一个response对象
                        CloseableHttpResponse response = httpClient.execute(httpPost);
                        try {
                            //获得一个 bodyAsString 字符串
                            String bodyAsString = EntityUtils.toString(response.getEntity());
                            return bodyAsString; //返回
                        } finally {
                            httpClient.close();
                            response.close();
                        }
                    }
                    /**
                     * 发送get方式请求
                     *
                     * @param url
                     * @return
                     */
                    private String get(String url) throws Exception {
                        CloseableHttpClient httpClient = getClient();
                        HttpGet httpGet = new HttpGet(url);
                        httpGet.addHeader(HttpHeaders.ACCEPT, 	ContentType.APPLICATION_JSON.toString());
                        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
                        httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
                        CloseableHttpResponse response = httpClient.execute(httpGet);
                        try {
                            String bodyAsString = EntityUtils.toString(response.getEntity());
                            return bodyAsString;
                        } finally {
                            httpClient.close();
                            response.close();
                        }
                    }
                    /**
                     * jsapi下单 (商户系统调用 JSAPI预下单接口 ,该操作为小程序微信支付的第一步)
                     *
                     * @param orderNum    商户订单号
                     * @param total       总金额
                     * @param description 商品描述
                     * @param openid      微信用户的openid
                     * @return
                     */
                    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
                        JSONObject jsonObject = new JSONObject(); //JSON对象
                        jsonObject.put("mchid", weChatProperties.getMchid()); //商户号
                        jsonObject.put("out_trade_no", orderNum);     //商户订单号
                        //小程序的appid (自己建的小程序有专属的appid)
                        jsonObject.put("appid", weChatProperties.getAppid()); 
                        jsonObject.put("description", description); //商品描述
                        //这个及其重要: 小程序用户支付成功则调用指定url (访问指定路径的请求)
                        /*
                         例子如:
                         notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess :支付成功则			访问  .../8080/notify/paySuccess路径
                         (https://58869fb.r2.cpolar.top/notify : 为内网穿透内容)
                         */
                        //支付成功的回调地址
                        jsonObject.put("notify_url", weChatProperties.getNotifyUrl()); 
                        JSONObject amount = new JSONObject(); //JSON对象 ---订单金额信息
                        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                        amount.put("currency", "CNY");
                        jsonObject.put("amount", amount); //订单金额信息
                        JSONObject payer = new JSONObject(); //JSON对象 ---支付者信息
                        payer.put("openid", openid); //微信用户的唯一id
                        jsonObject.put("payer", payer); //支付者信息
                        String body = jsonObject.toJSONString(); //转换为“JSON字符串”
                        //JSAPI 为url常量
                        return post(JSAPI, body); // 将post()方法的返回值 作为jsapi()方法的返回值
                    }
                    /**
                     * 小程序支付
                     *
                     * @param orderNum    商户订单号(订单号)
                     * @param total       金额,单位 元
                     * @param description 商品描述
                     * @param openid      微信用户的 openid
                     * @return
                     */
                    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
                        //统一下单,生成预支付交易单
                        String bodyAsString = jsapi(orderNum, total, description, openid);
                        /*
                         获得bodyAsString表明,商户系统成功调用JSAPI预下单接口了,此时或获得 			     “prepay_id” ---将prepay_id传给“小程序端”用于微信支付
                         */
                        //解析返回结果
                        JSONObject jsonObject = JSON.parseObject(bodyAsString);
                        System.out.println(jsonObject);
                        //获得 prepayId : 预支付交易会话标识
                        String prepayId = jsonObject.getString("prepay_id");
                        // 如果 prepayId : 预支付交易会话标识 不为空,才能让用户小程序端进行"微信支付"
                        if (prepayId != null) { 
                            //时间戳
                     String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
                            //随机字符串
                            String nonceStr = RandomStringUtils.randomNumeric(32);
                            ArrayList list = new ArrayList<>();
                            list.add(weChatProperties.getAppid());
                            list.add(timeStamp); //时间戳
                            list.add(nonceStr);  //随机字符串
                            list.add("prepay_id=" + prepayId); //预支付交易会话标识
                            //二次签名,调起支付需要重新签名
                            StringBuilder stringBuilder = new StringBuilder();
                            for (Object o : list) {
                                stringBuilder.append(o).append("\n");
                            }
                            String signMessage = stringBuilder.toString();
                            byte[] message = signMessage.getBytes();
                            Signature signature = Signature.getInstance("SHA256withRSA");
                            signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
                            signature.update(message);
                            String packageSign = Base64.getEncoder().encodeToString(signature.sign());
                            //设置好用户小程序端调用 wx.requestPayment()方法所需要的“参数”
                            //构造数据给微信小程序,用于调起微信支付
                            JSONObject jo = new JSONObject();
                            jo.put("timeStamp", timeStamp); //时间戳
                            jo.put("nonceStr", nonceStr); //随机字符串
                            jo.put("package", "prepay_id=" + prepayId); //预支付交易会话标识
                            jo.put("signType", "RSA"); //签名类型,默认为RSA,仅支持RSA
                            jo.put("paySign", packageSign); //签名。使用字段appId、timeStamp、nonceStr、package计算得出的“签名值”
                            //将 “微信小程序端调起支付”接口所需的JSON参数 (此时为JSON对象) 作为方法的返回值
                            return jo;
                        }
                        return jsonObject;
                    }
                    /**
                     * 申请退款
                     *
                     * @param outTradeNo    商户订单号
                     * @param outRefundNo   商户退款单号
                     * @param refund        退款金额
                     * @param total         原订单金额
                     * @return
                     */
                    public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("out_trade_no", outTradeNo);
                        jsonObject.put("out_refund_no", outRefundNo);
                        JSONObject amount = new JSONObject();
                        amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                        amount.put("currency", "CNY");
                        jsonObject.put("amount", amount);
                        jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
                        String body = jsonObject.toJSONString();
                        //调用申请退款接口
                        return post(REFUNDS, body);
                    }
                }
                 
              • OrderBusinessException.java :

                public class OrderBusinessException extends BaseException {
                    public OrderBusinessException(String msg) {
                        super(msg);
                    }
                }
                

                OrderMapper.java

                • OrderMapper.java :

                  @Mapper //将该接口的实现类加入到容器中
                  public interface OrderMapper {
                      /**
                       * 插入订单数据
                       * @param orders
                       */
                      void insert(Orders orders);
                      /**
                       * 根据订单号查询订单
                       * @param orderNumber
                       */
                      @Select("select * from orders where number = #{orderNumber}")
                      Orders getByNumber(String orderNumber);
                      /**
                       * 修改订单信息
                       * @param orders
                       */
                      void update(Orders orders);
                  }
                  
                • Orders.java :

                  /**
                   * 订单
                   */
                  @Data
                  @Builder
                  @NoArgsConstructor
                  @AllArgsConstructor
                  public class Orders implements Serializable {
                      /**
                       * 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
                       */
                      public static final Integer PENDING_PAYMENT = 1;
                      public static final Integer TO_BE_CONFIRMED = 2;
                      public static final Integer CONFIRMED = 3;
                      public static final Integer DELIVERY_IN_PROGRESS = 4;
                      public static final Integer COMPLETED = 5;
                      public static final Integer CANCELLED = 6;
                      /**
                       * 支付状态 0未支付 1已支付 2退款
                       */
                      public static final Integer UN_PAID = 0;
                      public static final Integer PAID = 1;
                      public static final Integer REFUND = 2;
                      private static final long serialVersionUID = 1L;
                      private Long id;
                      //订单号
                      private String number;
                      //订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款
                      private Integer status;
                      //下单用户id
                      private Long userId;
                      //地址id
                      private Long addressBookId;
                      //下单时间
                      private LocalDateTime orderTime;
                      //结账时间
                      private LocalDateTime checkoutTime;
                      //支付方式 1微信,2支付宝
                      private Integer payMethod;
                      //支付状态 0未支付 1已支付 2退款
                      private Integer payStatus;
                      //实收金额
                      private BigDecimal amount;
                      //备注
                      private String remark;
                      //用户名
                      private String userName;
                      //手机号
                      private String phone;
                      //地址
                      private String address;
                      //收货人
                      private String consignee;
                      //订单取消原因
                      private String cancelReason;
                      //订单拒绝原因
                      private String rejectionReason;
                      //订单取消时间
                      private LocalDateTime cancelTime;
                      //预计送达时间
                      private LocalDateTime estimatedDeliveryTime;
                      //配送状态  1立即送出  0选择具体时间
                      private Integer deliveryStatus;
                      //送达时间
                      private LocalDateTime deliveryTime;
                      //打包费
                      private int packAmount;
                      //餐具数量
                      private int tablewareNumber;
                      //餐具数量状态  1按餐量提供  0选择具体数量
                      private Integer tablewareStatus;
                  }
                  

                OrderMapper.xml

                • OrderMapper.xml :

                  
                  
                  
                      
                      
                          update orders
                          
                              
                                  cancel_reason=#{cancelReason},
                              
                              
                                  rejection_reason=#{rejectionReason},
                              
                              
                                  cancel_time=#{cancelTime},
                              
                              
                                  pay_status=#{payStatus},
                              
                              
                                  pay_method=#{payMethod},
                              
                              
                                  checkout_time=#{checkoutTime},
                              
                              
                                  status = #{status},
                              
                              
                                  delivery_time = #{deliveryTime}
                              
                          
                          where id = #{id}
                      
                      
                  
                  

                UserMapper.java

                • UserMapper.java :

                  @Mapper 
                  public interface UserMapper {
                      /**
                       * 根据id查询数据
                       */
                      @Select("select * from user where id = #{id}")
                      User getById(Long userId);
                  }
                  

                PayNotifyController.java / 支付回调相关接口

                • PayNotifyController.java / 支付回调相关接口:

                  /**
                   * 支付回调相关接口
                   */
                  @RestController
                  @RequestMapping("/notify")
                  @Slf4j
                  public class PayNotifyController {
                      @Autowired
                      private OrderService orderService;
                      @Autowired
                      private WeChatProperties weChatProperties;
                      /**
                       * 支付成功回调 :
                       *  小程序用户支付成功时,放根据 notify_url 设置的路径来访问到此处的 paySuccess路径  	  *下的 paySuccessNotify()方法。
                       *  同时在该方法中将调用 “商户系统”中的方法来修改数据库中的“订单表”的数据
                       * @param request
                       */
                      @RequestMapping("/paySuccess")
                      public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
                          //读取数据
                          String body = readData(request);
                          log.info("支付成功回调:{}", body);
                          //数据解密
                          String plainText = decryptData(body);
                          log.info("解密后的文本:{}", plainText);
                          JSONObject jsonObject = JSON.parseObject(plainText);
                          String outTradeNo = jsonObject.getString("out_trade_no");      //商户平台订单号
                          String transactionId = jsonObject.getString("transaction_id"); //微信支付交易号
                          log.info("商户平台订单号:{}", outTradeNo);
                          log.info("微信支付交易号:{}", transactionId);
                          //业务处理,修改订单状态、来单提醒 -- 用于修改"订单表"中的数据
                          /**
                           *  “商户系统”中的方法来修改数据库中的“订单表”的数据
                           */
                          orderService.paySuccess(outTradeNo);
                          //给微信响应
                          responseToWeixin(response);
                      }
                      /**
                       * 读取数据
                       *
                       * @param request
                       * @return
                       * @throws Exception
                       */
                      private String readData(HttpServletRequest request) throws Exception {
                          BufferedReader reader = request.getReader();
                          StringBuilder result = new StringBuilder();
                          String line = null;
                          while ((line = reader.readLine()) != null) {
                              if (result.length() > 0) {
                                  result.append("\n");
                              }
                              result.append(line);
                          }
                          return result.toString();
                      }
                      /**
                       * 数据解密
                       *
                       * @param body
                       * @return
                       * @throws Exception
                       */
                      private String decryptData(String body) throws Exception {
                          JSONObject resultObject = JSON.parseObject(body);
                          JSONObject resource = resultObject.getJSONObject("resource");
                          String ciphertext = resource.getString("ciphertext");
                          String nonce = resource.getString("nonce");
                          String associatedData = resource.getString("associated_data");
                          AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
                          //密文解密
                          String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                                  nonce.getBytes(StandardCharsets.UTF_8),
                                  ciphertext);
                          return plainText;
                      }
                      /**
                       * 给微信响应
                       * @param response
                       */
                      private void responseToWeixin(HttpServletResponse response) throws Exception{
                          response.setStatus(200);
                          HashMap map = new HashMap<>();
                          map.put("code", "SUCCESS");
                          map.put("message", "SUCCESS");
                          response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
                          response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
                          response.flushBuffer();
                      }
                  }
                  

                application.yml (springboot配置文件)

                • application.yml

                  server:
                    port: 8080
                  spring:
                    profiles:
                      active: dev
                    main:
                      allow-circular-references: true
                    datasource:
                      druid:
                        driver-class-name: ${sky.datasource.driver-class-name}
                        url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
                        username: ${sky.datasource.username}
                        password: ${sky.datasource.password}
                    redis:
                      host: ${sky.redis.host}
                      port: ${sky.redis.port}
                      password: ${sky.redis.password}
                      database: ${sky.redis.database}
                  mybatis:
                    #mapper配置文件
                    mapper-locations: classpath:mapper/*.xml
                    type-aliases-package: com.sky.entity
                    configuration:
                      #开启驼峰命名
                      map-underscore-to-camel-case: true
                  logging:
                    level:
                      com:
                        sky:
                          mapper: debug
                          service: info
                          controller: info
                  sky:
                    jwt:
                      # 设置jwt签名加密时使用的秘钥
                      admin-secret-key: itcast
                      # 设置jwt过期时间
                      admin-ttl: 7200000
                      # 设置前端传递过来的令牌名称
                      admin-token-name: token
                      user-secret-key: itheima
                      user-ttl: 7200000
                      user-token-name: authentication
                    alioss:
                      endpoint: ${sky.alioss.endpoint}
                      access-key-id: ${sky.alioss.access-key-id}
                      access-key-secret: ${sky.alioss.access-key-secret}
                      bucket-name: ${sky.alioss.bucket-name}
                    wechat:
                      appid: ${sky.wechat.appid}
                      secret: ${sky.wechat.secret}
                      mchid : ${sky.wechat.mchid}
                      mchSerialNo: ${sky.wechat.mchSerialNo}
                      privateKeyFilePath: ${sky.wechat.privateKeyFilePath}
                      apiV3Key: ${sky.wechat.apiV3Key}
                      weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}
                      notifyUrl: ${sky.wechat.notifyUrl}
                      refundNotifyUrl: ${sky.wechat.refundNotifyUrl}
                    shop:
                      address: 北京市海淀区上地十街10号
                    baidu:
                      ak: your-ak
                  

                application-dev.xml

                • application-dev.xml

                  sky:
                    datasource:
                      driver-class-name: com.mysql.cj.jdbc.Driver
                      host: localhost
                      port: 3306
                      database: sky_take_out
                      username: root
                      password: root
                    alioss:
                      endpoint: oss-cn-beijing.aliyuncs.com
                      access-key-id: your-access-key-id
                      access-key-secret: your-access-key-secret
                      bucket-name: your-bucket-name
                    redis:
                      host: localhost
                      port: 6379
                      password: 123456
                      database: 10
                    wechat:
                      appid: wxffb3637a228223b8 #小程序的appid
                      secret: 84311df9199ecacdf4f12d27b6b9522d  #小程序的秘钥
                      mchid : 1561414331 #商户号
                      mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606 #商户API证书的证书序列号
                      privateKeyFilePath: D:\pay\apiclient_key.pem #商户私钥文件(路径)
                      apiV3Key: CZBK51236435wxpay435434323FFDuv3  #证书解密的密钥
                      weChatPayCertFilePath: D:\pay\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem #微信支付平台证书(路径)
                      #(支付成功)通知url : 当“小程序端微信支付成功”时,调用该请求,请求到后端
                      #这个及其重要: 小程序用户支付成功则调用该url (访问该路径的请求), 如这里: 支付成功则访问  	#.../8080/notify/paySuccess路径
                   notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess #支付成功的回调地址
                      #(退款成功)通知Url
                      refundNotifyUrl: https://58869fb.r2.cpolar.top/notify/refundSuccess
                  

                微信支付工具类

                • 微信支付工具类 :

                  /**
                   * 微信支付工具类
                   */
                  @Component
                  public class WeChatPayUtil {
                      //微信支付下单接口地址 (JSAPI"商户系统"预下单接口)
                      public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
                      //申请退款接口地址
                      public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
                      @Autowired
                      private WeChatProperties weChatProperties;
                      /**
                       * 获取调用微信接口的客户端工具对象
                       *
                       * @return
                       */
                      private CloseableHttpClient getClient() {
                          PrivateKey merchantPrivateKey = null;
                          try {
                              //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
                              merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
                              //加载平台证书文件
                              X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
                              //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
                              List wechatPayCertificates = Arrays.asList(x509Certificate);
                              WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                                      .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                                      .withWechatPay(wechatPayCertificates);
                     // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
                              CloseableHttpClient httpClient = builder.build();
                              return httpClient;
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                              return null;
                          }
                      }
                      /**
                       * 发送post方式请求
                       *
                       * @param url   请求地址
                       * @param body  请求参数: JSON字符串
                       * @return
                       */
                      private String post(String url, String body) throws Exception {
                          CloseableHttpClient httpClient = getClient();
                          HttpPost httpPost = new HttpPost(url);
                          httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
                          httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
                          httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
                          httpPost.setEntity(new StringEntity(body, "UTF-8"));
                          //该post请求时,返回一个response对象
                          CloseableHttpResponse response = httpClient.execute(httpPost);
                          try {
                              //获得一个 bodyAsString 字符串
                              String bodyAsString = EntityUtils.toString(response.getEntity());
                              return bodyAsString; //返回
                          } finally {
                              httpClient.close();
                              response.close();
                          }
                      }
                      /**
                       * 发送get方式请求
                       *
                       * @param url
                       * @return
                       */
                      private String get(String url) throws Exception {
                          CloseableHttpClient httpClient = getClient();
                          HttpGet httpGet = new HttpGet(url);
                          httpGet.addHeader(HttpHeaders.ACCEPT, 	ContentType.APPLICATION_JSON.toString());
                          httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
                          httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
                          CloseableHttpResponse response = httpClient.execute(httpGet);
                          try {
                              String bodyAsString = EntityUtils.toString(response.getEntity());
                              return bodyAsString;
                          } finally {
                              httpClient.close();
                              response.close();
                          }
                      }
                      /**
                       * jsapi下单 (商户系统调用 JSAPI预下单接口 ,该操作为小程序微信支付的第一步)
                       *
                       * @param orderNum    商户订单号
                       * @param total       总金额
                       * @param description 商品描述
                       * @param openid      微信用户的openid
                       * @return
                       */
                      private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
                          JSONObject jsonObject = new JSONObject(); //JSON对象
                          jsonObject.put("mchid", weChatProperties.getMchid()); //商户号
                          jsonObject.put("out_trade_no", orderNum);     //商户订单号
                          //小程序的appid (自己建的小程序有专属的appid)
                          jsonObject.put("appid", weChatProperties.getAppid()); 
                          jsonObject.put("description", description); //商品描述
                          //这个及其重要: 小程序用户支付成功则调用指定url (访问指定路径的请求)
                          /*
                           例子如:
                           notifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccess :支付成功则			访问  .../8080/notify/paySuccess路径
                           (https://58869fb.r2.cpolar.top/notify : 为内网穿透内容)
                           */
                          //支付成功的回调地址
                          jsonObject.put("notify_url", weChatProperties.getNotifyUrl()); 
                          JSONObject amount = new JSONObject(); //JSON对象 ---订单金额信息
                          amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                          amount.put("currency", "CNY");
                          jsonObject.put("amount", amount); //订单金额信息
                          JSONObject payer = new JSONObject(); //JSON对象 ---支付者信息
                          payer.put("openid", openid); //微信用户的唯一id
                          jsonObject.put("payer", payer); //支付者信息
                          String body = jsonObject.toJSONString(); //转换为“JSON字符串”
                          //JSAPI 为url常量
                          return post(JSAPI, body); // 将post()方法的返回值 作为jsapi()方法的返回值
                      }
                      /**
                       * 小程序支付
                       *
                       * @param orderNum    商户订单号(订单号)
                       * @param total       金额,单位 元
                       * @param description 商品描述
                       * @param openid      微信用户的 openid
                       * @return
                       */
                      public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
                          //统一下单,生成预支付交易单
                          String bodyAsString = jsapi(orderNum, total, description, openid);
                          /*
                           获得bodyAsString表明,商户系统成功调用JSAPI预下单接口了,此时或获得 			     “prepay_id” ---将prepay_id传给“小程序端”用于微信支付
                           */
                          //解析返回结果
                          JSONObject jsonObject = JSON.parseObject(bodyAsString);
                          System.out.println(jsonObject);
                          //获得 prepayId : 预支付交易会话标识
                          String prepayId = jsonObject.getString("prepay_id");
                          // 如果 prepayId : 预支付交易会话标识 不为空,才能让用户小程序端进行"微信支付"
                          if (prepayId != null) { 
                              //时间戳
                       String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
                              //随机字符串
                              String nonceStr = RandomStringUtils.randomNumeric(32);
                              ArrayList list = new ArrayList<>();
                              list.add(weChatProperties.getAppid());
                              list.add(timeStamp); //时间戳
                              list.add(nonceStr);  //随机字符串
                              list.add("prepay_id=" + prepayId); //预支付交易会话标识
                              //二次签名,调起支付需要重新签名
                              StringBuilder stringBuilder = new StringBuilder();
                              for (Object o : list) {
                                  stringBuilder.append(o).append("\n");
                              }
                              String signMessage = stringBuilder.toString();
                              byte[] message = signMessage.getBytes();
                              Signature signature = Signature.getInstance("SHA256withRSA");
                              signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
                              signature.update(message);
                              String packageSign = Base64.getEncoder().encodeToString(signature.sign());
                              //设置好用户小程序端调用 wx.requestPayment()方法所需要的“参数”
                              //构造数据给微信小程序,用于调起微信支付
                              JSONObject jo = new JSONObject();
                              jo.put("timeStamp", timeStamp); //时间戳
                              jo.put("nonceStr", nonceStr); //随机字符串
                              jo.put("package", "prepay_id=" + prepayId); //预支付交易会话标识
                              jo.put("signType", "RSA"); //签名类型,默认为RSA,仅支持RSA
                              jo.put("paySign", packageSign); //签名。使用字段appId、timeStamp、nonceStr、package计算得出的“签名值”
                      //将 “微信小程序端调起支付”接口所需的JSON参数 (此时为JSON对象) 作为方法的返回值
                              return jo;
                          }
                          return jsonObject;
                      }
                      /**
                       * 申请退款
                       *
                       * @param outTradeNo    商户订单号
                       * @param outRefundNo   商户退款单号
                       * @param refund        退款金额
                       * @param total         原订单金额
                       * @return
                       */
                      public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
                          JSONObject jsonObject = new JSONObject();
                          jsonObject.put("out_trade_no", outTradeNo);
                          jsonObject.put("out_refund_no", outRefundNo);
                          JSONObject amount = new JSONObject();
                          amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                          amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
                          amount.put("currency", "CNY");
                          jsonObject.put("amount", amount);
                          jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
                          String body = jsonObject.toJSONString();
                          //调用申请退款接口
                          return post(REFUNDS, body);
                      }
                  }
                   
                   
                  

                  1