규모확장 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:
parent
19acf4ed96
commit
ea90bef264
1292 changed files with 53968 additions and 20 deletions
|
|
@ -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}
|
||||
};
|
||||
|
||||
|
|
|
|||
69
app/src/au/batch/au_blacklist_hit_batch.pgc
Normal file
69
app/src/au/batch/au_blacklist_hit_batch.pgc
Normal 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;
|
||||
}
|
||||
81
app/src/au/batch/au_cancel_reproc_batch.pgc
Normal file
81
app/src/au/batch/au_cancel_reproc_batch.pgc
Normal 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;
|
||||
}
|
||||
60
app/src/au/batch/au_delay_watch_batch.pgc
Normal file
60
app/src/au/batch/au_delay_watch_batch.pgc
Normal 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;
|
||||
}
|
||||
65
app/src/au/batch/au_dup_auth_batch.pgc
Normal file
65
app/src/au/batch/au_dup_auth_batch.pgc
Normal 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;
|
||||
}
|
||||
72
app/src/au/batch/au_hourly_stat_batch.pgc
Normal file
72
app/src/au/batch/au_hourly_stat_batch.pgc
Normal 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;
|
||||
}
|
||||
76
app/src/au/batch/au_issuer_mix_batch.pgc
Normal file
76
app/src/au/batch/au_issuer_mix_batch.pgc
Normal 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;
|
||||
}
|
||||
61
app/src/au/batch/au_limit_restore_batch.pgc
Normal file
61
app/src/au/batch/au_limit_restore_batch.pgc
Normal 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;
|
||||
}
|
||||
71
app/src/au/batch/au_merch_rank_batch.pgc
Normal file
71
app/src/au/batch/au_merch_rank_batch.pgc
Normal 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;
|
||||
}
|
||||
63
app/src/au/batch/au_night_auth_batch.pgc
Normal file
63
app/src/au/batch/au_night_auth_batch.pgc
Normal 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;
|
||||
}
|
||||
67
app/src/au/batch/au_noauth_detect_batch.pgc
Normal file
67
app/src/au/batch/au_noauth_detect_batch.pgc
Normal 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;
|
||||
}
|
||||
71
app/src/au/batch/au_preauth_convert_batch.pgc
Normal file
71
app/src/au/batch/au_preauth_convert_batch.pgc
Normal 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;
|
||||
}
|
||||
64
app/src/au/batch/au_standin_settle_batch.pgc
Normal file
64
app/src/au/batch/au_standin_settle_batch.pgc
Normal 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;
|
||||
}
|
||||
71
app/src/au/batch/au_velocity_flag_batch.pgc
Normal file
71
app/src/au/batch/au_velocity_flag_batch.pgc
Normal 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;
|
||||
}
|
||||
18
app/src/au/dbio/au_blacklist_hit_batch.h
Normal file
18
app/src/au/dbio/au_blacklist_hit_batch.h
Normal 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 */
|
||||
19
app/src/au/dbio/au_cancel_reproc_batch.h
Normal file
19
app/src/au/dbio/au_cancel_reproc_batch.h
Normal 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 */
|
||||
19
app/src/au/dbio/au_delay_watch_batch.h
Normal file
19
app/src/au/dbio/au_delay_watch_batch.h
Normal 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 */
|
||||
18
app/src/au/dbio/au_dup_auth_batch.h
Normal file
18
app/src/au/dbio/au_dup_auth_batch.h
Normal 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 */
|
||||
19
app/src/au/dbio/au_hourly_stat_batch.h
Normal file
19
app/src/au/dbio/au_hourly_stat_batch.h
Normal 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 */
|
||||
20
app/src/au/dbio/au_issuer_mix_batch.h
Normal file
20
app/src/au/dbio/au_issuer_mix_batch.h
Normal 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 */
|
||||
16
app/src/au/dbio/au_limit_restore_batch.h
Normal file
16
app/src/au/dbio/au_limit_restore_batch.h
Normal 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 */
|
||||
21
app/src/au/dbio/au_merch_rank_batch.h
Normal file
21
app/src/au/dbio/au_merch_rank_batch.h
Normal 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 */
|
||||
19
app/src/au/dbio/au_night_auth_batch.h
Normal file
19
app/src/au/dbio/au_night_auth_batch.h
Normal 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 */
|
||||
18
app/src/au/dbio/au_noauth_detect_batch.h
Normal file
18
app/src/au/dbio/au_noauth_detect_batch.h
Normal 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 */
|
||||
18
app/src/au/dbio/au_preauth_convert_batch.h
Normal file
18
app/src/au/dbio/au_preauth_convert_batch.h
Normal 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 */
|
||||
18
app/src/au/dbio/au_standin_settle_batch.h
Normal file
18
app/src/au/dbio/au_standin_settle_batch.h
Normal 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 */
|
||||
18
app/src/au/dbio/au_velocity_flag_batch.h
Normal file
18
app/src/au/dbio/au_velocity_flag_batch.h
Normal 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 */
|
||||
22
app/src/au/svc/APPROVAL_RATE.h
Normal file
22
app/src/au/svc/APPROVAL_RATE.h
Normal 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 */
|
||||
42
app/src/au/svc/APPROVAL_RATE.pgc
Normal file
42
app/src/au/svc/APPROVAL_RATE.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/AUTH_AMT_ADJUST.h
Normal file
19
app/src/au/svc/AUTH_AMT_ADJUST.h
Normal 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 */
|
||||
57
app/src/au/svc/AUTH_AMT_ADJUST.pgc
Normal file
57
app/src/au/svc/AUTH_AMT_ADJUST.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/AUTH_DELAY_MON.h
Normal file
22
app/src/au/svc/AUTH_DELAY_MON.h
Normal 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 */
|
||||
39
app/src/au/svc/AUTH_DELAY_MON.pgc
Normal file
39
app/src/au/svc/AUTH_DELAY_MON.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/AUTH_DUP_CHK.h
Normal file
22
app/src/au/svc/AUTH_DUP_CHK.h
Normal 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 */
|
||||
43
app/src/au/svc/AUTH_DUP_CHK.pgc
Normal file
43
app/src/au/svc/AUTH_DUP_CHK.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/AUTH_EXPIRE_CHK.h
Normal file
22
app/src/au/svc/AUTH_EXPIRE_CHK.h
Normal 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 */
|
||||
44
app/src/au/svc/AUTH_EXPIRE_CHK.pgc
Normal file
44
app/src/au/svc/AUTH_EXPIRE_CHK.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/AUTH_PARTIAL.h
Normal file
22
app/src/au/svc/AUTH_PARTIAL.h
Normal 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 */
|
||||
47
app/src/au/svc/AUTH_PARTIAL.pgc
Normal file
47
app/src/au/svc/AUTH_PARTIAL.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/AUTH_RESEND.h
Normal file
21
app/src/au/svc/AUTH_RESEND.h
Normal 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 */
|
||||
42
app/src/au/svc/AUTH_RESEND.pgc
Normal file
42
app/src/au/svc/AUTH_RESEND.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/BIN_ROUTE_INQ.h
Normal file
21
app/src/au/svc/BIN_ROUTE_INQ.h
Normal 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 */
|
||||
48
app/src/au/svc/BIN_ROUTE_INQ.pgc
Normal file
48
app/src/au/svc/BIN_ROUTE_INQ.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/BLACKLIST_ADD.h
Normal file
19
app/src/au/svc/BLACKLIST_ADD.h
Normal 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 */
|
||||
46
app/src/au/svc/BLACKLIST_ADD.pgc
Normal file
46
app/src/au/svc/BLACKLIST_ADD.pgc
Normal 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);
|
||||
}
|
||||
18
app/src/au/svc/BLACKLIST_DEL.h
Normal file
18
app/src/au/svc/BLACKLIST_DEL.h
Normal 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 */
|
||||
39
app/src/au/svc/BLACKLIST_DEL.pgc
Normal file
39
app/src/au/svc/BLACKLIST_DEL.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/BLACKLIST_INQ.h
Normal file
21
app/src/au/svc/BLACKLIST_INQ.h
Normal 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 */
|
||||
41
app/src/au/svc/BLACKLIST_INQ.pgc
Normal file
41
app/src/au/svc/BLACKLIST_INQ.pgc
Normal 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);
|
||||
}
|
||||
20
app/src/au/svc/CANCEL_REPROC.h
Normal file
20
app/src/au/svc/CANCEL_REPROC.h
Normal 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 */
|
||||
56
app/src/au/svc/CANCEL_REPROC.pgc
Normal file
56
app/src/au/svc/CANCEL_REPROC.pgc
Normal 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);
|
||||
}
|
||||
23
app/src/au/svc/CANCEL_STATUS.h
Normal file
23
app/src/au/svc/CANCEL_STATUS.h
Normal 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 */
|
||||
37
app/src/au/svc/CANCEL_STATUS.pgc
Normal file
37
app/src/au/svc/CANCEL_STATUS.pgc
Normal 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);
|
||||
}
|
||||
26
app/src/au/svc/CARD_DAILY_RPT.h
Normal file
26
app/src/au/svc/CARD_DAILY_RPT.h
Normal 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 */
|
||||
46
app/src/au/svc/CARD_DAILY_RPT.pgc
Normal file
46
app/src/au/svc/CARD_DAILY_RPT.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/CARD_REISSUE.h
Normal file
19
app/src/au/svc/CARD_REISSUE.h
Normal 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 */
|
||||
48
app/src/au/svc/CARD_REISSUE.pgc
Normal file
48
app/src/au/svc/CARD_REISSUE.pgc
Normal 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);
|
||||
}
|
||||
18
app/src/au/svc/CARD_RESUME.h
Normal file
18
app/src/au/svc/CARD_RESUME.h
Normal 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 */
|
||||
42
app/src/au/svc/CARD_RESUME.pgc
Normal file
42
app/src/au/svc/CARD_RESUME.pgc
Normal 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);
|
||||
}
|
||||
24
app/src/au/svc/CARD_STATE_INQ.h
Normal file
24
app/src/au/svc/CARD_STATE_INQ.h
Normal 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 */
|
||||
39
app/src/au/svc/CARD_STATE_INQ.pgc
Normal file
39
app/src/au/svc/CARD_STATE_INQ.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/CARD_SUSPEND.h
Normal file
19
app/src/au/svc/CARD_SUSPEND.h
Normal 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 */
|
||||
41
app/src/au/svc/CARD_SUSPEND.pgc
Normal file
41
app/src/au/svc/CARD_SUSPEND.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/DECLINE_ANALYZE.h
Normal file
22
app/src/au/svc/DECLINE_ANALYZE.h
Normal 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 */
|
||||
54
app/src/au/svc/DECLINE_ANALYZE.pgc
Normal file
54
app/src/au/svc/DECLINE_ANALYZE.pgc
Normal 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);
|
||||
}
|
||||
23
app/src/au/svc/FRAUD_CASE_INQ.h
Normal file
23
app/src/au/svc/FRAUD_CASE_INQ.h
Normal 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 */
|
||||
42
app/src/au/svc/FRAUD_CASE_INQ.pgc
Normal file
42
app/src/au/svc/FRAUD_CASE_INQ.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/FRAUD_RULE_AMT.h
Normal file
22
app/src/au/svc/FRAUD_RULE_AMT.h
Normal 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 */
|
||||
51
app/src/au/svc/FRAUD_RULE_AMT.pgc
Normal file
51
app/src/au/svc/FRAUD_RULE_AMT.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/FRAUD_RULE_DUP.h
Normal file
21
app/src/au/svc/FRAUD_RULE_DUP.h
Normal 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 */
|
||||
55
app/src/au/svc/FRAUD_RULE_DUP.pgc
Normal file
55
app/src/au/svc/FRAUD_RULE_DUP.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/FRAUD_RULE_FREQ.h
Normal file
19
app/src/au/svc/FRAUD_RULE_FREQ.h
Normal 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 */
|
||||
49
app/src/au/svc/FRAUD_RULE_FREQ.pgc
Normal file
49
app/src/au/svc/FRAUD_RULE_FREQ.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/LIMIT_HIST.h
Normal file
22
app/src/au/svc/LIMIT_HIST.h
Normal 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 */
|
||||
49
app/src/au/svc/LIMIT_HIST.pgc
Normal file
49
app/src/au/svc/LIMIT_HIST.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/LIMIT_RAISE_REQ.h
Normal file
21
app/src/au/svc/LIMIT_RAISE_REQ.h
Normal 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 */
|
||||
51
app/src/au/svc/LIMIT_RAISE_REQ.pgc
Normal file
51
app/src/au/svc/LIMIT_RAISE_REQ.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/LIMIT_REMAIN.h
Normal file
21
app/src/au/svc/LIMIT_REMAIN.h
Normal 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 */
|
||||
38
app/src/au/svc/LIMIT_REMAIN.pgc
Normal file
38
app/src/au/svc/LIMIT_REMAIN.pgc
Normal 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);
|
||||
}
|
||||
18
app/src/au/svc/LIMIT_RESTORE.h
Normal file
18
app/src/au/svc/LIMIT_RESTORE.h
Normal 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 */
|
||||
41
app/src/au/svc/LIMIT_RESTORE.pgc
Normal file
41
app/src/au/svc/LIMIT_RESTORE.pgc
Normal 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);
|
||||
}
|
||||
20
app/src/au/svc/LIMIT_USAGE.h
Normal file
20
app/src/au/svc/LIMIT_USAGE.h
Normal 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 */
|
||||
40
app/src/au/svc/LIMIT_USAGE.pgc
Normal file
40
app/src/au/svc/LIMIT_USAGE.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/MERCH_AUTH_RANK.h
Normal file
22
app/src/au/svc/MERCH_AUTH_RANK.h
Normal 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 */
|
||||
49
app/src/au/svc/MERCH_AUTH_RANK.pgc
Normal file
49
app/src/au/svc/MERCH_AUTH_RANK.pgc
Normal 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);
|
||||
}
|
||||
21
app/src/au/svc/NIGHT_POLICY.h
Normal file
21
app/src/au/svc/NIGHT_POLICY.h
Normal 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 */
|
||||
45
app/src/au/svc/NIGHT_POLICY.pgc
Normal file
45
app/src/au/svc/NIGHT_POLICY.pgc
Normal 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);
|
||||
}
|
||||
20
app/src/au/svc/NOAUTH_CAPTURE.h
Normal file
20
app/src/au/svc/NOAUTH_CAPTURE.h
Normal 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 */
|
||||
43
app/src/au/svc/NOAUTH_CAPTURE.pgc
Normal file
43
app/src/au/svc/NOAUTH_CAPTURE.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/OVERSEAS_POLICY.h
Normal file
22
app/src/au/svc/OVERSEAS_POLICY.h
Normal 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 */
|
||||
61
app/src/au/svc/OVERSEAS_POLICY.pgc
Normal file
61
app/src/au/svc/OVERSEAS_POLICY.pgc
Normal 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);
|
||||
}
|
||||
18
app/src/au/svc/PREAUTH_CANCEL.h
Normal file
18
app/src/au/svc/PREAUTH_CANCEL.h
Normal 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 */
|
||||
37
app/src/au/svc/PREAUTH_CANCEL.pgc
Normal file
37
app/src/au/svc/PREAUTH_CANCEL.pgc
Normal 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);
|
||||
}
|
||||
19
app/src/au/svc/PREAUTH_EXTEND.h
Normal file
19
app/src/au/svc/PREAUTH_EXTEND.h
Normal 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 */
|
||||
44
app/src/au/svc/PREAUTH_EXTEND.pgc
Normal file
44
app/src/au/svc/PREAUTH_EXTEND.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/PREAUTH_INQ.h
Normal file
22
app/src/au/svc/PREAUTH_INQ.h
Normal 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 */
|
||||
36
app/src/au/svc/PREAUTH_INQ.pgc
Normal file
36
app/src/au/svc/PREAUTH_INQ.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/RISK_SCORE_INQ.h
Normal file
22
app/src/au/svc/RISK_SCORE_INQ.h
Normal 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 */
|
||||
54
app/src/au/svc/RISK_SCORE_INQ.pgc
Normal file
54
app/src/au/svc/RISK_SCORE_INQ.pgc
Normal 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);
|
||||
}
|
||||
22
app/src/au/svc/STANDIN_INQ.h
Normal file
22
app/src/au/svc/STANDIN_INQ.h
Normal 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
Loading…
Add table
Add a link
Reference in a new issue