微信支付 APP端 第三弹 申请退款

本文详细介绍了微信支付退款的实现过程,包括退款设计思路、申请退款的步骤和微信退款回调的控制器、验证回调类和服务层验证。在退款设计中,提到微信退款接口的使用场景和限制,并提供了退款请求的示例。在申请退款的代码示例中,展示了如何构造退款请求并处理退款结果。在微信退款回调部分,讲解了回调验证的控制器方法和回调验证服务,确保退款信息的安全和准确性。
摘要由CSDN通过智能技术生成

目录

第三弹 申请退款

成果展示:

1.退款设计思路

 2.申请退款

3.微信退款回调

3.1controller

3.1.1 微信退款controller

3.1.2验证回调类 (和微信支付回调验证一样 如果看了之前的可以不用写)

3.1.3 service 退款回调验证


第三弹 申请退款

当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。

(这些要根据直接项目的情况按需来安排)

成果展示:

1.退款设计思路

微信官方请求示例:

{
  "transaction_id": "1217752501201407033233368018",
  "out_refund_no": "1217752501201407033233368018",
  "reason": "商品已售完",
  "notify_url": "https://weixin.qq.com",
  "funds_account": "AVAILABLE",
  "amount": {
    "refund": 888,
    "from": [
      {
        "account": "AVAILABLE",
        "amount": 444
      }
    ],
    "total": 888,
    "currency": "CNY"
  },
  "goods_detail": [
    {
      "merchant_goods_id": "1217752501201407033233368018",
      "wechatpay_goods_id": "1001",
      "goods_name": "iPhone6s 16G",
      "unit_price": 528800,
      "refund_amount": 528800,
      "refund_quantity": 1
    }
  ]
}

 2.申请退款

 /**
     * 微信退款
     * 注意:1、交易时间超过一年的订单无法提交退款
     * 2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
     * 3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
     * 4、每个支付订单的部分退款次数不能超过50次
     * 5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
     * 6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
     * 7、一个月之前的订单申请退款频率限制为:5000/min
     * 8、同一笔订单多次退款的请求需相隔1分钟
     * @param wxConfig 微信商家配置
     * @param orderId 订单id
     * @return String
     * @author zhangjunrong
     * @date 2022/4/21 15:11
     */
@Override
    @Transactional(rollbackFor = Exception.class)
    public String refundOrder(ToolWxConfig wxConfig, Long orderId) {
        TicketOrderReturn refundOrder = (TicketOrderReturn) redisUtil.get(WxRedisKey.WX_REFUND_ORDER + orderId);
        try {
            //通过redis 中获取订单相应的信息
            if (ObjectUtil.isNotEmpty(refundOrder)) {
                log.info("微信退款=====redis记录信息========={}==",refundOrder.toString());
                //1.请求配置参数
                HttpPost httpPost = new HttpPost(WxApiType.REFUND_ORDER.getValue());
                //格式配置
                //格式配置
                httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
                httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectMapper objectMapper = new ObjectMapper();
                ObjectNode rootNode = objectMapper.createObjectNode();
                //2.配置参数 订单商户号 退款订单号 微信订单号(微信生成) 退款回调地址(与下单回调地址不一样) 金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分) 退款币种 CNY 人民币
                //商户订单号 下单时生成
                rootNode.put(WXOrderConstant.OUT_TRADE_NO, refundOrder.getOrderSn())
                        //微信支付系统生成的订单号(微信生成) 下单时生成
                        .put(WXOrderConstant.TRANSACTION_ID, refundOrder.getTransactionId())
                        //微信支付系统生成的订单号 下单时生成(系统生成)
                        .put(WXOrderConstant.OUT_REFUND_NO, refundOrder.getOrderRefundSn())
                        //退款回调地址
                        .put(WXOrderConstant.NOTIFY_URL, wxConfig.getRefNotifyUrl());
                //金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分)
                rootNode.putObject(WXOrderConstant.AMOUNT)
                        //现阶段 total==refund 不支持部分退款
                        //原订单金额 total
                        .put(WXOrderConstant.AMOUNT_TOTAL, refundOrder.getRefundMoney().movePointRight(SystemConstant.NUM_TWO).intValue())
                        //退款金额 refund
                        .put(WXOrderConstant.AMOUNT_REFUND, refundOrder.getRefundMoney().movePointRight(SystemConstant.NUM_TWO).intValue())
                        //退款币种 CNY 人民币
                        .put(WXOrderConstant.AMOUNT_CURRENCY, wxConfig.getCurrency());
                objectMapper.writeValue(bos, rootNode);
                //3.调用微信退款接口
                httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
                //接口返回值
                CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
                String bodyAsString = EntityUtils.toString(response.getEntity());
                log.info("微信申请退款返回结果" + "response:" + bodyAsString);
                JsonNode refundNode = objectMapper.readTree(bodyAsString);
                // 修改订单退款状态 微信退款入数据库 库存恢复
                //1.修改订单退款状态
                String transactionId = refundNode.get(WXOrderConstant.TRANSACTION_ID).textValue();
                String status = refundNode.get(WXOrderConstant.STATUS).textValue();
                Boolean flag = iTicketOrderService.updateOrderStatus(transactionId, status);
                log.info("微信退款======母订单状态修改=={}===",flag);
                //2.微信退款入数据库
                Boolean insertRefund = iTicketOrderReturnService.insertRefund(refundNode, refundOrder);
                log.info("微信退款======退款订单入数据库=={}===",insertRefund);
                //3.库存恢复 实现 通过微信支付id(微信生成) 查询出母单id 根据母单id查询出所有子单 让子单库存恢复
                iTicketOrderService.restoreStock(transactionId);
                return status;
            }
        } catch (Exception e) {
            log.info("微信退款" + refundOrder.getOrderSn() + "失败");
            throw new YqsException(MessageEnum.NOT_REFUND.getCode(),"退款失败,请联系客服解决");
        }
        return null;
    }

