규모확장 Wave A: 전 11모듈 svc 30→75·batch 22→35 + 전 모듈 운영데이터 시드

- svc 330→825 (모듈당 +45 고유 실업무: chargeback·재매입·심사워크플로·전문유형별·
  대사세분·정산시뮬·지급재처리·복식결산·마감워크플로·마스터심사·검증룰·전문라우팅 등)
- batch 242→385 (모듈당 +13), 카피북 헤더·디스패처 advertise 전 75 반영
- db/schema.d/95-module-seed.sql: 전 모듈 테이블 운영데이터(au 8.4k·st 15.9k·mg 16.9k·
  lg 14.6k·rc 7.7k·py 2.9k·vl 8.5k·cm 10.4k 등, 총 ~171k행) — base 거래와 정합
- 화면 매니페스트 825 재생성, 큐 메모리 튜닝(NDRX_MSGMAX 50→30)
- 검증: build DONE modules=11 batches=385, 12서버 runok, xadmin psc 828 AVAIL,
  매입체인 XA 커밋(status=S), 새서비스 시드데이터 스모크(ACQ_MERCHSUM/RC_STATS 실값),
  전 코퍼스 .pgc 클론 0, 헤드리스 스크린샷 확인

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo-choi 2026-07-20 22:09:55 +00:00
parent 19acf4ed96
commit ea90bef264
1292 changed files with 53968 additions and 20 deletions

View file

@ -49,6 +49,51 @@
#include "PREAUTH.h"
#include "PREAUTH_COMPLETE.h"
#include "AUTH_VOID.h"
#include "LIMIT_HIST.h"
#include "LIMIT_RESTORE.h"
#include "LIMIT_REMAIN.h"
#include "LIMIT_USAGE.h"
#include "LIMIT_RAISE_REQ.h"
#include "CARD_SUSPEND.h"
#include "CARD_RESUME.h"
#include "CARD_REISSUE.h"
#include "CARD_STATE_INQ.h"
#include "AUTH_PARTIAL.h"
#include "AUTH_RESEND.h"
#include "TOKEN_VERIFY.h"
#include "BIN_ROUTE_INQ.h"
#include "CANCEL_REPROC.h"
#include "CANCEL_STATUS.h"
#include "NOAUTH_CAPTURE.h"
#include "NIGHT_POLICY.h"
#include "OVERSEAS_POLICY.h"
#include "VELOCITY_HOUR.h"
#include "VELOCITY_WEEK.h"
#include "VELOCITY_MERCH.h"
#include "BLACKLIST_ADD.h"
#include "BLACKLIST_DEL.h"
#include "BLACKLIST_INQ.h"
#include "FRAUD_RULE_AMT.h"
#include "FRAUD_RULE_FREQ.h"
#include "FRAUD_RULE_DUP.h"
#include "FRAUD_CASE_INQ.h"
#include "AUTH_DELAY_MON.h"
#include "STANDIN_SETTLE.h"
#include "STANDIN_INQ.h"
#include "PREAUTH_EXTEND.h"
#include "PREAUTH_CANCEL.h"
#include "PREAUTH_INQ.h"
#include "STAT_HOURLY.h"
#include "STAT_BYTYPE.h"
#include "STAT_ISSUER.h"
#include "DECLINE_ANALYZE.h"
#include "AUTH_AMT_ADJUST.h"
#include "AUTH_EXPIRE_CHK.h"
#include "MERCH_AUTH_RANK.h"
#include "CARD_DAILY_RPT.h"
#include "RISK_SCORE_INQ.h"
#include "APPROVAL_RATE.h"
#include "AUTH_DUP_CHK.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
@ -67,6 +112,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"AUTH_ROUTE", AUTH_ROUTE}, {"DCC_QUOTE", DCC_QUOTE},
{"AUTH_CAPTURE", AUTH_CAPTURE}, {"PREAUTH", PREAUTH},
{"PREAUTH_COMPLETE", PREAUTH_COMPLETE}, {"AUTH_VOID", AUTH_VOID},
{"LIMIT_HIST", LIMIT_HIST}, {"LIMIT_RESTORE", LIMIT_RESTORE},
{"LIMIT_REMAIN", LIMIT_REMAIN}, {"LIMIT_USAGE", LIMIT_USAGE},
{"LIMIT_RAISE_REQ", LIMIT_RAISE_REQ}, {"CARD_SUSPEND", CARD_SUSPEND},
{"CARD_RESUME", CARD_RESUME}, {"CARD_REISSUE", CARD_REISSUE},
{"CARD_STATE_INQ", CARD_STATE_INQ}, {"AUTH_PARTIAL", AUTH_PARTIAL},
{"AUTH_RESEND", AUTH_RESEND}, {"TOKEN_VERIFY", TOKEN_VERIFY},
{"BIN_ROUTE_INQ", BIN_ROUTE_INQ}, {"CANCEL_REPROC", CANCEL_REPROC},
{"CANCEL_STATUS", CANCEL_STATUS}, {"NOAUTH_CAPTURE", NOAUTH_CAPTURE},
{"NIGHT_POLICY", NIGHT_POLICY}, {"OVERSEAS_POLICY", OVERSEAS_POLICY},
{"VELOCITY_HOUR", VELOCITY_HOUR}, {"VELOCITY_WEEK", VELOCITY_WEEK},
{"VELOCITY_MERCH", VELOCITY_MERCH}, {"BLACKLIST_ADD", BLACKLIST_ADD},
{"BLACKLIST_DEL", BLACKLIST_DEL}, {"BLACKLIST_INQ", BLACKLIST_INQ},
{"FRAUD_RULE_AMT", FRAUD_RULE_AMT}, {"FRAUD_RULE_FREQ", FRAUD_RULE_FREQ},
{"FRAUD_RULE_DUP", FRAUD_RULE_DUP}, {"FRAUD_CASE_INQ", FRAUD_CASE_INQ},
{"AUTH_DELAY_MON", AUTH_DELAY_MON}, {"STANDIN_SETTLE", STANDIN_SETTLE},
{"STANDIN_INQ", STANDIN_INQ}, {"PREAUTH_EXTEND", PREAUTH_EXTEND},
{"PREAUTH_CANCEL", PREAUTH_CANCEL}, {"PREAUTH_INQ", PREAUTH_INQ},
{"STAT_HOURLY", STAT_HOURLY}, {"STAT_BYTYPE", STAT_BYTYPE},
{"STAT_ISSUER", STAT_ISSUER}, {"DECLINE_ANALYZE", DECLINE_ANALYZE},
{"AUTH_AMT_ADJUST", AUTH_AMT_ADJUST}, {"AUTH_EXPIRE_CHK", AUTH_EXPIRE_CHK},
{"MERCH_AUTH_RANK", MERCH_AUTH_RANK}, {"CARD_DAILY_RPT", CARD_DAILY_RPT},
{"RISK_SCORE_INQ", RISK_SCORE_INQ}, {"APPROVAL_RATE", APPROVAL_RATE},
{"AUTH_DUP_CHK", AUTH_DUP_CHK},
{NULL, NULL}
};

View file

