This guide shows how to integrate mavi pay with BetterAuth to gate access to content and features based on purchase status and subscription entitlements.
BetterAuth handles user authentication. mavi pay handles payments and entitlements. Together, they let you:
npm install @mavi-pay/sdk better-authSet up BetterAuth as described in their documentation. You need a working authentication flow before adding mavi pay.
// lib/mavi-pay.ts
import { MaviPay } from '@mavi-pay/sdk'
export const maviPay = new MaviPay({
accessToken: process.env.MAVI_PAY_ACCESS_TOKEN!,
baseUrl: 'https://api.mavifinans.sh',
})When a customer completes a checkout, use a webhook to link the mavi pay customer ID to the BetterAuth user.
// Handle the checkout.completed webhook event
async function handleCheckoutCompleted(event: any) {
const { customer_email, customer_id } = event.data
// Find the BetterAuth user by email
const user = await auth.getUserByEmail(customer_email)
if (user) {
// Store the mavi pay customer ID on the user record
await auth.updateUser(user.id, {
metadata: {
maviPayCustomerId: customer_id,
},
})
}
}Create a middleware that checks whether the authenticated user has an active purchase or subscription.
// middleware/require-purchase.ts
import { maviPay } from '@/lib/mavi-pay'
export async function requirePurchase(
userId: string,
productId: string
): Promise<boolean> {
const user = await auth.getUser(userId)
const customerId = user?.metadata?.maviPayCustomerId
if (!customerId) {
return false
}
const orders = await maviPay.orders.list({
customerId,
productId,
})
return orders.items.some(
(order) => order.status === 'paid' || order.status === 'active'
)
}// app/premium/page.tsx
import { auth } from '@/lib/auth'
import { requirePurchase } from '@/middleware/require-purchase'
import { redirect } from 'next/navigation'
export default async function PremiumPage() {
const session = await auth.getSession()
if (!session) {
redirect('/login')
}
const hasAccess = await requirePurchase(session.user.id, 'prod_xxxxxxxx')
if (!hasAccess) {
redirect('/pricing')
}
return (
<div>
<h1>Premium Content</h1>
<p>You have access to this content.</p>
</div>
)
}For subscription products, check the subscription status rather than individual orders.
// lib/entitlements.ts
import { maviPay } from '@/lib/mavi-pay'
export async function hasActiveSubscription(
customerId: string,
productId: string
): Promise<boolean> {
const subscriptions = await maviPay.subscriptions.list({
customerId,
productId,
})
return subscriptions.items.some(
(sub) => sub.status === 'active'
)
}Register a webhook endpoint in your mavi pay dashboard to keep user entitlements in sync:
checkout.completed -- Grant access after purchase.subscription.cancelled -- Revoke access when subscription ends.subscription.renewed -- Confirm continued access.See the Next.js integration guide for a webhook handler example.
MAVI_PAY_ACCESS_TOKEN=your_api_key_here
MAVI_PAY_WEBHOOK_SECRET=your_webhook_secret_here
BETTER_AUTH_SECRET=your_auth_secret_here