📖 Adit 서비스 가이드에 오신 것을 환영합니다!
system
정산 시스템

Adit 정산 시스템 설계 문서

최종 업데이트: 2026-02-26


1. 개요

1.1 정산 시스템의 역할

Adit 정산 시스템은 캠페인 완료 후 광고주로부터 대금을 수령하고 파트너(매체사)에게 정산금을 지급하는 전체 플로우를 관리합니다.

1.2 핵심 참여자

참여자역할
광고주캠페인 비용 지불, 세금계산서 수령
파트너(매체사)콘텐츠 업로드, 정산금 수령, 세금계산서 발행
Adit/대행사중개, 세금계산서 발행/수령, 수수료 정산, 원천징수
어드민정산 처리, 입금 확인, 송금 처리

2. 정산 유형

Adit 정산 (ADIT_SETTLEMENT)

광고주 → [입금] → Adit/대행사 계좌 → [정산금 송금] → 파트너
         ↓                              ↓
    세금계산서 수령              세금계산서 수령
    (Bolta 자동 정발행)          (파트너 발행 or Bolta 역발행)
  • 수수료 모델: 캠페인 금액의 N% (파트너별 설정, 기본 20%)
  • 정산금: 캠페인 금액 - 수수료
  • 원천징수: 개인사업자만 3.3% (정산금 기준)
  • 실지급액: 정산금 - 원천징수

정산 유형 변경

어드민이 정산 유형을 ADIT_SETTLEMENTDIRECT_INVOICE로 변경 가능합니다.

  • 변경 시 수수료/원천징수가 재계산됩니다
  • 정산 상태가 SETTLEMENT_PENDING으로 초기화됩니다
  • API: PATCH /api/admin/settlements/:id/type

3. 수수료 및 원천징수

3.1 수수료 체계

항목기본값설정 위치
Adit 정산 수수료율20%Partner.commissionRate
캠페인별 오버라이드가능Campaign.commissionRate
직정산 수수료0% (고정)-

수수료율 적용 우선순위:

  1. Campaign.commissionRate (설정된 경우)
  2. Partner.commissionRate (파트너 기본값)
  3. 시스템 기본값: 20% (Prisma 스키마 default)

3.2 원천징수

조건원천징수율적용
개인사업자(INDIVIDUAL) + ADIT_SETTLEMENT3.3%
법인사업자(CORPORATE) + ADIT_SETTLEMENT0%
DIRECT_INVOICE (개인/법인 무관)0%
  • 계산 기준: 정산금(settlementAmount) 기준
  • 세율 구성: 소득세 3% + 지방소득세 0.3% = 3.3%
  • 코드 위치: campaign-settlement.service.ts (하드코딩)

3.3 금액 계산 공식

campaignPrice        = Campaign.price (캠페인 가격)
commissionRate       = Campaign.commissionRate ?? Partner.commissionRate (기본 20%)
commissionAmount     = Math.round(campaignPrice × commissionRate / 100)
settlementAmount     = campaignPrice - commissionAmount
withholdingTaxRate   = (INDIVIDUAL + ADIT_SETTLEMENT) ? 3.3 : 0
withholdingTaxAmount = Math.round(settlementAmount × withholdingTaxRate / 100)
payoutAmount         = settlementAmount - withholdingTaxAmount

예시 (캠페인 100만원, 수수료 20%, 개인사업자, ADIT 정산):

항목계산금액
캠페인 가격-1,000,000원
수수료 (20%)1,000,000 × 0.2200,000원
정산금1,000,000 - 200,000800,000원
원천징수 (3.3%)800,000 × 0.03326,400원
실지급액800,000 - 26,400773,600원

4. 3계층 정산 구조

4.1 구조 개요

4.2 정산 모드

모드광고주 청구파트너 지급사용 케이스
건별 정산캠페인마다 청구서캠페인마다 지급소량 거래, 직정산
월별 정산월 1회 합산 청구월 1회 합산 지급대량 거래, 정기 정산
⚠️

현재 구현: 건별 정산만 구현됨. 월별 정산은 Phase 2에서 구현 예정.


5. 대행사(Agency) 지원

5.1 개념

정산 주체가 Adit(르도드코퍼레이션) 외에 다른 대행사도 될 수 있음.

대행사코드정산 계좌
르도드코퍼레이션 주식회사 (Adit)ADIT신한은행 100-038-478766 (르도드코퍼레이션 주식회사)
미드나잇웨이브MNW신한은행 110-XXX-XXXXXX (주식회사 미드나잇웨이브)

