acquire-core-x/db/schema.d/70-econ.sql
hyeongwoo 438ae8992d Wave G: 실제 매입사 경제모델 고도화 (딥리서치 갭분석 기반)
딥리서치(95 에이전트, OCC/Fed/Berlin Group 등 1차 출처, 검증된 9 finding)로
실제 카드 매입사 아키텍처를 조사(docs/GAP_ANALYSIS.md)하고, 최우선 갭 3개를
스키마+시드+런타임서비스+화면으로 메웠다.

메운 갭 (실제 매입사 핵심 메커니즘)
1. 수수료 3층 분해: 기존 flat MDR → interchange(발급사)+scheme fee(카드망)+
   markup(매입사 수익). 실측 결과 interchange=MDR의 86% (리서치 "70~90%"와 일치).
2. 클리어링 ≠ 정산 분리: 승인·정산과 별도인 프레젠트먼트(클리어링) 단계 추가.
3. 발급사↔매입사 순액정산: BIN/ICA 기준 다자 순액(한국 BC카드 컨소시엄형).
4. 펀딩 항등식: 총매출−환불−채그백−수수료−리저브=입금 (2,924건 전부 항등 성립).
5. 채그백/분쟁 라이프사이클: FIRST_CB→REPRESENT→PRE_ARB→ARBITRATION 상태기계.

스키마 (db/schema.d/70-econ.sql, 96-econ-seed.sql)
- 신규 9테이블: st_interchange_rate, st_scheme_fee, st_fee_component, cl_presentment,
  st_member_bank, st_net_settlement, st_reserve, st_merchant_funding, ac_dispute.
- 기존 정산건 소급 분해(요율표 카드종류×채널 차등) + 순액정산 배치 + 펀딩 계산.
- 시드 계산검증: 3층 항등식·펀딩 항등식 100% 성립(내가 채그백 일수중복 버그 1건 수정).

런타임 서비스 (전부 게이트웨이로 실호출 검증)
- st: ST_FEEDECOMP(ic+sf+markup=mdr), ST_NETSETTLE(BIN별 순액), ST_FUNDING(항등식)
- ac: ACQ_DISPUTE_OPEN, ACQ_DISPUTE_ADVANCE(상태기계-부적법 전이 거부),
  ACQ_PRESENT(클리어링). 각 120줄+, ECPG/XA 격리 규율 준수.

