当前位置: 首页 > news >正文

告别混乱概念!一文搞懂Stripe的Payment Intent、Session与Charge,并用SpringBoot 3实现订阅支付

告别混乱概念!一文搞懂Stripe的Payment Intent、Session与Charge,并用SpringBoot 3实现订阅支付

第一次接触Stripe的开发者,往往会被Payment Intent、Checkout Session、Charge、Price等概念搞得晕头转向。这些术语看似相似,实则各司其职,共同构成了Stripe强大的支付生态系统。本文将用通俗易懂的方式梳理这些核心概念的关系,并通过一个完整的SpringBoot 3.x项目,演示如何实现订阅支付功能。

1. Stripe支付核心概念解析

1.1 支付流程中的关键角色

想象Stripe的支付系统就像一家餐厅:

  • Customer:顾客,在Stripe中代表支付方,可以保存支付方式信息
  • Price:菜单上的价格,定义商品或服务的定价
  • Product:菜单项,代表你销售的商品或服务
  • PaymentIntent:顾客的"支付意图",记录支付状态和金额
  • Checkout Session:服务员,处理整个支付流程
  • Charge:实际的资金转移,相当于完成交易

1.2 概念关系图

Customer → 创建 Subscription → 使用 Price ↘ 创建 PaymentIntent → 通过 Checkout Session 完成支付 → 生成 Charge

1.3 何时使用哪种方式

场景推荐方式特点
一次性支付PaymentIntent简单直接,适合单次交易
复杂支付流程Checkout Session提供完整支付页面,支持多种支付方式
订阅服务Subscription自动周期性收费,管理生命周期
直接扣款Charge已获得客户授权时的直接扣款

2. SpringBoot 3.x集成Stripe基础配置

2.1 项目初始化

首先创建一个新的SpringBoot项目,添加必要的依赖:

<dependencies> <!-- Stripe Java SDK --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>26.3.0</version> </dependency> <!-- Spring Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 配置属性支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies>

2.2 配置Stripe密钥

在application.properties中添加:

stripe.api-key=sk_test_your_test_key_here stripe.webhook-secret=whsec_your_webhook_secret

创建配置类读取这些属性:

