From de941442ae10b52b5f4d52cb2d9d724a07b39723 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 22 Mar 2026 17:33:43 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20dev/prod=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=E2=80=94=20Google=20OAuth=20=EB=A6=AC?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20+=20TossPayments=20?= =?UTF-8?q?=ED=82=A4=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Google OAuth] - login/page.tsx: NODE_ENV=development일 때 NEXT_PUBLIC_SITE_URL 무시하고 window.location.origin(localhost) 사용 - auth/callback/route.ts: dev에서는 항상 request origin 사용하도록 수정 (이전: siteUrl이 없을 때만 origin 사용 → dev이면 무조건 origin) [TossPayments] - confirm/route.ts: 실수로 dev에서 live 키 사용 시 console.warn 추가 - PaymentButton.tsx: NEXT_PUBLIC_TOSS_CLIENT_KEY가 test_ck_* 이면 버튼 우상단에 TEST 배지 표시 (dev 확인용) [환경변수 구조] - dev (.env.local): test_ck_*, test_sk_* → 테스트 결제 (실청구 없음) - prod (Vercel ENV): live_ck_*, live_sk_* → 실결제 - 코드 변경 없이 같은 변수명으로 환경별 키만 다르게 설정 Co-Authored-By: Claude Sonnet 4.6 --- app/api/payment/confirm/route.ts | 7 +++++++ app/auth/callback/route.ts | 14 +++++++------- app/components/PaymentButton.tsx | 32 +++++++++++++++++++++++++------- app/login/page.tsx | 8 ++++++-- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/app/api/payment/confirm/route.ts b/app/api/payment/confirm/route.ts index d0b5dcf..bcbe2b8 100644 --- a/app/api/payment/confirm/route.ts +++ b/app/api/payment/confirm/route.ts @@ -28,7 +28,14 @@ export async function POST(request: NextRequest) { } // 2. 토스페이먼츠 서버 승인 + // dev: TOSS_SECRET_KEY=test_sk_* (테스트 결제) + // prod: TOSS_SECRET_KEY=live_sk_* (실결제) — Vercel 환경변수에 설정 const secretKey = process.env.TOSS_SECRET_KEY!; + const isTestKey = secretKey.startsWith('test_'); + if (!isTestKey && process.env.NODE_ENV === 'development') { + // 실수로 live 키를 dev에서 쓰는 것 방지 + console.warn('[Payment] WARNING: live Toss key detected in development!'); + } const encoded = Buffer.from(`${secretKey}:`).toString('base64'); const tossRes = await fetch('https://api.tosspayments.com/v1/payments/confirm', { diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts index 718a862..1d8195b 100644 --- a/app/auth/callback/route.ts +++ b/app/auth/callback/route.ts @@ -6,15 +6,15 @@ export async function GET(request: Request) { const code = searchParams.get('code'); const next = searchParams.get('next') ?? '/mypage'; - // 프로덕션 기준 URL 결정 - // 우선순위: NEXT_PUBLIC_SITE_URL > x-forwarded-host > origin + // 리다이렉트 기준 URL 결정 + // - dev: 항상 현재 request의 origin (localhost) → NEXT_PUBLIC_SITE_URL 무시 + // - prod: NEXT_PUBLIC_SITE_URL > x-forwarded-host (Vercel) > origin const siteUrl = process.env.NEXT_PUBLIC_SITE_URL; const forwardedHost = request.headers.get('x-forwarded-host'); - const baseUrl = - siteUrl ?? - (process.env.NODE_ENV !== 'development' && forwardedHost - ? `https://${forwardedHost}` - : origin); + const isDev = process.env.NODE_ENV === 'development'; + const baseUrl = isDev + ? origin + : (siteUrl ?? (forwardedHost ? `https://${forwardedHost}` : origin)); if (code) { const supabase = await createClient(); diff --git a/app/components/PaymentButton.tsx b/app/components/PaymentButton.tsx index ad7df11..e447a57 100644 --- a/app/components/PaymentButton.tsx +++ b/app/components/PaymentButton.tsx @@ -47,6 +47,8 @@ export default function PaymentButton({ productId, className, children, returnUr if (orderError) throw new Error('주문 생성 실패: ' + orderError.message); // 4. 토스페이먼츠 결제창 호출 + // dev: NEXT_PUBLIC_TOSS_CLIENT_KEY=test_ck_* → 테스트 결제 (실제 청구 없음) + // prod: NEXT_PUBLIC_TOSS_CLIENT_KEY=live_ck_* → 실결제 (Vercel 환경변수에 설정) const { loadTossPayments } = await import('@tosspayments/tosspayments-sdk'); const clientKey = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY!; const tossPayments = await loadTossPayments(clientKey); @@ -81,13 +83,29 @@ export default function PaymentButton({ productId, className, children, returnUr if (!product) return null; + const isTestMode = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY?.startsWith('test_'); + return ( - +
+ + {/* dev/test 환경에서만 표시되는 배지 — 실수로 실결제 누르는 것 방지 */} + {isTestMode && ( + + TEST + + )} +
); } diff --git a/app/login/page.tsx b/app/login/page.tsx index 477c90c..c210a2f 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -60,8 +60,12 @@ function LoginForm() { }; const handleGoogleLogin = async () => { - // NEXT_PUBLIC_SITE_URL 이 설정되어 있으면 우선 사용 (localhost 리다이렉트 방지) - const base = process.env.NEXT_PUBLIC_SITE_URL ?? window.location.origin; + // dev 환경에서는 NEXT_PUBLIC_SITE_URL을 무시하고 현재 브라우저 origin 사용 + // (NEXT_PUBLIC_SITE_URL이 .env.local에 있어도 localhost로 콜백 돌아옴) + const base = + process.env.NODE_ENV === 'development' + ? window.location.origin + : (process.env.NEXT_PUBLIC_SITE_URL ?? window.location.origin); const { error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${base}/auth/callback` },