# v5: 랜덤 결제 데이터 생성 ## 개요 이번 단계에서는 테스트용 결제 데이터를 자동으로 생성하는 로직을 작성합니다. 다양한 금액, 상품, 고객 정보를 랜덤으로 생성하여 현실적인 테스트 환경을 만듭니다. ## 랜덤 데이터 생성기 ### src/utils/random-generator.ts ```typescript /** * 랜덤 숫자 생성 (min ~ max 사이) */ export function randomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } /** * 배열에서 랜덤 요소 선택 */ export function randomChoice(array: T[]): T { return array[randomInt(0, array.length - 1)]; } /** * 랜덤 금액 생성 (센트 단위) * @param min - 최소 금액 (달러) * @param max - 최대 금액 (달러) */ export function randomAmount(min: number = 10, max: number = 1000): number { const dollars = randomInt(min, max); return dollars * 100; // 센트로 변환 } /** * 상품 카테고리 */ const PRODUCT_CATEGORIES = { electronics: [ 'Wireless Headphones', 'Smart Watch', 'Bluetooth Speaker', 'USB-C Cable', 'Phone Case', 'Laptop Stand', 'Webcam', 'Mechanical Keyboard', ], clothing: [ 'Cotton T-Shirt', 'Denim Jeans', 'Running Shoes', 'Winter Jacket', 'Baseball Cap', 'Leather Belt', 'Wool Sweater', ], books: [ 'TypeScript Handbook', 'Clean Code', 'Design Patterns', 'The Pragmatic Programmer', 'Refactoring', 'Domain-Driven Design', ], food: [ 'Organic Coffee Beans', 'Premium Tea Set', 'Dark Chocolate Box', 'Artisan Honey', 'Olive Oil', 'Spice Collection', ], home: [ 'Desk Lamp', 'Plant Pot', 'Wall Clock', 'Picture Frame', 'Candle Set', 'Throw Pillow', ], }; /** * 랜덤 상품명 생성 */ export function randomProduct(): { name: string; category: string } { const categories = Object.keys(PRODUCT_CATEGORIES) as Array< keyof typeof PRODUCT_CATEGORIES >; const category = randomChoice(categories); const name = randomChoice(PRODUCT_CATEGORIES[category]); return { name, category }; } /** * 한국 이름 생성 */ const KOREAN_SURNAMES = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임']; const KOREAN_GIVEN_NAMES = [ '민준', '서연', '하준', '서윤', '도윤', '지우', '예준', '수아', '시우', '지안', '주원', '하은', '유준', '소율', '건우', '채원', ]; /** * 영어 이름 생성 */ const ENGLISH_FIRST_NAMES = [ 'James', 'John', 'Michael', 'David', 'Mary', 'Sarah', 'Emma', 'Emily', 'Daniel', 'Matthew', 'Jennifer', 'Lisa', 'William', 'Robert', 'Linda', ]; const ENGLISH_LAST_NAMES = [ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', 'Anderson', 'Taylor', 'Thomas', ]; /** * 랜덤 고객 정보 생성 */ export function randomCustomer(): { name: string; email: string } { const useKoreanName = Math.random() > 0.5; let name: string; let emailPrefix: string; if (useKoreanName) { const surname = randomChoice(KOREAN_SURNAMES); const givenName = randomChoice(KOREAN_GIVEN_NAMES); name = `${surname}${givenName}`; // 한글 이름을 로마자로 변환 (간단하게 처리) emailPrefix = `${surname}${givenName}`.toLowerCase(); } else { const firstName = randomChoice(ENGLISH_FIRST_NAMES); const lastName = randomChoice(ENGLISH_LAST_NAMES); name = `${firstName} ${lastName}`; emailPrefix = `${firstName.toLowerCase()}.${lastName.toLowerCase()}`; } const domains = ['gmail.com', 'yahoo.com', 'outlook.com', 'example.com']; const domain = randomChoice(domains); const email = `${emailPrefix}${randomInt(1, 999)}@${domain}`; return { name, email }; } /** * 랜덤 날짜/시간 생성 (오늘) */ export function randomTimeToday(): Date { const now = new Date(); const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const endOfDay = new Date( now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59 ); const randomTime = startOfDay.getTime() + Math.random() * (endOfDay.getTime() - startOfDay.getTime()); return new Date(randomTime); } ``` ## 결제 데이터 생성 로직 ### src/stripe/generate-data.ts ```typescript import { stripe } from './client.js'; import { randomAmount, randomProduct, randomCustomer, } from '../utils/random-generator.js'; import { handleStripeError, logStripeError } from '../utils/stripe-error-handler.js'; /** * 단일 결제 생성 */ export async function createRandomPayment() { try { const product = randomProduct(); const customer = randomCustomer(); const amount = randomAmount(10, 1000); // $10 ~ $1000 // 1. Customer 생성 (선택사항, 하지만 분석에 유용) const stripeCustomer = await stripe.customers.create({ name: customer.name, email: customer.email, description: `Test customer for ${product.name}`, }); // 2. Payment Intent 생성 const paymentIntent = await stripe.paymentIntents.create({ amount, currency: 'usd', customer: stripeCustomer.id, description: `${product.name} (${product.category})`, metadata: { category: product.category, product_name: product.name, }, // Test Mode에서는 자동으로 성공 처리 confirm: true, payment_method: 'pm_card_visa', // Test Mode 전용 카드 return_url: 'https://example.com/return', // 필수 파라미터 }); console.log(`✅ 결제 생성 완료: ${paymentIntent.id}`); console.log(` 고객: ${customer.name} (${customer.email})`); console.log(` 상품: ${product.name}`); console.log(` 금액: $${(amount / 100).toFixed(2)}`); console.log(` 상태: ${paymentIntent.status}`); return paymentIntent; } catch (error) { logStripeError(error, '결제 생성 중'); throw error; } } /** * 여러 건의 결제 생성 */ export async function generateMultiplePayments(count: number = 15) { console.log(`\n🔄 ${count}건의 테스트 결제를 생성합니다...\n`); const results = { success: 0, failed: 0, total: count, }; for (let i = 1; i <= count; i++) { try { console.log(`\n[${i}/${count}] 결제 생성 중...`); await createRandomPayment(); results.success++; // API Rate Limit 방지를 위한 딜레이 if (i < count) { await new Promise((resolve) => setTimeout(resolve, 500)); } } catch (error) { results.failed++; console.error(`❌ [${i}/${count}] 결제 생성 실패`); console.error(handleStripeError(error)); } } console.log('\n📊 결제 생성 결과:'); console.log(` 성공: ${results.success}건`); console.log(` 실패: ${results.failed}건`); console.log(` 합계: ${results.total}건`); return results; } /** * 결제 통계 조회 */ export async function getPaymentStats() { // 오늘 자정부터의 timestamp const startOfDay = new Date(); startOfDay.setHours(0, 0, 0, 0); const timestamp = Math.floor(startOfDay.getTime() / 1000); const paymentIntents = await stripe.paymentIntents.list({ created: { gte: timestamp }, limit: 100, }); const stats = { totalCount: paymentIntents.data.length, totalAmount: 0, succeeded: 0, failed: 0, byCategory: {} as Record, }; paymentIntents.data.forEach((pi) => { if (pi.status === 'succeeded') { stats.succeeded++; stats.totalAmount += pi.amount; const category = pi.metadata?.category || 'unknown'; stats.byCategory[category] = (stats.byCategory[category] || 0) + 1; } else { stats.failed++; } }); return stats; } ``` ## 결제 생성 스크립트 ### src/generate-payments.ts ```typescript import { generateMultiplePayments, getPaymentStats } from './stripe/generate-data.js'; import { testStripeConnection } from './stripe/client.js'; async function main() { console.log('💳 Stripe 테스트 결제 생성기\n'); // 1. Stripe 연결 확인 console.log('🔄 Stripe 연결 확인 중...'); const isConnected = await testStripeConnection(); if (!isConnected) { console.error('\n❌ Stripe 연결 실패. 프로그램을 종료합니다.'); process.exit(1); } // 2. 사용자에게 생성할 결제 건수 확인 const count = process.argv[2] ? parseInt(process.argv[2]) : 15; if (isNaN(count) || count < 1 || count > 100) { console.error('❌ 유효하지 않은 건수입니다. 1~100 사이의 숫자를 입력하세요.'); process.exit(1); } // 3. 결제 생성 await generateMultiplePayments(count); // 4. 통계 확인 console.log('\n📈 오늘의 결제 통계 조회 중...\n'); const stats = await getPaymentStats(); console.log('📊 통계:'); console.log(` 총 결제: ${stats.totalCount}건`); console.log(` 성공: ${stats.succeeded}건`); console.log(` 실패: ${stats.failed}건`); console.log(` 총 금액: $${(stats.totalAmount / 100).toFixed(2)}`); if (stats.totalCount > 0) { console.log( ` 평균 금액: $${(stats.totalAmount / stats.succeeded / 100).toFixed(2)}` ); } console.log('\n📦 카테고리별:'); Object.entries(stats.byCategory) .sort((a, b) => b[1] - a[1]) .forEach(([category, count]) => { console.log(` ${category}: ${count}건`); }); console.log('\n✨ 완료!'); } main().catch((error) => { console.error('\n💥 예상치 못한 에러 발생:'); console.error(error); process.exit(1); }); ``` ### package.json에 스크립트 추가 ```json { "scripts": { "generate": "tsx src/generate-payments.ts", "generate:small": "tsx src/generate-payments.ts 5", "generate:large": "tsx src/generate-payments.ts 50" } } ``` ## 실행 및 테스트 ### 기본 실행 (15건) ```bash npm run generate ``` ### 커스텀 건수 ```bash npm run generate 20 # 또는 npm run generate:small # 5건 npm run generate:large # 50건 ``` ### 예상 출력 ``` 💳 Stripe 테스트 결제 생성기 🔄 Stripe 연결 확인 중... ✅ Stripe 연결 성공! Account ID: acct_1Abc...xyz Email: test@example.com Country: US Charges Enabled: true 🔄 15건의 테스트 결제를 생성합니다... [1/15] 결제 생성 중... ✅ 결제 생성 완료: pi_3Abc...xyz 고객: 김민준 (kimminju123@gmail.com) 상품: Wireless Headphones 금액: $89.00 상태: succeeded [2/15] 결제 생성 중... ✅ 결제 생성 완료: pi_3Def...xyz 고객: Sarah Johnson (sarah.johnson456@yahoo.com) 상품: Clean Code 금액: $45.00 상태: succeeded ... 📊 결제 생성 결과: 성공: 15건 실패: 0건 합계: 15건 📈 오늘의 결제 통계 조회 중... 📊 통계: 총 결제: 15건 성공: 15건 실패: 0건 총 금액: $8,432.00 평균 금액: $562.13 📦 카테고리별: electronics: 6건 books: 4건 clothing: 3건 home: 2건 ✨ 완료! ``` ## Stripe Dashboard에서 확인 1. https://dashboard.stripe.com/test/payments 접속 2. 생성된 결제 확인 3. 각 결제 클릭하여 상세 정보 확인 - Amount - Customer - Description - Metadata (category, product_name) ## 고급 기능 ### 시간대별 분포 조정 더 현실적인 데이터를 위해 특정 시간대에 결제가 집중되도록 할 수 있습니다: ```typescript export function randomTimeWithDistribution(): Date { const hour = Math.random(); // 오후 시간대(14-18시)에 가중치 부여 let selectedHour: number; if (hour < 0.4) { // 40% - 오후 시간대 selectedHour = randomInt(14, 18); } else if (hour < 0.7) { // 30% - 오전 시간대 selectedHour = randomInt(9, 12); } else { // 30% - 저녁/밤 시간대 selectedHour = randomInt(19, 23); } const now = new Date(); const date = new Date( now.getFullYear(), now.getMonth(), now.getDate(), selectedHour, randomInt(0, 59), randomInt(0, 59) ); return date; } ``` ### 결제 실패 시뮬레이션 일부 결제를 의도적으로 실패시킬 수도 있습니다: ```typescript // 10% 확률로 실패하는 카드 사용 const shouldFail = Math.random() < 0.1; const paymentMethod = shouldFail ? 'pm_card_chargeDeclined' : 'pm_card_visa'; ``` ### 반복 고객 시뮬레이션 같은 고객이 여러 번 구매하는 시나리오: ```typescript const REGULAR_CUSTOMERS = [ { name: 'John Doe', email: 'john.doe@example.com' }, { name: '김철수', email: 'kim.cs@gmail.com' }, ]; export function randomOrRegularCustomer() { // 30% 확률로 단골 고객 선택 if (Math.random() < 0.3) { return randomChoice(REGULAR_CUSTOMERS); } return randomCustomer(); } ``` ## 체크리스트 v5를 완료하기 전에 다음을 확인하세요: - [ ] `src/utils/random-generator.ts` 작성 - [ ] `src/stripe/generate-data.ts` 작성 - [ ] `src/generate-payments.ts` 작성 - [ ] package.json 스크립트 추가 - [ ] `npm run generate` 실행 성공 - [ ] Stripe Dashboard에서 결제 확인 - [ ] 다양한 카테고리의 상품 생성 확인 - [ ] 통계가 정확하게 표시되는지 확인 ## 트러블슈팅 ### 1. "pm_card_visa not found" 에러 **원인**: Test Mode가 아닌 Live Mode 사용 **해결**: Dashboard에서 Test Mode 토글 확인 ### 2. Rate Limit 에러 **원인**: 너무 많은 요청을 빠르게 전송 **해결**: - 딜레이 시간 증가 (500ms → 1000ms) - 한 번에 생성하는 건수 줄이기 ### 3. 결제가 생성되지 않음 **원인**: confirm: true 누락 또는 payment_method 미지정 **해결**: 코드 확인 및 필수 파라미터 추가 ## 다음 단계 v6에서는 생성된 결제 데이터를 조회하고 집계하는 로직을 작성합니다. 준비할 것: - 여러 건의 테스트 결제 생성 완료 - Stripe Dashboard에서 데이터 확인 --- **작성일**: 2025-11-28 **상태**: ✅ 완료 **다음**: v6 - Stripe 데이터 조회