Angular Stripe Payments: Unlock Digital Content with Firebase & User Balances (Part 3)



Introduction

In this blog post, we'll delve into how to sell digital content within your Angular application using Stripe payments and Firebase. Building on previous integrations of Stripe Checkout and Firebase Cloud Functions, we'll focus on implementing user balances and ensuring atomic updates for a seamless and reliable purchasing experience.


Implementing User Balances

The core of this system involves managing a user balance. Users can increase their balance by making payments through Stripe and decrease it by purchasing digital content. A modal window is presented to the user to confirm or cancel any purchase if sufficient funds are available. This balance is tracked as a property within the user's account in Firebase, represented as a number.


Atomic Updates with Cloud Functions

To ensure data integrity, especially when deducting funds for purchases, we'll update the Cloud Function. The goal is to execute the charge and the user balance update as an atomic operation. This means that if the charge fails, the user balance update will also fail, and vice-versa. This is critical for preventing inconsistencies in your data.

First, a balance variable is created, defaulting to zero. When the customer record is retrieved, it's updated with the user's existing balance. When a Stripe charge is confirmed to exist and is paid, an object is created to define the database updates. This object maps database references (keys) to the data we want to update (values). This allows atomic updates to multiple database locations. The charge amount from Stripe is used to increase the user's balance.

Example: Object structure for Atomic update. (Though no code in the transcript)


const updates = {
  '/users/userId/balance': newBalance,
  '/purchases/userId/itemId': purchaseObject
};
    

Handling Purchases in the Angular Service

The Angular service is responsible for managing the payment process within the app. To access the user's balance, we first need the authenticated user's ID. The `switchMap` operator is used on the AngularFire auth observable to accomplish this. The balance is retrieved from Firebase, emitted as an object, and then mapped to its numerical value.

To check if a user has already purchased a product, we check for the existence of the relevant key under their "purchases" in the database. This also returns an observable, which is then mapped to a boolean (true or false). A function is created to enable the user to make the purchase. This function requires the ID of the item being purchased, the amount, and a timestamp from the Firebase server. Using the Firebase server timestamp guarantees consistency across the application.

Another atomic update is needed for updating the balance and purchase history simultaneously. An "updates" object is created with corresponding data. The user's balance is decreased, and the purchase history is updated with the purchase object. These updates are then passed to the root of the database using the `update` function.


Buy Now Component and UI Enhancements

A reusable "buy now" component is created, accepting the ID of the purchasable item and its price from the parent component. A variable controls the visibility of the modal window. During the component's initialization (`ngOnInit`), observables are defined for the user balance and purchase status. An event handler toggles the modal's visibility. Finally, a function triggers the "buy digital content" function defined in the service. On successful purchase, the modal is closed, and you might consider adding a success message.

A custom Stripe pipe is also created to format currency amounts for display. Stripe amounts are stored as 1/100th of the underlying currency (e.g., 500 equals $5.00 USD).

The parent component passes the unique ID and price to the "buy now" component. Inside the component, the `isActive` class is added when the modal is visible (using Bulma CSS). The balance observable is unwrapped using the `async` pipe to check for sufficient funds. If funds are sufficient, "confirm" and "cancel" buttons are shown, along with the current and future balance. If funds are insufficient, a message indicating this is displayed. A button opens the modal in the first place. If the item has already been purchased, a disabled "already purchased" button is shown.

Example: Usage of Async Pipe:


<div *ngIf="(balance$ | async) > itemPrice">
  <p>Your balance: {{ balance$ | async | stripeCurrency }}</p>
</div>
    

Backend Security Rules

Backend data security rules are added to Firebase. When a purchase is made, the user ID must match the authenticated user's ID. Updates or deletions of purchases are disallowed after creation. This ensures that users cannot modify their purchase history.


Conclusion

This blog post has detailed how to implement user balances and atomic updates for selling digital content within an Angular application using Stripe and Firebase. By managing user balances, implementing atomic operations with Cloud Functions, and crafting a well-structured Angular service and UI component, you can create a robust and secure e-commerce experience for your users. Remember to prioritize backend security rules to protect your data and ensure a trustworthy system.

Keywords: Angular, Stripe, Firebase, Digital Content, Atomic Updates

Post a Comment

0 Comments