How to set up subscriptions for an online course in NuxtJS

How to set up subscriptions for an online course in NuxtJS

·

11 min read

As developers seek more straightforward and cost-effective ways to build apps, serverless functions have become a popular solution. They allow developers to perform tasks without worrying about managing servers, which saves time and money. Several backend-as-a-service (BaaS) providers have begun to offer serverless support.

Appwrite, an open-source BaaS provider, supports serverless functions and takes it to the next level by providing a wide range of pre-built functions ready for integration. In this article, we will explore one of the many functions developed by Appwrite: subscriptions-with-stripe.

We'll demonstrate by building a simple online Courseware platform where users can subscribe to access a course using Stripe as our payment provider. To achieve this, we'll use Nuxt.js for the app's frontend.

Prerequisites

To follow along with this article, you’ll need the following:

  1. A basic understanding of JavaScript and Nuxt.js
  2. Node.js installed on your machine and a basic understanding of Node.js
  3. An Appwrite Cloud account (create one here)
  4. A Stripe account (create one here)

Repository

All the code used in this article can be found in this repository

Function setup

First, let’s log into our Appwrite cloud account and create a new project by clicking Create project and naming it Courseware.

Appwrite cloud projects page

Next, on the sidebar, click on Functions and then Create function.

Appwrite cloud create function page

We'll be managing our function in a GitHub repository. Click on the GitHub button, choose a GitHub account, and then give it access to all repositories by selecting All repositories. Then, click Install & Authorize.

After that, we'll be redirected to Appwrite and see a dropdown list of our GitHub repositories. Click on All templates at the bottom left and search "Subscriptions with Stripe".

Appwrite cloud functions template page

Click on Create function to set up our function and also create a GitHub repository to manage it. Here are the specifics:

  • Configuration: Name the function Subscriptions with Stripe and choose any Node.js runtimes from the dropdown list. Click on Next.
  • Variables:
    • APPWRITE_API_KEY: Select Generate API key on completion
    • STRIPE_SECRET_KEY: To get the stripe secret key, in our stripe dashboard, we click on Developers on the navigation bar and then Click on the API keys tab. The secret key is located under the Standard keys section.
    • STRIPE_WEBHOOK_SECRET: Put a placeholder here, as we can only get the webhook secret once we add a URL to our stripe account. Click Next.
  • Connect: Select the option to create a new repository and click Next.
  • Repository: Give the repository a name, make it private or public, and click Next. Since we connected our GitHub account earlier, this new repository will be created in the GitHub account to which we connected the project.
  • Branch: Select the main branch, type / as the root directory of the function, and then click Create.

After this, our function is successfully created and deployed. It will also be in a repository on our GitHub profile.

Appwrite cloud function deployment page

Next, copy the URL in Domains; that’s what we’ll use to set up the webhook URL on stripe.

Setting up webhook URL

In the Subscriptions with Stripe function template, an endpoint, /webhook, validates the incoming webhook event from Stripe. If the event is successfully validated, then it processes two subscription-related events (customer.subscription.created and customer.subscription.deleted) and updates the user's subscription status in Appwrite.

To set up our webhook, click on the webhooks tab in the developers section of our Stripe dashboard and then click on the Add an endpoint button.

Stripe developers dashboard

  1. Endpoint URL: Paste the URL copied from Domains in our Appwrite function deployment dashboard. Prefix it with https:// and suffix it with /webhook.
  2. Listen to: Select Events on your account.
  3. Version: Leave as is.
  4. Select events to listen to: Search for and select customer.subscription.created and customer.subscription.deleted. Those are the events our serverless function would listen for.
  5. Then click on the Add endpoint button. Stripe add webhook page

After following these steps, our webhooks dashboard will look like this:

Stripe webhook page

Click on Reveal under Signing secret to reveal the webhook secret and copy it.

Updating function environment variables

Navigate to the settings tab of our Payments with Stripe function dashboard. Under the Environment variables section, edit the STRIPE_WEBHOOK_SECRET with the secret key copied from our stripe webhooks dashboard.

Next, click Redeploy in the function dashboard to apply the new changes.

Database setup

Since we are only letting subscribed users to access a course, we need to create a database to store the course details and then add permissions to it to only allow users with the label, subscriber, access it.

To setup our database, follow the steps listed below:

  1. First, click on Databases on the sidebar in the Appwrite console.
  2. Next, Click on Create database and give it a name of Courseware and then click Create.

Create database collection

We need a database collection to hold course data. To create a database collection, in the newly created database, click on Create collection, give it the name Courses, and click on Create. By doing this, we would see our course collections made in our database.

Add Course data to collection