5.2 적용 범위

  • Campaign.agencyId: 캠페인 담당 대행사
  • Settlement.agencyId: 정산 담당 대행사 (Campaign과 동기화)
  • AdminUser.agencyId: 어드민 소속 대행사
  • AdvertiserInvoice.agencyId: 청구서 발행 주체
  • 광고주 입금 계좌: 대행사별로 다름

5.3 권한 분리

소속볼 수 있는 데이터
슈퍼어드민 (agencyId = null)모든 대행사 데이터
Adit 어드민 (agencyId = 1)Adit 담당 캠페인/정산만
MNW 어드민 (agencyId = 2)미드나잇웨이브 담당 캠페인/정산만

5.4 대행사-정산 동기화

syncSettlementAgencyByCampaignId() 함수로 Campaign.agencyId 변경 시 Settlement.agencyId 자동 동기화.


6. 정산 플로우

6.1 Adit 정산 전체 플로우

캠페인 업로드 완료

Campaign.status = UPLOADED → Settlement 자동 생성 (수수료/원천징수 계산) → AdvertiserInvoice 자동 생성 (ADIT_SETTLEMENT인 경우) → Bolta 정발행 자동 요청 (광고주 세금계산서)

파트너가 "정산 요청" 클릭

  • Settlement.status = AWAITING_ADVERTISER_PAYMENT
  • 내부팀 슬랙 알림 (@adit_set)
  • 법인사업자: Bolta 역발행 자동 요청 → 승인 URL 반환
  • 개인사업자: 역발행 스킵 (정산 요청만 처리)

광고주 입금 확인

  • Settlement.status = ADVERTISER_PAID
  • 광고주 이메일: "입금이 확인되었습니다"
  • 파트너 이메일: "곧 정산금이 지급됩니다"

파트너에게 세금계산서 요청 (지급 시작)

  • Settlement.status = PARTNER_PAYOUT_PROCESSING
  • PartnerPayout 생성
  • 파트너 이메일: "세금계산서 발행 부탁드립니다"

파트너 세금계산서 수령 확인

  • PartnerPayout.partnerInvoiceReceivedAt 기록

파트너 정산금 송금

  • Settlement.status = SETTLEMENT_COMPLETED
  • Campaign.status = COMPLETED
  • 파트너 이메일: "정산이 완료되었습니다 (₩금액)"

6.2 매체사 직정산 플로우

캠페인 업로드 완료

Campaign.status = UPLOADED → Settlement 자동 생성 (수수료/원천징수 0%)

파트너가 "정산 요청" 클릭

  • Settlement.status = AWAITING_ADVERTISER_PAYMENT
  • 광고주 이메일: "매체사에 직접 정산해주세요" + 파트너 계좌 정보

파트너가 세금계산서 발급 완료 표시

  • Settlement.invoiceIssuedAt 기록

파트너가 "입금 확인" 클릭

  • Settlement.status = SETTLEMENT_COMPLETED
  • Campaign.status = COMPLETED

6.3 정산 상태 전이 (State Machine)

상태 전이 규칙 (settlement-state-machine.ts):

현재 상태가능한 전이
SETTLEMENT_PENDING→ AWAITING_ADVERTISER_PAYMENT, SETTLEMENT_CANCELLED
AWAITING_ADVERTISER_PAYMENT→ ADVERTISER_PAID, SETTLEMENT_CANCELLED
ADVERTISER_PAID→ PARTNER_PAYOUT_PROCESSING, SETTLEMENT_CANCELLED
PARTNER_PAYOUT_PROCESSING→ SETTLEMENT_COMPLETED, SETTLEMENT_CANCELLED
SETTLEMENT_COMPLETED(종료 상태)
SETTLEMENT_CANCELLED(종료 상태)

7. Bolta 세금계산서 연동

7.1 정발행 (광고주 청구서)

Adit 정산에서 광고주에게 발행하는 세금계산서를 Bolta API로 자동 처리합니다.

단계설명
1캠페인 업로드 완료 시 AdvertiserInvoice 자동 생성
2Bolta API로 정발행 요청 (자동)
3웹훅 SUCCESS → 세금계산서 번호/URL 저장
4Settlement.status → AWAITING_ADVERTISER_PAYMENT (자동 전이)
5실패 시: Slack 알림 + 어드민 수동 재시도 (POST /retry-issuance)

7.2 역발행 (파트너 세금계산서 · 법인사업자)

법인사업자 파트너의 세금계산서를 Bolta 역발행으로 자동 처리합니다.

