将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 通常会在尝试访问 stripeelements 时报错。 为了解决这个问题,我们将在项目的 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 的 createTokenawait 承诺可以解决。
  • 一旦我们从 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 模板驱动表单的表单。