diff --git a/src/main/java/com/ai/da/controller/AffiliateController.java b/src/main/java/com/ai/da/controller/AffiliateController.java index 004bc6e7..7b1b63f3 100644 --- a/src/main/java/com/ai/da/controller/AffiliateController.java +++ b/src/main/java/com/ai/da/controller/AffiliateController.java @@ -51,8 +51,17 @@ public class AffiliateController { @ApiOperation(value = "审批affiliate申请") @GetMapping("/approval") - public Response applicationApproval(@RequestParam("id") Long id, @RequestParam("isApproved")Boolean isApproved) { - return Response.success(affiliateService.applicationApproval(id, isApproved)); + public Response applicationApproval(@RequestParam("id") Long id, + @RequestParam("isApproved")Boolean isApproved, + @RequestParam("commission") Float commission) { + return Response.success(affiliateService.applicationApproval(id, isApproved, commission)); + } + + @ApiOperation(value = "更新佣金比例") + @GetMapping("/updateCommission") + public Response updateCommissionPercentage(@RequestParam("id") Long id, @RequestParam("commission") Float commission) { + affiliateService.updateCommissionPercentage(id, commission); + return Response.success("success"); } /*@ApiOperation(value = "定时计算佣金") diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java index 261777ae..e02a1765 100644 --- a/src/main/java/com/ai/da/controller/StripeController.java +++ b/src/main/java/com/ai/da/controller/StripeController.java @@ -141,6 +141,14 @@ public class StripeController { @RequestParam(required = false) String cooperator, @RequestParam(required = false) String remark){ return Response.success(stripeService.updateCouponsInfo(id, paidCommission, cooperator, remark)); } + + @ApiOperation("删除推广码") + @GetMapping("/deletePromCode") + public Response deleteCoupon(@RequestParam Long id){ + stripeService.deleteCoupon(id); + return Response.success("success"); + } + /*@ApiOperation("临时 取消订阅") @GetMapping("/cancelSubscriptionTemp") public Response cancelSubscriptionTemp(@RequestParam String subscriptionId) { diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java index beb52662..5b9b42d4 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java @@ -14,6 +14,8 @@ public class Affiliate extends BaseEntity{ // Active(活跃) || Inactive(过期) || Pending(待审批) || Refused(拒绝) private String status; + private Float commissionPercent; + private Float totalEarnings = 0.00F; private Float monthlyEarnings = 0.00F; diff --git a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java index a0b5dbfc..a4feff01 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java @@ -22,6 +22,8 @@ public class PaymentInfo extends BaseEntity{ * paid and liquidated means the refund request has been executed. * expired means the request has been rejected. * wait means the request is still under processing. + * ------------------------------------------------- + * 退款:Partial refund 部分退款; Refunded 全部退款 */ private String tradeState;//交易状态 diff --git a/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java b/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java index 2ffe97fb..88c9ed18 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java @@ -1,5 +1,7 @@ package com.ai.da.mapper.primary.entity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -37,6 +39,9 @@ public class ProductCoupons extends BaseEntity{ // 备注 private String remark; + @TableLogic + private Integer isDeleted; + public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, Long maxRedemptions, float percentOff, float commissionRate, String cooperator, String remark) { this.couponId = couponId; this.redeemBy = redeemBy; diff --git a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java index 5349fd89..b239baaa 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java @@ -13,11 +13,14 @@ public class RefundInfo extends BaseEntity{ private String refundId;//支付系统退款单号(微信) + // 退款必定对应一次具体的收费款项 + private String chargeId; + // private Integer totalFee;//原订单金额(分) private Float totalFee;//原订单金额(分) // private Integer refund;//退款金额(分) - private Float refund;//退款金额(分) + private Float refund;//退款金额(元) private String reason;//退款原因 diff --git a/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java index 43a2d3af..0e8ef223 100644 --- a/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java +++ b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java @@ -22,6 +22,8 @@ public class SubscriptionEmailParamsDTO { // 费用 private String totalFee; + private String renewalFee; + // 当前订阅开始时间 private String lastOrderDate; diff --git a/src/main/java/com/ai/da/service/AffiliateService.java b/src/main/java/com/ai/da/service/AffiliateService.java index a083e9d2..e34a172b 100644 --- a/src/main/java/com/ai/da/service/AffiliateService.java +++ b/src/main/java/com/ai/da/service/AffiliateService.java @@ -18,7 +18,9 @@ public interface AffiliateService extends IService { double[] getPersonalMonthlyIncome(int year); - Boolean applicationApproval(Long id, Boolean isApproved); + Boolean applicationApproval(Long id, Boolean isApproved, Float commission); + + void updateCommissionPercentage(Long id, Float commission); void updateAffiliateInfoWithPayment(); diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java index c0434e94..a9ef0453 100644 --- a/src/main/java/com/ai/da/service/PaymentInfoService.java +++ b/src/main/java/com/ai/da/service/PaymentInfoService.java @@ -34,4 +34,6 @@ public interface PaymentInfoService extends IService { PageBaseResponse getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO); List getPaymentInfoByPromCode(Long accountId, String promCode); + + PaymentInfo updatePaymentRefundStatus(Charge charge); } diff --git a/src/main/java/com/ai/da/service/RefundInfoService.java b/src/main/java/com/ai/da/service/RefundInfoService.java index 5cfeca4c..d1cb0fe7 100644 --- a/src/main/java/com/ai/da/service/RefundInfoService.java +++ b/src/main/java/com/ai/da/service/RefundInfoService.java @@ -3,6 +3,8 @@ package com.ai.da.service; import com.ai.da.mapper.primary.entity.RefundInfo; import com.baomidou.mybatisplus.extension.service.IService; +import com.stripe.model.Charge; +import com.stripe.model.Refund; import java.util.List; @@ -19,4 +21,13 @@ public interface RefundInfoService extends IService { void updateRefundForAliPay(String refundNo, String content, String refundStatus); void updateRefundForPayPal(Long id, String refundId, String content, String refundStatus); + + List getByChargeId(String chargeId); + + RefundInfo createRefundForStripe(Refund refund); + + RefundInfo updateRefundStatusForStripe(Refund refund); + + RefundInfo updateRefundForStripe(Charge charge); + } diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java index 7769fa66..7087ca1a 100644 --- a/src/main/java/com/ai/da/service/StripeService.java +++ b/src/main/java/com/ai/da/service/StripeService.java @@ -70,4 +70,6 @@ public interface StripeService { String retrievePromotionCode(String promotionCode); IPage getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO); + + void deleteCoupon(Long id); } diff --git a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java index c88251ae..ca3b87cc 100644 --- a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java @@ -147,7 +147,7 @@ public class AffiliateServiceImpl extends ServiceImpl 0){ - // 分配新用户首次订阅所付费用的25%作为佣金 - BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(new BigDecimal("0.25")); + // 分配新用户首次订阅所付费用 预设的佣金比例 作为佣金 + BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(BigDecimal.valueOf(affiliate.getCommissionPercent() / 100)); BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission); BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission); int visits = affiliate.getVisits() + 1; diff --git a/src/main/java/com/ai/da/service/impl/MessageCenterServiceImpl.java b/src/main/java/com/ai/da/service/impl/MessageCenterServiceImpl.java index 74a19386..bff32d27 100644 --- a/src/main/java/com/ai/da/service/impl/MessageCenterServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/MessageCenterServiceImpl.java @@ -106,7 +106,11 @@ public class MessageCenterServiceImpl extends ServiceImpl convert = notificationPage.convert(o -> { NotificationVO notificationVO = CopyUtil.copyObject(o, NotificationVO.class); Account senderAccount = accountService.getById(notificationVO.getSenderId()); - notificationVO.setUserName(senderAccount.getUserName()); + if (Objects.nonNull(senderAccount)){ + notificationVO.setUserName(senderAccount.getUserName()); + }else { + notificationVO.setUserName("--"); + } // notificationVO.setSenderUserAvatar(StringUtils.isNullOrEmpty(senderAccount.getAvatar()) ? null : minioUtil.getPreSignedUrl(senderAccount.getAvatar(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); if (Objects.isNull(notificationVO.getPortfolioId())){ notificationVO.setPortfolioId(null); @@ -131,7 +135,7 @@ public class MessageCenterServiceImpl extends ServiceImpl getPaymentInfoByPromCode(Long accountId, String promCode){ return baseMapper.selectPaidPaymentsByAccountAndPromotion(accountId, promCode); } + + public PaymentInfo updatePaymentRefundStatus(Charge charge){ + // 判断当前退款是部分退款还是全部退款 + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("transaction_id", charge.getInvoice()); + PaymentInfo paymentInfo = baseMapper.selectOne(qw); + if (Objects.nonNull(paymentInfo)){ + String status ; + if (Objects.equals(charge.getAmount(), charge.getAmountRefunded())){ + status = "Refunded"; + }else if (charge.getAmount() > charge.getAmountRefunded()){ + status = "Partial refund"; + }else { + status = "Refund Exception"; + log.warn("{}, 退款金额高于付款金额, ChargeId为:{}", status, charge.getId()); + } + if (!paymentInfo.getTradeState().equals(status)){ + paymentInfo.setTradeState(status); + paymentInfo.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(paymentInfo); + } + } + return null; + } } diff --git a/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java index ff1db544..cfc82fef 100644 --- a/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/RefundInfoServiceImpl.java @@ -10,14 +10,16 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; +import com.stripe.model.Charge; +import com.stripe.model.Refund; +import io.netty.util.internal.StringUtil; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.Duration; import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.util.*; @Service public class RefundInfoServiceImpl extends ServiceImpl implements RefundInfoService { @@ -170,4 +172,63 @@ public class RefundInfoServiceImpl extends ServiceImpl getByChargeId(String chargeId){ + List refundInfoList = baseMapper.selectList(new QueryWrapper().eq("charge_id", chargeId)); + // 判断当前已退款的金额是否全部退完(针对多次部分退款的情况) + if (refundInfoList.isEmpty()){ + return new ArrayList<>(); + }else { + return refundInfoList; + } + } + + public RefundInfo getByRefundId(String refundId){ + return baseMapper.selectOne(new QueryWrapper().eq("refund_id", refundId)); + } + + public RefundInfo createRefundForStripe(Refund refund){ + // 先确认这个回调是否已经被记录 + RefundInfo refundInfo = getByRefundId(refund.getId()); + if (Objects.isNull(refundInfo)){ + refundInfo = new RefundInfo(); + refundInfo.setRefundId(refund.getId()); + refundInfo.setChargeId(refund.getCharge()); + refundInfo.setRefund(refund.getAmount() / 100f); + refundInfo.setReason(refund.getReason()); + refundInfo.setRefundStatus(refund.getStatus()); + refundInfo.setCreateTime(LocalDateTime.now()); + baseMapper.insert(refundInfo); + } + return refundInfo; + } + + public RefundInfo updateRefundStatusForStripe(Refund refund){ + RefundInfo refundInfo = getByRefundId(refund.getId()); + if (Objects.isNull(refundInfo)){ + return null; + } + if (!refundInfo.getRefundStatus().equals(refund.getStatus())){ + refundInfo.setRefundStatus(refund.getStatus()); + refundInfo.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(refundInfo); + } + return refundInfo; + } + + public RefundInfo updateRefundForStripe(Charge charge){ + List refundInfoList = getByChargeId(charge.getId()); + if (!refundInfoList.isEmpty()){ + RefundInfo refundInfo = refundInfoList.get(refundInfoList.size() - 1); + if (StringUtil.isNullOrEmpty(refundInfo.getOrderNo())){ + String orderNo = charge.getDescription().replace("AiDA - ", ""); + refundInfo.setOrderNo(orderNo); + refundInfo.setTotalFee(charge.getAmount() / 100f); + refundInfo.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(refundInfo); + return refundInfo; + } + } + return null; + } } diff --git a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java index c83907f0..d8219bdb 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -24,6 +24,7 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.gson.Gson; import com.stripe.Stripe; +import com.stripe.exception.InvalidRequestException; import com.stripe.exception.SignatureVerificationException; import com.stripe.exception.StripeException; import com.stripe.model.*; @@ -116,6 +117,16 @@ public class StripeServiceImpl implements StripeService { default: throw new BusinessException("unknown subscription type"); } + + // 添加优惠券(只允许在订阅时使用优惠券) + String promotionCode = productPurchaseDTO.getPromotionCode(); + if (!StringUtil.isNullOrEmpty(promotionCode)){ + ProductCoupons productCoupon = checkProductCoupon(promotionCode);; + if (productCoupon != null){ + sessionBuilder.addDiscount(SessionCreateParams.Discount.builder() + .setPromotionCode(productCoupon.getPromotionCodeId()).build()); + } + } // 只有订阅时才允许使用推广码优惠 // sessionBuilder.setAllowPromotionCodes(true); break; @@ -170,16 +181,6 @@ public class StripeServiceImpl implements StripeService { .build()); sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息(可选) - // 添加优惠券 - String promotionCode = productPurchaseDTO.getPromotionCode(); - if (!StringUtil.isNullOrEmpty(promotionCode)){ - ProductCoupons productCoupon = checkProductCoupon(promotionCode);; - if (productCoupon != null){ - sessionBuilder.addDiscount(SessionCreateParams.Discount.builder() - .setPromotionCode(productCoupon.getPromotionCodeId()).build()); - } - } - Session session = Session.create(sessionBuilder.build()); List paymentMethodTypes = session.getPaymentMethodTypes(); log.info("paymentMethodTypes: {}", paymentMethodTypes); @@ -193,6 +194,9 @@ public class StripeServiceImpl implements StripeService { return session.getUrl(); } catch (BusinessException e) { throw e; + } catch (InvalidRequestException e) { + log.info("创建会话出现异常:", e); + throw new BusinessException(e.getMessage().substring(0, e.getMessage().indexOf(";"))); } catch (Exception e) { log.error("创建支付会话出现异常:", e); } @@ -430,6 +434,26 @@ public class StripeServiceImpl implements StripeService { orderInfo.setOrderStatus(OrderStatusEnum.SUCCESS.getType()); orderInfo.setNote(""); orderInfoService.updateById(orderInfo); + }else if (event.getType().equals("charge.refunded")){ + // 更新退款信息 + RefundInfo refundInfo = refundInfoService.updateRefundForStripe(charge); + // 更新 t_payment_info的支付状态 + if (Objects.nonNull(refundInfo)){ + paymentInfoService.updatePaymentRefundStatus(charge); + } + } + }else if (stripeObject instanceof Refund){ + Refund refund = (Refund) stripeObject; + if (event.getType().equals("refund.created")){ + // 新增退款信息 + refundInfoService.createRefundForStripe(refund); + }else if (event.getType().equals("refund.updated")){ + // 根据***id更新退款记录信息 + RefundInfo refundInfo = refundInfoService.updateRefundStatusForStripe(refund); + if (Objects.isNull(refundInfo)){ + // 等事件先创建,再更新。回调事件的顺序随机 + response = false; + } } } log.info("回调事件 {} 处理完成", event.getType()); @@ -1148,6 +1172,13 @@ public class StripeServiceImpl implements StripeService { emailParamsDTO.setFailMessage(orderByOrderNo.getNote()); emailParamsDTO.setSubscriptionType(subscriptionInfo.getType()); emailParamsDTO.setStartDate(DateUtil.changeTimeStampFormat(orderByOrderNo.getCreateTime())); + if (subscriptionInfo.getType().equals("month")){ + emailParamsDTO.setRenewalFee(String.valueOf(500)); + } else if (subscriptionInfo.getType().equals("year")){ + emailParamsDTO.setRenewalFee(String.valueOf(5000)); + } else { + emailParamsDTO.setRenewalFee(emailParamsDTO.getTotalFee()); + } if (subscriptionInfo.getStatus().equals("active")){ if (language.equals("ENGLISH")){ emailParamsDTO.setEndDate("When cancelled"); @@ -1385,6 +1416,7 @@ public class StripeServiceImpl implements StripeService { CouponCreateParams.Builder couponParams = CouponCreateParams.builder() // 任何客户只能用一次这个优惠券 .setDuration(CouponCreateParams.Duration.ONCE) + // percent_off 与 amount_off 不能同时设置 .setPercentOff(BigDecimal.valueOf(createCouponDTO.getPercentOff())); if (Objects.nonNull(createCouponDTO.getTimestamp())){ couponParams.setRedeemBy(createCouponDTO.getTimestamp()); @@ -1582,4 +1614,22 @@ public class StripeServiceImpl implements StripeService { return productCouponsMapper.selectPage(new Page<>(queryCouponsPageDTO.getPage(), queryCouponsPageDTO.getSize()), queryWrapper); } + + @Transactional + public void deleteCoupon(Long id){ + Stripe.apiKey = privateKey; + ProductCoupons productCoupons = productCouponsMapper.selectById(id); + if (Objects.isNull(productCoupons)){ + throw new BusinessException("unknown promotion code"); + } + try { + Coupon coupon = Coupon.retrieve(productCoupons.getCouponId()); + coupon.delete(); + log.info("coupon {} 删除成功", productCoupons.getCouponId()); + productCouponsMapper.deleteById(id); + } catch (StripeException e) { + log.error("未知coupons,无法通过couponId: {} 获得Coupons", productCoupons.getCouponId()); + } + } + }