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

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

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

View file

@ -0,0 +1,59 @@
/*
* st_feecalc_batch.pgc - st 수수료 계산 배치 (커서 + XA).
*
* 당일 정산 각 건에 대해 매입 금액 기준 구간요율(dbio)을 적용해 MDR 수수료를
* 계산하고 st_settle_dtl 에 정산 상세를 (dbio 로) 기록한다 (ONE global XA tx).
* 사용법: st_feecalc_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
#include "acq_common.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long rows = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_sid, h_pid, h_amount;
EXEC SQL END DECLARE SECTION;
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 sfc CURSOR FOR
SELECT s.settlement_id, s.purchase_id, s.merchant_id, p.amount
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
WHERE s.biz_date = :h_bizdate
ORDER BY s.settlement_id;
EXEC SQL OPEN sfc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
long bps, fee;
EXEC SQL FETCH sfc INTO :h_sid, :h_pid, :h_merch, :h_amount;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE sfc; tpabort(0); return 1; }
bps = stdb_lookup_rate("MDR", h_amount);
if (bps < 0) { EXEC SQL CLOSE sfc; tpabort(0); return 1; }
fee = acq_fee(h_amount, bps);
if (stdb_insert_fee_dtl(h_sid, h_pid, h_merch, "MDR", fee, bizdate) < 0) {
EXEC SQL CLOSE sfc; tpabort(0); return 1;
}
rows++;
}
EXEC SQL CLOSE sfc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> st_feecalc_batch COMMIT: bizdate=%s fee_rows=%ld\n", bizdate, rows);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,56 @@
/*
* st_settle_batch.pgc - st 가맹점 정산 네팅 배치 (GROUP BY 커서 + XA).
*
* ATMI client: tpinit/tpopen 로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx 를
* 시작한 뒤, 당일 정산(settlement)을 가맹점별로 GROUP BY 하여 net 합계를 구하고
* st_merch_settle 에 upsert(dbio) 한다. 마지막 tpcommit 이 XA 2PC 구동.
* 사용법: st_settle_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "st_dbio.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long rows = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_merch[64];
long h_cnt, h_gross, h_fee, h_net;
EXEC SQL END DECLARE SECTION;
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 ssc CURSOR FOR
SELECT s.merchant_id, count(*), coalesce(sum(p.amount),0),
coalesce(sum(p.fee),0), coalesce(sum(s.net),0)
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
WHERE s.biz_date = :h_bizdate
GROUP BY s.merchant_id ORDER BY s.merchant_id;
EXEC SQL OPEN ssc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH ssc INTO :h_merch, :h_cnt, :h_gross, :h_fee, :h_net;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE ssc; tpabort(0); return 1; }
if (stdb_upsert_merch_settle(h_merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) {
EXEC SQL CLOSE ssc; tpabort(0); return 1;
}
rows++;
}
EXEC SQL CLOSE ssc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> st_settle_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows);
tpclose(); tpterm();
return 0;
}

25
app/src/st/dbio/st_dbio.h Normal file
View file

@ -0,0 +1,25 @@
/*
* st_dbio.h - st (/) DB (libstdbio.a).
* ECPG (EXEC SQL) st_svr / st .
* XA / EXEC SQL CONNECT .
* : 0 = ok, -1 = SQL error ( ID/rate ).
*/
#ifndef ST_DBIO_H
#define ST_DBIO_H
/* 정산 상세 / 지급 / 가맹점 집계 (st_settle_dbio.pgc) */
long stdb_insert_fee_dtl(long sid, long pid, const char *merch,
const char *fee_type, long amount, const char *bizdate);
long stdb_insert_payment(const char *merch, long net, const char *paydate,
const char *status, const char *bizdate);
int stdb_update_payment_status(const char *merch, const char *from_status,
const char *to_status, const char *bizdate);
int stdb_upsert_merch_settle(const char *merch, const char *bizdate, long gross,
long fee, long net, long cnt);
/* 수수료 요율 / 조정 (st_fee_dbio.pgc) */
long stdb_lookup_rate(const char *fee_type, long amount); /* 구간 요율(bps), -1=오류 */
long stdb_insert_adjust(long sid, long pid, long delta, const char *reason,
const char *bizdate);
#endif /* ST_DBIO_H */

View file

