Wave G2: 경제모델 2·3순위 갭 (MCC interchange 등급·ISO8583 클리어링·이중/단일)

갭분석 2·3순위 보강. 전부 실측 검증.

① interchange qualification 심화 (MCC 업종별 등급)
- mm_merch_mcc: 40개 가맹점 → 실제 4자리 ISO MCC (커피 5814, 마트 5411,
  면세점 5309, 병원 8062, 주유소 5541, 여행사 4722, 숙박 7011 ...)
- mm_mcc: 26개 MCC + risk tier(1 저율/2 표준/3 고율)
- st_interchange_rate 에 MCC×카드종류 요율 override → fee_component 재계산
- 실측: tier1(병원0.69%/약국0.72%/주유0.75%) < tier2(요식~1.15%) < tier3(여행~1.62%)
  — 실제 매입사 interchange 차등 그대로. 3층 항등 7560/7560, 펀딩 2925/2925 유지.

② ISO 8583 클리어링 메시지 클래스 (승인전문과 분리된 클리어링 단계)
- mg_clearing: PRESENTMENT(1240)/CHARGEBACK(1442)/REPRESENT/RECONCILE(1500)/
  FEE_COLLECT(1740). 실측 8,658건.
- MG_CLEARING_INQ 서비스(mg, 120줄): 클래스/영업일/pid 필터 집계 조회.
  실호출 검증(DUAL 6,386 / SINGLE 2,272, 대표 MTI 1240).

③ 이중/단일 메시지 구분
- msg_type: 신용=DUAL(5,288) / 체크=SINGLE(2,272). 실제 dual/single-message 프로토콜.

포털 "정산 경제"(#econ)에 MCC 등급표 + ISO8583 클리어링 메시지표 추가.
통합 빌드 error 0, 도메인 5,883 서비스 AVAIL, 6단 체인 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo 2026-07-22 15:18:40 +09:00
parent 438ae8992d
commit 2ac4c3c920
8 changed files with 343 additions and 5 deletions

View file

@ -29,6 +29,7 @@
#include "MG_CONVERT.h"
#include "MG_ERRMSG.h"
#include "MG_LOG.h"
#include "MG_CLEARING_INQ.h"
#include "MG_CHANAUTH.h"
#include "MG_SESSION.h"
#include "MG_ACK.h"
@ -181,6 +182,7 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"MG_STAN", MG_STAN}, {"MG_ROUTE", MG_ROUTE},
{"MG_RESEND", MG_RESEND}, {"MG_CONVERT", MG_CONVERT},
{"MG_ERRMSG", MG_ERRMSG}, {"MG_LOG", MG_LOG},
{"MG_CLEARING_INQ", MG_CLEARING_INQ},
{"MG_CHANAUTH", MG_CHANAUTH}, {"MG_SESSION", MG_SESSION},
{"MG_ACK", MG_ACK}, {"MG_NACK", MG_NACK},
{"MG_ECHO", MG_ECHO}, {"MG_HEALTH", MG_HEALTH},

View file

@ -0,0 +1,12 @@
/*
* MG_CLEARING_INQ.h - mg () MG_CLEARING_INQ .
* ISO 8583 (mg_clearing) . Advertised by mg_svr.
*/
#ifndef MG_CLEARING_INQ_H
#define MG_CLEARING_INQ_H
#include "mg_svc.h"
void MG_CLEARING_INQ(TPSVCINFO *p);
#endif /* MG_CLEARING_INQ_H */

View file

