API 테스트 및 실행 시나리오
이 문서는 결제 서비스의 주요 API 흐름을 테스트하기 위한 시나리오를 정의합니다.
1. 사전 준비
Firebase Emulator 및 서버 실행
- 프로젝트 루트에서 Firebase Emulator를 시작하고, 개발 서버를 실행합니다.
# Firebase Emulator 시작 firebase emulators:start # 개발 서버 실행 npm run dev
2. 시나리오 1: 정상적인 예매 및 결제 성공 흐름
단계 1: 예매 생성 (좌석 잠금 및 결제 의향 생성)
- API:
POST /bookings - 설명: 사용자가 좌석을 선택하여 예매를 요청합니다. 이 과정에서
bookings문서가pending상태로 생성되고,paymentIntents문서가 생성되며,occupiedSeats문서가locked상태로 생성됩니다.
요청 예시 (curl):
curl -X POST http://localhost:3000/bookings \
-H "Content-Type: application/json" \
-d '{
"userId": "user_123",
"performanceId": "perf_001",
"seatIds": ["A1", "A2"],
"paymentMethod": "CREDIT_CARD"
}'
예상 응답:
HTTP 201 Created- 응답 본문에
bookingId와paymentIntentId가 포함됩니다. 이paymentIntentId를 다음 단계에서 사용합니다.
{
"message": "Booking initiated. Please proceed to payment.",
"bookingId": "some-booking-id",
"paymentIntentId": "some-payment-intent-id"
}
단계 2: 결제 실행 (성공)
- API:
POST /payments/execute - 설명: 생성된
paymentIntentId를 사용하여 결제를 실행합니다. CVV 끝자리가0,1,9중 하나이면 결제가 성공합니다.
요청 예시 (curl):
curl -X POST http://localhost:3000/payments/execute \
-H "Content-Type: application/json" \
-d '{
"paymentIntentId": "some-payment-intent-id",
"paymentMethodToken": "tok_visa_creditCard",
"cvv": "120"
}'
예상 결과:
HTTP 200 OK응답 및 결제 성공 메시지 반환.bookings문서의 상태가confirmed로 변경됩니다.occupiedSeats문서의 상태가booked로 변경됩니다.
단계 3: 예매 내역 확인
- API:
GET /bookings/user/:userId - 설명: 결제가 완료된 예매 내역을 확인합니다.
요청 예시 (curl):
curl -X GET http://localhost:3000/bookings/user/user_123
예상 응답:
HTTP 200 OKstatus가confirmed인 예매 내역 배열을 반환합니다.
단계 4: 점유 좌석 상태 확인
- API:
GET /occupiedSeats/:performanceId - 설명: 좌석이
booked상태인지 확인합니다.
요청 예시 (curl):
curl -X GET "http://localhost:3000/occupiedSeats/perf_001?seatIds=A1,A2"
예상 응답:
status가booked인 좌석 정보 배열을 반환합니다.
3. 시나리오 2: 예매 후 결제 실패 흐름
단계 1: 예매 생성
- 시나리오 1의 1단계와 동일하게 진행하여
paymentIntentId를 발급받습니다.
단계 2: 결제 실행 (실패)
- API:
POST /payments/execute - 설명: CVV 끝자리를 실패 조건(
2~8)에 맞춰 결제를 실패시킵니다.
요청 예시 (curl):
curl -X POST http://localhost:3000/payments/execute \
-H "Content-Type: application/json" \
-d '{
"paymentIntentId": "some-payment-intent-id",
"paymentMethodToken": "tok_visa_creditCard",
"cvv": "442"
}'
예상 결과:
HTTP 200 OK응답 및 결제 실패 메시지 반환.bookings문서의 상태가cancelled로 변경됩니다.occupiedSeats문서가 삭제되거나available상태로 변경되어 잠금이 해제됩니다.
4. 시나리오 3: 예매 취소
단계 1: 예매 생성
- 시나리오 1의 1단계와 동일하게 진행하여
bookingId를 발급받습니다.
단계 2: 예매 취소
- API:
DELETE /bookings/user/:userId - 설명: 사용자가 직접 예매를 취소합니다.
pending상태의 예매만 취소할 수 있습니다.
요청 예시 (curl):
curl -X DELETE http://localhost:3000/bookings/user/user_123 \
-H "Content-Type: application/json" \
-d '{
"bookingId": "some-booking-id"
}'
예상 결과:
HTTP 200 OKbookings문서 상태가cancelled로 변경됩니다.occupiedSeats잠금이 해제됩니다.
Firestore 컬렉션 분리 이유
Firestore의 컬렉션을 bookings, events, ledgerEntries, occupiedSeats, paymentIntents로 세분화한 것은 마이크로서비스 아키텍처의 설계 원칙과 데이터의 역할 및 책임을 명확히 분리하기 위함입니다. 각 컬렉션은 독립적인 역할을 수행하여 시스템의 확장성, 안정성, 유지보수성을 높입니다.
2.1 bookings - 예약 정보 관리

