SpringBoot | SpringBoot中实现“微信支付“

SpringBoot中实现"微信支付":

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

1.“微信支付”产品

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

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

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

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

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

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

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

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

  • 微信支付产品
    在这里插入图片描述

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

  • 在这里插入图片描述
  • 在这里插入图片描述
  • 在这里插入图片描述

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

  • 微信小程序支付时序图

    在这里插入图片描述

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

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

3.1 “商家端JSAPI下单” 接口

  • 商户系统调用 (小程序支付中的) JSAPI下单接口微信支付服务后台生成预支付交易单

  • “商家端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"
}                             
  • 适用对象: 直连商户
    请求URLhttps://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

在这里插入图片描述

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

在这里插入图片描述

支付者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
    (
    	{
    		"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下单”接口) 来生成 预支付交易单。由于这个接口是与“支付”相关的,所以这个接口的安全要求是非常高的,同时”推送支付结果“的安全性要求也是非常高的。

  • 如何保证调用过程的数据安全

    对数据进行加密解密签名

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

  • 获得微信支付平台证书、商户私钥文件 :这两个文件是从微信的商户平台下载下来的,程序开发过程中会使用到这两个文件。
    ps :要获得这两个文件必须注册成商户
  • 在这里插入图片描述

4.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命令
    在这里插入图片描述


  • cpolar.exe的目录敲cmd进入 cmd页面
    命令行页面中输入cpolar.exe authtoken 获得的Authtoken

    命令生成了一个 .yml文件 : 该文件当前“内网穿透工具”配置文件
    在这里插入图片描述

    在这里插入图片描述

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

输入命令 :cpolar.exe http 8080
在这里插入图片描述

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

OrderControlle.java (订单Controller)

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

    @RestController("userOrderController")  //起别名
    @Slf4j
    @RequestMapping("/user/order")
    @Api(tags = "用户端订单相关接口")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        /**
         * 订单支付  
         *
         * @param ordersPaymentDTO
         * @return
         */
        @PutMapping("/payment")
        @ApiOperation("订单支付")
        public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
            log.info("订单支付:{}", ordersPaymentDTO);
            OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
            log.info("生成预支付交易单:{}", orderPaymentVO);
            return Result.success(orderPaymentVO);
        }
    }
    
  • OrderPaymentVO.Java :

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    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);
    }
    
  • OrdersPaymentDTO.java :

    @Data
    public class OrdersPaymentDTO implements Serializable {
        //订单号
        private String orderNumber;
    
        //付款方式
        private Integer payMethod;
    }
    
  • OrderPaymentVO.java :

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class OrderPaymentVO implements Serializable {
    
        private String nonceStr; //随机字符串
        private String paySign; //签名
        private String timeStamp; //时间戳
        private String signType; //签名算法
        private String packageStr; //统一下单接口返回的 prepay_id 参数值
    }
    

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);
    
            //调用微信支付接口,生成预支付交易单
            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("该订单已支付");
            }
    
            OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
            vo.setPackageStr(jsonObject.getString("package"));
    
            return vo;
        }
    
        /**
         * 支付成功,修改订单状态
         *
         * @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 {
    
        //微信支付下单接口地址
        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
         * @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"));
    
            CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                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下单
         *
         * @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();
            jsonObject.put("appid", weChatProperties.getAppid());
            jsonObject.put("mchid", weChatProperties.getMchid());
            jsonObject.put("description", description);
            jsonObject.put("out_trade_no", orderNum);
            jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
    
            JSONObject amount = new JSONObject();
            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();
            payer.put("openid", openid);
    
            jsonObject.put("payer", payer);
    
            String body = jsonObject.toJSONString();
            return post(JSAPI, body);
        }
    
        /**
         * 小程序支付
         *
         * @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);
            //解析返回结果
            JSONObject jsonObject = JSON.parseObject(bodyAsString);
            System.out.println(jsonObject);
    
            String prepayId = jsonObject.getString("prepay_id");
            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());
    
                //构造数据给微信小程序,用于调起微信支付
                JSONObject jo = new JSONObject();
                jo.put("timeStamp", timeStamp);
                jo.put("nonceStr", nonceStr);
                jo.put("package", "prepay_id=" + prepayId);
                jo.put("signType", "RSA");
                jo.put("paySign", packageSign);
    
                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 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;
    
        /**
         * 支付成功回调
         *
         * @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();
        }
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

永恒之蓝漏洞 ms17_010 详解

文章目录 永恒之蓝 ms 17_0101.漏洞介绍1.1 影响版本1.2 漏洞原理 2.信息收集2.1 主机扫描2.2 端口扫描 3. 漏洞探测4. 漏洞利用5.后渗透阶段5.1创建新的管理员账户5.2开启远程桌面5.3蓝屏攻击 永恒之蓝 ms 17_010 1.漏洞介绍 永恒之蓝&#xff08;ms17-010&#xff09;爆发于…

搜索引擎搜索技巧总结

晚上在B站上刷到一个关于搜索技巧的干货视频&#xff0c;这个视频真的不错&#xff0c;结尾还提到了AI时代的搜索思路之前自己也零碎的探索出了一些搜索技巧&#xff0c;但是没有总结&#xff0c;就没法稳定的加入自己的工作流&#xff0c;持续提高效率受到这个视频的启发&…

MySQL 5.7限制general_log日志大小

背景 需求&#xff1a; 在MySQL 5.7.41中开启general_log 并限制其大小&#xff0c;避免快速增长占用硬盘空间。 解决&#xff1a; 通过定时任务&#xff0c;执行简单的脚本&#xff0c;判断general_log 日志的大小&#xff0c;实现对通用查询日志的“每日备份”或“每日清…

毅速丨哪些金属材料在3D打印中应用最多

金属3D打印作为一种新兴的制造技术已经在很多领域得到广泛应用&#xff0c;目前金属3D打印应用较多的材料有不锈钢、钛合金、铝合金、钴铬合金、镍基合金、模具钢等&#xff0c;其中不锈钢材料的应用最为常见。 这些金属3D打印材料各有其特点和适用场景&#xff0c;可以根据具体…

Qt重定向QDebug,Qt/C++开源作品39-日志输出增强版V2022

Qt重定向QDebug&#xff0c;自定义一个简易的日志管理类 Chapter1 Qt重定向QDebug&#xff0c;自定义一个简易的日志管理类0.前言1.最简单的操作运行结果2.实现一个简易的日志管理类 Chapter2 Qt::Qt Log日志模块Qt Log日志模块官方解释官方Demo思路 Chapter3 QT日志模块的个性…

蓝桥杯 第 2 场算法双周赛 第3题 摆玩具【算法赛】 c++ 贪心

题目 摆玩具【算法赛】https://www.lanqiao.cn/problems/5888/learning/?contest_id145 问题描述 小蓝是一个热爱收集玩具的小伙子&#xff0c;他拥有 n 个不同的玩具。 这天&#xff0c;他把 n 个玩具按照高度顺序从矮到高摆放在了窗台上&#xff0c;然后&#xff0c;他希…

阿里云对象存储OSS文件无法预览,Bucket设置了Referer

您发起的请求头中没有Referer字段或Referer字段为空&#xff0c;与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…

Docker GitLab-Runner安装

Docker GitLab-Runner安装 GitLab-Runner安装 问题合集GitLab 域名的配置修改Runner容器内注册失败&#xff0c;提示 dial tcp: lookup home.zsl0.com on 192.168.254.2:53: no such host GitLab-Runner 安装 拉去gitlab/gitlab-runner镜像 docker pull gitlab/gitlab-runne…

汽车行驶性能的主观评价方法(1)-底盘校准方法

底盘校准的目的是&#xff0c;从行驶性能和行驶舒适性两个方面进行协调&#xff0c;从而优化行驶动力学特性。为了达到这一目标&#xff0c;工程人员早在设计阶段&#xff0c;就对大多数对行驶动力性有重要意义的部件提出了要求。这些要求不仅与底盘的组件有关&#xff0c;还必…

轻量封装WebGPU渲染系统示例<2>-彩色立方体(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/version-1.01/src/voxgpu/sample/VertColorCube.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 5. …

超宽带技术在汽车领域的应用

随着科技的不断发展&#xff0c;超宽带&#xff08;Ultra-Wideband, UWB&#xff09;技术在各个领域展现出了强大的潜力&#xff0c;其中汽车领域更是受益匪浅。UWB技术以其高精度的定位能力、高速的数据传输和低功耗的特点&#xff0c;为汽车行业带来了许多创新。本文将探讨UW…

20.1 OpenSSL 字符BASE64压缩算法

OpenSSL 是一种开源的加密库&#xff0c;提供了一组用于加密和解密数据、验证数字证书以及实现各种安全协议的函数和工具。它可以用于创建和管理公钥和私钥、数字证书和其他安全凭据&#xff0c;还支持SSL/TLS、SSH、S/MIME、PKCS等常见的加密协议和标准。 OpenSSL 的功能非常…

安卓开发实例:方向传感器

调用手机的方向传感器&#xff0c;X轴&#xff0c;Y轴&#xff0c;Z轴的数值 activity_sensor.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutxmlns:android"http://schemas.android.c…

Git Gui使用技巧

资料 https://www.runoob.com/w3cnote/git-gui-window.html 操作过程 创建仓库→添加远程仓库→扫描目录→文件移动→提交→上传 注意填注释 文件忽略 创建文件.gitignore→编写内容 *.log #文件 config.ini #文件 temp/ #目录

Linux--安装与配置虚拟机及虚拟机服务器坏境配置与连接---超详细教学

一&#xff0c;操作系统介绍 1.1.什么是操作系统 操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是一种系统软件&#xff0c;它是计算机硬件和应用软件之间的桥梁。它管理计算机的硬件和软件资源&#xff0c;为应用程序提供接口和服务&#xff0c;并协调…

spark

spark Spark可以将Hadoop集群中的应用在内存中的运行速度提升100倍&#xff0c;甚至能够将应用在磁盘上的运行速度提升10倍。除了Map和Reduce操作之外&#xff0c;Spark还支持SQL查询&#xff0c;流数据&#xff0c;机器学习和图表数据处理。开发者可以在一个数据管道用例中单独…

面试总结之消息中间件

RabbitMQ的消息如何实现路由 RabbitMQ是一个基于AMQP协议实现的分布式消息中间件&#xff0c;AMQP具体的工作机制是生产者将消息发送到RabbitMQ Broker上的Exchange交换机上&#xff0c;Exchange交换机将收到的消息根据路由规则发给绑定的队列&#xff08;Queue&#xff09;&am…

汇总区间(Java)

大家好我是苏麟 , 这篇文章也是凑数的 ... 描述 : 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 n…

C++之特殊类的设计

目录 一、单例模式 1、设计模式 2、单例模式 1、饿汉模式 2、懒汉模式 3、单例对象的释放问题 二、设计一个不能被拷贝的类 三、设计一个只能在堆上创建对象的类 四、设计一个只能在栈上创建对象的类 五、设计一个不能被继承的类 一、单例模式 1、设计模式 概念&am…

二分归并法将两个数组合并

#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> main() {int a[5] {1,3,4,5,6};int b[4] {2,7,8,9};int c[9];int m0, n0,k0;while (m < 5 && n < 4){if (a[m] < b[n]){c[k] a[m];//谁小谁先进数组m; k;}else{c[k] b[n];k; n;}}while (m <…