This guide covers how to integrate mavi pay into a Next.js application using @mavi-pay/sdk. You will set up the SDK, create a checkout flow, and handle post-payment events.
npm install @mavi-pay/sdkOr with your preferred package manager:
pnpm add @mavi-pay/sdk
yarn add @mavi-pay/sdkCreate a mavi pay client instance. This is typically done in a shared utility file.
// 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',
})Generate your access token in the merchant dashboard under Settings > API Keys.
Wrap your application with the MaviPayProvider to make the SDK available throughout your component tree.
// app/layout.tsx
import { MaviPayProvider } from '@mavi-pay/sdk/react'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<MaviPayProvider
accessToken={process.env.NEXT_PUBLIC_MAVI_PAY_ACCESS_TOKEN!}
baseUrl="https://api.mavifinans.sh"
>
{children}
</MaviPayProvider>
</body>
</html>
)
}Use the useCheckout hook to initiate a checkout session from a client component.
// components/BuyButton.tsx
'use client'
import { useCheckout } from '@mavi-pay/sdk/react'
export function BuyButton({ productId }: { productId: string }) {
const { checkout, isLoading } = useCheckout()
const handleClick = async () => {
await checkout({
productId,
successUrl: `${window.location.origin}/success`,
})
}
return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Redirecting...' : 'Buy Now'}
</button>
)
}Render the checkout form inline on your page without redirecting.
// app/buy/page.tsx
'use client'
import { MaviPayCheckout } from '@mavi-pay/sdk/react'
export default function BuyPage() {
return (
<div className="mx-auto max-w-lg py-12">
<h1 className="mb-6 text-2xl font-bold">Complete Your Purchase</h1>
<MaviPayCheckout
productId="prod_xxxxxxxx"
onSuccess={(result) => {
window.location.href = `/success?order=${result.orderId}`
}}
onError={(error) => {
console.error('Checkout failed:', error.message)
}}
theme="dark"
/>
</div>
)
}Fetch products from the mavi pay API in a Server Component.
// app/products/page.tsx
import { maviPay } from '@/lib/mavi-pay'
export default async function ProductsPage() {
const products = await maviPay.products.list()
return (
<div>
<h1>Products</h1>
<ul>
{products.items.map((product) => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
<p>
{product.price.amount / 100} {product.price.currency}
</p>
</li>
))}
</ul>
</div>
)
}Set up an API route to receive mavi pay webhook events.
// app/api/mavi-pay/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifyWebhookSignature } from '@mavi-pay/sdk'
export async function POST(request: NextRequest) {
const body = await request.text()
const signature = request.headers.get('x-mavi-pay-signature')
if (!signature) {
return NextResponse.json({ error: 'Missing signature' }, { status: 401 })
}
const isValid = verifyWebhookSignature(
body,
signature,
process.env.MAVI_PAY_WEBHOOK_SECRET!
)
if (!isValid) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
const event = JSON.parse(body)
switch (event.type) {
case 'checkout.completed':
// Handle successful purchase
break
case 'subscription.created':
// Handle new subscription
break
case 'subscription.cancelled':
// Handle cancellation
break
}
return NextResponse.json({ received: true })
}Register your webhook URL in the dashboard under Settings > Webhooks.
Add these to your .env.local:
# Server-side (never expose to the client)
MAVI_PAY_ACCESS_TOKEN=your_api_key_here
MAVI_PAY_WEBHOOK_SECRET=your_webhook_secret_here
# Client-side (safe to expose)
NEXT_PUBLIC_MAVI_PAY_ACCESS_TOKEN=your_public_key_here