From 58a8acceb63d52377da4b837e859f2e03d2bce1b Mon Sep 17 00:00:00 2001 From: gongzuming <gongzuming> Date: 星期一, 14 十月 2024 08:44:40 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master-v3' into master-v2 --- src/main/java/com/mzl/flower/service/payment/UserPaymentSybService.java | 600 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 600 insertions(+), 0 deletions(-) diff --git a/src/main/java/com/mzl/flower/service/payment/UserPaymentSybService.java b/src/main/java/com/mzl/flower/service/payment/UserPaymentSybService.java new file mode 100644 index 0000000..39da81a --- /dev/null +++ b/src/main/java/com/mzl/flower/service/payment/UserPaymentSybService.java @@ -0,0 +1,600 @@ +package com.mzl.flower.service.payment; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.mzl.flower.config.SybPaymentProperties; +import com.mzl.flower.config.exception.ValidationException; +import com.mzl.flower.config.security.SecurityUtils; +import com.mzl.flower.constant.Constants; +import com.mzl.flower.dto.request.payment.UserPaymentDTO; +import com.mzl.flower.entity.flower.Flower; +import com.mzl.flower.entity.payment.*; +import com.mzl.flower.mapper.flower.FlowerMapper; +import com.mzl.flower.mapper.payment.*; +import com.mzl.flower.pay.SybConstants; +import com.mzl.flower.pay.SybPayService; +import com.mzl.flower.pay.SybUtil; +import com.mzl.flower.service.BaseService; +import com.mzl.flower.service.coupon.CouponRecordService; +import com.mzl.flower.service.flower.FlowerService; +import com.mzl.flower.service.point.PointGoodsService; +import com.mzl.flower.utils.UUIDGenerator; +import io.micrometer.core.instrument.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +@Service +@Transactional +@Slf4j +public class UserPaymentSybService extends BaseService { + + @Autowired + private UserPaymentMapper userPaymentMapper; + + @Autowired + private RedisLockService lockService; + + @Autowired + private OrderMapper orderMapper; + + @Autowired + private OrderItemMapper orderItemMapper; + + @Autowired + private FlowerMapper flowerMapper; + + @Autowired + private FlowerService flowerService; + + @Autowired + private DeliveryOrderService deliveryOrderService; + + @Autowired + private OrderRefundMapper orderRefundMapper; + + @Autowired + private OrderPointGoodsMapper orderPointGoodsMapper; + + @Autowired + private PointGoodsService pointGoodsService; + + @Autowired + private CouponRecordService couponRecordService; + + @Autowired + private OrderService orderService; + + @Autowired + private SybPaymentProperties sybPaymentProperties; + + /** + * 通联支付 + * + * @param order + * @return + */ + public Map prepay(Order order) throws Exception { + String userId = SecurityUtils.getUserId(); + UserPayment up = prepareUserPayment(userId, order); + log.info("UserPayment: " + toJSONString(up)); + + SybPayService service = new SybPayService(); + long trxamt = prepareAmount(up.getPaymentAmount()); + String reqsn = order.getId(); + String unireqsn = ""; + String body = "通联支付"; + String remark = "通联支付"; + String notifyUrl = sybPaymentProperties.getCallBackUrl() + "/flower/api/ua/notify/syb/paid"; + + // 调用接口 + Map map = service.createOrder(trxamt, reqsn, unireqsn, body, remark, notifyUrl); + + up.setPrepayResponse(toJSONString(map)); + + userPaymentMapper.insert(up); + + return map; + } + + private Integer prepareAmount(BigDecimal amount){ + return amount.multiply(new BigDecimal(100)).intValue(); + } + + private UserPayment prepareUserPayment(String userId, Order order){ + UserPayment up = new UserPayment(); + up.setId(UUIDGenerator.getUUID()); + up.setUserId(userId); + up.setOrderId(order.getId()); + up.setPaymentAmount(order.getTotalAmount()); + up.setPaymentTime(LocalDateTime.now()); + up.create(userId); + + return up; + } + + /** + * 动态遍历获取所有收到的参数,此步非常关键,因为收银宝以后可能会加字段,动态获取可以兼容由于收银宝加字段而引起的签名异常 + * @param request + * @return + */ + private TreeMap<String, String> getParams(HttpServletRequest request){ + TreeMap<String, String> map = new TreeMap<>(); + Map reqMap = request.getParameterMap(); + for(Object key:reqMap.keySet()){ + String value = ((String[])reqMap.get(key))[0]; + System.out.println(key+";"+value); + map.put(key.toString(),value); + } + return map; + } + + public String handlePayCallback(HttpServletRequest request) { + try { + request.setCharacterEncoding("UTF-8");//通知传输的编码为GBK + TreeMap<String,String> params = getParams(request);//动态遍历获取所有收到的参数,此步非常关键,因为收银宝以后可能会加字段,动态获取可以兼容 + + String appkey = ""; + if("RSA".equals(params.get("signtype"))) + appkey = SybConstants.SYB_RSATLPUBKEY; + else if("SM2".equals(params.get("signtype"))) + appkey = SybConstants.SYB_SM2TLPUBKEY; + else + appkey = SybConstants.SYB_MD5_APPKEY; + boolean isSign = SybUtil.validSign(params, appkey, params.get("signtype"));// 接受到推送通知,首先验签 + log.info("验签结果:" + isSign); + + //验签完毕进行业务处理 + if(isSign){ + String originalXml = toJSONString(params); + log.info("transaction: " + originalXml); + String outTradeNo = params.get("cusorderid");//统一下单对应的reqsn订单号 + String transactionId = params.get("trxid");//通联收银宝交易流水号 + String orderId = outTradeNo; + Integer trxamt = Integer.parseInt(params.get("trxamt"));//交易金额 单位:分 + log.info("======trxamt: " + trxamt); + + String trxstatus = params.get("trxstatus");//支付状态 + String status = Constants.PAYMENT_STATUS.FAILED.name(); + //交易状态详见交易返回码说明 + /*0000:交易成功 + 错误码为空: 交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(例如30秒)查询交易 + 1001:交易不存在 + 2008或者2000 : 交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易 + 3开头的错误码代表交易失败 + 3888-流水号重复 + 3889-交易控制失败,具体原因看errmsg + 3099-渠道商户错误 + 3014-交易金额小于应收手续费 + 3031-校验实名信息失败 + 3088-交易未支付(在查询时间区间内未成功支付,如已影响资金24小时内会做差错退款处理) + 3089-撤销异常,如已影响资金24小时内会做差错退款处理 + 3045-其他错误,具体原因看errmsg + 3999-其他错误,具体原因看errmsg + 其他3开头的错误码代表交易失败,具体原因请读取errmsg + */ + if ("0000".equals(trxstatus)){ + status = Constants.PAYMENT_STATUS.SUCCESS.name(); + } + + UserPaymentDTO dto = new UserPaymentDTO(); + dto.setOrderId(orderId); + dto.setTransactionId(transactionId); + dto.setOutTradeNo(outTradeNo); + dto.setOriginalXml(originalXml); + dto.setPaymentAmountCallback(trxamt + ""); + dto.setStatus(status); + + saveCallbackInfo(dto, Constants.ORDER_STATUS_BACKEND.PAYMENT.name()); + } + } catch (Exception e) { + log.error("解析付款通知出错:{}", e.getMessage(), e); + } + + return "success"; + } + + public void saveCallbackInfo(UserPaymentDTO dto, String orderStatus){ + String orderId = dto.getOrderId(); + boolean lock = lockService.getObjectLock(RedisLockService.LOCK_KEY_PAYMENT_NOTIFY_, orderId); + if(!lock){ + return; + } + + try { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", orderId)); + BeanUtils.copyProperties(dto, up); + + up.setPaymentTimeCallback(LocalDateTime.now()); + userPaymentMapper.updateById(up); + + Order order = orderMapper.selectById(orderId); + if(Constants.ORDER_STATUS_BACKEND.PAYMENT.name().equals(orderStatus)) { + order.setPaymentTrId(dto.getTransactionId()); + if (up.getPaymentAmountCallback() != null) { + order.setPaymentAmount(new BigDecimal(up.getPaymentAmountCallback()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));//转换成单位元 + } else { + order.setPaymentAmount(order.getTotalAmount()); + } + order.setPaymentTime(up.getPaymentTimeCallback()); + order.setStatus(Constants.ORDER_STATUS.SEND.name()); + } else if (Constants.ORDER_STATUS_BACKEND.CANCEL.name().equals(orderStatus)){ + order.setStatus(Constants.ORDER_STATUS.CANCEL.name()); + } + order.setStatusBackend(orderStatus); + orderMapper.updateById(order); + + if(Constants.ORDER_STATUS_BACKEND.PAYMENT.name().equals(orderStatus)) { + postPayment(order); + } else if (Constants.ORDER_STATUS_BACKEND.CANCEL.name().equals(orderStatus)){ + releasePrepayLock(order); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + lockService.releaseObjectLock(RedisLockService.LOCK_KEY_PAYMENT_NOTIFY_, orderId); + } + } + + private void postPayment(Order order){ + log.info("回调后处理订单信息:" + toJSONString(order)); + //创建配送单 + deliveryOrderService.createDeliveryOrder(order); + flowerService.updateFlowerSales(order); + } + + public boolean checkOrderStatusPayAgain(String outTradeNo) throws Exception { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", outTradeNo)); + String s = up.getStatus(); + if(StringUtils.isNotEmpty(s)){ + return true; + } + + SybPayService service = new SybPayService(); + Map<String,String> params = service.query(outTradeNo, up.getTransactionId()); + + String originalXml = toJSONString(params);//回调请求内容 + log.info("message: " + originalXml); + String transactionId = params.get("trxid");//通联收银宝交易流水号 + String orderId = outTradeNo; + + String trxcode = params.get("trxcode"); + /* 交易类型 + VSP501:微信支付 + VSP502:微信支付撤销 + VSP503:微信支付退款 + */ + String trxstatus = params.get("trxstatus");//支付状态 + if("VSP501".equals(trxcode)){ + if("0000".equals(trxstatus)) { + String status = Constants.PAYMENT_STATUS.SUCCESS.name(); + UserPaymentDTO dto = new UserPaymentDTO(); + dto.setOrderId(orderId); + dto.setTransactionId(transactionId); + dto.setOutTradeNo(outTradeNo); + dto.setOriginalXml(originalXml); + String trxamtStr = params.get("trxamt");//交易金额 单位:分 + log.info("======trxamt: " + trxamtStr); + dto.setPaymentAmountCallback(trxamtStr); + + dto.setStatus(status); + + String orderStatus = Constants.ORDER_STATUS_BACKEND.PAYMENT.name(); + + saveCallbackInfo(dto, orderStatus); + + return true; + } + } + + return !("1001".equals(trxstatus) || StringUtils.isEmpty(trxstatus) + || "2008".equals(trxstatus) + || "2000".equals(trxstatus)); + } + + public boolean checkOrderStatusRefund(String outTradeNo) throws Exception { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", outTradeNo)); + String s = up.getStatus(); + if(StringUtils.isEmpty(s)){ + return true; + } + + SybPayService service = new SybPayService(); + Map<String,String> params = service.query(outTradeNo, up.getTransactionId()); + + String originalXml = toJSONString(params);//回调请求内容 + log.info("message: " + originalXml); + + String trxcode = params.get("trxcode"); + /* 交易类型 + VSP501:微信支付 + VSP502:微信支付撤销 + VSP503:微信支付退款 + */ + if ("VSP502".equals(trxcode) || "VSP503".equals(trxcode)){ + String trxid = params.get("trxid"); + updateOrderRefund(outTradeNo, trxid); + + return true; + } + + return false; + } + + public void cancelOrder(String orderId) throws Exception { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", orderId)); + if(StringUtils.isNotEmpty(up.getStatus())){ + throw new ValidationException("订单不可取消"); + } + + SybPayService service = new SybPayService(); + + Map<String,String> params = service.query(orderId, up.getTransactionId()); + String retcode = params.get("retcode"); + if(!"SUCCESS".equals(retcode)){ + throw new ValidationException("查询订单交易状态失败: " + params.get("retmsg")); + } + String trxstatus = params.get("trxstatus");//支付状态 + + if("1001".equals(trxstatus) || "3088".equals(trxstatus)){ + //3088-交易未支付(在查询时间区间内未成功支付,如已影响资金24小时内会做差错退款处理) + //1001:交易不存在 + //直接取消 + } else if (StringUtils.isEmpty(trxstatus) + || "2008".equals(trxstatus) + || "2000".equals(trxstatus)) { + String trxid = params.get("trxid"); + //2008或者2000 : 交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易 + Map<String,String> map = service.close(trxid, orderId); + log.info("======关闭订单结果" + toJSONString(map)); + + retcode = map.get("retcode"); + if(!"SUCCESS".equals(retcode)){ + throw new ValidationException("调用通联关闭订单失败: " + map.get("retmsg")); + } + trxstatus = map.get("trxstatus"); + if(!"0000".equals(trxstatus)){ + throw new ValidationException("取消订单失败: " + map.get("errmsg")); + } + + } else { + throw new ValidationException("订单不可取消"); + } + + Order order = orderMapper.selectById(orderId); + order.setStatus(Constants.ORDER_STATUS.CANCEL.name()); + order.setStatusBackend(Constants.ORDER_STATUS_BACKEND.CANCEL.name()); + order.setCancelTime(LocalDateTime.now()); + order.update(SecurityUtils.getUserId()); + orderMapper.updateById(order); + + up.setStatus(Constants.PAYMENT_STATUS.CLOSED.name()); + up.update(SecurityUtils.getUserId()); + userPaymentMapper.updateById(up); + + releasePrepayLock(order); + } + + private void releasePrepayLock(Order order){ + log.info("恢复库存 积分商品兑换券 优惠券: " + order); + revertFlowerStock(order.getId()); + + //恢复积分商品兑换券 + revertPointGoodsRecord(order.getId()); + + //恢复优惠券 + String memberCouponId = order.getMemberCouponId(); + if(StringUtils.isNotEmpty(memberCouponId)) { + couponRecordService.cancelCouponUsage(order.getId()); + } + } + + private void revertPointGoodsRecord(String orderId){ + List<OrderPointGoods> ls = orderPointGoodsMapper.selectList(new QueryWrapper<OrderPointGoods>() + .eq("order_id", orderId)); + if(ls != null && ls.size() > 0){ + for(OrderPointGoods pg : ls){ + pointGoodsService.revertExchangeGoods(pg.getGoodsRecordId()); + } + } + } + + public Map payAgain(String orderId){ + // 获取订单里面的商品是否有限购的,如果有则判断是否已经超过限购数量 + List<OrderItem> orderItemList = orderItemMapper.selectList(new QueryWrapper<OrderItem>() + .eq("order_id", orderId)); + + orderItemList.forEach(orderItem -> { + // 限购数量 鲜花数量校验 + Integer completeNumToday=orderService.getFlowerCompleteNumToday(orderItem.getCreateBy(),orderItem.getFlowerId()); + Integer tmp=completeNumToday+orderItem.getNum(); + Flower flower=flowerMapper.selectById(orderItem.getFlowerId()); + if(null!=flower.getLimited() && tmp.compareTo(flower.getLimited())>0){ + throw new ValidationException("商品:'"+flower.getName()+"' 昨天17:00到今天17:00 超过限售数量:"+flower.getLimited()+"!"); + } + }); + + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", orderId)); + if(StringUtils.isNotEmpty(up.getStatus())){ + throw new ValidationException("订单不可再支付"); + } + + return parseObject(up.getPrepayResponse(), TreeMap.class); + } + + public synchronized void revertFlowerStock(String orderId){ + List<OrderItem> ls = orderItemMapper.selectList(new QueryWrapper<OrderItem>() + .eq("order_id", orderId)); + for(OrderItem c : ls){ + flowerMapper.addFlowerStock(c.getFlowerId(), c.getNum()); + } + } + + public void refundOrderCustomer(String orderId) throws Exception { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", orderId)); + if(up.getPaymentAmount() == null){ + throw new ValidationException("订单不可退款"); + } + + long trxamt = prepareAmount(up.getPaymentAmount()); + String reqsn = orderId; + String oldtrxid = up.getTransactionId(); + String oldreqsn = orderId; + + SybPayService service = new SybPayService(); + Map<String,String> map = service.cancel(trxamt, reqsn, oldtrxid, oldreqsn); + String retcode = map.get("retcode"); + if(!"SUCCESS".equals(retcode)){ + throw new ValidationException("调用通联撤销接口失败: " + map.get("retmsg")); + } + + //这个不是订单状态,是通用的,如果是支付查询,代表就是订单状态,如果是退款代表的是退款状态 + String trxstatus = map.get("trxstatus"); + if(!"0000".equals(trxstatus)){ + log.error("通联撤销交易失败:" + map.get("errmsg")); + throw new ValidationException("订单退款交易失败:" + map.get("errmsg")); + } + + updateOrderRefund(orderId, oldtrxid); + } + + /** + * 退款 + * + * @param orderId + */ + public void refundOrder(String orderId) throws Exception { + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", orderId)); + if(up.getPaymentAmount() == null){ + throw new ValidationException("订单不可退款"); + } + + long trxamt = prepareAmount(up.getPaymentAmount()); + String reqsn = orderId; + String oldtrxid = up.getTransactionId(); + String oldreqsn = orderId; + + SybPayService service = new SybPayService(); + Map<String,String> map = service.refund(trxamt, reqsn, oldtrxid, oldreqsn); + String retcode = map.get("retcode"); + if(!"SUCCESS".equals(retcode)){ + throw new ValidationException("调用通联退款失败: " + map.get("retmsg")); + } + + //这个不是订单状态,是通用的,如果是支付查询,代表就是订单状态,如果是退款代表的是退款状态 + String trxstatus = map.get("trxstatus"); + if(!"0000".equals(trxstatus)){ + throw new ValidationException("订单退款交易失败:" + map.get("errmsg")); + } + + updateOrderRefund(orderId, oldtrxid); + } + + private void updateOrderRefund(String orderId, String refundNo){ + Order o = orderMapper.selectById(orderId); + + if(Constants.ORDER_STATUS.REFUND.name().equals(o.getStatus())){ + return; + } + + o.setRefundAmount(o.getPaymentAmount()); + o.setRefundNo(refundNo); + o.setRefundTime(LocalDateTime.now()); + + o.setStatus(Constants.ORDER_STATUS.REFUND.name()); + o.setStatusBackend(Constants.ORDER_STATUS_BACKEND.REFUND.name()); + o.update("sys"); + + orderMapper.updateById(o); + + deliveryOrderService.refundDelete(orderId); + + releasePrepayLock(o); + } + + public String refundOrderSub(Order o, BigDecimal refundAmount) throws Exception { + if(o.getPaymentTime() == null || o.getPaymentAmount() == null){ + throw new ValidationException("未支付订单不可退款"); + } + + if(o.getRefundTime() != null){ + throw new ValidationException("已全额退款订单不可退款"); + } + + if (refundAmount == null || refundAmount.doubleValue() == 0) { + throw new ValidationException("退款金额不能为空"); + } + + List<OrderRefund> reLs = orderRefundMapper.selectList(new QueryWrapper<OrderRefund>() + .eq("order_id", o.getId())); + BigDecimal rra = new BigDecimal(0); + if(reLs != null && reLs.size() > 0){ + for(OrderRefund r : reLs){ + rra = rra.add(r.getRefundAmount()); + } + } + + long total = (long)prepareAmount(o.getPaymentAmount()); + long refund = (long)prepareAmount(refundAmount); + + long rraa = (long)prepareAmount(rra); + if(rraa + refund > total){ + throw new ValidationException("退款金额不能大于订单金额"); + } + + OrderRefund re = new OrderRefund(); + re.setId(UUIDGenerator.getUUID()); + re.setOrderId(o.getId()); + re.setOrderAmount(o.getPaymentAmount()); + re.setRefundAmount(refundAmount); + + UserPayment up = userPaymentMapper.selectOne( + new QueryWrapper<UserPayment>().eq("order_id", o.getId())); + + long trxamt = refund; + String reqsn = o.getId(); + String oldtrxid = up.getTransactionId(); + String oldreqsn = null; + + SybPayService service = new SybPayService(); + Map<String,String> map = service.refund(trxamt, reqsn, oldtrxid, oldreqsn); + String retcode = map.get("retcode"); + if(!"SUCCESS".equals(retcode)){ + throw new ValidationException("调用通联退款失败: " + map.get("retmsg")); + } + + //这个不是订单状态,是通用的,如果是支付查询,代表就是订单状态,如果是退款代表的是退款状态 + String trxstatus = map.get("trxstatus"); + if(!"0000".equals(trxstatus)){ + throw new ValidationException("订单退款交易失败:" + map.get("errmsg")); + } + + re.setRequest(toJSONString(map)); + re.create(SecurityUtils.getUserId()); + + re.setStatus(retcode); + re.setNotification(retcode); + re.setNotifyTime(LocalDateTime.now()); + + orderRefundMapper.insert(re); + + return re.getId(); + } + +} -- Gitblit v1.9.3