@ -0,0 +1,69 @@
/*
* au_blacklist_hit_batch.pgc - au 블랙리스트 승인시도 검출 배치 (조인 커서 + 로그, XA).
*
* 영업일 승인원장을 블랙리스트와 조인해, 등재 카드로 들어온 승인 시도를
* 카드 단위 커서로 적발한다. 카드마다 만점(999) 점수의 BLACKLIST_HIT
* 부정거래 로그를 남기고 시도 건수를 함께 기록한다.
* Usage: au_blacklist_hit_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_blacklist_hit_batch.h"
long aub_blacklist_hit_score(void)
{
return 999;
}
int main(int argc, char **argv)
{
au_blacklist_hit_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_card[24];
long h_tries, h_fid, h_score;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
h_score = aub_blacklist_hit_score();
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cbhb CURSOR FOR
SELECT a.card_no, count(*)
FROM au_authorization a JOIN au_blacklist k ON k.card_no = a.card_no
WHERE a.biz_date = :h_bizdate
GROUP BY a.card_no ORDER BY count(*) DESC;
EXEC SQL OPEN cbhb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cbhb INTO :h_card, :h_tries;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; }
ctx.hit_cards++;
ctx.hit_auths += h_tries;
EXEC SQL SELECT nextval('au_fraud_seq') INTO :h_fid;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; }
EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date)
VALUES (:h_fid, 0, :h_card, :h_score, 'BLACKLIST_HIT', :h_bizdate);
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; }
ctx.logged++;
printf(" 블랙리스트 히트 card=%.6s** 시도=%ld건\n", h_card, h_tries);
}
EXEC SQL CLOSE cbhb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_blacklist_hit_batch COMMIT: bizdate=%s 적발카드=%ld 시도=%ld건 기록=%ld\n",
ctx.biz_date, ctx.hit_cards, ctx.hit_auths, ctx.logged);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,81 @@
/*
* au_cancel_reproc_batch.pgc - au 미반영 승인취소 재처리 배치 (조인 커서 + 분기 UPDATE, XA).
*
* 영업일의 취소 접수(au_cancel) 중 원승인이 아직 취소류 상태가 아닌 건을
* 조인 커서로 골라, 유형별로 분기 반영한다: FULL->C, VOID->V,
* PARTIAL->원승인 금액 감액(원금액 미만일 때만). 전량 ONE XA transaction.
* Usage: au_cancel_reproc_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_cancel_reproc_batch.h"
long aub_cancel_reproc_total(const au_cancel_reproc_ctx_t *ctx)
{
return ctx->reproc_full + ctx->reproc_void + ctx->reproc_partial;
}
int main(int argc, char **argv)
{
au_cancel_reproc_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_ctype[12], h_astat[4];
long h_auth, h_camt, h_aamt;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE ccrb CURSOR FOR
SELECT c.auth_id, c.cancel_type, c.amount, a.status, a.amount
FROM au_cancel c JOIN au_authorization a ON a.auth_id = c.auth_id
WHERE c.biz_date = :h_bizdate AND a.status NOT IN ('C','V','R')
ORDER BY c.cancel_id;
EXEC SQL OPEN ccrb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH ccrb INTO :h_auth, :h_ctype, :h_camt, :h_astat, :h_aamt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; }
if (h_astat[0] != 'A' && h_astat[0] != 'M') { ctx.skipped++; continue; }
if (strcmp(h_ctype, "PARTIAL") == 0) {
if (h_camt >= h_aamt) { ctx.skipped++; continue; } /* 정합성 오류 건 스킵 */
EXEC SQL UPDATE au_authorization
SET amount = amount - :h_camt, updated_at = now()
WHERE auth_id = :h_auth;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; }
ctx.reproc_partial++;
} else if (strcmp(h_ctype, "VOID") == 0) {
EXEC SQL UPDATE au_authorization SET status = 'V', updated_at = now()
WHERE auth_id = :h_auth;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; }
ctx.reproc_void++;
} else {
EXEC SQL UPDATE au_authorization SET status = 'C', updated_at = now()
WHERE auth_id = :h_auth;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; }
ctx.reproc_full++;
}
}
EXEC SQL CLOSE ccrb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_cancel_reproc_batch COMMIT: bizdate=%s 재처리=%ld (FULL=%ld VOID=%ld PARTIAL=%ld) skip=%ld\n",
ctx.biz_date, aub_cancel_reproc_total(&ctx),
ctx.reproc_full, ctx.reproc_void, ctx.reproc_partial, ctx.skipped);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,60 @@
/*
* au_delay_watch_batch.pgc - au 승인 체류시간 감시 배치 (EPOCH 다중구간 집계, XA).
*
* 미매입 승인(A) 전체의 체류시간을 EPOCH 초로 환산해 평균/최대와
* 1시간·24시간 초과 구간별 건수를 한 번의 집계로 뽑아, 최악 체류 건의
* 지연구간 라벨(FRESH/AGING/STALE/CRITICAL)을 판정한다.
* Usage: au_delay_watch_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_delay_watch_batch.h"
const char *aub_delay_bucket(long age_sec)
{
if (age_sec >= 86400) return "CRITICAL";
if (age_sec >= 3600) return "STALE";
if (age_sec >= 600) return "AGING";
return "FRESH";
}
int main(int argc, char **argv)
{
au_delay_watch_ctx_t ctx;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_avg = 0, h_max = 0, h_o1h = 0, h_o1d = 0;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT count(*),
CAST(coalesce(avg(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint),
CAST(coalesce(max(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint),
count(*) FILTER (WHERE created_at < now() - interval '1 hour'),
count(*) FILTER (WHERE created_at < now() - interval '24 hours')
INTO :h_cnt, :h_avg, :h_max, :h_o1h, :h_o1d
FROM au_authorization
WHERE status = 'A' AND captured_at IS NULL;
if (sqlca.sqlcode < 0) { fprintf(stderr, "au_delay_watch_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
ctx.pending = h_cnt;
ctx.avg_age_sec = h_avg;
ctx.max_age_sec = h_max;
ctx.over_1h = h_o1h;
ctx.over_1d = h_o1d;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_delay_watch_batch COMMIT: 미매입=%ld 평균=%lds 최대=%lds(%s) 1h초과=%ld 24h초과=%ld\n",
ctx.pending, ctx.avg_age_sec, ctx.max_age_sec,
aub_delay_bucket(ctx.max_age_sec), ctx.over_1h, ctx.over_1d);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,65 @@
/*
* au_dup_auth_batch.pgc - au 중복승인 검출 배치 (다중키 GROUP 커서, XA).
*
* 영업일의 승인(A/M/S) 건을 카드+가맹점+금액 3중 키로 그룹핑해 2건 이상
* 반복된 그룹을 커서로 뽑고, 그룹마다 원거래 1건을 제외한 잉여 승인건수와
* 금액(중복 환불 예상액)을 적산해 보고한다.
* Usage: au_dup_auth_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_dup_auth_batch.h"
long aub_dup_extra(long group_cnt)
{
return (group_cnt > 1) ? group_cnt - 1 : 0;
}
int main(int argc, char **argv)
{
au_dup_auth_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_card[24], h_merch[64];
long h_amt, h_cnt;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cdab CURSOR FOR
SELECT card_no, merchant_id, amount, count(*)
FROM au_authorization
WHERE biz_date = :h_bizdate AND status IN ('A','M','S')
GROUP BY card_no, merchant_id, amount
HAVING count(*) >= 2
ORDER BY count(*) DESC, card_no;
EXEC SQL OPEN cdab;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cdab INTO :h_card, :h_merch, :h_amt, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdab; tpabort(0); return 1; }
ctx.dup_groups++;
ctx.extra_auths += aub_dup_extra(h_cnt);
ctx.extra_gross += aub_dup_extra(h_cnt) * h_amt;
printf(" 중복그룹 card=%.6s** merch=%s amt=%ld x%ld\n", h_card, h_merch, h_amt, h_cnt);
}
EXEC SQL CLOSE cdab;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_dup_auth_batch COMMIT: bizdate=%s 중복그룹=%ld 잉여승인=%ld건 %ld원\n",
ctx.biz_date, ctx.dup_groups, ctx.extra_auths, ctx.extra_gross);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,72 @@
/*
* au_hourly_stat_batch.pgc - au 시간대별 승인 히스토그램 배치 (GROUP 커서 + 배열 적재, XA).
*
* 영업일의 승인 접수를 시(0~23) 단위 그룹 커서로 페치해 24칸 히스토그램
* 배열에 적재한 뒤, C 루프로 피크 시간대를 찾고 오전/오후 분포를 출력한다.
* Usage: au_hourly_stat_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_hourly_stat_batch.h"
void aub_hourly_find_peak(au_hourly_stat_ctx_t *ctx)
{
int h;
ctx->peak_hour = 0;
ctx->peak_count = 0;
for (h = 0; h < 24; h++)
if (ctx->by_hour[h] > ctx->peak_count) {
ctx->peak_count = ctx->by_hour[h];
ctx->peak_hour = h;
}
}
int main(int argc, char **argv)
{
au_hourly_stat_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long am = 0, pm = 0;
int h;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_hour, h_cnt;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE chsb CURSOR FOR
SELECT CAST(EXTRACT(HOUR FROM created_at) AS bigint), count(*)
FROM au_authorization WHERE biz_date = :h_bizdate
GROUP BY 1 ORDER BY 1;
EXEC SQL OPEN chsb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH chsb INTO :h_hour, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chsb; tpabort(0); return 1; }
if (h_hour >= 0 && h_hour < 24) ctx.by_hour[h_hour] = h_cnt;
ctx.total += h_cnt;
}
EXEC SQL CLOSE chsb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
aub_hourly_find_peak(&ctx);
for (h = 0; h < 12; h++) am += ctx.by_hour[h];
for (h = 12; h < 24; h++) pm += ctx.by_hour[h];
printf(">>> au_hourly_stat_batch COMMIT: bizdate=%s 총=%ld 피크=%02d시(%ld건) 오전=%ld 오후=%ld\n",
ctx.biz_date, ctx.total, ctx.peak_hour, ctx.peak_count, am, pm);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,76 @@
/*
* au_issuer_mix_batch.pgc - au 발급사망 구성비 배치 (GROUP BY 커서 + 백분율, XA).
*
* 영업일 승인 접수를 카드번호 선두 자리로 그룹핑한 커서를 순회하며
* 카드망(AMEX/VISA/MC/LOCAL/ETC)별 건수와 구성비(%)를 출력하고
* 최다 카드망을 찾아 보고한다.
* Usage: au_issuer_mix_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_issuer_mix_batch.h"
const char *aub_issuer_network(char lead)
{
switch (lead) {
case '3': return "AMEX";
case '4': return "VISA";
case '5': return "MC";
case '6': return "LOCAL";
default: return "ETC";
}
}
int main(int argc, char **argv)
{
au_issuer_mix_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_lead[4];
long h_cnt, h_total = 0;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT count(*) INTO :h_total FROM au_authorization WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
ctx.total = h_total;
EXEC SQL DECLARE cimb CURSOR FOR
SELECT substr(card_no, 1, 1), count(*)
FROM au_authorization WHERE biz_date = :h_bizdate
GROUP BY substr(card_no, 1, 1)
ORDER BY count(*) DESC;
EXEC SQL OPEN cimb;
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cimb INTO :h_lead, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cimb; tpabort(0); return 1; }
ctx.networks++;
if (h_cnt > ctx.top_count) {
ctx.top_count = h_cnt;
strncpy(ctx.top_network, aub_issuer_network(h_lead[0]), sizeof(ctx.top_network)-1);
}
printf(" 망 %-5s : %6ld건 (%3ld%%)\n", aub_issuer_network(h_lead[0]), h_cnt,
(ctx.total > 0) ? (h_cnt * 100L) / ctx.total : 0);
}
EXEC SQL CLOSE cimb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_issuer_mix_batch COMMIT: bizdate=%s 망수=%ld 총=%ld 최다=%s(%ld건)\n",
ctx.biz_date, ctx.networks, ctx.total, ctx.top_network, ctx.top_count);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,61 @@
/*
* au_limit_restore_batch.pgc - au 임시한도 일괄 복원 배치 (커서 + UPDATE, XA).
*
* LIMIT_TEMP 로 임시 상향된 채 남아 있는 카드(temp_limit > 0)를 커서로 훑어
* 카드마다 daily_limit 에서 임시분을 걷어내고 temp_limit 를 0 으로 되돌린다.
* 전체가 ONE global XA transaction; 실패 시 tpabort 로 전량 원복.
* Usage: au_limit_restore_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_limit_restore_batch.h"
void aub_limit_restore_summary(const au_limit_restore_ctx_t *ctx, char *out, int cap)
{
snprintf(out, cap, "cards=%ld amount=%ld", ctx->restored_cards, ctx->restored_amount);
}
int main(int argc, char **argv)
{
au_limit_restore_ctx_t ctx;
char line[80];
EXEC SQL BEGIN DECLARE SECTION;
char h_card[24];
long h_temp;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE clrb CURSOR FOR
SELECT card_no, temp_limit FROM card_limit
WHERE temp_limit > 0 ORDER BY card_no;
EXEC SQL OPEN clrb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH clrb INTO :h_card, :h_temp;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clrb; tpabort(0); return 1; }
EXEC SQL UPDATE card_limit
SET daily_limit = daily_limit - :h_temp, temp_limit = 0, updated_at = now()
WHERE card_no = :h_card AND temp_limit = :h_temp;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clrb; tpabort(0); return 1; }
ctx.restored_cards++;
ctx.restored_amount += h_temp;
}
EXEC SQL CLOSE clrb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
aub_limit_restore_summary(&ctx, line, sizeof(line));
printf(">>> au_limit_restore_batch COMMIT: 임시한도 복원 %s\n", line);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,71 @@
/*
* au_merch_rank_batch.pgc - au 가맹점 승인 순위 배치 (window rank 커서 TOP-N, XA).
*
* 영업일의 가맹점별 유효 승인금액을 rank() 윈도우로 서열화한 서브쿼리를
* 커서로 열어 상위 5곳만 페치, 1위 대비 점유율(%)과 함께 순위표를 찍는다.
* Usage: au_merch_rank_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_merch_rank_batch.h"
long aub_rank_share_pct(long gross, long top_gross)
{
return (top_gross > 0) ? (gross * 100L) / top_gross : 0;
}
int main(int argc, char **argv)
{
au_merch_rank_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_rnk, h_gross, h_top;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
h_top = AU_MERCH_RANK_TOP;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cmrb CURSOR FOR
SELECT r.rnk, r.merchant_id, r.gross FROM (
SELECT merchant_id, sum(amount) AS gross,
rank() OVER (ORDER BY sum(amount) DESC) AS rnk
FROM au_authorization
WHERE biz_date = :h_bizdate AND status IN ('A','M','S')
GROUP BY merchant_id) r
WHERE r.rnk <= :h_top
ORDER BY r.rnk;
EXEC SQL OPEN cmrb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cmrb INTO :h_rnk, :h_merch, :h_gross;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cmrb; tpabort(0); return 1; }
if (ctx.listed == 0) {
ctx.top_gross = h_gross;
strncpy(ctx.top_merchant, h_merch, sizeof(ctx.top_merchant)-1);
}
ctx.listed++;
ctx.grand_gross += h_gross;
printf(" %ld위 %-10s %12ld원 (1위대비 %ld%%)\n",
h_rnk, h_merch, h_gross, aub_rank_share_pct(h_gross, ctx.top_gross));
}
EXEC SQL CLOSE cmrb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_merch_rank_batch COMMIT: bizdate=%s TOP%d=%ld곳 1위=%s(%ld원) 합계=%ld원\n",
ctx.biz_date, AU_MERCH_RANK_TOP, ctx.listed, ctx.top_merchant, ctx.top_gross, ctx.grand_gross);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,63 @@
/*
* au_night_auth_batch.pgc - au 심야 승인 집계 배치 (시간대 조건 집계, XA).
*
* 영업일의 승인 접수를 심야(23시~06시)/주간으로 나눠 한 번의 조건부 FILTER
* 집계로 건수·금액을 뽑고, 심야건수 비중(bps)을 계산해 등급을 판정한다.
* ATMI client: ONE global transaction, tpcommit 이 XA 2PC 를 몬다.
* Usage: au_night_auth_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_night_auth_batch.h"
const char *aub_night_grade(long bps)
{
if (bps >= 4000) return "ALERT"; /* 심야비중 40% 이상 */
if (bps >= 2000) return "ELEVATED";
return "LOW";
}
int main(int argc, char **argv)
{
au_night_auth_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_ncnt = 0, h_namt = 0, h_dcnt = 0;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT
count(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 23
OR EXTRACT(HOUR FROM created_at) < 6),
coalesce(sum(amount) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 23
OR EXTRACT(HOUR FROM created_at) < 6), 0),
count(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 6
AND EXTRACT(HOUR FROM created_at) < 23)
INTO :h_ncnt, :h_namt, :h_dcnt
FROM au_authorization WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { fprintf(stderr, "au_night_auth_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
ctx.night_count = h_ncnt;
ctx.night_gross = h_namt;
ctx.day_count = h_dcnt;
ctx.night_bps = (h_ncnt + h_dcnt > 0) ? (h_ncnt * 10000L) / (h_ncnt + h_dcnt) : 0;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_night_auth_batch COMMIT: bizdate=%s night=%ld(%ld원) day=%ld 심야비중=%ldbps grade=%s\n",
ctx.biz_date, ctx.night_count, ctx.night_gross, ctx.day_count,
ctx.night_bps, aub_night_grade(ctx.night_bps));
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,67 @@
/*
* au_noauth_detect_batch.pgc - au 무승인 매입 검출 배치 (NOT EXISTS 안티조인, XA).
*
* 영업일의 매입원장(purchase) 전수와, 같은 가맹점/영업일/금액의 유효 승인이
* 승인원장에 없는 매입(무승인 매입 의심)을 안티조인 두 번의 집계로 뽑아
* 의심비중 기반 심각도(CLEAN/MINOR/MAJOR)를 판정한다.
* Usage: au_noauth_detect_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_noauth_detect_batch.h"
int aub_noauth_severity(long suspects, long purchases)
{
if (suspects == 0 || purchases == 0) return 0;
if (suspects * 100 >= purchases * 5) return 2; /* 5% 이상이면 MAJOR */
return 1;
}
int main(int argc, char **argv)
{
static const char *sev_name[] = { "CLEAN", "MINOR", "MAJOR" };
au_noauth_detect_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
int sev;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_all = 0, h_bad = 0, h_badamt = 0;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT count(*) INTO :h_all FROM purchase
WHERE biz_date = :h_bizdate AND status NOT IN ('C','V');
if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
EXEC SQL SELECT count(*), coalesce(sum(p.amount),0) INTO :h_bad, :h_badamt
FROM purchase p
WHERE p.biz_date = :h_bizdate AND p.status NOT IN ('C','V')
AND NOT EXISTS (SELECT 1 FROM au_authorization a
WHERE a.merchant_id = p.merchant_id
AND a.biz_date = p.biz_date
AND a.amount = p.amount
AND a.status IN ('A','M','S'));
if (sqlca.sqlcode < 0) { fprintf(stderr, "DETECT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
ctx.purchases = h_all;
ctx.suspects = h_bad;
ctx.suspect_gross = h_badamt;
sev = aub_noauth_severity(ctx.suspects, ctx.purchases);
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_noauth_detect_batch COMMIT: bizdate=%s 매입=%ld 무승인의심=%ld건 %ld원 심각도=%s\n",
ctx.biz_date, ctx.purchases, ctx.suspects, ctx.suspect_gross, sev_name[sev]);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,71 @@
/*
* au_preauth_convert_batch.pgc - au 만기 선승인 처분 배치 (커서 + 방침 분기, XA).
*
* 기준일 이전에 잡힌 미확정(P) 선승인을 커서로 훑어 방침에 따라 처분한다:
* 고액(문턱 이상)은 자동확정(C)해 승인 파이프라인으로 넘기고, 나머지는
* 자동취소(X)로 홀드를 해제하며 풀린 금액을 적산한다. ONE XA transaction.
* Usage: au_preauth_convert_batch [기준일 YYYY-MM-DD] [확정문턱금액]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_preauth_convert_batch.h"
int aub_preauth_should_confirm(long amount, long threshold)
{
return (amount >= threshold) ? 1 : 0;
}
int main(int argc, char **argv)
{
au_preauth_convert_ctx_t ctx;
const char *cutoff = (argc > 1) ? argv[1] : "2026-07-12";
long threshold = (argc > 2) ? atol(argv[2]) : 500000;
EXEC SQL BEGIN DECLARE SECTION;
char h_cutoff[16];
long h_pid, h_amt;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.cutoff_date, cutoff, sizeof(ctx.cutoff_date)-1);
if (threshold <= 0) threshold = 500000;
strncpy(h_cutoff, cutoff, sizeof(h_cutoff)-1); h_cutoff[sizeof(h_cutoff)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cpcb CURSOR FOR
SELECT preauth_id, amount FROM au_preauth
WHERE status = 'P' AND biz_date < :h_cutoff
ORDER BY preauth_id;
EXEC SQL OPEN cpcb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cpcb INTO :h_pid, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; }
if (aub_preauth_should_confirm(h_amt, threshold)) {
EXEC SQL UPDATE au_preauth SET status = 'C' WHERE preauth_id = :h_pid;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; }
ctx.confirmed++;
} else {
EXEC SQL UPDATE au_preauth SET status = 'X' WHERE preauth_id = :h_pid;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; }
ctx.expired++;
ctx.released_amount += h_amt;
}
}
EXEC SQL CLOSE cpcb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_preauth_convert_batch COMMIT: cutoff=%s 자동확정=%ld 자동취소=%ld 해제금액=%ld (문턱=%ld)\n",
ctx.cutoff_date, ctx.confirmed, ctx.expired, ctx.released_amount, threshold);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,64 @@
/*
* au_standin_settle_batch.pgc - au 대행승인 정산반영 배치 (커서 + 개별 UPDATE, XA).
*
* 이슈어 장애 복구 후 실행: 영업일의 STANDIN 승인(A) 건을 커서로 돌며
* 건별 검증(금액>0) 후 정산반영(S)으로 전환하고 건수/금액을 적산한다.
* ONE global XA transaction 으로 전량 원자 반영.
* Usage: au_standin_settle_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_standin_settle_batch.h"
int aub_standin_any(const au_standin_settle_ctx_t *ctx)
{
return (ctx->settled > 0) ? 1 : 0;
}
int main(int argc, char **argv)
{
au_standin_settle_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_auth, h_amt;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cssb CURSOR FOR
SELECT auth_id, amount FROM au_authorization
WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN' AND status = 'A'
ORDER BY auth_id;
EXEC SQL OPEN cssb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cssb INTO :h_auth, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cssb; tpabort(0); return 1; }
if (h_amt <= 0) { ctx.skipped++; continue; } /* 금액 이상 건은 건너뜀 */
EXEC SQL UPDATE au_authorization SET status = 'S', updated_at = now()
WHERE auth_id = :h_auth AND status = 'A';
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cssb; tpabort(0); return 1; }
ctx.settled++;
ctx.settled_gross += h_amt;
}
EXEC SQL CLOSE cssb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_standin_settle_batch COMMIT: bizdate=%s 정산반영=%ld건 %ld원 skip=%ld any=%d\n",
ctx.biz_date, ctx.settled, ctx.settled_gross, ctx.skipped, aub_standin_any(&ctx));
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,71 @@
/*
* au_velocity_flag_batch.pgc - au velocity 위반 카드 일괄 적발 배치 (커서 + INSERT, XA).
*
* 영업일에 임계 건수를 초과 승인한 카드를 HAVING 커서로 골라, 카드별로
* 부정거래 로그(au_fraud_log)에 RULE_VELOCITY_DAY 사유의 적발 행을 남긴다.
* 모두 ONE global XA transaction 아래에서 수행된다.
* Usage: au_velocity_flag_batch [YYYY-MM-DD] [임계건수]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "au_velocity_flag_batch.h"
long aub_velocity_score(long cnt, long threshold)
{
long s = 650 + (cnt - threshold) * 15;
return (s > 999) ? 999 : s;
}
int main(int argc, char **argv)
{
au_velocity_flag_ctx_t ctx;
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_card[24];
long h_thr, h_cnt, h_fid, h_score;
EXEC SQL END DECLARE SECTION;
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1);
ctx.threshold = (argc > 2) ? atol(argv[2]) : 10;
if (ctx.threshold <= 0) ctx.threshold = 10;
h_thr = ctx.threshold;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cvfb CURSOR FOR
SELECT card_no, count(*) FROM au_authorization
WHERE biz_date = :h_bizdate
GROUP BY card_no HAVING count(*) > :h_thr
ORDER BY count(*) DESC;
EXEC SQL OPEN cvfb;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cvfb INTO :h_card, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; }
ctx.flagged_cards++;
h_score = aub_velocity_score(h_cnt, h_thr);
EXEC SQL SELECT nextval('au_fraud_seq') INTO :h_fid;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; }
EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date)
VALUES (:h_fid, 0, :h_card, :h_score, 'RULE_VELOCITY_DAY', :h_bizdate);
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; }
ctx.logged++;
}
EXEC SQL CLOSE cvfb;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> au_velocity_flag_batch COMMIT: bizdate=%s 임계=%ld건 적발카드=%ld 기록=%ld\n",
ctx.biz_date, ctx.threshold, ctx.flagged_cards, ctx.logged);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,18 @@
/*
* au_blacklist_hit_batch.h - copybook for au_blacklist_hit_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AU_BLACKLIST_HIT_BATCH_H
#define AU_BLACKLIST_HIT_BATCH_H
typedef struct {
char biz_date[16];
long hit_cards; /* 시도 적발 카드수 */
long hit_auths; /* 시도 승인건수 */
long logged; /* fraud_log 신규 기록 */
} au_blacklist_hit_ctx_t;
/* 블랙리스트 히트는 무조건 만점(999) 점수. */
long aub_blacklist_hit_score(void);
#endif /* AU_BLACKLIST_HIT_BATCH_H */

