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 接入HDFS_Java

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


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

  • 微信小程序支付时序图
  • 微信小程序支付 主要内容为以下三个部分:
  • “商家端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 接入HDFS_内网穿透_02

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

springboot 接入HDFS_内网穿透_03

支付者payerobject是body 支付者信息参数名变量类型[长度限制]必填描述用户标识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 :要获得这两个文件必须注册成商户

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

  • 让当前 电脑能获取一个公网的IP地址,让微信后台能调用到当前外卖系统后端服务,这样我们就需要来 获取临时域名
    (这个临时域名对应的就是一个公网IP
①下载且安装软件 : cpolar
②获得 “Authtoken” 且配置 cpolar ,生成“内网穿透”工具配置文件

  • 登录cpolar官网 : https://dashboard.cpolar.com/login 获得配置cpolar的cmd命令
  • springboot 接入HDFS_微信_04


  • cpolar.exe的目录敲cmd进入 cmd页面
    命令行页面中输入cpolar.exe authtoken 获得的Authtoken
    命令生成了一个 .yml文件 : 该文件当前“内网穿透工具”配置文件
③启动服务,临时获取到一个IP地址 (临时域名)
输入命令 :cpolar.exe http 8080

springboot 接入HDFS_微信_05

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<OrderPaymentVO> 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<Long> 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<X509Certificate> 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<Object> 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 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.OrderMapper">

    
    <update id="update" parameterType="com.sky.entity.Orders">
        update orders
        <set>
            <if test="cancelReason != null and cancelReason!='' ">
                cancel_reason=#{cancelReason},
            </if>
            <if test="rejectionReason != null and rejectionReason!='' ">
                rejection_reason=#{rejectionReason},
            </if>
            <if test="cancelTime != null">
                cancel_time=#{cancelTime},
            </if>
            <if test="payStatus != null">
                pay_status=#{payStatus},
            </if>
            <if test="payMethod != null">
                pay_method=#{payMethod},
            </if>
            <if test="checkoutTime != null">
                checkout_time=#{checkoutTime},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
            <if test="deliveryTime != null">
                delivery_time = #{deliveryTime}
            </if>
        </set>
        where id = #{id}
    </update>
    
</mapper>

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<Object, Object> 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<X509Certificate> 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<Object> 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);
    }
}