3.微信退款回调

3.1controller

3.1.1 微信退款controller

@PostMapping("/wechatPayCallback")
    @ApiOperation("支付回调给微信确认")
    @ApiIgnore
    public String wechatCallback(HttpServletRequest request) {
        ToolWxConfig wxConfig = iToolWxConfigService.find();
        log.info("微信退款回调通知调用=============================");
        Gson gson = new Gson();
        Map<String,String> result = new HashMap(SystemConstant.NUM_16);
        result.put("code", "FAIL");
        result.put("message","失败");
            try {
                //微信回调信息校验
                // 构建request,传入必要参数
                Notification notification = WxPayUtil.verifyBack(request, wxConfig);
                log.info("=================微信验证签名成功=======成功时间=={}=====",notification.getCreateTime());
                // 思路: 验证订单 订单号是否存在 订单状态 通过缓存来做到 一回调验证多订单的类型
                // 生成订单的时候 把订单信息放入缓存中 order:key key为订单号 30min 通过获取 订单消息做到 快速验证 插入操作 用if 进行
                if (iToolWxConfigService.verifyCreateOrder(notification.getDecryptData())) {
                    log.info("==============================微信退款成功订单=====================================");
                    result.put("code", WXOrderConstant.WX_BACK_OK);
                    result.put("message", "支付回调成功");
                }
            } catch (ValidationException | ParseException | IOException e) {
                log.error("微信支付回调失败验证" + e);
            }
        log.info("微信返回结果"+result);
        return gson.toJson(result);
    }

3.1.2验证回调类 (和微信支付回调验证一样 如果看了之前的可以不用写)

