Go serverless: Manage Payments in your Apps with Cloud Functions for Firebase

Jen Person
Firebase Developers
7 min readNov 5, 2018

Note: If you prefer this content in video form, you can see the talk written by Susan Goldblatt and me here.

Lots of applications require the ability to manage payments. For example, e-commerce apps need to be able to receive payments in exchange for their goods. On-demand services like deliveries and ridesharing need a way to take payments from customers and deliver some payments to employees. This is a common problem with lots of solutions. I’m going to show you one solution today, but first, let me pitch you my brilliant idea…

Goat Landscaping

What a great idea!

For my example, I’ve decided to create a hypothetical company I call Goat Landscaping. For an hourly fee, you can rent my herd of goats to come expertly manicure your lawn. As a bonus, goats love to eat poison ivy, and they’d be happy to rid your yard of this common nuisance!

I have an airtight plan for success!

I already have this great app where you can rent our goats to come mow your lawn. I’ve handled most of the toughest parts of the app, like auth and managing scheduling. There’s just one last hurdle left and that’s what I’m going to showcase today: managing payments. Managing payments for your app can be intimidating. How can you design a process that is secure and easy to use? One way that you can handle payments in a way that is safe and seamless for you and your customers is using the Stripe API with Cloud Functions for Firebase.

If you’ve never used Cloud Functions before, I recommend that you check out the getting started guide and Codelab before diving into the example I’m going to show. The guide will help you get your environment set up to use Cloud Functions.

Why Cloud Functions with Stripe?

There are lots of different methods for handling payments. A quick Google search brought up Stripe, Payoneer, Square, Paypal, Skrill, WePay, Google Payments, and so much more. So why am I choosing Stripe for this example? Honestly, the choice is rather random and primarily based on the fact that we had an existing sample using Stripe and the Realtime Database that I tweaked to use Cloud Firestore. Developers seem to like Stripe and it worked well for me here, but this is not an endorsement of Stripe over any other payment platform. The methods I use here could be modified to work on other payment platforms.

Stripe has a client-side API that enables you to do just about anything you’d need to do in terms of payments. It even includes a default UI you can incorporate into your iOS or Android app. So then, why am I choosing to use Cloud Functions? There are a couple reasons for this:

1. The ability to share pertinent information in the client user interface

In order to display information to the user, like past payments, I save data to Cloud Firestore. Using Cloud Functions enables me to update documents with additional Stripe information while applying security rules so users cannot modify this data from the client.

2. The ability to add multiple payment methods to a single Stripe customer

Stripe offers two ways to create payment methods: Tokens and Sources. Tokens are single-use. Sources can be used more than once when attached to a customer. The easiest way to attach Sources to a customer is using the Stripe server API, so I do this from within a Cloud Function.

With that explanation out of the way, let’s get back to the code!

Create a Stripe Customer

Stripe uses customer IDs to uniquely identify each customer. In Goat Landscaping, each Firebase user is associated with a Stripe customer ID. I achieve this using a Cloud Function which is triggered when a new Firebase user signs in for the first time.

When a user first logs in to Goat Landscaping, a Firebase Auth Cloud Function is triggered. The function communicates with the Stripe server API to create a new customer. The customer ID returned from Stripe is then written to Cloud Firestore in a document whose document ID is the user’s Firebase uid. The structure of the database then looks something like this:

stripe_customers/{firebase-uid}: {
customer_id: “cus_DSLH6qtxN3lMdL”
}

The function below is also available to view in the full Cloud Functions with Stripe sample.

exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection(‘stripe_customers’)
.doc(user.uid).set({customer_id: customer.id});
});

Create a Stripe Payment Method

Credit cards in Stripe can be associated with tokens for one-time use. These tokens allow you to identify the payment method without compromising the user’s credit card information.

It is possible to attach payment methods to Stripe customers for future use. One way to do this is when creating a customer. This limits you to attaching one payment method to the customer. In my case, I’ve already created a Stripe customer, and I want to have the option to attach multiple payment methods to that customer.

