如何通过Stripe PaymentIntent API复用自有存储的银行卡数据?
问题描述
我正在使用Angular、Stripe.js 3、Stripe Elements、Spring Boot以及Stripe PaymentIntent API开发支付功能。目前已通过Stripe Elements实现支持Strong Customer Authentication(SCA)的支付流程,可成功跳转至支付验证页面。但当前存在问题:每次支付都需要用户手动输入银行卡信息,而我的应用已独立于Stripe存储了用户的银行卡数据,希望实现卡片保存功能,无需用户重复输入。请问如何将自有表单中的银行卡数据传递给Stripe.js,以实现该需求?
相关代码
HTML代码
<!-- <form novalidate> <input type="text" name="cardNumber" id="" placeholder="Card Number"> <input type="text" name="expiry-month" id="" placeholder="Expiry Month"> <input type="text" name="expiry-year" id="" placeholder="Expiry Year"> <input type="text" name="cardNumber" id="" placeholder="CVV"> </form> --> <form id="payment-form"> <div id="link-authentication-element"> <!--Stripe.js injects the Link Authentication Element--> </div> <div id="payment-element" class="stripe-form-input"> <!--Stripe.js injects the Payment Element--> </div> <!-- <button id="submit"> <div class="spinner hidden" id="spinner"></div> <span id="button-text">Pay now</span> </button> --> <div id="payment-message" class="hidden"></div> </form> </div> <button type="submit" class="fill" style="width: 100%;" (click)="testCreateCard()">Pay</button> <div class="iframe-wrapper" #if> <iframe *ngIf="showIframe" frameborder="0" [src]="ifHtml | safe" height="600" width="400" #if></iframe></div>
TypeScript代码
export class AppComponent { title = 'stripePaymentIntents'; clientSecret:string = "" elements:any; email?:string; paymentElement:any; showIframe:boolean = false; ifHtml:string = "" paymentIntent:string = "" @ViewChild('if') iframeWrapper!: ElementRef; constructor(private http: HttpClient, private sanitizer: DomSanitizer, private route: ActivatedRoute){} ngOnInit(): void { this.route.queryParams.subscribe(qp => { this.paymentIntent = qp['payment_intent'] console.log("Payment Intent From SCA: " + this.paymentIntent) }) const appearance = { theme: 'stripe', }; this.http.get("http://localhost:8080/api/payment/intent/clientSecret", {responseType:'text'}).subscribe(secret => { this.elements = stripe.elements({ appearance, clientSecret: secret }); this.clientSecret = secret; const linkAuthenticationElement = this.elements.create("linkAuthentication"); linkAuthenticationElement.mount("#link-authentication-element"); linkAuthenticationElement.on('change', (event:any) => { this.email = event.value.email; }); const paymentElementOptions = { layout: "tabs", }; const cardElementStyle = { classes: 'stripe-' } this.paymentElement = this.elements.create("card", paymentElementOptions); this.paymentElement.mount("#payment-element"); }) } testCreateCard(){ console.log(this.clientSecret); console.log(this.elements); const here = this; stripe.confirmCardPayment(this.clientSecret, { payment_method: { card: this.paymentElement, billing_details: { name: 'Barack Obama', }, }, return_url: 'http://localhost:4200/' }, {handleActions: false}) .then(function(result:any) { if(result.paymentIntent.next_action.redirect_to_url.url !== null){ window.location = result.paymentIntent.next_action.redirect_to_url.url } if (result.error) { console.log(result); } else { here.showIframe = true console.log('iframe show; ' + here.showIframe); console.log("SUCCESS: ") console.log(result); } }); } }
解决方案
首先必须明确:Stripe不允许直接传递明文银行卡数据到Stripe.js——这会违反PCI合规要求,直接处理卡信息会让你的应用承担最高级别的PCI合规责任,风险极高。正确的做法是利用Stripe的Tokenization或Setup API安全处理已存储的卡信息,或者使用Stripe的Customer对象存储卡信息(推荐方案)。
方案1:使用Stripe Customer存储卡信息(推荐)
这是最安全且合规的方式,步骤如下:
- 后端创建Stripe Customer:用户首次添加卡片时,通过Stripe Elements收集卡信息,后端调用Stripe API创建
PaymentMethod,再关联到Stripe Customer对象。 - 存储Stripe Customer ID:将Stripe返回的
customer_id与你的系统用户关联存储,而非存储明文卡信息。 - 后续支付时复用PaymentMethod:
- 后端创建PaymentIntent时,指定
customer参数为存储的Stripe Customer ID,同时可指定payment_method为该用户已保存的PaymentMethod ID。 - 前端无需再收集卡信息,Stripe Elements的Payment Element会自动加载用户已保存的卡片供选择。
- 后端创建PaymentIntent时,指定
代码调整示例
后端(Spring Boot)创建关联PaymentMethod的Customer
// 伪代码,需引入Stripe Java SDK CustomerCreateParams customerParams = CustomerCreateParams.builder() .setEmail(userEmail) .build(); Customer customer = Customer.create(customerParams); // 将PaymentMethod关联到Customer PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder() .setCustomer(customer.getId()) .build(); PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodId); paymentMethod.attach(attachParams); // 将customer.getId()存储到你的用户表
前端(Angular)加载已保存卡片
初始化Elements时传入customer参数:
this.elements = stripe.elements({ appearance, clientSecret: secret, customer: 你的系统存储的stripeCustomerId // 添加该行 }); // 创建Payment Element时会自动加载用户已保存的卡片 this.paymentElement = this.elements.create("payment", paymentElementOptions);
方案2:若必须使用自有存储的卡信息(不推荐,需严格PCI合规)
如果因特殊原因必须自行存储卡信息,需使用Stripe的Token API将明文卡信息转换为Stripe Token,再用于支付:
- 前端收集卡信息:使用自有表单(需满足PCI SAQ D合规要求,包括HTTPS、数据加密等)。
- 调用Stripe.createToken创建卡Token:
// 从自有表单获取卡信息 const cardData = { number: 表单获取的卡号, exp_month: 表单获取的到期月, exp_year: 表单获取的到期年, cvc: 表单获取的CVV }; stripe.createToken('card', cardData).then((result) => { if (result.error) { // 处理错误 } else { // 将token.id传给后端,用于创建PaymentMethod或直接支付 this.http.post("/api/payment/create-payment-intent", { token: result.token.id, amount: 1000 }).subscribe(secret => { // 后续调用confirmCardPayment }); } });
- 后端使用Token创建PaymentIntent:接收前端传来的Token,创建PaymentIntent时指定
payment_method_data[type] = card和payment_method_data[card][token] = 前端传入的token。
注意:这种方式要求你的应用满足PCI SAQ D合规,成本和风险极高,强烈建议使用方案1。
关键注意事项
- 永远不要在前端或后端存储明文银行卡信息,包括卡号、CVV、有效期。
- Stripe Elements是Stripe提供的合规收集卡信息的方式,尽量使用它规避PCI合规风险。
- 复用已保存的PaymentMethod时,Stripe会自动处理SCA验证流程,无需额外开发。
内容的提问来源于stack exchange,提问作者Adam Howard
相关产品推荐
相关产品推荐

