From a2d259aea1abe48603600582eb2828dc1ee0754c Mon Sep 17 00:00:00 2001 From: xupei Date: Fri, 25 Apr 2025 10:06:07 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BB=8EStripe=E9=80=80=E6=AC=BE=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=94=B9=E8=AE=A2=E5=8D=95=E7=8A=B6=E6=80=81?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=80=80=E6=AC=BE=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../da/mapper/primary/entity/PaymentInfo.java | 2 + .../da/mapper/primary/entity/RefundInfo.java | 5 +- .../com/ai/da/service/PaymentInfoService.java | 2 + .../com/ai/da/service/RefundInfoService.java | 11 +++ .../service/impl/PaymentInfoServiceImpl.java | 24 +++++++ .../service/impl/RefundInfoServiceImpl.java | 67 ++++++++++++++++++- .../ai/da/service/impl/StripeServiceImpl.java | 20 ++++++ 7 files changed, 127 insertions(+), 4 deletions(-) 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/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/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/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java index d85d3725..355e29ac 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -435,4 +435,28 @@ public class PaymentInfoServiceImpl 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..e7bf9780 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -430,6 +430,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()); From 200c0adfba26b83395686581e374e542a8b59b99 Mon Sep 17 00:00:00 2001 From: xupei Date: Mon, 28 Apr 2025 14:40:42 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Affiliate=20=E5=85=81=E8=AE=B8=E4=B8=BA?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=9A=84=E7=94=A8=E6=88=B7=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=9A=84affiliate=20Stripe=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BC=98=E6=83=A0=E5=88=B8=E5=88=A0=E9=99=A4=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=9B=E9=99=90=E5=88=B6=E4=BC=98=E6=83=A0=E5=88=B8?= =?UTF-8?q?=E5=8F=AA=E8=83=BD=E5=9C=A8=E8=AE=A2=E9=98=85=E6=97=B6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/da/controller/AffiliateController.java | 13 ++++++++-- .../ai/da/controller/StripeController.java | 8 ++++++ .../da/mapper/primary/entity/Affiliate.java | 2 ++ .../mapper/primary/entity/ProductCoupons.java | 5 ++++ .../model/dto/SubscriptionEmailParamsDTO.java | 2 ++ .../com/ai/da/service/AffiliateService.java | 4 ++- .../java/com/ai/da/service/StripeService.java | 2 ++ .../da/service/impl/AffiliateServiceImpl.java | 25 ++++++++++++++++--- .../ai/da/service/impl/StripeServiceImpl.java | 23 +++++++++-------- 9 files changed, 68 insertions(+), 16 deletions(-) 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/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/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/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/StripeServiceImpl.java b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java index e7bf9780..5290b097 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -116,6 +116,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 +180,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 +193,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); } From 89e6ee9eff256d6de517e9db738b419187038a99 Mon Sep 17 00:00:00 2001 From: xupei Date: Mon, 28 Apr 2025 14:51:19 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=84=E8=AE=BA=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ai/da/service/impl/MessageCenterServiceImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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..b5e5a4f4 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);