/**
     *回调验证
     * @param request 微信回调请求
     * @param wxConfig 微信基本配置信息
     * @return String
     * @author zhangjunrong
     * @date 2022/4/21 15:02
     */
    public static Notification verifyBack(HttpServletRequest request, ToolWxConfig wxConfig) throws IOException, ValidationException, ParseException {
        //应答报文主体
        BufferedReader br = request.getReader();
        String str;
        StringBuilder builder = new StringBuilder();
        while ((str = br.readLine()) != null) {
            builder.append(str);
        }
        // 构建request,传入必要参数
        //参数 1.微信序列号 2.应答随机串 3.应答时间戳 4.应答签名 5.应答报文主体
        NotificationRequest notificationRequest = new NotificationRequest.Builder()
                .withSerialNumber(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SERIAL))
                .withNonce(request.getHeader(WechatPayHttpHeaders.WECHATPAY_NONCE))
                .withTimestamp(request.getHeader(WechatPayHttpHeaders.WECHATPAY_TIMESTAMP))
                .withSignature(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SIGNATURE))
                .withBody(builder.toString())
                .build();
        NotificationHandler handler = new NotificationHandler(WxPayUtil.getVerifier(wxConfig), wxConfig.getApiV3key().getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        log.info("验签和解析请求体==============================开始验证==============================");
        Notification notification = handler.parse(notificationRequest);
        Assert.assertNotNull(notification);
        return notification;
    }

3.1.3 service 退款回调验证

 /**
      *微信支付回调验证判定 核对成功 数据异步入库
      * @param
      * @param decryptOrder
      * @return Boolean
      * @author zhangjunrong
      * @date 2022/5/3 8:23
      */
@Override
    public Boolean verifyCreateOrder(String decryptOrder) {
        //在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
        //实现: 加入一把可重入锁
        if (reentrantLock.tryLock()) {
            try {
                log.info("===================================进入微信支付回调核对订单中========================================");
                ObjectMapper objectMapper = new ObjectMapper();
                //微信回调 解密后 信息
                JsonNode node = objectMapper.readTree(decryptOrder);
                //获取订单商户号
                String orderNo = node.get(WXOrderConstant.OUT_TRADE_NO).textValue();
                //1.获取redis中的订单信息
                OrderTotalRedisRO totalRedisRO = (OrderTotalRedisRO) redisUtil.get(SystemConstant.ORDER_TOTAL + orderNo);
                //1.1微信 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
                //实现方法: 通过订单状态来 判定是否要进行判定 出未支付以外的 都返回 结果 通过缓存获取到支付状态 防止微信重复调用该方法
                //如果回调 缓存中记录清除说明 入库判定等等成功 直接返回true
                if (ObjectUtil.isEmpty(totalRedisRO)) {
                    return true;
                }
                log.info(node.get(WXOrderConstant.OUT_TRADE_NO) + "订单回调信息记录:订单状态:" + orderNo);
                //2.如果回调 支付类型为成功 核对金额 入数据库
                //获取支付状态
                String tradeState = node.get(WXOrderConstant.TRADE_STATE).textValue();
                if (StrUtil.equals(WXOrderConstant.WX_BACK_OK, tradeState)) {
                    //redis缓存中的金额
                    int redisTotal = totalRedisRO.getTicketOrder().getPayMoney().movePointRight(SystemConstant.NUM_TWO).intValue();
                    //校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。
                    //缓存中存入的用户支付金额 totalRedisRO.getTicketOrder().getTotalMoney().movePointRight(SystemConstant.NUM_TWO).intValue()
                    if (WxPayUtil.verifyMoney(node, redisTotal)) {
                        //3.对应的数据入库
                        log.info("redis入数据库信息======================" + totalRedisRO);
                        if (!ObjectUtil.isEmpty(totalRedisRO)) {
                            //缓存放入一个状态 表明已操作该订单 存放200秒
                            // 支付成功就把redis中缓存记录清除
                            totalRedisRO.getTicketOrder().setOrderStatus(SystemConstant.NUM_ONE);
                            redisUtil.del(SystemConstant.ORDER_TOTAL + orderNo);
                            //订单入库
                            iTicketOrderService.createAllTicket(totalRedisRO, node.get(WXOrderConstant.TRANSACTION_ID).textValue());
                        }
                    }
                    //为什么没有插入成功也返回true?
                    //因为就算数据库没有入成功 但是金额 订单校验等等的都通过
                    //说明数据库入库失败
                    //如果入库失败 让用户联系客服接入管理 [钱一定要收下来]
                    return true;
                }
            } catch (Exception e) {
                log.error("订单支付异常===>订单回调信息记录:订单状态:" + decryptOrder);
            }finally {
                //释放锁
                reentrantLock.unlock();
            }
        }
        return false;
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新生代农民工-小王八

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值