| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- package com.kym.service.wechat.impl;
- import cn.dev33.satoken.stp.StpUtil;
- import cn.hutool.core.date.DateTime;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.core.date.LocalDateTimeUtil;
- import cn.hutool.core.io.IoUtil;
- import cn.hutool.core.util.RandomUtil;
- import com.alibaba.fastjson2.JSONObject;
- import com.kym.common.config.WxPayConfig;
- import com.kym.common.constant.ResponseEnum;
- import com.kym.common.exception.BusinessException;
- import com.kym.common.utils.CommUtil;
- import com.kym.common.utils.LambadaTools;
- import com.kym.common.utils.OrderUtils;
- import com.kym.entity.Account;
- import com.kym.entity.*;
- import com.kym.service.*;
- import com.kym.service.wechat.WxPayService;
- import com.wechat.pay.java.core.Config;
- import com.wechat.pay.java.core.RSAAutoCertificateConfig;
- import com.wechat.pay.java.core.exception.ValidationException;
- import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
- import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
- import com.wechat.pay.java.core.notification.NotificationConfig;
- import com.wechat.pay.java.core.notification.NotificationParser;
- import com.wechat.pay.java.core.notification.RequestParam;
- import com.wechat.pay.java.service.payments.jsapi.JsapiService;
- import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
- import com.wechat.pay.java.service.payments.jsapi.model.Amount;
- import com.wechat.pay.java.service.payments.jsapi.model.*;
- import com.wechat.pay.java.service.payments.model.Transaction;
- import com.wechat.pay.java.service.refund.RefundService;
- import com.wechat.pay.java.service.refund.model.*;
- import jakarta.annotation.PostConstruct;
- import jakarta.servlet.ServletInputStream;
- import jakarta.servlet.http.HttpServletRequest;
- import lombok.SneakyThrows;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.core.io.ClassPathResource;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.nio.charset.StandardCharsets;
- import java.time.LocalDateTime;
- import java.util.ArrayList;
- import java.util.Map;
- import java.util.concurrent.atomic.AtomicInteger;
- /**
- * @author skyline
- * @description 微信支付
- * @date 2023-08-10 14:03
- */
- @Service
- public class WxPayServiceImpl implements WxPayService {
- private static final Logger LOGGER = LoggerFactory.getLogger(WxPayServiceImpl.class);
- public static JsapiService jsapiService;
- public static RefundService refundService;
- public static Config config;
- private final WxPayConfig conf;
- private final WalletDetailService walletDetailService;
- private final PayLogService payLogService;
- private final AccountService accountService;
- private final RefundLogService refundLogService;
- private final ActivityService activityService;
- private final UserRechargeRightsService userRechargeRightsService;
- private final RechargeConfigService rechargeConfigService;
- private final StationAccountService stationAccountService;
- private final SplitRecordService splitRecordService;
- /**
- * 微信支付专用,支持自动签名验签解密等
- */
- private OkHttpClientAdapter wxHttpClient;
- public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService,
- PayLogService payLogService, AccountService accountService,
- RefundLogService refundLogService,
- ActivityService activityService, UserRechargeRightsService userRechargeRightsService,
- RechargeConfigService rechargeConfigService, StationAccountService stationAccountService, SplitRecordService splitRecordService) {
- this.conf = conf;
- this.walletDetailService = walletDetailService;
- this.payLogService = payLogService;
- this.accountService = accountService;
- this.refundLogService = refundLogService;
- this.activityService = activityService;
- this.userRechargeRightsService = userRechargeRightsService;
- this.rechargeConfigService = rechargeConfigService;
- this.stationAccountService = stationAccountService;
- this.splitRecordService = splitRecordService;
- }
- /**
- * 商户订单号查询订单
- */
- public static Transaction queryOrderByOutTradeNo() {
- QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
- // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
- // 调用接口
- return jsapiService.queryOrderByOutTradeNo(request);
- }
- /**
- * 初始化
- *
- * @throws IOException
- */
- @PostConstruct
- void init() throws IOException {
- String privateKey;
- File file = new File(conf.getKeyPath());
- if (file.exists()) {
- privateKey = IoUtil.read(new FileInputStream(file), StandardCharsets.UTF_8);
- } else {
- ClassPathResource resource = new ClassPathResource(conf.getKeyPath());
- privateKey = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
- }
- config = new RSAAutoCertificateConfig.Builder()
- .merchantId(conf.getMchid()) // 商户号
- .privateKey(privateKey)//商户API私钥路径
- .merchantSerialNumber(conf.getMchsn()) // 商户证书序列号
- .apiV3Key(conf.getV3key()) // 商户APIV3密钥
- .build();
- jsapiService = new JsapiService.Builder().config(config).build();
- refundService = new RefundService.Builder().config(config).build();
- wxHttpClient = (OkHttpClientAdapter) new DefaultHttpClientBuilder().newInstance().config(config).build();
- // // 初始化微信电子发票开发配置,主要是回调地址 todo 区块链电子发票开通后放开
- // devConfig();
- }
- /**
- * 回调签名验证失败排查指引 (body中字段顺序变化造成验签失败是个大坑)
- * https://developers.weixin.qq.com/community/pay/article/doc/0004a879f60928d89340b8b9f64c13
- * https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/issues/152
- *
- * @param request
- * @return
- */
- @SneakyThrows
- Object[] handleWxNotify(HttpServletRequest request) {
- var no = RandomUtil.randomInt(1000, 9999);
- var signature = request.getHeader("Wechatpay-Signature");
- var serial = request.getHeader("Wechatpay-Serial");
- var nonce = request.getHeader("Wechatpay-Nonce");
- var timestamp = request.getHeader("Wechatpay-Timestamp");
- var signatureType = request.getHeader("Wechatpay-Signature-Type");
- // 应对探测流量
- if (signature.contains("SIGNTEST")) {
- throw new BusinessException("接收到签名探测流量");
- }
- LOGGER.info("微信支付回调{}: Request参数: signature:{},serial:{},nonce:{},timestamp:{},signatureType:{}", no, signature, serial, nonce, timestamp, signatureType);
- ServletInputStream inputStream = request.getInputStream();
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- for (int lenght; (lenght = inputStream.read(buffer)) != -1; ) {
- result.write(buffer, 0, lenght);
- }
- var body = result.toString();
- LOGGER.info("微信支付回调{}:\nBody数据:\n{}", no, result);
- // 构造 RequestParam
- RequestParam requestParam = new RequestParam.Builder()
- .serialNumber(serial)
- .nonce(nonce) // 随机数
- .signature(signature)
- .timestamp(timestamp)
- .body(body)
- .build();
- LOGGER.info("微信支付回调{}:构造 RequestParam完毕", no);
- // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
- // 初始化 NotificationParser
- NotificationParser parser = new NotificationParser((NotificationConfig) config);
- return new Object[]{requestParam, parser, no};
- }
- /**
- * JSAPI支付下单
- */
- @Override
- @Transactional
- public PrepayWithRequestPaymentResponse wxPay(Long rechargeConfigId, String stationId) {
- // 充值配置
- var rechargeConfig = rechargeConfigService.getById(rechargeConfigId);
- if ((rechargeConfig == null) || rechargeConfig.getRechargeAmount() <= 0) {
- throw new BusinessException(ResponseEnum.WX_PAY_AMOUNT_ERROR);
- }
- var rechargeAmount = rechargeConfig.getRechargeAmount();
- var openid = StpUtil.getSession().getString("openid");
- var userId = StpUtil.getLoginIdAsLong();
- // 生成订单号
- String outTradeNo = OrderUtils.getOrderNo();
- // 创建钱包流水
- var walletDetail = new WalletDetail()
- .setType(WalletDetail.TYPE_充值)
- .setStatus(WalletDetail.STATUS_待确认)
- .setUserId(userId)
- .setAmount(rechargeAmount)
- .setOrderNo(outTradeNo);
- walletDetailService.save(walletDetail);
- // request.setXxx(val)设置所需参数,具体参数可见Request定义
- PrepayRequest request = new PrepayRequest();
- Amount amount = new Amount();
- // 传入金额单位为分
- amount.setTotal(rechargeAmount);
- request.setAmount(amount);
- request.setAppid(conf.getAppid());
- request.setMchid(conf.getMchid());
- request.setDescription("超级进化车生活充值");
- request.setNotifyUrl(conf.getNotifyUrl());
- request.setOutTradeNo(outTradeNo);
- // 把stationId传给微信,微信回调时携带再取出,记录是充值到哪个站点
- request.setAttach(stationId);
- Payer payer = new Payer();
- payer.setOpenid(openid);
- request.setPayer(payer);
- JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
- // response包含了调起支付所需的所有参数,可直接用于前端调起支付
- return service.prepayWithRequestPayment(request);
- }
- /**
- * 微信支付订单号查询订单
- */
- public Transaction queryOrderById(String transactionId) {
- QueryOrderByIdRequest request = new QueryOrderByIdRequest();
- // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
- request.setMchid(conf.getMchid());
- request.setTransactionId(transactionId);
- // 调用接口
- return jsapiService.queryOrderById(request);
- }
- /**
- * 关闭订单
- *
- * @param outTradeNo
- */
- public void closeOrder(String outTradeNo) {
- CloseOrderRequest request = new CloseOrderRequest();
- // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
- request.setMchid(conf.getMchid());
- request.setOutTradeNo(outTradeNo);
- // 调用接口
- jsapiService.closeOrder(request);
- }
- /**
- * 微信支付结果通知(充值完成)
- *
- * @param request
- * @return
- */
- @SneakyThrows
- @Override
- @Transactional(rollbackFor = Exception.class)
- public ResponseEntity<Object> wxNotify(HttpServletRequest request) {
- try {
- var notifyRes = handleWxNotify(request);
- // 以支付通知回调为例,验签、解密并转换成 Transaction
- Transaction transaction = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], Transaction.class);
- LOGGER.info("微信支付回调{}:验签解密完毕,数据:{}", notifyRes[2], transaction);
- // 判断是否已经接收处理过通知
- if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
- return ResponseEntity.status(HttpStatus.OK).build();
- }
- DateTime dt = DateUtil.parse(transaction.getSuccessTime());
- LocalDateTime successTime = LocalDateTimeUtil.of(dt);
- // 钱包流水
- var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo(), WalletDetail.TYPE_充值);
- if (walletDetail != null) {
- // 更新余额
- var account = accountService.getAccountByUserId(walletDetail.getUserId());
- accountService.lambdaUpdate().setSql("balance = (balance + %d)".formatted(transaction.getAmount().getTotal()))
- .eq(Account::getUserId, walletDetail.getUserId()).update();
- walletDetail.setStatus(WalletDetail.STATUS_已确认); //已确认
- walletDetail.setCurrency(transaction.getAmount().getCurrency());
- walletDetail.setAmount(transaction.getAmount().getTotal());
- walletDetail.setBeforeBalance(account.getBalance());
- walletDetail.setAfterBalance(account.getBalance() + walletDetail.getAmount());
- walletDetail.setTransactionTime(successTime);
- walletDetailService.updateById(walletDetail);
- // 异步处理充值服务费打折权益活动相关逻辑
- activityService.handleRechargeActivity(walletDetail.getUserId(), transaction.getAmount().getTotal());
- // 支付记录
- var payLog = new PayLog();
- payLog.setUserId(walletDetail.getUserId());
- payLog.setOpenid(transaction.getPayer().getOpenid());
- payLog.setBankType(transaction.getBankType());
- payLog.setMchId(transaction.getMchid());
- payLog.setOutTradeNo(transaction.getOutTradeNo());
- payLog.setTransactionId(transaction.getTransactionId());
- payLog.setSuccessTime(successTime);
- payLog.setTradeType(transaction.getTradeType().name());
- payLog.setTradeState(transaction.getTradeState().name());
- payLog.setAttach(transaction.getAttach());
- var totalAmount = transaction.getAmount().getTotal();
- payLog.setTotal(totalAmount);
- payLog.setCurrency(transaction.getAmount().getCurrency());
- payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
- payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
- payLogService.save(payLog);
- // 用户(此时要知道用户归属的站点)充值的资金先进到洗车站商户账户的基本户和冻结户,然后在消费时再将冻结户金额进行分润
- var stationId = transaction.getAttach();
- // 70%进入站点商户基本户,30%进入站点商户冻结户 todo 后面将比例进行配置化(需要考虑历史数据处理)
- var stationBasicAmount = (int) (totalAmount * 0.7);
- var stationFreezeAmount = totalAmount - stationBasicAmount;
- stationAccountService.lambdaUpdate()
- .setSql("balance = (balance + %d), frozen_amount = (frozen_amount + %d)".formatted(stationBasicAmount, stationFreezeAmount))
- .eq(StationAccount::getStationId, stationId)
- .update();
- // 分账记录
- var splitRecord = new SplitRecord()
- .setFromStationId(stationId)
- .setToStationId(stationId)
- .setTradeNo(transaction.getTransactionId())
- .setAmount(totalAmount)
- .setType(SplitRecord.TYPE_RECHARGE);
- splitRecordService.save(splitRecord);
- LOGGER.info("微信支付回调{}:业务处理结束", notifyRes[2]);
- return ResponseEntity.status(HttpStatus.OK).build();
- } else {
- LOGGER.error("微信支付通知处理异常,资金流水为空,回调信息:{}", transaction);
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "资金流水为空"));
- }
- } catch (Exception e) {
- if (e instanceof ValidationException) {
- // 签名验证失败,返回 401 UNAUTHORIZED 状态码
- LOGGER.error("微信支付通知验签失败", e);
- }
- if (e instanceof BusinessException) {
- LOGGER.error("业务异常", e);
- }
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
- }
- }
- /**
- * 申请退款
- */
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void applyWxRefund(String reason) {
- var userId = StpUtil.getLoginIdAsLong();
- // var chargeOrder = chargeOrderService.getChargingOrderByUserId(userId);
- // if (chargeOrder != null) {
- // throw new BusinessException("存在未完结的订单,请等待所有订单完结之后重试");
- // }
- // todo 洗车未完结订单校验
- var account = accountService.getAccountByUserId(userId);
- if (account.getBalance() <= 0) {
- throw new BusinessException("账户余额不足,无需退款");
- }
- // 校验余额大于优惠金额
- if (account.getBalance() <= account.getDiscountAmount()) {
- throw new BusinessException("不可退金额高于钱包余额,不支持退款");
- }
- // 将余额转移至冻结余额
- accountService.lambdaUpdate().setSql(" frozen_amount = (frozen_amount + balance) ,balance = 0").eq(Account::getUserId, userId).update();
- // 退款时,充值权益失效(权益余额转入冻结余额,权益状态设置为失效)
- userRechargeRightsService.lambdaUpdate()
- .setSql("frozen_balance = (rights_balance + frozen_balance) , rights_balance = 0")
- .set(UserRechargeRights::getStatus, UserRechargeRights.STATUS_无效)
- .eq(UserRechargeRights::getUserId, userId)
- .eq(UserRechargeRights::getStatus, UserRechargeRights.STATUS_有效)
- .update();
- // 余减去优惠金额作为退款金额
- AtomicInteger refundAmount = new AtomicInteger(account.getBalance() - account.getDiscountAmount());
- var originalRefundAmount = BigDecimal.valueOf(refundAmount.get());
- // 充值记录
- var payLogs = payLogService.lambdaQuery().eq(PayLog::getUserId, userId).eq(PayLog::getTradeState, PayLog.STATUS_充值成功).orderByDesc(PayLog::getSuccessTime).list();
- // 历史退款金额
- var refundsAmount = refundLogService.lambdaQuery().eq(RefundLog::getUserId, userId).list().stream().mapToInt(RefundLog::getRefund).sum();
- // 充值金额-历史退款金额不足以支付余额退款
- if (!CommUtil.isEmptyOrNull(payLogs)) {
- var total = payLogs.stream().mapToInt(PayLog::getTotal).sum();
- if ((total - refundsAmount) < account.getBalance()) {
- LOGGER.error("用户:{},历史充值金额:{},不足以支付余额退款金额:{}", userId, total - refundsAmount, account.getBalance());
- throw new BusinessException("充值金额不足以支付余额退款");
- }
- }
- // 最后一次的充值金额可以覆盖退款金额
- if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() >= refundAmount.get()) {
- // 退款日志
- var refundLog = new RefundLog().setUserId(payLogs.get(0).getUserId()).setOutTradeNo(payLogs.get(0).getOutTradeNo())
- .setTotal(payLogs.get(0).getTotal())
- .setRefund(refundAmount.get())
- .setDiscountAmount(account.getDiscountAmount())
- .setOutRefundNo(OrderUtils.getOrderNo());
- refundLogService.save(refundLog);
- } else if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() < refundAmount.get()) {
- // 最后一次的充值金额不能覆盖退款金额,拆分成多笔退款
- int amount = 0;
- // 用来退款的充值记录
- var refundPayLogs = new ArrayList<PayLog>();
- for (PayLog payLog : payLogs) {
- amount += payLog.getTotal();
- refundPayLogs.add(payLog);
- if (amount >= refundAmount.get()) {
- break;
- }
- }
- // 需要退款的记录数量
- var size = refundPayLogs.size();
- var refundLogList = new ArrayList<RefundLog>(size);
- var newRefundLogList = new ArrayList<RefundLog>(size);
- refundPayLogs.forEach(LambadaTools.forEachWithIndex((item, index) -> {
- // 如果不是最后一笔,金额全退,最后一笔退剩余的金额
- if (index < size - 1) {
- refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
- .setTotal(item.getTotal()).setRefund(item.getTotal()).setOutRefundNo(OrderUtils.getOrderNo()));
- refundAmount.addAndGet(-item.getTotal());
- } else {
- refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
- .setTotal(item.getTotal()).setRefund(refundAmount.get()).setOutRefundNo(OrderUtils.getOrderNo()));
- }
- }));
- // 不可退金额按退款金额比例放入每笔退款中
- refundLogList.forEach(LambadaTools.forEachWithIndex((refundLog, index) -> {
- int discountAmount;
- if (index < size - 1) {
- discountAmount = BigDecimal.valueOf(account.getDiscountAmount()).multiply((BigDecimal.valueOf(refundLog.getRefund()).divide(originalRefundAmount, 2, RoundingMode.HALF_UP))).intValue();
- } else {
- // 前面存在精度误差,最后一个元素用总数相减最精确
- discountAmount = account.getDiscountAmount() - newRefundLogList.stream().mapToInt(RefundLog::getDiscountAmount).sum();
- }
- refundLog.setDiscountAmount(discountAmount);
- refundLog.setReason(CommUtil.isEmptyOrNull(reason) ? reason : JSONObject.parseObject(reason).getString("reason"));
- newRefundLogList.add(index, refundLog);
- }));
- refundLogService.saveBatch(newRefundLogList);
- } else {
- // 退款异常
- LOGGER.error("退款异常:userId:{},退款金额:{},payLogs:{}", userId, originalRefundAmount, payLogs);
- throw new BusinessException("退款异常");
- }
- }
- /**
- * 处理退款
- *
- * @return
- */
- @Override
- public void wxRefund(long refundLogId) {
- // 通过退款申请id获取退款申请记录
- var refundLog = refundLogService.getById(refundLogId);
- // 钱包流水
- var walletDetail = new WalletDetail()
- .setType(WalletDetail.TYPE_提现)
- .setStatus(WalletDetail.STATUS_待确认)
- .setUserId(refundLog.getUserId())
- .setAmount(refundLog.getRefund())
- .setOrderNo(refundLog.getOutRefundNo());
- walletDetailService.save(walletDetail);
- CreateRequest request = new CreateRequest();
- // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
- // 原始第三方订单号
- request.setOutTradeNo(refundLog.getOutTradeNo());
- // 退款订单号
- request.setOutRefundNo(refundLog.getOutRefundNo());
- // 退款原因
- request.setReason("用户申请退款");
- // 回调通知地址
- request.setNotifyUrl(conf.getRefundNotifyUrl());
- var amount = new AmountReq();
- // 退款金额
- amount.setRefund((long) refundLog.getRefund());
- amount.setTotal((long) refundLog.getTotal());
- amount.setCurrency(refundLog.getCurrency());
- request.setAmount(amount);
- var refund = refundService.create(request);
- refundLog.setChannel(refund.getChannel().name());
- refundLog.setFundsAccount(refund.getFundsAccount().name());
- refundLog.setCurrency(refund.getAmount().getCurrency());
- refundLog.setAdminUserId(StpUtil.getLoginIdAsLong());
- refundLog.setAdminUsername(StpUtil.getSession().getString("username"));
- refundLog.setStatus(RefundLog.STATUS_退款处理中);
- refundLogService.updateById(refundLog);
- }
- /**
- * 查询单笔退款(通过商户退款单号)
- *
- * @return
- */
- @Override
- public Refund queryByOutRefundNo(String outRefundNo) {
- QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
- // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
- request.setOutRefundNo(outRefundNo);
- // 调用接口
- return refundService.queryByOutRefundNo(request);
- }
- /**
- * 微信退款结果通知
- *
- * @param request
- * @return
- */
- @Override
- @Transactional(rollbackFor = Exception.class)
- public ResponseEntity<Object> wxRefundNotify(HttpServletRequest request) {
- var notifyRes = handleWxNotify(request);
- try {
- // 以支付通知回调为例,验签、解密并转换成 RefundNotification
- RefundNotification refundNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], RefundNotification.class);
- LOGGER.info("微信退款回调{}:验签解密完毕,数据:\n{}", notifyRes[2], refundNotification);
- //退款日志在申请时插入,接收通知时更新
- var refundLog = refundLogService.lambdaQuery().eq(RefundLog::getOutRefundNo, refundNotification.getOutRefundNo()).one();
- // 防止重复处理消息
- if (RefundLog.STATUS_退款成功.equals(refundLog.getStatus())) {
- return ResponseEntity.status(HttpStatus.OK).build();
- }
- DateTime dt = DateUtil.parse(refundNotification.getSuccessTime());
- LocalDateTime successTime = LocalDateTimeUtil.of(dt);
- refundLogService.lambdaUpdate()
- .set(RefundLog::getRefundId, refundNotification.getRefundId())
- .set(RefundLog::getTransactionId, refundNotification.getTransactionId())
- .set(RefundLog::getUserReceivedAccount, refundNotification.getUserReceivedAccount())
- .set(RefundLog::getSuccessTime, successTime)
- .set(RefundLog::getStatus, refundNotification.getRefundStatus().name())
- .set(RefundLog::getTotal, refundNotification.getAmount().getTotal().intValue())
- .set(RefundLog::getRefund, refundNotification.getAmount().getRefund().intValue())
- .eq(RefundLog::getId, refundLog.getId()).update();
- if (RefundLog.STATUS_退款成功.equals(refundNotification.getRefundStatus().name())) {
- // 冻结金额扣减此次(退款金额+优惠金额),优惠金额字段减去申请退款时的优惠金额
- var account = accountService.getAccountByUserId(refundLog.getUserId());
- accountService.lambdaUpdate().setSql("frozen_amount = (frozen_amount - (%d + %d)) , discount_amount = (discount_amount - %d)"
- .formatted(refundNotification.getAmount().getRefund().intValue(), refundLog.getDiscountAmount(), refundLog.getDiscountAmount()))
- .eq(Account::getUserId, refundLog.getUserId()).update();
- // 更新资金流水
- var walletDetail = walletDetailService.getWalletDetailByOrderNo(refundNotification.getOutRefundNo(), WalletDetail.TYPE_提现);
- walletDetailService.lambdaUpdate()
- .set(WalletDetail::getStatus, WalletDetail.STATUS_已确认)
- .set(WalletDetail::getTransactionId, refundNotification.getTransactionId())
- .set(WalletDetail::getTransactionTime, successTime)
- .set(WalletDetail::getAmount, refundNotification.getAmount().getRefund().intValue())
- .set(WalletDetail::getBeforeBalance, account.getBalance())
- .set(WalletDetail::getAfterBalance, account.getBalance() - refundNotification.getAmount().getRefund().intValue())
- .set(WalletDetail::getTransactionTime, successTime)
- .eq(WalletDetail::getId, walletDetail.getId()).update();
- LOGGER.info("微信退款回调{}:业务处理结束", notifyRes[2]);
- return ResponseEntity.status(HttpStatus.OK).build();
- } else {
- // 退款失败
- LOGGER.error("微信退款失败,用户id:{},退款状态:{} \n 退款结果通知详情:{}", refundLog.getUserId(), refundNotification.getRefundStatus().name(), refundNotification);
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "退款处理异常"));
- }
- } catch (ValidationException e) {
- // 签名验证失败,返回 401 UNAUTHORIZED 状态码
- LOGGER.error("微信退款通知验签失败", e);
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
- }
- }
- //================================================================发票=====================================================================
- }
|