# v4: Stripe SDK 연동 ## 개요 이번 단계에서는 Stripe SDK를 프로젝트에 연동하고 실제로 API가 정상적으로 작동하는지 테스트합니다. ## Stripe 클라이언트 생성 ### src/stripe/client.ts Stripe 클라이언트를 초기화하는 파일을 생성합니다: ```typescript import Stripe from 'stripe'; import { env } from '../config/env.js'; /** * Stripe 클라이언트 초기화 * * @see https://stripe.com/docs/api */ export const stripe = new Stripe(env.stripeSecretKey!, { apiVersion: '2024-11-20.acacia', // 최신 API 버전 사용 typescript: true, // TypeScript 지원 활성화 }); /** * Stripe 연결 테스트 * 계정 정보를 조회하여 API 키가 유효한지 확인 */ export async function testStripeConnection(): Promise { try { const account = await stripe.accounts.retrieve(); console.log('✅ Stripe 연결 성공!'); console.log(`Account ID: ${account.id}`); console.log(`Email: ${account.email || 'N/A'}`); console.log(`Country: ${account.country}`); console.log(`Charges Enabled: ${account.charges_enabled}`); return true; } catch (error) { console.error('❌ Stripe 연결 실패:', error); if (error instanceof Stripe.errors.StripeAuthenticationError) { console.error('API 키가 유효하지 않습니다. .env 파일을 확인해주세요.'); } return false; } } ``` ### 주요 포인트 #### 1. API 버전 지정 ```typescript apiVersion: '2024-11-20.acacia' ``` Stripe는 정기적으로 API를 업데이트하므로 버전을 명시하는 것이 좋습니다. 최신 버전은 [Stripe API Changelog](https://stripe.com/docs/upgrades)에서 확인할 수 있습니다. #### 2. TypeScript 지원 ```typescript typescript: true ``` 이 옵션을 활성화하면 Stripe SDK가 더 나은 타입 추론을 제공합니다. #### 3. 에러 처리 Stripe는 다양한 에러 타입을 제공합니다: - `StripeAuthenticationError`: API 키 인증 실패 - `StripePermissionError`: 권한 부족 - `StripeRateLimitError`: 요청 한도 초과 - `StripeConnectionError`: 네트워크 연결 실패 - `StripeAPIError`: 일반적인 API 에러 ## 연결 테스트 스크립트 ### src/test-stripe.ts Stripe 연결을 테스트하는 간단한 스크립트를 작성합니다: ```typescript import { testStripeConnection, stripe } from './stripe/client.js'; async function main() { console.log('🔄 Stripe API 연결 테스트 중...\n'); const isConnected = await testStripeConnection(); if (!isConnected) { process.exit(1); } console.log('\n📊 추가 정보 조회 중...\n'); // Balance 조회 (Test Mode에서는 0) const balance = await stripe.balance.retrieve(); console.log('Balance:', { available: balance.available, pending: balance.pending, }); console.log('\n✨ 모든 테스트 완료!'); } main().catch(console.error); ``` ### package.json에 스크립트 추가 ```json { "scripts": { "test:stripe": "tsx src/test-stripe.ts" } } ``` ### 실행 ```bash npm run test:stripe ``` ### 예상 출력 ``` 🔄 Stripe API 연결 테스트 중... ✅ Stripe 연결 성공! Account ID: acct_1Abc...xyz Email: test@example.com Country: US Charges Enabled: true 📊 추가 정보 조회 중... Balance: { available: [ { amount: 0, currency: 'usd', source_types: [Object] } ], pending: [ { amount: 0, currency: 'usd', source_types: [Object] } ] } ✨ 모든 테스트 완료! ``` ## Stripe API 주요 개념 ### 1. Payment Intent Payment Intent는 Stripe의 핵심 결제 객체입니다. ```typescript const paymentIntent = await stripe.paymentIntents.create({ amount: 2000, // 금액 (센트 단위, $20.00) currency: 'usd', // 통화 description: 'Test payment', payment_method_types: ['card'], }); ``` ### 주요 속성 - `amount`: 금액 (센트/최소 단위) - `currency`: 통화 코드 (usd, krw, jpy 등) - `status`: 상태 - `requires_payment_method`: 결제 수단 필요 - `requires_confirmation`: 확인 필요 - `processing`: 처리 중 - `succeeded`: 성공 - `canceled`: 취소됨 ### 2. Customer 고객 정보를 저장하고 관리합니다. ```typescript const customer = await stripe.customers.create({ email: 'customer@example.com', name: 'John Doe', description: 'Test customer', }); ``` ### 3. Charge 실제 결제 기록입니다. Payment Intent가 성공하면 자동으로 생성됩니다. ```typescript // Payment Intent로부터 자동 생성됨 // 직접 생성보다는 Payment Intent 사용 권장 ``` ## TypeScript 타입 활용 Stripe SDK는 훌륭한 TypeScript 타입을 제공합니다. ### 예시 1: Payment Intent 타입 ```typescript import Stripe from 'stripe'; // 타입 안전한 함수 async function getPaymentIntent(id: string): Promise { const paymentIntent = await stripe.paymentIntents.retrieve(id); return paymentIntent; } // 사용 const payment = await getPaymentIntent('pi_abc123'); console.log(payment.amount); // ✅ 타입 추론 console.log(payment.invalid); // ❌ TypeScript 에러 ``` ### 예시 2: 커스텀 타입 정의 `src/types/index.ts`에 추가: ```typescript import Stripe from 'stripe'; /** * Payment Intent 생성 파라미터 */ export interface CreatePaymentParams { amount: number; currency: string; description: string; customerEmail?: string; } /** * 간소화된 결제 정보 */ export interface SimplePayment { id: string; amount: number; currency: string; status: Stripe.PaymentIntent.Status; created: Date; description: string | null; } /** * Stripe Payment Intent를 Simple Payment로 변환 */ export function toSimplePayment(pi: Stripe.PaymentIntent): SimplePayment { return { id: pi.id, amount: pi.amount, currency: pi.currency, status: pi.status, created: new Date(pi.created * 1000), description: pi.description, }; } ``` ## 에러 처리 유틸리티 ### src/utils/stripe-error-handler.ts ```typescript import Stripe from 'stripe'; /** * Stripe 에러를 사용자 친화적인 메시지로 변환 */ export function handleStripeError(error: unknown): string { if (error instanceof Stripe.errors.StripeError) { switch (error.type) { case 'StripeAuthenticationError': return 'API 키 인증에 실패했습니다. 환경 변수를 확인해주세요.'; case 'StripePermissionError': return '권한이 부족합니다. API 키 권한을 확인해주세요.'; case 'StripeRateLimitError': return 'API 요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요.'; case 'StripeConnectionError': return 'Stripe 서버에 연결할 수 없습니다. 네트워크를 확인해주세요.'; case 'StripeCardError': return `카드 에러: ${error.message}`; case 'StripeInvalidRequestError': return `잘못된 요청: ${error.message}`; default: return `Stripe 에러: ${error.message}`; } } return '알 수 없는 에러가 발생했습니다.'; } /** * 에러 로깅 */ export function logStripeError(error: unknown, context?: string): void { console.error('\n❌ Stripe 에러 발생'); if (context) { console.error(`Context: ${context}`); } if (error instanceof Stripe.errors.StripeError) { console.error(`Type: ${error.type}`); console.error(`Message: ${error.message}`); console.error(`Code: ${error.code || 'N/A'}`); console.error(`Status: ${error.statusCode || 'N/A'}`); if (error.raw) { console.error('Raw Error:', JSON.stringify(error.raw, null, 2)); } } else { console.error(error); } } ``` ### 사용 예시 ```typescript import { handleStripeError, logStripeError } from './utils/stripe-error-handler.js'; try { const payment = await stripe.paymentIntents.create({ /* ... */ }); } catch (error) { logStripeError(error, 'Payment Intent 생성 중'); const message = handleStripeError(error); console.error(message); } ``` ## Webhook Secret (선택사항) 실제 프로덕션 환경에서는 Webhook을 사용하여 이벤트를 수신합니다. ### .env에 추가 ```bash STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret ``` ### src/config/env.ts 수정 ```typescript export const env = { stripeSecretKey: process.env.STRIPE_SECRET_KEY, stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET, // 추가 openaiApiKey: process.env.OPENAI_API_KEY, nodeEnv: process.env.NODE_ENV || 'development', } as const; ``` ### Webhook 핸들러 예시 (이번 프로젝트에서는 사용 안 함) ```typescript import { stripe } from './stripe/client.js'; import { env } from './config/env.js'; export function handleWebhook(body: string, signature: string) { const event = stripe.webhooks.constructEvent( body, signature, env.stripeWebhookSecret! ); switch (event.type) { case 'payment_intent.succeeded': console.log('결제 성공:', event.data.object); break; case 'payment_intent.payment_failed': console.log('결제 실패:', event.data.object); break; } } ``` ## 디버깅 팁 ### 1. Stripe CLI 설치 (선택사항) ```bash # macOS brew install stripe/stripe-cli/stripe # Windows (Scoop) scoop install stripe # 로그인 stripe login ``` ### 2. 실시간 로그 확인 ```bash stripe logs tail ``` ### 3. Dashboard에서 로그 확인 https://dashboard.stripe.com/test/logs 모든 API 요청과 응답을 확인할 수 있습니다. ## 체크리스트 v4를 완료하기 전에 다음을 확인하세요: - [ ] `src/stripe/client.ts` 작성 - [ ] `src/test-stripe.ts` 작성 - [ ] `src/utils/stripe-error-handler.ts` 작성 - [ ] `.env` 파일에 `STRIPE_SECRET_KEY` 설정 - [ ] `npm run test:stripe` 실행 성공 - [ ] Account 정보 정상 출력 확인 - [ ] TypeScript 타입 정의 이해 ## 트러블슈팅 ### 1. "Invalid API Key" 에러 **원인**: API 키가 잘못되었거나 `.env` 파일이 로드되지 않음 **해결**: - `.env` 파일 존재 확인 - `STRIPE_SECRET_KEY`가 `sk_test_`로 시작하는지 확인 - `dotenv/config`가 제대로 import되었는지 확인 ### 2. "Module not found" 에러 **원인**: ESM 모듈 설정 문제 **해결**: - import 경로에 `.js` 확장자 포함 확인 - `package.json`에 `"type": "module"` 설정 확인 - `tsconfig.json`에 `"module": "NodeNext"` 설정 확인 ### 3. TypeScript 타입 에러 **원인**: Stripe 타입 정의 문제 **해결**: ```bash npm install --save-dev @types/node npm install stripe@latest ``` ## 다음 단계 v5에서는 랜덤 결제 데이터를 생성하는 로직을 작성합니다. 준비할 것: - Stripe 연결 테스트 성공 확인 - Payment Intent 개념 이해 --- **작성일**: 2025-11-28 **상태**: ✅ 완료 **다음**: v5 - 랜덤 결제 데이터 생성