Subscription system with Vue 3, Vite, and Stripe.

Create a subscription system with Vue 3, Vite, and Stripe.

Denisse Avatar

By:

Denisse Abreu
Oct. 10, 2022 7pm ET

Eng/Spa 10-min

Hello 👋🏼, In this tutorial, I will show you how to build a subscription system with Vue 3, Vite, and Stripe. Our working environment is Vue 3 with Vite, Express js, and TypeScript. For the CSS, I will use Tailwind. If you don't have a project, you can fork my repository here: Vue 3 Stripe Subscriptions. If you're looking for a subscription system with vue 2, check out this tutorial: Build A Subscription System With Stripe and Vue js.
Let's get started!

  • Subscription Workflow

Set Up the Express js and Typescript Server.

We'll start by installing Express js and TypeScript. If you don't want to use Express js, check the different server integrations that Stripe has in their Build a subscriptions integration documentation.

  • Make a new directory on your computer and go to that directory.
Terminal
mkdir vue-subscriptions cd vue-subscriptions

Create a Node js project, use the -y flag to create it without asking questions, and open the project in your code editor. Go to package.json and fill in the empty values.

Terminal
npm init -y code .
  • Install all the libraries for the Express js server.
Install through npm or yarn
npm install stripe --save npm install cors dotenv express
  • Install all the libraries related to TypeScript with the -D (development) flag.

  npm install -D typescript @types/cors @types/express @types/node concurrently nodemon
  
  • Create the TypeScript configuration file.
Config
npx tsc --init
  • Go to the tsconfig.json file uncomment and add these JSON values.
tsconfig.json
  • Go to package.json and update the scripts values.
package.json
  • Create index.ts in the root of the project and insert the following code.
index.ts
  • Create a routes folder in the root, and inside create a stripe.ts file.
stripe.ts
  • Restart your editor if you're using VS Code, open your terminal again and run:
Terminal
npm run build npm run dev
  • You should see this 👇 in your terminal.

Concurrently terminal

Set Up Vue 3 and Stripe.

In this part of the tutorial, we'll install and integrate Stripe into Vue 3. First, open your terminal and run npm init vue@latest; after running this command, Vue will ask you some questions about the project. For more insight about installing Vue on your machine, visit Vue's quick start guide.

Terminal
Project name: client Add TypeScript? Yes Add JSX Support? No Add Vue Router for Single Page Application development? Yes Add Pinia for state management? Yes
  • Go to the client folder and install the packages.
Terminal
cd client npm install
  • After successfully installing Vue 3, go to index.html and copy the Stripe CDN in the head of the file.
index.html

Create the .env file in the root of the client folder and insert your Stripe Secret keys. If you don't have your product's secret keys or haven't created your subscription plans, follow the Stripe Documentation on how to create your pricing model.

client/.env
VITE_STRIPE_PUBLISHABLE_KEY= VITE_BASIC_PLAN= VITE_PREMIUM_PLAN=
  • If you run into problems with the TypeScript server not recognizing your .env variables, create an env.d.ts in the root of the client folder and restart the TS server.
client/env.d.ts

Integrate Stripe into your Vue 3 Components.

Our first move to integrate Stripe into your Vue 3 Components is by building the signup form. Collect all the personal information from your customer, including his physical address; Send this information to the TypeScript server. If everything is good, you will receive a JSON response from the server with the customer object. Store this object in the user store that we'll create using Pinia. The rules parameter is the validation provider Vee-Validate.

client/src/components/SignUpForm.vue

SignUp View

To send the customer's personal information to the server, we need a store library. Go to the src folder and create stores; inside, create user.ts. This file will make the server call with Axios and store the customer object in the state.

client/src/stores/user.ts
  • Call the SignUp() function and push the plan view with Vue Router.
client/src/components/SignUpForm.vue

After storing the customer object, we'll push the plan view. This plan view will be in charge of creating the subscription. We'll call the server again and use Pinia to store the plan that the customer chose, store the plan id, and the customer secret that we'll get from the server.

  • Go to the stores folder, and create a subscription.ts file. Insert the following code there.
client/src/stores/subscription.ts
import { defineStore } from 'pinia' import axios from 'axios' interface PlanData { subscriptionId: string clientSecret: string } interface PlanChose { plan: string price: string } const url = '/stripe' export const usePlanStore = defineStore('plan', { state: () => ({ planData: {} as PlanData, planChose: {} as PlanChose }), actions: { async createSubscription( customerId: string | null, priceId: string | undefined, price: string, plan: string ) { try { const res = await axios.post(`${url}/create-subscription`, { customerId, priceId }) if (res.status === 200) { this.planData = res.data planChose = { plan, price } return this.planData } } catch (error) { throw new Error() } } } })
  • Build your Plan View.
client/src/components/PlanView.vue

Choose Your Plan View

Bring the user store and send the subscription parameters to the plan store we created earlier. If the response from the server is good, push the checkout view.

client/src/components/PlanView.vue

Congratulations 🎉 this is the final part! We'll build the last part of this subscription system with Stripe. Build a checkout view; inside this checkout view, create a checkout form. This form will show the Stripe card element to the customer, finish the transaction, and activate the subscription.

client/src/components/CheckoutForm.vue

Checkout View

Bring your stores and the publishable Stripe key from the .env file. Push the Thankyou route on a successful subscription.

client/src/components/CheckoutForm.vue
import { ref, onMounted } from 'vue' import { useRouter } from 'vue-router' import { useUserStore } from '../stores/user' import { usePlanStore } from '../stores/subscription' const userStore = useUserStore() const planStore = usePlanStore() const router = useRouter() const disabled = ref(false) const card = ref(null) const stripe = window.Stripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY) const elements = stripe.elements() const style = { base: { color: '#32325d', fontFamily: '"Helvetica Neue", Helvetica, sans-serif', fontSmoothing: 'antialiased', fontSize: '16px', '::placeholder': { color: '#aab7c4' } }, invalid: { color: '#fa755a', iconColor: '#fa755a' } } const el = elements.create('card', { style: style }) function displayError(event: typeof stripe) { const displayError: typeof stripe = document.getElementById('card-errors') if (event.error) { displayError.textContent = event.error.message } else { displayError.textContent = '' } } onMounted(() => { el.mount(card.value) el.on('change', (event: HTMLElement) => { displayError(event) }) }) const Submit = async () => { disabled.value = true const clientSecret = planStore.planData.clientSecret const fullName = userStore.userData.name const result = await stripe.confirmCardPayment(clientSecret, { payment_method: { type: 'card', card: el, billing_details: { name: fullName } } }) if (result.error) { disabled.value = false alert(result.error.message) } else { // Successful subscription payment // The subscription automatically becomes active upon payment. router.push({ name: 'thankyou', params: { subscription: planStore.planData.subscriptionId } }) } }
  • In case the TypeScript server starts to throw errors on the window.Stripe() function, go to the src folder and declare an index.d.ts file with the following code.
client/src/index.d.ts
export {} declare global { interface Window { Stripe: Stripe } }