本教程详细阐述了在使用Stripe PaymentIntent API时,如何安全地保存客户银行卡信息以供未来支付。强调了PCI DSS合规性的重要性,并指导开发者利用Stripe的PaymentMethod和Customer对象,而非自行存储敏感卡数据,从而实现便捷且安全的重复支付体验。
1. 理解PCI DSS合规性与数据安全
在处理银行卡数据时,首要且最关键的原则是遵守支付卡行业数据安全标准(PCI DSS)。PCI DSS旨在保护持卡人数据,其要求非常严格。除非您的组织达到了PCI DSS一级服务提供商或SAQ D级别,否则绝不应在自己的服务器上存储原始银行卡号、有效期或CVV等敏感信息。自行存储这些数据不仅风险巨大,还可能导致严重的法律和财务后果。
Stripe作为PCI DSS一级服务提供商,提供了安全的方式来处理和存储银行卡信息。最佳实践是让Stripe来处理所有敏感数据,您只需存储Stripe返回的非敏感标识符(如PaymentMethod ID)。
2. Stripe的解决方案:PaymentMethod与Customer对象
Stripe提供了PaymentMethod和Customer对象来安全地管理客户的支付信息。
- PaymentMethod:代表一种支付方式(如银行卡、银行账户等)。它包含了加密的支付信息,并由Stripe安全存储。您会获得一个pm_xxx格式的ID,用于后续操作。
- Customer:代表您的客户。您可以将一个或多个PaymentMethod关联到特定的Customer对象。这样,当客户再次购买时,您只需使用其Customer ID和关联的PaymentMethod ID即可完成支付,无需再次输入卡信息。
3. 在PaymentIntent流程中保存银行卡信息
Stripe允许您在创建PaymentIntent时,同时收集并保存客户的支付方式,以供未来使用。这通过在创建PaymentIntent时设置setup_future_usage参数来实现。
3.1 后端实现:创建带有 setup_future_usage 的 PaymentIntent
当您的前端请求创建一个支付意图时,后端(例如Spring Boot)应在创建PaymentIntent时指定setup_future_usage参数。这告诉Stripe在支付成功后保存此支付方式。
// Spring Boot (Java) 示例 - 创建 PaymentIntent import com.stripe.Stripe; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; public class PaymentService { public String createPaymentIntentWithSaveOption(long amount, String currency, String customerId) throws StripeException { Stripe.apiKey = "YOUR_STRIPE_SECRET_KEY"; // 替换为您的Stripe Secret Key PaymentIntentCreateParams.Builder paramsBuilder = PaymentIntentCreateParams.builder() .setAmount(amount) // 支付金额,单位为最小货币单位(如美分) .setCurrency(currency) // 货币类型,如 "usd", "eur" .addPaymentMethodType("card") // 允许的支付方式类型 .setSetupFutureUsage(PaymentIntentCreateParams.SetupFutureUsage.OFF_SESSION); // 关键:设置为离线会话使用 // 如果您有客户ID,可以将其关联到PaymentIntent if (customerId != null && !customerId.isEmpty()) { paramsBuilder.setCustomer(customerId); } PaymentIntent intent = PaymentIntent.create(paramsBuilder.build()); return intent.getClientSecret(); // 返回 client secret 给前端 } }
setup_future_usage 参数的含义:
- off_session:表示此支付方式将在客户不在场(离线)时被复用。
- on_session:表示此支付方式将在客户在场(在线)时被复用。
通常,为了实现“保存卡片,下次无需输入”的功能,您会选择off_session。
3.2 前端实现:使用 Payment Element 确认 PaymentIntent
前端(例如Angular应用)使用Stripe.js和Payment Element来收集客户的银行卡信息,并确认PaymentIntent。前端无需关心setup_future_usage,因为它已在后端PaymentIntent创建时设置。
// Angular Component (AppComponent) import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ActivatedRoute } from '@angular/router'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; // 确保在 index.html 中引入了 Stripe.js declare var Stripe: any; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'stripePaymentIntents'; clientSecret: string = ""; elements: any; email?: string; paymentElement: any; // 用于挂载 Payment Element // ... 其他属性和构造函数 constructor(private http: HttpClient, private sanitizer: DomSanitizer, private route: ActivatedRoute) {} ngOnInit(): void { // 1. 从后端获取 clientSecret this.http.get("http://localhost:8080/api/payment/intent/clientSecret", {responseType:'text'}).subscribe(secret => { this.clientSecret = secret; // 2. 初始化 Stripe Elements const appearance = { theme: 'stripe' }; this.elements = Stripe(this.clientSecret).elements({ appearance, clientSecret: this.clientSecret }); // 3. 挂载 Payment Element // 注意:这里使用 'payment' 而不是 'card',因为 Payment Element 是更推荐的集成方式 // 它会自动处理各种支付方式的UI,包括卡片。 this.paymentElement = this.elements.create("payment"); this.paymentElement.mount("#payment-element"); // 可选:挂载 Link Authentication Element const linkAuthenticationElement = this.elements.create("linkAuthentication"); linkAuthenticationElement.mount("#link-authentication-element"); linkAuthenticationElement.on('change', (event:any) => { this.email = event.value.email; }); }); } async confirmPayment() { if (!this.elements || !this.clientSecret) { console.error("Stripe Elements or clientSecret not initialized."); return; } // 4. 确认支付意图 // Stripe.confirmPayment() 是新版推荐的确认方式 // 它会自动处理 3D Secure 等强客户认证流程 const { error, paymentIntent } = await Stripe(this.clientSecret).confirmPayment({ elements: this.elements, confirmParams: { // 确保 return_url 指向您的应用中处理支付结果的页面 return_url: 'http://localhost:4200/payment-success', // 示例:支付成功后的跳转URL // 可选:设置账单详情,如果 Payment Element 没有自动收集 // billing_details: { name: 'Barack Obama', email: this.email } }, redirect: 'if_required' // 自动处理重定向(如3D Secure) }); if (error) { // 显示错误信息给用户 console.error("Payment confirmation failed:", error.message); // const messageContainer = document.querySelector('#payment-message'); // if (messageContainer) messageContainer.textContent = error.message; } else if (paymentIntent.status === 'succeeded') { console.log("Payment succeeded!", paymentIntent); // 支付成功,此时 PaymentMethod 已经与 PaymentIntent 关联并可能已保存 // 您可以在这里进行后续的业务逻辑,例如更新订单状态 // paymentIntent.payment_method 将包含已保存的 PaymentMethod ID (pm_xxx) console.log("Saved PaymentMethod ID:", paymentIntent.payment_method); } else { console.log("Payment status:", paymentIntent.status); // 处理其他状态,如 'requires_action'(Stripe.confirmPayment() 通常会处理重定向) } } // testCreateCard() 方法可以替换为 confirmPayment() // @deprecated: testCreateCard 逻辑已合并到 confirmPayment // testCreateCard() { /* ... */ } }
关键点:
- 使用elements.create(“payment”)创建Payment Element,它能灵活处理多种支付方式。
- 使用Stripe(clientSecret).confirmPayment()来确认支付。这个方法会自动处理3D Secure等强客户认证流程,并在必要时进行页面重定向。
- 当PaymentIntent成功完成(status: ‘succeeded’)时,如果后端设置了setup_future_usage,Stripe会自动保存此支付方式,并将其ID关联到PaymentIntent或指定的Customer。
4. 复用已保存的 PaymentMethod
一旦您通过PaymentIntent或SetupIntent保存了PaymentMethod,并将其关联到Customer对象,您就可以在未来的支付中复用它,无需客户再次输入卡信息。
4.1 后端实现:使用已保存的 PaymentMethod 创建 PaymentIntent
当客户选择使用已保存的支付方式时,您的后端需要使用该PaymentMethod ID和Customer ID来创建新的PaymentIntent。
// Spring Boot (Java) 示例 - 使用已保存的 PaymentMethod 创建 PaymentIntent import com.stripe.Stripe; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; public class PaymentService { public PaymentIntent createPaymentIntentWithSavedMethod(long amount, String currency, String customerId, String paymentMethodId) throws StripeException { Stripe.apiKey = "YOUR_STRIPE_SECRET_KEY"; PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(amount) .setCurrency(currency) .setCustomer(customerId) // 关联到客户 .setPaymentMethod(paymentMethodId) // 使用已保存的 PaymentMethod ID .setConfirm(true) // 立即确认支付 .setOffSession(true) // 如果是离线支付,设置为 true .build(); PaymentIntent paymentIntent = PaymentIntent.create(params); return paymentIntent; } }
setConfirm(true) 和 setOffSession(true) 的重要性:
- setConfirm(true):指示Stripe在创建PaymentIntent后立即尝试确认支付。
- setOffSession(true):表示此支付是在客户不在场的情况下进行的。如果支付需要客户交互(如3D Secure),而off_session为true,Stripe可能会拒绝支付或要求额外的处理。因此,确保您保存的PaymentMethod适合离线使用,或者在需要时引导客户完成额外的认证。
4.2 前端实现:触发复用支付
前端通常会展示客户已保存的支付方式列表。当客户选择其中一个时,前端将相应的PaymentMethod ID和Customer ID发送给后端,由后端发起支付。
// Angular Component - 触发复用支付的示例方法 // 假设您已经获取了客户的 PaymentMethod 列表 import { HttpClient } from '@angular/common/http'; export class AppComponent { // ... 其他属性 constructor(private http: HttpClient) {} // 假设从后端获取了客户的 PaymentMethod 列表 savedPaymentMethods: any[] = [ { id: 'pm_123abc', brand: 'Visa', last4: '4242' }, { id: 'pm_456def', brand: 'MasterCard', last4: '5555' } ]; currentCustomerId: string = 'cus_Gxxx'; // 假设已获取客户ID useSavedCard(paymentMethodId: string) { const payload = { amount: 1000, // 支付金额 (美分) currency: 'usd', customerId: this.currentCustomerId, paymentMethodId: paymentMethodId }; this.http.post("http://localhost:8080/api/payment/intent/reuse", payload).subscribe( (response: any) => { console.log("Payment with saved card successful:", response); // 根据 PaymentIntent 的状态处理结果 if (response.status === 'succeeded') { console.log("Payment succeeded with saved card!"); // 更新UI,显示支付成功信息 } else if (response.status === 'requires_action') { // 如果需要额外的客户操作(如3D Secure),前端需要处理重定向 // 这通常只在 off_session 为 false 或 PaymentMethod 不支持离线支付时发生 window.location.href = response.next_action.redirect_to_url.url; } }, error => { console.error("Error using saved card:", error); // 显示错误信息 } ); } }
5. 注意事项与最佳实践
- PCI DSS 合规性:再次强调,不要自行存储敏感卡数据。始终使用Stripe提供的安全机制。
- 用户体验:清晰地告知用户他们的支付信息将被保存以供未来使用,并提供管理(添加/删除)已保存支付方式的界面。
- 错误处理:在前端和后端都实现健壮的错误处理机制。Stripe API调用可能会失败,或者支付可能需要额外的用户交互(如3D Secure)。
- 客户管理:为每个客户创建一个Customer对象,并将他们所有的PaymentMethod关联到该Customer。这有助于您管理客户的支付历史和偏好。
- 测试:在沙盒(Test Mode)环境中充分测试您的支付流程,包括首次支付、保存卡片、复用卡片以及各种失败场景(如卡片拒绝、3D Secure挑战)。
- Stripe Webhooks:对于异步事件(如支付成功、退款、争议等),强烈建议使用Stripe Webhooks。Webhooks可以确保您的系统及时收到Stripe的通知,并更新订单状态,而不是仅仅依赖前端的跳转结果。
总结
通过Stripe的PaymentIntent API结合PaymentMethod和Customer对象,您可以安全、高效地实现银行卡信息的保存和复用功能。这不仅提升了用户体验,也确保了您的应用程序符合PCI DSS标准,避免了处理敏感支付数据的风险。始终遵循Stripe的最佳实践,将敏感数据处理交给专业的支付服务商,从而专注于您的核心业务逻辑。
暂无评论内容