단계설명
1정산 요청 시 Bolta 역발행 자동 요청
2파트너에게 승인 URL 이메일 발송 (10분 유효)
3파트너 승인 → 세금계산서 자동 발행
4승인 URL 만료 시 파트너 콘솔에서 재발급 가능

7.3 BoltaReverseIssuance 모델

model BoltaReverseIssuance {
  id               BigInt   @id @default(autoincrement())
  settlementId     String   @unique  // SET{NNN}
  issuanceKey      String   // Bolta 발행 키
  status           String   // PENDING, SUCCESS, FAILURE, CANCELLED
  grantUrl         String?  // 역발행 승인 URL (10분 유효)
  failureCode      String?
  failureMessage   String?
}

7.4 개인사업자 처리

개인사업자(businessEntityType = INDIVIDUAL)는 Bolta 역발행을 사용하지 않습니다.

  • 정산 요청 시 역발행 스킵
  • AdvertiserInvoice는 정상 생성
  • 파트너가 홈택스 등을 통해 직접 세금계산서 발행

8. DB 스키마

8.1 Settlement (정산)

model Settlement {
  id                      String    @id  // "SET001"
  campaignId              BigInt    @unique
  partnerId               BigInt
  agencyId                BigInt?   // null = Adit 기본
 
  // 금액 계산
  campaignPrice           Decimal(15,2)
  commissionRate          Decimal(5,2)   // 수수료율 (기본 20%)
  commissionAmount        Decimal(15,2)  // 수수료 금액
  settlementAmount        Decimal(15,2)  // 정산금 (price - commission)
  withholdingTaxRate      Decimal(5,2)   // 원천징수율 (0 or 3.3)
  withholdingTaxAmount    Decimal(15,2)  // 원천징수 금액
  payoutAmount            Decimal(15,2)  // 실지급액 (settlement - withholding)
 
  // 상태
  status                  SettlementStatus
  settlementType          SettlementType
 
  // 2계층 연결
  advertiserInvoiceId     BigInt?   // N:1 → AdvertiserInvoice
  partnerPayoutId         BigInt?   // N:1 → PartnerPayout
 
  // 직정산 전용
  invoiceIssuedAt         DateTime?
  invoicePaidAt           DateTime?
 
  // Bolta 역발행 수령 확인
  partnerInvoiceReceivedAt DateTime?
 
  // 파트너 계좌 스냅샷
  bankName                String?
  accountNumber           String?
  accountHolder           String?
 
  // 타임스탬프
  requestedAt             DateTime?
  processedAt             DateTime?
  completedAt             DateTime?
  adminNotes              String?   @db.Text
}

8.2 Agency (대행사)

model Agency {
  id                    BigInt    @id @default(autoincrement())
  name                  String    // "르도드코퍼레이션"
  code                  String    @unique  // "ADIT"
  businessName          String    // 사업자등록증상 상호
  businessNumber        String    // 사업자번호
  representativeName    String    // 대표자명
  bankName              String    // "신한은행"
  accountNumber         String    // "100-038-478766"
  accountHolder         String    // "르도드코퍼레이션 주식회사"
  email                 String
  isActive              Boolean   @default(true)
}

8.3 AdvertiserInvoice (광고주 청구서)

model AdvertiserInvoice {
  id                    BigInt    @id @default(autoincrement())
  invoiceNumber         String    @unique  // "INV-2026-01-001"
  advertiserId          BigInt
  advertiserName        String
  agencyId              BigInt?   // 정산 주체 (null이면 Adit 기본값)
 
  // 유형
  invoiceType           String    // PER_CAMPAIGN | MONTHLY
  periodStart           DateTime? // 월별 정산 시
  periodEnd             DateTime? // 월별 정산 시
 
  // 금액
  subtotal              Decimal   // 공급가액
  taxAmount             Decimal   // 부가세 (10%)
  totalAmount           Decimal   // 합계
 
  // 세금계산서
  taxInvoiceNumber      String?
  taxInvoiceFileKey     String?
  taxInvoiceIssuedAt    DateTime?
 
  // 입금
  status                InvoiceStatus  // DRAFT, ISSUED, PAID, OVERDUE, CANCELLED
  dueDate               DateTime?
  paidAt                DateTime?
 
  // 입금 계좌 스냅샷
  bankName              String?
  accountNumber         String?
  accountHolder         String?
 
  // Relations
  settlements           Settlement[]
}

8.4 PartnerPayout (파트너 지급)

