|
|
@@ -0,0 +1,230 @@
|
|
|
+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<SpecialPayProduct> 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<String, String> 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<String, String> 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("支付宝回调金额异常");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|