How to set up subscriptions for an online course in NuxtJS
Tags: webdev, vue, nuxt, tutorial
Published: 2024-01-12 11:22:40

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](https://nodejs.org/en/) installed on your machine and a basic understanding of Node.js
3. An Appwrite Cloud account (create one [here](https://cloud.appwrite.io/login?utm_source=hackmamba&utm_medium=hackmamba-blog))
4. A Stripe account (create one [here](https://dashboard.stripe.com/register?utm_source=hackmamba&utm_medium=hackmamba-blog))

## Repository

All the code used in this article can be found in this [repository](https://github.com/folucode/courseware?utm_source=hackmamba&utm_medium=hackmamba-blog)

## 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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1703070148399_PwiE2GUY.png)


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

![Appwrite cloud create function page](https://paper-attachments.dropboxusercontent.com/s_2ED46675DFC7A75178CE3FF2283700A856C8EF4F940219E84088BF51FEDF742A_1696323918677_Screenshot+2023-10-03+at+10.04.19.png)


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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701871303255_Screenshot+2023-12-06+at+15.00.48.png)


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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701871533335_Screenshot+2023-12-06+at+15.04.34.png)


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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701813419186_Screenshot+2023-12-05+at+22.48.47.png)

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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701872157098_Screenshot+2023-12-06+at+15.15.13.png)


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

![Stripe webhook page](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701872315135_Screenshot+2023-12-06+at+15.16.33.png)


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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1704664291874_Screenshot+2024-01-07+at+22.50.58.png)


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**.

``` javascript

    {
      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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1704664799073_Screenshot+2024-01-07+at+22.59.19.png)

### 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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1704667096547_Screenshot+2024-01-07+at+23.36.59.png)

## Frontend setup

We're using Nuxt.js for our frontend to implement the function. To install Node, go to the [Node.js website](https://nodejs.org/en/) 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 <project-name>

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


    cd <project-name>

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:

``` javascript

    <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:

``` javascript
    ...
    
    <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:

``` javascript

    <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:

``` javascript

    <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](https://paper-attachments.dropboxusercontent.com/s_8054468BE277D1C83D01D5EE760A721EE0A516C6D95A070348B299A25FF7F87C_1701878123035_Screenshot+2023-12-06+at+16.55.00.png)


This is how the full demo looks:

{% embed https://www.loom.com/share/2a439860cd7149938e8eb775e59344c4?sid=b11c8319-0405-4ce9-98c6-1a6eff6951c3 %}



To test the payment with Stripe, you can copy test cards from the Stripe [documentation](https://stripe.com/docs/testing?testing-method=card-numbers?utm_source=hackmamba&utm_medium=hackmamba-blog).

## 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
- [Nuxt.js Documentation](https://nuxt.com/docs/getting-started/installation?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Functions Documentation](https://appwrite.io/docs/products/functions?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Subscriptions with Stripe Documentation](https://github.com/appwrite/templates/tree/main/node/subscriptions-with-stripe?utm_source=hackmamba&utm_medium=hackmamba-blog)