Create a subscription system with Vue 3, Vite, and Stripe.
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.
Terminalmkdir 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.
Terminalnpm init -y code .
- Install all the libraries for the Express js server.
Install through npm or yarnnpm 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.
Confignpx 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 astripe.ts
file.
stripe.ts
- Restart your editor if you're using VS Code, open your terminal again and run:
Terminalnpm run build npm run dev
- You should see this 👇 in your 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.
TerminalProject 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.
Terminalcd 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/.envVITE_STRIPE_PUBLISHABLE_KEY= VITE_BASIC_PLAN= VITE_PREMIUM_PLAN=
-
If you run into problems with the TypeScript server not recognizing your
.env
variables, create anenv.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
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 asubscription.ts
file. Insert the following code there.
client/src/stores/subscription.tsimport { 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
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
Bring your stores and the publishable Stripe key from the .env
file. Push the Thankyou route on a successful subscription.
client/src/components/CheckoutForm.vueimport { 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 thesrc
folder and declare anindex.d.ts
file with the following code.
client/src/index.d.tsexport {} declare global { interface Window { Stripe: Stripe } }