Tutorial

Integrating Stripe in Angular With Stripe Elements

Published on November 8, 2017
Default avatar

By Alligator.io

Integrating Stripe in Angular With Stripe Elements

This tutorial is out of date and no longer maintained.

Introduction

Stripe recently released Stripe Elements, a set of UI elements that make it easy to build a custom checkout flow, complete with real-time validation and autocomplete support. In this post, we’ll go over the basics of integrating Stripe and Stripe Elements on the frontend with Angular.

We’ll create a simple payment form that gets a token from the Stripe API. Stripe’s documentation for Elements is for integrating using vanilla JavaScript, but here we’ll modify this implementation slightly to integrate with an Angular template-driven form.

Setting Up the Project

First, in your project’s index.html file, you’ll want to add and initialize Stripe.js as well as initialize Stripe Elements:

index.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>

Warning: Remember to change to your live publishable key once your app goes to production.

Since Stripe.js is added outside the scope of the project and doesn’t have typings, TypeScript would normally complain when trying to access stripe or elements. To fix this, we’ll add two declarations to the project’s typings.d.ts file:

typings.d.ts
// ...

declare var stripe: any;
declare var elements: any;

We’ll be using Angular’s template-driven forms for our simple payment form, so we also have to import the FormsModule in our app or feature module:

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 { }

Building the Form

The template markup for a basic checkout flow is as simple as it gets:

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>

The #card-info element will be the container for the Stripe Elements, and we also created a container div to display error messages, if any.

The fun part starts when we hook everything up in the component class. Here’s the code to make our example work, with some interesting parts highlighted:

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
    }
  }
}

There may seem to be a lot doing on at first glance, but it’s all really straightforward. Here are a few things to note:

  • We get access to the container for the card element using the ViewChild decorator.
  • We bind our onChange method to the this of the class and save the new reference as cardHandler. This reference is used to add an event listener when the card element is created and remove it in the OnDestroy hook.
  • We initialize the card element in the AfterViewInit lifecycle hook, to ensure that our container element is available.
  • We make use of ChangeDetectorRef to manually instruct Angular to run a change detection cycle in the onChange method.
  • Our form submission method, onSubmit, is an async function that awaits for Stripe’s createToken promise to resolve.
  • Once we get a valid token back from Stripe, the token should be sent to your backend or to a cloud function to process the charge.
  • In the OnDestroy we clean up by removing the change event listener and destroying the card element.

Our example is really barebones, but in a real app, you’ll want to also implement a simple boolean flag that prevents the user from submitting the form multiple times in a row. For example, the submit button could be replaced by a loading indicator from the time the form is submitted up until your backend sends a success message indicating that the charge was processed. In that case, you’d redirect the user to something like a confirmation page.

Note: To test things out, use card number 4242 4242 4242 4242 with any expiration date in the future, any 3-digit number for the CVC, and any valid zip code.

Customizing Styles

We have a simple checkout form working, but it looks pretty dull. Let’s add a touch of styles. First, let’s style our form and submit button:

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);
}

The Stripe card element itself can be styled using selectors like .StripeElement, .StripeElement--focus and .StripeElement--invalid. There are a few ready-made theme examples available, but here we’ll just add the default style provided by 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;
}

Some basic styles can also be passed-in with an option object as a second argument to the create method:

app.component.ts (partial)
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);
}

You can refer to the options API reference for a list of all the possible style configuration options.

Example of Stripe Elements in Angular

Saving Additional Fields

Our checkout form is all well and good, but what if we also want to save some additional data for the customer in Stripe? It’s as simple as passing a second argument to createToken with any extra field.

Here for example we also send the email address for the customer:

app.component.ts
async onSubmit(form: NgForm) {
  const { token, error } = await stripe.createToken(this.card, {
    email: this.emailAddress
  });

  // ...
}

Conclusion

In this post, you created a form using the Stripe API and Stripe Elements with an Angular template-driven form.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alligator.io

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

The Pay Button is missing from the document also many unwanted scripts are there in the app.component.html section

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel