Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Stripe Subscription Payments With Firebase Cloud Functions and Angular

Episode 51 written by Jeff Delaney
full courses and content on fireship.io

Update! Watch the latest video and get the most up-to-date code by enrolling in the Stripe Payments Master Course on Fireship.io

</div<>

In this lesson, we are going to build a recurring subscription payment system with Stripe Checkout. It will be able to handle the following tasks.

  1. Create a Stripe customer on signup.
  2. Collect credit card details.
  3. Create subscription and charge the customer.
  4. Use a Stripe webhook to update the subscription status after a recurring charge is made.

Stripe subscription payment with Firebase Cloud Functions demo

Initial Setup

You must have a basic Firebase user auth system in place and a Stripe account

I also recommend starting with the first Stripe checkout lesson, but it’s not completely necessary.

Add Stripe Checkout to tndex.html

First, add the Stripe checkout library to src/app/index.html - it will allow us to securely collect credit card payments and transmit them back to Stripe.

<head>
<script src="https://checkout.stripe.com/checkout.js"></script>
</head>

Register with TypeScript

The StripeCheckout class is not known to TypeScript, so we need declare it in typings.d.ts

declare var StripeCheckout:any;

Add API Key to the Angular Environment

Lastly, you should add your Stripe API keys to your environment files. Here I am adding the test key to the development environment in src/environment.ts.

export const environment = {
production: false,
stripeKey: 'YOUR_STRIPE_TEST_KEY',
// Your Firebase Config {}
};

Firebase Data Structure

We are going to keep the subscription information on the /users node in the database, but you may want to denormalize it if users can have multiple subscriptions. I also found it useful to add a /customers node that keeps track of the Stripe customer id and the Firebase auth uid for dealing with webhooks.

users
--$uid
----customerId: string
----pro-membership
------token: string
------status string


customers
--stripeCustomerId: $uid

Subscription Management Component and Service

For simplicity, I am wrapping all subscription functionality into a single component. Optionally, all payment features can be kept in their own feature module.

ng g module payments
ng g component payments/subscription-payment --m payments/payments
ng g service payments/payment-service --m payments/payments

Triggering the Stripe Checkout Modal

In the HTML, we only need a button to trigger the checkout modal.

<button (click)="openHandler()">
Subscribe for $15/m
</button>

We can configure the openHandler() method in the TypeScript. Notice how we are using a callback function processPayment() from to our service. This saves the token to the db and will be defined in the next step.

import { Component, OnInit } from '@angular/core';
import { PaymentService } from '../payment.service';
import { environment } from '../../../environments/environment';

@Component({
selector: 'subscription-payment',
templateUrl: './subscription-payment.component.html',
styleUrls: ['./subscription-payment.component.scss']
})
export class SubscriptionPaymentComponent implements OnInit {

handler: any;

constructor(public pmt: PaymentService) { }

ngOnInit() {
this.configHandler()
}

private configHandler() {
this.handler = StripeCheckout.configure({
key: environment.stripeKey,
image: 'https://goo.gl/EJJYq8',
locale: 'auto',
token: token => {
this.pmt.processPayment(token);
}
});
}

openHandler() {
this.handler.open({
name: 'FireStarter',
excerpt: 'PRO Subscription',
amount: 1500
});
}

}

payment.service.ts

The service will save the initial token in the database (which triggers the cloud function). We also use the serives to create an Observable of the user’s membership. This will allow us to react to changes to the membership status in realtime.

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';

import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/do';


@Injectable()
export class PaymentService {

userId: string;
membership: any;

constructor(private db: AngularFireDatabase, private afAuth: AngularFireAuth) {

this.membership = this.afAuth.authState
.do(user => this.userId = user.uid)
.switchMap(user => {
return this.db.object(`users/${user.uid}/pro-membership`);
});

}

processPayment(token: any) {
return this.db.object(`/users/${this.userId}/pro-membership`)
.update({ token: token.id });
}

Updated HTML

Now that we have an observable of the pro-membership, we can go back to the HTML and display the subscription button conditionally based its current status.

<div *ngIf="pmt.membership | async as pro">

<button *ngIf="pro.status != 'active'" (click)="openHandler()">
Subscribe for $15 per Month
</button>

<div *ngIf="pro.status == 'active'">
<h3>Subscription is Active</h3>
</div>

</div>

Configuring Stripe Cloud Functions

We need 3 cloud functions to make this system work and they must be fired sequentially.