model PartnerPayout {
  id                    BigInt    @id @default(autoincrement())
  payoutNumber          String    @unique  // "PAY-2026-01-001"
  partnerId             BigInt
  partnerName           String
 
  // 유형
  payoutType            String    // PER_CAMPAIGN | MONTHLY
  periodStart           DateTime? // 월별 정산 시
  periodEnd             DateTime? // 월별 정산 시
 
  // 금액
  subtotal              Decimal   // 정산금 합계
  taxAmount             Decimal   // 원천징수 금액
  totalAmount           Decimal   // 실지급액 (정산금 - 원천징수)
 
  // 파트너 세금계산서
  partnerInvoiceNumber  String?
  partnerInvoiceFileKey String?
  partnerInvoiceReceivedAt DateTime?
 
  // 지급
  status                PayoutStatus  // PENDING, INVOICE_RECEIVED, PROCESSING, COMPLETED, CANCELLED
  paidAt                DateTime?
  transactionId         String?
 
  // 계좌 스냅샷
  bankName              String?
  accountNumber         String?
  accountHolder         String?
 
  // Relations
  settlements           Settlement[]
}

9. API 엔드포인트

9.1 어드민 정산 API

MethodEndpoint설명
GET/api/admin/settlements정산 목록 조회 (필터: partner, campaign, status, date, agency)
GET/api/admin/settlements/:id정산 상세 조회
GET/api/admin/settlements/:id/reverse-issuance-statusBolta 역발행 상태 실시간 조회 + DB 동기화
PATCH/api/admin/settlements/:id/status정산 상태 변경
PATCH/api/admin/settlements/:id/type정산 유형 변경 (ADIT ↔ DIRECT, 재계산)
POST/api/admin/settlements/:id/start-payout파트너 지급 시작
POST/api/admin/settlements/:id/complete정산 완료 (+ 캠페인 COMPLETED)
POST/api/admin/settlements/:id/retry-issuanceBolta 정발행 재시도 (ADIT_SETTLEMENT 전용)
POST/api/admin/settlements/:id/invoice-issued세금계산서 발급 완료 (DIRECT_INVOICE)
POST/api/admin/settlements/:id/invoice-paid입금 확인 (DIRECT_INVOICE → 즉시 완료)
POST/api/admin/settlements/:id/cancel정산 취소

9.2 파트너 정산 API

MethodEndpoint설명
GET/api/partner/settlements내 정산 목록
GET/api/partner/settlements/summary정산 요약 (상태별 건수/금액)
GET/api/partner/settlements/:id정산 상세
POST/api/partner/settlements/:id/request정산 요청 (법인: 역발행 자동)
POST/api/partner/settlements/:id/invoice-issued세금계산서 발급 완료 (DIRECT)
POST/api/partner/settlements/:id/invoice-paid입금 확인 (DIRECT → 완료)
GET/api/partner/settlements/:id/reverse-issuance-grant-urlBolta 역발행 URL 재발급

9.3 AdvertiserInvoice / PartnerPayout (내부 서비스)

AdvertiserInvoice와 PartnerPayout은 별도 API 라우트가 없습니다. Settlement 엔드포인트를 통해 자동 생성/관리됩니다.

  • AdvertiserInvoice: 캠페인 업로드 완료 시 자동 생성 + Bolta 정발행
  • PartnerPayout: start-payout 호출 시 자동 생성

9.4 대행사 API

MethodEndpoint설명권한
GET/api/admin/agencies대행사 목록 조회Admin
GET/api/admin/agencies/default기본 대행사(ADIT) 조회Admin
GET/api/admin/agencies/:id대행사 상세 조회Admin
GET/api/admin/agencies/code/:code코드로 대행사 조회Admin
GET/api/admin/agencies/:id/settlement-account대행사 정산 계좌 조회Admin
POST/api/admin/agencies대행사 생성SUPER_ADMIN
PUT/api/admin/agencies/:id대행사 수정Admin
DELETE/api/admin/agencies/:id대행사 비활성화SUPER_ADMIN

9.5 견적서 API

MethodEndpoint설명
GET/api/admin/quotation견적서 목록 조회
POST/api/admin/quotation견적서 저장 (DRAFT)
GET/api/admin/quotation/:quoId견적서 상세 조회
PUT/api/admin/quotation/:quoId견적서 수정 (DRAFT만 가능)
DELETE/api/admin/quotation/:quoId견적서 삭제 (소프트 → CANCELLED)
PATCH/api/admin/quotation/:quoId/status견적서 상태 변경
POST/api/admin/quotation/:quoId/execute견적서 실행 → 캠페인 일괄 생성
GET/api/admin/quotation/:quoId/download견적서 XLSX 다운로드
POST/api/admin/quotation/generate견적서 XLSX 즉시 생성 (저장 없이 다운로드)