View file

@ -0,0 +1,19 @@
/*
* au_cancel_reproc_batch.h - copybook for au_cancel_reproc_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AU_CANCEL_REPROC_BATCH_H
#define AU_CANCEL_REPROC_BATCH_H
typedef struct {
char biz_date[16];
long reproc_full; /* FULL/기타 -> C 전환 */
long reproc_void; /* VOID -> V 전환 */
long reproc_partial; /* PARTIAL 감액 반영 */
long skipped; /* 상태 부적합으로 건너뜀 */
} au_cancel_reproc_ctx_t;
/* 재처리 합계. */
long aub_cancel_reproc_total(const au_cancel_reproc_ctx_t *ctx);
#endif /* AU_CANCEL_REPROC_BATCH_H */

View file

@ -0,0 +1,19 @@
/*
* au_delay_watch_batch.h - copybook for au_delay_watch_batch (XA client).
* -> + . Kept in dbio/ for -I resolution.
*/
#ifndef AU_DELAY_WATCH_BATCH_H
#define AU_DELAY_WATCH_BATCH_H
typedef struct {
long pending; /* 미매입 승인(A) 잔량 */
long avg_age_sec; /* 평균 경과초 */
long max_age_sec; /* 최대 경과초 */
long over_1h; /* 1시간 초과 체류 건수 */
long over_1d; /* 24시간 초과 체류 건수 */
} au_delay_watch_ctx_t;
/* 경과초를 지연구간 라벨(FRESH/AGING/STALE/CRITICAL)로 변환. */
const char *aub_delay_bucket(long age_sec);
#endif /* AU_DELAY_WATCH_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* au_dup_auth_batch.h - copybook for au_dup_auth_batch (XA client).
* + . Kept in dbio/ for -I resolution.
*/
#ifndef AU_DUP_AUTH_BATCH_H
#define AU_DUP_AUTH_BATCH_H
typedef struct {
char biz_date[16];
long dup_groups; /* 중복 그룹수 (카드+가맹점+금액) */
long extra_auths; /* 원거래를 뺀 잉여 승인건수 */
long extra_gross; /* 잉여 승인 금액합 */
} au_dup_auth_ctx_t;
/* 그룹 건수에서 원거래 1건을 뺀 잉여 건수. */
long aub_dup_extra(long group_cnt);
#endif /* AU_DUP_AUTH_BATCH_H */

View file

@ -0,0 +1,19 @@
/*
* au_hourly_stat_batch.h - copybook for au_hourly_stat_batch (XA client).
* (24 ). Kept in dbio/ for -I resolution.
*/
#ifndef AU_HOURLY_STAT_BATCH_H
#define AU_HOURLY_STAT_BATCH_H
typedef struct {
char biz_date[16];
long by_hour[24]; /* 시간대별 건수 히스토그램 */
long total;
int peak_hour;
long peak_count;
} au_hourly_stat_ctx_t;
/* 히스토그램에서 피크 시간대를 찾아 ctx.peak_* 를 채운다. */
void aub_hourly_find_peak(au_hourly_stat_ctx_t *ctx);
#endif /* AU_HOURLY_STAT_BATCH_H */

View file

@ -0,0 +1,20 @@
/*
* au_issuer_mix_batch.h - copybook for au_issuer_mix_batch (XA client).
* () + BIN -> .
* Kept in dbio/ for -I resolution.
*/
#ifndef AU_ISSUER_MIX_BATCH_H
#define AU_ISSUER_MIX_BATCH_H
typedef struct {
char biz_date[16];
long networks; /* 관측된 카드망 수 */
long total; /* 전체 승인건수 */
char top_network[16];
long top_count;
} au_issuer_mix_ctx_t;
/* 카드번호 선두 한 자리를 카드망 이름으로 매핑. */
const char *aub_issuer_network(char lead);
#endif /* AU_ISSUER_MIX_BATCH_H */

View file

@ -0,0 +1,16 @@
/*
* au_limit_restore_batch.h - copybook for au_limit_restore_batch (XA client).
* . Kept in dbio/ for -I resolution.
*/
#ifndef AU_LIMIT_RESTORE_BATCH_H
#define AU_LIMIT_RESTORE_BATCH_H
typedef struct {
long restored_cards; /* 복원한 카드수 */
long restored_amount; /* 걷어낸 임시한도 총액 */
} au_limit_restore_ctx_t;
/* 복원 결과 요약 한 줄을 out 에 만든다 (버퍼 cap 바이트). */
void aub_limit_restore_summary(const au_limit_restore_ctx_t *ctx, char *out, int cap);
#endif /* AU_LIMIT_RESTORE_BATCH_H */

View file

@ -0,0 +1,21 @@
/*
* au_merch_rank_batch.h - copybook for au_merch_rank_batch (XA client).
* TOP-N . Kept in dbio/ for -I resolution.
*/
#ifndef AU_MERCH_RANK_BATCH_H
#define AU_MERCH_RANK_BATCH_H
#define AU_MERCH_RANK_TOP 5
typedef struct {
char biz_date[16];
long listed; /* 출력한 순위 줄 수 */
long top_gross; /* 1위 가맹점 금액 */
char top_merchant[64]; /* 1위 가맹점 */
long grand_gross; /* TOP-N 합계 금액 */
} au_merch_rank_ctx_t;
/* 1위 대비 점유율(%)을 계산. */
long aub_rank_share_pct(long gross, long top_gross);
#endif /* AU_MERCH_RANK_BATCH_H */

View file

@ -0,0 +1,19 @@
/*
* au_night_auth_batch.h - copybook for au_night_auth_batch (XA client).
* + . Kept in dbio/ for -I resolution.
*/
#ifndef AU_NIGHT_AUTH_BATCH_H
#define AU_NIGHT_AUTH_BATCH_H
typedef struct {
char biz_date[16];
long night_count;
long night_gross;
long day_count;
long night_bps; /* 전체 대비 심야건수 비중 (bps) */
} au_night_auth_ctx_t;
/* 심야비중 bps 를 등급 문자열로 변환 (LOW/ELEVATED/ALERT). */
const char *aub_night_grade(long bps);
#endif /* AU_NIGHT_AUTH_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* au_noauth_detect_batch.h - copybook for au_noauth_detect_batch (XA client).
* + . Kept in dbio/ for -I resolution.
*/
#ifndef AU_NOAUTH_DETECT_BATCH_H
#define AU_NOAUTH_DETECT_BATCH_H
typedef struct {
char biz_date[16];
long purchases; /* 검사한 매입건수 */
long suspects; /* 무승인 의심건수 */
long suspect_gross; /* 의심 금액합 */
} au_noauth_detect_ctx_t;
/* 의심비중으로 심각도 판정 (0=CLEAN, 1=MINOR, 2=MAJOR). */
int aub_noauth_severity(long suspects, long purchases);
#endif /* AU_NOAUTH_DETECT_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* au_preauth_convert_batch.h - copybook for au_preauth_convert_batch (XA client).
* (/) . Kept in dbio/ for -I resolution.
*/
#ifndef AU_PREAUTH_CONVERT_BATCH_H
#define AU_PREAUTH_CONVERT_BATCH_H
typedef struct {
char cutoff_date[16]; /* 이 날짜 이전 P 건이 처분 대상 */
long confirmed; /* 고액 -> C 자동확정 */
long expired; /* 일반 -> X 자동취소 */
long released_amount; /* 취소로 풀린 홀드 금액 */
} au_preauth_convert_ctx_t;
/* 처분 방침: 금액이 문턱 이상이면 확정(1), 아니면 취소(0). */
int aub_preauth_should_confirm(long amount, long threshold);
#endif /* AU_PREAUTH_CONVERT_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* au_standin_settle_batch.h - copybook for au_standin_settle_batch (XA client).
* (stand-in) . Kept in dbio/ for -I resolution.
*/
#ifndef AU_STANDIN_SETTLE_BATCH_H
#define AU_STANDIN_SETTLE_BATCH_H
typedef struct {
char biz_date[16];
long settled; /* A -> S 전환 건수 */
long settled_gross; /* 전환 금액 합계 */
long skipped; /* 금액 0 이하로 건너뛴 건수 */
} au_standin_settle_ctx_t;
/* 정산반영이 하나라도 있었는지 (1/0). */
int aub_standin_any(const au_standin_settle_ctx_t *ctx);
#endif /* AU_STANDIN_SETTLE_BATCH_H */

View file

@ -0,0 +1,18 @@
/*
* au_velocity_flag_batch.h - copybook for au_velocity_flag_batch (XA client).
* velocity + . Kept in dbio/ for -I resolution.
*/
#ifndef AU_VELOCITY_FLAG_BATCH_H
#define AU_VELOCITY_FLAG_BATCH_H
typedef struct {
char biz_date[16];
long threshold; /* 일일 건수 임계값 */
long flagged_cards; /* 적발 카드수 */
long logged; /* 신규 기록한 fraud_log 건수 */
} au_velocity_flag_ctx_t;
/* 위반 건수를 fraud score 로 환산 (650 + 초과건수*15, 상한 999). */
long aub_velocity_score(long cnt, long threshold);
#endif /* AU_VELOCITY_FLAG_BATCH_H */

View file

@ -0,0 +1,22 @@
/*
* APPROVAL_RATE.h - au APPROVAL_RATE (copybook / record header).
* Request fields: T_MERCHANT(), T_BIZDATE()
* Response fields: T_ARG1( bps), T_COUNT( ), T_ARG2(),
* T_RC(0=, 1= 80% )
*/
#ifndef APPROVAL_RATE_H
#define APPROVAL_RATE_H
#include "au_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long rate_bps;
long attempts;
long approved;
} au_approval_rate_rec_t;
void APPROVAL_RATE(TPSVCINFO *p);
#endif /* APPROVAL_RATE_H */

View file

@ -0,0 +1,42 @@
/*
* APPROVAL_RATE.pgc - au 모듈 서비스 APPROVAL_RATE (one service = one file).
* 가맹점 승인율 조회: 영업일 접수 대비 승인 성공(A/M/S) 비율을 bps 로
* 환산하고, 표본 10건 이상에서 80% 를 밑돌면 승인율 저조 경보(RC=1)를 낸다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "APPROVAL_RATE.h"
void APPROVAL_RATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long bps = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_all = 0, h_ok = 0;
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));
if (merch[0] == 0 || bizdate[0] == 0) FAIL(b);
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 IN ('A','M','S'))
INTO :h_all, :h_ok
FROM au_authorization
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_all > 0) bps = (h_ok * 10000L) / h_all;
setl(b, T_ARG1, bps);
setl(b, T_COUNT, h_all);
setl(b, T_ARG2, h_ok);
if (h_all >= 10 && bps < 8000) {
userlog("APPROVAL_RATE 저조 경보 merch=%s %ldbps (%ld/%ld)", merch, bps, h_ok, h_all);
setl(b, T_RC, 1);
} else {
setl(b, T_RC, 0);
}
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* AUTH_AMT_ADJUST.h - au AUTH_AMT_ADJUST (copybook / record header).
* Request fields: T_ID1(), T_DELTA(, +/-)
* Response fields: T_AMOUNT( ), T_RC(0=, 1= , 2=)
*/
#ifndef AUTH_AMT_ADJUST_H
#define AUTH_AMT_ADJUST_H
#include "au_svc.h"
typedef struct {
long auth_id;
long delta;
long new_amount;
} au_auth_amt_adjust_rec_t;
void AUTH_AMT_ADJUST(TPSVCINFO *p);
#endif /* AUTH_AMT_ADJUST_H */

View file

