| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
- Props
- angular
- 데이터베이스 #try #이중
- 0.25px border
- ZOOM
- 1px border
- TS
- npm
- 서버리스 #
- Websocket
- es6
- Strict
- 클론코딩
- 타입스크립트
- 10px
- ES5
- TypeScript
- jwt
- 문서번호
- 0.5px border
- &연산
- 당근마켓
- 0.75px border
- literal
- 전역변수
- github
- 컴포넌튼
- entity
- 으
- font-size
- Today
- Total
복잡한뇌구조마냥
[Next.js] 토스페이먼츠 결제 위젯 연동하기 (+ 사업자번호 없이 ) 본문
프로젝트에서 토스페이먼츠 결제를 붙이면서 정리한 내용입니다.
Next.js(App Router) + 토스 페이먼츠 결제 위젯 SDK 기반으로 구현했습니다.
사업자번호 없이도 테스트 가능한 방법도 함께 정리했습니다.
1. 🧾 들어가며 — 왜 토스 페이먼츠를 사용했나?
프리랜서 매칭 플랫폼을 개발하면서, 의뢰자가 프리랜서에게 비용을 지불하는 결제 기능이 필요했다.
여러 PG를 검토했지만 위젯 방식 + 문서 품질 + 간단한 테스트 환경 때문에 결국 토스 페이먼츠(TossPayments) 를 선택했다.
특히 좋았던 점은:
- 사업자 등록이 없어도 테스트 결제 전 과정이 가능
- React 기반 프로젝트에서 매우 쉽게 적용 가능
- payment-widget-sdk로 UX 좋은 결제 UI를 그대로 가져올 수 있음
- Next.js App Router에서도 문제 없이 동작
2. 🧰 사용한 라이브러리
Next.js 클라이언트 컴포넌트에서 아래 두 패키지를 사용했다.
"@tosspayments/payment-widget-sdk": "^0.12.0",
"@tosspayments/tosspayments-sdk": "^2.4.0"
역할은 다음과 같다:
| payment-widget-sdk | 결제 UI 위젯 렌더링용 SDK |
| tosspayments-sdk | 결제 요청(requestPayment) 처리용 SDK |
3. 🧪 사업자 번호 없이 토스 결제를 테스트하는 방법
토스페이먼츠는 개발자용 테스트 계정만 있으면 누구나 결제 테스트를 진행할 수 있다.
테스트 환경 특징은 다음과 같다:
✔️ 1) 테스트용 key만 있으면 모든 기능 사용 가능
- test_client_api_key
- test_secret_api_key
테스트 키는 절대 돈이 빠져나가지 않는다.
즉, 진짜 카드번호 입력해도 결제가 되지 않는다 ✔️
✔️ 2) 테스트 카드번호 제공
결제 테스트는 카드사별로 제공되는 테스트 카드번호를 사용한다.
모든 승인/실패 상황을 시뮬레이션할 수 있다.
예:
- 성공 케이스
- 잔액 부족
- 한도 초과
- 도난 카드
- OTP 인증 실패
등을 카드 번호만 바꿔가며 재현 가능하다.
✔️ 3) 실제 결제 페이지와 동일한 UI 제공
테스트 환경에서도 실제 서비스와 동일한 결제 페이지가 뜬다.
이 덕분에 UX 테스트도 손쉽게 가능했다.
4. 🚀 Next.js에서 토스 결제 위젯 연동하기
아래는 내가 실제로 프로젝트에 적용했던 구조다.
구성은 크게 3단계:
- 결제 위젯 로드 및 렌더링
- 결제 버튼에서 requestPayment 호출
- 결제 성공/실패 페이지 이동
5. 🧩 Step 1 — 결제 위젯 로딩 컴포넌트 (TossPayments.tsx)
이 컴포넌트는 위젯을 로딩하고, UI를 특정 DOM에 렌더링한다.
- loadTossPayments(clientKey)로 SDK를 로드
- 위젯 인스턴스 생성 후 결제·약관 영역에 렌더링
- 금액 변경 시 위젯도 다시 업데이트
"use client";
import { useEffect } from "react";
import { loadTossPayments } from "@tosspayments/payment-widget-sdk";
import { useTossWidgetStore } from "@/store/payment-store";
const clientKey = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY!;
const customerKey = "ANONYMOUS";
export default function TossPayments({ amount }: { amount: number }) {
const { setWidgets, setReady, widgets } = useTossWidgetStore();
useEffect(() => {
let mounted = true;
(async () => {
const tossPayments = await loadTossPayments(clientKey);
if (!mounted) return;
const widgetsInstance = tossPayments.widgets({ customerKey });
setWidgets(widgetsInstance);
})();
return () => {
mounted = false;
setWidgets(null);
setReady(false);
};
}, [setWidgets, setReady]);
useEffect(() => {
if (!widgets) return;
(async () => {
await widgets.setAmount({ currency: "KRW", value: amount });
await widgets.renderPaymentMethods({
selector: "#payment-method",
variantKey: "DEFAULT",
});
await widgets.renderAgreement({
selector: "#agreement",
variantKey: "AGREEMENT",
});
setReady(true);
})();
}, [widgets, amount, setReady]);
return (
<div>
<div id="payment-method" />
<div id="agreement" />
</div>
);
}
핵심 포인트
- 금액 변경 시 항상 setAmount → 렌더링 순서로 진행해야 한다
- 클라이언트 컴포넌트에서만 실행해야 함 ("use client" 필수)
- useEffect로 위젯 초기화/재렌더링 관리
6. 🧨 Step 2 — 결제 버튼 (TossPaymentsButton.tsx)
사용자가 금액 확인 후 “결제하기”를 누르면 실행된다.
- 서버에 먼저 orderId 생성 요청
- 이후 widgets.requestPayment() 호출
- 결제 성공 시 successUrl로 이동
"use client";
import { useTossWidgetStore } from "@/store/payment-store";
import { crypto } from "crypto";
export default function TossPaymentsButton({ freelancer, qty }) {
const { widgets, ready } = useTossWidgetStore();
const handlePayment = async () => {
if (!widgets || !ready) return;
const orderId = crypto.randomUUID();
await widgets.requestPayment({
method: "CARD",
amount: { currency: "KRW", value: freelancer.salary * qty },
orderId,
orderName: `${freelancer.title} ${qty}건`,
successUrl: `${window.location.origin}/payment/success?orderId=${orderId}`,
failUrl: `${window.location.origin}/payment/fail?orderId=${orderId}`,
});
};
return (
<button disabled={!ready} onClick={handlePayment}>
결제하기
</button>
);
}
핵심 포인트
- widgets.requestPayment()가 실제 결제 창을 띄움
- 반드시 서버에서 같은 orderId로 금액 검증이 필요함
- URL은 반드시 배포 환경 기준으로 작성해야 함
7. 🧾 Step 3 — 금액 계산 + 결제 페이지 구성
수량(qty)에 따라 금액을 계산 → 그 값을 TossPayments에 전달 → 위젯이 해당 금액으로 다시 렌더링됨.
사용자가 UI에서 즉시 변경 사항을 확인할 수 있어 경험이 매우 자연스럽다.
8. ✔️ 실제 서비스에서 유의해야 할 점
1) 프론트 계산 금액을 절대 그대로 신뢰하면 안 됨
결제 검증은 반드시 서버에서 해야함
렌더링 전에 결제 금액을 위젯에 전달해야함.
2) 테스트 키 관리
- 테스트용 키 사용하면 됨.
NEXT_PUBLIC_TOSS_CLIENT_KEY=API 키
NEXT_PUBLIC_TOSS_SECRET_KEY=시크릿 키
3) successUrl에서 서버 검증 로직 수행
- orderId
- amount
- paymentKey
등을 조합해 서버에서 승인 요청 후 DB에 저장
4) 재결제 / 중복 승인 대비 필요
✨ 마무리
Next.js에서 토스 페이먼츠 결제를 연동하는 과정은 생각보다 간단했고,
테스트 환경도 훌륭해서 사업자 등록 전에도 기능 개발·QA를 충분히 할 수 있었던 점이 정말 좋았다.
👉 참고 문서:
🔗 사업자 없이 토스페이먼츠 테스트하는 법
https://docs.tosspayments.com/blog/how-to-test-toss-payments
회원가입, 사업자번호 없이 결제 테스트하기 | 토스페이먼츠 개발자센터
오늘은 계약 전에 토스페이먼츠의 테스트 환경에서 온라인 결제를 연동하고 시뮬레이션하는 방법을 쉽고 간단하게 소개할게요.
docs.tosspayments.com
🔗 결제 위젯 공식 문서
https://docs.tosspayments.com/sdk/v2/js#%EA%B2%B0%EC%A0%9C%EC%9C%84%EC%A0%AF
토스페이먼츠 JavaScript SDK | 토스페이먼츠 개발자센터
토스페이먼츠 JavaScript SDK를 추가하고 메서드를 사용하는 방법을 알아봅니다.
docs.tosspayments.com
'FE > Next.js' 카테고리의 다른 글
| [Next.js] Supabase 연동하기 (0) | 2025.07.14 |
|---|