9.6 사업자등록정보 검증 API (NTS)

MethodEndpoint설명
POST/api/public/business/verify사업자등록번호 진위확인 + 상태조회 (인증 불필요)

NTS API는 공개 엔드포인트입니다 (인증 불필요). 가입/온보딩 시 사업자번호 검증에 사용됩니다. Rate limit: 15분당 IP당 30회.


10. 알림 시스템

10.1 광고주 알림

이벤트트리거채널
INVOICE_ISSUED_ADVERTISER세금계산서 발행 (Bolta 정발행)Email
PAYMENT_REQUEST_ADVERTISER입금 요청 (계좌 안내)Email
PAYMENT_CONFIRMED_ADVERTISER입금 확인 완료Email
DIRECT_SETTLEMENT_REQUEST매체사 직정산 안내Email

10.2 파트너 알림

이벤트트리거채널
ADVERTISER_PAID_PARTNER광고주 입금 완료Email
INVOICE_REQUEST_PARTNER세금계산서 발행 요청Email
PAYOUT_COMPLETED_PARTNER정산금 송금 완료Email

10.3 내부 알림

이벤트트리거채널
SETTLEMENT_REQUESTED파트너 정산 요청Slack (@adit_set)
정산 상태 변경각 단계별Slack Thread

11. 구현 현황

11.1 완료 ✅

  • Settlement 기본 모델 (수수료, 원천징수, 실지급액)
  • 정산 상태 전이 State Machine
  • 파트너 정산 요청 API
  • 어드민 정산 관리 API (상태 변경, 유형 변경, 취소)
  • Agency 모델 및 API
  • AdminUser.agencyId, Campaign.agencyId 확장
  • Settlement.agencyId 동기화
  • AdvertiserInvoice 모델 및 API (건별)
  • PartnerPayout 모델 및 API (건별)
  • Bolta 정발행 (광고주 세금계산서)
  • Bolta 역발행 (법인사업자 파트너 세금계산서)
  • 원천징수 계산 (개인사업자 3.3%)
  • 광고주 정산 알림 (세금계산서, 입금 요청, 입금 확인)
  • 파트너 정산 알림 (세금계산서 요청, 송금 완료)
  • 캠페인 상태 변경 알림 (Slack, Email)
  • 슬랙 캠페인별 스레드
  • 정산 유형 변경 (ADIT ↔ DIRECT, 재계산)
  • 매체사 직정산 플로우 (발급/입금 확인)
  • 역발행 승인 URL 재발급

11.2 Phase 2 (추후 구현)

  • 월별 일괄 정산 (MONTHLY 모드)
  • SettlementConfig (광고주/파트너별 정산 주기 설정)
  • 월별 배치 Job (매월 25일 청구서 생성, 말일 지급 배치)
  • 정산 대시보드

부록

A. 용어 정의

용어설명
Settlement캠페인 단위 정산 레코드 (1:1)
AdvertiserInvoice광고주에게 발행하는 청구서
PartnerPayout파트너에게 지급하는 정산 배치
Agency정산 주체 (Adit, 미드나잇웨이브 등)
BoltaReverseIssuanceBolta 역발행 추적 레코드
건별 정산캠페인 1건 = 청구서/지급 1건
월별 정산한 달치 캠페인 합산하여 청구/지급
원천징수개인사업자 소득에 대한 선납 세금 (3.3%)
payoutAmount실지급액 (정산금 - 원천징수)

B. 관련 파일 위치

adit-backend/
├── prisma/schema.prisma                          # DB 스키마
├── src/utils/settlement-state-machine.ts          # 상태 전이 규칙
├── src/modules/
│   ├── admin/settlements/                         # 어드민 정산 서비스
│   │   ├── settlement.service.ts                  # 핵심 비즈니스 로직
│   │   ├── settlement.routes.ts                   # API 라우트
│   │   └── settlement.types.ts                    # Zod 스키마
│   ├── partner/settlements/                       # 파트너 정산 서비스
│   │   └── settlement.service.ts                  # 정산 요청, 역발행
│   ├── partner/campaigns/
│   │   └── campaign-settlement.service.ts         # 정산 레코드 자동 생성
│   ├── admin/invoices/                            # 광고주 청구서
│   ├── admin/payouts/                             # 파트너 지급
│   ├── admin/agencies/                            # 대행사 관리
│   ├── bolta/                                     # Bolta API 연동
│   └── notification/                              # 알림 서비스
└── src/templates/email/                           # 이메일 템플릿