@ -0,0 +1,57 @@
/*
* AUTH_AMT_ADJUST.pgc - au 모듈 서비스 AUTH_AMT_ADJUST (one service = one file).
* 승인금액 조정(incremental/decremental auth): 호텔·주유소처럼 최종 금액이
* 달라지는 업종의 승인(A) 건 금액을 증감한다. 증액은 잔여한도를 검사해
* 부족하면 RC=2 로 반려하고, 조정분만큼 카드 사용액도 함께 보정한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_AMT_ADJUST.h"
void AUTH_AMT_ADJUST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long delta, lim = 0, used = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_delta, h_amt = 0;
char h_card[24], h_status[4];
EXEC SQL END DECLARE SECTION;
h_id = getl(b, T_ID1);
delta = getl(b, T_DELTA);
if (h_id <= 0 || delta == 0) { userlog("AUTH_AMT_ADJUST: 인자 오류"); FAIL(b); }
memset(h_card, 0, sizeof(h_card));
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT card_no, status, amount INTO :h_card, :h_status, :h_amt
FROM au_authorization WHERE auth_id = :h_id;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_status[0] != 'A') { /* 매입확정/취소 후에는 조정 불가 */
setl(b, T_AMOUNT, h_amt);
setl(b, T_RC, 1);
OK(b);
}
if (h_amt + delta <= 0) FAIL(b); /* 전액 이하로의 감액은 취소로 처리해야 함 */
if (delta > 0) {
if (audb_limit_get(h_card, &lim, &used) < 0) FAIL(b);
if (used + delta > lim) {
userlog("AUTH_AMT_ADJUST 한도부족 auth=%ld delta=%ld", h_id, delta);
setl(b, T_AMOUNT, h_amt);
setl(b, T_RC, 2);
OK(b);
}
}
h_delta = delta;
EXEC SQL UPDATE au_authorization
SET amount = amount + :h_delta, updated_at = now()
WHERE auth_id = :h_id AND status = 'A';
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (audb_limit_dec(h_card, delta) < 0) FAIL(b); /* 음수 delta 면 사용액 환원 */
userlog("AUTH_AMT_ADJUST auth=%ld %ld -> %ld", h_id, h_amt, h_amt + delta);
setl(b, T_AMOUNT, h_amt + delta);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* AUTH_DELAY_MON.h - au AUTH_DELAY_MON (copybook / record header).
* Request fields: T_BIZDATE(), T_ARG1( , 30)
* Response fields: T_COUNT( ), T_ARG2( ), T_ARG3( ),
* T_RC(1= )
*/
#ifndef AUTH_DELAY_MON_H
#define AUTH_DELAY_MON_H
#include "au_svc.h"
typedef struct {
char biz_date[16];
long threshold_min;
long delayed_count;
long avg_age_sec;
long max_age_sec;
} au_auth_delay_rec_t;
void AUTH_DELAY_MON(TPSVCINFO *p);
#endif /* AUTH_DELAY_MON_H */

View file

@ -0,0 +1,39 @@
/*
* AUTH_DELAY_MON.pgc - au 모듈 서비스 AUTH_DELAY_MON (one service = one file).
* 승인 지연 모니터: 승인(A) 상태로 기준 시간(분)을 넘겨 매입확정이 안 된
* 체류 건을 세고, EPOCH 환산으로 평균/최대 경과초를 산출해 관제에 알린다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_DELAY_MON.h"
void AUTH_DELAY_MON(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_min, h_cnt = 0, h_avg = 0, h_max = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (bizdate[0] == 0) FAIL(b);
h_min = getl(b, T_ARG1); if (h_min <= 0) h_min = 30;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*),
CAST(coalesce(avg(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint),
CAST(coalesce(max(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint)
INTO :h_cnt, :h_avg, :h_max
FROM au_authorization
WHERE biz_date = :h_bizdate AND status = 'A' AND captured_at IS NULL
AND created_at < now() - CAST(:h_min AS bigint) * interval '1 minute';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 0)
userlog("AUTH_DELAY_MON 지연 %ld건 (기준 %ld분, 최대 %ld초)", h_cnt, h_min, h_max);
setl(b, T_COUNT, h_cnt);
setl(b, T_ARG2, h_avg);
setl(b, T_ARG3, h_max);
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* AUTH_DUP_CHK.h - au AUTH_DUP_CHK (copybook / record header).
* Request fields: T_KEY1(), T_MERCHANT(), T_AMOUNT()
* Response fields: T_RC(0= , 1= ), T_ID1( ),
* T_ARG1(5 )
*/
#ifndef AUTH_DUP_CHK_H
#define AUTH_DUP_CHK_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char merchant_id[64];
long amount;
long dup_count;
long last_dup_auth;
} au_auth_dup_chk_rec_t;
void AUTH_DUP_CHK(TPSVCINFO *p);
#endif /* AUTH_DUP_CHK_H */

View file

@ -0,0 +1,43 @@
/*
* AUTH_DUP_CHK.pgc - au 모듈 서비스 AUTH_DUP_CHK (one service = one file).
* 중복승인 검출: 단말 재시도 등으로 동일 카드+가맹점+금액 조합이 직전 5분내
* 이미 승인돼 있는지 검사한다. 중복이면 가장 최근 원승인 번호를 돌려줘
* 단말이 신규 승인 대신 원승인 응답을 재사용하도록 한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_DUP_CHK.h"
void AUTH_DUP_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64];
EXEC SQL BEGIN DECLARE SECTION;
long h_amt, h_cnt = 0, h_last = 0;
char h_card[24], h_merch[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_MERCHANT, merch, sizeof(merch));
h_amt = getl(b, T_AMOUNT);
if (card[0] == 0 || merch[0] == 0 || h_amt <= 0) FAIL(b);
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;
EXEC SQL SELECT count(*), coalesce(max(auth_id),0) INTO :h_cnt, :h_last
FROM au_authorization
WHERE card_no = :h_card AND merchant_id = :h_merch AND amount = :h_amt
AND status = 'A'
AND created_at >= now() - interval '5 minutes';
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_ARG1, h_cnt);
if (h_cnt > 0) {
userlog("AUTH_DUP_CHK 중복 의심 card=%s merch=%s amt=%ld 원승인=%ld", card, merch, h_amt, h_last);
setl(b, T_ID1, h_last);
setl(b, T_RC, 1);
} else {
setl(b, T_ID1, 0);
setl(b, T_RC, 0);
}
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* AUTH_EXPIRE_CHK.h - au AUTH_EXPIRE_CHK (copybook / record header).
* Request fields: T_ARG1( , 14)
* Response fields: T_COUNT( ), T_GROSS( ),
* T_ID1( ), T_STR1( ), T_RC(1= )
*/
#ifndef AUTH_EXPIRE_CHK_H
#define AUTH_EXPIRE_CHK_H
#include "au_svc.h"
typedef struct {
long grace_days;
long expired_count;
long expired_gross;
long oldest_auth_id;
char oldest_date[16];
} au_auth_expire_rec_t;
void AUTH_EXPIRE_CHK(TPSVCINFO *p);
#endif /* AUTH_EXPIRE_CHK_H */

View file

@ -0,0 +1,44 @@
/*
* AUTH_EXPIRE_CHK.pgc - au 모듈 서비스 AUTH_EXPIRE_CHK (one service = one file).
* 미매입 승인 만기 점검: 기준일수를 넘겨도 매입확정되지 않은 승인(A) 건의
* 규모(건수/금액)와 가장 오래된 건(승인번호/영업일)을 min 서브쿼리 조회로
* 찾아 만기 스윕 배치의 대상 규모를 미리 알려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_EXPIRE_CHK.h"
void AUTH_EXPIRE_CHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_days, h_cnt = 0, h_amt = 0, h_oldest = 0;
char h_odate[16];
EXEC SQL END DECLARE SECTION;
h_days = getl(b, T_ARG1); if (h_days <= 0) h_days = 14;
memset(h_odate, 0, sizeof(h_odate));
EXEC SQL SELECT count(*), coalesce(sum(amount),0)
INTO :h_cnt, :h_amt
FROM au_authorization
WHERE status = 'A' AND captured_at IS NULL
AND biz_date < current_date - CAST(:h_days AS integer);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_amt);
if (h_cnt == 0) { setl(b, T_RC, 0); setl(b, T_ID1, 0); OK(b); }
EXEC SQL SELECT auth_id, CAST(biz_date AS text) INTO :h_oldest, :h_odate
FROM au_authorization
WHERE status = 'A' AND captured_at IS NULL
AND biz_date < current_date - CAST(:h_days AS integer)
ORDER BY biz_date, auth_id LIMIT 1;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("AUTH_EXPIRE_CHK 만기 %ld건 %ld원 (최고 오래된 auth=%ld %s)", h_cnt, h_amt, h_oldest, h_odate);
setl(b, T_ID1, h_oldest);
Bchg(b, T_STR1, 0, h_odate, 0L);
setl(b, T_RC, 1);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* AUTH_PARTIAL.h - au AUTH_PARTIAL (copybook / record header).
* Request fields: T_KEY1(), T_MERCHANT(), T_AMOUNT(), T_BIZDATE()
* Response fields: T_ID1(), T_AMT1(), T_RC(0=, 2=, 1=),
* T_STR2()
*/
#ifndef AUTH_PARTIAL_H
#define AUTH_PARTIAL_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char merchant_id[64];
long req_amount;
long approved_amount;
char biz_date[16];
} au_auth_partial_rec_t;
void AUTH_PARTIAL(TPSVCINFO *p);
#endif /* AUTH_PARTIAL_H */

View file

@ -0,0 +1,47 @@
/*
* AUTH_PARTIAL.pgc - au 모듈 서비스 AUTH_PARTIAL (one service = one file).
* 부분승인: 잔여한도가 요청금액에 못 미치면 잔여한도만큼만 승인(RC=2)하고,
* 잔여한도가 0 이면 거절(D)로 기록한다(RC=1). 승인분만 한도를 차감한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_PARTIAL.h"
void AUTH_PARTIAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], bizdate[16], appr[12];
long amount, remain, grant, auth_id, lim = 0, used = 0;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
if (card[0] == 0 || amount <= 0) { userlog("AUTH_PARTIAL: 인자 오류"); FAIL(b); }
if (audb_limit_get(card, &lim, &used) < 0) FAIL(b);
remain = lim - used;
if (remain < 0) remain = 0;
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (remain == 0) { /* 잔여한도 전무 - 거절 기록 */
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_AMT1, 0);
setl(b, T_RC, 1);
OK(b);
}
grant = (amount <= remain) ? amount : remain;
sprintf(appr, "P%05ld", auth_id % 100000);
if (audb_insert_auth(auth_id, card, merch, grant, appr, "AUTH", "A", bizdate) < 0) FAIL(b);
if (audb_limit_dec(card, grant) < 0) FAIL(b);
userlog("AUTH_PARTIAL card=%s 요청=%ld 승인=%ld (잔여한도 %ld)", card, amount, grant, remain);
setl(b, T_ID1, auth_id);
setl(b, T_AMT1, grant);
Bchg(b, T_STR2, 0, appr, 0L);
setl(b, T_RC, (grant < amount) ? 2 : 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* AUTH_RESEND.h - au AUTH_RESEND (copybook / record header).
* Request fields: T_ID1()
* Response fields: T_STR2(), T_AMOUNT(), T_STATUS(),
* T_RC(0=, 1= )
*/
#ifndef AUTH_RESEND_H
#define AUTH_RESEND_H
#include "au_svc.h"
typedef struct {
long auth_id;
char approval_code[16];
long amount;
char status[4];
} au_auth_resend_rec_t;
void AUTH_RESEND(TPSVCINFO *p);
#endif /* AUTH_RESEND_H */

View file

@ -0,0 +1,42 @@
/*
* AUTH_RESEND.pgc - au 모듈 서비스 AUTH_RESEND (one service = one file).
* 승인 재전송: 망 단절 등으로 응답을 못 받은 단말이 원승인의 승인코드를
* 다시 받아가는 멱등 조회. 승인(A)/매입(M) 상태만 재전송을 허용하고
* updated_at 을 갱신해 재전송 시각을 남긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "AUTH_RESEND.h"
void AUTH_RESEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_amt = 0;
char h_appr[16], h_status[4];
EXEC SQL END DECLARE SECTION;
h_id = getl(b, T_ID1);
if (h_id <= 0) { userlog("AUTH_RESEND: 승인번호 누락"); FAIL(b); }
memset(h_appr, 0, sizeof(h_appr));
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT approval_code, amount, status INTO :h_appr, :h_amt, :h_status
FROM au_authorization WHERE auth_id = :h_id;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_AMOUNT, h_amt);
if (h_status[0] != 'A' && h_status[0] != 'M') { /* 취소/역전/거절 건은 재전송 불가 */
setl(b, T_RC, 1);
OK(b);
}
EXEC SQL UPDATE au_authorization SET updated_at = now() WHERE auth_id = :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
userlog("AUTH_RESEND auth_id=%ld appr=%s 재전송", h_id, h_appr);
Bchg(b, T_STR2, 0, h_appr, 0L);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* BIN_ROUTE_INQ.h - au BIN_ROUTE_INQ (copybook / record header).
* Request fields: T_KEY1( BIN 6)
* Response fields: T_STR1( ), T_STR2(BIN), T_COUNT( BIN ),
* T_RC(0= , 1= BIN)
*/
#ifndef BIN_ROUTE_INQ_H
#define BIN_ROUTE_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char bin[8];
char gateway[16];
long bin_today;
} au_bin_route_rec_t;
void BIN_ROUTE_INQ(TPSVCINFO *p);
#endif /* BIN_ROUTE_INQ_H */

View file

@ -0,0 +1,48 @@
/*
* BIN_ROUTE_INQ.pgc - au 모듈 서비스 BIN_ROUTE_INQ (one service = one file).
* BIN별 라우팅 조회: 카드번호 선두 6자리(BIN)의 첫 자리로 매입 게이트웨이를
* 결정하고(3=AMEX, 4=VISA, 5=MC, 6=LOCAL, 9=TESTNET), 당일 동일 BIN 대역의
* 승인 트래픽 건수를 LIKE 프리픽스 집계로 함께 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "BIN_ROUTE_INQ.h"
void BIN_ROUTE_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bin[8];
const char *gw;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0;
char h_pat[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (strlen(card) < 6) { userlog("BIN_ROUTE_INQ: BIN 6자리 미만"); FAIL(b); }
memcpy(bin, card, 6); bin[6] = 0;
switch (bin[0]) {
case '3': gw = "AMEX-GW"; break;
case '4': gw = "VISA-GW"; break;
case '5': gw = "MC-GW"; break;
case '6': gw = "LOCAL-GW"; break;
case '9': gw = "TESTNET"; break;
default: gw = ""; break;
}
sprintf(h_pat, "%s%%", bin);
EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization
WHERE card_no LIKE :h_pat AND biz_date = current_date;
if (sqlca.sqlcode < 0) FAIL(b);
Bchg(b, T_STR2, 0, bin, 0L);
setl(b, T_COUNT, h_cnt);
if (gw[0] == 0) { /* 미지원 BIN 대역 */
Bchg(b, T_STR1, 0, "UNROUTED", 0L);
setl(b, T_RC, 1);
OK(b);
}
Bchg(b, T_STR1, 0, (char *)gw, 0L);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* BLACKLIST_ADD.h - au BLACKLIST_ADD (copybook / record header).
* Request fields: T_KEY1(), T_REASON()
* Response fields: T_RC(0=, 1= ), T_COUNT( )
*/
#ifndef BLACKLIST_ADD_H
#define BLACKLIST_ADD_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char reason[128];
long total_after;
} au_blacklist_add_rec_t;
void BLACKLIST_ADD(TPSVCINFO *p);
#endif /* BLACKLIST_ADD_H */

