다크모드
BE 핸드오프 — 선결제 잔액·크레딧
상태: 설계 확정, 구현 미착수. 정책·약관·아키텍처 확정본이며 코드는 아직 없다. 결정 근거·벤치마크:
docs/decisions/BILLING-CANON-V2-OWNER-DECIDED-2026-08-11.md약관 초안:docs/handoff/backend/billing-prepaid-balance-terms.md
0. 한 줄 요약
조직(Organization)이 카드로 선결제해 잔액을 만들고, 워크스페이스(Workspace)의 종량 기능(전자세금계산서 발행, AI 사용 등)이 그 잔액을 차감한다. 세금계산서는 발행하지 않는다 — 카드 매출전표가 공급시기와 매입세액 공제 증빙을 모두 담당한다.
1. 확정된 정책 (오너 결정)
| # | 항목 | 확정 |
|---|---|---|
| 1 | 결제·계약 주체 | Organization 하나. Workspace는 결제 주체가 아니다 |
| 2 | 자산 종류 | 선결제 잔액(유상) · 크레딧(무상/할인) 2종. 코인·포인트·전환 없음 |
| 3 | 잔액 단위 | 금액(원). 건수 팩·플랜 포함량 단독 모델은 채택하지 않음 |
| 4 | 세금계산서 | 발행하지 않음. 카드 매출전표로 완결 |
| 5 | 유상 잔액 만료 | 없음. 12·24개월 미사용 시 안내만(이용 차단 없음) |
| 6 | Workspace별 사용 한도 | 없음. 공유 허용/차단 flag 하나만 |
| 7 | 잔액 0 | 즉시 차단. 법정 기한 기능도 예외 없음. 여신 없음 |
| 8 | 환불 | 트랙 A(미사용 전액 + 90일, 셀프) / 트랙 B(그 외, 신청) |
| 9 | 교차 후원 | 조직 내에서만. 리셀러·제3자 대납 없음 |
2. 데이터 모델
2-1. 객체
| 객체 | 역할 |
|---|---|
organizations (기존) | 계약·결제·증빙의 유일 주체. billing profile 보유 |
workspaces (기존) | 소비 단위. shared_funding_enabled 하나만 추가 |
funding_lots | 재원 1건 |
ledger_entries | append-only 원장. 사용은 lot별 debit 여러 건 |
payment_receipts | 카드 매출전표·취소 전표 참조 |
usage_records를 별도로 두지 않는다. "이 사용이 어느 재원에서 얼마씩 빠졌는가"는 debit ledger_entries 여러 건으로 표현된다.
2-2. funding_lots
organization_id 결제·소유 주체
kind PAID | GRANT
reason topup | plan_allowance | promotion | trial
| service_credit | support_goodwill
paid_amount 실제 결제금액 (GRANT는 0)
face_value 부여된 잔액
balance 미사용 잔액
refund_ratio paid_amount / face_value (환불 산정용)
expires_at nullable. PAID는 항상 null
applicability nullable. v1 기본값 전체(null)
policy_version 적용 약관·프로모션 버전 고정
granted_reason_note 자유 메모
created_at결제·증빙 필드 — 세금계산서를 발행하지 않으므로 이 묶음이 고객의 유일한 증빙 경로다.
pg_tid 나이스 결제 승인 키
pg_order_id 상점 거래 고유번호
approve_no 카드사 승인번호 (나이스 approveNo, 선택 필드)
receipt_url 매출전표 확인 URL (나이스 receiptUrl, 선택 필드)
cancel_receipt_url 취소 전표 URL (나이스 cancels 배열의 receiptUrl)
supply_amount 공급가액 — 우리가 계산해 저장
vat_amount 부가세액 — 우리가 계산해 저장
card_refundable_until 원 결제 취소 가능 기한주의: 나이스 응답에는 공급가액·부가세 분해가 없다. 결제금액에서 우리가 계산해 저장해야 월별 명세서를 만들 수 있다.
2-3. 충전 보너스는 별도 lot이 아니다
paid_amount와 face_value를 분리하면 충분하다.
- 보너스 없음:
paid_amount == face_value,refund_ratio = 1.0 - 100만원 결제로 110만원 부여:
paid_amount=1000000,face_value=1100000,refund_ratio ≈ 0.9091
v1에서는 만료·적용범위·회수 조건이 유상분과 다른 보너스 프로모션을 만들지 않는다. 그런 프로모션이 필요해지면 그때 별도 lot이 필요해진다.
3. 세무 계약
3-1. 근거
| 조문 | 내용 |
|---|---|
| 부가세법 제36조 | 신용카드매출전표는 영수증으로 본다 |
| 제17조 제1항 | 대가를 받고 제36조 영수증을 발급하면 그 발급하는 때를 공급시기로 본다 |
| 제33조 제2항 | 신용카드매출전표등을 발급한 경우 세금계산서를 발급하지 아니한다 |
| 제46조 | 부가세액이 별도로 구분되는 신용카드매출전표는 매입세액으로 본다 |
| 제12조 제2항 | 대가 없이 공급하는 용역은 용역의 공급으로 보지 아니한다 |
3-2. 시점별 처리
| 시점 | 세무 | 회계 |
|---|---|---|
| 충전 | 공급시기 확정. 카드전표 발급 | 계약부채 증가 |
| 사용 (PAID) | 이벤트 없음 | 계약부채 감소, 매출 인식 |
| 사용 (GRANT) | 이벤트 없음 (무상 용역) | 부채 아님 |
| 환불 트랙 A | 카드 취소 = 전표 취소. 별도 조치 없음 | 계약부채 감소, 현금 유출 |
| 환불 트랙 B | 원 전표 취소 불가 → (-)세금계산서 등 예외 처리 | 동일 |
세무와 회계의 시점차가 발생한다. 부가세는 충전 과세기간, 매출은 사용 시점이다. "선결제 충전분" 관리대장을 두고 VAT 신고 ↔ 총계정원장 ↔ 계약부채 잔액 3자 대사 절차가 필요하다.
3-3. 증빙 제공 (v1 필수)
세금계산서를 주지 않으므로 아래 셋이 고객의 증빙 경로 전부다.
- 충전 전표 —
receipt_url이 있을 때만 조회 버튼 노출. 없으면 서버에서 거래조회로 재획득 - 취소 전표 — 환불 시
cancel_receipt_url제공. 고객이 이미 공제받은 매입세액을 되돌리는 근거 - 월별 충전 내역 명세서 — PDF/Excel 자체 생성. 컬럼: 결제일 · 승인번호 · 상품명 · 공급가액 · 부가세 · 합계 · 결제수단 · 전표 링크 · 취소 여부
receipt_url은 나이스가 호스팅하는 선택 필드다. 영구 보존을 보장할 수 없으므로 증빙의 1차 원천은 우리 DB(승인번호·공급가액·부가세)와 자체 월별 명세서로 둔다.
4. PG 연동 (나이스페이먼츠)
| 항목 | 값 |
|---|---|
| 과세 구분 | taxFreeAmt = 0 또는 미전송 → 전액 과세 → 전표에 공급가액·부가세 자동 분리 |
| (v1 구모듈) | SupplyAmt·GoodsVat·ServiceAmt·TaxFreeAmt 직접 계산 전송. 합계 = Amt |
| 상품명 | goodsName = "Leyve 선결제" (40 byte, 구모듈은 euc-kr) |
| 통화 | currency = KRW |
| 에스크로 | useEscrow = false |
| 취소 | PartialCancelCode = 0 · CancelAmt · Moid로 중복취소 방지 · 응답 RemainAmt 검증 |
| 자동충전 | 카드 빌링 API(빌링키) |
부분취소 별도 계약은 v1에 필요 없다. 트랙 A가 미사용 전액 취소만 하기 때문이다.
⚠️ taxFreeAmt를 면세로 잘못 잡으면 고객이 매입세액 공제를 받지 못한다. 구조 전체가 이 한 값에 걸려 있다.
⚠️ 거래조회는 반드시 서버에서. Secret Key를 브라우저에 노출하지 않는다.
5. 소진 순서
1. 적용 가능한 재원만 필터 (applicability, 만료, 공유 허용 여부)
2. GRANT — 만료 임박 순 → 적용 범위 좁은 순
3. PAID — 먼저 충전된 lot 순설정 없음. 관리자가 바꿀 수 없다.
6. 환불 계약
6-1. 트랙
| 조건 | 처리 | 세무 | |
|---|---|---|---|
| A | 미사용 전액 + 결제일부터 90일 이내 | 셀프 → PG 전체 취소 | 조치 없음 |
| B | 부분 환불 / 90일 초과 / A 실패 | 신청 → 심사 → 계좌 송금 | 예외 처리 |
기간이 지나면 환불 불가로 처리하지 않는다. 창구 구분일 뿐 권리는 유지된다.
6-2. 산식
환불액 = 해당 lot의 미사용 잔액 × refund_ratio원 단위는 조직에게 유리하게 절상. 여러 lot이면 조직이 지정 가능하고, 자동 배분은 환불액을 감소시키지 않는 방향으로만 한다.
7. 불변식
- 잔액 차감은 원자적. 동시 요청으로 음수 잔액이나 이중 소진이 발생하면 안 된다
sum(ledger_entries.amount)=funding_lots.balance— 항상 일치refundable_cash_remaining=balance × refund_ratio— 파생값, 저장하지 않아도 됨- GRANT는 환불 대상이 아니다
- 이미 적용된 과거 사용은 만료·회수로 소급 재계산하지 않는다
- lot의
policy_version은 생성 후 변경되지 않는다. 약관 변경이 기존 lot에 소급되지 않게 하는 장치 - 멱등키(요청 ID)로 재시도 중복 차감을 막는다
- 차지백 시 과거 사용 기록을 삭제하지 않는다. lot 반전 + 미수금 생성
8. 엣지 케이스
| 상황 | 처리 |
|---|---|
| 잔액 0 | 유료 기능 즉시 차단. 여신 없음. 알림·자동충전으로 예방 |
| 자동충전 실패 | 즉시 알림. 카드 유효기간 만료 30일 전 사전 통지 |
| 차지백 | 분쟁 금액 범위에서 lot 반전 + 미수금 청구 + 서비스 제한. 사전 통지·소명. 다른 조직 재원에서 상계 금지 |
| 카드 명의 ≠ 조직 명의 | 트랙 A는 원 결제수단(그 카드)으로, 트랙 B만 조직 명의 계좌로 |
| Workspace 삭제 | 잔액·크레딧은 Organization에 귀속되어 유지. 사용 이력 보존 |
| 크레딧 회수 | 오발급·중복발급·부정취득·자격요건 불충족·원 거래 취소에 한정. 미사용분만. 사전 통지·소명. 이미 사용된 금액을 음수 잔액으로 만들지 않는다 |
| PG 취소 실패 | 트랙 B로 전환 |
| 중복 충전·과오차감 | 조직 청구 없이도 회사가 정정. 수수료 부과 금지 |
9. 기존 문서와의 정합 — AI 크레딧
ai-credit-metering.md는 월 정액 플랜 크레딧(Free 100 / 기본 300 / AI Standard 2,000 / AI Pro 8,000)을 정의한다. 선결제 잔액과 충돌하지 않고 층으로 쌓인다.
플랜 포함 크레딧 funding_lots(kind=GRANT, reason=plan_allowance,
expires_at=해당 월 말일) ← 매월 리셋
↓ 소진되면
선결제 잔액 funding_lots(kind=PAID)
↓ 소진되면
차단소진 순서 §5(GRANT 먼저, 만료 임박 순)가 그대로 적용되므로 별도 규칙이 필요 없다. 월말 만료가 가장 임박한 GRANT이므로 자연히 먼저 소진된다.
ai-credit-metering.md의 나머지 계약(요청 ID 멱등키, 공급자 실패 시 환불 이벤트 append-only, 클라이언트 전송 토큰·비용 불신뢰, Edge Function 정규화)은 그대로 유효하며 본 문서의 §7 불변식과 일치한다.
미확정: 플랜 포함 크레딧의 잔량이 월말에 이월되는지 소멸하는지. expires_at = 월말로 두면 소멸이다. 오너·개발 협의 필요.
10. 미해결 · 확인 필요
| # | 항목 | 상태 |
|---|---|---|
| 1 | 세무대리인 서면 검토 — 카드전표 세무 완결 구조, 시점차 관리 | 통과 가정 중. 뒤집히면 §3 전체가 월 세금계산서 발행 모델로 회귀 |
| 2 | 나이스 goodsName 카드전표 표기 | 가능 가정 |
| 3 | 나이스 빌링키 계약 | 가능 가정 |
| 4 | 최소 충전 금액 | 미정. 팝빌 11,000원 참고 |
| 5 | 플랜 포함 크레딧 이월 여부 | 미정 (§9) |
| 6 | 이관 lot의 증빙·환불 경로 | 이관 lot에는 우리 시스템의 카드 매출전표가 없다. receipt_url 없음 → 이관 명세로 대체, 환불은 트랙 B만(refund_track = B_ONLY). 기존 체계에서 발행된 세금계산서 번호가 있으면 legacy_tax_invoice_no로 보존 |