Phase 2b: 11개 모듈 전량 실동작 (333 서비스, 전서버 부팅)

- 10개 모듈 추가/확장(au py rc st lg cl mm vl mg cm), 각 30 서비스 = 330 + tmsrv
- build.sh 자동발견으로 11 모듈서버 + dbio 라이브러리 23 + 배치 22 빌드
- schema.d 11파일 → 75 테이블, 모듈별 disjoint
- 부팅 리소스 규명: fs.mqueue.queues_max=8192(330큐>256), NDRX_MSGSIZEMAX 16000·
  MSGMAX 50(큐당 5.6MB→0.8MB), msgqueue ulimit 2GB → 전 12서버 runok
- 검증: xadmin psc 333 AVAIL, 매입체인 XA 커밋, prepared_xacts=0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
forge-bot 2026-07-19 09:58:39 +00:00
parent 8219b85f14
commit bec83c721e
73 changed files with 10705 additions and 35 deletions

660
app/src/au/au_svr.pgc Normal file
View file

@ -0,0 +1,660 @@
/*
* au_svr.pgc - au (승인/한도) MODULE SERVER.
*
* ONE binary that tpadvertise()s ALL ~30 online au services (the real Tuxedo
* "one module server, many services" pattern). Every service is a distinct
* 승인/한도 operation with genuine EXEC SQL (directly, or via the linked
* libaudbio.a dbio layer) and common math from libacqcommon.a.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
* EXEC SQL CONNECT. Owner-writes discipline: services in this server never
* tpcall each other; each owns its tables on this process's XA branch.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include <ubf.h>
#include <userlog.h>
#include <ndebug.h>
#include "acq.fd.h"
#include "au_dbio.h"
#include "acq_common.h"
/* ----- small UBF helpers ------------------------------------------------- */
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
/* ----- service prototypes ------------------------------------------------ */
void AUTH(TPSVCINFO *p); void AUTH_CANCEL(TPSVCINFO *p);
void AUTH_PARTCXL(TPSVCINFO *p); void LIMIT_CHK(TPSVCINFO *p);
void LIMIT_DEC(TPSVCINFO *p); void LIMIT_RST(TPSVCINFO *p);
void LIMIT_INQ(TPSVCINFO *p); void STANDIN(TPSVCINFO *p);
void FRAUD_DETECT(TPSVCINFO *p); void AUTH_RETRY(TPSVCINFO *p);
void INSTALL_AUTH(TPSVCINFO *p); void AUTH_STATUS(TPSVCINFO *p);
void AUTH_REVERSE(TPSVCINFO *p); void AUTH_APPROVE(TPSVCINFO *p);
void AUTH_DECLINE(TPSVCINFO *p); void CARD_VALIDATE(TPSVCINFO *p);
void VELOCITY_CHK(TPSVCINFO *p); void BLACKLIST_CHK(TPSVCINFO *p);
void AUTH_HOLD(TPSVCINFO *p); void AUTH_RELEASE(TPSVCINFO *p);
void AUTH_HISTORY(TPSVCINFO *p); void LIMIT_SET(TPSVCINFO *p);
void LIMIT_TEMP(TPSVCINFO *p); void AUTH_SUMMARY(TPSVCINFO *p);
void AUTH_ROUTE(TPSVCINFO *p); void DCC_QUOTE(TPSVCINFO *p);
void AUTH_CAPTURE(TPSVCINFO *p); void PREAUTH(TPSVCINFO *p);
void PREAUTH_COMPLETE(TPSVCINFO *p); void AUTH_VOID(TPSVCINFO *p);
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"AUTH", AUTH}, {"AUTH_CANCEL", AUTH_CANCEL},
{"AUTH_PARTCXL", AUTH_PARTCXL}, {"LIMIT_CHK", LIMIT_CHK},
{"LIMIT_DEC", LIMIT_DEC}, {"LIMIT_RST", LIMIT_RST},
{"LIMIT_INQ", LIMIT_INQ}, {"STANDIN", STANDIN},
{"FRAUD_DETECT", FRAUD_DETECT}, {"AUTH_RETRY", AUTH_RETRY},
{"INSTALL_AUTH", INSTALL_AUTH}, {"AUTH_STATUS", AUTH_STATUS},
{"AUTH_REVERSE", AUTH_REVERSE}, {"AUTH_APPROVE", AUTH_APPROVE},
{"AUTH_DECLINE", AUTH_DECLINE}, {"CARD_VALIDATE", CARD_VALIDATE},
{"VELOCITY_CHK", VELOCITY_CHK}, {"BLACKLIST_CHK", BLACKLIST_CHK},
{"AUTH_HOLD", AUTH_HOLD}, {"AUTH_RELEASE", AUTH_RELEASE},
{"AUTH_HISTORY", AUTH_HISTORY}, {"LIMIT_SET", LIMIT_SET},
{"LIMIT_TEMP", LIMIT_TEMP}, {"AUTH_SUMMARY", AUTH_SUMMARY},
{"AUTH_ROUTE", AUTH_ROUTE}, {"DCC_QUOTE", DCC_QUOTE},
{"AUTH_CAPTURE", AUTH_CAPTURE}, {"PREAUTH", PREAUTH},
{"PREAUTH_COMPLETE", PREAUTH_COMPLETE}, {"AUTH_VOID", AUTH_VOID},
{NULL, NULL}
};
int tpsvrinit(int argc, char **argv)
{
int i;
if (tpopen() < 0) {
userlog("au_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("au_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
return -1;
}
}
userlog("au_svr: %d au(승인/한도) 서비스 광고 완료, RM 오픈", i);
return 0;
}
void tpsvrdone(void)
{
tpclose();
userlog("au_svr: tpsvrdone");
}
/* ========================================================================= */
/* 1. AUTH - 승인 접수: 한도 점검 후 authorization 원장 기록 + 한도 차감. */
/* ========================================================================= */
void AUTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], bizdate[16], appr[12];
long amount, auth_id, lim = 0, used = 0;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) { userlog("AUTH: T_KEY1(카드번호) 누락"); FAIL(b); }
gets_(b, T_MERCHANT, merch, sizeof(merch));
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
/* 한도 점검: 초과면 거절(D) 상태로 기록 */
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
if (used + amount > lim) {
userlog("AUTH 한도초과 card=%s used=%ld amt=%ld lim=%ld -> 거절", card, used, amount, lim);
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (audb_insert_auth(auth_id, card, merch, amount, "000000", "AUTH", "D", bizdate) < 0) FAIL(b);
setl(b, T_ID1, auth_id);
setl(b, T_RC, 1);
Bchg(b, T_STATUS, 0, "D", 0L);
OK(b);
}
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
sprintf(appr, "%06ld", auth_id % 1000000);
if (audb_insert_auth(auth_id, card, merch, amount, appr, "AUTH", "A", bizdate) < 0) FAIL(b);
if (audb_limit_dec(card, amount) < 0) FAIL(b);
setl(b, T_ID1, auth_id);
setl(b, T_RC, 0);
Bchg(b, T_STR2, 0, appr, 0L);
Bchg(b, T_STATUS, 0, "A", 0L);
userlog("AUTH 승인 auth_id=%ld card=%s merch=%s amt=%ld appr=%s", auth_id, card, merch, amount, appr);
OK(b);
}
/* 2. AUTH_CANCEL - 승인 전체 취소 + 한도 원복. */
void AUTH_CANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16], reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_auth, h_amt;
char h_card[24], h_bizdate[16], h_reason[128];
EXEC SQL END DECLARE SECTION;
h_auth = getl(b, T_ID1);
h_amt = getl(b, T_AMOUNT);
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "CUSTOMER");
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_auth, 'FULL', :h_amt, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (audb_update_auth_status(h_auth, "C") < 0) FAIL(b);
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
Bchg(b, T_STATUS, 0, "C", 0L);
OK(b);
}
/* 3. AUTH_PARTCXL - 부분 취소: 승인 금액 감액 + 한도 부분 원복. */
void AUTH_PARTCXL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_auth, h_amt, h_new;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_auth = getl(b, T_ID1);
h_amt = getl(b, T_AMOUNT); /* 취소 금액 */
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT amount INTO :h_new FROM au_authorization WHERE auth_id = :h_auth;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
h_new = h_new - h_amt;
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_auth, 'PARTIAL', :h_amt, 'PARTIAL', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (audb_update_auth_amount(h_auth, h_new) < 0) FAIL(b);
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
setl(b, T_AMOUNT, h_new);
OK(b);
}
/* 4. LIMIT_CHK - 카드 한도 점검 (사용가능 여부만 리턴). */
void LIMIT_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long amount, lim = 0, used = 0;
gets_(b, T_KEY1, card, sizeof(card));
amount = getl(b, T_AMOUNT);
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
setl(b, T_AMT1, lim);
setl(b, T_AMT2, used);
setl(b, T_RC, (used + amount > lim) ? 1 : 0);
OK(b);
}
/* 5. LIMIT_DEC - 한도 차감(사용금액 증가). */
void LIMIT_DEC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long amount = getl(b, T_AMOUNT);
gets_(b, T_KEY1, card, sizeof(card));
if (audb_limit_dec(card, amount) < 0) FAIL(b);
OK(b);
}
/* 6. LIMIT_RST - 한도 사용금액 초기화(일마감 원복). */
void LIMIT_RST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
gets_(b, T_KEY1, card, sizeof(card));
if (audb_limit_reset(card) < 0) FAIL(b);
OK(b);
}
/* 7. LIMIT_INQ - 한도 조회 (가용한도 계산). */
void LIMIT_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long lim = 0, used = 0;
gets_(b, T_KEY1, card, sizeof(card));
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
setl(b, T_AMT1, lim);
setl(b, T_AMT2, used);
setl(b, T_AMT3, lim - used); /* 가용한도 */
OK(b);
}
/* 8. STANDIN - 발급사 무응답 시 대리승인(STANDIN, 소액 한도 내). */
void STANDIN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], bizdate[16];
long amount, auth_id, stand_lim = 50000;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_MERCHANT, merch, sizeof(merch));
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (amount > stand_lim) { setl(b, T_RC, 1); Bchg(b, T_STATUS, 0, "D", 0L); OK(b); }
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (audb_insert_auth(auth_id, card, merch, amount, "STDIN0", "STANDIN", "A", bizdate) < 0) FAIL(b);
setl(b, T_ID1, auth_id);
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 9. FRAUD_DETECT - 부정거래 스코어링 + 위험시 fraud_log 기록. */
void FRAUD_DETECT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16], reason[128];
long auth_id, amount, score = 0, fraud_id;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_card[24];
EXEC SQL END DECLARE SECTION;
auth_id = getl(b, T_ID1);
amount = getl(b, T_AMOUNT);
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
/* 룰: 고액 + 당일 승인건수 과다 = 스코어 상승 */
if (amount > 1000000) score += 50;
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 5) score += 40;
score += (h_cnt * 2);
strcpy(reason, score >= 50 ? "HIGH_RISK" : "OK");
setl(b, T_ARG1, score);
if (score >= 50) {
fraud_id = audb_insert_fraud(auth_id, card, score, reason, bizdate);
if (fraud_id < 0) FAIL(b);
setl(b, T_ID2, fraud_id);
setl(b, T_RC, 1);
} else {
setl(b, T_RC, 0);
}
OK(b);
}
/* 10. AUTH_RETRY - 거절 승인 재시도 (상태 D->A 재승인). */
void AUTH_RETRY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long auth_id = getl(b, T_ID1), amount = getl(b, T_AMOUNT), lim = 0, used = 0;
gets_(b, T_KEY1, card, sizeof(card));
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
if (used + amount > lim) { setl(b, T_RC, 1); FAIL(b); }
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
if (audb_limit_dec(card, amount) < 0) FAIL(b);
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 11. INSTALL_AUTH - 할부 승인: 개월 수만큼 분할 승인 레코드. */
void INSTALL_AUTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], bizdate[16];
long months, amount, i, auth_id;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_MERCHANT, merch, sizeof(merch));
amount = getl(b, T_AMOUNT);
months = getl(b, T_INSTALL_N); if (months < 1) months = 3;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
for (i = 1; i <= months; i++) {
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (audb_insert_auth(auth_id, card, merch, amount / months, "INST00", "INSTALL", "A", bizdate) < 0) FAIL(b);
}
if (audb_limit_dec(card, amount) < 0) FAIL(b);
setl(b, T_COUNT, months);
OK(b);
}
/* 12. AUTH_STATUS - 승인 상태 조회 (dbio). */
void AUTH_STATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long auth_id = getl(b, T_ID1);
char st[4];
if (audb_get_auth_status(auth_id, st) < 0) FAIL(b);
Bchg(b, T_STATUS, 0, st, 0L);
OK(b);
}
/* 13. AUTH_REVERSE - 승인 역전(reversal) + 한도 원복. */
void AUTH_REVERSE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long auth_id = getl(b, T_ID1), amount = getl(b, T_AMOUNT);
gets_(b, T_KEY1, card, sizeof(card));
if (audb_update_auth_status(auth_id, "R") < 0) FAIL(b);
if (card[0] && audb_limit_dec(card, -amount) < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "R", 0L);
OK(b);
}
/* 14. AUTH_APPROVE - 승인 확정(pending->approved). */
void AUTH_APPROVE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long auth_id = getl(b, T_ID1);
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 15. AUTH_DECLINE - 승인 거절 처리. */
void AUTH_DECLINE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_auth;
char h_reason[128];
EXEC SQL END DECLARE SECTION;
h_auth = getl(b, T_ID1);
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "DECLINE");
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
EXEC SQL UPDATE au_authorization SET status = 'D', decline_reason = :h_reason, updated_at = now()
WHERE auth_id = :h_auth;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
Bchg(b, T_STATUS, 0, "D", 0L);
OK(b);
}
/* 16. CARD_VALIDATE - 카드번호 Luhn 검증 (common lib). */
void CARD_VALIDATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
gets_(b, T_KEY1, card, sizeof(card));
setl(b, T_RC, acq_luhn_ok(card) ? 0 : 1);
OK(b);
}
/* 17. VELOCITY_CHK - 속도(velocity) 점검: 당일 카드 승인건수/합계. */
void VELOCITY_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_sum;
char h_card[24], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
FROM au_authorization WHERE card_no = :h_card AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_RC, (h_cnt > 10) ? 1 : 0);
OK(b);
}
/* 18. BLACKLIST_CHK - 블랙리스트 카드 조회. */
void BLACKLIST_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
OK(b);
}
/* 19. AUTH_HOLD - 승인 보류. */
void AUTH_HOLD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long auth_id = getl(b, T_ID1);
if (audb_update_auth_status(auth_id, "H") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "H", 0L);
OK(b);
}
/* 20. AUTH_RELEASE - 보류 해제. */
void AUTH_RELEASE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long auth_id = getl(b, T_ID1);
if (audb_update_auth_status(auth_id, "A") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 21. AUTH_HISTORY - 카드 승인 이력 건수 조회. */
void AUTH_HISTORY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
OK(b);
}
/* 22. LIMIT_SET - 카드 한도 설정/등록 (dbio upsert). */
void LIMIT_SET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long lim = getl(b, T_AMT1);
gets_(b, T_KEY1, card, sizeof(card));
if (lim <= 0) lim = 1000000;
if (audb_limit_set(card, lim) < 0) FAIL(b);
OK(b);
}
/* 23. LIMIT_TEMP - 임시 한도 상향 (temp_limit 저장 + 당일 상향). */
void LIMIT_TEMP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_temp;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
h_temp = getl(b, T_AMT1); if (h_temp <= 0) h_temp = 2000000;
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL UPDATE card_limit
SET temp_limit = :h_temp, daily_limit = daily_limit + :h_temp, updated_at = now()
WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
OK(b);
}
/* 24. AUTH_SUMMARY - 가맹점 당일 승인 집계 upsert (dbio). */
void AUTH_SUMMARY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_appr, h_dec, h_amt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*),
count(*) FILTER (WHERE status = 'A'),
count(*) FILTER (WHERE status = 'D'),
coalesce(sum(amount),0)
INTO :h_cnt, :h_appr, :h_dec, :h_amt
FROM au_authorization WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (audb_upsert_auth_summary(merch, bizdate, h_cnt, h_appr, h_dec, h_amt) < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_amt);
OK(b);
}
/* 25. AUTH_ROUTE - 승인 라우팅(발급사 BIN 기준 라우팅 코드 산출). */
void AUTH_ROUTE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], route[8];
long bin;
gets_(b, T_KEY1, card, sizeof(card));
bin = atol(card) / 1000000000L; /* 앞 6자리 근사 BIN */
if (card[0] == '4') strcpy(route, "VISA");
else if (card[0] == '5') strcpy(route, "MC");
else if (card[0] == '3') strcpy(route, "AMEX");
else strcpy(route, "LOCAL");
Bchg(b, T_STR1, 0, route, 0L);
setl(b, T_ARG1, bin);
OK(b);
}
/* 26. DCC_QUOTE - 해외승인 DCC 환산 견적(통화 -> 원화). */
void DCC_QUOTE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char ccy[8];
long fx, rate = 1350, krw;
fx = getl(b, T_FX_AMT);
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
krw = fx * rate / 100;
setl(b, T_AMOUNT, krw);
setl(b, T_ARG1, rate);
OK(b);
}
/* 27. AUTH_CAPTURE - 승인 매입확정(capture): 상태 A->M(매입대상). */
void AUTH_CAPTURE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_auth;
EXEC SQL END DECLARE SECTION;
h_auth = getl(b, T_ID1);
EXEC SQL UPDATE au_authorization SET status = 'M', captured_at = now(), updated_at = now()
WHERE auth_id = :h_auth AND status = 'A';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); FAIL(b); }
setl(b, T_RC, 0);
Bchg(b, T_STATUS, 0, "M", 0L);
OK(b);
}
/* 28. PREAUTH - 예비승인(pre-authorization) 기록. */
void PREAUTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_amt;
char h_card[24], h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_amt = getl(b, T_AMOUNT);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('au_preauth_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO au_preauth (preauth_id, card_no, merchant_id, amount, status, biz_date)
VALUES (:h_id, :h_card, :h_merch, :h_amt, 'P', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_ID2, h_id);
OK(b);
}
/* 29. PREAUTH_COMPLETE - 예비승인 확정: preauth 소진 + 본승인 생성. */
void PREAUTH_COMPLETE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long auth_id;
EXEC SQL BEGIN DECLARE SECTION;
long h_pre, h_amt;
char h_card[24], h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pre = getl(b, T_ID2);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT card_no, merchant_id, amount INTO :h_card, :h_merch, :h_amt
FROM au_preauth WHERE preauth_id = :h_pre;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
EXEC SQL UPDATE au_preauth SET status = 'C' WHERE preauth_id = :h_pre;
if (sqlca.sqlcode < 0) FAIL(b);
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (audb_insert_auth(auth_id, h_card, h_merch, h_amt, "PRECMP", "PREAUTH", "A", bizdate) < 0) FAIL(b);
setl(b, T_ID1, auth_id);
OK(b);
}
/* 30. AUTH_VOID - 당일 승인 무효화(void) + 한도 원복. */
void AUTH_VOID(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_auth, h_amt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_auth = getl(b, T_ID1); h_amt = getl(b, T_AMOUNT);
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('au_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_auth, 'VOID', :h_amt, 'VOID', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (audb_update_auth_status(h_auth, "V") < 0) FAIL(b);
if (card[0] && audb_limit_dec(card, -h_amt) < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
Bchg(b, T_STATUS, 0, "V", 0L);
OK(b);
}
/* vim: set ts=4 sw=4 et smartindent: */