View file

@ -0,0 +1,46 @@
/*
* BLACKLIST_ADD.pgc - au 모듈 서비스 BLACKLIST_ADD (one service = one file).
* 블랙리스트 등재: 카드를 블랙리스트에 올리고(기등재 건은 사유만 갱신),
* 해당 카드의 한도원장이 ACTIVE 면 동시에 SUSPEND 로 정지시킨다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "BLACKLIST_ADD.h"
void BLACKLIST_ADD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], reason[128];
long fresh;
EXEC SQL BEGIN DECLARE SECTION;
long h_before = 0, h_after = 0;
char h_card[24], h_reason[128];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_REASON, reason, sizeof(reason));
if (card[0] == 0) { userlog("BLACKLIST_ADD: 카드번호 누락"); FAIL(b); }
if (reason[0] == 0) strcpy(reason, "MANUAL");
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_before FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
fresh = (h_before == 0);
EXEC SQL INSERT INTO au_blacklist (card_no, reason) VALUES (:h_card, :h_reason)
ON CONFLICT (card_no) DO UPDATE SET reason = EXCLUDED.reason;
if (sqlca.sqlcode < 0) FAIL(b);
/* 연계 정지: 한도원장이 살아 있으면 같이 잠근다 (원장 없으면 무시) */
EXEC SQL UPDATE card_limit SET status = 'SUSPEND', updated_at = now()
WHERE card_no = :h_card AND status = 'ACTIVE';
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*) INTO :h_after FROM au_blacklist;
if (sqlca.sqlcode < 0) FAIL(b);
userlog("BLACKLIST_ADD card=%s reason=%s %s", card, reason, fresh ? "신규" : "갱신");
setl(b, T_COUNT, h_after);
setl(b, T_RC, fresh ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,18 @@
/*
* BLACKLIST_DEL.h - au BLACKLIST_DEL (copybook / record header).
* Request fields: T_KEY1(), T_ARG1(1 ACTIVE )
* Response fields: T_RC(0=, 1=)
*/
#ifndef BLACKLIST_DEL_H
#define BLACKLIST_DEL_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long reactivate;
} au_blacklist_del_rec_t;
void BLACKLIST_DEL(TPSVCINFO *p);
#endif /* BLACKLIST_DEL_H */

View file

@ -0,0 +1,39 @@
/*
* BLACKLIST_DEL.pgc - au 모듈 서비스 BLACKLIST_DEL (one service = one file).
* 블랙리스트 해제: 등재 행을 삭제하고, 옵션(T_ARG1=1)에 따라 함께 정지됐던
* 한도원장을 ACTIVE 로 복원한다. 미등재 카드는 RC=1 로 멱등 응답.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "BLACKLIST_DEL.h"
void BLACKLIST_DEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long reactivate;
EXEC SQL BEGIN DECLARE SECTION;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
reactivate = getl(b, T_ARG1);
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL DELETE FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { /* 등재된 적 없음 - 멱등 */
setl(b, T_RC, 1);
OK(b);
}
if (reactivate == 1) {
EXEC SQL UPDATE card_limit SET status = 'ACTIVE', updated_at = now()
WHERE card_no = :h_card AND status = 'SUSPEND';
if (sqlca.sqlcode < 0) FAIL(b);
}
userlog("BLACKLIST_DEL card=%s 해제 (복원=%ld)", card, reactivate);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* BLACKLIST_INQ.h - au BLACKLIST_INQ (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_RC(0=, 1=), T_REASON(), T_STR1(),
* T_COUNT( )
*/
#ifndef BLACKLIST_INQ_H
#define BLACKLIST_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char reason[128];
char listed_at[32];
long total;
} au_blacklist_inq_rec_t;
void BLACKLIST_INQ(TPSVCINFO *p);
#endif /* BLACKLIST_INQ_H */

View file

@ -0,0 +1,41 @@
/*
* BLACKLIST_INQ.pgc - au 모듈 서비스 BLACKLIST_INQ (one service = one file).
* 블랙리스트 조회: 특정 카드의 등재 여부/사유/등재일시(문자열 포맷)를
* 조회하고 전체 등재 건수도 함께 내려준다. 미등재는 RC=0 정상 응답.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "BLACKLIST_INQ.h"
void BLACKLIST_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_total = 0;
char h_card[24], h_reason[128], h_when[32];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
memset(h_reason, 0, sizeof(h_reason));
memset(h_when, 0, sizeof(h_when));
EXEC SQL SELECT count(*) INTO :h_total FROM au_blacklist;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_total);
EXEC SQL SELECT reason, to_char(created_at, 'YYYY-MM-DD HH24:MI:SS')
INTO :h_reason, :h_when
FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) {
setl(b, T_RC, 0); /* 미등재 */
OK(b);
}
Bchg(b, T_REASON, 0, h_reason, 0L);
Bchg(b, T_STR1, 0, h_when, 0L);
setl(b, T_RC, 1);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* CANCEL_REPROC.h - au CANCEL_REPROC (copybook / record header).
* Request fields: T_CANCEL_ID( ID)
* Response fields: T_ID1(), T_RC(0= , 1= , 2= )
*/
#ifndef CANCEL_REPROC_H
#define CANCEL_REPROC_H
#include "au_svc.h"
typedef struct {
long cancel_id;
long auth_id;
char cancel_type[12];
long amount;
} au_cancel_reproc_rec_t;
void CANCEL_REPROC(TPSVCINFO *p);
#endif /* CANCEL_REPROC_H */

View file

@ -0,0 +1,56 @@
/*
* CANCEL_REPROC.pgc - au 모듈 서비스 CANCEL_REPROC (one service = one file).
* 승인취소 재처리: au_cancel 에 접수됐지만 원승인 원장에 반영되지 못한 건을
* 다시 반영한다. FULL/VOID 는 원승인 상태를 C/V 로 전환하고,
* PARTIAL 은 원승인 금액을 취소분만큼 감액한다. 이미 반영된 건은 RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CANCEL_REPROC.h"
void CANCEL_REPROC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_cxl, h_auth = 0, h_camt = 0, h_aamt = 0;
char h_ctype[12], h_astat[4];
EXEC SQL END DECLARE SECTION;
h_cxl = getl(b, T_CANCEL_ID);
if (h_cxl <= 0) { userlog("CANCEL_REPROC: 취소 ID 누락"); FAIL(b); }
memset(h_ctype, 0, sizeof(h_ctype));
memset(h_astat, 0, sizeof(h_astat));
EXEC SQL SELECT c.auth_id, c.cancel_type, c.amount, a.status, a.amount
INTO :h_auth, :h_ctype, :h_camt, :h_astat, :h_aamt
FROM au_cancel c JOIN au_authorization a ON a.auth_id = c.auth_id
WHERE c.cancel_id = :h_cxl;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
setl(b, T_ID1, h_auth);
if (h_astat[0] == 'C' || h_astat[0] == 'V' || h_astat[0] == 'R') {
setl(b, T_RC, 1); /* 이미 취소류 상태 - 재처리 불필요 */
OK(b);
}
if (h_astat[0] != 'A' && h_astat[0] != 'M') {
setl(b, T_RC, 2); /* 보류/거절 등 - 재처리 부적합 */
OK(b);
}
if (strcmp(h_ctype, "PARTIAL") == 0) {
if (h_camt >= h_aamt) FAIL(b); /* 부분취소가 원금액 이상이면 정합성 오류 */
EXEC SQL UPDATE au_authorization
SET amount = amount - :h_camt, updated_at = now()
WHERE auth_id = :h_auth;
} else if (strcmp(h_ctype, "VOID") == 0) {
EXEC SQL UPDATE au_authorization SET status = 'V', updated_at = now()
WHERE auth_id = :h_auth;
} else {
EXEC SQL UPDATE au_authorization SET status = 'C', updated_at = now()
WHERE auth_id = :h_auth;
}
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("CANCEL_REPROC cancel_id=%ld auth_id=%ld type=%s 재처리 완료", h_cxl, h_auth, h_ctype);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* CANCEL_STATUS.h - au CANCEL_STATUS (copybook / record header).
* Request fields: T_BIZDATE()
* Response fields: T_COUNT( ), T_ARG1(FULL), T_ARG2(PARTIAL), T_ARG3(VOID),
* T_GROSS( )
*/
#ifndef CANCEL_STATUS_H
#define CANCEL_STATUS_H
#include "au_svc.h"
typedef struct {
char biz_date[16];
long total;
long full_cnt;
long partial_cnt;
long void_cnt;
long gross;
} au_cancel_status_rec_t;
void CANCEL_STATUS(TPSVCINFO *p);
#endif /* CANCEL_STATUS_H */

View file

@ -0,0 +1,37 @@
/*
* CANCEL_STATUS.pgc - au 모듈 서비스 CANCEL_STATUS (one service = one file).
* 취소 처리현황 조회: 영업일 기준 취소 접수를 유형(FULL/PARTIAL/VOID)별로
* FILTER 집계해 건수 분포와 총 취소금액을 한 번의 SELECT 로 산출한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CANCEL_STATUS.h"
void CANCEL_STATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_tot = 0, h_full = 0, h_part = 0, h_void = 0, h_amt = 0;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (bizdate[0] == 0) { userlog("CANCEL_STATUS: 영업일 누락"); FAIL(b); }
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*),
count(*) FILTER (WHERE cancel_type = 'FULL'),
count(*) FILTER (WHERE cancel_type = 'PARTIAL'),
count(*) FILTER (WHERE cancel_type = 'VOID'),
coalesce(sum(amount),0)
INTO :h_tot, :h_full, :h_part, :h_void, :h_amt
FROM au_cancel WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_tot);
setl(b, T_ARG1, h_full);
setl(b, T_ARG2, h_part);
setl(b, T_ARG3, h_void);
setl(b, T_GROSS, h_amt);
OK(b);
}

View file

@ -0,0 +1,26 @@
/*
* CARD_DAILY_RPT.h - au CARD_DAILY_RPT (copybook / record header).
* Request fields: T_KEY1(), T_BIZDATE()
* Response fields: T_ARG1(), T_ARG2(), T_ARG3(), T_ARG4(),
* T_GROSS(), T_AMT1( ), T_ARG5( bps)
*/
#ifndef CARD_DAILY_RPT_H
#define CARD_DAILY_RPT_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char biz_date[16];
long approved;
long declined;
long canceled;
long captured;
long gross;
long max_one;
long approve_bps;
} au_card_daily_rpt_rec_t;
void CARD_DAILY_RPT(TPSVCINFO *p);
#endif /* CARD_DAILY_RPT_H */

View file

@ -0,0 +1,46 @@
/*
* CARD_DAILY_RPT.pgc - au 모듈 서비스 CARD_DAILY_RPT (one service = one file).
* 카드 일일 승인 리포트: 카드+영업일 기준 상태별(승인/거절/취소류/매입) 건수,
* 유효 승인금액 합, 최대 단건금액을 FILTER 집계 한 방으로 뽑고
* 승인율(bps)을 계산해 종합 리포트로 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CARD_DAILY_RPT.h"
void CARD_DAILY_RPT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
long attempts;
EXEC SQL BEGIN DECLARE SECTION;
long h_ap = 0, h_de = 0, h_cx = 0, h_cap = 0, h_sum = 0, h_max = 0;
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));
if (card[0] == 0 || bizdate[0] == 0) FAIL(b);
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(*) FILTER (WHERE status = 'A'),
count(*) FILTER (WHERE status = 'D'),
count(*) FILTER (WHERE status IN ('C','R','V')),
count(*) FILTER (WHERE status IN ('M','S')),
coalesce(sum(amount) FILTER (WHERE status IN ('A','M','S')), 0),
coalesce(max(amount) FILTER (WHERE status IN ('A','M','S')), 0)
INTO :h_ap, :h_de, :h_cx, :h_cap, :h_sum, :h_max
FROM au_authorization
WHERE card_no = :h_card AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
attempts = h_ap + h_de + h_cap;
setl(b, T_ARG1, h_ap);
setl(b, T_ARG2, h_de);
setl(b, T_ARG3, h_cx);
setl(b, T_ARG4, h_cap);
setl(b, T_GROSS, h_sum);
setl(b, T_AMT1, h_max);
setl(b, T_ARG5, (attempts > 0) ? ((h_ap + h_cap) * 10000L) / attempts : 0);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* CARD_REISSUE.h - au CARD_REISSUE (copybook / record header).
* Request fields: T_KEY1( ), T_KEY2( )
* Response fields: T_AMT1( ), T_RC(0=, 1= )
*/
#ifndef CARD_REISSUE_H
#define CARD_REISSUE_H
#include "au_svc.h"
typedef struct {
char old_card[24];
char new_card[24];
long moved_limit;
} au_card_reissue_rec_t;
void CARD_REISSUE(TPSVCINFO *p);
#endif /* CARD_REISSUE_H */

View file

@ -0,0 +1,48 @@
/*
* CARD_REISSUE.pgc - au 모듈 서비스 CARD_REISSUE (one service = one file).
* 카드 재발급 연계: 구 카드의 한도원장을 신 카드번호로 이관 생성하고
* (사용액은 승계, 임시한도는 소멸) 구 카드는 CLOSED 처리한다.
* 신 카드 원장이 이미 있으면 RC=1 로 응답하고 이관하지 않는다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CARD_REISSUE.h"
void CARD_REISSUE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char oldc[24], newc[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_daily = 0, h_used = 0, h_exists = 0;
char h_old[24], h_new[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, oldc, sizeof(oldc));
gets_(b, T_KEY2, newc, sizeof(newc));
if (oldc[0] == 0 || newc[0] == 0 || strcmp(oldc, newc) == 0) {
userlog("CARD_REISSUE: 카드번호 인자 오류");
FAIL(b);
}
strncpy(h_old, oldc, sizeof(h_old)-1); h_old[sizeof(h_old)-1] = 0;
strncpy(h_new, newc, sizeof(h_new)-1); h_new[sizeof(h_new)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_exists FROM card_limit WHERE card_no = :h_new;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_exists > 0) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); }
EXEC SQL SELECT daily_limit, used_amount INTO :h_daily, :h_used
FROM card_limit WHERE card_no = :h_old;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
EXEC SQL INSERT INTO card_limit (card_no, daily_limit, used_amount, temp_limit, status)
VALUES (:h_new, :h_daily, :h_used, 0, 'ACTIVE');
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE card_limit SET status = 'CLOSED', temp_limit = 0, updated_at = now()
WHERE card_no = :h_old;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("CARD_REISSUE %s -> %s 한도 %ld 이관", oldc, newc, h_daily);
setl(b, T_AMT1, h_daily);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,18 @@
/*
* CARD_RESUME.h - au CARD_RESUME (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_RC(0=, 1= ), T_STATUS( )
*/
#ifndef CARD_RESUME_H
#define CARD_RESUME_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char new_status[16];
} au_card_resume_rec_t;
void CARD_RESUME(TPSVCINFO *p);
#endif /* CARD_RESUME_H */

