딥리서치(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>
290 lines
9.8 KiB
Text
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: */
|