쉽고 빠르게 Razorpay를 Next.js 14/15에 통합하는 방법
2025-01-25 17:17:48razorpaynextjspaymentwebdev
Next.js 14/15에서 Razorpay 통합하기: 간단한 단계로 해결!
Next.js 프로젝트에 Razorpay를 도입하여 결제 시스템을 추가하는 방법을 소개합니다. 이 가이드는 쉽게 따라할 수 있도록 각 단계를 세세하게 설명합니다. 특히, 최신 Next.js 14/15 버전에서 특별히 유용한 팁들을 포함하고 있습니다.
목차
라이브러리 설치하기
먼저, Razorpay SDK와 필요한 패키지를 설치합니다. 프로젝트 디렉토리에서 다음 명령어를 실행하세요:
npm install razorpay @types/razorpay axios
Razorpay API 키 받기
Razorpay 대시보드에서 API 키를 발급받아 프로젝트 루트 디렉토리의 .env 파일에 저장하세요. 이렇게 환경 변수를 설정합니다:
NEXT_PUBLIC_RAZORPAY_KEY_ID=YOUR_RAZORPAY_KEY_ID
RAZORPAY_KEY_ID=YOUR_RAZORPAY_KEY_ID
RAZORPAY_KEY_SECRET=YOUR_RAZORPAY_KEY_SECRET
주문 API 경로 생성하기
이제 Next.js 내장 API 경로 시스템을 사용하여 주문 생성을 위한 백엔드 로직을 구현할 차례입니다. /api/createOrder/route.ts라는 경로를 만들어 코드를 추가합니다:
import { NextRequest, NextResponse } from "next/server";
import Razorpay from "razorpay";
const key_id = process.env.RAZORPAY_KEY_ID as string;
const key_secret = process.env.RAZORPAY_KEY_SECRET as string;
if (!key_id || !key_secret) {
throw new Error("Razorpay keys are missing");
}
const razorpay = new Razorpay({
key_id,
key_secret
});
export type OrderBody = {
amount: number;
currency: string;
}
export async function POST(request: NextRequest) {
try {
const { amount, currency }: OrderBody = await request.json();
if (!amount) {
return NextResponse.json({ message: `Amount is required` }, { status: 400 });
}
const options = {
amount,
currency: currency || "INR",
receipt: `receipt#${Date.now()}`,
};
const order = await razorpay.orders.create(options);
console.log("Order Created Successfully");
return NextResponse.json({ orderId: order.id }, { status: 200 });
} catch (error) {
return NextResponse.json({ message: "Server Error", error }, { status: 500 });
}
}
주문 ID 생성 함수 만들기
createOrderId 함수를 만들어 주문 ID를 생성해 봅시다.
import axios from "axios";
export async function createOrderId(amount: number, currency: string) {
try {
const response = await axios.post("/api/createOrder", {
amount: amount * 100, // Convert to paise
currency: "INR",
});
console.log("Order Response:", response.data);
return response.data.orderId;
} catch (error) {
console.error(error);
throw new Error("Failed to create order");
}
}
결제 버튼 구현하기
Razorpay 결제 버튼용으로 재사용할 수 있는 컴포넌트를 만듭니다. components/BuyButton.tsx를 생성하고 다음 코드를 추가하세요:
"use client";
import axios from "axios";
import React from "react";
import Script from "next/script";
import { createOrderId } from "@/utils/createOrderId";
export default function PurchaseButton() {
const [isLoading, setIsLoading] = React.useState(false);
const handlePayment = async () => {
setIsLoading(true);
const price = 10000; // 실제 판매 가격으로 대체
try {
const orderId: string = await createOrderId(price, "INR");
const options = {
key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID,
amount: price * 100,
currency: "INR",
name: "당신의 회사 이름", // 회사 이름을 동적으로 대체
description: "주문 결제", // 주문 설명을 동적으로 대체
order_id: orderId,
handler: async function (response: any) {
try {
const paymentResponse = await axios.post("/api/verifyOrder", {
razorpay_order_id: orderId,
razorpay_payment_id: response.razorpay_payment_id,
razorpay_signature: response.razorpay_signature,
});
alert("결제 성공!");
console.log(paymentResponse.data);
} catch (error) {
alert("결제 검증 실패. 지원팀에 문의하세요.");
console.error(error);
}
},
prefill: {
name: "사용자 이름", // 사용자 데이터를 동적으로 대체
email: "사용자 이메일", // 사용자 데이터를 동적으로 대체
},
theme: {
color: "#3399cc",
},
};
const razorpay = new (window as any).Razorpay(options);
razorpay.on("payment.failed", function (response: any) {
alert("결제 실패");
console.error(response.error);
});
razorpay.open();
} catch (error) {
alert("결제 실패. 다시 시도하세요.");
console.error(error);
} finally {
setIsLoading(false);
}
};
return (
<>
<button
className="bg-emerald-700 text-white font-semibold px-4 py-2 rounded-xl hover:bg-emerald-600 transition-all duration-300 hover:shadow-lg hover:scale-105"
onClick={handlePayment}
disabled={isLoading}
>
{isLoading ? "처리 중..." : "구매하기"}
</button>
<Script
id="razorpay-checkout-js"
src="https://checkout.razorpay.com/v1/checkout.js"
/>
</>
);
}
결제 검증하기
추가적인 보안을 위해 결제 서명을 서버 측에서 검증하세요. 새로운 API 경로 /api/verifyOrder/route.ts를 만듭니다:
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
export interface VerifyBody {
razorpay_order_id: string;
razorpay_payment_id: string;
razorpay_signature: string
};
export async function POST(request: NextRequest) {
try {
const { razorpay_order_id, razorpay_payment_id, razorpay_signature }: VerifyBody = await request.json();
if (!razorpay_order_id || !razorpay_payment_id || !razorpay_signature) {
return NextResponse.json({ error: "필수 파라미터가 누락되었습니다.", success: false }, { status: 400 });
}
const secret = process.env.RAZORPAY_KEY_SECRET as string;
if (!secret) { return NextResponse.json({ error: "Razorpay secret가 없습니다." }, { status: 400 }); }
const HMAC = crypto.createHmac("sha256", secret);
HMAC.update(`${razorpay_order_id}|${razorpay_payment_id}`);
const generatedSignature = HMAC.digest("hex");
if (generatedSignature === razorpay_signature) {
return NextResponse.json({ message: "결제가 성공적으로 검증되었습니다.", success: true });
} else {
return NextResponse.json({ error: "잘못된 서명입니다.", success: false }, { status: 400 });
}
} catch (error) {
return NextResponse.json({ error: "오류가 발생했습니다.", success: false }, { status: 500 });
}
}
참고 자료
Razorpay 통합하기에 대한 GitHub 예제 저장소