package com.cyksj.service.special.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.alipay.api.internal.util.AlipaySignature; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.EnvCommonService; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.snowflake.Sequence; import com.cyksj.common.util.Codec; import com.cyksj.common.util.Jsons; import com.cyksj.config.zfb.BaseZfbConfig; import com.cyksj.config.zfb.ZfbProductCode; import com.cyksj.mapper.channel.ShopConfigMapper; import com.cyksj.mapper.special.SpecialMemberEntitlementMapper; import com.cyksj.mapper.special.SpecialPayOrderMapper; import com.cyksj.mapper.special.SpecialPayProductMapper; import com.cyksj.model.dto.AliPayParams; import com.cyksj.model.entity.ShopConfig; import com.cyksj.model.entity.SpecialPayOrder; import com.cyksj.model.entity.SpecialPayProduct; import com.cyksj.model.entity.SpecialMemberEntitlement; import com.cyksj.model.request.SpecialPayCreateReq; import com.cyksj.model.request.OrderAttach; import com.cyksj.model.response.AliPayTradeStatus; import com.cyksj.model.response.SpecialPayCreateRep; import com.cyksj.service.pay.AliPayCommonService; import com.cyksj.service.special.SpecialPayService; import com.cyksj.service.sys.SysConfigService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; @Slf4j @Service @RequiredArgsConstructor public class SpecialPayServiceImpl implements SpecialPayService { private static final Sequence SEQUENCE = new Sequence(0); private static final int ORDER_EXPIRE_MINUTES = 5; private final SpecialPayProductMapper productMapper; private final SpecialPayOrderMapper orderMapper; private final SpecialMemberEntitlementMapper entitlementMapper; private final ShopConfigMapper shopConfigMapper; private final SysConfigService sysConfigService; private final AliPayCommonService aliPayCommonService; private final EnvCommonService envCommonService; @Override public List getEnabledProducts() { return productMapper.selectList(Wrappers.lambdaQuery(SpecialPayProduct.class) .eq(SpecialPayProduct::getEnabled, true) .orderByAsc(SpecialPayProduct::getSortBy, SpecialPayProduct::getId)); } @Override @Transactional(rollbackFor = Throwable.class) public SpecialPayCreateRep createAlipayOrder(Long userId, SpecialPayCreateReq request) throws Exception { SpecialPayProduct product = productMapper.selectOne(Wrappers.lambdaQuery(SpecialPayProduct.class) .eq(SpecialPayProduct::getProductCode, request.getProductCode()) .eq(SpecialPayProduct::getEnabled, true) .last("limit 1")); if (product == null) { throw BusinessRuntimeException.getInstance("支付套餐不存在或已下架"); } if (product.getAmount() == null || product.getAmount().compareTo(BigDecimal.ZERO) <= 0 || product.getDurationDays() == null || product.getDurationDays() <= 0) { throw BusinessRuntimeException.getInstance("支付套餐配置异常"); } String alipayProductCode = getAlipayProductCode(request.getPayType()); Date now = new Date(); SpecialPayOrder order = new SpecialPayOrder(); order.setOrderNo("SP" + SEQUENCE.nextId()); order.setUserId(userId); order.setProductCode(product.getProductCode()); order.setSubject(product.getProductName()); order.setAmount(product.getAmount()); order.setDurationDays(product.getDurationDays()); order.setStatus(SpecialPayOrder.Status.CREATED); order.setFulfillStatus(SpecialPayOrder.FulfillStatus.INIT); order.setShopId(sysConfigService.getDefaultShopConfigByType("zfb")); order.setExpireTime(DateUtil.offsetMinute(now, ORDER_EXPIRE_MINUTES)); order.setCreatedTime(now); order.setUpdateTime(now); orderMapper.insert(order); String notifyUrl = envCommonService.getNotifyHost() + BaseZfbConfig.specialPayNotifyUrl; AliPayParams payParams = aliPayCommonService.builderPayParams( order.getId(), order.getOrderNo(), order.getShopId(), order.getAmount(), order.getCreatedTime(), order.getSubject(), alipayProductCode, request.getPayMode(), notifyUrl, request.getReturnUrl()); if (payParams == null || StrUtil.isBlank(payParams.getBody())) { throw BusinessRuntimeException.getInstance("支付宝下单失败"); } SpecialPayCreateRep response = new SpecialPayCreateRep(); response.setOrderNo(order.getOrderNo()); response.setStatus(order.getStatus()); response.setExpireTime(order.getExpireTime()); response.setPayBody(payParams.getBody()); return response; } @Override public SpecialPayOrder getUserOrder(Long userId, String orderNo) { SpecialPayOrder order = orderMapper.selectOne(Wrappers.lambdaQuery(SpecialPayOrder.class) .eq(SpecialPayOrder::getOrderNo, orderNo) .eq(SpecialPayOrder::getUserId, userId) .last("limit 1")); if (order == null) { throw BusinessRuntimeException.getInstance("订单不存在"); } return order; } @Override public SpecialMemberEntitlement getMemberEntitlement(Long userId) { return entitlementMapper.selectOne(Wrappers.lambdaQuery(SpecialMemberEntitlement.class) .eq(SpecialMemberEntitlement::getUserId, userId) .last("limit 1")); } @Override @Transactional(rollbackFor = Throwable.class) public void handleAlipayNotify(Map params) throws Exception { String appId = params.get("app_id"); ShopConfig shopConfig = StrUtil.isBlank(appId) ? null : shopConfigMapper.getByAppId(appId); if (shopConfig == null || !AlipaySignature.rsaCheckV1(params, shopConfig.getPublicKey(), BaseZfbConfig.charset, BaseZfbConfig.signType)) { throw BusinessRuntimeException.getInstance("支付宝回调验签失败"); } String orderNo = params.get("out_trade_no"); SpecialPayOrder order = orderMapper.selectByOrderNoForUpdate(orderNo); if (order == null) { throw BusinessRuntimeException.getInstance("特殊支付订单不存在"); } if (!StrUtil.equals(order.getShopId(), appId)) { throw BusinessRuntimeException.getInstance("支付宝回调商户不匹配"); } String tradeStatus = params.get("trade_status"); if (!AliPayTradeStatus.TRADE_SUCCESS.getStatus().equals(tradeStatus) && !AliPayTradeStatus.TRADE_FINISHED.getStatus().equals(tradeStatus)) { throw BusinessRuntimeException.getInstance("支付宝交易状态未成功"); } verifyOrderIdentity(params, order); verifyAmount(params.get("total_amount"), order.getAmount()); String tradeNo = params.get("trade_no"); if (StrUtil.isBlank(tradeNo)) { throw BusinessRuntimeException.getInstance("支付宝交易号为空"); } if (order.getStatus() == SpecialPayOrder.Status.PAID) { if (!StrUtil.equals(order.getAlipayTradeNo(), tradeNo)) { throw BusinessRuntimeException.getInstance("支付宝交易号不匹配"); } return; } if (order.getStatus() != SpecialPayOrder.Status.CREATED) { throw BusinessRuntimeException.getInstance("当前订单状态不可支付"); } Date payTime = new Date(); if (StrUtil.isNotBlank(params.get("gmt_payment"))) { payTime = DateUtil.parse(params.get("gmt_payment"), "yyyy-MM-dd HH:mm:ss"); } String buyerId = Optional.ofNullable(params.get("buyer_id")) .orElse(params.get("buyer_open_id")); if (orderMapper.markPaid(order.getId(), tradeNo, buyerId, payTime) != 1) { throw BusinessRuntimeException.getInstance("订单支付状态更新失败"); } if (order.getDurationDays() == null || order.getDurationDays() <= 0) { throw BusinessRuntimeException.getInstance("订单套餐配置异常"); } entitlementMapper.extend(order.getUserId(), order.getDurationDays(), order.getId()); if (orderMapper.markFulfilled(order.getId()) != 1) { throw BusinessRuntimeException.getInstance("会员权益发放失败"); } log.info("特殊支付宝订单支付并履约成功, orderNo:{}, tradeNo:{}", orderNo, tradeNo); } private String getAlipayProductCode(Integer payType) { if (Integer.valueOf(1).equals(payType)) { return ZfbProductCode.MOBILE_WEB.getProductCode(); } if (Integer.valueOf(2).equals(payType)) { return ZfbProductCode.PC_WEB.getProductCode(); } throw BusinessRuntimeException.getInstance("不支持的支付宝支付方式"); } private void verifyOrderIdentity(Map params, SpecialPayOrder order) { try { String body = Codec.DoBase64.custom().setData(params.get("body")).decodeAsText(); OrderAttach attach = Jsons.parseObject(body, OrderAttach.class); if (attach == null || !order.getId().equals(attach.getO()) || !StrUtil.equals(order.getOrderNo(), params.get("out_trade_no"))) { throw BusinessRuntimeException.getInstance("支付宝回调订单不匹配"); } } catch (BusinessRuntimeException e) { throw e; } catch (Exception e) { throw BusinessRuntimeException.getInstance("支付宝回调订单参数异常"); } } private void verifyAmount(String totalAmount, BigDecimal expectedCents) { try { BigDecimal callbackCents = new BigDecimal(totalAmount) .multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.UNNECESSARY); if (expectedCents == null || callbackCents.compareTo(expectedCents) != 0) { throw BusinessRuntimeException.getInstance("支付宝回调金额不匹配"); } } catch (BusinessRuntimeException e) { throw e; } catch (Exception e) { throw BusinessRuntimeException.getInstance("支付宝回调金额异常"); } } }