Skip to content

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개월 미사용 시 안내만(이용 차단 없음)
6Workspace별 사용 한도없음. 공유 허용/차단 flag 하나만
7잔액 0즉시 차단. 법정 기한 기능도 예외 없음. 여신 없음
8환불트랙 A(미사용 전액 + 90일, 셀프) / 트랙 B(그 외, 신청)
9교차 후원조직 내에서만. 리셀러·제3자 대납 없음

2. 데이터 모델

2-1. 객체

객체역할
organizations (기존)계약·결제·증빙의 유일 주체. billing profile 보유
workspaces (기존)소비 단위. shared_funding_enabled 하나만 추가
funding_lots재원 1건
ledger_entriesappend-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_amountface_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 필수)

세금계산서를 주지 않으므로 아래 셋이 고객의 증빙 경로 전부다.

  1. 충전 전표receipt_url이 있을 때만 조회 버튼 노출. 없으면 서버에서 거래조회로 재획득
  2. 취소 전표 — 환불 시 cancel_receipt_url 제공. 고객이 이미 공제받은 매입세액을 되돌리는 근거
  3. 월별 충전 내역 명세서 — 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. 불변식

  1. 잔액 차감은 원자적. 동시 요청으로 음수 잔액이나 이중 소진이 발생하면 안 된다
  2. sum(ledger_entries.amount) = funding_lots.balance — 항상 일치
  3. refundable_cash_remaining = balance × refund_ratio — 파생값, 저장하지 않아도 됨
  4. GRANT는 환불 대상이 아니다
  5. 이미 적용된 과거 사용은 만료·회수로 소급 재계산하지 않는다
  6. lot의 policy_version은 생성 후 변경되지 않는다. 약관 변경이 기존 lot에 소급되지 않게 하는 장치
  7. 멱등키(요청 ID)로 재시도 중복 차감을 막는다
  8. 차지백 시 과거 사용 기록을 삭제하지 않는다. 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로 보존