@ -0,0 +1,121 @@
/*
* MG_CLEARING_INQ.pgc - mg 모듈 서비스 MG_CLEARING_INQ (one service = one file).
* ISO 8583 클리어링 메시지(mg_clearing) 조회. 승인전문(mg_msg_log)과 분리된
* 클리어링 단계의 전문 클래스(PRESENTMENT/CHARGEBACK/REPRESENT/RECONCILE/
* FEE_COLLECT/FILE_REJECT)를 집계·조회한다.
* 입력계약:
* T_PURCHASE_ID (선택) 특정 매입건의 클리어링 이력 조회
* T_STR1 (선택) 특정 msg_class 필터
* T_BIZDATE (선택) 영업일
* 출력계약:
* T_COUNT=총 메시지 건수, T_ARG1=DUAL 건수, T_ARG2=SINGLE 건수,
* T_AMT1=총 금액, T_STR2=대표 MTI, T_STATUS=조회상태.
* Runs on caller's XA branch (no EXEC SQL CONNECT). Advertised by mg_svr.
*/
#include "MG_CLEARING_INQ.h"
void MG_CLEARING_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
const char *SVC = "MG_CLEARING_INQ";
char in_class[24], in_bizdate[16];
long in_pid;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_class[24];
char h_bizdate[16];
long h_total, h_dual, h_single, h_amt;
long h_present, h_chargeback, h_represent, h_reconcile, h_feecol, h_reject;
char h_top_mti[8];
EXEC SQL END DECLARE SECTION;
userlog("%s 진입: ISO 8583 클리어링 메시지 조회", SVC);
/* --- 1) 입력 검증/수집부 ------------------------------------------- */
in_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_STR1, in_class, sizeof(in_class));
gets_(b, T_BIZDATE, in_bizdate, sizeof(in_bizdate));
h_pid = in_pid;
strncpy(h_class, in_class, sizeof(h_class) - 1); h_class[sizeof(h_class) - 1] = 0;
strncpy(h_bizdate, in_bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate) - 1] = 0;
if (h_class[0] == 0) strcpy(h_class, "ALL");
if (h_bizdate[0] == 0) strcpy(h_bizdate, "ALL");
h_total = h_dual = h_single = h_amt = 0;
h_present = h_chargeback = h_represent = h_reconcile = h_feecol = h_reject = 0;
strcpy(h_top_mti, "");
/* --- 2) 본 조회부: 필터 조합별 집계 -------------------------------- */
if (h_pid > 0) {
/* 특정 매입건의 클리어링 이력 (라이프사이클 추적) */
EXEC SQL SELECT count(*),
coalesce(sum(CASE WHEN msg_type='DUAL' THEN 1 ELSE 0 END), 0),
coalesce(sum(CASE WHEN msg_type='SINGLE' THEN 1 ELSE 0 END), 0),
coalesce(sum(amount), 0)
INTO :h_total, :h_dual, :h_single, :h_amt
FROM mg_clearing WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) {
userlog("%s: 매입건 클리어링 조회 실패 [%d]", SVC, sqlca.sqlcode);
setl(b, T_RC, 9501); sets_(b, T_MSG, "클리어링 조회 실패"); FAIL(b);
}
} else {
/* 전체(또는 클래스/영업일 필터) 집계 */
EXEC SQL SELECT count(*),
coalesce(sum(CASE WHEN msg_type='DUAL' THEN 1 ELSE 0 END), 0),
coalesce(sum(CASE WHEN msg_type='SINGLE' THEN 1 ELSE 0 END), 0),
coalesce(sum(amount), 0)
INTO :h_total, :h_dual, :h_single, :h_amt
FROM mg_clearing
WHERE (:h_class = 'ALL' OR msg_class = :h_class)
AND (:h_bizdate = 'ALL' OR to_char(biz_date,'YYYY-MM-DD') = :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("%s: 클리어링 집계 실패 [%d]", SVC, sqlca.sqlcode);
setl(b, T_RC, 9502); sets_(b, T_MSG, "클리어링 집계 실패"); FAIL(b);
}
}
/* --- 3) 메시지 클래스별 분해 (전체 조회일 때만) -------------------- */
if (h_pid <= 0) {
EXEC SQL SELECT
coalesce(sum(CASE WHEN msg_class='PRESENTMENT' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN msg_class='CHARGEBACK' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN msg_class='REPRESENT' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN msg_class='RECONCILE' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN msg_class='FEE_COLLECT' THEN 1 ELSE 0 END),0),
coalesce(sum(CASE WHEN status='REJECTED' THEN 1 ELSE 0 END),0)
INTO :h_present, :h_chargeback, :h_represent, :h_reconcile, :h_feecol, :h_reject
FROM mg_clearing
WHERE (:h_bizdate = 'ALL' OR to_char(biz_date,'YYYY-MM-DD') = :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("%s: 클래스 분해 실패 [%d]", SVC, sqlca.sqlcode); }
}
/* --- 4) 대표 MTI 조회 (건수 최다 클래스의 MTI) --------------------- */
EXEC SQL SELECT mti INTO :h_top_mti FROM mg_clearing
WHERE (:h_class = 'ALL' OR msg_class = :h_class)
AND (:h_pid <= 0 OR purchase_id = :h_pid)
GROUP BY mti ORDER BY count(*) DESC LIMIT 1;
if (sqlca.sqlcode == 100) strcpy(h_top_mti, "-");
/* --- 5) 업무규칙: 이상신호 판정(거부 메시지 존재시 경보) ----------- */
if (h_reject > 0)
userlog("%s 경보: 거부(REJECTED) 클리어링 메시지 %ld건", SVC, h_reject);
/* --- 6) 응답 전문 구성부 ------------------------------------------- */
setl(b, T_COUNT, h_total);
setl(b, T_ARG1, h_dual);
setl(b, T_ARG2, h_single);
setl(b, T_AMT1, h_amt);
setl(b, T_AMT2, h_present);
setl(b, T_AMT3, h_chargeback);
setl(b, T_ARG3, h_reconcile);
setl(b, T_ARG4, h_feecol);
sets_(b, T_STR2, h_top_mti);
setl(b, T_RC, 0);
sets_(b, T_STATUS, h_total > 0 ? "FOUND" : "EMPTY");
/* --- 7) 로깅부 ----------------------------------------------------- */
userlog("%s 완료: total=%ld dual=%ld single=%ld amt=%ld present=%ld cb=%ld recon=%ld",
SVC, h_total, h_dual, h_single, h_amt, h_present, h_chargeback, h_reconcile);
OK(b);
}