Build a Paid Membership Site with Magic and Stripe: Pt. 3 - Node Server
Tags: passwordless, stripe, magic, auth
Published: 2021-03-10 01:25:10

---
title: Build a Paid Membership Site with Magic and Stripe: Pt. 3 - Node Server
published: true
description: A deep dive into how we built the server side of our paid membership website.
tags: passwordless, stripe, magic, auth
cover_image: https://i.imgur.com/EElejSN.png
---

This is the third part of the Build a Paid Membership Site with Magic and Stripe series. Make sure to read the following before proceeding:
1. [**Quickstart**](https://dev.to/magiclabs/build-a-paid-membership-site-with-magic-and-stripe-pt-1-quickstart-bp) to set up Stripe and Magic.
2. [**How the React client side is built**](https://dev.to/magiclabs/build-a-paid-membership-site-with-magic-and-stripe-pt-2-react-client-3j3m).

# Server
Now that we understand how the Client side is built and how it works, let's learn about our Server side code (located in `server.js`). 

Here are the major functions our server needs to work seamlessly with the Client:

1. Validate the Auth Token (`didToken`) returned by Magic's `loginWithMagicLink()`.
2. Create a Stripe `PaymentIntent` to keep track of the Customer's payment lifecycle.
3. Create a Stripe Customer so that we can tie the Customer to a matching `PaymentIntent` and keep track of whether or not the Customer has successfully paid.
4. Validate that the user is indeed a Customer who has lifetime access to your Premium Content.

## Validate the Auth Token (didToken)
As mentioned, when the user clicks the email link to log in, we send the `didToken` to a server endpoint called `/login` in order to validate it. It's best practice to validate the `DID Token` before continuing to avoid invalid or malformed tokens.

We verify the `didToken` with Magic's `validate` method. If the `didToken` is indeed valid, we then send a `200` status code back to the client.

```js
/* File: server.js */

// Import, then initiate Magic instance for server-side methods
const { Magic } = require("@magic-sdk/admin");
const magic = new Magic(process.env.MAGIC_SECRET_KEY);

// Route to validate the user's DID token
app.post("/login", async (req, res) => {
  try {
    const didToken = req.headers.authorization.substr(7);
    await magic.token.validate(didToken);
    res.status(200).json({ authenticated: true });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
```

## Create a Stripe Payment Intent and Stripe Customer
Once a user decides to buy a lifetime access pass to our Premium Content, we consider them a customer. In order to keep track of the customer's payment cycle, we'll need to add a new server endpoint called `/create-payment-intent` in `server.js` that:

1. Creates a customer with their email address.
2. And then creates a `PaymentIntent` that is linked to this customer. 

The `PaymentIntent` will keep track of any failed payment attempts and ensures that the customer is only charged once.

```js
/* File: server.js */

// Import & initiate Stripe instance
const  stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

// Add the user to your list of customers
// Then create a PaymentIntent to track the customer's payment cycle
app.post("/create-payment-intent", async (req, res) => {
  const { email } = req.body;

  const paymentIntent = await stripe.customers
    .create({
      email,
    })
    .then((customer) =>
      stripe.paymentIntents
        .create({
          amount: 50000, // Replace this constant with the price of your service
          currency: "usd",
          customer: customer.id,
        })
        .catch((error) => console.log("error: ", error))
    );

  res.send({
    clientSecret: paymentIntent.client_secret,
    customer: paymentIntent.customer,
  });
});
```
As you can see, we'll be charging our customers $500 for a lifetime access pass to our awesome Premium Content. 😎

## Update the Stripe Customer's Info
As soon as the payment goes through, the Client side will send a request to `/update-customer` to update the Stripe Customer's information with a `metadata` of `{ lifetimeAccess: true }`. Since we have a special use case; charging customers a one time fee, setting this metadata will help us validate whether or not the customer has paid.

```js
/* File: server.js */

// Update the customer's info to reflect that they've
// paid for lifetime access to your Premium Content
app.post("/update-customer", async (req, res) => {
  const { customerID } = req.body;

  const customer = await stripe.customers.update(customerID, {
    metadata: { lifetimeAccess: true },
  });

  res.send({
    customer,
  });
});
```

## Validate a Paid Customer
Now that the user has successfully paid, they should be able to access the Premium Content page. To check whether or not the user is authorized, we'll be using the `/validate-customer` endpoint. It expects the user's email address, and returns a list of customers who has that email.

Ideally, your customer should know to only buy their lifetime access *once*. This way, the list that [`stripe.customers.list()`](https://stripe.com/docs/api/customers/list) returns will always have the single customer who paid. 

*However*, accidents do happen. 🤷🏻‍♀️

To prevent users from purchasing a lifetime access twice, I suggest adding some logic to your `SignUp` component that checks whether or not the user who's trying to sign up is already a Stripe Customer with lifetime access. If they are, send them to the Premium Content page. Otherwise, they can continue to the Payment page.

```js
/* File: server.js */

// Collect the customer's information to help validate
// that they've paid for lifetime access
app.post("/validate-customer", async (req, res) => {
  const { email } = req.body;

  const customer = await stripe.customers.list({
    limit: 1,
    email,
  });

  res.send({
    customer: customer.data,
  });
});
```

# Test the integration
Alright, now that we know how the paid membership app works on both the Client and Server side, let's give it a test run! Here are a few UX flows I suggest testing out:

1. Head to the Premium Content page to sign up and pay for lifetime access.
2. Log in as a **paid** Customer and try to access the Premium Content page.
3. Using a different email, log in as an **unpaid** Customer and try to access the Premium Content page.

Btw, you can make your payments with this test card number: `4242 4242 4242 4242`

# What's next
YAY! You now have a paid membership site. Read our [**final article**](https://dev.to/magiclabs/build-a-paid-membership-site-with-magic-and-stripe-pt-4-deploy-to-heroku-3dpa) of this series to learn how to deploy your app to Heroku.