@ -0,0 +1,51 @@
/*
* st_fee_dbio.pgc - 수수료 구간요율 조회 + 정산 조정 DB 접근 (libstdbio.a).
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
*/
#include <string.h>
#include <userlog.h>
#include "st_dbio.h"
/* 금액에 해당하는 최상위 구간 요율(bps)을 반환. 없으면 0, SQL 오류면 -1. */
long stdb_lookup_rate(const char *fee_type, long amount)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_bps, h_amt = amount;
char h_type[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_type, fee_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
EXEC SQL SELECT rate_bps INTO :h_bps FROM st_fee_rate
WHERE fee_type = :h_type AND band_min <= :h_amt
ORDER BY band_min DESC LIMIT 1;
if (sqlca.sqlcode == 100) return 0;
if (sqlca.sqlcode < 0) {
userlog("stdb_lookup_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_bps;
}
long stdb_insert_adjust(long sid, long pid, long delta, const char *reason,
const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_sid = sid, h_pid = pid, h_delta = delta;
char h_reason[128], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_reason, reason, sizeof(h_reason) - 1); h_reason[sizeof(h_reason)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('st_adj_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO st_adjust (adj_id, settlement_id, purchase_id, delta, reason, biz_date)
VALUES (:h_id, :h_sid, :h_pid, :h_delta, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("stdb_insert_adjust FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}

View file

@ -0,0 +1,105 @@
/*
* st_settle_dbio.pgc - 정산 상세/지급/가맹점 집계 DB 접근 (libstdbio.a).
* ECPG 함수; 호출자의 XA 브랜치에서 수행. EXEC SQL CONNECT 없음.
*/
#include <string.h>
#include <userlog.h>
#include "st_dbio.h"
long stdb_insert_fee_dtl(long sid, long pid, const char *merch,
const char *fee_type, long amount, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_sid = sid, h_pid = pid, h_amt = amount;
char h_merch[64], h_type[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_type, fee_type, sizeof(h_type) - 1); h_type[sizeof(h_type)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('st_dtl_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO st_settle_dtl
(dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date)
VALUES (:h_id, :h_sid, :h_pid, :h_merch, :h_type, :h_amt, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("stdb_insert_fee_dtl FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long stdb_insert_payment(const char *merch, long net, const char *paydate,
const char *status, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_net = net;
char h_merch[64], h_paydate[16], h_status[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_paydate, paydate, sizeof(h_paydate) - 1); h_paydate[sizeof(h_paydate)-1] = 0;
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('st_pay_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date)
VALUES (:h_id, :h_merch, :h_net, :h_paydate, :h_status, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("stdb_insert_payment FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
int stdb_update_payment_status(const char *merch, const char *from_status,
const char *to_status, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_from[16], h_to[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_from, from_status, sizeof(h_from) - 1); h_from[sizeof(h_from)-1] = 0;
strncpy(h_to, to_status, sizeof(h_to) - 1); h_to[sizeof(h_to)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL UPDATE st_payment SET status = :h_to
WHERE merchant_id = :h_merch AND status = :h_from AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) {
userlog("stdb_update_payment_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}
int stdb_upsert_merch_settle(const char *merch, const char *bizdate, long gross,
long fee, long net, long cnt)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_gross = gross, h_fee = fee, h_net = net, h_cnt = cnt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
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 INSERT INTO st_merch_settle
(merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount)
VALUES (:h_merch, :h_bizdate, :h_cnt, :h_gross, :h_fee, :h_net)
ON CONFLICT (merchant_id, biz_date) DO UPDATE
SET txn_count = st_merch_settle.txn_count + EXCLUDED.txn_count,
gross_amount = st_merch_settle.gross_amount + EXCLUDED.gross_amount,
fee_amount = st_merch_settle.fee_amount + EXCLUDED.fee_amount,
net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount,
updated_at = now();
if (sqlca.sqlcode < 0) {
userlog("stdb_upsert_merch_settle FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}

View file

@ -1,9 +1,16 @@
/*
* st_svr.pgc - st (정산/수수료) MODULE SERVER.
*
* Advertises the settlement services. SETTLE keeps the proven behavior: its own
* XA branch writes the settlement + settle ledger rows (the purchase status
* transition is owned by the ACQUIRE branch). Terminal service of the 매입 chain.
* ONE binary that tpadvertise()s ALL ~30 online st(정산/수수료) services — the
* real Enduro-X "one module server, many services" pattern, modeled on ac_svr.
* Every service is a distinct 정산/수수료 operation with genuine EXEC SQL (directly,
* or via the linked libstdbio.a dbio layer) plus fee math from libacqcommon.a.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch); there is NO
* EXEC SQL CONNECT. SETTLE keeps the proven behavior: its own XA branch writes
* the settlement + settle ledger rows (the purchase status transition is owned by
* the ACQUIRE branch). Terminal service of the 매입 chain. Owner-writes: st
* services never tpcall each other.
*/
#include <stdio.h>
#include <stdlib.h>
@ -13,7 +20,10 @@
#include <userlog.h>
#include <ndebug.h>
#include "acq.fd.h"
#include "acq_common.h"
#include "st_dbio.h"
/* ----- small UBF helpers ------------------------------------------------- */
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
@ -22,14 +32,40 @@ static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
void SETTLE(TPSVCINFO *p);
void ST_MDR(TPSVCINFO *p);
void ST_NETTING(TPSVCINFO *p);
void ST_VAT(TPSVCINFO *p);
/* ----- service prototypes ------------------------------------------------ */
void SETTLE(TPSVCINFO *p); void ST_MDR(TPSVCINFO *p);
void ST_NETTING(TPSVCINFO *p); void ST_VAT(TPSVCINFO *p);
void ST_VANFEE(TPSVCINFO *p); void ST_RELAYFEE(TPSVCINFO *p);
void ST_WHT(TPSVCINFO *p); void ST_ADJUST(TPSVCINFO *p);
void ST_ADVANCE(TPSVCINFO *p); void ST_DELAYINT(TPSVCINFO *p);
void ST_HOLD(TPSVCINFO *p); void ST_RELEASE(TPSVCINFO *p);
void ST_SPLIT(TPSVCINFO *p); void ST_PAYDATE(TPSVCINFO *p);
void ST_BYMERCH(TPSVCINFO *p); void ST_MONTHLY(TPSVCINFO *p);
void ST_OFFSET(TPSVCINFO *p); void ST_DIFFCHK(TPSVCINFO *p);
void ST_RECALC(TPSVCINFO *p); void ST_RATEAPPLY(TPSVCINFO *p);
void ST_REPORT(TPSVCINFO *p); void ST_XFERLINK(TPSVCINFO *p);
void ST_STATS(TPSVCINFO *p); void ST_CANCEL(TPSVCINFO *p);
void ST_LEDGERLINK(TPSVCINFO *p);void ST_BANDRATE(TPSVCINFO *p);
void ST_CAP(TPSVCINFO *p); void ST_FXCONV(TPSVCINFO *p);
void ST_CONFIRM(TPSVCINFO *p); void ST_STATUS(TPSVCINFO *p);
/* ----- advertise table --------------------------------------------------- */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"SETTLE", SETTLE}, {"ST_MDR", ST_MDR},
{"ST_NETTING", ST_NETTING}, {"ST_VAT", ST_VAT},
{"SETTLE", SETTLE}, {"ST_MDR", ST_MDR},
{"ST_NETTING", ST_NETTING}, {"ST_VAT", ST_VAT},
{"ST_VANFEE", ST_VANFEE}, {"ST_RELAYFEE", ST_RELAYFEE},
{"ST_WHT", ST_WHT}, {"ST_ADJUST", ST_ADJUST},
{"ST_ADVANCE", ST_ADVANCE}, {"ST_DELAYINT", ST_DELAYINT},
{"ST_HOLD", ST_HOLD}, {"ST_RELEASE", ST_RELEASE},
{"ST_SPLIT", ST_SPLIT}, {"ST_PAYDATE", ST_PAYDATE},
{"ST_BYMERCH", ST_BYMERCH}, {"ST_MONTHLY", ST_MONTHLY},
{"ST_OFFSET", ST_OFFSET}, {"ST_DIFFCHK", ST_DIFFCHK},
{"ST_RECALC", ST_RECALC}, {"ST_RATEAPPLY", ST_RATEAPPLY},
{"ST_REPORT", ST_REPORT}, {"ST_XFERLINK", ST_XFERLINK},
{"ST_STATS", ST_STATS}, {"ST_CANCEL", ST_CANCEL},
{"ST_LEDGERLINK", ST_LEDGERLINK},{"ST_BANDRATE", ST_BANDRATE},
{"ST_CAP", ST_CAP}, {"ST_FXCONV", ST_FXCONV},
{"ST_CONFIRM", ST_CONFIRM}, {"ST_STATUS", ST_STATUS},
{NULL, NULL}
};
@ -48,6 +84,9 @@ int tpsvrinit(int argc, char **argv)
void tpsvrdone(void) { tpclose(); userlog("st_svr: tpsvrdone"); }
/* ========================================================================= */
/* 1. SETTLE - 정산 + settle 원장 (own branch). 매입 체인 종단. (KEEP) */
/* ========================================================================= */
void SETTLE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
@ -79,7 +118,7 @@ void SETTLE(TPSVCINFO *p)
OK(b);
}
/* ST_MDR - MDR 수수료 원장 기록. */
/* 2. ST_MDR - MDR 수수료 원장 기록. (KEEP) */
void ST_MDR(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
@ -97,7 +136,7 @@ void ST_MDR(TPSVCINFO *p)
OK(b);
}
/* ST_NETTING - 당일 net 합계 조회. */
/* 3. ST_NETTING - 당일 net 합계 조회. (KEEP) */
void ST_NETTING(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
@ -117,7 +156,7 @@ void ST_NETTING(TPSVCINFO *p)
OK(b);
}
/* ST_VAT - 정산 부가세 원장 기록. */
/* 4. ST_VAT - 정산 부가세 원장 기록. (KEEP) */
void ST_VAT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
@ -136,4 +175,454 @@ void ST_VAT(TPSVCINFO *p)
OK(b);
}
/* 5. ST_VANFEE - VAN 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). */
void ST_VANFEE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
bps = stdb_lookup_rate("VAN", amount);
if (bps < 0) FAIL(b);
fee = acq_fee(amount, bps);
dtl = stdb_insert_fee_dtl(sid, pid, merch, "VAN", fee, bizdate);
if (dtl < 0) FAIL(b);
setl(b, T_FEE, fee);
OK(b);
}
/* 6. ST_RELAYFEE - 중계 수수료: 구간요율 조회 후 정산 상세 기록 (dbio). */
void ST_RELAYFEE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
bps = stdb_lookup_rate("RELAY", amount);
if (bps < 0) FAIL(b);
fee = acq_fee(amount, bps);
dtl = stdb_insert_fee_dtl(sid, pid, merch, "RELAY", fee, bizdate);
if (dtl < 0) FAIL(b);
setl(b, T_FEE, fee);
OK(b);
}
/* 7. ST_WHT - 원천징수(수수료의 22%) 정산 상세 기록 (dbio). */
void ST_WHT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long fee = getl(b, T_FEE), tax, dtl;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
tax = acq_wht_amt(fee, 2200);
dtl = stdb_insert_fee_dtl(sid, pid, merch, "WHT", tax, bizdate);
if (dtl < 0) FAIL(b);
setl(b, T_TAX, tax);
OK(b);
}
/* 8. ST_ADJUST - 정산 조정 (가감) 기록 (dbio). */
void ST_ADJUST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], reason[128];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long delta = getl(b, T_DELTA), adj;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "ADJ");
adj = stdb_insert_adjust(sid, pid, delta, reason, bizdate);
if (adj < 0) FAIL(b);
setl(b, T_ADJ, adj);
OK(b);
}
/* 9. ST_ADVANCE - 선지급 예약: 지급 레코드(SCHEDULED) 생성 (dbio). */
void ST_ADVANCE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16], paydate[16];
long net = getl(b, T_NET), pay;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_KEY1, paydate, sizeof(paydate));
if (paydate[0] == 0) { strncpy(paydate, bizdate, sizeof(paydate)-1); paydate[sizeof(paydate)-1] = 0; }
pay = stdb_insert_payment(merch, net, paydate, "SCHEDULED", bizdate);
if (pay < 0) FAIL(b);
setl(b, T_ID1, pay);
OK(b);
}
/* 10. ST_DELAYINT - 지연이자(net * bps/10000 * 일수) 정산 상세 기록 (dbio). */
void ST_DELAYINT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long net = getl(b, T_NET), days = getl(b, T_ARG1), bps = getl(b, T_ARG2), intr, dtl;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (days <= 0) days = 1;
if (bps <= 0) bps = 15; /* 일 0.15% 기본 */
intr = acq_fee(net, bps) * days;
dtl = stdb_insert_fee_dtl(sid, pid, merch, "DELAYINT", intr, bizdate);
if (dtl < 0) FAIL(b);
setl(b, T_AMOUNT, intr);
OK(b);
}
/* 11. ST_HOLD - 가맹점 지급 보류 (SCHEDULED -> HELD) (dbio). */
void ST_HOLD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (stdb_update_payment_status(merch, "SCHEDULED", "HELD", bizdate) < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "H", 0L);
OK(b);
}
/* 12. ST_RELEASE - 지급 보류 해제 (HELD -> SCHEDULED) (dbio). */
void ST_RELEASE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (stdb_update_payment_status(merch, "HELD", "SCHEDULED", bizdate) < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "R", 0L);
OK(b);
}
/* 13. ST_SPLIT - 분할 지급: net 을 n 회로 나눠 지급 레코드 생성 (dbio 루프). */
void ST_SPLIT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long net = getl(b, T_NET), n = getl(b, T_INSTALL_N), part, i, pay;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (n < 1) n = 2;
part = net / n;
for (i = 0; i < n; i++) {
pay = stdb_insert_payment(merch, part, bizdate, "SCHEDULED", bizdate);
if (pay < 0) FAIL(b);
}
setl(b, T_COUNT, n);
OK(b);
}
/* 14. ST_PAYDATE - 지급일 산정(익영업일) 후 지급일 갱신. */
void ST_PAYDATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16], paydate[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_merch[64], h_bizdate[16], h_paydate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (acq_next_bizday(bizdate, paydate) < 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;
strncpy(h_paydate, paydate, sizeof(h_paydate)-1); h_paydate[sizeof(h_paydate)-1] = 0;
EXEC SQL UPDATE st_payment SET pay_date = :h_paydate
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED';
if (sqlca.sqlcode < 0) FAIL(b);
Bchg(b, T_KEY1, 0, paydate, 0L);
OK(b);
}
/* 15. ST_BYMERCH - 가맹점별 정산 집계 upsert (dbio). */
void ST_BYMERCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_gross, h_fee, h_net;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(p.amount),0), coalesce(sum(p.fee),0), coalesce(sum(s.net),0)
INTO :h_cnt, :h_gross, :h_fee, :h_net
FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id
WHERE s.merchant_id = :h_merch AND s.biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (stdb_upsert_merch_settle(merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) FAIL(b);
setl(b, T_COUNT, h_cnt); setl(b, T_GROSS, h_gross);
OK(b);
}
/* 16. ST_MONTHLY - 월별 정산 net 합계 (biz_date LIKE 'YYYY-MM%'). */
void ST_MONTHLY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], ym[16], like[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_sum;
char h_merch[64], h_like[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_KEY1, ym, sizeof(ym)); if (ym[0] == 0) strcpy(ym, "2026-07");
snprintf(like, sizeof(like), "%s%%", ym);
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0;
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_sum FROM settlement
WHERE merchant_id = :h_merch AND to_char(biz_date, 'YYYY-MM-DD') LIKE :h_like;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_GROSS, h_sum);
OK(b);
}
/* 17. ST_OFFSET - 상계: 음수 조정으로 채권 상계 기록 (dbio). */
void ST_OFFSET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long amt = getl(b, T_AMOUNT), adj;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
adj = stdb_insert_adjust(sid, pid, -amt, "OFFSET", bizdate);
if (adj < 0) FAIL(b);
setl(b, T_ADJ, adj);
OK(b);
}
/* 18. ST_DIFFCHK - 정산 net vs 매입 net 차이 점검. */
void ST_DIFFCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_snet, h_pnet, h_diff;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
h_diff = h_pnet - h_snet;
setl(b, T_DELTA, h_diff);
setl(b, T_RC, h_diff == 0 ? 0 : 1);
OK(b);
}
/* 19. ST_RECALC - 매입 수수료 기준 정산 net 재계산 + 갱신. */
void ST_RECALC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_amount, h_fee, h_net;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT amount, fee INTO :h_amount, :h_fee FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
h_net = h_amount - h_fee;
EXEC SQL UPDATE settlement SET net = :h_net WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_NET, h_net);
OK(b);
}
/* 20. ST_RATEAPPLY - 구간요율 적용 수수료 계산 + 정산 상세 기록 (dbio). */
void ST_RATEAPPLY(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16], ftype[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long amount = getl(b, T_AMOUNT), bps, fee, dtl;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR");
bps = stdb_lookup_rate(ftype, amount);
if (bps < 0) FAIL(b);
fee = acq_fee(amount, bps);
dtl = stdb_insert_fee_dtl(sid, pid, merch, ftype, fee, bizdate);
if (dtl < 0) FAIL(b);
setl(b, T_FEE, fee);
setl(b, T_RC, bps);
OK(b);
}
/* 21. ST_REPORT - 당일 정산 건수/합계 리포트. */
void ST_REPORT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_sum;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum
FROM settlement WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
OK(b);
}
/* 22. ST_XFERLINK - 이체 연계 원장 기록. */
void ST_XFERLINK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
VALUES (nextval('ledger_seq'), :h_pid, 'ST_XFER', :h_net, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
OK(b);
}
/* 23. ST_STATS - 정산 통계(건수/net 합계) 조회. */
void ST_STATS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_sum, h_max;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT count(*), coalesce(sum(net),0), coalesce(max(net),0)
INTO :h_cnt, :h_sum, :h_max FROM settlement WHERE biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_GROSS, h_sum);
setl(b, T_AMT1, h_max);
OK(b);
}
/* 24. ST_CANCEL - 정산 취소: 상태 CANCELLED + 취소 조정 (dbio). */
void ST_CANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long adj;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
EXEC SQL SELECT coalesce(sum(net),0) INTO :h_net FROM settlement WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE settlement SET status = 'CANCELLED' WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
adj = stdb_insert_adjust(0, h_pid, -h_net, "SETTLE_CANCEL", bizdate);
if (adj < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "C", 0L);
OK(b);
}
/* 25. ST_LEDGERLINK - 정산 연계 원장 기록. */
void ST_LEDGERLINK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_net;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID); h_net = getl(b, T_NET);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
VALUES (nextval('ledger_seq'), :h_pid, 'SETTLELINK', :h_net, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
OK(b);
}
/* 26. ST_BANDRATE - 금액 구간 요율(bps) 조회 (dbio). */
void ST_BANDRATE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char ftype[16];
long amount = getl(b, T_AMOUNT), bps;
gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR");
bps = stdb_lookup_rate(ftype, amount);
if (bps < 0) FAIL(b);
setl(b, T_RC, bps);
OK(b);
}
/* 27. ST_CAP - 수수료 상한 적용: 상한 초과분을 감액 조정 (dbio). */
void ST_CAP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID);
long fee = getl(b, T_FEE), cap = getl(b, T_ARG1), over, adj;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (cap <= 0) cap = 100000; /* 기본 상한 */
if (fee > cap) {
over = fee - cap;
adj = stdb_insert_adjust(sid, pid, -over, "FEE_CAP", bizdate);
if (adj < 0) FAIL(b);
setl(b, T_FEE, cap);
setl(b, T_DELTA, -over);
} else {
setl(b, T_DELTA, 0);
}
OK(b);
}
/* 28. ST_FXCONV - 외화 환산: fx * rate/100 = KRW. */
void ST_FXCONV(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long fx = getl(b, T_FX_AMT), rate = getl(b, T_ARG1), krw;
if (rate <= 0) rate = 1350; /* 13.50 KRW/unit (bps of 100) */
krw = fx * rate / 100;
setl(b, T_AMOUNT, krw);
OK(b);
}
/* 29. ST_CONFIRM - 정산 확정 (SETTLED -> CONFIRMED). */
void ST_CONFIRM(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL UPDATE settlement SET status = 'CONFIRMED'
WHERE purchase_id = :h_pid AND status = 'SETTLED';
if (sqlca.sqlcode < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "C", 0L);
OK(b);
}
/* 30. ST_STATUS - 정산 상태 조회. */
void ST_STATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_status[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT status INTO :h_status FROM settlement
WHERE purchase_id = :h_pid ORDER BY settlement_id DESC LIMIT 1;
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlcode == 100) strcpy(h_status, "NONE");
Bchg(b, T_STATUS, 0, h_status, 0L);
OK(b);
}
/* vim: set ts=4 sw=4 et smartindent: */