- 역할: 사용자의 예약 요청 자체를 기록하고 관리하는 컬렉션입니다.
- 분리 이유:
- 상태 관리의 명확성: 예약의 생명주기(
pending,confirmed,cancelled)를 독립적으로 관리할 수 있습니다. 예를 들어, 사용자가 좌석을 선택했지만 아직 결제하지 않은 상태(pending)를 명확히 표현할 수 있습니다. - 관심사의 분리: 결제, 좌석 점유, 회계 등 다른 도메인과 예약 정보를 분리하여 각 서비스가 자신의 책임에만 집중하도록 합니다.
bookings는 "어떤 사용자가 어떤 공연의 어떤 좌석을 예매하려고 하는가"라는 정보만 기록합니다.
- 상태 관리의 명확성: 예약의 생명주기(
2.2 events - 비동기 이벤트 처리

- 역할: 외부 시스템(Stripe 등)으로부터 들어오는 웹훅 이벤트를 저장하는 '받은 편지함(Inbox)' 역할을 합니다.
- 분리 이유:
- 비동기 처리 및 안정성: 웹훅을 받는 즉시 최소한의 검증만 거쳐
events컬렉션에 저장하고 성공 응답을 보냅니다. 이후 별도의 프로세스가 이벤트를 읽어 실제 비즈니스 로직을 처리합니다. 이 과정을 통해 데이터 유실 없이 안정적으로 이벤트를 처리할 수 있습니다. - 디커플링(Decoupling): 웹훅 수신 로직과 비즈니스 처리 로직을 완전히 분리하여 결제 시스템의 변경과 비즈니스 로직의 복잡성 증가가 서로 영향을 주지 않도록 합니다.
- 비동기 처리 및 안정성: 웹훅을 받는 즉시 최소한의 검증만 거쳐
2.3 ledgerEntries - 회계 원장 기록

- 역할: 시스템 내에서 발생하는 모든 금전적 거래를 복식 부기(Double-Entry Bookkeeping) 원칙에 따라 기록하는 회계 원장입니다.
- 분리 이유:
- 금융 데이터의 무결성 및 감사 추적: 모든 거래는 차변(Debit)과 대변(Credit)의 한 쌍으로 기록되어야 하며, 합계는 항상 0이 되어야 합니다. 이를 통해 시스템의 돈의 흐름을 정확히 추적하고 재무 상태의 무결성을 보장합니다.
- 역할의 명확화: 이는 순수한 회계 데이터만을 다루는 전문화된 컬렉션입니다. 예약이나 결제 상태와는 독립적으로 '돈 이동'만을 기록하여 시스템의 재무 관련 분석이나 리포팅을 용이하게 합니다.
2.4 occupiedSeats - 좌석 점유 관리

- 역할: 사용자가 예약을 시작하여 결제를 완료하기 전까지 선택한 좌석을 일시적으로 점유하는 상태를 기록합니다.
- 분리 이유:
- 동시성 문제 해결: 여러 사용자가 동시에 같은 좌석을 예매하려는 것을 방지하기 위해
occupiedSeats컬렉션은 좌석에 대한 잠금(Lock) 메커니즘으로 동작합니다. - 상세 예시:
- 스크린샷의 문서(
seatId: "A3")는 A3 좌석이 특정 예약(bookingId)을 위해locked상태임을 명시합니다. lockedUntil필드를 사용해 일정 시간(예: 10분)이 지나면 자동으로 잠금이 해제되는 타임아웃 로직을 구현할 수 있습니다. 이를 통해, 사용자가 결제를 완료하지 않고 이탈하더라도 좌석이 영구적으로 점유되지 않도록 합니다.
- 스크린샷의 문서(
- 동시성 문제 해결: 여러 사용자가 동시에 같은 좌석을 예매하려는 것을 방지하기 위해
2.5 paymentIntents - 결제 의도 관리

- 역할: 외부 결제 시스템(PG)을 통해 생성된 결제 의도(Payment Intent)의 전체 생명주기를 추적하고 관리합니다.
- 분리 이유:
- 결제 상태의 상세 추적: 결제는
생성,처리 중,성공,실패등 복잡한 상태를 포함합니다.paymentIntents컬렉션은 이 상태 변화를bookings와 분리하여 독립적으로 상세히 관리합니다. - 외부 시스템과의 연동: 스크린샷의
pgMockData필드처럼, 외부 결제 시스템이 반환한 성공/실패 정보를 저장하여 문제 발생 시 원인을 추적하고 디버깅하는 데 중요한 단서를 제공합니다. 이는 결제 도메인의 복잡성을 다른 서비스로부터 격리하는 효과적인 전략입니다.
- 결제 상태의 상세 추적: 결제는
'배우는거끄적이기' 카테고리의 다른 글
| Redux와 Recoil의 차이점 분석 (0) | 2025.11.18 |
|---|---|
| Props와 Redux: 개념부터 코드까지 종합 비교 (0) | 2025.11.18 |
| [독학]Nest.js 대비 정리 (0) | 2025.09.27 |
| [과제] Express 실행 구조 요약 및 정리 (0) | 2025.09.25 |
| [과제] Node.js 기본 생태계(패키지 매니저, NPM 등) 정리 및 업로드 (0) | 2025.09.15 |