  1. Create stripe customer on signup
  2. Create the subscription after sending payment
  3. Update subscription status based on recurring payment webhooks.

Let’s build them step-by-step. First, initialize cloud functions and save your Stripe API key to the cloud functions environment. Sidenote: this part assumes you have Firebase Tools configured in your Angular project.

firebase init functions
cd functions
npm install stripe --save

firebase functions:config:set stripe.testkey="YOUR_STRIPE_TEST_KEY"

From here, you should have an index.js to define the functions. Add the following lines to the top of it.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
const stripe = require('stripe')(firebaseConfig.stripe.testkey);

1. Create the Stripe Customer on Signup

Before we can create a subscription in Stripe, we need to have a customer. The most convenient way to handle this is to create the customer when the user signs up. In this function, we use an auth trigger to create the customer on stripe, then save the customerId in the Firebase database for later use.

exports.createStripeCustomer = functions.auth.user().onCreate(event => {
// user auth data
const user = event.data;

// register Stripe user
return stripe.customers.create({
email: user.email
})
.then(customer => {
/// update database with stripe customer id

const data = { customerId: customer.id }

const updates = {}
updates[`/customers/${customer.id}`] = user.uid
updates[`/users/${user.uid}/customerId`] = customer.id


return admin.database().ref().update(updates);
});
});

2. Stripe Subscription Cloud Function

After the user enters their card details and the token is updated in the Firebase database, the next step is to create the subscription and charge the credit card for the first payment.

exports.createSubscription = functions.database.ref('/users/{userId}/pro-membership/token').onWrite(event => {

const tokenId = event.after.val();
const userId = event.params.userId;


if (!tokenId) throw new Error('token missing');

return admin.database()
.ref(`/users/${userId}`)
.once('value')
.then(snapshot => snapshot.val())
.then(user => {

return stripe.subscriptions.create({
customer: user.customerId,
source: tokenId,
items: [
{
plan: 'pro-membership',
},
],
});

})
.then(sub => {
admin.database()
.ref(`/users/${userId}/pro-membership`)
.update( {status: 'active'} )


})
.catch(err => console.log(err))

});

3. Handle Recurring Payments with Webhooks

Stripe subscription payment with Firebase Cloud Functions demo

A webhook is simply an HTTP endpoint that Stripe can send information to when something important happens. In this example, we want to know when a recurring subscription payment was made and whether it was successful or declined. Stripe will send this information to a Firebase Cloud Function that will update the database with the results.

exports.recurringPayment = functions.https.onRequest((req, res) => {

const hook = req.body.type
const data = req.body.data.object

if (!data) throw new Error('missing data')

return admin.database()
.ref(`/customers/${data.customer}`)
.once('value')
.then(snapshot => snapshot.val())
.then((userId) => {
const ref = admin.database().ref(`/users/${userId}/pro-membership`)

// Handle successful payment webhook
if (hook === 'invoice.payment_succeeded') {
return ref.update({ status: 'active' });
}

// Handle failed payment webhook
if (hook === 'invoice.payment_failed') {
return ref.update({ status: 'pastDue' });
}


})
.then(() => res.status(200).send(`successfully handled ${hook}`) )
.catch(err => res.status(400).send(`error handling ${hook}`))


});

Now we need to add this webhook to Stripe from the dashboard. This tells Stripe to send data to this URL each time a subscription charge is made on the user’s account.

Add a new webhook to your stripe account

Then we can tell stripe to send a test webhook to Firebase Cloud Functions. Make sure to check the function logs for debugging.

Send a test request to your webhook

The End

There are many other considerations that you should think about to create a robust subscription management system with Stripe. Join our slack team if you want to discuss these topics with experienced developers.

  1. Changing credit card details.
  2. Multiple subscription tiers.
  3. Transactional email for expired or declined cards.
  4. Show the payment history.