근거 본 정의서는 로컬 실 DB nestpay(MariaDB, docker nestpay-mariadb)의 information_schema 실측값과 설계 원본 db/schema.dbml의 컬럼 주석을 대조하여 작성하였습니다. 모든 수치(테이블·컬럼·인덱스·트리거·파티션·시스템 버저닝)는 조회 시점의 실제 DB 상태입니다.
공통 규약 — 금액은 DECIMAL(15,0) KRW 정수, 시각은 DATETIME(KST 단일), 문자셋 utf8mb4. 개인정보는 *_enc=AES-256-GCM 암호문, *_hash=검색·UNIQUE용 HMAC-SHA256(char(64)). 비밀번호·PIN·API시크릿은 Argon2id(varchar(255)).
1. 요약 — 테이블 구성 총계
69
전체 테이블 (도메인 68 + flyway_schema_history)
52
BASE TABLE (일반 테이블, flyway 포함)
17
SYSTEM VERSIONED (시스템 버저닝)
테이블 유형 구분
- BASE 일반 테이블 — 원장·거래·로그·집계 등. 총 52개(도메인 51 + 마이그레이션 관리
flyway_schema_history 1).
- VERSIONED 시스템 버전 테이블 —
WITH SYSTEM VERSIONING. 변경 이력을 DB가 자동 보존(그 시점 정책·마스터 값 증빙). 총 17개: 마스터(users·merchants·merchant_accounts·cards·bank_accounts) + 정책류(fee_policies·limit_policies·payout_policies·cancel_policies·card_quota_policies·point_expiry_policies·global_settings·fds_rules) + 인증·연동(admin_accounts·admin_allowed_ips·deposit_identifiers·merchant_api_credentials).
- PART 월 RANGE 파티션 테이블 — 복합 PK
(id, created_at). 보존기간 경과분은 DROP PARTITION(O(1))으로 삭제. 총 7개: ledger_entries·notification_inbox·external_api_logs·reject_logs·app_error_logs·login_histories·pii_access_logs.
transactions·deposit_notices는 비파티션 확정 — MariaDB는 파티션 테이블의 모든 UNIQUE 키에 파티션 컬럼 포함을 강제하는데, 멱등성 UNIQUE(ux_txn_idem·ux_notice_ref)에 created_at을 넣으면 멱등 보장이 깨지므로 무결성을 우선함(실 DB 검증으로 확정).
외부연동 스텁 상태(계약 전) 확인필요/미구현
펌뱅킹(출금이체·잔액조회), 본인인증(CI/DI), 은행 실명조회·1원인증, 발주사 PG 가상계좌 입금통지는 계약·규격서 확정 전으로 스키마만 완비된 스텁 상태입니다. 관련 컬럼(deposit_identifiers.kind/value, deposit_notices.source, bank_tran_ref, ci_hash·di_hash 등)의 최종 규격은 규격서 회신 후 고정됩니다. 포인트 전환(exchange_providers, EXCHANGE_*)은 설계 유지·구현 보류 상태입니다.
2. 도메인 그룹별 테이블 목록
68개 도메인 테이블을 업무 도메인 12개 그룹으로 분류. 유형: BASE VERSIONED PART · ★ = 아래 3장에 컬럼 전체 수록(핵심 20개 테이블).
2.1 회원·인증 (10)
| 테이블 | 유형 | 용도 |
| users ★ | VERSIONED | 회원 마스터. 본인인증 실명·CI·전화(암호화), 1인 1활성계정 강제(active_ci UNIQUE), 탈퇴 후 법정보존·파기 일정. |
| account_closure_requests | BASE | 회원 탈퇴 신청·승인 대장. 승인 시 WITHDRAWN+포인트 소멸, 소멸금액 기록. |
| auth_pins | BASE | 간편로그인+거래인증 PIN(Argon2id). 계정 3종 공용(principal_type), 5회 초과 잠금. |
| auth_passkeys | BASE | FIDO2/WebAuthn 패스키(자격증명·공개키). 계정 3종 공용. |
| used_challenges | BASE | 패스키 챌린지 1회용 소진 기록 — 등록·로그인·거래서명 replay 방지. |
| auth_devices | BASE | 기기 등록(설치 식별자). 동시 1기기(새 기기 등록 시 기존 REVOKED), FDS 다계정기기 역조회. |
| login_histories | PART | 로그인 이력(성공·실패·차단). 사용자 화면+보안감사, 월 파티션·5년 보존·방어 트리거. |
| bank_accounts ★ | VERSIONED | 은행 계좌(사용자·매장 공용). 계좌번호 암호화, 실명+1원인증, 변경 시 REMOVED 이력보존·24h 냉각. |
| rate_limit_rules | BASE | 동작별 호출 빈도 제한 규칙(관리자 조정). 창(초)·최대횟수·기준(IP/USER). |
| rate_limit_counters | BASE | 고정창 카운터(초과 시 429). 오래된 창은 배치 정리. |
2.2 카드·지갑·포인트 (3)
| 테이블 | 유형 | 용도 |
| cards ★ | VERSIONED | 앱 카드. 카드번호=지갑 식별자, number_hash UNIQUE로 재사용 영구차단, 발급+5년 만료, 재발행 체인. |
| wallets ★ | BASE | 지갑(USER_CARD·MERCHANT·SYSTEM). 잔액 캐시. 사용자·매장=FOR UPDATE 잠금, SYSTEM=일 배치 파생. |
| point_lots ★ | BASE | 포인트 로트(유상 DEPOSIT/무상 REWARD). 유효기간 FIFO 소진, 감소만 허용(방어 트리거). |
2.3 원장·거래 (3)
| 테이블 | 유형 | 용도 |
| transactions ★ | BASE | 거래 헤더(단일 테이블, type/subtype 분기). append-only, 사용자스코프 멱등키 UNIQUE, 각종 스냅샷. |
| ledger_entries ★ | PART | 복식 원장(DR/CR). 거래별 ΣDR=ΣCR, 잔액 체인, append-only 절대(월 파티션·방어 트리거). |
| lot_allocations ★ | BASE | 차감:로트 배분(N:M). FIFO 소진·취소 복원 근거. |
2.4 충전·입금 (4)
| 테이블 | 유형 | 용도 |
| deposit_identifiers | VERSIONED | 회원별 입금 식별자(가상계좌/입금자코드). 펌뱅킹 계약 확정 후 단일화 (규격 확인필요). |
| deposit_notices ★ | BASE | 입금통지(PG 웹훅·펌뱅킹·SMS). 소스별 결정적 중복키 UNIQUE, 원문 증거보존(비파티션·방어 트리거). |
| unmatched_deposits ★ | BASE | 미매칭 입금 — 즉시 UNMATCHED 시스템 지갑 계상(장부 밖 돈 없음). 관리자 재량 처리·반환. |
| deposit_accounts | BASE | 회사 수납 입금통장(관리자 등록·앱 충전화면 안내). |
2.5 선물 (1)
| 테이블 | 유형 | 용도 |
| gift_links ★ | BASE | 선물 링크. 선차감(GIFT_ESCROW 계상)·1회용 클레임 토큰(해시)·TTL 24h 만료 자동반환. |
2.6 매장·QR·PG결제 (8)
| 테이블 | 유형 | 용도 |
| merchants ★ | VERSIONED | 매장 마스터. 사업자번호(진위확인)·대표자 본인인증·부가세 모드/율. 심사 상태머신(대표자 변경=재심사). |
| merchant_accounts ★ | VERSIONED | 매장 로그인 계정(매장 1계정, 하위계정 미도입). |
| merchant_qrs ★ | BASE | 매장 QR(정적·동적·주문형). CPM/수취 QR은 Redis 단명 토큰(DB 미저장). |
| merchant_api_credentials ★ | VERSIONED | 매장 open API 자격증명. 시크릿 해시·키 롤 유예·웹훅 URL(암호화 시크릿)·LIVE 전환 체크리스트. |
| merchant_api_allowed_ips | BASE | 매장 open API 화이트 IP(매장 신청·관리자 승인). 승인분만 호출 허용. |
| merchant_documents | BASE | 매장 필수 서류(사업자등록증 등). 전부 제출되어야 승인 가능(중앙 파일 대장 참조). |
| pg_orders ★ | BASE | PG 온라인 결제 주문. (merchant_id, order_no) 멱등, 공급가/부가세, 샌드박스 원장 분리. |
| webhook_deliveries | BASE | 매장 발신 웹훅. 지수 백오프 재시도, 클레임 토큰으로 2대 서버 중복발송 방지. |
2.7 정산·명세 (3)
| 테이블 | 유형 | 용도 |
| monthly_statements | BASE | 카드 월 명세(충전·출금·결제·선물·소멸·수수료·월말잔액). 월 단위 대사 겸용. |
| merchant_monthly_statements | BASE | 매장 월 정산서(카드사 동일 수준). 매출·취소·공급가·부가세·수수료·정산·월말잔액. |
| merchant_daily_summaries | BASE | 매장 일 차트 집계(멱등 재계산). 과거=본 테이블, 당일=실시간, 접합은 UNION. |
2.8 정책 (8)
| 테이블 | 유형 | 용도 |
| fee_policies ★ | VERSIONED | 수수료 정책(유형·범위). 정률+정액 합산, 거래에 스냅샷 저장. |
| limit_policies | VERSIONED | 한도 정책(충전·보유·출금·선물·결제 × 건당/일/월/보유). 사용자 전 카드 합산 집행. |
| payout_policies ★ | VERSIONED | 정산/출금 대기(+N일 HH시). 매장 기본 delay=0(즉시 정산), 값만 바꾸면 대기 적용. |
| cancel_policies | VERSIONED | 결제 취소 가능기간(N일). 거래에 cancelable_until 스냅샷. |
| card_quota_policies | VERSIONED | 카드 발급 수량 상한(ACTIVE 카드 기준). 전역·사용자별. |
| point_expiry_policies | VERSIONED | 포인트 유효기간(DEPOSIT/REWARD 개월). 충전성 값은 법무 확인 후 설정. |
| global_settings | VERSIONED | 단일값 전역 설정(재가입 대기·점검 모드·앱 최소버전·냉각시간 등). 변경 이력 자동 보존. |
| exchange_providers | BASE | 포인트 전환 공급자·환율. 설계 유지·구현 보류(#49). |
2.9 FDS(이상거래 탐지) (2)
| 테이블 | 유형 | 용도 |
| fds_rules | VERSIONED | 탐지 룰 8종(통과·선물집중·자가결제·취소남용 등). 임계값 JSON, 배포 없이 관리자 조정. |
| fds_alerts | BASE | 탐지 경보. 근거 JSON(증거보존), 처리 상태(OPEN→REVIEWED→ACTIONED/DISMISSED). |
2.10 콘텐츠·고객지원·약관 (7)
| 테이블 | 유형 | 용도 |
| notices | BASE | 공지·이벤트(대상 앱·강제팝업·이미지·링크·게시기간). |
| banners | BASE | 앱 배너(노출 위치·이미지·링크·정렬·게시기간). 중앙 파일 대장 참조. |
| faqs | BASE | FAQ(카테고리·질문·답변·정렬·게시 상태). |
| inquiries | BASE | 1:1 문의(사용자·매장). 답변 시 푸시 통지, 미답변 큐. |
| inquiry_attachments | BASE | 문의 첨부 이미지(최대 5장/장당 10MB). 중앙 파일 대장 참조. |
| policy_documents | BASE | 약관·개인정보·탈퇴안내 문서(버전·시행일·재동의 여부). 과거 버전 영구보존. |
| policy_agreements | BASE | 약관 동의 이력(분쟁 대비 증빙). 주체×문서 UNIQUE. |
2.11 알림·푸시 (5)
| 테이블 | 유형 | 용도 |
| notification_settings | BASE | 이벤트별 알림 발송 설정(채널 INBOX/PUSH/BOTH·심각도). 관리자 조정, OutboxWorker 참조. |
| notification_inbox | PART | 앱 알림함. 푸시 발송과 동일 outbox 작업에서 원자적 기록, 월 파티션·30일 보존. |
| push_tokens | BASE | FCM 토큰(계정 3종 공용·플랫폼별). |
| push_campaigns | BASE | 푸시 캠페인(대상·광고성·예약·발송/실패 집계). 광고성=동의자만+야간 차단. |
| push_campaign_targets | BASE | 지정 발송 대상+개별 발송 결과(도달 분석). JSON 금지 원칙의 연결 테이블. |
2.12 관리자·감사·운영·로그·대사 (14)
| 테이블 | 유형 | 용도 |
| admin_accounts | VERSIONED | 관리자 계정(OTP 의무·잠금). 관리자 전원 동등, 변경 이력 보존. |
| admin_allowed_ips | VERSIONED | 관리자 접근 허용 IP/CIDR(공통·개인 규칙). 규칙 관리·이력 보존. |
| files | BASE | 중앙 파일 대장 — 모든 업로드는 이 한 테이블로만, 타 테이블은 file_id 참조. 실물은 오브젝트 스토리지. |
| audit_logs | BASE | 관리자 행위 감사(변경 전후 JSON·사유 필수). append-only(방어 트리거). |
| pii_access_logs | PART | 개인신용정보 접근기록(감독규정). *_enc 복호화 표시 API 전부에 기록, 월 파티션·5년 보존·방어 트리거. |
| outbox_jobs ★ | BASE | 비동기 작업 아웃박스 — 원장 트랜잭션과 같은 커밋에 INSERT(후속작업 유실 불가). 클레임 토큰 방식. |
| external_api_logs | PART | 대외 호출 전건(요청·응답 마스킹 보존). 리컨실러·분쟁 판정 근거, 월 파티션·방어 트리거. |
| reject_logs | PART | 거래 생성 전 거절 시도 기록(CS 즉답·FDS 열거 탐지). 월 파티션·방어 트리거. |
| app_error_logs | PART | 서버 예외 요약(운영자 화면·알림). 전체 스택은 파일 로그, trace_id 연결. 월 파티션. |
| integrity_check_runs | BASE | 무결성 검증 실행 이력(복식·잔액체인·격리 등 9종). 모니터링 대시보드 원천. |
| integrity_findings | BASE | 이상 건 개별 추적(발견→역분개 조정 해소). 기대값 vs 실제값. |
| daily_wallet_snapshots | BASE | 희소 일 잔액 스냅샷(감사 확정). 체인 검증 재개점, ODKU 멱등 적재. |
| daily_summaries | BASE | 전역 일 지표(충전·결제·신규회원 등). 대시보드·추이 그래프 원천. |
| hourly_summaries | BASE | 전역 당일 시간대 추이(시간 배치·90일 보존). |
※ 위 68개 외 flyway_schema_history(BASE)는 Flyway 마이그레이션 적용 이력 관리용 시스템 테이블입니다.
3. 핵심 테이블 컬럼 정의 (20개)
NULL 열: N=NOT NULL(필수), Y=NULL 허용. 키 열: PK기본키 UQ유니크 IX인덱스/FK. 설명은 실 DB 컬럼 코멘트 및 schema.dbml 주석 기준.
3.1 users — 회원 마스터 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 회원 고유번호(auto_increment) |
| login_id | varchar(20) | N | UQ | 영숫자 4~20, 소문자 정규화 저장 |
| password_hash | varchar(255) | N | | Argon2id 해시 |
| name | varchar(100) | N | | 본인인증 실명 |
| name_norm | varchar(100) | N | | 이름 비교용 정규화(공백·특수문자 제거, 로마자화) |
| ci_hash | char(64) | N | IX | 본인인증 CI HMAC. 재가입 대기 판정 |
| active_ci | char(64) | Y | UQ | ACTIVE일 때만 ci_hash 복사 — UNIQUE로 1인 1활성계정 강제 |
| di_hash | char(64) | Y | | 본인인증 DI HMAC |
| phone_enc | varbinary(64) | N | | 전화번호 AES-256 암호문 |
| phone_hash | char(64) | N | IX | 선물 대상 검색용 HMAC |
| birth_date | date | Y | | 본인인증 결과. 성인 전용 검증 |
| gender | varchar(1) | Y | | 성별 M|F (본인인증 결과) |
| avatar_file_id | bigint | Y | IX | 프로필 사진(files.id 참조). NULL=기본 아이콘 |
| marketing_agree | tinyint(1) | N | | 광고성 푸시 수신동의(기본 0) |
| status | varchar(20) | N | IX | ACTIVE|SUSPENDED|WITHDRAWN (기본 ACTIVE) |
| kyc_status | varchar(20) | N | | VERIFIED|PENDING(관리자 사전생성 - 앱서 본인인증 대기) |
| withdrawn_at | datetime | Y | | 탈퇴 시각 - 재가입 대기 판정 기준 |
| destroy_due_at | date | Y | | 개인정보 파기 예정일 = 탈퇴+법정보존 |
| anonymized_at | datetime | Y | | 파기(익명화) 완료 시각 - phone_enc·name·birth_date 무효화 |
| created_at | datetime | N | | 가입 일시 |
3.2 cards — 앱 카드 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 카드 고유번호 |
| user_id | bigint | N | IX | 소유 회원(users.id) |
| number_hash | char(64) | N | UQ | 재사용 영구 차단의 단일 진실. 삭제 금지 |
| number_enc | varbinary(64) | N | | 카드번호 암호문. BIN 972963 + 랜덤9 + Luhn, 16자리 |
| cvc_enc | varbinary(32) | N | | 고정 CVC. 표시용, 서버 암호화 보관 |
| issued_at | datetime | N | | 발급 일시 |
| expires_on | date | N | | 발급+5년 그 달 말일. 만료 시 재발행 절차 |
| design_code | varchar(20) | N | | 카드 디자인 코드(OCEAN|MIDNIGHT|SUNSET 등) |
| color_code | varchar(20) | Y | | 사용자 선택 색상 |
| is_primary | tinyint(1) | N | | 대표 카드 = 기본 수취(기본 0) |
| status | varchar(20) | N | | ACTIVE|SUSPENDED|REISSUED|EXPIRED (기본 ACTIVE) |
| reissued_to | bigint | Y | IX | 재발행 체인 참조(cards.id) |
| created_at | datetime | N | | 생성 일시 |
3.3 wallets — 지갑 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 지갑 고유번호 |
| owner_type | varchar(12) | N | IX | USER_CARD|MERCHANT|SYSTEM |
| card_id | bigint | Y | UQ | USER_CARD일 때만, 카드 1:1 |
| merchant_id | bigint | Y | UQ | MERCHANT일 때만. 정산 출금·취소 반환 전용 |
| system_code | varchar(30) | Y | UQ | SYSTEM일 때만: FEE_REVENUE|EXPIRED|GIFT_ESCROW|UNMATCHED|SETTLEMENT_CLEARING|FORFEITED|EXCHANGE_{제휴사} |
| balance | decimal(15,0) | N | | 잔액. CHECK: SYSTEM 제외 balance≥0. 사용자·매장=FOR UPDATE 잠금 지점, SYSTEM=일 배치 파생(기본 0) |
| created_at | datetime | N | | 생성 일시 |
CHECK: owner_type별 해당 참조 컬럼만 NOT NULL(마이그레이션 정의).
3.4 point_lots — 포인트 로트 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 로트 고유번호 |
| wallet_id | bigint | N | IX | 소속 지갑(wallets.id) |
| lot_type | varchar(10) | N | IX | DEPOSIT(유상: 결제·선물·출금·환급 가능)|REWARD(무상: 결제만). 환급은 소스 무관 전체 DEPOSIT 잔액 기준 |
| source | varchar(30) | N | | DEPOSIT|GIFT|REISSUE|EVENT|EXCHANGE:{code}(구현보류) - 이력 메타(환급 판정 미사용) |
| amount_init | decimal(15,0) | N | | 최초 적립 금액 |
| amount_remaining | decimal(15,0) | N | | 잔여. CHECK(0≤remaining≤init). 감소만 허용(방어 트리거) |
| expires_at | datetime | N | IX | 유효기간(필수). 재발행 이관은 승계, 선물 수취는 리셋 |
| origin_lot_id | bigint | Y | IX | 재발행 승계 시 원 로트 참조(point_lots.id) |
| created_at | datetime | N | | 생성 일시 |
3.5 transactions — 거래 헤더 BASE (비파티션·멱등 우선)
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 거래 고유번호 |
| txn_uid | char(26) | N | UQ | 대외 노출용 ULID |
| type | varchar(12) | N | IX | DEPOSIT|WITHDRAW|GIFT|PAYMENT|CANCEL|ADJUST|EXPIRE |
| subtype | varchar(20) | N | | VACCT|FIRMBANK|REISSUE|GIFT_LINK|GIFT_PHONE|GIFT_QR|QR_STORE|QR_ORDER|CPM|PG_ONLINE|SETTLEMENT|FORFEIT 등 |
| status | varchar(12) | N | IX | PENDING|HOLD|UNKNOWN|CONFIRMED|FAILED|REVERSED|CANCELED - 선차감·리컨실러 상태머신 |
| initiator_type | varchar(10) | N | IX | USER|MERCHANT|ADMIN|SYSTEM |
| initiator_id | bigint | N | | 주체 식별자(기본 0) |
| idempotency_key | varchar(100) | Y | UQ | UNIQUE(initiator_type, initiator_id, idempotency_key) - 사용자 스코프 멱등 |
| card_id | bigint | Y | IX | 거래 카드(cards.id) |
| counterparty_card_id | bigint | Y | IX | 선물 수신 카드 - 받은선물·gift_recv 집계·FDS 선물집중 키 |
| merchant_id | bigint | Y | IX | 매장(merchants.id) |
| amount | decimal(15,0) | N | | 거래 금액 |
| fee_amount | decimal(15,0) | N | | 수수료 금액(기본 0) |
| fee_rate_snap | decimal(7,4) | Y | | 적용 정률 스냅샷 |
| fee_fixed_snap | decimal(15,0) | Y | | 적용 정액 스냅샷 |
| vat_amount | decimal(15,0) | Y | | 결제 시점 부가세 스냅샷 |
| cancelable_until | datetime | Y | | 결제 시점 취소기한 스냅샷 |
| scheduled_at | datetime | Y | | 출금·정산 실행 예정 시각 - payout_policies 스냅샷(정책 변경 소급 방지) |
| related_txn_id | bigint | Y | IX | 역분개·이관·취소의 원거래 참조 |
| bank_tran_ref | varchar(64) | Y | IX | 펌뱅킹 거래 식별자 - 리컨실러 조회 키 |
| bank_account_id | bigint | Y | IX | 출금·정산 대상 계좌 스냅샷(이후 변경돼도 불변) |
| detail_id | bigint | Y | | 거래상세 PK 참조 - (type,subtype)이 상세 테이블 결정(재논의 예정) |
| pg_order_id | bigint | Y | | PG 온라인 결제 주문 참조(detail_id의 PG 사례) |
| fail_reason | varchar(30) | Y | | MERCHANT_INSUFFICIENT_BALANCE|CANCEL_WINDOW_EXPIRED 등 코드 |
| memo | varchar(200) | Y | | 선물 메시지(50자·금칙어 필터) 등 |
| created_at | datetime | N | IX | 생성 일시 |
| confirmed_at | datetime | Y | | 확정 일시 |
3.6 ledger_entries — 복식 원장 PART (월 파티션·append-only)
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 실PK는 (id, created_at) - 월 파티셔닝 |
| txn_id | bigint | N | IX | 거래(transactions) - 파티션 간 FK는 논리 참조(앱 강제) |
| wallet_id | bigint | N | IX | 대상 지갑(wallets.id) |
| direction | char(2) | N | | DR|CR. 거래별 ΣDR=ΣCR (복식·Java 검증+일 대사) |
| amount | decimal(15,0) | N | | 금액. CHECK(amount > 0) |
| balance_after | decimal(15,0) | Y | | 사용자·매장 지갑=NOT NULL 체인. SYSTEM 지갑 leg=NULL(핫로우 규약, 일 배치 SUM 검증) |
| created_at | datetime | N | PK | 생성 일시(파티션 키) |
3.7 lot_allocations — 차감·로트 배분 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 배분 고유번호 |
| ledger_entry_id | bigint | N | IX | 차감(DR) 엔트리. 취소 시 원 소진 로트 역추적 |
| lot_id | bigint | N | IX | 소진 로트(point_lots.id) |
| amount | decimal(15,0) | N | | 이 로트에서 차감한 금액 |
3.8 bank_accounts — 은행 계좌 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 계좌 고유번호 |
| owner_type | varchar(10) | N | IX | USER|MERCHANT |
| owner_id | bigint | N | | 소유자 식별자 |
| bank_code | varchar(10) | N | | 은행 코드 |
| account_enc | varbinary(128) | N | | 계좌번호 AES-256 암호문(법적 암호화 의무) |
| account_hash | char(64) | N | | 중복 등록 검사용 HMAC |
| holder_name | varchar(100) | N | | 실명인증 예금주 |
| holder_name_norm | varchar(100) | N | | 정규화 prefix 비교 |
| verified_at | datetime | Y | | 실명+1원인증 완료 시각(순서: 실명일치→1원인증) 외부연동 스텁 |
| status | varchar(20) | N | | ACTIVE|REMOVED - 변경 시 REMOVED 처리, 이력 보존(기본 ACTIVE) |
| cooldown_until | datetime | Y | | 계좌 변경 후 출금 냉각 24h |
| created_at | datetime | N | | 등록 일시 |
출금은 ACTIVE 계좌 1건으로만. ACTIVE 1건 보장은 앱 트랜잭션.
3.9 merchants — 매장 마스터 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 매장 고유번호 |
| biz_no | varchar(10) | N | UQ | 사업자등록번호(국세청 진위확인) 외부연동 스텁 |
| biz_name | varchar(200) | N | | 상호 |
| ceo_name | varchar(100) | N | | 대표자명 |
| ceo_name_norm | varchar(100) | N | | 대표자명 정규화 |
| ceo_ci_hash | char(64) | N | | 대표자 본인인증 CI |
| biz_type | varchar(20) | N | | CORP(법인)|SOLE(개인사업자) |
| category | varchar(50) | Y | | 업종 |
| address | varchar(300) | Y | | 주소 |
| vat_mode | varchar(10) | N | | TAXED|EXEMPT - 매장이 설정(기본 TAXED) |
| vat_rate | decimal(5,2) | N | | 부가세율(기본 10.00). 결제 시점 스냅샷의 원천 |
| status | varchar(20) | N | IX | PENDING|SUPPLEMENT|ACTIVE|REJECTED|SUSPENDED|CLOSED (기본 PENDING) |
| reject_reason | varchar(500) | Y | | 반려 사유 |
| supplement_note | varchar(500) | Y | | 자료 보충 요청 사유(SUPPLEMENT 상태 표시) |
| approved_by | bigint | Y | IX | 승인 관리자(admin_accounts.id) |
| approved_at | datetime | Y | | 승인 일시 |
| created_at | datetime | N | | 신청 일시 |
3.10 merchant_accounts — 매장 로그인 계정 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 계정 고유번호 |
| merchant_id | bigint | N | UQ | 소속 매장(매장 1계정, 하위계정 미도입) |
| login_id | varchar(20) | N | UQ | 로그인 아이디 |
| password_hash | varchar(255) | N | | Argon2id 해시 |
| status | varchar(20) | N | | 계정 상태(기본 ACTIVE) |
| created_at | datetime | N | | 생성 일시 |
3.11 deposit_notices — 입금통지 BASE (비파티션·멱등 우선·방어 트리거)
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 통지 고유번호 |
| source | varchar(10) | N | UQ | PGVACCT(PG 가상계좌 웹훅)|FIRMBANK(펌뱅킹 조회)|SMS(보조) 외부연동 스텁 |
| raw_text | text | Y | | SMS 원문 보존(증거 보존 - 절단 방지 TEXT) |
| dedup_key | char(64) | N | UQ | 소스별 결정적 중복키. SMS=HMAC, 펌뱅킹=bank_tran_ref 사본 |
| bank_tran_ref | varchar(64) | Y | | 펌뱅킹 거래 식별자 |
| parsed_amount | decimal(15,0) | Y | | 파싱 금액 |
| parsed_name | varchar(100) | Y | | 파싱 입금자명 |
| parsed_name_norm | varchar(100) | Y | | 정규화 prefix 매칭용 |
| identifier_value | varchar(64) | Y | | 가상계좌/입금자코드 파싱값 |
| status | varchar(20) | N | IX | NEW|MATCHED|UNMATCHED|IGNORED (기본 NEW) |
| matched_txn_id | bigint | Y | | 매칭 성사 시 DEPOSIT 거래 참조 |
| received_at | datetime | N | | 수신 일시 |
3.12 unmatched_deposits — 미매칭 입금 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 고유번호 |
| notice_id | bigint | N | IX | 원 입금통지(deposit_notices.id) |
| amount | decimal(15,0) | N | | 입금 금액 |
| status | varchar(20) | N | | PENDING|MATCHED|RETURNED|HOLD - 관리자 재량 처리(기본 PENDING) |
| resolved_by | bigint | Y | IX | 처리 관리자(admin_accounts.id) |
| resolved_at | datetime | Y | | 처리 일시 |
| resolution | varchar(20) | Y | | MANUAL_MATCH|RETURN|HOLD |
| resolution_note | varchar(500) | Y | | 사유 필수 - 원장 역분개와 이중 기록 |
| return_method | varchar(30) | Y | | 반환 방법(펌뱅킹 이체 등) |
| return_bank_code | varchar(10) | Y | | 반환 은행 코드 |
| return_account_enc | varbinary(128) | Y | | 반환 계좌 AES-256(암호화 의무) |
| created_at | datetime | N | | 생성 일시 |
입금 즉시 UNMATCHED 시스템 지갑에 원장 계상 - 장부 밖 돈 없음.
3.13 gift_links — 선물 링크 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 선물 고유번호 |
| sender_card_id | bigint | N | IX | 보낸 카드(cards.id) |
| hold_txn_id | bigint | N | | 선차감(GIFT_ESCROW 계상) 거래 |
| token_hash | char(64) | N | UQ | 클레임 토큰(1회용) - 원문 미저장 |
| amount | decimal(15,0) | N | | 선물 금액 |
| message | varchar(50) | Y | | 선물 메시지(금칙어 필터) |
| expires_at | datetime | N | IX | TTL 24시간 - 만료 시 자동 반환 |
| status | varchar(20) | N | | CREATED|CLAIMED|EXPIRED|CANCELED(송금인 회수) (기본 CREATED) |
| claimed_card_id | bigint | Y | IX | 수령 카드(수령자 대표 카드) |
| claimed_at | datetime | Y | | 수령 일시 |
3.14 pg_orders — PG 온라인 결제 주문 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 주문 고유번호 |
| merchant_id | bigint | N | UQ | 매장(merchants.id) |
| order_no | varchar(64) | N | UQ | 쇼핑몰 주문번호 - UNIQUE(merchant_id, order_no) 멱등 |
| amount | decimal(15,0) | N | | 결제 금액 |
| supply_amount | decimal(15,0) | Y | | 공급가액 |
| vat_amount | decimal(15,0) | N | | 부가세(PG API로 수신·보관) |
| item_name | varchar(200) | Y | | 상품명 |
| qr_id | bigint | Y | IX | 연결 QR(merchant_qrs.id) |
| txn_id | bigint | Y | | 결제 성사 시 거래 참조 |
| status | varchar(20) | N | IX | CREATED|PAID|CANCELED|EXPIRED (기본 CREATED) |
| expires_at | datetime | Y | | 주문 만료 시각 - 만료 스위퍼 기준 |
| paid_at | datetime | Y | | 결제 완료 시각 |
| canceled_at | datetime | Y | | 취소·환불 시각 |
| is_sandbox | tinyint(1) | N | | 테스트 주문 실원장 완전 분리(기본 0) |
| created_at | datetime | N | | 생성 일시 |
3.15 merchant_qrs — 매장 QR BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | QR 고유번호 |
| merchant_id | bigint | N | IX | 매장(merchants.id) |
| qr_type | varchar(15) | N | | STORE_STATIC|STORE_DYNAMIC|ORDER (CPM·수취는 Redis 단명 토큰) |
| label | varchar(50) | Y | | 카운터1 등 - 매장당 복수 QR |
| amount | decimal(15,0) | Y | | ORDER형만 |
| key_version | int(11) | N | | AES-GCM 키 버전(기본 1) |
| status | varchar(20) | N | | ACTIVE|REVOKED|USED(ORDER 1회용) (기본 ACTIVE) |
| expires_at | datetime | Y | | DYNAMIC/ORDER TTL |
| created_at | datetime | N | | 생성 일시 |
3.16 merchant_api_credentials — 매장 API 자격증명 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 고유번호 |
| merchant_id | bigint | N | UQ | 매장(merchants.id) |
| client_id | varchar(32) | N | UQ | API 클라이언트 ID |
| api_key_hash | varchar(255) | N | | 시크릿 해시만 보관(발급 시 1회 표시) |
| prev_key_hash | varchar(255) | Y | | 키 롤 유예(24h 신구 동시 유효) |
| prev_key_expires_at | datetime | Y | | 구 키 만료 시각 |
| webhook_url | varchar(500) | Y | | 매장앱에서 입력하는 웹훅 수신 URL |
| webhook_secret_enc | varbinary(512) | Y | | AES-256-GCM - 발신 웹훅 HMAC 서명 생성에 원문 필요 |
| status | varchar(15) | N | IX | REQUESTED|APPROVED|REJECTED|REVOKED - admin 승인 큐(기본 REQUESTED) |
| requested_at | datetime | N | | 신청 일시 |
| approved_by | bigint | Y | IX | 승인 관리자(admin_accounts.id) |
| mode | varchar(10) | N | | SANDBOX|LIVE - 테스트 통과 후 매장이 전환(기본 SANDBOX) |
| test_create_ok_at | datetime | Y | | 체크리스트: 결제 생성 |
| test_webhook_ok_at | datetime | Y | | 체크리스트: 웹훅 2xx |
| test_query_ok_at | datetime | Y | | 체크리스트: 상태조회 |
| created_at | datetime | N | | 생성 일시 |
LIVE 전환 조건 = status=APPROVED + 3개 test_*_ok_at 모두 존재(앱 강제).
3.17 fee_policies — 수수료 정책 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 정책 고유번호 |
| fee_type | varchar(20) | N | UQ | MERCHANT_PAYMENT|MERCHANT_PAYOUT|USER_DEPOSIT|USER_WITHDRAW|USER_GIFT|USER_EXCHANGE(구현보류) |
| scope | varchar(15) | N | UQ | GLOBAL|USER|MERCHANT |
| target_id | bigint | Y | UQ | 개별 오버라이드 대상(GLOBAL이면 NULL) |
| rate | decimal(7,4) | N | | 정률% - 정액과 합산 조합(기본 0.0000) |
| fixed_amount | decimal(15,0) | N | | 정액 수수료(기본 0) |
3.18 payout_policies — 정산/출금 대기 정책 VERSIONED
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 정책 고유번호 |
| scope | varchar(20) | N | UQ | GLOBAL_USER|GLOBAL_MERCHANT|USER|MERCHANT |
| target_id | bigint | Y | UQ | 개별 오버라이드 대상 |
| delay_days | int(11) | N | | 신청 +N일. 매장(GLOBAL_MERCHANT) 기본 0=즉시 정산. 사용자 출금만 대기 가능 |
| execute_time | time | N | | 실행 시각 HH:mm(KST). delay_days=0이면 무시(즉시) |
3.19 audit_logs — 관리자 행위 감사 BASE (append-only)
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 고유번호 |
| actor_type | varchar(10) | N | IX | ADMIN|SYSTEM |
| actor_id | bigint | Y | | 행위 주체 식별자 |
| action | varchar(50) | N | | MERCHANT_APPROVE|FORCE_SUSPEND|POLICY_CHANGE|MANUAL_MATCH|OTP_RESET|IP_ADD 등 |
| target_type | varchar(30) | Y | IX | 대상 유형 |
| target_id | bigint | Y | | 대상 식별자 |
| detail | text | Y | | 변경 전후 값 JSON(개인정보 원문 금지) |
| reason | varchar(500) | Y | | 강제 조치·조정은 사유 필수 |
| ip | varchar(45) | Y | | 행위 IP |
| created_at | datetime | N | | 기록 일시 |
3.20 outbox_jobs — 비동기 작업 아웃박스 BASE
| 컬럼 | 타입 | NULL | 키 | 설명 |
| id | bigint | N | PK | 작업 고유번호 |
| job_type | varchar(30) | N | | PUSH|WEBHOOK|RECONCILE|GIFT_EXPIRE|LOT_EXPIRE|SETTLEMENT|STATEMENT|PARTITION 등 - 큐 미사용 단일 패턴 |
| payload | text | N | | 작업 인자 JSON |
| run_after | datetime | N | IX | 실행 예정 시각(폴링 기준) |
| attempts | int(11) | N | | 시도 횟수(기본 0) |
| max_attempts | int(11) | N | | 최대 시도(기본 10) |
| status | varchar(10) | N | IX | PENDING|RUNNING|DONE|EXHAUSTED (기본 PENDING) |
| claimed_at | datetime | Y | | 워커가 RUNNING으로 집어간 시각 - 고아 스위퍼 기준 |
| claim_token | char(36) | Y | IX | 집기 표(UPDATE로 붙이고 자기 것만 SELECT - 10.3 호환 클레임) |
| last_error | varchar(500) | Y | | 마지막 오류 |
| created_at | datetime | N | | 생성 일시 |
원장 트랜잭션과 같은 커밋에 INSERT - 후속작업 유실 불가.
4. 무결성·보존 장치 요약
4.1 append-only 방어 트리거 (14개)
비즈니스 로직은 전부 Java, DB에는 "로직 없는 방어 트리거"만 둡니다. 아래 트리거는 append-only 테이블의 UPDATE/DELETE(또는 불변 컬럼 변경·잔여 증가)를 물리적으로 차단합니다. 앱 DB 계정에 UPDATE/DELETE 권한 미부여와 병행됩니다.
| 대상 테이블 | 트리거 | 시점·이벤트 | 차단 대상 |
| transactions | trg_txn_no_delete | BEFORE DELETE | 거래 행 삭제(취소=CANCEL 신규 행) |
| transactions | trg_txn_immutable_columns | BEFORE UPDATE | 거래 불변 컬럼 변경 |
| ledger_entries | trg_ledger_no_delete | BEFORE DELETE | 원장 행 삭제 |
| ledger_entries | trg_ledger_no_update | BEFORE UPDATE | 원장 행 수정 |
| point_lots | trg_lot_decrease_only | BEFORE UPDATE | 로트 잔여 증가(감소만 허용) |
| audit_logs | trg_audit_no_delete | BEFORE DELETE | 감사 로그 삭제 |
| audit_logs | trg_audit_no_update | BEFORE UPDATE | 감사 로그 수정 |
| pii_access_logs | trg_pii_no_delete | BEFORE DELETE | 개인정보 접근기록 삭제 |
| pii_access_logs | trg_pii_no_update | BEFORE UPDATE | 개인정보 접근기록 수정 |
| deposit_notices | trg_notice_no_delete | BEFORE DELETE | 입금통지 삭제 |
| deposit_notices | trg_notice_immutable | BEFORE UPDATE | 입금통지 불변 컬럼 변경 |
| external_api_logs | trg_extapi_no_update | BEFORE UPDATE | 대외 호출 기록 수정 |
| reject_logs | trg_reject_no_update | BEFORE UPDATE | 거절 기록 수정 |
| login_histories | trg_login_no_update | BEFORE UPDATE | 로그인 이력 수정 |
4.2 시스템 버저닝 (17개)
가변 마스터·정책 테이블에 WITH SYSTEM VERSIONING을 적용하여 변경 이력을 DB가 자동 보존합니다("그 시점 정책"의 법적 증빙). 현재 테이블은 최신 행만, 과거 이력은 히스토리 파티션에 자동 축적됩니다.
| 구분 | 테이블 |
| 마스터 | users · merchants · merchant_accounts · cards · bank_accounts |
| 정책류 | fee_policies · limit_policies · payout_policies · cancel_policies · card_quota_policies · point_expiry_policies · global_settings · fds_rules |
| 인증·연동 | admin_accounts · admin_allowed_ips · deposit_identifiers · merchant_api_credentials |
4.3 유니크 멱등키·핵심 UNIQUE 제약
| 테이블 | UNIQUE 인덱스 | 컬럼 | 목적 |
| transactions | ux_txn_idem | initiator_type, initiator_id, idempotency_key | 사용자 스코프 거래 멱등(중복 처리·replay 방지). 비파티션 확정의 결정적 근거 |
| deposit_notices | ux_notice_ref | source, dedup_key | 입금통지 소스별 결정적 중복키(1062만 앱에서 멱등 처리) |
| users | ux_users_active_ci | active_ci | 1인 1활성계정 강제(본인인증 CI 기준) |
| cards | number_hash | number_hash | 카드번호 재사용 영구 차단 |
| pg_orders | ux_pg_order | merchant_id, order_no | 매장 주문번호 멱등(중복 결제 방지) |
| gift_links | token_hash | token_hash | 선물 클레임 토큰 1회용 |
| used_challenges | PRIMARY | challenge | 패스키 챌린지 재사용(replay) 방지 |
| policy_agreements | ux_agreement | principal_type, principal_id, policy_id | 약관 동의 중복 방지 |
4.4 원장 무결성 규약(복식·잔액 체인)
- 복식 분개 — 모든 거래는
ledger_entries에서 거래별 ΣDR = ΣCR. Java 검증 + 일 대사 배치(integrity_check_runs의 DOUBLE_ENTRY)로 이중 확인.
- 잔액 체인 — 사용자·매장 지갑은
balance_after NOT NULL 연쇄로 각 원장 행이 직전 잔액을 승계(BALANCE_CHAIN 검증). SYSTEM 지갑은 핫로우 방지를 위해 balance_after=NULL, 일 배치 SUM으로 파생·검증.
- 희소 스냅샷 —
daily_wallet_snapshots가 체인 검증 재개점을 제공(당일 변동 지갑만 + 월 1회 전량 베이스라인).
- 고아 없음 — 미매칭 입금도 즉시 UNMATCHED 시스템 지갑에 계상(
unmatched_deposits), 장부 밖 돈이 존재하지 않음.
- 후속작업 유실 불가 — 원장 트랜잭션과 동일 커밋에
outbox_jobs INSERT(2대 서버 클레임 토큰으로 중복 실행 방지).
4.5 보존 매트릭스(요약)
| 보존 등급 | 대상 | 삭제 수단 |
| 영구/법정 5년+ | transactions, ledger_entries, lot_allocations, point_lots, audit_logs, policy_agreements, users·cards·merchants(+버전 이력), unmatched_deposits | 미삭제(연 단위 아카이브) |
| 5년 후 파티션 DROP | external_api_logs, reject_logs, app_error_logs, login_histories, pii_access_logs, deposit_notices(아카이브), daily_wallet_snapshots | DROP PARTITION(O(1)) |
| 단기 | notification_inbox 30일(파티션 DROP), outbox_jobs DONE/EXHAUSTED 90일(DELETE), webhook_deliveries DELIVERED 180일(DELETE), hourly_summaries 90일(DELETE) | 배치 DELETE / DROP |
NestPay 산출물 · (주)페이네스트 · 작성일 2026-07-27 · 실제 코드/DB 기준 · 외부연동(펌뱅킹·본인인증·은행 실명조회)은 계약 전 스텁 상태임을 명시