将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 模板驱动表单的表单。