I use the Stripe client SDKs to create a token from a user’s payment method. You can find the iOS guide and the Android guide in Stripe’s documentation. I then write that token to Cloud Firestore in a subcollection of the user’s document called tokens.

stripe_customers/{firebase-uid}: {
customer_id: “cus_DSLH6qtxN3lMdL”
[tokens/{pushId}: {
}]
}

This triggers a Cloud Function. Then within that function, we create a Source, which can be linked to a Stripe customer through the stripe.customers.createSource method.

exports.addPaymentSource = functions.firestore
.document(‘/stripe_customers/{userId}/tokens/{pushId}’)
.onWrite(async (change, context) => {
const source = change.after.data();
const token = source.token;
if (source === null) {
return null;
}
try {
const snapshot = await
admin.firestore()
.collection(‘stripe_customers’)
.doc(context.params.userId)
.get();
const customer = snapshot.data().customer_id;
const response = await stripe.customers
.createSource(customer, {source: token});
return admin.firestore()
.collection(‘stripe_customers’)
.doc(context.params.userId)
.collection(“sources”)
.doc(response.fingerprint)
.set(response, {merge: true});
} catch (error) {
await change.after.ref
.set({‘error’:userFacingMessage(error)},{merge:true});
}
});

The code above can also be found in the Cloud Functions GitHub repo.

Make a Payment

Stripe does give you the option to make a payment directly from the client. However, I’d like to store some information about the status of the payment in Cloud Firestore. This makes it easier for the customer and me to know the status of payments. When the client makes a payment, the amount is written to Cloud Firestore. This triggers a Cloud Function, which runs whenever data is created at the given payment location. The data from the write is used to process the payment using the Stripe SDK. The result of the payment is then written back to the database so the client can be informed of the result.

Idempotency

Before I dive into the code for creating the Stripe charge, there’s an important thing to note: I don’t want users to accidentally get charged more than once. In order to guarantee a function fires at least once, there are conditions under which a Cloud Function may be fired more than once. If you enable retries on your Cloud Functions, this is important to note. It’s therefore necessary to write functions in such a way that the effect will not change based on the number of times the function is triggered. For example, you need a check to ensure a notification is only sent once. Or in the case of payments, you need a way to identify a unique payment request so it isn’t paid more than once. The concept of running a function or program multiple times without changing the result beyond the initial call is called idempotency. I achieve idempotency in my function using the unique push ID created by Cloud Firestore when I write the charge request to the database. The Stripe API lets you pass an idempotency key upon making a payment request. If this function is triggered more than once, it will have the same idempotency key with each invocation. When this key is sent to the Stripe API, it will know that this payment has already been processed and not run it again.

The code for the function below can also be found in the Cloud Functions samples GitHub repo.

exports.createStripeCharge = functions.firestore
.document(‘stripe_customers/{userId}/charges/{id}’)
.onCreate(async (snap, context) => {
const val = snap.data();
try {
// Look up the Stripe customer id written in createStripeCustomer
const snapshot = await admin.firestore()
.collection(`stripe_customers`)
.doc(context.params.userId).get();

const snapval = snapshot.data();
const customer = snapval.customer_id;
// Create a charge using the pushId as the idempotency key
// protecting against double charges
const amount = val.amount;
const idempotencyKey = context.params.id;
const charge = {amount, currency, customer};
if (val.source !== null) {
charge.source = val.source;
}
const response = await stripe.charges
.create(charge, {idempotency_key: idempotencyKey});
// If the result is successful, write it back to the database
return snap.ref.set(response, { merge: true });
} catch(error) {
await snap.ref.set({error: userFacingMessage(error)}, { merge: true });
}
});

Conclusion

You now have the basics that you need to confidently start managing customer payments in a secure way using Cloud Functions for Firebase. Stay tuned for part 2 of this blog on managing payments, where I’ll show you a method for handling customer refunds.

--

--

Jen Person
Firebase Developers

Developer Relations Engineer for Google Cloud. Pun connoisseur.