acquire-core-x/app/src/ac/ac_svr.pgc
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

290 lines
9.8 KiB
Text

/*
* ac_svr.pgc - ac (매입) MODULE SERVER (thin dispatcher).
*
* The legacy "one module server, many services" pattern: this binary owns NO
* business logic. Each of ac's ~30 online services now lives in its own file
* (app/src/ac/svc/<SVCNAME>.pgc) with its own copybook header
* (app/src/ac/svc/<SVCNAME>.h); those objects are archived into libacsvc.a and
* linked into this server. Here we only:
* - include every service copybook (for the extern prototypes),
* - tpopen() the ECPG XA RM,
* - tpadvertise() all services from a {name, fn} table,
* - tpclose() at shutdown.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch (see
* the ACQUIRE->RECONCILE->SETTLE chain, which crosses ac_svr/rc_svr/st_svr).
*/
#include <atmi.h>
#include <ubf.h>
#include <userlog.h>
/* every ac service's copybook header (declares its record struct + prototype) */
#include "ACQUIRE.h"
#include "ACQ_DDC.h"
#include "ACQ_EDI.h"
#include "ACQ_EDC.h"
#include "ACQ_CANCEL.h"
#include "ACQ_CORRECT.h"
#include "ACQ_PARTIAL.h"
#include "ACQ_INSTALL.h"
#include "ACQ_FOREIGN.h"
#include "ACQ_UNMATCH.h"
#include "ACQ_REPROC.h"
#include "ACQ_DUPCHK.h"
#include "ACQ_LIMITCHK.h"
#include "ACQ_FEEADJ.h"
#include "ACQ_STATUS.h"
#include "ACQ_MERCHSUM.h"
#include "ACQ_ISSUERCLS.h"
#include "ACQ_WHT.h"
#include "ACQ_TAXINV.h"
#include "ACQ_APPRLINK.h"
#include "ACQ_DATECHG.h"
#include "ACQ_AMTFIX.h"
#include "ACQ_RECLASS.h"
#include "ACQ_XFERLINK.h"
#include "ACQ_APPRMAP.h"
#include "ACQ_SETTLELINK.h"
#include "ACQ_HOLD.h"
#include "ACQ_RELEASE.h"
#include "ACQ_VOID.h"
#include "ACQ_REFUND.h"
#include "ACQ_CB_OPEN.h"
#include "ACQ_CB_QRY.h"
#include "ACQ_CB_DEFEND.h"
#include "ACQ_CB_STAT.h"
#include "ACQ_REBUY.h"
#include "ACQ_PARTCXL.h"
#include "ACQ_PARTRFND.h"
#include "ACQ_SPLITPAY.h"
#include "ACQ_SPLITQRY.h"
#include "ACQ_CCYSUM.h"
#include "ACQ_FXRATE.h"
#include "ACQ_CTRYSUM.h"
#include "ACQ_FEERECALC.h"
#include "ACQ_FEEWAIVE.h"
#include "ACQ_FEEHIST.h"
#include "ACQ_CLAIMREG.h"
#include "ACQ_CLAIMPROC.h"
#include "ACQ_CLAIMQRY.h"
#include "ACQ_CORRHIST.h"
#include "ACQ_TAXREISSUE.h"
#include "ACQ_TAXCXL.h"
#include "ACQ_RETURN.h"
#include "ACQ_REMATCH.h"
#include "ACQ_BULKVERIFY.h"
#include "ACQ_LIMITUSAGE.h"
#include "ACQ_RESERVE.h"
#include "ACQ_RESERVECXL.h"
#include "ACQ_RESERVEQRY.h"
#include "ACQ_PROMO.h"
#include "ACQ_NOINTSHARE.h"
#include "ACQ_DAYSTAT.h"
#include "ACQ_WEEKSTAT.h"
#include "ACQ_MONSTAT.h"
#include "ACQ_CHANSTAT.h"
#include "ACQ_ISSSTAT.h"
#include "ACQ_ALERTCHK.h"
#include "ACQ_EDIRESEND.h"
#include "ACQ_SETTLEDUE.h"
#include "ACQ_HOLDQRY.h"
#include "ACQ_DUPSCAN.h"
#include "ACQ_VELOCITY.h"
#include "ACQ_TOPMERCH.h"
#include "ACQ_NETCALC.h"
#include "ACQ_APPRGAP.h"
#include "ACQ_FXCONVERT.h"
#include "ACQ_CHANDETAIL.h"
#include "ACQ_BRANDSUM.h"
#include "ACQ_BRANDQRY.h"
#include "ACQ_CTRYQRY.h"
#include "ACQ_CCYQRY.h"
#include "ACQ_CCYRANK.h"
#include "ACQ_CHANRANK.h"
#include "ACQ_BRANDRANK.h"
#include "ACQ_CTRYRANK.h"
#include "ACQ_ERRCLASS.h"
#include "ACQ_ERRQRY.h"
#include "ACQ_ERRRETRY.h"
#include "ACQ_ERRSTAT.h"
#include "ACQ_REBUYAPPR.h"
#include "ACQ_REBUYREJ.h"
#include "ACQ_REBUYHIST.h"
#include "ACQ_CB_RND2.h"
#include "ACQ_CB_ARB.h"
#include "ACQ_CB_FINAL.h"
#include "ACQ_CB_REVERSE.h"
#include "ACQ_CORRRSN.h"
#include "ACQ_CORRSTAT.h"
#include "ACQ_CORRAPPR.h"
#include "ACQ_INSTQRY.h"
#include "ACQ_INSTPREPAY.h"
#include "ACQ_INSTPLAN.h"
#include "ACQ_INSTCXL.h"
#include "ACQ_ESCROW.h"
#include "ACQ_ESCROWREL.h"
#include "ACQ_GUARANTEE.h"
#include "ACQ_GUARQRY.h"
#include "ACQ_TAXMODIFY.h"
#include "ACQ_TAXREVERSE.h"
#include "ACQ_TAXQRY.h"
#include "ACQ_ALERTAMT.h"
#include "ACQ_ALERTVEL.h"
#include "ACQ_ALERTDUP.h"
#include "ACQ_ALERTQRY.h"
#include "ACQ_EDIRSNDCH.h"
#include "ACQ_EDIQRY.h"
#include "ACQ_EDISTAT.h"
#include "ACQ_HOURSTAT.h"
#include "ACQ_DOWSTAT.h"
#include "ACQ_QTRSTAT.h"
#include "ACQ_GRADESTAT.h"
#include "ACQ_HALFSTAT.h"
#include "ACQ_BULKCHK.h"
#include "ACQ_BULKRPT.h"
#include "ACQ_EVENTBUY.h"
#include "ACQ_PROMOQRY.h"
#include "ACQ_PROMOSTAT.h"
#include "ACQ_LIMITEXC.h"
#include "ACQ_LIMITOVR.h"
#include "ACQ_SETTLEPRE.h"
#include "ACQ_SETTLEEXP.h"
#include "ACQ_EXCHANGE.h"
#include "ACQ_RETURNQRY.h"
#include "ACQ_RETURNSTAT.h"
/* Wave G — 매입사 경제모델(분쟁 라이프사이클 + 클리어링) */
#include "ACQ_DISPUTE_OPEN.h"
#include "ACQ_DISPUTE_ADVANCE.h"
#include "ACQ_PRESENT.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"ACQUIRE", ACQUIRE}, {"ACQ_DDC", ACQ_DDC},
{"ACQ_EDI", ACQ_EDI}, {"ACQ_EDC", ACQ_EDC},
{"ACQ_CANCEL", ACQ_CANCEL}, {"ACQ_CORRECT", ACQ_CORRECT},
{"ACQ_PARTIAL", ACQ_PARTIAL}, {"ACQ_INSTALL", ACQ_INSTALL},
{"ACQ_FOREIGN", ACQ_FOREIGN}, {"ACQ_UNMATCH", ACQ_UNMATCH},
{"ACQ_REPROC", ACQ_REPROC}, {"ACQ_DUPCHK", ACQ_DUPCHK},
{"ACQ_LIMITCHK", ACQ_LIMITCHK}, {"ACQ_FEEADJ", ACQ_FEEADJ},
{"ACQ_STATUS", ACQ_STATUS}, {"ACQ_MERCHSUM", ACQ_MERCHSUM},
{"ACQ_ISSUERCLS", ACQ_ISSUERCLS},{"ACQ_WHT", ACQ_WHT},
{"ACQ_TAXINV", ACQ_TAXINV}, {"ACQ_APPRLINK", ACQ_APPRLINK},
{"ACQ_DATECHG", ACQ_DATECHG}, {"ACQ_AMTFIX", ACQ_AMTFIX},
{"ACQ_RECLASS", ACQ_RECLASS}, {"ACQ_XFERLINK", ACQ_XFERLINK},
{"ACQ_APPRMAP", ACQ_APPRMAP}, {"ACQ_SETTLELINK", ACQ_SETTLELINK},
{"ACQ_HOLD", ACQ_HOLD}, {"ACQ_RELEASE", ACQ_RELEASE},
{"ACQ_VOID", ACQ_VOID}, {"ACQ_REFUND", ACQ_REFUND},
{"ACQ_CB_OPEN", ACQ_CB_OPEN}, {"ACQ_CB_QRY", ACQ_CB_QRY},
{"ACQ_CB_DEFEND", ACQ_CB_DEFEND},{"ACQ_CB_STAT", ACQ_CB_STAT},
{"ACQ_REBUY", ACQ_REBUY}, {"ACQ_PARTCXL", ACQ_PARTCXL},
{"ACQ_PARTRFND", ACQ_PARTRFND}, {"ACQ_SPLITPAY", ACQ_SPLITPAY},
{"ACQ_SPLITQRY", ACQ_SPLITQRY}, {"ACQ_CCYSUM", ACQ_CCYSUM},
{"ACQ_FXRATE", ACQ_FXRATE}, {"ACQ_CTRYSUM", ACQ_CTRYSUM},
{"ACQ_FEERECALC", ACQ_FEERECALC},{"ACQ_FEEWAIVE", ACQ_FEEWAIVE},
{"ACQ_FEEHIST", ACQ_FEEHIST}, {"ACQ_CLAIMREG", ACQ_CLAIMREG},
{"ACQ_CLAIMPROC", ACQ_CLAIMPROC},{"ACQ_CLAIMQRY", ACQ_CLAIMQRY},
{"ACQ_CORRHIST", ACQ_CORRHIST}, {"ACQ_TAXREISSUE", ACQ_TAXREISSUE},
{"ACQ_TAXCXL", ACQ_TAXCXL}, {"ACQ_RETURN", ACQ_RETURN},
{"ACQ_REMATCH", ACQ_REMATCH}, {"ACQ_BULKVERIFY", ACQ_BULKVERIFY},
{"ACQ_LIMITUSAGE", ACQ_LIMITUSAGE},{"ACQ_RESERVE", ACQ_RESERVE},
{"ACQ_RESERVECXL", ACQ_RESERVECXL},{"ACQ_RESERVEQRY", ACQ_RESERVEQRY},
{"ACQ_PROMO", ACQ_PROMO}, {"ACQ_NOINTSHARE", ACQ_NOINTSHARE},
{"ACQ_DAYSTAT", ACQ_DAYSTAT}, {"ACQ_WEEKSTAT", ACQ_WEEKSTAT},
{"ACQ_MONSTAT", ACQ_MONSTAT}, {"ACQ_CHANSTAT", ACQ_CHANSTAT},
{"ACQ_ISSSTAT", ACQ_ISSSTAT}, {"ACQ_ALERTCHK", ACQ_ALERTCHK},
{"ACQ_EDIRESEND", ACQ_EDIRESEND},{"ACQ_SETTLEDUE", ACQ_SETTLEDUE},
{"ACQ_HOLDQRY", ACQ_HOLDQRY}, {"ACQ_DUPSCAN", ACQ_DUPSCAN},
{"ACQ_VELOCITY", ACQ_VELOCITY}, {"ACQ_TOPMERCH", ACQ_TOPMERCH},
{"ACQ_NETCALC", ACQ_NETCALC}, {"ACQ_APPRGAP", ACQ_APPRGAP},
{"ACQ_FXCONVERT", ACQ_FXCONVERT},
{"ACQ_CHANDETAIL", ACQ_CHANDETAIL},
{"ACQ_BRANDSUM", ACQ_BRANDSUM},
{"ACQ_BRANDQRY", ACQ_BRANDQRY},
{"ACQ_CTRYQRY", ACQ_CTRYQRY},
{"ACQ_CCYQRY", ACQ_CCYQRY},
{"ACQ_CCYRANK", ACQ_CCYRANK},
{"ACQ_CHANRANK", ACQ_CHANRANK},
{"ACQ_BRANDRANK", ACQ_BRANDRANK},
{"ACQ_CTRYRANK", ACQ_CTRYRANK},
{"ACQ_ERRCLASS", ACQ_ERRCLASS},
{"ACQ_ERRQRY", ACQ_ERRQRY},
{"ACQ_ERRRETRY", ACQ_ERRRETRY},
{"ACQ_ERRSTAT", ACQ_ERRSTAT},
{"ACQ_REBUYAPPR", ACQ_REBUYAPPR},
{"ACQ_REBUYREJ", ACQ_REBUYREJ},
{"ACQ_REBUYHIST", ACQ_REBUYHIST},
{"ACQ_CB_RND2", ACQ_CB_RND2},
{"ACQ_CB_ARB", ACQ_CB_ARB},
{"ACQ_CB_FINAL", ACQ_CB_FINAL},
{"ACQ_CB_REVERSE", ACQ_CB_REVERSE},
{"ACQ_CORRRSN", ACQ_CORRRSN},
{"ACQ_CORRSTAT", ACQ_CORRSTAT},
{"ACQ_CORRAPPR", ACQ_CORRAPPR},
{"ACQ_INSTQRY", ACQ_INSTQRY},
{"ACQ_INSTPREPAY", ACQ_INSTPREPAY},
{"ACQ_INSTPLAN", ACQ_INSTPLAN},
{"ACQ_INSTCXL", ACQ_INSTCXL},
{"ACQ_ESCROW", ACQ_ESCROW},
{"ACQ_ESCROWREL", ACQ_ESCROWREL},
{"ACQ_GUARANTEE", ACQ_GUARANTEE},
{"ACQ_GUARQRY", ACQ_GUARQRY},
{"ACQ_TAXMODIFY", ACQ_TAXMODIFY},
{"ACQ_TAXREVERSE", ACQ_TAXREVERSE},
{"ACQ_TAXQRY", ACQ_TAXQRY},
{"ACQ_ALERTAMT", ACQ_ALERTAMT},
{"ACQ_ALERTVEL", ACQ_ALERTVEL},
{"ACQ_ALERTDUP", ACQ_ALERTDUP},
{"ACQ_ALERTQRY", ACQ_ALERTQRY},
{"ACQ_EDIRSNDCH", ACQ_EDIRSNDCH},
{"ACQ_EDIQRY", ACQ_EDIQRY},
{"ACQ_EDISTAT", ACQ_EDISTAT},
{"ACQ_HOURSTAT", ACQ_HOURSTAT},
{"ACQ_DOWSTAT", ACQ_DOWSTAT},
{"ACQ_QTRSTAT", ACQ_QTRSTAT},
{"ACQ_GRADESTAT", ACQ_GRADESTAT},
{"ACQ_HALFSTAT", ACQ_HALFSTAT},
{"ACQ_BULKCHK", ACQ_BULKCHK},
{"ACQ_BULKRPT", ACQ_BULKRPT},
{"ACQ_EVENTBUY", ACQ_EVENTBUY},
{"ACQ_PROMOQRY", ACQ_PROMOQRY},
{"ACQ_PROMOSTAT", ACQ_PROMOSTAT},
{"ACQ_LIMITEXC", ACQ_LIMITEXC},
{"ACQ_LIMITOVR", ACQ_LIMITOVR},
{"ACQ_SETTLEPRE", ACQ_SETTLEPRE},
{"ACQ_SETTLEEXP", ACQ_SETTLEEXP},
{"ACQ_EXCHANGE", ACQ_EXCHANGE},
{"ACQ_RETURNQRY", ACQ_RETURNQRY},
{"ACQ_RETURNSTAT", ACQ_RETURNSTAT},
{"ACQ_DISPUTE_OPEN", ACQ_DISPUTE_OPEN},
{"ACQ_DISPUTE_ADVANCE", ACQ_DISPUTE_ADVANCE},
{"ACQ_PRESENT", ACQ_PRESENT},
{NULL, NULL}
};
int tpsvrinit(int argc, char **argv)
{
int i;
if (tpopen() < 0) {
userlog("ac_svr: tpopen FAIL: %s", tpstrerror(tperrno));
return -1;
}
for (i = 0; SVCS[i].name != NULL; i++) {
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
userlog("ac_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
return -1;
}
}
userlog("ac_svr: %d ac services advertised, RM opened", i);
return 0;
}
void tpsvrdone(void)
{
tpclose();
userlog("ac_svr: tpsvrdone");
}
/* vim: set ts=4 sw=4 et smartindent: */