도움말 · 2026-09-24 최종본 (종합감사 v1.2 F01~F16 반영)
충전·정산 안내
운영사 ↔ 회원사 정산, 무엇이 어떻게 기록되나. 충전·결제·증빙·환불·조정이 한 원장에 남고, 증빙은 동서물류가 팝빌로 냅니다. 팝빌↔동서물류 요금은 별건입니다.
규칙 한 줄씩
- 충전 금액
- 1,000원 ~ 5,000,000원, 1,000원 단위
- 세액
- 총액 기준: 공급가액 = 총액×10/11 반올림, 세액 = 총액 − 공급가액 (10% 재계산 금지)
- 증빙
- 카드 = 카드전표(증빙 없음) · 계좌이체 = 현금영수증 또는 세금계산서 필수
- 결제 상태
- REQUESTED=결제 대기 · APPROVED=결제 승인 · DECLINED=결제 거절 · CANCELLED=취소·환불
- 증빙 상태
- NONE=해당 없음 · PENDING=발행 대기 · ISSUING=발행 중(팝빌 응답 대기) · ISSUED=발행 완료 · FAILED=발행 실패 · REVOKING=취소 발행 중 · REVOKED=취소 발행 완료 · REVOKE_FAILED=취소 발행 실패 · VOID=취소로 무효(발행 전) · EXPIRED=만료(식별번호 파기)
누가 무엇을
| 역할 | 할 수 있다 | 할 수 없다 |
|---|---|---|
| 회원사 구성원 | 충전 요청(카드·계좌이체) · 카드 결제(모의 PG) · 결제 전 철회 · 발행 전 건의 취소(내부 환불) · 자기 회사 원장·충전 내역·CSV · 문의(클레임) 접수 | 증빙 발행·재발행·취소 발행 · 발행된 증빙이 있는 건의 취소 · 문서번호가 남은 실패 건의 취소 · 다른 회사 조회 · 팝빌 화면 조회·발행 |
| 운영자(operator) | 팝빌 화면 조회(예금주·문서 상태·장부·문서 표) · 테스트 환경 발행 시험 | 회원사 입금 확인·조정·증빙 기록 · 전 회사 장부 |
| 운영사 관리자(admin) | 입금 확인 → 지갑 반영 → 증빙 발행 · 증빙 재발행·취소 재시도(끊긴 시도 승계) · 발행된 건 취소(내부 환불 + 취소 발행) · 수기 조정(사유 필수 · 원장 ADJUST) · 검색·통계·건별 이력·클레임 처리·CSV · 팝빌 문서 동기화 · 식별번호 파기 · 전 회사 팝빌 장부 | 원장 없이 지갑 바꾸기(어긋남으로 드러남) · 회원 토큰으로 카드 확정(서버 자격만) |
카드 충전(모의 PG)
- 충전 요청 저장 → 결제 대기(REQUESTED)
- 모의 승인기가 승인/거절을 정한다(4242 승인 · 4000 0000 0000 0002 거절). 카드번호는 뒤 4자리만 남는다.
- 승인이면 서버 자격(service_role) 이 DB 확정을 부른다 — 회원 토큰이 "승인" 이라고 말해도 DB 는 믿지 않는다(F02). 서버 자격이 없으면 503 으로 멈추고 요청은 결제 대기로 남는다.
- 확정 = 지갑 +금액 · 원장 TOPUP 한 줄(PG 거래번호 중복이면 PG_TID_REUSED). 증빙은 없다(카드전표).
계좌이체 충전 + 증빙
- 충전 요청 저장(현금영수증 또는 세금계산서를 고른다). 식별번호(휴대전화·사업자번호)는 목록 응답에 실리지 않는다.
- 운영사 관리자가 입금 확인 → 지갑 +금액 · 원장 TOPUP.
- 증빙 발행: DB 가 문서번호를 예약하고(ISSUING) → 팝빌 호출 → 결과 기록(ISSUED/FAILED). 발행이 끝나면 식별번호를 지운다.
- 실패하면 돈은 그대로, 상태만 FAILED. 관리자가 "재발행" 을 누르면 이전 문서번호를 팝빌에서 먼저 조회해 이미 있으면 승계하고 없을 때만 새로 낸다(이중 발행 방지).
취소 · 내부 환불 · 취소 발행
- 환불은 지갑으로 되돌리는 내부 환불이다 — 실제 카드 취소·계좌 송금은 하지 않는다(원장 REFUND, payout=INTERNAL).
- 잔액이 충전액보다 적으면 환불하지 않는다(REFUND_EXCEEDS_BALANCE). 이미 쓴 돈이다.
- 발행 중·취소 발행 중(ISSUING/REVOKING)에는 아무도 무르지 않는다(EVIDENCE_IN_FLIGHT). 10분 지나면 관리자가 이전 번호를 조회한 뒤 이어 간다.
- 문서번호가 남은 실패 건은 관리자가 팝빌에 문서가 없음을 확인한 뒤에만 무른다(PRIOR_DOC_UNCHECKED). 있으면 승계해 ISSUED 로 적고 취소 발행으로 간다.
- 발행된 증빙이 있으면 환불 뒤 취소 발행: 현금영수증 취소, 세금계산서는 전송 전이면 발행취소 · 전송 성공이면 수정(계약의 해제) 발행.
운영사 도구
- 검색: 충전 건 번호·문서번호·승인번호·PG 거래번호·입금자·회원사명 · 기간 · 상태.
- 통계: 월별·회사별 충전/차감/환불/조정, 증빙 성공·실패, 팝빌 호출·차감 대사, 지갑 합계.
- 건별 이력: 요청·결제·증빙 이력·원장·팝빌 문서·시도·클레임·감사를 시간순 한 줄기로.
- 클레임: 회원이 열고 운영사가 처리 중 → 종결(조치 결과 필수).
- 문서 동기화: 열린 팝빌 문서(300 전송 전 등)를 다시 읽어 304/305/600 으로 종결. QStash 크론이 하루 한 번 같은 일을 한다.
- 식별번호 파기: 30일 넘게 실패로 남은 건의 휴대전화·사업자번호를 지우고 만료(EXPIRED)로 둔다.
시험 절차 기록으로 판정 · /billing 맨 위 절차표가 같은 것을 센다
| # | 누가 | 무엇을 | 무엇을 증명 |
|---|---|---|---|
| 1 | shipper@poc8.demo | 카드 4242 로 10,000원 충전 → 지갑 +10,000 · 원장 TOPUP | BIL-21 · F02(서버 자격 확정) |
| 2 | shipper | 카드 4000 0000 0000 0002 → 거절로 기록, 지갑 그대로 | BIL-22 |
| 3 | shipper | 계좌이체 + 현금영수증(휴대전화) 요청 → 입금 대기 | BIL-23 · F15 |
| 4 | admin@poc8.demo | 입금 확인 → 지갑 반영 → 현금영수증 발행 완료(승인번호) · 식별번호 지워짐 | BIL-24 · F06 |
| 5 | shipper | 계좌이체 + 세금계산서 요청 → admin 입금 확인 → 발행(공동인증서 필요) | BIL-24 |
| 6 | shipper | /directory 사업자 조회 1건 → 원장 CHARGE(BIZ_LOOKUP) | BIL-20 · F01(정책 단가) |
| 7 | shipper | 카드 충전 건 취소 → 내부 환불 · 원장 REFUND | BIL-26 · F07 |
| 8 | admin | 이체 건 취소 → 환불 + 취소 발행(현금영수증 취소) | BIL-26 · F03 |
| 9 | admin | 수기 조정 +1,000(사유) → 원장 ADJUST · 지갑=원장 마지막 잔액 | F04 · F05 |
| 10 | shipper | 클레임 접수 → admin 처리 중 → 종결(조치 결과) | F10 |
| 11 | admin | 검색·통계·건별 이력·CSV·문서 동기화 | F10 · F12 |
오류 코드 → 뜻 → 조치
| 코드 | 뜻 | 조치 |
|---|---|---|
APPROVED_MEMBER_REQUIRED | 승인 회원 로그인이 아니다 | 승인된 계정으로 로그인 |
PROGRAM_ADMIN_REQUIRED | 운영사 관리자만 하는 일 | admin 계정으로 |
STAFF_REQUIRED | 운영자·관리자만 부르는 팝빌 화면 | operator/admin 계정으로 |
NOT_YOUR_TOPUP | 다른 회사의 충전 건 | 자기 회사 건만 |
NOT_YOUR_COMPANY | 다른 회사 앞으로 차감 시도 | 부담 주체를 내 회사로 |
SERVICE_ROLE_NOT_CONFIGURED | 카드 확정용 서버 자격이 없다(503) | Vercel 에 SUPABASE_SERVICE_ROLE_KEY 설정 — 소유자가 직접 |
SERVICE_ROLE_REQUIRED | 회원 토큰으로 카드 확정을 시도했다 | 서버 경로로만 — 화면은 이 경로를 쓰지 않는다 |
PG_TID_REQUIRED | PG 거래번호 없는 승인 | 모의 PG 응답을 확인 |
PG_TID_REUSED | 같은 PG 거래번호가 다른 건에 이미 있다 | 이중 확정 의심 — 건별 이력 확인 |
TOPUP_NOT_PENDING | 이미 처리된 건에 다시 확정 | 새 충전으로 |
REFUND_EXCEEDS_BALANCE | 잔액이 충전액보다 적다(이미 사용) | 환불 불가 — 클레임으로 조정 요청 |
EVIDENCE_IN_FLIGHT | 증빙 발행·취소 발행이 진행 중(10분 안) | 잠시 뒤 다시 |
ADMIN_CANCEL_REQUIRED | 발행된 증빙이 있거나 확인이 필요한 건 | 운영사 관리자가 처리 |
PRIOR_DOC_UNCHECKED | 문서번호가 남은 실패 건을 확인 없이 취소 | 관리자 화면에서 취소(팝빌 조회가 먼저 돈다) |
PRIOR_DOC_UNRESOLVED | 이전 문서가 있는데 금액이 다르다 | 팝빌 화면에서 문서 상태 확인 뒤 수동 처리 |
EVIDENCE_KEY_MISMATCH | 시도한 번호와 다른 번호로 발행 완료를 적으려 했다 | 건별 이력·시도 표 대조 |
EVIDENCE_TRANSITION_INVALID | 전이표에 없는 상태 변경 | 현재 상태 확인 |
AMOUNT_EXCEEDS_POLICY | 건수 × 정책 단가를 넘는 차감 | 정책 단가 확인 |
IDENTITY_NOT_PHONE | 소득공제용에 휴대전화 11자리가 아니다 | 휴대전화번호로(주민번호 불가) |
IDENTITY_NOT_BIZNO | 지출증빙용에 사업자번호 10자리가 아니다 | 사업자등록번호로 |
REASON_REQUIRED | 조정·취소 사유가 없다 | 사유를 적는다 |
RESOLUTION_REQUIRED | 클레임 종결에 조치 결과가 없다 | 조치 결과를 적는다 |
ATTEMPT_NOT_RECORDED | 시도 기록을 못 남겨 팝빌을 부르지 않았다 | DB 상태 확인 뒤 재시도 |
종합감사(2026-09-23) 지적 → 조치
| # | 지적 | 조치 |
|---|---|---|
F01 | 공용 차감(billing_charge)을 아무 회원이나 남의 회사 앞으로 부를 수 있었다 | authenticated 실행권 회수 · dispatch_charge 가 회사 대표 권한과 정책 단가를 검사 |
F02 | 카드 확정이 호출자의 "승인" 을 믿었다 | 서버 자격(service_role)만 카드 확정 · 없으면 503 · PG 거래번호 필수·중복 금지 |
F03 | 타임아웃 뒤 발행됐을 수 있는 실패 건을 그냥 무를 수 있었다 | ISSUING/REVOKING 상태 · 10분 규칙 · 취소 전 팝빌 조회(prior_checked) |
F04 | 관리자 크레딧 직접 저장이 원장에 남지 않았다 | member_billing_save 가 ADJUST 원장 + 감사 기록 · 화면에 사유 칸 |
F05 | 지갑과 원장 마지막 잔액의 어긋남을 보지 않았다 | 연속성 대조에 wallet_mismatch · 회사별 표에 원장 마지막 잔액 |
F06 | 문서번호를 앱이 시각으로 만들어 중복·경합이 가능했다 | DB popbill_attempt 가 base36 순번으로 예약 · 멱등 키 · 화면은 시도 번호를 응답 뒤에만 교체 |
F07 | "환불" 이 실제 지급처럼 읽혔다 | 내부 환불(지갑) · payout=INTERNAL · 화면 문구 |
F08 | 팝빌 결과를 DB 에 못 적으면 결과가 사라졌다 | record_error 를 응답과 화면에 · 다음 재발행이 그 번호를 조회해 승계 |
F09 | 옛 NOTIFY/FAX 원장 줄에 kind/product 가 없어 통계에서 빠졌다 | ledger_classify 트리거 + 소급 분류 |
F10 | 건별 이력·클레임·문서 상태 추적이 없었다 | popbill_document 표 · 건별 이력 화면 · 클레임 표 · 동기화 |
F11 | 예금주 조회 결과(이름)가 장부에 그대로 남았다 · 열람 기록 없음 | result_masked(첫 글자) · view_log 열람 기록 |
F12 | 검색·통계·CSV·페이지 없이 전체를 한 번에 읽었다 | 검색·통계·CSV·더 보기(offset/limit) |
F13 | 실패 건의 식별번호가 무기한 남았다 | 30일 뒤 파기 → EXPIRED · 크론 |
F14 | 원장 순서를 created_at 과 uuid 로 놓아 동시 거래에서 없는 끊김 | seq 순서(2026-09-23 05:50Z 이후) · 옛 줄은 시각 |
F15 | 소득공제용에 주민번호 13자리가 들어갈 수 있었다 | 휴대전화 11자리만(IDENTITY_NOT_PHONE) |
F16 | 팝빌 화면이 역할과 무관하게 열려 403 이 뒤늦게 났다 | can_read/can_issue 로 단추 개폐 · POST 는 STAFF_REQUIRED |
운영 전제
- 외부 연동은 전부 무료 계정이다. 실 PG(토스 등)는 가맹 계약이 필요해 소유자 요청 시에만 붙인다 — 지금은 모의 승인기(MOCK).
- 회원 카드 확정에는 Vercel 환경변수 SUPABASE_SERVICE_ROLE_KEY 가 필요하다(소유자가 직접 넣는다 — 코드·문서에 값은 없다). 없으면 회원 카드 충전은 503 으로 닫히고 계좌이체만 열린다.
- 팝빌은 테스트 환경(국세청 실제 신고 없음). 세금계산서는 팝빌에 공동인증서가 등록되어야 발행된다.
- 문서 동기화·식별번호 파기 크론은 QStash 스케줄이 /api/cron/popbill-sync 를 두드린다(서명 검증 · 서버 자격 필요).
- 팝빌 문의 글에는 경로·URL·JSON 을 넣지 않는다(웹방화벽 차단). 문서 질문은 팝빌 문서 MCP 로 먼저.
문서
docs/AUDIT-RESPONSE-20260924.md— 감사 F01~F16 → 조치·증거docs/MEMBER-SETTLEMENT-20260923.md— 설계·실측·리뷰·시행착오docs/RUNBOOK.md §4-13— 운영 절차(재발행·취소·크론·자격)docs/AUDIT-REQUEST-20260923.md— 감사 요청서(v2)