自动驾驶之心课程续费来啦!欢迎和我们一起继续成长

最近很多老学员向我们反馈,课程1年有效期到了,还想继续学习课程,怎么续费!

需要重新原价下单购买课程吗!?no no no!完全不需要!老学员是有足够优惠的!

如何续费?

我们团队为大家准备了1个月、3个月、6个月、12个月四种续费选择!

1个月续费按照:  (原价 / 12)  x 1 x 100%计算费用;

3个月续费按照:  (原价 / 12)  x 3 x 70%计算费用;

6个月续费按照:  (原价 / 12)  x 6 x 50%计算费用;

12个月续费按照:  (原价 / 12)  x 12 x 30%计算费用;

续费越长,优惠越多哦~~~

联系我们

如果您真的有续费需求,欢迎联系小助理做进一步咨询!

<think>我们需要设计一个套餐续费提醒自动续费系统,主要包含以下功能: 1. 续费提醒:在订阅到期前发送提醒(比如提前3天、1天) 2. 自动续费:用户可以选择开启自动续费,系统在到期时自动扣款续费 设计要点: - 定时任务:用于检查即将到期的订阅并发送提醒,以及执行自动续费扣款 - 支付集成:需要与支付网关(如支付宝、微信支付、Stripe等)集成,支持自动扣款 - 用户设置:允许用户管理自动续费开关 - 失败处理:自动续费失败后的处理(如重试、通知用户等) 我们将分模块设计: 1. 数据库设计(新增自动续费相关字段) 2. 定时任务模块(续费提醒自动续费执行) 3. 支付集成模块(封装支付网关的扣款接口) 4. 用户设置接口 5. 失败重试与通知机制 下面详细展开:</think>### 套餐续费提醒与自动续费功能设计 #### 整体架构设计 ``` 用户设置 → 定时任务调度 → 续费处理引擎 → 支付网关 ↑ ↓ ↓ ↓ 数据库 提醒服务 失败处理机制 扣款结果 ``` #### 核数据库设计 ```sql -- 用户订阅表(新增续费相关字段) ALTER TABLE subscriptions ADD COLUMN ( auto_renew BOOLEAN DEFAULT false COMMENT '是否自动续费', next_billing_date DATE COMMENT '下次扣款日期', payment_token VARCHAR(255) COMMENT '支付令牌', renewal_attempts INT DEFAULT 0 COMMENT '续费尝试次数', renewal_status ENUM('PENDING', 'SUCCESS', 'FAILED') DEFAULT 'PENDING' ); -- 续费记录表 CREATE TABLE renewal_records ( id BIGINT AUTO_INCREMENT PRIMARY KEY, subscription_id BIGINT NOT NULL, attempt_date DATETIME NOT NULL, amount DECIMAL(10,2) NOT NULL, status ENUM('SUCCESS', 'FAILED') NOT NULL, failure_reason VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ); -- 提醒配置表 CREATE TABLE reminder_settings ( user_id BIGINT PRIMARY KEY, days_before INT DEFAULT 3 COMMENT '提前几天提醒', methods VARCHAR(100) COMMENT '提醒方式: EMAIL,SMS,PUSH', last_reminder_sent DATE ); ``` #### 1. 续费提醒系统实现 ##### 提醒服务核代码 ```java @Service public class RenewalReminderService { private final SubscriptionRepository subscriptionRepo; private final UserRepository userRepo; private final EmailService emailService; private final PushNotificationService pushService; private final SmsService smsService; @Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行 public void sendRenewalReminders() { LocalDate today = LocalDate.now(); // 获取即将到期的订阅(3天内到期) List<Subscription> expiringSubs = subscriptionRepo .findSubscriptionsExpiringInDays(3); for (Subscription sub : expiringSubs) { User user = userRepo.findById(sub.getUserId()) .orElseThrow(() -> new UserNotFoundException(sub.getUserId())); ReminderSetting setting = getReminderSetting(user.getId()); // 计算到期天数 long daysUntilExpire = ChronoUnit.DAYS.between(today, sub.getEndDate()); // 检查是否需要发送提醒 if (shouldSendReminder(setting, daysUntilExpire)) { sendReminder(user, sub, setting, daysUntilExpire); updateLastReminderSent(setting, today); } } } private void sendReminder(User user, Subscription sub, ReminderSetting setting, long daysLeft) { String message = String.format( "您的%s会员将在%d天后到期。请及时续费以保证服务不中断!", sub.getPlan().getName(), daysLeft ); // 多渠道发送提醒 if (setting.getMethods().contains("EMAIL")) { emailService.sendRenewalReminder(user.getEmail(), message); } if (setting.getMethods().contains("SMS")) { smsService.sendSms(user.getPhone(), message); } if (setting.getMethods().contains("PUSH")) { pushService.sendPush(user.getId(), "会员到期提醒", message); } } private boolean shouldSendReminder(ReminderSetting setting, long daysLeft) { // 检查是否达到配置的提醒天数 if (daysLeft > setting.getDaysBefore()) return false; // 检查今天是否已发送过 return !today.equals(setting.getLastReminderSent()); } } ``` ##### 用户设置接口 ```java @RestController @RequestMapping("/api/subscription") public class SubscriptionController { // 更新自动续费设置 @PutMapping("/{subscriptionId}/auto-renew") public ResponseEntity<?> updateAutoRenew( @PathVariable Long subscriptionId, @RequestBody AutoRenewRequest request) { Subscription sub = subscriptionRepo.findById(subscriptionId) .orElseThrow(() -> new SubscriptionNotFoundException(subscriptionId)); // 验证用户权限 if (!sub.getUserId().equals(SecurityUtils.getCurrentUserId())) { throw new AccessDeniedException("无权修改此订阅"); } sub.setAutoRenew(request.isAutoRenew()); // 如果开启自动续费,需要验证支付令牌 if (request.isAutoRenew() && request.getPaymentToken() != null) { if (!paymentService.validateToken(request.getPaymentToken())) { throw new PaymentTokenInvalidException(); } sub.setPaymentToken(request.getPaymentToken()); } subscriptionRepo.save(sub); return ResponseEntity.ok().build(); } // 更新提醒设置 @PutMapping("/reminder-settings") public ReminderSetting updateReminderSettings( @RequestBody ReminderSettingRequest request) { Long userId = SecurityUtils.getCurrentUserId(); ReminderSetting setting = settingRepo.findByUserId(userId) .orElse(new ReminderSetting(userId)); setting.setDaysBefore(request.getDaysBefore()); setting.setMethods(String.join(",", request.getMethods())); return settingRepo.save(setting); } } ``` #### 2. 自动续费系统实现 ##### 自动续费引擎 ```java @Service public class AutoRenewalEngine { private final SubscriptionRepository subscriptionRepo; private final PaymentService paymentService; private final RenewalRecordRepository renewalRecordRepo; private final NotificationService notificationService; @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void processAutoRenewals() { LocalDate today = LocalDate.now(); // 获取今天需要续费的订阅 List<Subscription> renewSubs = subscriptionRepo .findSubscriptionsForRenewal(today); for (Subscription sub : renewSubs) { processRenewal(sub); } } private void processRenewal(Subscription sub) { RenewalRecord record = new RenewalRecord(); record.setSubscriptionId(sub.getId()); record.setAttemptDate(LocalDateTime.now()); record.setAmount(sub.getPlan().getPrice()); try { // 执行扣款 PaymentResult result = paymentService.charge( sub.getPaymentToken(), sub.getPlan().getPrice(), "会员自动续费" ); if (result.isSuccess()) { // 续费成功 renewSubscription(sub); record.setStatus("SUCCESS"); sendRenewalSuccessNotification(sub); } else { // 支付失败 handlePaymentFailure(sub, result.getErrorMessage()); record.setStatus("FAILED"); record.setFailureReason(result.getErrorMessage()); } } catch (Exception e) { // 系统异常 record.setStatus("FAILED"); record.setFailureReason("系统错误: " + e.getMessage()); logger.error("续费处理异常", e); } finally { renewalRecordRepo.save(record); } } private void renewSubscription(Subscription sub) { // 计算新订阅周期 LocalDate newEndDate = sub.getEndDate().plusDays( sub.getPlan().getDurationDays() ); // 更新订阅 sub.setStartDate(LocalDate.now()); sub.setEndDate(newEndDate); sub.setRenewalStatus("SUCCESS"); sub.setRenewalAttempts(0); // 设置下次扣款日期(提前3天) sub.setNextBillingDate(newEndDate.minusDays(3)); subscriptionRepo.save(sub); } private void handlePaymentFailure(Subscription sub, String reason) { // 更新尝试次数 sub.setRenewalAttempts(sub.getRenewalAttempts() + 1); sub.setRenewalStatus("FAILED"); // 根据失败次数采取不同措施 if (sub.getRenewalAttempts() >= 3) { // 超过最大尝试次数,关闭自动续费 sub.setAutoRenew(false); sendAutoRenewDisabledNotification(sub, reason); } else { // 计划下次重试(1天后) sub.setNextBillingDate(LocalDate.now().plusDays(1)); } subscriptionRepo.save(sub); sendRenewalFailureNotification(sub, reason); } } ``` ##### 支付服务集成 ```java @Service public class PaymentService { // 统一支付网关接口 public PaymentResult charge(String paymentToken, BigDecimal amount, String description) { // 根据token类型路由到不同支付渠道 if (paymentToken.startsWith("wx_")) { return wechatPay(paymentToken, amount, description); } else if (paymentToken.startsWith("ali_")) { return alipay(paymentToken, amount, description); } else if (paymentToken.startsWith("stripe_")) { return stripePay(paymentToken, amount, description); } throw new UnsupportedPaymentMethodException(); } // 微信支付实现 private PaymentResult wechatPay(String token, BigDecimal amount, String desc) { try { WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody(desc); request.setOutTradeNo(generateTradeNo()); request.setTotalFee(amount.multiply(BigDecimal.valueOf(100)).intValue()); request.setSpbillCreateIp("127.0.0.1"); request.setTradeType("MWEB"); WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request); return new PaymentResult(true, "支付成功"); } catch (WxPayException e) { return new PaymentResult(false, "微信支付失败: " + e.getReturnMsg()); } } // 支付宝实现 private PaymentResult alipay(String token, BigDecimal amount, String desc) { AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); AlipayTradePagePayModel model = new AlipayTradePagePayModel(); model.setOutTradeNo(generateTradeNo()); model.setTotalAmount(amount.toString()); model.setSubject(desc); model.setProductCode("FAST_INSTANT_TRADE_PAY"); try { AlipayTradePagePayResponse response = alipayClient.pageExecute(request); if (response.isSuccess()) { return new PaymentResult(true, "支付成功"); } else { return new PaymentResult(false, "支付宝支付失败"); } } catch (AlipayApiException e) { return new PaymentResult(false, "支付宝系统错误: " + e.getMessage()); } } } ``` #### 3. 失败处理与通知系统 ##### 失败重试机制 ```java public class RenewalRetryHandler { // 指数退避重试策略 public void handleRetry(Subscription sub) { int attempt = sub.getRenewalAttempts(); Duration delay = calculateDelay(attempt); // 使用消息队列延迟处理 renewalQueue.sendDelayedMessage( new RenewalTask(sub.getId()), delay.toMillis() ); } private Duration calculateDelay(int attempt) { // 指数退避算法:2^attempt 分钟,最大24小时 long minutes = (long) Math.pow(2, Math.min(attempt, 10)); return Duration.ofMinutes(Math.min(minutes, 24 * 60)); } } ``` ##### 通知服务 ```java @Service public class RenewalNotificationService { // 续费成功通知 public void sendRenewalSuccessNotification(Subscription sub) { User user = userRepo.findById(sub.getUserId()) .orElseThrow(() -> new UserNotFoundException(sub.getUserId())); String message = String.format( "您的%s会员已成功续费至%s,本次扣款%s元。", sub.getPlan().getName(), sub.getEndDate().format(DateTimeFormatter.ISO_DATE), sub.getPlan().getPrice() ); notificationService.send(user, "续费成功通知", message); } // 续费失败通知 public void sendRenewalFailureNotification(Subscription sub, String reason) { User user = userRepo.findById(sub.getUserId()) .orElseThrow(() -> new UserNotFoundException(sub.getUserId())); String message = String.format( "您的%s会员续费失败(原因:%s),系统将在%d小时后再次尝试。", sub.getPlan().getName(), reason, calculateRetryHours(sub.getRenewalAttempts()) ); notificationService.send(user, "续费失败提醒", message); } // 自动续费关闭通知 public void sendAutoRenewDisabledNotification(Subscription sub, String reason) { User user = userRepo.findById(sub.getUserId()) .orElseThrow(() -> new UserNotFoundException(sub.getUserId())); String message = String.format( "由于连续%d次续费失败(原因:%s),您的%s会员自动续费功能已被关闭。", sub.getRenewalAttempts(), reason, sub.getPlan().getName() ); notificationService.send(user, "自动续费已关闭", message); } } ``` ### 关键设计要点 1. **支付令牌安全存储** ```java // 使用AES加密存储支付令牌 public class TokenEncryptor { private static final String SECRET_KEY = "your-256-bit-secret"; private static final String ALGORITHM = "AES/GCM/NoPadding"; public String encryptToken(String token) { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(token.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new TokenEncryptionException("支付令牌加密失败", e); } } public String decryptToken(String encrypted) { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decoded = Base64.getDecoder().decode(encrypted); return new String(cipher.doFinal(decoded)); } catch (Exception e) { throw new TokenDecryptionException("支付令牌解密失败", e); } } } ``` 2. **续费状态机设计** ```java // 续费状态机管理 public class RenewalStateMachine { private static final Map<RenewalState, Map<RenewalEvent, RenewalState>> TRANSITIONS = Map.of( RenewalState.PENDING, Map.of( RenewalEvent.SCHEDULED, RenewalState.PENDING, RenewalEvent.ATTEMPT, RenewalState.PROCESSING ), RenewalState.PROCESSING, Map.of( RenewalEvent.SUCCESS, RenewalState.COMPLETED, RenewalEvent.FAILURE, RenewalState.RETRYING, RenewalEvent.ABORT, RenewalState.ABORTED ), RenewalState.RETRYING, Map.of( RenewalEvent.ATTEMPT, RenewalState.PROCESSING, RenewalEvent.ABORT, RenewalState.ABORTED ) ); public RenewalState transition(RenewalState current, RenewalEvent event) { Map<RenewalEvent, RenewalState> events = TRANSITIONS.get(current); if (events == null || !events.containsKey(event)) { throw new IllegalStateException("无效状态转换: " + current + " -> " + event); } return events.get(event); } } enum RenewalState { PENDING, PROCESSING, RETRYING, COMPLETED, ABORTED } enum RenewalEvent { SCHEDULED, ATTEMPT, SUCCESS, FAILURE, ABORT } ``` 3. **账单记录与对账系统** ```java // 对账服务 @Service public class ReconciliationService { @Scheduled(cron = "0 0 4 * * ?") // 每天凌晨4点执行 public void dailyReconciliation() { LocalDate yesterday = LocalDate.now().minusDays(1); // 获取系统记录的所有续费交易 List<RenewalRecord> systemRecords = renewalRecordRepo.findByDate(yesterday); // 从支付网关获取实际交易 List<PaymentRecord> gatewayRecords = paymentGateway.getDailyReport(yesterday); // 对账处理 ReconciliationResult result = reconcile(systemRecords, gatewayRecords); // 处理差异 handleDiscrepancies(result.getDiscrepancies()); } private ReconciliationResult reconcile( List<RenewalRecord> systemRecords, List<PaymentRecord> gatewayRecords) { Map<String, RenewalRecord> systemMap = systemRecords.stream() .collect(Collectors.toMap(r -> r.getTransactionId(), r -> r)); Map<String, PaymentRecord> gatewayMap = gatewayRecords.stream() .collect(Collectors.toMap(p -> p.getTransactionId(), p -> p)); List<Discrepancy> discrepancies = new ArrayList<>(); // 检查系统有但网关没有的记录 systemMap.keySet().forEach(txId -> { if (!gatewayMap.containsKey(txId)) { discrepancies.add(new Discrepancy( txId, "MISSING_IN_GATEWAY", systemMap.get(txId) )); } }); // 检查金额不一致的记录 gatewayMap.values().forEach(gateway -> { RenewalRecord system = systemMap.get(gateway.getTransactionId()); if (system != null && system.getAmount().compareTo(gateway.getAmount()) != 0) { discrepancies.add(new Discrepancy( gateway.getTransactionId(), "AMOUNT_MISMATCH", system, gateway )); } }); return new ReconciliationResult(systemRecords.size(), gatewayRecords.size(), discrepancies); } } ``` ### 安全与合规设计 1. **PCI DSS合规处理** ```java // 支付令牌管理服务 @Service public class PaymentTokenService { // 使用第三方支付tokenization服务 public String tokenizePaymentInfo(PaymentCard card) { TokenizationRequest request = new TokenizationRequest( card.getNumber(), card.getExpiry(), card.getCvv() ); TokenizationResponse response = thirdPartyService.tokenize(request); return "tok_" + response.getToken(); } // 敏感数据掩码 public String maskCardNumber(String cardNumber) { if (cardNumber == null || cardNumber.length() < 12) return "****"; return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4); } } ``` 2. **GDPR合规设计** ```java // 用户数据清理服务 @Service public class DataCleanupService { @Transactional public void processDataDeletionRequest(Long userId) { // 匿名化用户数据 anonymizeUserData(userId); // 删除支付令牌 deletePaymentTokens(userId); // 保留必要的财务记录(仅匿名化) anonymizeFinancialRecords(userId); } private void anonymizeUserData(Long userId) { userRepo.update(userId, Map.of( "email", "deleted@" + UUID.randomUUID(), "phone", "deleted", "name", "Deleted User" )); } } ``` ### 性能优化方案 1. **批量处理优化** ```java // 批量续费处理 @Scheduled(fixedDelay = 30000) // 每30秒执行 public void processRenewalBatch() { List<Subscription> renewSubs = subscriptionRepo .findSubscriptionsForRenewalBatch(100); // 每次处理100条 if (!renewSubs.isEmpty()) { // 并行处理 renewSubs.parallelStream().forEach(this::processRenewal); } } ``` 2. **缓存优化** ```java // 用户支付令牌缓存 @Service public class TokenCacheService { private final Cache<Long, String> tokenCache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .maximumSize(10_000) .build(); public String getPaymentToken(Long userId) { return tokenCache.get(userId, key -> { Subscription sub = subscriptionRepo.findActiveByUserId(userId); return (sub != null) ? sub.getPaymentToken() : null; }); } } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值