@Configuration @ConfigurationProperties(prefix = "stripe") public class StripeConfig { private String apiKey; private String webhookSecret; @PostConstruct public void init() { Stripe.apiKey = this.apiKey; } // getters and setters }

3. 实现订阅支付全流程

3.1 创建订阅产品

首先需要定义订阅产品和价格:

public String createSubscriptionProduct(String productName, String currency, Long unitAmount, String interval) { try { // 1. 创建产品 Product product = Product.builder() .setName(productName) .build() .create(); // 2. 创建价格 Price price = Price.builder() .setProduct(product.getId()) .setCurrency(currency) .setUnitAmount(unitAmount) .setRecurring(Price.Recurring.builder() .setInterval(Price.Recurring.Interval.valueOf(interval)) .build()) .build() .create(); return price.getId(); } catch (StripeException e) { throw new RuntimeException("创建订阅产品失败", e); } }

3.2 创建订阅Checkout Session

这是订阅流程的核心部分:

public String createSubscriptionCheckoutSession(String customerEmail, String priceId, String successUrl, String cancelUrl) { try { SessionCreateParams params = SessionCreateParams.builder() .setCustomerEmail(customerEmail) .setSuccessUrl(successUrl) .setCancelUrl(cancelUrl) .setMode(SessionCreateParams.Mode.SUBSCRIPTION) .addLineItem( SessionCreateParams.LineItem.builder() .setPrice(priceId) .setQuantity(1L) .build()) .build(); Session session = Session.create(params); return session.getUrl(); } catch (StripeException e) { throw new RuntimeException("创建订阅会话失败", e); } }

3.3 处理Webhook事件

订阅支付需要处理多种Webhook事件:

@RestController @RequestMapping("/webhook") public class StripeWebhookController { @Autowired private StripeConfig stripeConfig; @PostMapping public ResponseEntity<String> handleWebhook( @RequestBody String payload, @RequestHeader("Stripe-Signature") String sigHeader) { try { Event event = Webhook.constructEvent(payload, sigHeader, stripeConfig.getWebhookSecret()); switch (event.getType()) { case "customer.subscription.created": handleSubscriptionCreated(event); break; case "customer.subscription.updated": handleSubscriptionUpdated(event); break; case "customer.subscription.deleted": handleSubscriptionDeleted(event); break; case "invoice.payment_succeeded": handleInvoicePaid(event); break; case "invoice.payment_failed": handleInvoiceFailed(event); break; default: log.info("未处理的事件类型: {}", event.getType()); } return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } } private void handleSubscriptionCreated(Event event) { Subscription subscription = (Subscription) event.getData().getObject(); log.info("新订阅创建: {}", subscription.getId()); // 业务逻辑:激活用户订阅状态 } // 其他事件处理方法类似... }

4. 高级订阅管理功能

4.1 订阅升级与降级

允许用户更改订阅计划:

public Subscription changeSubscriptionPlan(String subscriptionId, String newPriceId) { try { Subscription subscription = Subscription.retrieve(subscriptionId); SubscriptionUpdateParams params = SubscriptionUpdateParams.builder() .addItem(SubscriptionUpdateParams.Item.builder() .setId(subscription.getItems().getData().get(0).getId()) .setPrice(newPriceId) .build()) .setProrationBehavior(SubscriptionUpdateParams.ProrationBehavior.CREATE_PRORATIONS) .build(); return subscription.update(params); } catch (StripeException e) { throw new RuntimeException("更改订阅计划失败", e); } }

4.2 订阅暂停与恢复

public Subscription pauseSubscription(String subscriptionId) { try { SubscriptionUpdateParams params = SubscriptionUpdateParams.builder() .setPauseCollection(SubscriptionUpdateParams.PauseCollection.builder() .setBehavior(SubscriptionUpdateParams.PauseCollection.Behavior.VOID) .build()) .build(); return Subscription.retrieve(subscriptionId).update(params); } catch (StripeException e) { throw new RuntimeException("暂停订阅失败", e); } } public Subscription resumeSubscription(String subscriptionId) { try { SubscriptionUpdateParams params = SubscriptionUpdateParams.builder() .setPauseCollection(SubscriptionUpdateParams.PauseCollection.builder() .setBehavior(SubscriptionUpdateParams.PauseCollection.Behavior.UNPAUSE) .build()) .build(); return Subscription.retrieve(subscriptionId).update(params); } catch (StripeException e) { throw new RuntimeException("恢复订阅失败", e); } }

4.3 优惠券与折扣

public Subscription applyCouponToSubscription(String subscriptionId, String couponId) { try { SubscriptionUpdateParams params = SubscriptionUpdateParams.builder() .setCoupon(couponId) .build(); return Subscription.retrieve(subscriptionId).update(params); } catch (StripeException e) { throw new RuntimeException("应用优惠券失败", e); } }

5. 测试与调试技巧

5.1 测试信用卡号

Stripe提供了一系列测试卡号:

卡号场景
4242424242424242基本成功支付
40000000000032203D Secure验证
4000000000000002支付失败
5555555555554444国际信用卡(Master)

5.2 Webhook本地测试

使用Stripe CLI进行本地测试:

stripe listen --forward-to localhost:8080/webhook stripe trigger payment_intent.succeeded

5.3 常见问题排查

提示:当Webhook无法正常工作时,首先检查签名验证和事件类型过滤是否正确

  1. 支付失败:检查是否设置了正确的支付方式
  2. Webhook未触发:验证端点URL是否可公开访问
  3. 订阅不续费:检查客户是否有有效的支付方式
  4. 货币不匹配:确保所有操作使用相同的货币

在实际项目中,我发现最常遇到的问题是对事件处理的不完整。例如,只处理了订阅创建事件,却忽略了续费失败的情况。建议为所有关键事件都添加日志记录,这样当问题发生时可以快速定位。

http://www.cnnetsun.cn/news/1599231.html

相关文章:

  • GLM-4.1V-9B-Base参数详解:temperature/top_p对图文问答稳定性影响
  • RK3588 PCIE设备全解析:从Realtek网卡到Intel SSD的地址映射与驱动加载
  • rPPG远程生理监测:5个简单步骤从零构建无接触健康分析系统
  • 避坑指南:C# FFT计算声音频谱时,采样率、汉明窗与复数处理的那些细节
  • 从工作流到超级智能体,Claude Code 重构AI应用底层逻辑
  • 【仅限首批读者】Java等保三级测评前72小时紧急加固包:含配置检查脚本、渗透测试用例、整改报告模板(2024新版)
  • 华为交换机Combo接口:从原理到实战的灵活组网指南
  • 终极LaTeX-PPT解决方案:3分钟告别PowerPoint公式排版噩梦
  • DrissionPage无头模式破盾记:实战绕过CloudFlare 5秒验证
  • 为什么选择ODB++格式?Cadence与HyperLynx数据交换的最佳实践
  • 告别付费IP!手把手教你用ZCU102 PS端DP接口点亮显示器(附参数调试心得)
  • 5个场景带你体验KISS Translator:让网页双语阅读不再是难题
  • 昇腾910A单卡部署Qwen2-7B API实战:从性能测试到OpenAI接口调优全记录
  • 程序实现静电干扰自动屏蔽,无需额外硬件,颠覆抗干扰全靠硬件的观念。
  • 人肉区块链:用群体记忆对抗AI篡改
  • 字节面试官都在问的Multi-Agent教程(非常详细),架构设计与实战从入门到精通,收藏这一篇就够了!
  • 保姆级教程:用UE5.3的SimpleHttpServer插件,5分钟搞定一个局域网可用的HTTP服务
  • 新手福音:跟着快马生成的提示词轻松完成openclaw windows部署入门
  • 别再写定时任务了!用Kettle的‘插入/更新’组件,每周自动同步MySQL增量数据
  • RabbitMQ消息丢了怎么办?用aio-pika写个可靠的Python消费者(含自动重连与死信队列配置)
  • 别再死记硬背了!用CODESYS V3.5 SP18手把手实现两台PLC的Socket互发数据
  • 告别臃肿!用原生Python+UPX打包exe,体积缩小80%的保姆级教程
  • AI辅助开发:让快马模型智能理解你的网址,自动生成完美打印文档代码
  • 基于电压/电流数值分析的逆变器故障诊断方法:流程图与结构拓扑图详解
  • 从二极管到MOSFET:手把手拆解电源中的‘象限’密码,搞定逆变器与同步整流选型
  • 数据采集卡选型必看:同步采样 vs 异步采样,你的多通道应用到底该选哪个?(以NI和研华为例)
  • Android AudioEffect 音效方案:从基础到高级的动态处理技术
  • MelonLoader终极指南:Unity游戏模组开发的跨架构解决方案
  • Lychee Rerank与SpringBoot集成:Java开发生态对接
  • 农业新质生产力数据(2012-2022年)