배우는거끄적이기

[MSA] 프로젝트설명

PARKpatchnotes 2025. 11. 20. 16:27

API 테스트 및 실행 시나리오

이 문서는 결제 서비스의 주요 API 흐름을 테스트하기 위한 시나리오를 정의합니다.

1. 사전 준비

  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
  • 응답 본문에 bookingIdpaymentIntentId가 포함됩니다. 이 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 OK
  • statusconfirmed인 예매 내역 배열을 반환합니다.

단계 4: 점유 좌석 상태 확인

  • API: GET /occupiedSeats/:performanceId
  • 설명: 좌석이 booked 상태인지 확인합니다.

요청 예시 (curl):

curl -X GET "http://localhost:3000/occupiedSeats/perf_001?seatIds=A1,A2"

예상 응답:

  • statusbooked인 좌석 정보 배열을 반환합니다.

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 OK
  • bookings 문서 상태가 cancelled로 변경됩니다.
  • occupiedSeats 잠금이 해제됩니다.

Firestore 컬렉션 분리 이유

Firestore의 컬렉션을 bookings, events, ledgerEntries, occupiedSeats, paymentIntents로 세분화한 것은 마이크로서비스 아키텍처의 설계 원칙데이터의 역할 및 책임을 명확히 분리하기 위함입니다. 각 컬렉션은 독립적인 역할을 수행하여 시스템의 확장성, 안정성, 유지보수성을 높입니다.


2.1 bookings - 예약 정보 관리

bookings 컬렉션 문서

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

2.2 events - 비동기 이벤트 처리

events 컬렉션 문서

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

2.3 ledgerEntries - 회계 원장 기록

MERCHANT_BALANCE CREDIT CUSTOMER_PAYABLE DEBIT

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

2.4 occupiedSeats - 좌석 점유 관리

occupiedSeats 컬렉션 문서

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

2.5 paymentIntents - 결제 의도 관리

paymentIntents 컬렉션 문서

  • 역할: 외부 결제 시스템(PG)을 통해 생성된 결제 의도(Payment Intent)의 전체 생명주기를 추적하고 관리합니다.
  • 분리 이유:
    • 결제 상태의 상세 추적: 결제는 생성, 처리 중, 성공, 실패 등 복잡한 상태를 포함합니다. paymentIntents 컬렉션은 이 상태 변화를 bookings와 분리하여 독립적으로 상세히 관리합니다.
    • 외부 시스템과의 연동: 스크린샷의 pgMockData 필드처럼, 외부 결제 시스템이 반환한 성공/실패 정보를 저장하여 문제 발생 시 원인을 추적하고 디버깅하는 데 중요한 단서를 제공합니다. 이는 결제 도메인의 복잡성을 다른 서비스로부터 격리하는 효과적인 전략입니다.