화면 (포털 AR0003 "정산 경제", #econ)
- 수수료 3층 색상바 + 펀딩 항등식 워터폴 + 발급사별 순액정산 + 분쟁 라이프사이클
  + 클리어링. 전부 실측 데이터 조회.

검증
- 통합 빌드 error 0, 도메인 재기동 5,879 서비스 AVAIL
- 6개 신규 서비스 실호출 정상(수수료 3층 84=74+6+4, 상태기계 부적법 전이 거부)
- 기존 6단 매입 체인 COMMIT OK 유지, 미결 2PC 0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 14:03:35 +09:00

148 lines
8.1 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ===========================================================================
-- 70-econ.sql : 실제 매입사 경제모델 (딥리서치 갭분석 고도화)
-- 수수료 3층 분해(interchange+scheme fee+acquirer markup),
-- 클리어링/프레젠트먼트(정산과 분리), 발급사↔매입사 순액정산(BIN/ICA·회원사),
-- 정산 항등식(총매출−환불−채그백−수수료−리저브=입금), 채그백/분쟁 라이프사이클.
-- 기존 flat MDR 은 markup 으로 재해석하여 하위호환.
-- ===========================================================================
-- ---------------------------------------------------------------------------
-- 1. Interchange 요율표 (발급사 귀속분). 카드종류×MCC×채널×거래유형 차등.
-- 실제 매입사에서 요율이 갈리는 축을 그대로 반영(interchange qualification).
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS st_interchange_rate (
card_type TEXT NOT NULL, -- CREDIT/CHECK/PREPAID
mcc TEXT NOT NULL DEFAULT 'DEFAULT',-- 업종(가맹점 category 매핑)
channel TEXT NOT NULL DEFAULT 'DEFAULT',-- POS/EDC/EDI/FOREIGN
txn_type TEXT NOT NULL DEFAULT 'SALE', -- SALE/CASH/REFUND
rate_bps BIGINT NOT NULL DEFAULT 0, -- basis points (1bp=0.01%)
fixed_fee BIGINT NOT NULL DEFAULT 0, -- 건당 고정수수료(원)
eff_date DATE NOT NULL DEFAULT current_date,
PRIMARY KEY (card_type, mcc, channel, txn_type)
);
-- ---------------------------------------------------------------------------
-- 2. Scheme fee (카드망 assessment). 스킴×거래유형.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS st_scheme_fee (
scheme TEXT NOT NULL, -- BC/VISA/MASTER/LOCAL
txn_type TEXT NOT NULL DEFAULT 'SALE',
rate_bps BIGINT NOT NULL DEFAULT 0,
fixed_fee BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (scheme, txn_type)
);
-- ---------------------------------------------------------------------------
-- 3. 거래별 수수료 분해 (MDR = interchange + scheme_fee + markup).
-- 매입사 손익: markup 만 매입사 수익, interchange 는 발급사, scheme_fee 는 카드망.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS st_fee_component (
purchase_id BIGINT PRIMARY KEY,
amount BIGINT NOT NULL,
interchange BIGINT NOT NULL DEFAULT 0, -- 발급사 귀속
scheme_fee BIGINT NOT NULL DEFAULT 0, -- 카드망 귀속
markup BIGINT NOT NULL DEFAULT 0, -- 매입사 수익(협상대상)
mdr_total BIGINT NOT NULL DEFAULT 0, -- = interchange+scheme_fee+markup
card_type TEXT NOT NULL DEFAULT 'CREDIT',
scheme TEXT NOT NULL DEFAULT 'BC',
computed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ---------------------------------------------------------------------------
-- 4. 프레젠트먼트(클리어링). 승인·정산과 분리된 단계.
-- 클리어링=데이터 교환(발급사↔매입사 채무 확정). 정산=자금이동.
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS cl_present_seq START 700000;
CREATE TABLE IF NOT EXISTS cl_presentment (
presentment_id BIGINT PRIMARY KEY,
purchase_id BIGINT NOT NULL,
scheme TEXT NOT NULL DEFAULT 'BC',
msg_class TEXT NOT NULL DEFAULT 'FIRST_PRESENT', -- FIRST_PRESENT/REPRESENT/CHARGEBACK
clearing_amt BIGINT NOT NULL, -- 클리어링 대상 금액(net of interchange)
present_date DATE NOT NULL,
cycle TEXT NOT NULL DEFAULT 'T+1', -- 정산주기
status TEXT NOT NULL DEFAULT 'CLEARED', -- CLEARED/REJECTED/PENDING
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ---------------------------------------------------------------------------
-- 5. 회원사(정산 참가 은행/발급사) — 한국 BC카드형 다자간 순액정산.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS st_member_bank (
bank_code TEXT PRIMARY KEY, -- BC/KB/SHINHAN/...
bank_name TEXT NOT NULL,
bin TEXT NOT NULL, -- 스킴 회원식별자(Visa BIN / MC ICA 대응)
role TEXT NOT NULL DEFAULT 'BOTH' -- ISSUER/ACQUIRER/BOTH
);
-- ---------------------------------------------------------------------------
-- 6. 순액정산 배치 (발급사↔매입사). BIN/ICA 기준 차대변 합산 → 단일 순액.
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS st_netset_seq START 800000;
CREATE TABLE IF NOT EXISTS st_net_settlement (
net_id BIGINT PRIMARY KEY,
scheme TEXT NOT NULL DEFAULT 'BC',
member_bin TEXT NOT NULL, -- 상대 회원사 BIN/ICA
biz_date DATE NOT NULL,
txn_count BIGINT NOT NULL DEFAULT 0,
gross_amt BIGINT NOT NULL DEFAULT 0,
interchange_amt BIGINT NOT NULL DEFAULT 0,
scheme_fee_amt BIGINT NOT NULL DEFAULT 0,
net_amt BIGINT NOT NULL DEFAULT 0, -- 실제 이체 순액(부호=방향)
direction TEXT NOT NULL DEFAULT 'RECEIVE', -- RECEIVE(수취)/PAY(지급)
status TEXT NOT NULL DEFAULT 'SETTLED',
settled_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (scheme, member_bin, biz_date)
);
-- ---------------------------------------------------------------------------
-- 7. 롤링 리저브(유보금).
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS st_reserve (
merchant_id TEXT NOT NULL,
biz_date DATE NOT NULL,
reserve_bps BIGINT NOT NULL DEFAULT 0, -- 유보율
reserve_amt BIGINT NOT NULL DEFAULT 0, -- 이번 회차 유보액
release_date DATE, -- 유보 해제 예정일(롤링)
released BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (merchant_id, biz_date)
);
-- ---------------------------------------------------------------------------
-- 8. 가맹점 펀딩 항등식: 총매출 환불 채그백 수수료 리저브 = 입금.
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS st_funding_seq START 900000;
CREATE TABLE IF NOT EXISTS st_merchant_funding (
funding_id BIGINT PRIMARY KEY,
merchant_id TEXT NOT NULL,
biz_date DATE NOT NULL,
gross_sales BIGINT NOT NULL DEFAULT 0,
refunds BIGINT NOT NULL DEFAULT 0,
chargebacks BIGINT NOT NULL DEFAULT 0,
fees BIGINT NOT NULL DEFAULT 0, -- MDR 합계
reserve BIGINT NOT NULL DEFAULT 0,
deposit BIGINT NOT NULL DEFAULT 0, -- 실제 입금액
funding_model TEXT NOT NULL DEFAULT 'NET_DAILY', -- NET_DAILY/MONTHLY_DISCOUNT
status TEXT NOT NULL DEFAULT 'FUNDED',
funded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (merchant_id, biz_date)
);
-- ---------------------------------------------------------------------------
-- 9. 채그백/분쟁 라이프사이클: 1차CB → representment → pre-arb → arbitration.
-- ---------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS ac_dispute_seq START 600000;
CREATE TABLE IF NOT EXISTS ac_dispute (
dispute_id BIGINT PRIMARY KEY,
purchase_id BIGINT NOT NULL,
merchant_id TEXT NOT NULL,
reason_code TEXT NOT NULL, -- 10.4(사기) / 13.1(상품미수취) / 12.6(중복) ...
amount BIGINT NOT NULL,
stage TEXT NOT NULL DEFAULT 'FIRST_CB', -- FIRST_CB/REPRESENT/PRE_ARB/ARBITRATION
status TEXT NOT NULL DEFAULT 'OPEN', -- OPEN/WON/LOST/ACCEPTED
opened_at TIMESTAMPTZ NOT NULL DEFAULT now(),
resolved_at TIMESTAMPTZ,
memo TEXT
);
CREATE INDEX IF NOT EXISTS ix_dispute_pid ON ac_dispute(purchase_id);
CREATE INDEX IF NOT EXISTS ix_present_pid ON cl_presentment(purchase_id);