16화 | [AI와 1인기업] 무인 디지털 숍 구축: Next.js 스트라이프/토스 결제 웹훅 연동을 통한 라이선스 키 자동 발급 파이프라인 구현
무인 디지털 숍 구축: Next.js 스트라이프/토스 결제 웹훅 연동을 통한 라이선스 키 자동 발급 파이프라인 구현 (16화)
안녕하세요! AI 크루들과 무자본 IT 1인 기업을 꾸려가고 있는 크리에이티브 디렉터 DAVID입니다.
지난 15화에서는 유료로 개발한 자동화 프로그램이나 알고리즘 트레이딩 봇(EA)의 무단 불법 유출을 철통 방어하기 위해 만료일과 허용 기기 대수를 원격 제어하는 '데이터베이스 연동형 라이선스 키 인증 API'를 구축해 보았습니다. 금고의 철저한 잠금장치가 세워졌으니, 이제 유저가 결제 시스템을 통해 돈을 지불했을 때 이 라이선스 키가 수동 개입 없이 '100% 무인 자동으로 발급 및 발송'되는 자동 판매기 깔때기(퍼널)를 완성할 때입니다.
온라인 쇼핑몰 구축 시 단순히 결제창 띄우기 단계에만 집중하다 보면, 결제가 끝난 직후 브라우저 창을 닫아버려 라이선스 키가 배송되지 않는 결제 누락 장애(Fulfillment Failure)나 프론트엔드 가격 정보를 해킹 조작하여 1,000불짜리 제품을 1불에 강제 승인받는 결제 금액 위변조 공격에 맥없이 뚫리게 됩니다. 오늘 100% 신뢰할 수 있는 '비동기 웹훅(Webhook) 연동'과 '2차 결제 검증 알고리즘'이 더해진 무인 숍 구축의 정석을 공개합니다!
1. 왜 단순 브라우저 리다이렉션이 아니라 '비동기 웹훅(Webhook)'이어야 하는가?
일반적인 초보 개발자들이 가장 흔히 범하는 실수가 있습니다. 결제창이 닫히며 돌아오는 리다이렉트 성공 페이지(Success Page)의 프론트엔드 단에서 라이선스 키 발급 API를 호출하게 설계하는 것입니다.
이 구조는 다음과 같은 심각한 인프라 붕괴 시나리오에 취약합니다.
- 네트워크 장애 및 강제 종료: 결제 대행사(PG) 카드 승인은 정상 처리되어 유저 지갑에서 돈은 빠져나갔으나, 성공 페이지로 넘어오는 찰나에 인터넷 연결이 끊기거나 사용자가 브라우저 탭을 닫아버리면 라이선스 키가 데이터베이스에 아예 생성되지 않는 대형 배송 누락 사고가 터집니다.
- 프론트엔드 우회 해킹: 결제를 실제로 하지도 않고, 성공 페이지 URL(
/checkout/success?paymentKey=mock_key) 주소를 브라우저에 임의로 직접 쳐서 진입하여 유료 소프트웨어 라이선스를 공짜로 탈취해 가는 우회 공격에 노출됩니다.
이 문제를 차단하는 정석 해법이 바로 **'비동기 웹훅(Webhook)'**입니다. 결제가 성공하는 순간, 결제 대행사(토스페이먼츠, 스트라이프)의 서버가 우리의 백엔드 API 주소로 직접 결제 성공 데이터 패킷을 쏘아 보내줍니다. 유저가 컴퓨터를 끄든 창을 닫든 상관없이 서버와 서버끼리 직접 통신하므로 결제 무결성이 100% 보장됩니다.
2. 해커들의 우회 결제를 막는 '2차 금액 대조(Price Reconciliation)' 메커니즘
프론트엔드 브라우저에 노출되는 JavaScript 코드의 결제 요청 금액(Amount)은 개발자 도구를 켜서 임의로 값을 변조할 수 있습니다. 1,000,000원짜리 라이선스를 요청 금액 변조를 통해 10원으로 결제해도 PG사 승인은 떨어집니다. 이를 백엔드에서 2차 검증하지 않으면 대형 금전적 손실을 겪게 됩니다.
🛡️ 2차 금액 교차 검증 알고리즘 3단계
- 웹훅 신호 수신: PG사로부터
paymentKey와 승인 금액(amount) 정보가 담긴 웹훅 수신. - 실제 승인 금액 조회 (Reconciliation API): 전달받은 키 값을 들고 백엔드 서버가 PG사 승인 API를 다시 직접 호출하여, 실제 PG사 시스템 데이터베이스에 기록된 승인액을 교차 조회합니다.
- 서버 DB 가격 매핑 대조: 조회된 실 결제 금액이 우리 데이터베이스 상품 정보 테이블에 정의되어 있는 고정된 정가 가격과 단 1원도 틀리지 않고 정확히 일치하는지 비교 연산합니다. 금액이 불일치할 시 즉각 배송을 정지하고 에러 로그를 뿜게 처리합니다.
3. Next.js API Route 기반의 결제 웹훅 및 라이선스 자동 발급 실전 코드
토스페이먼츠(Toss Payments) 가상계좌 및 카드 결제 완료 웹훅을 수신하여 1초 만에 보안 라이선스 키를 자동 발행하는 실전 Next.js Route Handler 백엔드 소스코드입니다.
import { NextResponse } from 'next/server';
import { db } from '@/lib/firebase-admin';
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';
export async function POST(request) {
try {
const body = await request.json();
const { eventType, data } = body; // 토스페이먼츠 웹훅 바디 구조
// 1. 결제 승인 완료 이벤트가 아니면 즉시 리턴
if (eventType !== 'PAYMENT_STATUS_CHANGED' || data.status !== 'DONE') {
return NextResponse.json({ received: true }, { status: 200 });
}
const { paymentKey, orderId, amount, metadata } = data;
const customerEmail = metadata?.email || "customer@example.com";
const productId = metadata?.productId; // 'gold_ea_premium' 등
// 2. [위변조 차단] 우리 DB의 실제 상품 가격 테이블 조회
const productDoc = await db.collection('products').doc(productId).get();
if (!productDoc.exists) {
return NextResponse.json({ error: 'PRODUCT_NOT_FOUND' }, { status: 400 });
}
const officialPrice = productDoc.data().price;
// 3. [2차 교차 검증] 토스 API를 직접 찔러 실제 승인된 금액 대조
const secretKey = Buffer.from(process.env.TOSS_SECRET_KEY + ":").toString('base64');
const tossRes = await axios.get(`https://api.tosspayments.com/v1/payments/${paymentKey}`, {
headers: { Authorization: `Basic ${secretKey}` }
});
const verifiedAmount = tossRes.data.totalAmount;
if (verifiedAmount !== officialPrice || verifiedAmount !== amount) {
// 가격 불일치 시 해킹 시도로 간주 및 경고 알림 전송
send_telegram_notification(f"🚨 [결제 변조 시도 감지] Order ID: {orderId}, 결제금액: {verifiedAmount}원, 실제가격: {officialPrice}원");
return NextResponse.json({ error: 'PRICE_MISMATCH' }, { status: 400 });
}
// 4. [Fulfillment] 임의 패턴 추측이 불가능한 UUID 라이선스 키 생성 및 DB 추가
const newLicenseKey = `AG-KEY-${uuidv4().toUpperCase()}`;
const expiredDate = new Date();
expiredDate.setMonth(expiredDate.getMonth() + 1); // 30일권 기준 설정
await db.collection('licenses').doc(newLicenseKey).set({
licenseKey: newLicenseKey,
status: 'ACTIVE',
maxDevices: 1,
activeDevices: [],
expiredAt: expiredDate.toISOString(),
orderId: orderId,
productId: productId,
createdAt: new Date().toISOString()
});
// 5. 고객 이메일로 라이선스 즉각 자동 발송 API 호출 (Nodemailer 등 연동)
await send_license_email(customerEmail, newLicenseKey, expiredDate.toISOString());
// 6. 관리자 텔레그램 통지 가동
send_telegram_notification(f"🔔 [결제 완료 및 키 교부] 상품: {productId}, 이메일: {customerEmail}, 라이선스 발급 완료!");
return NextResponse.json({ success: true, licenseKey: newLicenseKey }, { status: 200 });
} catch (error) {
return NextResponse.json({ error: 'INTERNAL_SERVER_ERROR' }, { status: 500 });
}
}
4. 16화를 마치며: 24시간 가동되는 완벽한 자판기 퍼널
결제 대행사의 비동기 웹훅과 실시간 위변조 교차 대조 로직이 유기적으로 완성되었습니다. 이제 대장님은 깊은 단잠에 빠져 있는 밤중이라 하더라도, 지구 반대편의 누군가가 카드를 긁으면 시스템이 1초 만에 돈을 받고 보안 라이선스를 스스로 생산해 발송하는 완벽한 **'무인 디지털 유동성 자판기'**를 소유하게 되었습니다.
유료 결제와 자동 배송 금고가 마련되었으나, 라이선스를 구매하지 않은 일반 사용자가 프로그램 실행 파일(.ex5)이나 프리미엄 퀀트 설정값(.set)을 브라우저 주소창에 직접 치고 들어와 무단 다운로드해 가는 경로 탈취 공격을 막아야 합니다.
다음 17화에서는 Next.js의 강력한 미들웨어(Middleware) 기능과 쿠키/세션 인증 토큰 제어 기술을 활용해, 구매가 인증된 유저만 다운로드 경로에 파일 조각 단위로 접근할 수 있게 게이트를 굳게 잠그는 "5부 유료 회원 멤버십 구축: Next.js 미들웨어(Middleware) 기반의 특정 제품 및 다운로드 경로 접근 차단 시스템 설계"를 입체적으로 마스터해 보겠습니다.
자가 작동하는 디지털 쇼핑몰의 무한한 현금 흐름 레버리지를 누려보세요. 다음 편에서 뵙겠습니다! 😊🚀
*글쓴이: AI와 함께 무한한 가치를 만들어가는 Creative Director, DAVID.*
댓글
댓글 쓰기