To build Paystack subscriptions in Vue, create a plan on your backend using the Paystack Plans API, then initialize a transaction with the plan code. Paystack handles the recurring billing automatically. Your backend listens for subscription webhooks (subscription.create, invoice.payment_failed, subscription.disable) and updates your database. Your Vue frontend displays the subscription status by querying your backend API.
Paystack subscriptions follow a simple model. You create a plan that defines the amount, interval (daily, weekly, monthly, annually), and currency. When a customer pays for the first time using a transaction initialized with a plan code, Paystack creates a subscription and charges them automatically on the next billing date.
The flow for a Vue application:
Vue handles steps 2, 3, 4, and 7. Everything else is backend work.
Create plans using the Paystack Plans API from your backend:
// server.js (Express)
var PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;
app.post('/api/plans', async function(req, res) {
var response = await fetch('https://api.paystack.co/plan', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: req.body.name,
amount: req.body.amount, // in kobo
interval: req.body.interval, // daily, weekly, monthly, quarterly, biannually, annually
currency: 'NGN',
}),
});
var data = await response.json();
if (data.status) {
// Save plan_code to your database
res.json({ plan_code: data.data.plan_code, name: data.data.name });
} else {
res.status(400).json({ error: data.message });
}
});
// Fetch available plans for Vue to display
app.get('/api/plans', async function(req, res) {
// Return plans from your database
var plans = [
{ name: 'Basic', plan_code: 'PLN_xxxbasic', amount: 500000, interval: 'monthly' },
{ name: 'Pro', plan_code: 'PLN_xxxpro', amount: 1500000, interval: 'monthly' },
];
res.json({ plans: plans });
});
You typically create plans once (through the Paystack dashboard or a seed script) and store the plan codes in your database. Your Vue app fetches the list of plans from your backend, not from Paystack directly.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Initialize a transaction with the plan code and open the Paystack popup:
<script setup>
import { ref, onMounted } from 'vue';
var API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';
var plans = ref([]);
var loading = ref(false);
var selectedPlan = ref(null);
var email = ref('');
onMounted(async function() {
var script = document.createElement('script');
script.src = 'https://js.paystack.co/v2/inline.js';
document.head.appendChild(script);
var res = await fetch(API_URL + '/api/plans');
var data = await res.json();
plans.value = data.plans;
});
async function subscribe(plan) {
if (!email.value) return;
loading.value = true;
selectedPlan.value = plan;
var res = await fetch(API_URL + '/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email.value,
plan_code: plan.plan_code,
}),
});
var data = await res.json();
if (!data.access_code) {
alert(data.error || 'Failed to initialize');
loading.value = false;
return;
}
var popup = new window.PaystackPop();
popup.checkout({
accessCode: data.access_code,
onSuccess: async function(transaction) {
var verifyRes = await fetch(
API_URL + '/api/verify?reference=' + transaction.reference
);
var result = await verifyRes.json();
if (result.verified) {
alert('Subscription activated!');
}
loading.value = false;
},
onCancel: function() {
loading.value = false;
},
});
}
</script>
<template>
<div>
<input v-model="email" type="email" placeholder="Your email" />
<div v-for="plan in plans" :key="plan.plan_code">
<h3>{{ plan.name }}</h3>
<p>NGN {{ (plan.amount / 100).toLocaleString() }} / {{ plan.interval }}</p>
<button @click="subscribe(plan)" :disabled="loading || !email">
Subscribe
</button>
</div>
</div>
</template>
The subscribe endpoint initializes a transaction with the plan code:
app.post('/api/subscribe', async function(req, res) {
var email = req.body.email;
var planCode = req.body.plan_code;
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
plan: planCode,
// amount is not needed when a plan is provided
}),
});
var data = await response.json();
if (data.status) {
res.json({
access_code: data.data.access_code,
reference: data.data.reference,
});
} else {
res.status(400).json({ error: data.message });
}
});
When you pass a plan to the Initialize Transaction endpoint, you do not need to pass an amount. Paystack pulls the amount from the plan. After the customer pays, Paystack automatically creates a subscription and schedules the next charge.
Subscription lifecycle events arrive as webhooks. Your backend must handle these events to keep your database in sync:
var crypto = require('crypto');
app.post('/api/webhooks/paystack', express.raw({ type: 'application/json' }), function(req, res) {
var hash = crypto
.createHmac('sha512', PAYSTACK_SECRET)
.update(req.body)
.digest('hex');
if (hash !== req.headers['x-paystack-signature']) {
return res.status(400).send('Invalid signature');
}
var event = JSON.parse(req.body);
if (event.event === 'subscription.create') {
// New subscription created
var sub = event.data;
// Save: sub.subscription_code, sub.plan.plan_code, sub.customer.email, sub.next_payment_date
}
if (event.event === 'invoice.payment_failed') {
// Recurring charge failed
// Update subscription status to 'past_due'
// Notify the customer via email
}
if (event.event === 'subscription.not_renew') {
// Customer cancelled, subscription will not renew
// Keep access until current period ends
}
if (event.event === 'subscription.disable') {
// Subscription fully disabled
// Revoke access
}
if (event.event === 'charge.success') {
// Could be initial or recurring charge
// Update payment history
}
res.sendStatus(200);
});
Vue cannot receive webhooks. This endpoint runs on your backend. See Handle Paystack Webhooks in Vue for the architectural explanation.
Create a composable that fetches the subscription status from your backend:
// src/composables/useSubscription.js
import { ref, onMounted } from 'vue';
var API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';
export function useSubscription() {
var subscription = ref(null);
var loading = ref(true);
onMounted(async function() {
try {
var res = await fetch(API_URL + '/api/subscription', {
headers: { Authorization: 'Bearer ' + localStorage.getItem('token') },
});
var data = await res.json();
subscription.value = data.subscription;
} catch (err) {
subscription.value = null;
} finally {
loading.value = false;
}
});
return { subscription, loading };
}
Use it in a subscription management page:
<script setup>
import { useSubscription } from '../composables/useSubscription';
var { subscription, loading } = useSubscription();
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="subscription">
<p>Plan: {{ subscription.planName }}</p>
<p>Status: {{ subscription.status }}</p>
<p>Next billing: {{ subscription.nextPaymentDate }}</p>
<a :href="subscription.manageUrl" target="_blank">Manage Subscription</a>
</div>
<div v-else>
<p>No active subscription.</p>
</div>
</template>
Paystack provides two ways to let customers manage subscriptions:
manage_url that the customer can visit to cancel or update their card. Store this link and show it in your Vue UI.app.post('/api/subscription/cancel', async function(req, res) {
var response = await fetch('https://api.paystack.co/subscription/disable', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: req.body.subscription_code,
token: req.body.email_token,
}),
});
var data = await response.json();
res.json(data);
});
The email_token is sent in the subscription creation webhook. Store it alongside the subscription code. After cancellation, the customer keeps access until the current billing period ends.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course