Wave J: 카드보안·자금·규제 고도화 (패리티 70%→80%)
패리티 분석의 최우선 갭(카드보안·자금연동·운영·규제)을 데이터모델+시드+ 런타임서비스+화면으로 체계적 보강. 전부 실측·런타임 검증. ■ J-1 카드보안 (승인 60→82) - card_vault(6,680): PCI 토큰볼트 — PAN 원문 미저장, 마스킹+SHA256 해시. - au_3ds(1,235): 3DS/SCA — ECI·CAVV·책임전환(ISSUER 1,143). - au_verify(8,191): AVS/CVV + 리스크판정(APPROVE/REVIEW/DECLINE). - 서비스 TOKENIZE/AUTH_3DS/AUTH_VERIFY (au). ■ J-2 자금이동 (정산 75→85) - st_funding_instr(355k): 펌뱅킹 펀딩지시/반환(CONFIRMED/RETURNED). - ac_dcc(426): DCC 다통화 승인시 환율고정(USD/JPY/EUR/CNY + 마크업 3%). - 서비스 ST_FUNDINSTR/AC_DCC. ■ J-3 수수료·분쟁 (수수료 80→88, 분쟁 70→82) - st_cb_fee(175): 채그백 건당수수료(단계별 차등). - ac_retrieval(127) + 분쟁 SLA타이머·fraud_flag. 서비스 ACQ_RETRIEVAL. ■ J-4 리포팅·규제 (리포팅 70→85) - rp_merchant_stmt(12.8k): 가맹점 월명세서. - rp_regulatory(134): 여신전문금융업법 정산주기 준수·거래량 보고. ■ J-5 화면·검증 - 포털 "카드보안·규제"(SY0004, #sec): 토큰볼트·3DS·AVS/CVV·DCC·펀딩·규제. - 통합빌드 error 0, 5,907 서비스 AVAIL, 6본 신규서비스 실호출 검증(미결2PC 0). - PARITY_ANALYSIS 재채점: 가중 ≈80/100. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
782b802156
commit
d292772b1f
21 changed files with 1337 additions and 7 deletions
|
|
@ -160,6 +160,10 @@
|
|||
#include "ACQ_DISPUTE_ADVANCE.h"
|
||||
#include "ACQ_PRESENT.h"
|
||||
|
||||
/* Wave J — 자금연동(DCC 다통화 환산) + 운영(매출전표 조회요청) */
|
||||
#include "AC_DCC.h"
|
||||
#include "ACQ_RETRIEVAL.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},
|
||||
|
|
@ -261,6 +265,8 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
|||
{"ACQ_DISPUTE_OPEN", ACQ_DISPUTE_OPEN},
|
||||
{"ACQ_DISPUTE_ADVANCE", ACQ_DISPUTE_ADVANCE},
|
||||
{"ACQ_PRESENT", ACQ_PRESENT},
|
||||
{"AC_DCC", AC_DCC},
|
||||
{"ACQ_RETRIEVAL", ACQ_RETRIEVAL},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
|||
25
app/src/ac/svc/ACQ_RETRIEVAL.h
Normal file
25
app/src/ac/svc/ACQ_RETRIEVAL.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* ACQ_RETRIEVAL.h - ac 서비스 ACQ_RETRIEVAL 카피북 (copybook / record header).
|
||||
* 매출전표 조회요청(retrieval request) 생성/이행: 채그백 전 단계.
|
||||
* action=FULFILL → 열린 조회요청을 FULFILLED(+fulfilled_at) 로 이행,
|
||||
* 그 외 → 신규 OPEN 조회요청 생성(sla_due = today + 20).
|
||||
* Request fields: T_PURCHASE_ID, T_STR1(reason 또는 action=FULFILL)
|
||||
* Response fields: T_ID1(retr_id), T_STATUS
|
||||
*/
|
||||
#ifndef ACQ_RETRIEVAL_H
|
||||
#define ACQ_RETRIEVAL_H
|
||||
|
||||
#include "ac_svc.h"
|
||||
|
||||
typedef struct {
|
||||
long retr_id;
|
||||
long purchase_id;
|
||||
char merchant_id[64];
|
||||
char reason[128];
|
||||
char status[16]; /* OPEN/FULFILLED/EXPIRED */
|
||||
char sla_due[16];
|
||||
} ac_retrieval_rec_t;
|
||||
|
||||
void ACQ_RETRIEVAL(TPSVCINFO *p);
|
||||
|
||||
#endif /* ACQ_RETRIEVAL_H */
|
||||
117
app/src/ac/svc/ACQ_RETRIEVAL.pgc
Normal file
117
app/src/ac/svc/ACQ_RETRIEVAL.pgc
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* ACQ_RETRIEVAL.pgc - ac (매입) 모듈 서비스 ACQ_RETRIEVAL (one service = one file).
|
||||
* 매출전표 조회요청(retrieval request) 생성/이행: 정식 채그백 직전 단계로, 발급사가
|
||||
* 거래 증빙(매출전표/서명/수령증)을 요청하면 조회요청을 열고(OPEN, SLA=today+20),
|
||||
* 가맹점이 증빙을 제출하면 이행(FULFILLED + fulfilled_at)한다.
|
||||
* 처리단계: 입력검증 -> 매입 컨텍스트 조회 -> action 분기(FULFILL/신규) -> 본처리 ->
|
||||
* 감사이력 -> 재검증 -> 응답 -> 로깅.
|
||||
* 호출자 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). ac_svr 가 tpsvrinit 에서 광고.
|
||||
* XA 격리: 조회요청/매입은 본 브랜치에서 조회 가능한 커밋행 기준으로 판정한다.
|
||||
*/
|
||||
#include "ACQ_RETRIEVAL.h"
|
||||
|
||||
/* ACQ_RETRIEVAL - 매출전표 조회요청 생성/이행. */
|
||||
void ACQ_RETRIEVAL(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
/* ---- 지역 스크래치 (DECLARE SECTION 밖에서 초기화) ---- */
|
||||
char reason[128], msgbuf[220];
|
||||
int fulfil = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_retr, h_chk;
|
||||
char h_merch[64], h_reason[128], h_status[16], h_bizdate[16];
|
||||
char h_cstatus[16], h_svc[24], h_msg[220];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strcpy(h_svc, "ACQ_RETRIEVAL");
|
||||
h_pid = h_retr = h_chk = 0;
|
||||
strcpy(h_merch, "UNKNOWN"); strcpy(h_status, "OPEN");
|
||||
strcpy(h_bizdate, "2026-07-19"); h_cstatus[0] = 0;
|
||||
|
||||
/* ===== 1) 입력 검증부 ===== */
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
if (h_pid <= 0) {
|
||||
setl(b, T_RC, 8801); Bchg(b, T_MSG, 0, "매입 ID(purchase_id) 필수", 0L);
|
||||
userlog("[ACQ_RETRIEVAL] 매입 ID 검증 실패 pid=%ld", h_pid); FAIL(b);
|
||||
}
|
||||
gets_(b, T_STR1, reason, sizeof(reason));
|
||||
if (strcmp(reason, "FULFILL") == 0) fulfil = 1;
|
||||
if (reason[0] == 0) strcpy(reason, "매출전표사본");
|
||||
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
|
||||
userlog("[운영] 조회요청 진입 pid=%ld action=%s", h_pid, fulfil ? "FULFILL" : reason);
|
||||
|
||||
/* ===== 2) 매입 컨텍스트 조회부 (가맹점/영업일) ===== */
|
||||
EXEC SQL SELECT merchant_id, biz_date INTO :h_merch, :h_bizdate FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode == 100) {
|
||||
strcpy(h_merch, "UNKNOWN");
|
||||
if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19");
|
||||
userlog("[ACQ_RETRIEVAL] 매입 미조회 pid=%ld — 기본값으로 진행", h_pid);
|
||||
} else if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8810); Bchg(b, T_MSG, 0, "매입 조회 오류", 0L); FAIL(b);
|
||||
}
|
||||
|
||||
/* ===== 3) action 분기부 (이행 / 신규 생성) ===== */
|
||||
if (fulfil) {
|
||||
/* 이행: 열린 조회요청 1건을 FULFILLED 로 전이 */
|
||||
EXEC SQL SELECT retr_id INTO :h_retr FROM ac_retrieval
|
||||
WHERE purchase_id = :h_pid AND status = 'OPEN' ORDER BY retr_id LIMIT 1;
|
||||
if (sqlca.sqlcode == 100) {
|
||||
Bchg(b, T_STATUS, 0, "NOOPEN", 0L);
|
||||
setl(b, T_ID1, 0); Bchg(b, T_SVCNAME, 0, "ACQ_RETRIEVAL", 0L); setl(b, T_RC, 0);
|
||||
userlog("[ACQ_RETRIEVAL] 이행 대상(OPEN) 없음 pid=%ld", h_pid);
|
||||
OK(b);
|
||||
} else if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8811); Bchg(b, T_MSG, 0, "조회요청 조회 오류", 0L); FAIL(b);
|
||||
}
|
||||
strcpy(h_status, "FULFILLED");
|
||||
EXEC SQL UPDATE ac_retrieval SET status = 'FULFILLED', fulfilled_at = now()
|
||||
WHERE retr_id = :h_retr;
|
||||
if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8820); Bchg(b, T_MSG, 0, "조회요청 이행 실패", 0L);
|
||||
userlog("[ACQ_RETRIEVAL] FULFILL UPDATE 실패 [%d] retr=%ld", sqlca.sqlcode, h_retr); FAIL(b);
|
||||
}
|
||||
} else {
|
||||
/* 신규: OPEN 조회요청 생성(SLA = today + 20) */
|
||||
strcpy(h_status, "OPEN");
|
||||
EXEC SQL SELECT nextval('ac_retrieval_seq') INTO :h_retr;
|
||||
if (sqlca.sqlcode < 0) { setl(b, T_RC, 8821); Bchg(b, T_MSG, 0, "조회요청 시퀀스 실패", 0L); FAIL(b); }
|
||||
EXEC SQL INSERT INTO ac_retrieval
|
||||
(retr_id, purchase_id, merchant_id, reason, status, sla_due)
|
||||
VALUES (:h_retr, :h_pid, :h_merch, :h_reason, 'OPEN', CURRENT_DATE + 20);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8822); Bchg(b, T_MSG, 0, "조회요청 적재 실패", 0L);
|
||||
userlog("[ACQ_RETRIEVAL] ac_retrieval INSERT 실패 [%d]", sqlca.sqlcode); FAIL(b);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 4) 감사이력 적재부 (cm_log) ===== */
|
||||
snprintf(msgbuf, sizeof(msgbuf), "ACQ_RETRIEVAL pid=%ld retr=%ld merch=%s status=%s reason=%s",
|
||||
h_pid, h_retr, h_merch, h_status, h_reason);
|
||||
strncpy(h_msg, msgbuf, sizeof(h_msg)-1); h_msg[sizeof(h_msg)-1] = 0;
|
||||
EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
|
||||
VALUES (nextval('cm_log_seq'), :h_svc, 'I', :h_msg, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) { setl(b, T_RC, 8830); Bchg(b, T_MSG, 0, "감사 로그 적재 실패", 0L); FAIL(b); }
|
||||
|
||||
/* ===== 5) 재검증부 (적재/전이 상태 재조회) ===== */
|
||||
EXEC SQL SELECT retr_id INTO :h_chk FROM ac_retrieval WHERE retr_id = :h_retr AND status = :h_status;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
setl(b, T_RC, 8840); Bchg(b, T_MSG, 0, "조회요청 재검증 실패", 0L);
|
||||
userlog("[ACQ_RETRIEVAL] 재검증 실패 code=%d retr=%ld status=%s", sqlca.sqlcode, h_retr, h_status); FAIL(b);
|
||||
}
|
||||
|
||||
/* ===== 6) 응답부 (계약: T_ID1=retr_id, T_STATUS) ===== */
|
||||
setl(b, T_ID1, h_retr);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
setl(b, T_PURCHASE_ID, h_pid);
|
||||
Bchg(b, T_MERCHANT, 0, h_merch, 0L);
|
||||
Bchg(b, T_REASON, 0, h_reason, 0L);
|
||||
Bchg(b, T_SVCNAME, 0, "ACQ_RETRIEVAL", 0L);
|
||||
setl(b, T_RC, 0);
|
||||
|
||||
/* ===== 7) 완료 로깅 ===== */
|
||||
userlog("[운영] 조회요청 완료 pid=%ld retr=%ld status=%s merch=%s", h_pid, h_retr, h_status, h_merch);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
26
app/src/ac/svc/AC_DCC.h
Normal file
26
app/src/ac/svc/AC_DCC.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* AC_DCC.h - ac 서비스 AC_DCC 카피북 (copybook / record header).
|
||||
* DCC(Dynamic Currency Conversion) 다통화 환산: 원화 매입금액을 카드소지자
|
||||
* 통화(USD/JPY/EUR/CNY)로 승인시점 고정환율 + 마크업(3%) 환산 → ac_dcc 적재.
|
||||
* Request fields: T_PURCHASE_ID, T_STR1(txn_ccy), T_AMOUNT(원화)
|
||||
* Response fields: T_AMT1(txn_amount 정수부), T_STR2(txn_ccy), T_ARG1(fx_rate 정수부)
|
||||
*/
|
||||
#ifndef AC_DCC_H
|
||||
#define AC_DCC_H
|
||||
|
||||
#include "ac_svc.h"
|
||||
|
||||
typedef struct {
|
||||
long dcc_id;
|
||||
long purchase_id;
|
||||
char base_ccy[8]; /* KRW */
|
||||
char txn_ccy[8]; /* USD/JPY/EUR/CNY */
|
||||
long base_amount; /* 원화 금액 */
|
||||
long txn_amount; /* 외화 금액(정수부) */
|
||||
long fx_rate; /* 고정환율(정수부) */
|
||||
long markup_bps; /* DCC 마크업 */
|
||||
} ac_dcc_rec_t;
|
||||
|
||||
void AC_DCC(TPSVCINFO *p);
|
||||
|
||||
#endif /* AC_DCC_H */
|
||||
131
app/src/ac/svc/AC_DCC.pgc
Normal file
131
app/src/ac/svc/AC_DCC.pgc
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* AC_DCC.pgc - ac (매입) 모듈 서비스 AC_DCC (one service = one file).
|
||||
* DCC(Dynamic Currency Conversion) 다통화 환산: 해외카드 소지자가 원화가맹점에서
|
||||
* 자국통화 결제를 선택할 때, 원화 매입금액을 승인시점 고정환율 + DCC 마크업(3%)
|
||||
* 으로 카드소지자 통화(USD/JPY/EUR/CNY)로 환산해 ac_dcc 에 적재한다.
|
||||
* 환율(원/외화 1단위): USD 1330, JPY 8.9, EUR 1440, CNY 185 (스케일 x10 정수보관).
|
||||
* txn_amount = base_amount / rate * (1 + markup) — 정수 절삭 없이 NUMERIC 연산.
|
||||
* 처리단계: 입력검증 -> 매입 컨텍스트 조회 -> 통화/환율 결정 -> 환산 -> 본처리(INSERT)
|
||||
* -> 감사이력 -> 재검증 -> 응답 -> 로깅.
|
||||
* 호출자 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). ac_svr 가 tpsvrinit 에서 광고.
|
||||
*/
|
||||
#include "AC_DCC.h"
|
||||
|
||||
/* AC_DCC - DCC 다통화 환산(승인시점 환율고정 + 마크업). */
|
||||
void AC_DCC(TPSVCINFO *p)
|
||||
{
|
||||
UBFH *b = (UBFH *)p->data;
|
||||
/* ---- 지역 스크래치 (DECLARE SECTION 밖에서 초기화) ---- */
|
||||
char ccy[8], msgbuf[220];
|
||||
long rate10 = 0, txn_int = 0, fx_int = 0;
|
||||
|
||||
EXEC SQL BEGIN DECLARE SECTION;
|
||||
long h_pid, h_base, h_rate10, h_markup, h_dcc, h_txn_i, h_orig;
|
||||
char h_ccy[8], h_base_ccy[8], h_bizdate[16], h_status[16], h_svc[24], h_msg[220];
|
||||
EXEC SQL END DECLARE SECTION;
|
||||
|
||||
strcpy(h_svc, "AC_DCC");
|
||||
h_pid = h_base = h_dcc = h_txn_i = h_orig = 0;
|
||||
h_markup = 300; strcpy(h_base_ccy, "KRW"); strcpy(h_status, "CONVERTED");
|
||||
strcpy(h_bizdate, "2026-07-19");
|
||||
|
||||
/* ===== 1) 입력 검증부 ===== */
|
||||
h_pid = getl(b, T_PURCHASE_ID);
|
||||
if (h_pid <= 0) {
|
||||
setl(b, T_RC, 8701); Bchg(b, T_MSG, 0, "매입 ID(purchase_id) 필수", 0L);
|
||||
userlog("[AC_DCC] 매입 ID 검증 실패 pid=%ld", h_pid); FAIL(b);
|
||||
}
|
||||
gets_(b, T_STR1, ccy, sizeof(ccy));
|
||||
if (ccy[0] == 0) strcpy(ccy, "USD");
|
||||
h_base = getl(b, T_AMOUNT);
|
||||
|
||||
/* 통화별 고정환율(스케일 x10): 문자열 비교로 결정(캐스팅 없음). */
|
||||
if (strcmp(ccy, "USD") == 0) rate10 = 13300;
|
||||
else if (strcmp(ccy, "JPY") == 0) rate10 = 89;
|
||||
else if (strcmp(ccy, "EUR") == 0) rate10 = 14400;
|
||||
else if (strcmp(ccy, "CNY") == 0) rate10 = 1850;
|
||||
else {
|
||||
setl(b, T_RC, 8702); Bchg(b, T_MSG, 0, "지원하지 않는 통화(txn_ccy)", 0L);
|
||||
userlog("[AC_DCC] 미지원 통화 ccy=[%s]", ccy); FAIL(b);
|
||||
}
|
||||
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
|
||||
h_rate10 = rate10;
|
||||
userlog("[다통화] DCC 환산 진입 pid=%ld ccy=%s base=%ld rate10=%ld", h_pid, h_ccy, h_base, h_rate10);
|
||||
|
||||
/* ===== 2) 매입 컨텍스트 조회부 (원금/영업일 보정) ===== */
|
||||
EXEC SQL SELECT amount, biz_date INTO :h_orig, :h_bizdate FROM purchase WHERE purchase_id = :h_pid;
|
||||
if (sqlca.sqlcode == 100) {
|
||||
/* 체인/미커밋 매입일 수 있어 실패 처리하지 않는다(RECONCILE 격리 규율). */
|
||||
if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19");
|
||||
userlog("[AC_DCC] 매입 미조회 pid=%ld — 전문/기본값으로 진행", h_pid);
|
||||
} else if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8710); Bchg(b, T_MSG, 0, "매입 조회 오류", 0L); FAIL(b);
|
||||
}
|
||||
/* 원화금액 미지정 시 원 매입금액을 환산 대상으로 사용 */
|
||||
if (h_base <= 0) h_base = h_orig;
|
||||
if (h_base <= 0) {
|
||||
setl(b, T_RC, 8703); Bchg(b, T_MSG, 0, "환산 대상 원화 금액 없음", 0L);
|
||||
userlog("[AC_DCC] 환산 금액 0 pid=%ld", h_pid); FAIL(b);
|
||||
}
|
||||
|
||||
/* ===== 3) 환산부 (외화금액·환율 정수부 산출) ===== */
|
||||
/* txn = base/rate*(1+markup) = base*(10000+markup)/(rate10*1000) */
|
||||
txn_int = h_base * (10000 + h_markup) / (h_rate10 * 1000);
|
||||
fx_int = h_rate10 / 10;
|
||||
userlog("[AC_DCC] 환산 base=%ld ccy=%s rate=%ld.%ld markup=%ld -> txn≈%ld %s",
|
||||
h_base, h_ccy, fx_int, h_rate10 % 10, h_markup, txn_int, h_ccy);
|
||||
|
||||
/* ===== 4) 본처리부 (ac_dcc INSERT — NUMERIC 정밀연산) ===== */
|
||||
EXEC SQL SELECT nextval('ac_dcc_seq') INTO :h_dcc;
|
||||
if (sqlca.sqlcode < 0) { setl(b, T_RC, 8720); Bchg(b, T_MSG, 0, "DCC 시퀀스 실패", 0L); FAIL(b); }
|
||||
EXEC SQL INSERT INTO ac_dcc
|
||||
(dcc_id, purchase_id, base_ccy, txn_ccy, base_amount, txn_amount, fx_rate, markup_bps, opted_in)
|
||||
VALUES (:h_dcc, :h_pid, :h_base_ccy, :h_ccy, :h_base,
|
||||
:h_base * (10000 + :h_markup) / (:h_rate10 * 1000.0),
|
||||
:h_rate10 / 10.0, :h_markup, true);
|
||||
if (sqlca.sqlcode < 0) {
|
||||
setl(b, T_RC, 8721); Bchg(b, T_MSG, 0, "DCC 적재 실패", 0L);
|
||||
userlog("[AC_DCC] ac_dcc INSERT 실패 [%d]", sqlca.sqlcode); FAIL(b);
|
||||
}
|
||||
|
||||
/* ===== 5) 감사이력 적재부 (cm_log) ===== */
|
||||
snprintf(msgbuf, sizeof(msgbuf), "AC_DCC pid=%ld base=%ld KRW -> %ld %s (rate=%ld.%ld markup=%ldbps)",
|
||||
h_pid, h_base, txn_int, h_ccy, fx_int, h_rate10 % 10, h_markup);
|
||||
strncpy(h_msg, msgbuf, sizeof(h_msg)-1); h_msg[sizeof(h_msg)-1] = 0;
|
||||
EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
|
||||
VALUES (nextval('cm_log_seq'), :h_svc, 'I', :h_msg, :h_bizdate);
|
||||
if (sqlca.sqlcode < 0) { setl(b, T_RC, 8730); Bchg(b, T_MSG, 0, "감사 로그 적재 실패", 0L); FAIL(b); }
|
||||
|
||||
/* ===== 6) 재검증부 (적재행 외화금액 정수부 재조회) ===== */
|
||||
EXEC SQL SELECT floor(txn_amount) INTO :h_txn_i FROM ac_dcc WHERE dcc_id = :h_dcc;
|
||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
||||
setl(b, T_RC, 8740); Bchg(b, T_MSG, 0, "DCC 적재 재검증 실패", 0L);
|
||||
userlog("[AC_DCC] 재검증 실패 code=%d", sqlca.sqlcode); FAIL(b);
|
||||
}
|
||||
if (h_txn_i != txn_int) {
|
||||
/* 반올림 경계 1 이내 허용, 그 외 불일치는 실패 처리 */
|
||||
long d = h_txn_i - txn_int; if (d < 0) d = -d;
|
||||
if (d > 1) {
|
||||
setl(b, T_RC, 8741); Bchg(b, T_MSG, 0, "DCC 환산 불일치(보상 롤백)", 0L);
|
||||
userlog("[AC_DCC] 환산 불일치 db=%ld calc=%ld", h_txn_i, txn_int); FAIL(b);
|
||||
}
|
||||
txn_int = h_txn_i;
|
||||
}
|
||||
|
||||
/* ===== 7) 응답부 (계약: T_AMT1=txn_amount, T_STR2=txn_ccy, T_ARG1=fx_rate) ===== */
|
||||
setl(b, T_AMT1, txn_int);
|
||||
Bchg(b, T_STR2, 0, h_ccy, 0L);
|
||||
setl(b, T_ARG1, fx_int);
|
||||
setl(b, T_AMT2, h_base);
|
||||
setl(b, T_ID1, h_dcc);
|
||||
Bchg(b, T_STATUS, 0, h_status, 0L);
|
||||
Bchg(b, T_CCY, 0, h_ccy, 0L);
|
||||
Bchg(b, T_SVCNAME, 0, "AC_DCC", 0L);
|
||||
setl(b, T_RC, 0);
|
||||
|
||||
/* ===== 8) 완료 로깅 ===== */
|
||||
userlog("[다통화] DCC 환산 완료 pid=%ld dcc=%ld base=%ld -> %ld %s", h_pid, h_dcc, h_base, txn_int, h_ccy);
|
||||
OK(b);
|
||||
}
|
||||
|
||||
/* vim: set ts=4 sw=4 et smartindent: */
|
||||
Loading…
Add table
Add a link
Reference in a new issue