You have unlimited access as a PRO member
You are receiving a free preview of 3 lessons
Your free preview as expired - please upgrade to PRO
Contents
Recent Posts
- Object Oriented Programming With TypeScript
- Angular Elements Advanced Techniques
- TypeScript - the Basics
- The Real State of JavaScript 2018
- Cloud Scheduler for Firebase Functions
- Testing Firestore Security Rules With the Emulator
- How to Use Git and Github
- Infinite Virtual Scroll With the Angular CDK
- Build a Group Chat With Firestore
- Async Await Pro Tips
Angular Stripe Payments Part 3 - Sell Digital Content
Episode 26 written by Jeff DelaneyHealth Check: This lesson was last reviewed on and tested with these packages:
- Angular v4.2
- AngularFire2 v4
Update Notes: Serious about Stripe Payments in your Angular Firebase app? Check out the Full Stack Stripe Payments course.
Find an issue? Let's fix it
Update! Watch the latest video and get the most up-to-date code by enrolling in the Stripe Payments Master Course on Fireship.io
This is part 3 of our Stripe Payments with Angular series. If you’re just getting started, check out:
Now that we have the ability to collect payments from customers, we need a way to apply those payments in the app. There are several ways you might approach this problem.
- Account Deposit (our method)
- Shopping cart
- Single product purchase
- Subscription
Each of these methods have their own benefits and drawbacks. In my opinion, the account deposit method is the most flexible and is relatively simple to implement.
Account Deposit Method
The account deposit payment strategy enables users to deposit funds on there account, which can be used to access restricted content. Sometimes developers might rename the underlying currency to coins, tokens, or credits to distinguish funds that can only be spent within a specific marketplace. This payment model works especially well for apps selling digital content or pay-per-use features.
For example, IconFinder requires users to deposit funds in specific increments, which can then be used to unlock digital content. Burner App allows users to purchase credits for private phone numbers, which get debited based on usage.
Database Structure
There are only two types of transactions that can affect a user’s balance in our app. (1) An successful charge via Stripe. (2) Digital content purchase. Here’s how we will structure the database.
-|users |
Enforcing an Atomic Operation in Firebase
When dealing with people’s money, you need to be especially careful to avoid data anomalies in NoSQL. Although unlikely, it is possible that one operation succeeds, while the other fails, causing a data mismatch or anomaly. In other words, the purchase would be recorded, but the balance would remain the same, or vice versa.
Thankfully, Firebase supports a multi location update technique that will force the operation to fail/succeed in unison, which is known as an atomic operation in database theory. In the following sections, we will use atomic updates to prevent anomalies in our payment and purchase data.
Update the Payment Cloud Function
Our cloud function needs to be modified to update the user’s balance after the charge is recorded. Let’s first add an extra variable to keep track of the user’s existing balance
. When the charge is received from Stripe, we credit the user balance by the charge amount.
To perform an atomic update, we save the operations in an object where the database reference path is the key and the data is the value. Then you can reference the root of the database and pass this updates
object to the update
method.
functions/index.js
exports.stripeCharge = functions.database |
Update the Payment Service
getUserBalace()
- The service constructor is updated pull the current user’s balance and return it as an Observable, by using switchMap
, instead of subscribe
.
hasPurchased()
- Returns a boolean observable telling us if an item was already purchased.
buyDigitalContent()
- To complete a purchase, we need another atomic operation to simultaneously update the user’s balance and purchase history.
Note how we are using the Firebase server timestamp. This prevents data integrity issues with JavaScript date objects caused by timezones and local clock settings.
payment.service.ts
import { Injectable } from '@angular/core'; |
Buy Now Component
ng g c payments/buy-now --module payments/payment |
The buy-now component is designed for re-usability, so you can attach it to any “buyable” content by passing it a unique id and price. Most commonly, you would pass it a Firebase push $key
and the price from a parent component.
<buy-now |
To prevent accidental purchases, it uses two-steps to confirm the user’s intention. When the purchase component is clicked it brings up a confirmation window, showing the user the change to their current balance in a modal window. They can then click “Confirm” or “Cancel”.
buy-now.component.ts
import { Component, OnInit, Input } from '@angular/core'; |
Create a Stripe Pipe
I created pipe to present the balance in a user-friendly format because Stripe uses integers representing 1/100th of their underlying currency amount. (500 == $5.00)
ng g pipe payments/stripe --module payments/payment |
stripe.pipe.ts
import { Pipe, PipeTransform } from '@angular/core'; |
HTML Template
In the template, we are using Bulma’s modal CSS, but this process works equally well with Bootstrap, Material, or Ionic.
In the template, we have a payment button that start the purchase process by firing the toggleModel()
function. Bulma has an is-active
CSS class that toggles the modal’s visibility. The modal window displays the change to the user’s balance and the confirmation button. When clicked it will perform the atomic update defined in the service, giving the user access to the digital content.
buy-now.component.html
<div class="modal" [class.is-active]="showModal"> |
Extra Backend Security
Don’t forget to add backend security rules. At the very least, you should have purchases locked down by auth UID. You might also want to keep records as “read only” after they are created to prevent accidental or malicious deletion/altering of purchase data.
"purchases": { |
Next Steps
In upcoming installments I will talk about building subscription models with stripe and processing refunds.