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();
|
}
|
|
}
|