Stripe 支付集成实战指南:Checkout Sessions、订阅计费与 Webhook 安全处理(agents24 stripe-integration 技能解析)
【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents
导读
本文基于 agents24 仓库中 stripe-integration 技能 及其 详细模式文档,系统讲解 Stripe 支付集成的完整技术路径:从 Checkout Sessions 与 Payment Intents 的选择,到订阅计费、客户支付方式管理、Webhook 签名校验与幂等处理,再到退款、争议与测试。读完本文,你将掌握一套可直接落地的 PCI 合规支付接入方案,能够独立完成 Web/移动应用的一次性支付、订阅计费与自动化账单处理。
技能定位与适用场景
stripe-integration是支付处理插件(plugins/payment-processing)中的核心技能,用于实现健壮、PCI 合规的支付流程,覆盖结账(checkout)、订阅(subscription)与 Webhook 三个关键领域。该技能尤其适用于以下场景:
- 在 Web/移动应用中实现支付处理;
- 搭建订阅计费系统;
- 处理一次性支付与周期性扣款;
- 处理退款(refund)与争议(dispute);
- 管理客户支付方式;
- 面向欧洲市场的 SCA(Strong Customer Authentication,强客户认证)合规支付;
- 基于 Stripe Connect 构建市场/平台型支付流。
从技能定义(frontmatter)看,其 description 明确要求"在集成 Stripe 支付、构建订阅系统或实现安全结账流时使用",与同目录下的 payment-integration Agent(统筹 Stripe/PayPal/Square 集成)和 pci-compliance 技能(PCI DSS 合规)形成互补——前者负责策略统筹,后者负责安全底线。
核心概念一:三种支付流的选型
Stripe 集成中最容易出错的就是选错 API 抽象层。该技能明确将支付流划分为三种:
1. Checkout Sessions(推荐用于大多数集成)
Checkout Sessions 是 Stripe 官方推荐的首选方案,因为它在服务端一次调用即可生成一个完整的结账会话,并内置了大量开箱即用的能力:
- 支持全部 UI 形态:Stripe 托管结账页(hosted checkout)、嵌入式结账表单(embedded checkout)、以及基于 Elements 的自定义 UI(使用
ui_mode='custom'配合 Payment Element、Express Checkout Element); - 内置结账能力:line items(商品行)、折扣(discounts)、税费(tax)、运费(shipping)、地址收集(address collection)、保存支付方式(saved payment methods)以及完整的结账生命周期事件;
- 相比 Payment Intents 的集成与长期维护成本更低——支付方式的多样性、SCA 等合规细节由 Stripe 托管处理。
2. Payment Intents(定制化控制)
当你的业务需要完全掌控结账体验时使用 Payment Intents,但其代价是:
- 你需要自行计算最终金额,包括税费、折扣、订阅价格与货币转换;
- 实现复杂度与长期维护负担显著高于 Checkout Sessions;
- 由于需要在自建页面上收集卡信息,必须依赖 Stripe.js(Elements)才能满足 PCI 合规——原始卡号绝不允许经过你的服务器。
3. Setup Intents(保存支付方式,不扣款)
用于在不发起扣款的前提下收集并保存客户的支付方式,典型场景是:
- 创建订阅前的支付方式绑定;
- 为未来的"后付费"(pay later)场景做准备。
与 Payment Intents 的关键区别在于 Setup Intents 只保存支付授权,不产生交易金额;且需要客户确认(confirmation)后才算完成设置。
核心概念二:Webhook 关键事件
支付是典型的异步流程:扣款成功、订阅变更等结果通过 Webhook 推送到你的服务端。技能列出以下必须监听的关键事件:
| 事件类型 | 语义 |
|---|---|
payment_intent.succeeded | 支付完成 |
payment_intent.payment_failed | 支付失败 |
customer.subscription.updated | 订阅变更(如价格调整、周期变化) |
customer.subscription.deleted | 订阅被取消 |
charge.refunded | 退款处理完成 |
invoice.payment_succeeded | 订阅账单支付成功(周期性收款成功) |
后续的"Webhook 安全处理"小节将给出这些事件的完整落地方案。
核心概念三:订阅模型与客户管理
订阅的四层对象模型
技能用四个对象概括 Stripe 订阅计费的领域模型:
- Product(产品):你出售的东西(抽象的商品或服务定义);
- Price(价格):卖多少钱、多久收一次(如 20 美元/月);
- Subscription(订阅):客户与你的周期性付款约定;
- Invoice(发票):每个计费周期自动生成一次。
这四层与 billing-automation 技能 描述的账单生命周期(trial → active → past_due → canceled / paused / resumed)协同工作:Stripe 负责按周期出账与扣款,应用侧只需围绕invoice.payment_succeeded等事件做业务响应。
客户管理四要素
- 创建并管理客户记录(
stripe.Customer); - 为客户保存多个支付方式;
- 跟踪客户 metadata(自定义元数据,如内部 user_id);
- 管理账单细节(billing details)。
快速上手:一条命令创建订阅结账会话
技能提供的 Quick Start 是理解 Stripe 集成的最短路径——只需一个stripe.checkout.Session.create调用,即可获得一个可重定向的托管结账地址:
import stripe stripe.api_key = "sk_test_..." # Create a checkout session session = stripe.checkout.Session.create( line_items=[{ 'price_data': { 'currency': 'usd', 'product_data': { 'name': 'Premium Subscription', }, 'unit_amount': 2000, # $20.00(单位:美分) 'recurring': { 'interval': 'month', }, }, 'quantity': 1, }], mode='subscription', success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}', cancel_url='https://yourdomain.com/cancel' ) # 将用户重定向到 session.url print(session.url)要点说明:
mode='subscription'与price_data.recurring组合声明这是一个周期性订阅;若仅需一次性支付,将mode改为'payment'并去掉recurring即可;unit_amount的单位是最小货币单位(美分),2000即 $20.00;success_url/cancel_url中的{CHECKOUT_SESSION_ID}是 Stripe 自动替换的模板占位符,用于回跳后在服务端查询会话状态;stripe.api_key必须使用测试密钥sk_test_...,详见文末"测试"章节。
五大实操模式(来自 references/details.md)
技能主文档把深度内容收敛在 references/details.md 中,以下五个模式是其中最核心的实战模板,可直接复制改造。
模式一:一次性支付(托管结账)
适用于最简单的商品售卖场景,把mode设为'payment',并可通过metadata携带业务侧的订单与用户标识:
def create_checkout_session(amount, currency='usd'): """Create a one-time payment checkout session.""" try: session = stripe.checkout.Session.create( line_items=[{ 'price_data': { 'currency': currency, 'product_data': { 'name': 'Blue T-shirt', 'images': ['https://example.com/product.jpg'], }, 'unit_amount': amount, # Amount in cents }, 'quantity': 1, }], mode='payment', success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}', cancel_url='https://yourdomain.com/cancel', metadata={ 'order_id': 'order_123', 'user_id': 'user_456' } ) return session except stripe.error.StripeError as e: # Handle error print(f"Stripe error: {e.user_message}") raise注意stripe.error.StripeError异常基类与e.user_message(面向用户的友好错误信息)的使用,这是生产级代码的基本错误处理姿势。
模式二:Elements + Checkout Sessions(自定义 UI)
当你想把结账页嵌入自己的站点而非跳转到 Stripe 托管页时,使用ui_mode='custom'创建会话,服务端只返回client_secret给前端:
def create_checkout_session_for_elements(amount, currency='usd'): """Create a checkout session configured for Payment Element.""" session = stripe.checkout.Session.create( mode='payment', ui_mode='custom', line_items=[{ 'price_data': { 'currency': currency, 'product_data': {'name': 'Blue T-shirt'}, 'unit_amount': amount, }, 'quantity': 1, }], return_url='https://yourdomain.com/complete?session_id={CHECKOUT_SESSION_ID}' ) return session.client_secret # Send to frontend前端使用stripe.initCheckout接管整个结账流程:加载动作、挂载 Payment Element、更新邮箱、触发确认:
const stripe = Stripe("pk_test_..."); const appearance = { theme: "stripe" }; const checkout = stripe.initCheckout({ clientSecret, elementsOptions: { appearance }, }); const loadActionsResult = await checkout.loadActions(); if (loadActionsResult.type === "success") { const { actions } = loadActionsResult; const session = actions.getSession(); const button = document.getElementById("pay-button"); const checkoutContainer = document.getElementById("checkout-container"); const emailInput = document.getElementById("email"); const emailErrors = document.getElementById("email-errors"); const errors = document.getElementById("confirm-errors"); // Display a formatted string representing the total amount checkoutContainer.append(`Total: ${session.total.total.amount}`); // Mount Payment Element const paymentElement = checkout.createPaymentElement(); paymentElement.mount("#payment-element"); // Store email for submission emailInput.addEventListener("blur", () => { actions.updateEmail(emailInput.value).then((result) => { if (result.error) emailErrors.textContent = result.error.message; }); }); // Handle form submission button.addEventListener("click", () => { actions.confirm().then((result) => { if (result.type === "error") errors.textContent = result.error.message; }); }); }关键点:前端拿到的是client_secret与公开密钥pk_test_...,卡号等敏感数据由 Stripe 的 iframe 收集,你的服务器与前端 JS 全程不接触原始卡号——这正是 PCI 合规的核心前提。
模式三:Elements + Payment Intents(自建结账 UI 的替代方案)
官方推荐优先采用模式二,但当你需要完全自定义结账页时,可以用 Payment Intents 替代。服务端创建 Payment Intent 并返回client_secret:
def create_payment_intent(amount, currency='usd', customer_id=None): """Create a payment intent for bespoke checkout UI with Payment Element.""" intent = stripe.PaymentIntent.create( amount=amount, currency=currency, customer=customer_id, automatic_payment_methods={ 'enabled': True, }, metadata={ 'integration_check': 'accept_a_payment' } ) return intent.client_secret # Send to frontend前端通过stripe.confirmPayment完成确认:
// Mount Payment Element and confirm via Payment Intents const stripe = Stripe("pk_test_..."); const appearance = { theme: "stripe" }; const elements = stripe.elements({ appearance, clientSecret }); const paymentElement = elements.create("payment"); paymentElement.mount("#payment-element"); document.getElementById("pay-button").addEventListener("click", async () => { const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: "https://yourdomain.com/complete", }, }); if (error) { document.getElementById("errors").textContent = error.message; } });automatic_payment_methods.enabled=True意味着 Stripe 会根据客户地区自动提供合适的支付方式并处理 SCA 认证,显著降低合规实现成本。
模式四:创建订阅(含首期付款确认)
订阅创建与一次性支付的关键差异在于payment_behavior与expand参数:前者控制首期付款失败时的行为,后者让你在一次往返中拿到首张发票对应的 Payment Intent,以便直接向客户发起付款确认:
def create_subscription(customer_id, price_id): """Create a subscription for a customer.""" try: subscription = stripe.Subscription.create( customer=customer_id, items=[{'price': price_id}], payment_behavior='default_incomplete', payment_settings={'save_default_payment_method': 'on_subscription'}, expand=['latest_invoice.payment_intent'], ) return { 'subscription_id': subscription.id, 'client_secret': subscription.latest_invoice.payment_intent.client_secret } except stripe.error.StripeError as e: print(f"Subscription creation failed: {e}") raisepayment_behavior='default_incomplete':订阅以incomplete状态创建,直到首期付款成功后才转为active;payment_settings.save_default_payment_method='on_subscription':将本次使用的支付方式自动保存为默认支付方式,后续周期扣款无需客户重复授权;- 返回的
client_secret用于前端完成首期付款的 3D Secure / SCA 认证。
模式五:客户自助门户(Billing Portal)
为减少客服成本,Stripe 提供托管客户门户,让客户自助管理订阅与支付方式。服务端只需一次调用并重定向:
def create_customer_portal_session(customer_id): """Create a portal session for customers to manage subscriptions.""" session = stripe.billing_portal.Session.create( customer=customer_id, return_url='https://yourdomain.com/account', ) return session.url # Redirect customer here客户可以在门户中完成升级/降级套餐、更换支付方式、查看发票、取消订阅等操作,而这些动作产生的customer.subscription.updated/deleted事件会通过 Webhook 同步回你的系统。
Webhook 安全处理:签名校验与幂等
支付事件异步到达,安全性是 Webhook 端点设计的头等大事。技能给出了完整的 Flask 实现范式:
from flask import Flask, request import stripe app = Flask(__name__) endpoint_secret = 'whsec_...' @app.route('/webhook', methods=['POST']) def webhook(): payload = request.data sig_header = request.headers.get('Stripe-Signature') try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError: # Invalid payload return 'Invalid payload', 400 except stripe.error.SignatureVerificationError: # Invalid signature return 'Invalid signature', 400 # Handle the event if event['type'] == 'payment_intent.succeeded': payment_intent = event['data']['object'] handle_successful_payment(payment_intent) elif event['type'] == 'payment_intent.payment_failed': payment_intent = event['data']['object'] handle_failed_payment(payment_intent) elif event['type'] == 'customer.subscription.deleted': subscription = event['data']['object'] handle_subscription_canceled(subscription) return 'Success', 200 def handle_successful_payment(payment_intent): """Process successful payment.""" customer_id = payment_intent.get('customer') amount = payment_intent['amount'] metadata = payment_intent.get('metadata', {}) # Update your database # Send confirmation email # Fulfill order print(f"Payment succeeded: {payment_intent['id']}") def handle_failed_payment(payment_intent): """Handle failed payment.""" error = payment_intent.get('last_payment_error', {}) print(f"Payment failed: {error.get('message')}") # Notify customer # Update order status def handle_subscription_canceled(subscription): """Handle subscription cancellation.""" customer_id = subscription['customer'] # Update user access # Send cancellation email print(f"Subscription canceled: {subscription['id']}")这一范式与 payment-integration Agent 中"Critical Requirements"章节的要求完全吻合,归纳为五条硬性约束:
- 签名校验不可省略:必须使用官方 SDK(如
stripe.Webhook.construct_event)验证Stripe-Signature头。ValueError表示 payload 非法,SignatureVerificationError表示签名不匹配,均须返回 400。跳过签名校验等同于向恶意请求敞开系统大门; - 保留原始请求体:验签基于原始字节,任何 JSON 中间件对 body 的改写都会破坏签名验证——这正是示例中使用
request.data而非request.get_json()的原因; - 幂等处理:Webhook 失败会重试,且 Stripe 不保证单次投递。必须把
event.id存入数据库,处理前先查重; - 快速响应:应在200ms 内返回
2xx,把数据库写入、外部 API 调用等耗时操作放到响应之后异步执行,否则超时触发重试会导致重复处理; - 服务端二次确认:支付状态以服务端向 Stripe API 重新查询的结果为准,不要轻信 Webhook payload 或前端返回值。
Webhook 签名的手动验证与幂等封装
若无法使用 SDK(或想深入理解原理),技能也提供了 HMAC-SHA256 的手动验证实现与幂等包装器:
import hashlib import hmac def verify_webhook_signature(payload, signature, secret): """Manually verify webhook signature.""" expected_sig = hmac.new( secret.encode('utf-8'), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_sig) def handle_webhook_idempotently(event_id, handler): """Ensure webhook is processed exactly once.""" # Check if event already processed if is_event_processed(event_id): return # Process event try: handler() mark_event_processed(event_id) except Exception as e: log_error(e) # Stripe will retry failed webhooks raise注意hmac.compare_digest的使用——它执行常量时间比较,可防御时序攻击。幂等包装器的核心思想是:先查重、再处理、成功后标记、失败则抛出异常以触发 Stripe 重试。
客户与支付方式管理
围绕stripe.Customer与stripe.PaymentMethod的完整生命周期管理:
def create_customer(email, name, payment_method_id=None): """Create a Stripe customer.""" customer = stripe.Customer.create( email=email, name=name, payment_method=payment_method_id, invoice_settings={ 'default_payment_method': payment_method_id } if payment_method_id else None, metadata={ 'user_id': '12345' } ) return customer def attach_payment_method(customer_id, payment_method_id): """Attach a payment method to a customer.""" stripe.PaymentMethod.attach( payment_method_id, customer=customer_id ) # Set as default stripe.Customer.modify( customer_id, invoice_settings={ 'default_payment_method': payment_method_id } ) def list_customer_payment_methods(customer_id): """List all payment methods for a customer.""" payment_methods = stripe.PaymentMethod.list( customer=customer_id, type='card' ) return payment_methods.data三个动作对应三类场景:
- create_customer:注册即建档,若客户在注册时已提供支付方式 token,可同时将其设为默认支付方式;
- attach_payment_method:客户在账户设置中新增/更换卡时,先
attach再通过Customer.modify提升为默认支付方式; - list_customer_payment_methods:在结账页或账户页展示已保存的卡(仅返回 token 化的支付方式对象,绝不包含完整卡号)。
退款与争议处理
退款支持全额与部分退款,并可附加退款原因;争议(dispute/拒付)则以证据提交为处理手段:
def create_refund(payment_intent_id, amount=None, reason=None): """Create a refund.""" refund_params = { 'payment_intent': payment_intent_id } if amount: refund_params['amount'] = amount # Partial refund if reason: refund_params['reason'] = reason # 'duplicate', 'fraudulent', 'requested_by_customer' refund = stripe.Refund.create(**refund_params) return refund def handle_dispute(charge_id, evidence): """Update dispute with evidence.""" stripe.Dispute.modify( charge_id, evidence={ 'customer_name': evidence.get('customer_name'), 'customer_email_address': evidence.get('customer_email'), 'shipping_documentation': evidence.get('shipping_proof'), 'customer_communication': evidence.get('communication'), } )- 不传
amount即为全额退款,传入amount则执行部分退款; reason的合法取值包括duplicate(重复收费)、fraudulent(欺诈)与requested_by_customer(客户要求);- 争议处理的关键是在 Stripe 规定的时限内提交证据(客户姓名/邮箱、物流凭证、客服沟通记录),证据的质量直接决定拒付仲裁结果。退款的最终结果通过
charge.refundedWebhook 事件回传。
测试:测试密钥、测试卡与全流程验证
技能强调所有开发工作都应基于测试模式(test mode),并给出了完整的测试卡矩阵与全流程测试代码:
# Use test mode keys stripe.api_key = "sk_test_..." # Test card numbers TEST_CARDS = { 'success': '4242424242424242', 'declined': '4000000000000002', '3d_secure': '4000002500003155', 'insufficient_funds': '4000000000009995' } def test_payment_flow(): """Test complete payment flow.""" # Create test customer customer = stripe.Customer.create( email="test@example.com" ) # Create payment intent intent = stripe.PaymentIntent.create( amount=1000, automatic_payment_methods={ 'enabled': True }, currency='usd', customer=customer.id ) # Confirm with test card confirmed = stripe.PaymentIntent.confirm( intent.id, payment_method='pm_card_visa' # Test payment method ) assert confirmed.status == 'succeeded'测试卡矩阵语义:
| 测试卡号 | 场景 |
|---|---|
4242424242424242 | 支付成功 |
4000000000000002 | 支付被拒绝 |
4000002500003155 | 需要 3D Secure 认证 |
4000000000009995 | 余额不足 |
测试要点:
stripe.PaymentIntent.confirm支持直接传入 Stripe 预置的测试支付方式 token(如pm_card_visa),无需真实卡号即可触发完整扣款链路;- 用
pm_card_visa配合assert confirmed.status == 'succeeded'验证主流程;换用4000000000000002等卡号即可回归失败路径; - 测试模式与生产模式必须严格隔离。payment-integration Agent 特别警告:误配置导致生产环境接受测试卡是真实的 PCI 违规事故——"Test credentials must fail in production"。
PCI 合规要点:为什么不该碰原始卡号
Stripe 技能反复强调"PCI 合规"并非口号,而是有具体的架构约束。结合 pci-compliance 技能 的 12 项 PCI DSS 核心要求,本技能落地为三条可直接执行的原则:
- 绝不处理原始卡数据:卡号、CVV 必须由 Stripe.js / Elements / Stripe 托管页在 Stripe 的 iframe 内收集并 token 化。你的服务器永远不要存储、传输或记录完整卡号。pci-compliance 技能中给出了日志脱敏示例(PAN 掩码:保留前 6 后 4 位,中间打码)以及"禁止存储"清单(磁道数据、CVV、PIN 属于永远禁止项);
- 服务端校验:所有支付验证必须在服务端通过直连 Stripe API 完成,而不是信任前端返回结果;
- 环境隔离:测试密钥在生产环境必须失效。
更进一步,pci-compliance 技能 还提供了两类兜底方案供确实需要接触卡数据的团队参考:一是服务端仅接触 token(charge_with_token、store_payment_method);二是高级自定义 token vault(基于secrets.token_urlsafe生成随机 token,配合 Fernet 对称加密存储卡数据映射),以及在传输层强制 TLS 1.2+、会话 Cookie 安全属性(Secure/HttpOnly/SameSite)等加固手段。
与同插件其他技能的协作边界
stripe-integration并非孤立存在,它与 plugins/payment-processing 下的其他技能形成完整体系:
- billing-automation:在订阅模型之上补充计费周期、dunning(欠费催缴重试)、proration(按比例计费)、税务计算等自动化能力;
- pci-compliance:提供 PCI DSS 合规清单、数据最小化与 tokenization/加密实现;
- paypal-integration:多支付通道场景下与 Stripe 并行使用;
- payment-integration Agent:作为策略层,统筹以上技能的选用时机,并沉淀了 Webhook 安全、幂等、快速响应等跨通道通用要求。
结语:从技能到生产可用的接入路径
回顾整个技能体系,一条清晰的落地路径是:先用 Checkout Sessions 以最小成本跑通一次性支付与订阅结账;当需要自定义 UI 时升级到ui_mode='custom'的 Elements 方案;用 Setup Intents 完成"先绑卡、后扣款"场景;围绕六大 Webhook 事件搭建带签名校验与幂等处理的事件处理层;最后用测试卡矩阵覆盖成功、拒绝、3D Secure、余额不足四类路径,再在充分验证后切换生产密钥。全程守住"服务器不碰原始卡号"这条 PCI 红线,即完成了一套健壮、合规、可维护的 Stripe 支付接入。
延伸阅读(仓库内路径)
- stripe-integration 技能主文档
- stripe-integration 详细模式文档
- pci-compliance 技能(PCI DSS 与 tokenization)
- billing-automation 技能(订阅生命周期与 dunning)
- payment-integration Agent(支付集成策略与安全要求)
【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考