View file

@ -0,0 +1,42 @@
/*
* CARD_RESUME.pgc - au 모듈 서비스 CARD_RESUME (one service = one file).
* 카드 정지 해제: 블랙리스트에 남아 있으면 해제를 거부하고,
* SUSPEND 상태의 카드만 ACTIVE 로 되돌린다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CARD_RESUME.h"
void CARD_RESUME(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_black = 0;
char h_card[24], h_status[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT count(*) INTO :h_black FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (h_black > 0) { userlog("CARD_RESUME 거부: 블랙리스트 등재 card=%s", card); FAIL(b); }
EXEC SQL UPDATE card_limit SET status = 'ACTIVE', updated_at = now()
WHERE card_no = :h_card AND status = 'SUSPEND';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) {
EXEC SQL SELECT status INTO :h_status FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_RC, 1); /* 정지상태가 아니었음 */
OK(b);
}
userlog("CARD_RESUME 카드=%s 정지 해제", card);
Bchg(b, T_STATUS, 0, "ACTIVE", 0L);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,24 @@
/*
* CARD_STATE_INQ.h - au CARD_STATE_INQ (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_STATUS(), T_AMT1(), T_AMT2(), T_AMT3(),
* T_COUNT( ), T_RC(1= )
*/
#ifndef CARD_STATE_INQ_H
#define CARD_STATE_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char status[16];
long daily_limit;
long used_amount;
long temp_limit;
long today_count;
long black_flag;
} au_card_state_rec_t;
void CARD_STATE_INQ(TPSVCINFO *p);
#endif /* CARD_STATE_INQ_H */

View file

@ -0,0 +1,39 @@
/*
* CARD_STATE_INQ.pgc - au 모듈 서비스 CARD_STATE_INQ (one service = one file).
* 카드 상태 종합조회: 한도원장(상태/기본·임시한도/사용액)에
* 당일 승인건수와 블랙리스트 등재 여부를 스칼라 서브쿼리로 묶어 한 번에 조회.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CARD_STATE_INQ.h"
void CARD_STATE_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_daily = 0, h_used = 0, h_temp = 0, h_cnt = 0, h_black = 0;
char h_card[24], h_status[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT l.status, l.daily_limit, l.used_amount, l.temp_limit,
(SELECT count(*) FROM au_authorization a
WHERE a.card_no = l.card_no AND a.biz_date = current_date),
(SELECT count(*) FROM au_blacklist k WHERE k.card_no = l.card_no)
INTO :h_status, :h_daily, :h_used, :h_temp, :h_cnt, :h_black
FROM card_limit l WHERE l.card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { userlog("CARD_STATE_INQ: 원장 없음 card=%s", card); FAIL(b); }
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_AMT1, h_daily);
setl(b, T_AMT2, h_used);
setl(b, T_AMT3, h_temp);
setl(b, T_COUNT, h_cnt);
setl(b, T_RC, (h_black > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* CARD_SUSPEND.h - au CARD_SUSPEND (copybook / record header).
* Request fields: T_KEY1(), T_REASON()
* Response fields: T_RC(0=, 1= / ), T_COUNT( )
*/
#ifndef CARD_SUSPEND_H
#define CARD_SUSPEND_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char reason[128];
long today_auths;
} au_card_suspend_rec_t;
void CARD_SUSPEND(TPSVCINFO *p);
#endif /* CARD_SUSPEND_H */

View file

@ -0,0 +1,41 @@
/*
* CARD_SUSPEND.pgc - au 모듈 서비스 CARD_SUSPEND (one service = one file).
* 카드 정지: ACTIVE 상태의 카드만 SUSPEND 로 전환하고, 참고용으로
* 당일 승인건수를 함께 내려준다. 이미 정지/폐기된 카드는 RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "CARD_SUSPEND.h"
void CARD_SUSPEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_today = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_REASON, reason, sizeof(reason));
if (card[0] == 0) { userlog("CARD_SUSPEND: 카드번호 누락"); FAIL(b); }
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*) INTO :h_today FROM au_authorization
WHERE card_no = :h_card AND biz_date = current_date;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_today);
EXEC SQL UPDATE card_limit SET status = 'SUSPEND', updated_at = now()
WHERE card_no = :h_card AND status = 'ACTIVE';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { /* ACTIVE 아님: 존재만 하면 RC=1 */
EXEC SQL SELECT count(*) INTO :h_today FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || h_today == 0) FAIL(b);
setl(b, T_RC, 1);
OK(b);
}
userlog("CARD_SUSPEND 카드=%s 정지 (사유=%s)", card, reason);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* DECLINE_ANALYZE.h - au DECLINE_ANALYZE (copybook / record header).
* Request fields: T_BIZDATE()
* Response fields: T_STR1( "사유:건수;" 3), T_REASON(1 ),
* T_COUNT( ), T_ARG1( bps)
*/
#ifndef DECLINE_ANALYZE_H
#define DECLINE_ANALYZE_H
#include "au_svc.h"
typedef struct {
char biz_date[16];
char top3[256];
char top_reason[64];
long decline_total;
long decline_bps;
} au_decline_analyze_rec_t;
void DECLINE_ANALYZE(TPSVCINFO *p);
#endif /* DECLINE_ANALYZE_H */

View file

@ -0,0 +1,54 @@
/*
* DECLINE_ANALYZE.pgc - au 모듈 서비스 DECLINE_ANALYZE (one service = one file).
* 승인 거절사유 분석: 영업일 거절(D) 건을 사유별로 그룹핑해 상위 3개 사유를
* 커서로 뽑아 "사유:건수;" 문자열로 조립하고, 전체 접수 대비 거절률(bps)을
* 계산해 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "DECLINE_ANALYZE.h"
void DECLINE_ANALYZE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], top3[256] = "", top_reason[64] = "";
char item[96];
int rank = 0;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_dec = 0, h_all = 0;
char h_bizdate[16], h_reason[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (bizdate[0] == 0) FAIL(b);
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'D')
INTO :h_all, :h_dec
FROM au_authorization WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL DECLARE cur_dean CURSOR FOR
SELECT CASE WHEN decline_reason = '' THEN 'UNSPECIFIED' ELSE decline_reason END,
count(*)
FROM au_authorization
WHERE biz_date = :h_bizdate AND status = 'D'
GROUP BY 1 ORDER BY 2 DESC LIMIT 3;
EXEC SQL OPEN cur_dean;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_dean INTO :h_reason, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_dean; FAIL(b); }
rank++;
if (rank == 1) strncpy(top_reason, h_reason, sizeof(top_reason)-1);
snprintf(item, sizeof(item), "%s:%ld;", h_reason, h_cnt);
if (strlen(top3) + strlen(item) < sizeof(top3) - 1) strcat(top3, item);
}
EXEC SQL CLOSE cur_dean;
Bchg(b, T_STR1, 0, top3, 0L);
Bchg(b, T_REASON, 0, top_reason, 0L);
setl(b, T_COUNT, h_dec);
setl(b, T_ARG1, (h_all > 0) ? (h_dec * 10000L) / h_all : 0);
OK(b);
}

View file

@ -0,0 +1,23 @@
/*
* FRAUD_CASE_INQ.h - au FRAUD_CASE_INQ (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_COUNT( ), T_ARG1(), T_ARG2(),
* T_REASON( ), T_STR1( )
*/
#ifndef FRAUD_CASE_INQ_H
#define FRAUD_CASE_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long case_count;
long max_score;
long avg_score;
char last_reason[128];
char last_date[16];
} au_fraud_case_inq_rec_t;
void FRAUD_CASE_INQ(TPSVCINFO *p);
#endif /* FRAUD_CASE_INQ_H */

View file

@ -0,0 +1,42 @@
/*
* FRAUD_CASE_INQ.pgc - au 모듈 서비스 FRAUD_CASE_INQ (one service = one file).
* 이상거래 케이스 조회: 카드의 부정거래 적발 이력을 집계(건수/최고/평균점수)
* 하고, 가장 최근 적발 건의 사유와 발생일을 LIMIT 1 정렬 조회로 내려준다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "FRAUD_CASE_INQ.h"
void FRAUD_CASE_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_max = 0, h_avg = 0;
char h_card[24], h_reason[128], h_date[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
memset(h_reason, 0, sizeof(h_reason));
memset(h_date, 0, sizeof(h_date));
EXEC SQL SELECT count(*), coalesce(max(score),0), CAST(coalesce(avg(score),0) AS bigint)
INTO :h_cnt, :h_max, :h_avg
FROM au_fraud_log WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_ARG1, h_max);
setl(b, T_ARG2, h_avg);
if (h_cnt == 0) OK(b); /* 깨끗한 카드 */
EXEC SQL SELECT reason, CAST(biz_date AS text) INTO :h_reason, :h_date
FROM au_fraud_log WHERE card_no = :h_card
ORDER BY fraud_id DESC LIMIT 1;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
Bchg(b, T_REASON, 0, h_reason, 0L);
Bchg(b, T_STR1, 0, h_date, 0L);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* FRAUD_RULE_AMT.h - au FRAUD_RULE_AMT (copybook / record header).
* Request fields: T_KEY1(), T_AMOUNT(), T_BIZDATE()
* Response fields: T_RC(0=, 1= ), T_ID1( ID),
* T_AMT1(30 ), T_ARG1()
*/
#ifndef FRAUD_RULE_AMT_H
#define FRAUD_RULE_AMT_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long amount;
long avg30;
long score;
long fraud_id;
} au_fraud_rule_amt_rec_t;
void FRAUD_RULE_AMT(TPSVCINFO *p);
#endif /* FRAUD_RULE_AMT_H */

View file

@ -0,0 +1,51 @@
/*
* FRAUD_RULE_AMT.pgc - au 모듈 서비스 FRAUD_RULE_AMT (one service = one file).
* 이상거래 고액 룰: 거래금액이 카드의 30일 평균 사용액의 3배를 넘고
* 절대금액 100만원 이상이면 배수 기반 점수를 산출해 부정거래 로그에
* 적발 기록을 남긴다. 평균 이력이 없는 신규 카드는 룰을 건너뛴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "FRAUD_RULE_AMT.h"
void FRAUD_RULE_AMT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
long amount, score, fid;
EXEC SQL BEGIN DECLARE SECTION;
long h_avg = 0, h_hist = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
if (card[0] == 0 || amount <= 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*), CAST(coalesce(avg(amount),0) AS bigint)
INTO :h_hist, :h_avg
FROM au_authorization
WHERE card_no = :h_card AND status IN ('A','M','S')
AND biz_date >= current_date - 30;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_AMT1, h_avg);
if (h_hist < 3 || h_avg <= 0) { /* 이력 부족 - 룰 미적용 */
setl(b, T_RC, 0); setl(b, T_ARG1, 0); OK(b);
}
if (amount <= h_avg * 3 || amount < 1000000) {
setl(b, T_RC, 0); setl(b, T_ARG1, 0); OK(b);
}
score = 500 + (amount / h_avg) * 50; /* 평균 대비 배수 가중 점수 */
if (score > 999) score = 999;
fid = audb_insert_fraud(0, card, score, "RULE_AMT_SPIKE", bizdate);
if (fid < 0) FAIL(b);
userlog("FRAUD_RULE_AMT 적발 card=%s amt=%ld avg=%ld score=%ld", card, amount, h_avg, score);
setl(b, T_ID1, fid);
setl(b, T_ARG1, score);
setl(b, T_RC, 1);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* FRAUD_RULE_DUP.h - au FRAUD_RULE_DUP (copybook / record header).
* Request fields: T_KEY1(), T_BIZDATE()
* Response fields: T_RC(0=, 1= ), T_COUNT( ),
* T_AMT1( ), T_ID1( ID)
*/
#ifndef FRAUD_RULE_DUP_H
#define FRAUD_RULE_DUP_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long dup_groups;
long worst_amount;
long fraud_id;
} au_fraud_rule_dup_rec_t;
void FRAUD_RULE_DUP(TPSVCINFO *p);
#endif /* FRAUD_RULE_DUP_H */

View file

@ -0,0 +1,55 @@
/*
* FRAUD_RULE_DUP.pgc - au 모듈 서비스 FRAUD_RULE_DUP (one service = one file).
* 이상거래 동일금액 반복 룰: 당일 카드 승인 가운데 같은 금액이 3회 이상
* 반복된 금액 그룹을 HAVING 커서로 훑어, 가장 반복이 심한 금액을 찾아
* 반복횟수 비례 점수로 부정거래 로그에 적발 기록한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "FRAUD_RULE_DUP.h"
void FRAUD_RULE_DUP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
long groups = 0, worst_amt = 0, worst_rep = 0, score, fid;
EXEC SQL BEGIN DECLARE SECTION;
long h_amt, h_rep;
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));
if (card[0] == 0 || bizdate[0] == 0) FAIL(b);
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 DECLARE cur_fdup CURSOR FOR
SELECT amount, count(*) FROM au_authorization
WHERE card_no = :h_card AND biz_date = :h_bizdate
GROUP BY amount HAVING count(*) >= 3
ORDER BY count(*) DESC;
EXEC SQL OPEN cur_fdup;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_fdup INTO :h_amt, :h_rep;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_fdup; FAIL(b); }
groups++;
if (h_rep > worst_rep) { worst_rep = h_rep; worst_amt = h_amt; }
}
EXEC SQL CLOSE cur_fdup;
setl(b, T_COUNT, groups);
setl(b, T_AMT1, worst_amt);
if (groups == 0) { setl(b, T_RC, 0); OK(b); }
score = 600 + worst_rep * 30;
if (score > 999) score = 999;
fid = audb_insert_fraud(0, card, score, "RULE_DUP_AMOUNT", bizdate);
if (fid < 0) FAIL(b);
userlog("FRAUD_RULE_DUP 적발 card=%s 반복금액=%ld x%ld score=%ld", card, worst_amt, worst_rep, score);
setl(b, T_ID1, fid);
setl(b, T_RC, 1);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* FRAUD_RULE_FREQ.h - au FRAUD_RULE_FREQ (copybook / record header).
* Request fields: T_KEY1(), T_BIZDATE()
* Response fields: T_RC(0=, 1= ), T_COUNT(10 ), T_ID1( ID)
*/
#ifndef FRAUD_RULE_FREQ_H
#define FRAUD_RULE_FREQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long tries10m;
long fraud_id;
} au_fraud_rule_freq_rec_t;
void FRAUD_RULE_FREQ(TPSVCINFO *p);
#endif /* FRAUD_RULE_FREQ_H */