To add the course data to the Courses collection, we must first create Attributes. Click on the Courses collection's Attributes tab and then click Create attribute. Create these attributes:

  1. A String attribute with Attribute Key named name and Size of 30 and click on Create.
  2. A String attribute with Attribute Key named description and Size of 500 and click on Create.
  3. A String attribute with Attribute Key named tutor and Size of 25 and click on Create.

The attributes should look like this.

Appwrite collection attributes page

Now, we want to add our data to the collection. Click on the Documents tab, click Create document, insert this data for each key, then click Next and Create.


    {
      name: "Introduction to JavaScript",
      description: "Embark on a comprehensive journey to grasp the fundamental concepts of the JavaScript programming language. This engaging learning experience is tailored for beginners, offering a step-by-step exploration of key JavaScript principles, syntax, and functionalities. Discover the power of JavaScript as a versatile scripting language widely employed for both front-end and back-end web development.",
      tutor: "John Doe",
    }

Apwrite collection page

Add permissions to collection data

For us to allow only subscriber users to access our course page, we need to add permissions to the course collection. To do this:

  1. Click on Settings in the Courses collection and scroll until we get to the two sections, Permissions and Document Security.
  2. In the Permissions section, click on Add a role to get started, select Label, and give it a subscriber name.
  3. Next, select only the READ checkbox for this role and click Update. This will give the user with the Label subscriber READ permission.
  4. Next, in the Document Security section below the Permissions section, click on Document Security and click Update. Appwrite database permissions page

Frontend setup

We're using Nuxt.js for our frontend to implement the function. To install Node, go to the Node.js website and follow the instructions to install the software compatible with our operating system.

Then, verify the installation using:

node -v

To create the Nuxt.js app, run the command below; it will automatically set up a boilerplate Nuxt.js app:

npx nuxi@latest init

Next, change the directory to the directory of the app we just created:

cd

Now run npm run dev or yarn dev to start the development server on http://localhost:3000.

Building the functionality

Open the app.vue file and replace the boilerplate code with the code below:


    <template>
      <div>
        <div v-if="authUser === false" class="section">
          <p>
            Only registered users can create subscriptions.
          </p>
          <button @click="register()">
            <span>Create account</span>
          </button>
        </div>

        <div v-if="authUser">
          <button @click="signOut()" class="sign-out">
            <span>Sign out</span>
          </button>

          <div>
            <CourseList v-if="courses === null" :buyCourse="buyCourse" class="section" />
            <CoursePage v-else :course="courses[0]" class="section" />
          </div>

        </div>
      </div>
    </template>

    <style>
    body {
      background-color: aliceblue;
      font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
    }
    .section {
      margin: 0 auto;
      width: 400px;
      border: 2px solid rebeccapurple;
      padding: 10px;
      border-radius: 5px;
      margin-top: 100px;
    }
    button {
      background-color: black;
      border: 1px solid white;
      color: white;
      border-radius: 5px;
      width: 150px;
      height: 35px;
      font-size: medium;
    }
    button:hover {
      cursor: pointer;
      background-color: rgb(51, 51, 51);
    }
    .sign-out {
      background-color: white;
      color: red;
      border: 1px solid red;
    }
    </style>

The code above defines a component managing user authentication and course display, conditionally rendering sections based on the authUser variable.

The component prompts non-authenticated users to register and authenticated users to sign out or view a list of courses. We then add some styling to define the background and positioning of the various components.

Next, we need to install the Appwrite package into our project. We’ll do that by running the command below in our project directory:

$npm install appwrite

Then, in the app.vue file, after the style tag, add the code below:

    ...

    <script>
    import { Account, Client, Functions } from 'appwrite';

    const client = new Client()
      .setEndpoint('https://cloud.appwrite.io/v1')
      .setProject('[PROJECT-ID]');
    const account = new Account(client);
    const functions = new Functions(client)
    const databases = new Databases(client)

    export default {
      name: 'IndexPage',
      data() {
        return {
          authUser: null,
          courses: null,
        };
      },
      mounted() {
        this.checkAnonymousSession();
        this.loadCourses();
      },
      methods: {
        async checkAnonymousSession() {
          try {
            const anonymousSession = await account.get();
            if (anonymousSession) {
              this.authUser = anonymousSession;
            }
          } catch (error) {
            console.error('Error checking anonymous session:', error.message);
          }
        },
        async register() {
          const user = await account.createAnonymousSession();
          this.authUser = user
        },
        async signOut() {
          await account.deleteSession('current');
          this.authUser = null;
        },
        async buyCourse() {
          try {
            const execution = await functions.createExecution(
              '[FUNCTION-ID]',
              JSON.stringify({
                failureUrl: window.location.href,
                successUrl: window.location.href,
              }),
              false,
              '/subscribe',
              'POST',
              {
                'Content-Type': 'application/json',
              }
            );
            const URL =
              execution.responseHeaders.find(
                (header) => header.name === 'location'
              ) ?? {};
            window.location.replace(URL.value ?? '/');
          } catch (error) {
            console.error('Error fetching courses', error);
          }
        },
      async loadCourses() {
          try {
            const courses = await databases.listDocuments(
              "[DATABASE-ID]",
              "[COLLECTION-ID]",
            )
            if (courses.total > 0) {
              this.courses = courses.documents
            }
          } catch (error) {
            console.log(error);
          }
        }
      }
    }
    </script>

