Experiments >

Supabase + Svelte Kit + Stripe

Experiment #6421st March, 2021by Joshua Nussbaum

Started building an example app for svelte-kit, that integrates supabase with Stripe. I’m not releasing the code yet because I hope to turn it into a blog series.

Environment variables

The first challenge was sharing environment variables. vite does support dotenv for the client side. For protection it only includes variables that start with VITE_, ie VITE_SUPABASE_PUBLIC_KEY. For the private keys needed by the backend, I couldn’t find a place to tell svelte-kit to load dotenv, so I used dotenv-cli instead and prefixed the dev and start commands with it.

// in package.json

"scripts": {
  // prefix with dotenv
  "dev": "dotenv -e .env -- svelte-kit dev",

  // prefix with dotenv
  "start": "dotenv -e .env -- svelte-kit start"
},

worked great.

Stripe checkout

I created an endpoint src/routes/checkout.json.js, it handles POST /checkout and returns a Stripe checkout sessionId. It requires the price_id of the plan, and the supabase access_token. It verifies the access token, and extracts the email address and user id from the JWT and attaches it as meta data on the checkout:

import Stripe from 'stripe'
import jwt from 'jsonwebtoken'
import { createClient } from '@supabase/supabase-js'

const {env} = process

// init stripe & supabase
const stripe = new Stripe(env.STRIPE_PRIVATE_KEY)
const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_PRIVATE_KEY)

// handle HTTP POST
export async function post(req) {
  // priceId and accessToken are required
  const { priceId, cancelUrl, accessToken } = req.body

  try {
    // verify that it's a valid accessToken
    const {email, sub: userId} = jwt.verify(accessToken, env.SUPABASE_JWT_SECRET)

    // create a checkout session
    const session = await createCheckout({
      priceId,
      cancelUrl,
      userId,
      email,
      host: req.headers.origin
    })

    // return sessionId as JSON
    return {
      body: {
        sessionId: session.id
      }
    }
  } catch (e) {
    // when error, return the message
    return {
      status: 400,
      body: {
        error: e.message
      }
    }
  }
}

// helper function to create the checkout object
function createCheckout({ priceId, cancelUrl, userId, email, host }) {
  return stripe.checkout.sessions.create({
    mode: "subscription",
    payment_method_types: ["card"],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      }
    ],
    // attach metadata from the JWT
    metadata: {
      userId,
      email
    },
    success_url: new URL('payment/success?session_id={CHECKOUT_SESSION_ID}', host).toString(),
    cancel_url: cancelUrl || host,
  })
}

In my future experiments, I will look at handling webhooks from Stripe and updating the user data in Supabase.

view all experiments

Stay tuned in

Learn how to add more experimentation to your workflow