View file

@ -0,0 +1,49 @@
/*
* FRAUD_RULE_FREQ.pgc - au 모듈 서비스 FRAUD_RULE_FREQ (one service = one file).
* 이상거래 다빈도 룰: 직전 10분간 승인 시도(거절 포함)가 5건 이상이면
* 건수 비례 점수(700 + 초과건수*20)를 매겨 부정거래 로그에 적발한다.
* 같은 카드가 이미 당일 동일 룰로 적발돼 있으면 중복 적발을 생략한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "FRAUD_RULE_FREQ.h"
void FRAUD_RULE_FREQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], bizdate[16];
long score, fid;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_dup = 0;
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));
if (card[0] == 0 || bizdate[0] == 0) FAIL(b);
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(*) INTO :h_cnt
FROM au_authorization
WHERE card_no = :h_card AND created_at >= now() - interval '10 minutes';
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
if (h_cnt < 5) { setl(b, T_RC, 0); OK(b); }
EXEC SQL SELECT count(*) INTO :h_dup
FROM au_fraud_log
WHERE card_no = :h_card AND biz_date = :h_bizdate AND reason = 'RULE_HIGH_FREQ';
if (sqlca.sqlcode < 0) FAIL(b);
if (h_dup > 0) { setl(b, T_RC, 1); setl(b, T_ID1, 0); OK(b); } /* 기적발 */
score = 700 + (h_cnt - 5) * 20;
if (score > 999) score = 999;
fid = audb_insert_fraud(0, card, score, "RULE_HIGH_FREQ", bizdate);
if (fid < 0) FAIL(b);
userlog("FRAUD_RULE_FREQ 적발 card=%s 10분 %ld건 score=%ld", card, h_cnt, score);
setl(b, T_ID1, fid);
setl(b, T_RC, 1);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* LIMIT_HIST.h - au LIMIT_HIST (copybook / record header).
* Request fields: T_KEY1(), T_ARG1()
* Response fields: T_COUNT(), T_GROSS( ), T_AMT1( ), T_STR1( )
*/
#ifndef LIMIT_HIST_H
#define LIMIT_HIST_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long days;
long used_days;
long total_used;
long peak_amount;
char peak_date[16];
} au_limit_hist_rec_t;
void LIMIT_HIST(TPSVCINFO *p);
#endif /* LIMIT_HIST_H */

View file

@ -0,0 +1,49 @@
/*
* LIMIT_HIST.pgc - au 모듈 서비스 LIMIT_HIST (one service = one file).
* 한도 사용 이력조회: 최근 N일 일자별 승인합계를 커서로 훑어
* 사용일수 / 기간 총사용액 / 최대 일사용액(및 해당 일자)을 산출한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "LIMIT_HIST.h"
void LIMIT_HIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long days_used = 0, total = 0, peak = 0;
char peak_date[16] = "";
EXEC SQL BEGIN DECLARE SECTION;
long h_days, h_daysum;
char h_card[24], h_date[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) { userlog("LIMIT_HIST: 카드번호 누락"); FAIL(b); }
h_days = getl(b, T_ARG1); if (h_days <= 0 || h_days > 90) h_days = 30;
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL DECLARE cur_lhist CURSOR FOR
SELECT CAST(biz_date AS text), coalesce(sum(amount),0)
FROM au_authorization
WHERE card_no = :h_card
AND status IN ('A','M','S')
AND biz_date >= current_date - CAST(:h_days AS integer)
GROUP BY biz_date ORDER BY biz_date;
EXEC SQL OPEN cur_lhist;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH cur_lhist INTO :h_date, :h_daysum;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_lhist; FAIL(b); }
days_used++;
total += h_daysum;
if (h_daysum > peak) { peak = h_daysum; strncpy(peak_date, h_date, sizeof(peak_date)-1); }
}
EXEC SQL CLOSE cur_lhist;
setl(b, T_COUNT, days_used);
setl(b, T_GROSS, total);
setl(b, T_AMT1, peak);
Bchg(b, T_STR1, 0, peak_date, 0L);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* LIMIT_RAISE_REQ.h - au LIMIT_RAISE_REQ (copybook / record header).
* Request fields: T_KEY1(), T_AMT1( )
* Response fields: T_RC(0=, 1= , 2= ), T_AMT2( ),
* T_GROSS( 30 )
*/
#ifndef LIMIT_RAISE_REQ_H
#define LIMIT_RAISE_REQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long want_limit;
long approved_limit;
long spend30;
} au_limit_raise_rec_t;
void LIMIT_RAISE_REQ(TPSVCINFO *p);
#endif /* LIMIT_RAISE_REQ_H */

View file

@ -0,0 +1,51 @@
/*
* LIMIT_RAISE_REQ.pgc - au 모듈 서비스 LIMIT_RAISE_REQ (one service = one file).
* 한도 영구상향 심사: 최근 30일 승인실적 합계와 거절 건수를 심사해
* 거절이력이 있으면 반려(RC=2), 실적이 희망한도의 절반 미만이면 반려(RC=1),
* 통과 시 daily_limit 를 희망한도로 확정한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "LIMIT_RAISE_REQ.h"
void LIMIT_RAISE_REQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_want, h_spend = 0, h_decl = 0, h_cur = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
h_want = getl(b, T_AMT1);
if (card[0] == 0 || h_want <= 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT daily_limit INTO :h_cur FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_want <= h_cur) { userlog("LIMIT_RAISE_REQ: 현행한도 이하 요청 card=%s", card); FAIL(b); }
EXEC SQL SELECT coalesce(sum(amount) FILTER (WHERE status IN ('A','M','S')), 0),
count(*) FILTER (WHERE status = 'D')
INTO :h_spend, :h_decl
FROM au_authorization
WHERE card_no = :h_card AND biz_date >= current_date - 30;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_GROSS, h_spend);
if (h_decl > 0) { /* 최근 거절이력 보유 - 심사 반려 */
setl(b, T_RC, 2); setl(b, T_AMT2, h_cur); OK(b);
}
if (h_spend * 2 < h_want) { /* 사용실적 부족 - 심사 반려 */
setl(b, T_RC, 1); setl(b, T_AMT2, h_cur); OK(b);
}
EXEC SQL UPDATE card_limit SET daily_limit = :h_want, updated_at = now()
WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("LIMIT_RAISE_REQ 승인 card=%s %ld -> %ld (30일실적 %ld)", card, h_cur, h_want, h_spend);
setl(b, T_RC, 0);
setl(b, T_AMT2, h_want);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* LIMIT_REMAIN.h - au LIMIT_REMAIN (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_AMT1(), T_AMT2(=+), T_STATUS(),
* T_RC(0=, 1= )
*/
#ifndef LIMIT_REMAIN_H
#define LIMIT_REMAIN_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long remain;
long ceiling;
char status[16];
} au_limit_remain_rec_t;
void LIMIT_REMAIN(TPSVCINFO *p);
#endif /* LIMIT_REMAIN_H */

View file

@ -0,0 +1,38 @@
/*
* LIMIT_REMAIN.pgc - au 모듈 서비스 LIMIT_REMAIN (one service = one file).
* 잔여한도 조회: (기본한도 + 임시한도 - 사용액) 을 계산해 내려준다.
* 음수면 0 으로 절사하고, 카드 상태가 ACTIVE 가 아니면 RC=1 로 표시.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "LIMIT_REMAIN.h"
void LIMIT_REMAIN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long remain;
EXEC SQL BEGIN DECLARE SECTION;
long h_daily = 0, h_used = 0, h_temp = 0;
char h_card[24], h_status[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT daily_limit, used_amount, temp_limit, status
INTO :h_daily, :h_used, :h_temp, :h_status
FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { userlog("LIMIT_REMAIN: 한도원장 없음 card=%s", card); FAIL(b); }
remain = h_daily + h_temp - h_used;
if (remain < 0) remain = 0;
setl(b, T_AMT1, remain);
setl(b, T_AMT2, h_daily + h_temp);
Bchg(b, T_STATUS, 0, h_status, 0L);
setl(b, T_RC, (strcmp(h_status, "ACTIVE") == 0) ? 0 : 1);
OK(b);
}

View file

@ -0,0 +1,18 @@
/*
* LIMIT_RESTORE.h - au LIMIT_RESTORE (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_AMT1( ), T_RC(0=, 1= )
*/
#ifndef LIMIT_RESTORE_H
#define LIMIT_RESTORE_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long restored_amount;
} au_limit_restore_rec_t;
void LIMIT_RESTORE(TPSVCINFO *p);
#endif /* LIMIT_RESTORE_H */

View file

@ -0,0 +1,41 @@
/*
* LIMIT_RESTORE.pgc - au 모듈 서비스 LIMIT_RESTORE (one service = one file).
* 임시한도 자동복원: LIMIT_TEMP 로 상향됐던 temp_limit 를 걷어내고
* daily_limit 를 원상 복구한다. temp_limit 가 0 이면 멱등하게 RC=1 로 응답.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "LIMIT_RESTORE.h"
void LIMIT_RESTORE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_temp = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) { userlog("LIMIT_RESTORE: 카드번호 누락"); FAIL(b); }
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT temp_limit INTO :h_temp FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { userlog("LIMIT_RESTORE: 미등록 카드 %s", card); FAIL(b); }
if (h_temp <= 0) { /* 복원할 임시한도 없음 - 멱등 처리 */
setl(b, T_AMT1, 0);
setl(b, T_RC, 1);
OK(b);
}
EXEC SQL UPDATE card_limit
SET daily_limit = daily_limit - :h_temp, temp_limit = 0, updated_at = now()
WHERE card_no = :h_card AND temp_limit = :h_temp;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("LIMIT_RESTORE 카드=%s 임시한도 %ld 복원", card, h_temp);
setl(b, T_AMT1, h_temp);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* LIMIT_USAGE.h - au LIMIT_USAGE (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_ARG1( bps, 10000=100%), T_STR1( NORMAL/WARN/CRIT/OVER),
* T_AMT1(), T_AMT2()
*/
#ifndef LIMIT_USAGE_H
#define LIMIT_USAGE_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long usage_bps;
char grade[8];
} au_limit_usage_rec_t;
void LIMIT_USAGE(TPSVCINFO *p);
#endif /* LIMIT_USAGE_H */

View file

@ -0,0 +1,40 @@
/*
* LIMIT_USAGE.pgc - au 모듈 서비스 LIMIT_USAGE (one service = one file).
* 한도 사용률 조회: 사용액/기본한도를 bps(만분율)로 환산하고
* 70% 미만 NORMAL, 70~90% WARN, 90~100% CRIT, 100% 초과 OVER 등급을 매긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "LIMIT_USAGE.h"
void LIMIT_USAGE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long bps = 0;
const char *grade;
EXEC SQL BEGIN DECLARE SECTION;
long h_daily = 0, h_used = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT daily_limit, used_amount INTO :h_daily, :h_used
FROM card_limit WHERE card_no = :h_card;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
if (h_daily > 0) bps = (h_used * 10000L) / h_daily;
if (bps < 7000) grade = "NORMAL";
else if (bps < 9000) grade = "WARN";
else if (bps <= 10000) grade = "CRIT";
else grade = "OVER";
setl(b, T_ARG1, bps);
Bchg(b, T_STR1, 0, (char *)grade, 0L);
setl(b, T_AMT1, h_used);
setl(b, T_AMT2, h_daily);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* MERCH_AUTH_RANK.h - au MERCH_AUTH_RANK (copybook / record header).
* Request fields: T_MERCHANT(), T_BIZDATE()
* Response fields: T_ARG1( ), T_GROSS( ), T_COUNT( ),
* T_RC(1= )
*/
#ifndef MERCH_AUTH_RANK_H
#define MERCH_AUTH_RANK_H
#include "au_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long rank;
long gross;
long merchant_total;
} au_merch_auth_rank_rec_t;
void MERCH_AUTH_RANK(TPSVCINFO *p);
#endif /* MERCH_AUTH_RANK_H */

View file

@ -0,0 +1,49 @@
/*
* MERCH_AUTH_RANK.pgc - au 모듈 서비스 MERCH_AUTH_RANK (one service = one file).
* 가맹점 승인 순위: 영업일의 가맹점별 승인금액을 rank() 윈도우로 서열화한
* 서브쿼리에서 요청 가맹점의 순위와 금액을 찾아준다. 실적이 없으면 RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "MERCH_AUTH_RANK.h"
void MERCH_AUTH_RANK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_rank = 0, h_gross = 0, h_total = 0;
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));
if (merch[0] == 0 || bizdate[0] == 0) FAIL(b);
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(DISTINCT merchant_id) INTO :h_total
FROM au_authorization
WHERE biz_date = :h_bizdate AND status IN ('A','M','S');
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_total);
EXEC SQL SELECT r.rnk, r.gross INTO :h_rank, :h_gross FROM (
SELECT merchant_id, sum(amount) AS gross,
rank() OVER (ORDER BY sum(amount) DESC) AS rnk
FROM au_authorization
WHERE biz_date = :h_bizdate AND status IN ('A','M','S')
GROUP BY merchant_id) r
WHERE r.merchant_id = :h_merch;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { /* 당일 승인실적 없음 */
setl(b, T_ARG1, 0);
setl(b, T_GROSS, 0);
setl(b, T_RC, 1);
OK(b);
}
setl(b, T_ARG1, h_rank);
setl(b, T_GROSS, h_gross);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,21 @@
/*
* NIGHT_POLICY.h - au NIGHT_POLICY (copybook / record header).
* Request fields: T_KEY1(), T_AMOUNT( )
* Response fields: T_RC(0=, 1= , 2= ),
* T_COUNT( 7 ), T_GROSS( 7 )
*/
#ifndef NIGHT_POLICY_H
#define NIGHT_POLICY_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long req_amount;
long night_count;
long night_gross;
} au_night_policy_rec_t;
void NIGHT_POLICY(TPSVCINFO *p);
#endif /* NIGHT_POLICY_H */

View file