In the code above, we:

  1. Import the Appwrite library and initialize an Appwrite client with the endpoint and project ID. We get the project ID from our Appwrite dashboard by clicking on the Overview tab.
  2. Create instances of the Account, Functions, and Databases classes using the initialized client. Appwrite's Account class allows us to set up an anonymous user, providing a convenient means to test the functionality of our Appwrite function without the need to implement a custom authentication system.
  3. Define data properties authUser and courses and a mounted lifecycle hook.
  4. Initiate four methods:
    • checkAnonymousSession: Fetches the authenticated user information using the account.get() method.
    • register: Creates an anonymous session.
    • signOut: Deletes the current session to sign out the user.
    • buyCourse: Calls our serverless function via the functions.createExecution method to handle the course purchase using Stripe. We call the /subscribe endpoint in the function template; it retrieves the success and failure URLs from the request body. It creates a checkout session using Stripe and redirects the user to the Stripe checkout URL. We get the function ID from our Appwrite dashboard by clicking the Functions tab.
    • loadCourses: Calls our database to fetch the course data.

Building other components

Now, we need to create the CourseList and CoursePage components, respectively. Add two new files to the components folder and name them CourseList.vue and CoursePage.vue.

In the CourseList.vue file, paste the following code:


    <template>
      <div>
        <div v-for="course in courses" :key="course.$id">
          <h2>{{ course.name }}</h2>
          <p>{{ course.description }}</p>
          <p>{{ course.tutor }}</p>
          <p>Price: ${{ course.price }}</p>
          <button @click="buyCourse()">Buy Now</button>
        </div>
      </div>
    </template>

    <script>
    export default {
      data() {
        return {
          courses: [
            {
              name: "Introduction to JavaScript",
              description: "Embark on a comprehensive journey to grasp the fundamental concepts of the JavaScript programming language. This engaging learning experience is tailored for beginners, offering a step-by-step exploration of key JavaScript principles, syntax, and functionalities. Discover the power of JavaScript as a versatile scripting language widely employed for both front-end and back-end web development.",
              tutor: "John Doe",
              price: 10
            },
          ],
        };
      },
      props: {
        buyCourse: Function
      },
    };
    </script>

This file defines the CourseList component. It takes in a prop called buyCourse and has a data property, courses.

In the CoursePage.vue file, paste the following code:


    <template>
        <div>
            <h2><u>Welcome to:</u> {{ course.name }}</h2>
            <p><u><b>Course description:</b></u><br> {{ course.description }}</p>
            <p><u><b>Tutor:</b></u> {{ course.tutor }}</p>
        </div>
    </template>

    <script>
    export default {
        props: {
            course: Object,
        },
    };
    </script>

This file defines the CoursePage component and takes in just one prop, course.

Now, let’s start our app, and we should see this page:

App home page

This is how the full demo looks:

{% embed loom.com/share/2a439860cd7149938e8eb775e593.. %}

To test the payment with Stripe, you can copy test cards from the Stripe documentation.

Conclusion

This tutorial provided a walkthrough of the "Subscriptions with Stripe" functions template on the Appwrite Cloud platform. We demonstrated the seamless integration of Stripe subscriptions by building a simple online Courseware. We also utilized Appwrite labels to grant users additional permissions.

The tutorial not only emphasized the straightforward setup of the template but also presented a practical demonstration of its integration in a real-world scenario. This hands-on example is a valuable reference for developers seeking to implement similar functionalities in their applications.

As with all Appwrite Functions, the "Subscriptions with Stripe" function template is flexible and can be easily customized to fit our needs. Developers can customize the template to accept payload from our frontend app. For instance, in the case of the online Courseware we built in this article, we can list multiple courses and their respective prices and have Stripe deduct the specific amount from our users. The ability to tailor the function to particular needs can significantly improve the process of adding features to our applications, ultimately saving time and effort.

Resources