将Angular中的Stripe与Stripe元素集成
介绍
Stripe 最近发布了 Stripe Elements,这是一组 UI 元素,可以轻松构建自定义结帐流程,并提供实时验证和自动完成支持。 在这篇文章中,我们将介绍在前端将 Stripe 和 Stripe Elements 与 Angular 集成的基础知识。
我们将创建一个从 Stripe API 获取令牌的简单支付表单。 Stripe 的 Elements 文档用于使用原生 JavaScript 进行集成,但在这里我们将稍微修改此实现以与 Angular 模板驱动的表单集成。
设置项目
首先,在项目的 index.html
文件中,您需要添加和初始化 Stripe.js
以及初始化条纹元素:
索引.html
... <body> <app-root></app-root> <script src="https://js.stripe.com/v3/"></script> <script type="text/javascript"> var stripe = Stripe('pk_test_XXXXXXXXXXXXXXXXX'); // use your test publishable key var elements = stripe.elements(); </script> </body> </html>
警告: 请记住在您的应用投入生产后更改为您的实时可发布密钥。
由于 Stripe.js
被添加到项目范围之外并且没有类型,TypeScript 通常会在尝试访问 stripe
或 elements
时报错。 为了解决这个问题,我们将在项目的 typings.d.ts
文件中添加两个声明:
打字.d.ts
// ... declare var stripe: any; declare var elements: any;
我们将在简单的支付表单中使用 Angular 的模板驱动表单,因此我们还必须在我们的应用程序或功能模块中导入 FormsModule:
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import {FormsModule} from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
构建表单
基本结帐流程的模板标记非常简单:
app.component.html
<form #checkout="ngForm" (ngSubmit)="onSubmit(checkout)" class="checkout"> <div class="form-row"> <label for="card-info">Card Info</label> <div id="card-info" #cardInfo></div> <div id="card-errors" role="alert" *ngIf="error">{{ error }}</div> </div> <button type="submit">Pay $777</button> </form>
#card-info
元素将成为条纹元素的容器,我们还创建了一个容器 div 来显示错误消息(如果有)。
当我们在组件类中连接所有东西时,有趣的部分就开始了。 这是使我们的示例工作的代码,突出显示了一些有趣的部分:
app.components.ts
import { Component, AfterViewInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ ... }) export class AppComponent implements AfterViewInit, OnDestroy { @ViewChild('cardInfo') cardInfo: ElementRef; card: any; cardHandler = this.onChange.bind(this); error: string; constructor(private cd: ChangeDetectorRef) {} ngAfterViewInit() { this.card = elements.create('card'); this.card.mount(this.cardInfo.nativeElement); this.card.addEventListener('change', this.cardHandler); } ngOnDestroy() { this.card.removeEventListener('change', this.cardHandler); this.card.destroy(); } onChange({ error }) { if (error) { this.error = error.message; } else { this.error = null; } this.cd.detectChanges(); } async onSubmit(form: NgForm) { const { token, error } = await stripe.createToken(this.card); if (error) { console.log('Something is wrong:', error); } else { console.log('Success!', token); // ...send the token to the your backend to process the charge } } }
乍一看似乎有很多事情要做,但这一切都非常简单。 这里有几点需要注意:
- 我们可以使用
ViewChild
装饰器访问卡片元素的容器。 - 我们将
onChange
方法绑定到类的this
并将新引用保存为cardHandler
。 此引用用于在创建卡片元素时添加事件侦听器,并在OnDestroy
挂钩中将其删除。 - 我们在
AfterViewInit
生命周期钩子 中初始化card
元素,以确保我们的容器元素可用。 - 我们使用 ChangeDetectorRef 手动指示 Angular 在
onChange
方法中运行更改检测循环。 - 我们的表单提交方法
onSubmit
是一个 异步函数 ,Stripe 的createToken
的await
承诺可以解决。 - 一旦我们从 Stripe 得到一个有效的令牌,该令牌应该被发送到您的后端或云功能来处理费用。
- 在
OnDestroy
中,我们通过移除更改事件侦听器并销毁卡片元素来进行清理。
我们的示例确实是准系统,但在真正的应用程序中,您还需要实现一个简单的布尔标志,以防止用户连续多次提交表单。 例如,从提交表单到您的后端发送成功消息指示收费已处理,提交按钮可以被加载指示器替换。 在这种情况下,您会将用户重定向到确认页面之类的内容。
注意: 要进行测试,请使用卡号 4242 4242 4242 4242 以及未来的任何到期日期、CVC 的任何 3 位数字和任何有效的邮政编码。
自定义样式
我们有一个简单的结帐表格,但它看起来很乏味。 让我们添加一些样式。 首先,让我们设置表单和提交按钮的样式:
app.component.css
form.checkout { max-width: 500px; margin: 2rem auto; text-align: center; border: 2px solid #eee; border-radius: 8px; padding: 1rem 2rem; background: white; font-family: monospace; color: #525252; font-size: 1.1rem; } form.checkout button { padding: 0.5rem 1rem; color: white; background: coral; border: none; border-radius: 4px; margin-top: 1rem; } form.checkout button:active { background: rgb(165, 76, 43); }
Stripe 卡片元素本身可以使用 .StripeElement
、.StripeElement--focus
和 .StripeElement--invalid
等选择器设置样式。 有几个现成的主题示例可用,但这里我们只添加Stripe提供的默认样式:
... .StripeElement { margin: 1rem 0 1rem; background-color: white; padding: 8px 12px; border-radius: 4px; border: 1px solid transparent; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; }
一些基本样式也可以通过选项对象作为 create 方法的第二个参数传入:
app.component.ts(部分)
ngAfterViewInit() { const style = { base: { lineHeight: '24px', fontFamily: 'monospace', fontSmoothing: 'antialiased', fontSize: '19px', '::placeholder': { color: 'purple' } } }; this.card = elements.create('card', { style }); this.card.mount(this.cardInfo.nativeElement); this.card.addEventListener('change', this.cardHandler); }
您可以参考 选项 API 参考 以获取所有可能的样式配置选项的列表。
保存附加字段
我们的结帐表格一切都很好,但是如果我们还想在 Stripe 中为客户保存一些额外的数据怎么办? 这就像使用任何额外字段将第二个参数传递给 createToken
一样简单。
例如,我们还发送客户的电子邮件地址:
app.component.ts
async onSubmit(form: NgForm) { const { token, error } = await stripe.createToken(this.card, { email: this.emailAddress }); // ... }
结论
在这篇文章中,您使用 Stripe API 和 Stripe Elements 创建了一个带有 Angular 模板驱动表单的表单。