Framework GuideNext.js 13/14/15
Next.js Integration Guide
Learn how to integrate @omkarbhosale/upiqr into Next.js using React Server Components, Client Components, and TypeScript.
Environment Setup
Store your store's default UPI ID and merchant details in .env.local. Prefix with NEXT_PUBLIC_ for client-side access:
.env.local
# .env.local
NEXT_PUBLIC_UPI_ID=merchant@okhdfcbank
NEXT_PUBLIC_PAYEE_NAME="Acme Store"Single QR Component
Use this client component to render an interactive QR code that dynamically updates when the payment amount changes:
app/components/SingleQRCode.tsx
// app/components/SingleQRCode.tsx
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { generateQR } from "@omkarbhosale/upiqr";
interface Props {
amount: number;
}
export default function SingleQRCode({ amount }: Props) {
const [qrUrl, setQrUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let active = true;
async function createQR() {
try {
setLoading(true);
setError(null);
const url = await generateQR({
UPI_ID: process.env.NEXT_PUBLIC_UPI_ID!,
AMOUNT: amount,
name: process.env.NEXT_PUBLIC_PAYEE_NAME,
note: `Order payment for ₹${amount}`,
});
if (active) setQrUrl(url);
} catch (err: any) {
if (active) setError(err.message || "Failed to generate QR");
} finally {
if (active) setLoading(false);
}
}
createQR();
return () => { active = false; };
}, [amount]);
if (loading) {
return <div className="animate-pulse p-8 text-center text-sm text-slate-500">Generating UPI QR...</div>;
}
if (error) {
return <div className="p-4 rounded-lg bg-red-50 text-red-600 text-xs">{error}</div>;
}
return (
<div className="flex flex-col items-center p-6 border rounded-2xl bg-white shadow-sm text-center">
<h3 className="text-lg font-bold text-slate-900 mb-1">Scan to Pay</h3>
<p className="text-2xl font-black text-indigo-600 mb-4">₹{amount}</p>
{qrUrl && (
<div className="p-2 border rounded-xl bg-white shadow-inner">
<Image src={qrUrl} width={220} height={220} alt="UPI QR Code" priority />
</div>
)}
<p className="text-xs text-slate-400 mt-3">
Supports GPay, PhonePe, Paytm & any BHIM UPI app
</p>
</div>
);
}Split Checkout Component
For orders exceeding ₹2,000, this component breaks the total into ₹1,999 parts and presents a multi-part tab stepper:
app/components/SplitCheckout.tsx
// app/components/SplitCheckout.tsx
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { splitTransactionQR, SplitQRItem } from "@omkarbhosale/upiqr";
export default function SplitCheckout({ amount = 5000 }: { amount?: number }) {
const [splits, setSplits] = useState<SplitQRItem[]>([]);
const [activeStep, setActiveStep] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadSplits() {
try {
setLoading(true);
const data = await splitTransactionQR({
UPI_ID: process.env.NEXT_PUBLIC_UPI_ID!,
AMOUNT: amount,
name: "Acme Store",
note: "Invoice #109",
});
setSplits(data);
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}
loadSplits();
}, [amount]);
if (loading) return <div>Calculating splits...</div>;
const current = splits[activeStep];
return (
<div className="max-w-md mx-auto p-6 rounded-2xl border bg-white shadow-lg">
<div className="flex items-center justify-between pb-4 border-b mb-4">
<div>
<h2 className="font-bold text-slate-900">Multi-Part UPI Payment</h2>
<p className="text-xs text-slate-500">Split into {splits.length} parts under ₹2,000</p>
</div>
<span className="text-sm font-bold text-indigo-600">Total: ₹{amount}</span>
</div>
{/* Part tabs */}
<div className="flex gap-2 mb-6">
{splits.map((s, idx) => (
<button
key={s.id}
onClick={() => setActiveStep(idx)}
className={`flex-1 py-2 text-xs font-semibold rounded-lg border transition-all ${
activeStep === idx
? "bg-indigo-600 text-white border-indigo-600 shadow-sm"
: "bg-slate-50 text-slate-600 hover:bg-slate-100"
}`}
>
Part {idx + 1} (₹{s.amount})
</button>
))}
</div>
{/* Current QR Code */}
{current && (
<div className="flex flex-col items-center">
<div className="p-2 border rounded-xl bg-white mb-3">
<Image src={current.image} width={200} height={200} alt="QR part" />
</div>
<div className="text-lg font-black text-slate-900">₹{current.amount}</div>
<p className="text-xs text-slate-400 mt-1 mb-4">Part {activeStep + 1} of {splits.length}</p>
<button
onClick={() => setActiveStep((prev) => Math.min(prev + 1, splits.length - 1))}
disabled={activeStep === splits.length - 1}
className="w-full py-2.5 rounded-xl bg-indigo-600 text-white text-xs font-semibold hover:bg-indigo-500 disabled:opacity-40 transition-all"
>
{activeStep === splits.length - 1 ? "All Parts Scanned" : "Next Part →"}
</button>
</div>
)}
</div>
);
}App Router vs Pages Router
App Router (app/): Because QR codes contain data generated dynamically for the user, mark your components with "use client".
Pages Router (pages/): Components work directly within standard Pages lifecycle. No directive is required.