Stripe Connect 및 구독 결제
하나의 Stripe 계정에서 두 개의 자금 흐름. 마켓플레이스는 구매자에게 한 번 결제를 받고 여러 판매자에게 수익을 분배합니다(Connect, 별도 결제 및 이체). 플랫폼 구독은 해당 판매자에게 오프세션(off-session)으로 정기 수수료를 청구합니다(Billing). 두 흐름은 하나의 클라이언트, 하나의 웹훅 쌍, 하나의 화폐 유형을 공유합니다.
모든 것을 결정짓는 통찰: 구매자의 결제와 판매자의 정산은 독립적으로 실패합니다. 정산이 실행될 때쯤이면 이미 자금이 이동했으므로, 거부된 이체는 결제나 웹훅을 실패하게 해서는 안 되며, 나중에 누군가에게 두 번 지불하지 않고 재시도 가능해야 합니다.
사용 시기
- 하나의 결제가 여러 판매자에게 분할되고, 플랫폼이 수수료를 공제합니다.
- 판매자가 Connect에 온보딩하고 지연되어 지급받습니다(에스크로, 준비금, KYC).
- 플랫폼이 자체 판매자에게 저장된 카드로 정기 수수료를 청구합니다.
- 커넥티드 계정이 정상으로 보이지만 아직 돈을 받은 적이 없습니다.
사용하지 말아야 할 경우
| 이런 경우 아님 | 대신 사용할 것 |
|---|---|
| 단일 판매자 체크아웃, 분할 없음 | Stripe Checkout / Payment Element 직접 사용 |
| PayPal 마켓플레이스 온보딩 + 정산 | PayPal 스킬; 공유되는 부분은 연결 지점뿐입니다 |
| 카드 UI, Payment Element 스타일링 | 자체 디자인 시스템; 이 스킬은 서버사이드입니다 |
아키텍처
구매자 ──결제 (플랫폼 계정, transfer_group = orderId)──▶ 플랫폼 잔액
│ 정산 팬아웃 (웹훅 또는 읽기 경로 동기; 리스 청구)
┌────────────┴──┬────────────────────────┬────────────────┐
▼ ▼ ▼ ▼
이체:판매자 이체:파트너 에스크로 보류 롤링 준비금
(source_transaction = 결제) (release_at) (% 보류)
│ │ 에스크로 해제 크론
▼ ▼
커넥티드 잔액 ◀── 이체 재시도 지급 가능 잔액 ──▶ 정산 (KYC/위험 게이트)
(자금 미확보 레그)
플랫폼 ──off_session PaymentIntent (고객 + 저장된 결제수단)──▶ 구독 인보이스
채권추심: 3번 시도, 3일 간격, 그 후 중단
중요 사실
- 별도 결제 및 이체, 절대 목적지 결제가 아닙니다. 한 번의 결제는 하나의
destination을 받습니다. 여러 판매자가 있는 장바구니는 하나의transfer_group아래 N개의 이체가 필요하므로application_fee_amount를 사용할 수 없습니다. source_transaction이 없는 이체는 가용 잔액에서 인출되며, 결제가 아직 정산 중일 때 거부됩니다. 젊은 플랫폼 계정에서는 모든 이체가 이에 해당합니다. 이 하나의 플래그가 결제가 정산 중일 때 분할 자금을 조달할 수 있게 합니다.- Stripe 이체는 미국/캐나다/영국/EEA/스위스 내에서만 국경 간 이동이 가능합니다. 다른 국가의 플랫폼은 자국 내 커넥티드 계정에만 지급합니다. Stripe는 온보딩 시점이 아닌 이체 시점에 이를 시행하므로, 템플릿은 온보딩 시 지역 규칙을 적용합니다.
- Connect 이벤트는 플랫폼 이벤트와 다른 서명 시크릿을 전달합니다. 두 개의 엔드포인트, 두 개의 시크릿, 하나의 URL — 둘 다 확인합니다.
- 커넥티드 계정의 국가는 변경할 수 없습니다. 잘못된 국가는 재온보딩을 의미하며 패치로 해결되지 않습니다.
- 돈은 절대 부동소수점이 아닙니다. 끝에서 끝까지
numeric(19,4)문자열,bigint연산, SDK 경계에서만 소수 단위를 사용합니다.
엄격한 규칙
이체 실패로 정산이 실패하게 하지 마십시오. 레그를 자금 미확보로 기록하고 계속 진행하십시오. 결제는 이미 성공했으므로 웹훅도 성공해야 합니다. 재시도 크론이 나중에 해당 레그에 자금을 조달합니다.
중복 웹훅 삽입을 "이미 처리됨"으로 간주하지 마십시오. 삽입은 청구입니다. 전달이 처리 중간에 충돌한 후의 재시도는 핸들러를 다시 실행하므로, 모든 이벤트는 정확히 한 번 실행되어야 합니다.
Stripe에 이미 존재하는지 묻지 않고 이체를 재전송하지 마십시오. 응답이 손실되어 자금 미확보로 기록된 레그는 Stripe의 24시간 멱등성 창이 지나면 두 번 지불됩니다.
마지막에 작성된 상태 플래그에서 정산 독점성을 도출하지 마십시오. 두 호출자가 정기적으로 정산에 도달합니다. 원자적 리스 청구가 인벤토리와 이체를 한 번만 발생하게 합니다.
이체의 남은 여유 공간보다 더 많이 취소하지 마십시오. 누적 취소(게이트웨이 수수료, 그 다음 환불)는 원래 금액을 초과하면 거부됩니다.
빠른 시작
- 돈 모델링 — money.md, data-model.md, store.md.
- 클라이언트, 이중 웹훅 시크릿, 환경 변수 — stripe-adapter.md.
- 계정 온보딩; 먼저 지역 규칙 확인 — connect-accounts.md.
- 이벤트를 멱등적으로 수신 — webhooks.md.
- 정산: 청구, 팬아웃, 에스크로, 준비금 — settlement.md.
- 수렴: 레그 재시도, 수수료 조정 — reconciliation.md.
- 테넌트 청구 — subscriptions.md; operations.md로 실행.
애플리케이션에 맞게 조정하려면 adaptation.md를 참조하십시오. 감사 기록은 provenance.md에 있습니다.
적응 계약
| 연결 지점 | 이 스킬이 제공 | 호스트가 제공 |
|---|---|---|
| 도메인 엔티티 | Tenant(판매자 조직), Order, VendorOrder, Partner | 자체 용어 |
| 테넌트 범위 | 하나의 서버 파생 tenantId | org / workspace / seller |
| 인증 가드 | 경로별 어댑터 시그니처 | Clerk, NextAuth, Supabase, 커스텀 |
| 데이터 접근 | PaymentsStore 계약 + SQL | 자체 ORM 또는 SDK |
| 감사 + 알림 | 호출 사이트, 이벤트 이름, 페이로드 형태 | 자체 감사 테이블 / 아웃박스 |
| 백그라운드 작업 | 네 개의 크론과 그 주기 | 자체 스케줄러 |
| 유효성 검증 | 경로 본문당 스키마 형태 | zod / valibot / yup |
| UI | 없음 — 이 스킬은 서버사이드 | 모두 |
참조 디렉토리
| 시나리오 | 트리거 키워드 | 참조 |
|---|---|---|
| 이 돈 모델의 이유 | 별도 결제 및 이체, destination_charge, application_fee_amount, transfer_group, 원장 | architecture.md |
| 테이블, 컬럼, RLS | 스키마, 마이그레이션, payment_intents, transfers, escrow_holds, webhook_events | data-model.md |
| 데이터 접근 계약 | store, repository, atomic claim, conditional update, ORM | store.md |
| 금액 및 반올림 | numeric, bigint, minor units, distribute, largest remainder, cents | money.md |
| 클라이언트 및 어댑터 코드 | stripeClient, apiVersion, constructEvent, whsec_, PaymentIntent, refund, payout | stripe-adapter.md |
| 온보딩, 계정 상태 | Express, Accounts v2, account.updated, charges_enabled, acct_, country, region | connect-accounts.md |
| 안전한 이벤트 수신 | idempotency, duplicate, replay, raw body, signature, invalid_signature | webhooks.md |
| 유료 주문 분할 | settlement, fan-out, source_transaction, escrow, reserve, single-flight | settlement.md |
| 도착하지 않은 것 수정 | retry, unfunded, backoff, adopt, gateway fee, clawback, reversal, headroom | reconciliation.md |
| 정기 플랫폼 수수료 | SetupIntent, off_session, saved card, dunning, invoice, past_due, suspend | subscriptions.md |
| 설정, 환경 변수, 크론, 디버깅 | STRIPE_SECRET_KEY, webhook endpoint, stripe listen, cron, "merchant never paid" | operations.md |
| 이 앱에 맞게 조정 | adapt, host probe, rename, ORM, auth guard, port | adaptation.md |
| 감사 기록 | provenance, deviation, kept, added, unverified | provenance.md |