대규모 결제 시스템 설계
Source: Dev.to
위의 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL, 마크다운 구문 및 기술 용어는 그대로 유지됩니다.)
마리아가 “Confirm Ride”를 탭하면 실제로 무슨 일이 일어날까?
마리아는 15분 안에 중요한 회의가 있다.
그녀는 현금이 없다.
그녀는 Uber를 열고, 차량을 요청하고, 목적지에 도착한다.
결제는? 보이지 않는다. 즉시 이루어진다. 손쉽다.
하지만 그 한 번의 탭 뒤에는 현대 소프트웨어에서 가장 복잡한 분산 시스템 중 하나가 숨어 있다.
오늘 우리는 이를 분석한다 – 단순히 “카드를 청구하는 방법”이 아니라, 수백만 건의 라이드를 매일 처리할 수 있는 안전하고, 신뢰성 있으며, 확장 가능한 결제 시스템을 구축하는 방법을.
단순함의 환상
사용자 관점:
Trip ends → $20 charged → Done
백엔드 관점:
- 결제 정보를 안전하게 수집
- 민감한 카드 데이터 저장을 피함
- 사기 방지
- 은행 장애 처리
- 여러 파티에 금액 분배
- 재무 정확성 유지
- 불일치 조정
- 재시도와 타임아웃 견디기
- 전 세계 규모 지원
이것은 기능이 아니라 인프라입니다.
1️⃣ 첫 번째 문제: 카드 데이터를 저장할 수 없습니다
사용자가 입력할 때:
- 카드 번호
- CVV
- 만료일
이를 직접 저장하면:
- 무거운 PCI‑DSS 준수 요구
- 대규모 침해 위험
- 법적 노출
Solution: Tokenization
- 모바일 앱이 결제 제공업체 SDK(Stripe, Adyen 등)를 통합합니다.
- SDK가 카드 데이터를 직접 제공업체에 전송합니다.
- 제공업체가 토큰을 반환합니다.
- 해당 토큰만 저장합니다.
토큰은 카드를 청구할 수 있는 재사용 가능한, 범위가 제한된 권한입니다. 토큰이 탈취되더라도 귀하의 상점 계정 외에서는 무용지물입니다. 보안이 해결되었습니다(대부분).
2️⃣ Authorization vs. Capture (세부적인 차이가 드러나는 곳)
탑승이 끝났을 때 단순히 “청구”만 하는 것이 아닙니다. 일반적으로 다음과 같이 진행합니다:
- Authorize – 카드에 잔액이 있는지 확인하고 금액을 잠급니다.
- Capture – 실제로 금액을 이체합니다.
왜 나눠서 진행하나요?
- 탑승 요금이 변동될 수 있습니다.
- 최종 요금을 조정해야 할 수도 있습니다.
- 미결제 탑승을 원하지 않습니다.
대규모 시스템에서는 보통 초기에 (예상 요금) 승인을 하고, 나중에 (최종 요금) 캡처합니다. 작은 디테일이지만 아키텍처에 큰 영향을 미칩니다.
3️⃣ 돈이 라이더 → 드라이버로 직접 가지 않는다
라이더는 운전자를 직접 결제하지 않습니다. 대신:
Rider → Uber Merchant Account → Split →
→ Driver
→ Uber Commission
→ Taxes
→ Fees
왜?
- 수수료 관리
- 세금 처리
- 분쟁 처리
- 사기 방지
직접적인 피어‑투‑피어 결제는 회계 처리를 무너뜨릴 수 있습니다.
Source: …
4️⃣ 숨겨진 영웅: 내부 원장 시스템
결제 제공자를 진실의 원천으로 신뢰할 수 없습니다. 원장 서비스를 직접 구축하세요.
단순화된 복식부기 예시:
| Account | Debit | Credit |
|---|---|---|
| Rider | $20 | |
| Driver | $15 | |
| Platform | $5 |
모든 움직임이 기록됩니다. 복식부기는 돈이 사라질 수 없도록 보장합니다. 차변 ≠ 대변 → 무언가가 깨진 것입니다. 규모가 커질수록 이것이 “잘 작동한다”와 “ silently $3 M 손실” 사이의 차이를 만듭니다.
Source: …
5️⃣ 신뢰성: 외부 시스템은 실패할 수 있다
귀하의 결제 시스템은 다음에 의존합니다:
- 은행
- 카드 네트워크
- 결제 제공업체
- 네트워크 호출
이들 모두가 실패할 수 있습니다. 흔히 발생하는 악몽:
- 승인에 성공합니다.
- 캡처 요청이 시간 초과됩니다.
- 재시도합니다.
- 고객이 이중 청구됩니다.
해결책: 멱등성 키
- 각 결제 시도에는 고유 키가 포함됩니다(예:
ride_id). - 재시도 시, 제공자는 해당 키를 인식하고 중복 처리를 방지합니다.
멱등성을 사용하지 않으면 사용자를 이중 청구하게 되고 신뢰를 잃게 됩니다.
6️⃣ 스마트 재시도 (맹목적인 재시도 아님)
| 오류 | 재시도? |
|---|---|
| 네트워크 타임아웃 | Yes |
| 속도 제한 | Yes |
| 자금 부족 | No |
| 사기 차단 | No |
맹목적인 재시도는 혼란을 초래합니다. 지능적인 재시도는 회복력을 높입니다.
7️⃣ Fraud Layer (Before Money Moves)
Before charging, run:
- Velocity checks
- Device fingerprinting
- Location mismatch detection
- Behavioral anomaly detection
If something looks suspicious, trigger:
- 3‑D Secure
- OTP verification
- Manual review
Payment systems are also fraud systems. Ignoring this will let chargebacks destroy margins.
7️⃣ 사기 레이어 (자금 이동 전)
청구하기 전에 실행:
- 속도 검사
- 디바이스 지문 채취
- 위치 불일치 감지
- 행동 이상 감지
뭔가 의심스러우면 트리거:
- 3‑D Secure
- OTP 인증
- 수동 검토
결제 시스템은 사기 방지 시스템이기도 합니다. 이를 무시하면 청구 취소가 마진을 파괴합니다.
8️⃣ Refunds Aren’t Simple
Refunding isn’t just “reverse the transaction.” It requires:
- Updating the internal ledger
- Issuing a refund request to the provider
- Adjusting the driver’s balance
- Handling payouts that may have already been completed
Sometimes the platform absorbs a temporary loss. Complexity compounds over time.
8️⃣ 환불은 간단하지 않다
환불은 단순히 “거래를 되돌리는” 것이 아니다. 다음이 필요하다:
- 내부 원장 업데이트
- 제공자에게 환불 요청 발송
- 운전자의 잔액 조정
- 이미 완료된 지급 처리
때때로 플랫폼이 일시적인 손실을 흡수한다. 복잡성은 시간이 지남에 따라 누적된다.
9️⃣ Driver Payouts: A Different System
Charging cards is one system. Paying drivers is another.
Typical flow:
- Aggregate earnings daily
- Settle weekly (or offer instant payout for a fee)
Uses bank rails like ACH, SEPA, etc., which are completely different from card networks. Two financial systems under one product.
9️⃣ 운전사 지급: 다른 시스템
카드 결제는 하나의 시스템이다. 운전사에게 지급하는 것은 또 다른 시스템이다.
일반적인 흐름:
- 일일 수익 집계
- 주간 정산 (또는 수수료를 받고 즉시 지급 제공)
ACH, SEPA 등과 같은 은행 레일을 사용하며, 이는 카드 네트워크와 완전히 다르다. 하나의 제품 안에 두 개의 금융 시스템이 존재한다.
🔟 조정 (성인이 일하는 곳)
매일 밤:
- 결제 제공업체에서 보고서를 가져옵니다.
- 내부 원장과 비교합니다.
- 불일치를 식별합니다.
불일치가 발견되면:
- 검토를 위해 표시
- 조사 시작
조정이 없으면 작은 불일치가 수백만으로 합쳐집니다.
1️⃣1️⃣ 수백만 건의 라이드 확장
고규모에서:
- 하루 1 M+ 라이드
- 피크 시 초당 1 000+ 트랜잭션
필요한 것:
- 무상태 결제 서비스
- 이벤트‑드리븐 아키텍처
- 메시지 큐(Kafka, Pub/Sub 등)
- 수평 확장
동기식 “Ride → Immediate Charge” 대신, 분리된 흐름을 사용하세요:
RideCompleted Event → Payment Queue → Worker → Provider
분리하면 연쇄적인 장애를 방지할 수 있습니다.
1️⃣2️⃣ 다중 제공자 전략
단일 결제 제공자에만 의존하지 마세요. 다음을 구현합니다:
- 기본 제공자
- 보조 폴백
추상화 레이어와 함께:
def charge(amount, token):
# routing logic decides which provider to use
...
서비스 중단이 발생할 수 있습니다; 폴백이 플랫폼을 지속시킵니다.
“만약.”
“그들은 ‘언제.’”
What Looks Simple Is Actually Distributed Finance
A ride‑payment system is not:
- Just API calls
- Just token storage
- Just Stripe integration
It is:
- Distributed systems
- Financial accounting
- Legal compliance
- Fault tolerance
- Fraud modeling
- Bank integrations
- Event‑driven infrastructure
That’s why payment infrastructure is one of the hardest backend domains in the world.
겉보기엔 간단해 보여도 실제로는 분산 금융
승차 결제 시스템은 아닙니다:
- 단순히 API 호출
- 단순히 토큰 저장
- 단순히 Stripe 통합
그것은 다음과 같습니다:
- 분산 시스템
- 재무 회계
- 법적 준수
- 내결함성
- 사기 모델링
- 은행 연동
- 이벤트 기반 인프라
그래서 결제 인프라는 세계에서 가장 어려운 백엔드 분야 중 하나입니다.
Final Thought
마리아가 프라하에서 택시를 내렸을 때, 그녀는 다음과 같은 것들을 생각하지 않았습니다:
- Idempotency keys
- Double‑entry accounting
- Multi‑provider failover
- Fraud scoring
- Reconciliation pipelines
그녀는 그냥 회의실로 걸어 들어갔습니다.
그것이 목표입니다. 훌륭한 엔지니어링은 복잡성을 보이지 않게 합니다.
If you’re building systems
기능만 설계하지 마세요. 다음을 위해 설계하세요:
- Failure
- Scale
- Auditability
- Correctness
왜냐하면 금융 시스템은 실수를 용서하지 않기 때문입니다.