This tutorial is out of date and no longer maintained.
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.
First, in your project’s index.html
file, you’ll want to add and initialize Stripe.js
as well as initialize Stripe Elements:
...
<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:
// ...
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:
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 { }
The template markup for a basic checkout flow is as simple as it gets:
<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:
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:
ViewChild
decorator.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.card
element in the AfterViewInit
lifecycle hook, to ensure that our container element is available.ChangeDetectorRef
to manually instruct Angular to run a change detection cycle in the onChange
method.onSubmit
, is an async function that await
s for Stripe’s createToken
promise to resolve.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.
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:
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:
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.
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:
async onSubmit(form: NgForm) {
const { token, error } = await stripe.createToken(this.card, {
email: this.emailAddress
});
// ...
}
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.
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