Phase 2c: 전 11모듈 per-service 레거시 구조 분해·확장 (실동작 유지)

- 10개 모듈(au py rc st lg cl mm vl mg cm)을 ac 템플릿대로 분해:
  svc/ 30 서비스 .pgc + 카피북 .h, 얇은 <mod>_svr 디스패처
  dbio/ ~25 .pgc(+.h), batch/ ~17 .pgc, run/ ~17 .sh (모듈마다)
- 파일: svc 330 + dbio 276 + batch 187 + run 187, 총 소스 1602본
- 서비스별 고유 실로직(정규화 md5), build.sh 자동발견(inline/split 양립)
- 통합 검증: build DONE modules=11 servers=11 batches=187, 12서버 runok,
  333 서비스 AVAIL, 매입체인 XA 커밋(status=S), 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 11:58:44 +00:00
parent c3b99a8b75
commit fd8b418c6e
1404 changed files with 30322 additions and 5994 deletions

View file

@ -1,53 +1,56 @@
/*
* au_svr.pgc - au (승인/한도) MODULE SERVER.
* au_svr.pgc - au (승인/한도) MODULE SERVER (thin dispatcher).
*
* 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.
* The legacy "one module server, many services" pattern: this binary owns NO
* business logic. Each of au's ~30 online services now lives in its own file
* (app/src/au/svc/<SVCNAME>.pgc) with its own copybook header
* (app/src/au/svc/<SVCNAME>.h); those objects are archived into libausvc.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); 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.
* 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.
*/
#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); }
/* every au service's copybook header (declares its record struct + prototype) */
#include "AUTH.h"
#include "AUTH_CANCEL.h"
#include "AUTH_PARTCXL.h"
#include "LIMIT_CHK.h"
#include "LIMIT_DEC.h"
#include "LIMIT_RST.h"
#include "LIMIT_INQ.h"
#include "STANDIN.h"
#include "FRAUD_DETECT.h"
#include "AUTH_RETRY.h"
#include "INSTALL_AUTH.h"
#include "AUTH_STATUS.h"
#include "AUTH_REVERSE.h"
#include "AUTH_APPROVE.h"
#include "AUTH_DECLINE.h"
#include "CARD_VALIDATE.h"
#include "VELOCITY_CHK.h"
#include "BLACKLIST_CHK.h"
#include "AUTH_HOLD.h"
#include "AUTH_RELEASE.h"
#include "AUTH_HISTORY.h"
#include "LIMIT_SET.h"
#include "LIMIT_TEMP.h"
#include "AUTH_SUMMARY.h"
#include "AUTH_ROUTE.h"
#include "DCC_QUOTE.h"
#include "AUTH_CAPTURE.h"
#include "PREAUTH.h"
#include "PREAUTH_COMPLETE.h"
#include "AUTH_VOID.h"
#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) ------------- */
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"AUTH", AUTH}, {"AUTH_CANCEL", AUTH_CANCEL},
{"AUTH_PARTCXL", AUTH_PARTCXL}, {"LIMIT_CHK", LIMIT_CHK},
@ -90,571 +93,4 @@ void tpsvrdone(void)
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: */