@ -0,0 +1,45 @@
/*
* NIGHT_POLICY.pgc - au 모듈 서비스 NIGHT_POLICY (one service = one file).
* 심야 승인정책 점검: 최근 7일간 심야시간대(23시~06시) 승인 이력을 집계해
* 심야건수 8건 초과면 차단(RC=1), 심야누적 + 요청금액이 500만원을 넘으면
* 금액제한 차단(RC=2)을 판정한다. 실제 차단은 호출자(AUTH 흐름)가 수행.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "NIGHT_POLICY.h"
void NIGHT_POLICY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long amount;
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_sum = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
amount = getl(b, T_AMOUNT);
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
FROM au_authorization
WHERE card_no = :h_card
AND status IN ('A','M','S')
AND created_at >= now() - interval '7 days'
AND (EXTRACT(HOUR FROM created_at) >= 23 OR EXTRACT(HOUR FROM created_at) < 6);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
if (h_cnt > 8) {
userlog("NIGHT_POLICY 심야 건수제한 card=%s cnt=%ld", card, h_cnt);
setl(b, T_RC, 1);
} else if (h_sum + amount > 5000000) {
userlog("NIGHT_POLICY 심야 금액제한 card=%s sum=%ld req=%ld", card, h_sum, amount);
setl(b, T_RC, 2);
} else {
setl(b, T_RC, 0);
}
OK(b);
}

View file

@ -0,0 +1,20 @@
/*
* NOAUTH_CAPTURE.h - au NOAUTH_CAPTURE (copybook / record header).
* Request fields: T_MERCHANT(), T_BIZDATE()
* Response fields: T_COUNT( ), T_GROSS( ), T_RC(1= )
*/
#ifndef NOAUTH_CAPTURE_H
#define NOAUTH_CAPTURE_H
#include "au_svc.h"
typedef struct {
char merchant_id[64];
char biz_date[16];
long suspect_count;
long suspect_gross;
} au_noauth_capture_rec_t;
void NOAUTH_CAPTURE(TPSVCINFO *p);
#endif /* NOAUTH_CAPTURE_H */

View file

@ -0,0 +1,43 @@
/*
* NOAUTH_CAPTURE.pgc - au 모듈 서비스 NOAUTH_CAPTURE (one service = one file).
* 무승인 매입 검출: 매입원장(purchase)에는 있는데 같은 가맹점/영업일/금액의
* 유효 승인(A/M/S)이 승인원장에 존재하지 않는 매입 건을 NOT EXISTS
* 안티조인으로 골라 건수와 금액을 보고한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "NOAUTH_CAPTURE.h"
void NOAUTH_CAPTURE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt = 0, h_amt = 0;
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));
if (merch[0] == 0 || bizdate[0] == 0) { userlog("NOAUTH_CAPTURE: 인자 누락"); FAIL(b); }
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(*), coalesce(sum(p.amount),0)
INTO :h_cnt, :h_amt
FROM purchase p
WHERE p.merchant_id = :h_merch AND p.biz_date = :h_bizdate
AND p.status NOT IN ('C','V')
AND NOT EXISTS (SELECT 1 FROM au_authorization a
WHERE a.merchant_id = p.merchant_id
AND a.biz_date = p.biz_date
AND a.amount = p.amount
AND a.status IN ('A','M','S'));
if (sqlca.sqlcode < 0) FAIL(b);
if (h_cnt > 0)
userlog("NOAUTH_CAPTURE 의심건 merch=%s date=%s cnt=%ld amt=%ld", merch, bizdate, h_cnt, h_amt);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_amt);
setl(b, T_RC, (h_cnt > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* OVERSEAS_POLICY.h - au OVERSEAS_POLICY (copybook / record header).
* Request fields: T_KEY1(), T_MERCHANT(), T_AMOUNT(),
* T_STR1(), T_BIZDATE()
* Response fields: T_ID1(), T_RC(0=, 1= , 9= )
*/
#ifndef OVERSEAS_POLICY_H
#define OVERSEAS_POLICY_H
#include "au_svc.h"
typedef struct {
char card_no[24];
char merchant_id[64];
long amount;
char country[4];
char biz_date[16];
} au_overseas_policy_rec_t;
void OVERSEAS_POLICY(TPSVCINFO *p);
#endif /* OVERSEAS_POLICY_H */

View file

@ -0,0 +1,61 @@
/*
* OVERSEAS_POLICY.pgc - au 모듈 서비스 OVERSEAS_POLICY (one service = one file).
* 해외 승인정책: 제재국(간이 목록) 거래는 즉시 차단(RC=9)하고,
* 당일 해외(auth_type=OVERSEA) 누적 + 요청금액이 300만원 한도를 넘으면
* 거절(D) 기록 후 RC=1, 통과 시 OVERSEA 유형으로 승인 원장을 기록한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "OVERSEAS_POLICY.h"
static const char *AU_BLOCKED_CTRY[] = { "KP", "IR", "SY", "CU", 0 };
void OVERSEAS_POLICY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24], merch[64], ctry[4], bizdate[16], appr[12];
long amount, auth_id;
int i;
EXEC SQL BEGIN DECLARE SECTION;
long h_daysum = 0;
char h_card[24], 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_STR1, ctry, sizeof(ctry));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
if (card[0] == 0 || amount <= 0) FAIL(b);
for (i = 0; AU_BLOCKED_CTRY[i]; i++)
if (strcmp(ctry, AU_BLOCKED_CTRY[i]) == 0) {
userlog("OVERSEAS_POLICY 제재국 차단 card=%s ctry=%s", card, ctry);
setl(b, T_RC, 9);
OK(b);
}
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 coalesce(sum(amount),0) INTO :h_daysum
FROM au_authorization
WHERE card_no = :h_card AND biz_date = :h_bizdate
AND auth_type = 'OVERSEA' AND status = 'A';
if (sqlca.sqlcode < 0) FAIL(b);
auth_id = audb_next_auth_id();
if (auth_id < 0) FAIL(b);
if (h_daysum + amount > 3000000) { /* 일일 해외한도 300만원 */
if (audb_insert_auth(auth_id, card, merch, amount, "000000", "OVERSEA", "D", bizdate) < 0) FAIL(b);
setl(b, T_ID1, auth_id);
setl(b, T_RC, 1);
OK(b);
}
sprintf(appr, "F%05ld", auth_id % 100000);
if (audb_insert_auth(auth_id, card, merch, amount, appr, "OVERSEA", "A", bizdate) < 0) FAIL(b);
userlog("OVERSEAS_POLICY 승인 card=%s ctry=%s amt=%ld 누적=%ld", card, ctry, amount, h_daysum + amount);
setl(b, T_ID1, auth_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,18 @@
/*
* PREAUTH_CANCEL.h - au PREAUTH_CANCEL (copybook / record header).
* Request fields: T_ID2( ID)
* Response fields: T_AMT1( ), T_RC(0=, 1=, 2= , 3= )
*/
#ifndef PREAUTH_CANCEL_H
#define PREAUTH_CANCEL_H
#include "au_svc.h"
typedef struct {
long preauth_id;
long released_amount;
} au_preauth_cancel_rec_t;
void PREAUTH_CANCEL(TPSVCINFO *p);
#endif /* PREAUTH_CANCEL_H */

View file

@ -0,0 +1,37 @@
/*
* PREAUTH_CANCEL.pgc - au 모듈 서비스 PREAUTH_CANCEL (one service = one file).
* 선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다.
* 상태별로 세분화된 RC(없음/이미 확정/이미 취소)를 돌려줘 콜센터 응대에 쓴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "PREAUTH_CANCEL.h"
void PREAUTH_CANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_amt = 0;
char h_status[4];
EXEC SQL END DECLARE SECTION;
h_id = getl(b, T_ID2);
if (h_id <= 0) FAIL(b);
memset(h_status, 0, sizeof(h_status));
EXEC SQL SELECT status, amount INTO :h_status, :h_amt
FROM au_preauth WHERE preauth_id = :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); }
if (h_status[0] == 'C') { setl(b, T_RC, 2); setl(b, T_AMT1, h_amt); OK(b); }
if (h_status[0] == 'X') { setl(b, T_RC, 3); setl(b, T_AMT1, h_amt); OK(b); }
EXEC SQL UPDATE au_preauth SET status = 'X'
WHERE preauth_id = :h_id AND status = 'P';
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("PREAUTH_CANCEL id=%ld 금액 %ld 홀드 해제", h_id, h_amt);
setl(b, T_AMT1, h_amt);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,19 @@
/*
* PREAUTH_EXTEND.h - au PREAUTH_EXTEND (copybook / record header).
* Request fields: T_ID2( ID), T_ARG1(, 7)
* Response fields: T_STR1( ), T_RC(0=, 1= )
*/
#ifndef PREAUTH_EXTEND_H
#define PREAUTH_EXTEND_H
#include "au_svc.h"
typedef struct {
long preauth_id;
long extend_days;
char new_expiry[16];
} au_preauth_extend_rec_t;
void PREAUTH_EXTEND(TPSVCINFO *p);
#endif /* PREAUTH_EXTEND_H */

View file

@ -0,0 +1,44 @@
/*
* PREAUTH_EXTEND.pgc - au 모듈 서비스 PREAUTH_EXTEND (one service = one file).
* 선승인 연장: 아직 확정되지 않은(P) 선승인의 기준일(biz_date)을 지정 일수만큼
* 미뤄 만기를 연장하고, 연장 후 만기일을 문자열로 돌려준다.
* 확정(C)/취소(X) 상태는 연장 불가로 RC=1 응답한다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "PREAUTH_EXTEND.h"
void PREAUTH_EXTEND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_days;
char h_newdate[16], h_status[4];
EXEC SQL END DECLARE SECTION;
h_id = getl(b, T_ID2);
h_days = getl(b, T_ARG1); if (h_days <= 0 || h_days > 60) h_days = 7;
if (h_id <= 0) { userlog("PREAUTH_EXTEND: 선승인 ID 누락"); FAIL(b); }
memset(h_newdate, 0, sizeof(h_newdate));
memset(h_status, 0, sizeof(h_status));
EXEC SQL UPDATE au_preauth
SET biz_date = biz_date + CAST(:h_days AS integer)
WHERE preauth_id = :h_id AND status = 'P';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) {
EXEC SQL SELECT status INTO :h_status FROM au_preauth WHERE preauth_id = :h_id;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("PREAUTH_EXTEND 연장불가 id=%ld status=%s", h_id, h_status);
setl(b, T_RC, 1);
OK(b);
}
EXEC SQL SELECT CAST(biz_date AS text) INTO :h_newdate
FROM au_preauth WHERE preauth_id = :h_id;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
userlog("PREAUTH_EXTEND id=%ld +%ld일 -> %s", h_id, h_days, h_newdate);
Bchg(b, T_STR1, 0, h_newdate, 0L);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* PREAUTH_INQ.h - au PREAUTH_INQ (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_ARG1( P건수), T_ARG2( C건수), T_ARG3( X건수),
* T_AMT1( ), T_RC(1= )
*/
#ifndef PREAUTH_INQ_H
#define PREAUTH_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long pending_cnt;
long confirmed_cnt;
long canceled_cnt;
long hold_amount;
} au_preauth_inq_rec_t;
void PREAUTH_INQ(TPSVCINFO *p);
#endif /* PREAUTH_INQ_H */

View file

@ -0,0 +1,36 @@
/*
* PREAUTH_INQ.pgc - au 모듈 서비스 PREAUTH_INQ (one service = one file).
* 선승인 조회: 카드의 선승인 현황을 상태(P/C/X)별 FILTER 집계로 나눠 주고
* 미확정 홀드금액 합계를 함께 내려준다. 홀드가 남아 있으면 RC=1.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "PREAUTH_INQ.h"
void PREAUTH_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
EXEC SQL BEGIN DECLARE SECTION;
long h_p = 0, h_c = 0, h_x = 0, h_hold = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*) FILTER (WHERE status = 'P'),
count(*) FILTER (WHERE status = 'C'),
count(*) FILTER (WHERE status = 'X'),
coalesce(sum(amount) FILTER (WHERE status = 'P'), 0)
INTO :h_p, :h_c, :h_x, :h_hold
FROM au_preauth WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_ARG1, h_p);
setl(b, T_ARG2, h_c);
setl(b, T_ARG3, h_x);
setl(b, T_AMT1, h_hold);
setl(b, T_RC, (h_p > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* RISK_SCORE_INQ.h - au RISK_SCORE_INQ (copybook / record header).
* Request fields: T_KEY1()
* Response fields: T_ARG1( 0~1000), T_STR1( LOW/MID/HIGH/SEVERE),
* T_ARG2( ), T_ARG3(30 )
*/
#ifndef RISK_SCORE_INQ_H
#define RISK_SCORE_INQ_H
#include "au_svc.h"
typedef struct {
char card_no[24];
long risk_score;
char grade[8];
long fraud_cases;
long declines30;
} au_risk_score_rec_t;
void RISK_SCORE_INQ(TPSVCINFO *p);
#endif /* RISK_SCORE_INQ_H */

View file

@ -0,0 +1,54 @@
/*
* RISK_SCORE_INQ.pgc - au 모듈 서비스 RISK_SCORE_INQ (one service = one file).
* 카드 리스크 점수 산출: 부정거래 적발 이력(평균점수/건수), 30일 거절률,
* 블랙리스트 등재 여부를 가중 합산해 0~1000 종합 리스크 점수와
* 4단계 등급(LOW/MID/HIGH/SEVERE)을 매긴다.
* Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
*/
#include "RISK_SCORE_INQ.h"
void RISK_SCORE_INQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char card[24];
long score;
const char *grade;
EXEC SQL BEGIN DECLARE SECTION;
long h_fcnt = 0, h_favg = 0, h_dec = 0, h_all = 0, h_black = 0;
char h_card[24];
EXEC SQL END DECLARE SECTION;
gets_(b, T_KEY1, card, sizeof(card));
if (card[0] == 0) FAIL(b);
strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0;
EXEC SQL SELECT count(*), CAST(coalesce(avg(score),0) AS bigint)
INTO :h_fcnt, :h_favg FROM au_fraud_log WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'D')
INTO :h_all, :h_dec
FROM au_authorization
WHERE card_no = :h_card AND biz_date >= current_date - 30;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT count(*) INTO :h_black FROM au_blacklist WHERE card_no = :h_card;
if (sqlca.sqlcode < 0) FAIL(b);
/* 가중 합산: 적발평균 40% + 적발건수(건당 60점) + 거절률(최대 300점) + 블랙리스트 300점 */
score = (h_favg * 4) / 10 + h_fcnt * 60;
if (h_all > 0) score += (h_dec * 300L) / h_all;
if (h_black > 0) score += 300;
if (score > 1000) score = 1000;
if (score < 200) grade = "LOW";
else if (score < 500) grade = "MID";
else if (score < 800) grade = "HIGH";
else grade = "SEVERE";
setl(b, T_ARG1, score);
Bchg(b, T_STR1, 0, (char *)grade, 0L);
setl(b, T_ARG2, h_fcnt);
setl(b, T_ARG3, h_dec);
OK(b);
}

View file

@ -0,0 +1,22 @@
/*
* STANDIN_INQ.h - au STANDIN_INQ (copybook / record header).
* Request fields: T_BIZDATE()
* Response fields: T_ARG1( A건수), T_ARG2( S건수), T_AMT1( ),
* T_AMT2( ), T_RC(1= )
*/
#ifndef STANDIN_INQ_H
#define STANDIN_INQ_H
#include "au_svc.h"
typedef struct {
char biz_date[16];
long pending_cnt;
long settled_cnt;
long pending_amt;
long settled_amt;
} au_standin_inq_rec_t;
void STANDIN_INQ(TPSVCINFO *p);
#endif /* STANDIN_INQ_H */

Some files were not shown because too many files have changed in this diff Show more