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 getParams(HttpServletRequest request){ TreeMap 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 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().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().eq("order_id", outTradeNo)); String s = up.getStatus(); if(StringUtils.isNotEmpty(s)){ return true; } SybPayService service = new SybPayService(); Map 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().eq("order_id", outTradeNo)); String s = up.getStatus(); if(StringUtils.isEmpty(s)){ return true; } SybPayService service = new SybPayService(); Map 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().eq("order_id", orderId)); if(StringUtils.isNotEmpty(up.getStatus())){ throw new ValidationException("订单不可取消"); } SybPayService service = new SybPayService(); Map params = service.query(orderId, up.getTransactionId()); 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 map = service.close(trxid, orderId); log.info("======关闭订单结果" + toJSONString(map)); String retcode = map.get("retcode"); if(!"SUCCESS".equals(retcode)){ throw new ValidationException("取消订单失败: " + map.get("retmsg")); } } 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 ls = orderPointGoodsMapper.selectList(new QueryWrapper() .eq("order_id", orderId)); if(ls != null && ls.size() > 0){ for(OrderPointGoods pg : ls){ pointGoodsService.revertExchangeGoods(pg.getGoodsRecordId()); } } } public Map payAgain(String orderId){ // 获取订单里面的商品是否有限购的,如果有则判断是否已经超过限购数量 List orderItemList = orderItemMapper.selectList(new QueryWrapper() .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().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 ls = orderItemMapper.selectList(new QueryWrapper() .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().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 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)){ throw new ValidationException("订单退款交易失败:" + map.get("errmsg")); } updateOrderRefund(orderId, oldtrxid); } /** * 退款 * * @param orderId */ public void refundOrder(String orderId) throws Exception { UserPayment up = userPaymentMapper.selectOne( new QueryWrapper().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 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 reLs = orderRefundMapper.selectList(new QueryWrapper() .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().eq("order_id", o.getId())); long trxamt = refund; String reqsn = o.getId(); String oldtrxid = up.getTransactionId(); String oldreqsn = null; SybPayService service = new SybPayService(); Map 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(); } }