Phase 2a: 모듈서버 패턴 + 재사용 빌드시스템 (ac 모듈 38서비스 실동작)

- ac_svr(30 매입서비스 advertise) + rc_svr/st_svr, dbio 정적라이브러리, common 라이브러리
- app/build.sh: 모듈 자동발견(app/src/<mod>/<mod>_svr.pgc) → ecpg+buildserver+ndrxconfig 생성
- ac 배치 2종, 스키마 9테이블, 검증: 41서비스 AVAIL, 체인·배치 psql 커밋, 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:37:17 +00:00
parent e6e29e6b5d
commit 05a0ff3e56
28 changed files with 1964 additions and 410 deletions

687
app/src/ac/ac_svr.pgc Normal file
View file

@ -0,0 +1,687 @@
/*
* ac_svr.pgc - ac (매입) MODULE SERVER.
*
* ONE binary that tpadvertise()s ALL ~30 online ac services (the real Tuxedo
* "one module server, many services" pattern). Every service is a distinct 매입
* operation with genuine EXEC SQL (directly, or via the linked libacdbio.a dbio
* layer) and common math from libacqcommon.a.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
* there is NO EXEC SQL CONNECT. Each tpcall runs on this process's XA branch, so
* a service sees its OWN uncommitted writes but NOT a sibling branch's (see the
* ACQUIRE->RECONCILE->SETTLE chain, which crosses ac_svr/rc_svr/st_svr).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include <ubf.h>
#include <userlog.h>
#include <ndebug.h>
#include "acq.fd.h"
#include "ac_dbio.h"
#include "acq_common.h"
/* ----- small UBF helpers ------------------------------------------------- */
static long getl(UBFH *b, BFLDID f) { long v = 0; Bget(b, f, 0, (char *)&v, 0L); return v; }
static void gets_(UBFH *b, BFLDID f, char *out, int cap)
{ BFLDLEN l = cap; out[0] = 0; Bget(b, f, 0, out, &l); }
static void setl(UBFH *b, BFLDID f, long v) { Bchg(b, f, 0, (char *)&v, 0L); }
#define FAIL(b) do { tpreturn(TPFAIL, 0, (char *)(b), 0L, 0L); return; } while (0)
#define OK(b) do { tpreturn(TPSUCCESS, 0, (char *)(b), 0L, 0L); return; } while (0)
/* ----- service prototypes ------------------------------------------------ */
void ACQUIRE(TPSVCINFO *p); void ACQ_DDC(TPSVCINFO *p);
void ACQ_EDI(TPSVCINFO *p); void ACQ_EDC(TPSVCINFO *p);
void ACQ_CANCEL(TPSVCINFO *p); void ACQ_CORRECT(TPSVCINFO *p);
void ACQ_PARTIAL(TPSVCINFO *p); void ACQ_INSTALL(TPSVCINFO *p);
void ACQ_FOREIGN(TPSVCINFO *p); void ACQ_UNMATCH(TPSVCINFO *p);
void ACQ_REPROC(TPSVCINFO *p); void ACQ_DUPCHK(TPSVCINFO *p);
void ACQ_LIMITCHK(TPSVCINFO *p); void ACQ_FEEADJ(TPSVCINFO *p);
void ACQ_STATUS(TPSVCINFO *p); void ACQ_MERCHSUM(TPSVCINFO *p);
void ACQ_ISSUERCLS(TPSVCINFO *p);void ACQ_WHT(TPSVCINFO *p);
void ACQ_TAXINV(TPSVCINFO *p); void ACQ_APPRLINK(TPSVCINFO *p);
void ACQ_DATECHG(TPSVCINFO *p); void ACQ_AMTFIX(TPSVCINFO *p);
void ACQ_RECLASS(TPSVCINFO *p); void ACQ_XFERLINK(TPSVCINFO *p);
void ACQ_APPRMAP(TPSVCINFO *p); void ACQ_SETTLELINK(TPSVCINFO *p);
void ACQ_HOLD(TPSVCINFO *p); void ACQ_RELEASE(TPSVCINFO *p);
void ACQ_VOID(TPSVCINFO *p); void ACQ_REFUND(TPSVCINFO *p);
/* ----- advertise table (this is what "scales to 30" cleanly) ------------- */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"ACQUIRE", ACQUIRE}, {"ACQ_DDC", ACQ_DDC},
{"ACQ_EDI", ACQ_EDI}, {"ACQ_EDC", ACQ_EDC},
{"ACQ_CANCEL", ACQ_CANCEL}, {"ACQ_CORRECT", ACQ_CORRECT},
{"ACQ_PARTIAL", ACQ_PARTIAL}, {"ACQ_INSTALL", ACQ_INSTALL},
{"ACQ_FOREIGN", ACQ_FOREIGN}, {"ACQ_UNMATCH", ACQ_UNMATCH},
{"ACQ_REPROC", ACQ_REPROC}, {"ACQ_DUPCHK", ACQ_DUPCHK},
{"ACQ_LIMITCHK", ACQ_LIMITCHK},{"ACQ_FEEADJ", ACQ_FEEADJ},
{"ACQ_STATUS", ACQ_STATUS}, {"ACQ_MERCHSUM", ACQ_MERCHSUM},
{"ACQ_ISSUERCLS", ACQ_ISSUERCLS},{"ACQ_WHT", ACQ_WHT},
{"ACQ_TAXINV", ACQ_TAXINV}, {"ACQ_APPRLINK", ACQ_APPRLINK},
{"ACQ_DATECHG", ACQ_DATECHG}, {"ACQ_AMTFIX", ACQ_AMTFIX},
{"ACQ_RECLASS", ACQ_RECLASS}, {"ACQ_XFERLINK", ACQ_XFERLINK},
{"ACQ_APPRMAP", ACQ_APPRMAP}, {"ACQ_SETTLELINK", ACQ_SETTLELINK},
{"ACQ_HOLD", ACQ_HOLD}, {"ACQ_RELEASE", ACQ_RELEASE},
{"ACQ_VOID", ACQ_VOID}, {"ACQ_REFUND", ACQ_REFUND},
{NULL, NULL}
};
int tpsvrinit(int argc, char **argv)
{
int i;
if (tpopen() < 0) {
userlog("ac_svr: tpopen FAIL: %s", tpstrerror(tperrno));
return -1;
}
for (i = 0; SVCS[i].name != NULL; i++) {
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
userlog("ac_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
return -1;
}
}
userlog("ac_svr: %d ac services advertised, RM opened", i);
return 0;
}
void tpsvrdone(void)
{
tpclose();
userlog("ac_svr: tpsvrdone");
}
/* ========================================================================= */
/* 1. ACQUIRE - 매입 접수 + 승인, then the XA chain RECONCILE->SETTLE. */
/* ========================================================================= */
void ACQUIRE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long amount, bps = 25, fee, net, pid, rlen = 0;
gets_(b, T_MERCHANT, merch, sizeof(merch));
if (merch[0] == 0) { userlog("ACQUIRE: missing T_MERCHANT"); FAIL(b); }
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (acdb_merchant_mdr(merch, &bps) < 0) bps = 25; /* keep 25bps default */
fee = acq_fee(amount, bps);
net = amount - fee;
pid = acdb_next_purchase_id();
if (pid < 0) FAIL(b);
if (acdb_insert_purchase(pid, merch, amount, fee, net, "A", "POS", bizdate) < 0) FAIL(b);
if (acdb_insert_approval(pid, 1) < 0) FAIL(b);
setl(b, T_PURCHASE_ID, pid);
setl(b, T_FEE, fee);
setl(b, T_NET, net);
Bchg(b, T_STATUS, 0, "A", 0L);
userlog("ACQUIRE pid=%ld merch=%s amount=%ld fee=%ld net=%ld -> RECONCILE",
pid, merch, amount, fee, net);
/* continue the SAME global tx into rc_svr (distinct branch) */
if (tpcall("RECONCILE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
userlog("ACQUIRE: tpcall(RECONCILE) FAIL: %s", tpstrerror(tperrno));
FAIL(b);
}
/* our branch resumes: settle-complete the purchase we own */
if (acdb_update_status(pid, "S") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "S", 0L);
OK(b);
}
/* 2. ACQ_DDC - Dynamic Currency Conversion: record fx leg for a purchase. */
void ACQ_DDC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], ccy[8];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_fx, h_krw, h_rate;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_fx = getl(b, T_FX_AMT);
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_rate = 1350; /* 13.50 KRW per unit (bps of 100) */
h_krw = h_fx * h_rate / 100;
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_fx_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date)
VALUES (:h_id, :h_pid, :h_ccy, :h_fx, :h_krw, :h_rate, :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("ACQ_DDC INSERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
setl(b, T_AMOUNT, h_krw);
OK(b);
}
/* 3. ACQ_EDI - EDI 청구 문서 접수. */
void ACQ_EDI(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], docno[32], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_amount;
char h_merch[64], h_doc[32], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_DOCNO, docno, sizeof(docno)); if (docno[0] == 0) strcpy(docno, "EDI-AUTO");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_amount = getl(b, T_AMOUNT);
strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0;
strncpy(h_doc, docno, sizeof(h_doc)-1); h_doc[sizeof(h_doc)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_edi_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_edi (edi_id, merchant_id, doc_no, amount, biz_date)
VALUES (:h_id, :h_merch, :h_doc, :h_amount, :h_bizdate);
if (sqlca.sqlcode < 0) { userlog("ACQ_EDI INSERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
OK(b);
}
/* 4. ACQ_EDC - EDC 단말 매입 접수 (채널=EDC, 체인 없음). */
void ACQ_EDC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long amount, bps = 25, fee, net, pid;
gets_(b, T_MERCHANT, merch, sizeof(merch));
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (acdb_merchant_mdr(merch, &bps) < 0) bps = 25;
fee = acq_fee(amount, bps); net = amount - fee;
pid = acdb_next_purchase_id();
if (pid < 0) FAIL(b);
if (acdb_insert_purchase(pid, merch, amount, fee, net, "A", "EDC", bizdate) < 0) FAIL(b);
if (acdb_insert_approval(pid, 1) < 0) FAIL(b);
setl(b, T_PURCHASE_ID, pid); setl(b, T_FEE, fee); setl(b, T_NET, net);
OK(b);
}
/* 5. ACQ_CANCEL - 전체 취소. */
void ACQ_CANCEL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_amt;
char h_bizdate[16], h_reason[128];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
h_amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "CUSTOMER");
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_pid, 'FULL', :h_amt, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (acdb_update_status(h_pid, "C") < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
Bchg(b, T_STATUS, 0, "C", 0L);
OK(b);
}
/* 6. ACQ_CORRECT - 금액 정정 + 수수료 재계산. */
void ACQ_CORRECT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long pid, amount, bps = 25, fee, net;
pid = getl(b, T_PURCHASE_ID); amount = getl(b, T_AMOUNT);
gets_(b, T_MERCHANT, merch, sizeof(merch));
if (merch[0] && acdb_merchant_mdr(merch, &bps) < 0) bps = 25;
fee = acq_fee(amount, bps); net = amount - fee;
if (acdb_update_amount(pid, amount, fee, net) < 0) FAIL(b);
setl(b, T_FEE, fee); setl(b, T_NET, net);
OK(b);
}
/* 7. ACQ_PARTIAL - 부분 취소. */
void ACQ_PARTIAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_amt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID); h_amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_pid, 'PARTIAL', :h_amt, 'PARTIAL', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
OK(b);
}
/* 8. ACQ_INSTALL - 할부 개월 분할 등록. */
void ACQ_INSTALL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long months, amount, i;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_seq, h_months, h_mamt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
months = getl(b, T_INSTALL_N); if (months < 1) months = 3;
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
h_months = months; h_mamt = amount / months;
for (i = 1; i <= months; i++) {
h_seq = i;
EXEC SQL SELECT nextval('ac_install_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_install (install_id, purchase_id, seq_no, months, month_amount, biz_date)
VALUES (:h_id, :h_pid, :h_seq, :h_months, :h_mamt, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
}
setl(b, T_COUNT, months);
OK(b);
}
/* 9. ACQ_FOREIGN - 해외 매입 (채널=FOREIGN) + fx leg. */
void ACQ_FOREIGN(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16], ccy[8];
long amount, bps = 30, fee, net, pid;
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_fx, h_krw, h_rate;
char h_ccy[8], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
amount = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_CCY, ccy, sizeof(ccy)); if (ccy[0] == 0) strcpy(ccy, "USD");
if (acdb_merchant_mdr(merch, &bps) < 0) bps = 30;
fee = acq_fee(amount, bps); net = amount - fee;
pid = acdb_next_purchase_id();
if (pid < 0) FAIL(b);
if (acdb_insert_purchase(pid, merch, amount, fee, net, "A", "FOREIGN", bizdate) < 0) FAIL(b);
h_pid = pid; h_fx = amount; h_rate = 1350; h_krw = amount;
strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_fx_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date)
VALUES (:h_id, :h_pid, :h_ccy, :h_fx, :h_krw, :h_rate, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_PURCHASE_ID, pid); setl(b, T_FEE, fee);
OK(b);
}
/* 10. ACQ_UNMATCH - 미매입(원장 없는 매입) 추출. */
void ACQ_UNMATCH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_cnt;
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 INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date)
SELECT nextval('ac_unmatch_seq'), p.purchase_id, 'NO_LEDGER', p.biz_date
FROM purchase p
WHERE p.biz_date = :h_bizdate
AND NOT EXISTS (SELECT 1 FROM ledger l WHERE l.purchase_id = p.purchase_id)
AND NOT EXISTS (SELECT 1 FROM ac_unmatch u WHERE u.purchase_id = p.purchase_id);
if (sqlca.sqlcode < 0) { userlog("ACQ_UNMATCH FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
h_cnt = sqlca.sqlerrd[2];
setl(b, T_COUNT, h_cnt);
OK(b);
}
/* 11. ACQ_REPROC - 재처리(상태 A로 되돌림). */
void ACQ_REPROC(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pid = getl(b, T_PURCHASE_ID);
if (acdb_update_status(pid, "A") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 12. ACQ_DUPCHK - 중복 매입 건수 조회. */
void ACQ_DUPCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_cnt, h_amt;
char h_merch[64], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_amt = getl(b, T_AMOUNT);
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(*) INTO :h_cnt FROM purchase
WHERE merchant_id = :h_merch AND amount = :h_amt AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
OK(b);
}
/* 13. ACQ_LIMITCHK - 가맹점 일한도 점검. */
void ACQ_LIMITCHK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_sum, h_lim;
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 coalesce(sum(amount),0) INTO :h_sum FROM purchase
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
setl(b, T_GROSS, h_sum);
setl(b, T_RC, h_sum > h_lim ? 1 : 0);
OK(b);
}
/* 14. ACQ_FEEADJ - 수수료 조정 (dbio) + purchase 반영. */
void ACQ_FEEADJ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], reason[128];
long pid, delta, adj;
EXEC SQL BEGIN DECLARE SECTION;
long e_pid, e_delta;
EXEC SQL END DECLARE SECTION;
pid = getl(b, T_PURCHASE_ID);
delta = getl(b, T_DELTA);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "ADJ");
adj = acdb_insert_fee_adj(pid, delta, reason, bizdate);
if (adj < 0) FAIL(b);
e_pid = pid; e_delta = delta;
EXEC SQL UPDATE purchase SET fee = fee + :e_delta, net = net - :e_delta, updated_at = now()
WHERE purchase_id = :e_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
setl(b, T_ADJ, adj);
OK(b);
}
/* 15. ACQ_STATUS - 매입 상태 조회 (dbio). */
void ACQ_STATUS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pid = getl(b, T_PURCHASE_ID);
char st[4];
if (acdb_get_status(pid, st) < 0) FAIL(b);
Bchg(b, T_STATUS, 0, st, 0L);
OK(b);
}
/* 16. ACQ_MERCHSUM - 단일 가맹점 당일 집계 upsert (dbio). */
void ACQ_MERCHSUM(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(amount),0), coalesce(sum(fee),0), coalesce(sum(net),0)
INTO :h_cnt, :h_gross, :h_fee, :h_net FROM purchase
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) FAIL(b);
if (acdb_upsert_merch_sum(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);
}
/* 17. ACQ_ISSUERCLS - 발급사 분류 갱신. */
void ACQ_ISSUERCLS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char issuer[32];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_issuer[32];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_ISSUER, issuer, sizeof(issuer)); if (issuer[0] == 0) strcpy(issuer, "BC");
strncpy(h_issuer, issuer, sizeof(h_issuer)-1); h_issuer[sizeof(h_issuer)-1] = 0;
EXEC SQL UPDATE purchase SET issuer = :h_issuer, updated_at = now() WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
OK(b);
}
/* 18. ACQ_WHT - 원천징수 (수수료의 22%). */
void ACQ_WHT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long pid, tax, wht_id;
EXEC SQL BEGIN DECLARE SECTION;
long e_pid, e_fee;
EXEC SQL END DECLARE SECTION;
pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
e_pid = pid;
EXEC SQL SELECT fee INTO :e_fee FROM purchase WHERE purchase_id = :e_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
tax = acq_wht_amt(e_fee, 2200); /* 22% */
wht_id = acdb_insert_wht(pid, tax, bizdate);
if (wht_id < 0) FAIL(b);
setl(b, T_TAX, tax);
OK(b);
}
/* 19. ACQ_TAXINV - 세금계산서 발행 (부가세 10%). */
void ACQ_TAXINV(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64], bizdate[16];
long pid, supply, vat, inv;
pid = getl(b, T_PURCHASE_ID);
supply = getl(b, T_AMOUNT);
gets_(b, T_MERCHANT, merch, sizeof(merch));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
vat = acq_vat(supply);
inv = acdb_insert_tax_invoice(pid, merch, supply, vat, bizdate);
if (inv < 0) FAIL(b);
setl(b, T_TAX, vat);
OK(b);
}
/* 20. ACQ_APPRLINK - 승인 연계 기록. */
void ACQ_APPRLINK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pid = getl(b, T_PURCHASE_ID);
if (acdb_insert_approval(pid, 1) < 0) FAIL(b);
OK(b);
}
/* 21. ACQ_DATECHG - 영업일 변경. */
void ACQ_DATECHG(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL UPDATE purchase SET biz_date = :h_bizdate, updated_at = now() WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
OK(b);
}
/* 22. ACQ_AMTFIX - 금액 확정 정정 (절대값). */
void ACQ_AMTFIX(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char merch[64];
long pid, amount, bps = 25, fee, net;
pid = getl(b, T_PURCHASE_ID); amount = getl(b, T_AMOUNT);
gets_(b, T_MERCHANT, merch, sizeof(merch));
if (merch[0] && acdb_merchant_mdr(merch, &bps) < 0) bps = 25;
fee = acq_fee(amount, bps); net = amount - fee;
if (acdb_update_amount(pid, amount, fee, net) < 0) FAIL(b);
setl(b, T_FEE, fee); setl(b, T_NET, net);
OK(b);
}
/* 23. ACQ_RECLASS - 업종/분류 재지정. */
void ACQ_RECLASS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char cat[32];
EXEC SQL BEGIN DECLARE SECTION;
long h_pid;
char h_cat[32];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
gets_(b, T_CATEGORY, cat, sizeof(cat)); if (cat[0] == 0) strcpy(cat, "GEN");
strncpy(h_cat, cat, sizeof(h_cat)-1); h_cat[sizeof(h_cat)-1] = 0;
EXEC SQL UPDATE purchase SET category = :h_cat, updated_at = now() WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b);
OK(b);
}
/* 24. ACQ_XFERLINK - 이체 연계 원장 기록. */
void ACQ_XFERLINK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long pid = getl(b, T_PURCHASE_ID), amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (acdb_insert_ledger(pid, "XFER", amt, bizdate) < 0) FAIL(b);
OK(b);
}
/* 25. ACQ_APPRMAP - 승인 매핑 건수 조회. */
void ACQ_APPRMAP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
EXEC SQL BEGIN DECLARE SECTION;
long h_pid, h_cnt;
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID);
EXEC SQL SELECT count(*) INTO :h_cnt FROM approval WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
OK(b);
}
/* 26. ACQ_SETTLELINK - 정산 연계 원장 기록. */
void ACQ_SETTLELINK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long pid = getl(b, T_PURCHASE_ID), net = getl(b, T_NET);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
if (acdb_insert_ledger(pid, "SETTLELINK", net, bizdate) < 0) FAIL(b);
OK(b);
}
/* 27. ACQ_HOLD - 매입 보류. */
void ACQ_HOLD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pid = getl(b, T_PURCHASE_ID);
if (acdb_update_status(pid, "H") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "H", 0L);
OK(b);
}
/* 28. ACQ_RELEASE - 보류 해제. */
void ACQ_RELEASE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long pid = getl(b, T_PURCHASE_ID);
if (acdb_update_status(pid, "A") < 0) FAIL(b);
Bchg(b, T_STATUS, 0, "A", 0L);
OK(b);
}
/* 29. ACQ_VOID - 매입 무효화 (당일 취소). */
void ACQ_VOID(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_amt;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID); h_amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_pid, 'VOID', :h_amt, 'VOID', :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
if (acdb_update_status(h_pid, "V") < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
Bchg(b, T_STATUS, 0, "V", 0L);
OK(b);
}
/* 30. ACQ_REFUND - 환불 기록. */
void ACQ_REFUND(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16], reason[128];
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid, h_amt;
char h_bizdate[16], h_reason[128];
EXEC SQL END DECLARE SECTION;
h_pid = getl(b, T_PURCHASE_ID); h_amt = getl(b, T_AMOUNT);
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "REFUND");
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
VALUES (:h_id, :h_pid, 'REFUND', :h_amt, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_CANCEL_ID, h_id);
OK(b);
}
/* vim: set ts=4 sw=4 et smartindent: */

View file

@ -0,0 +1,56 @@
/*
* ac_merchsum_batch.pgc - ac 가맹점 일집계 배치 (커서 + XA).
*
* ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global
* transaction, an EXEC SQL cursor aggregates purchase rows per merchant for a
* biz_date and upserts merchant_summary (via the linked dbio lib), then tpcommit
* drives XA 2PC. Usage: ac_merchsum_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_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];
char 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 msc CURSOR FOR
SELECT merchant_id, count(*), coalesce(sum(amount),0),
coalesce(sum(fee),0), coalesce(sum(net),0)
FROM purchase WHERE biz_date = :h_bizdate
GROUP BY merchant_id ORDER BY merchant_id;
EXEC SQL OPEN msc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH msc 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 msc; tpabort(0); return 1; }
if (acdb_upsert_merch_sum(h_merch, bizdate, h_gross, h_fee, h_net, h_cnt) < 0) {
EXEC SQL CLOSE msc; tpabort(0); return 1;
}
rows++;
}
EXEC SQL CLOSE msc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_merchsum_batch COMMIT: bizdate=%s merchants=%ld\n", bizdate, rows);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,55 @@
/*
* ac_wht_batch.pgc - ac 원천징수 배치 (커서 + XA).
*
* Cursors over settled purchases of a biz_date that have no withholding row yet
* and inserts a 22% withholding record (via dbio) for each, all under ONE global
* XA transaction. Usage: ac_wht_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "ac_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];
long h_pid, h_fee;
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 whc CURSOR FOR
SELECT p.purchase_id, p.fee FROM purchase p
WHERE p.biz_date = :h_bizdate
AND NOT EXISTS (SELECT 1 FROM ac_wht w WHERE w.purchase_id = p.purchase_id)
ORDER BY p.purchase_id;
EXEC SQL OPEN whc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH whc INTO :h_pid, :h_fee;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE whc; tpabort(0); return 1; }
if (acdb_insert_wht(h_pid, acq_wht_amt(h_fee, 2200), bizdate) < 0) {
EXEC SQL CLOSE whc; tpabort(0); return 1;
}
rows++;
}
EXEC SQL CLOSE whc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> ac_wht_batch COMMIT: bizdate=%s wht_rows=%ld\n", bizdate, rows);
tpclose(); tpterm();
return 0;
}

35
app/src/ac/dbio/ac_dbio.h Normal file
View file

@ -0,0 +1,35 @@
/*
* ac_dbio.h - ac DB (libacdbio.a).
* These are ECPG (EXEC SQL) functions compiled into a static library and linked
* into ac_svr and the ac batches. They run on the caller's XA branch/connection
* (no EXEC SQL CONNECT); sqlca is the shared per-thread ECPG SQLCA.
* Return convention: 0 = ok, -1 = SQL error (caller may also inspect sqlca).
*/
#ifndef AC_DBIO_H
#define AC_DBIO_H
/* purchase */
long acdb_next_purchase_id(void);
int acdb_insert_purchase(long pid, const char *merch, long amount, long fee,
long net, const char *status, const char *channel,
const char *bizdate);
int acdb_update_status(long pid, const char *status);
int acdb_get_status(long pid, char *out /* >= 2 bytes */);
int acdb_update_amount(long pid, long amount, long fee, long net);
int acdb_insert_approval(long pid, int approved);
/* fee / tax */
long acdb_insert_fee_adj(long pid, long delta, const char *reason, const char *bizdate);
long acdb_insert_wht(long pid, long tax, const char *bizdate);
long acdb_insert_tax_invoice(long pid, const char *merch, long supply, long vat,
const char *bizdate);
/* merchant */
int acdb_merchant_mdr(const char *merch, long *bps_out);
int acdb_upsert_merch_sum(const char *merch, const char *bizdate, long gross,
long fee, long net, long cnt);
/* misc ledger */
long acdb_insert_ledger(long pid, const char *type, long amount, const char *bizdate);
#endif /* AC_DBIO_H */

View file

@ -0,0 +1,72 @@
/*
* ac_fee_dbio.pgc - fee-adjust / withholding / tax-invoice DB access (libacdbio.a).
*/
#include <string.h>
#include <userlog.h>
#include "ac_dbio.h"
long acdb_insert_fee_adj(long pid, long delta, const char *reason, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, 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('ac_feeadj_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO ac_fee_adj (adj_id, purchase_id, delta, reason, biz_date)
VALUES (:h_id, :h_pid, :h_delta, :h_reason, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_fee_adj FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long acdb_insert_wht(long pid, long tax, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid = pid, h_tax = tax;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL SELECT nextval('ac_wht_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO ac_wht (wht_id, purchase_id, tax_amount, biz_date)
VALUES (:h_id, :h_pid, :h_tax, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_wht FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
long acdb_insert_tax_invoice(long pid, const char *merch, long supply, long vat,
const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id, h_pid = pid, h_supply = supply, h_vat = vat;
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 SELECT nextval('ac_taxinv_seq') INTO :h_id;
if (sqlca.sqlcode < 0) return -1;
EXEC SQL INSERT INTO ac_tax_invoice (inv_id, purchase_id, merchant_id, supply, vat, biz_date)
VALUES (:h_id, :h_pid, :h_merch, :h_supply, :h_vat, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_tax_invoice FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}

View file

@ -0,0 +1,51 @@
/*
* ac_merch_dbio.pgc - merchant lookup + daily summary upsert (libacdbio.a).
*/
#include <string.h>
#include <userlog.h>
#include "ac_dbio.h"
int acdb_merchant_mdr(const char *merch, long *bps_out)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_bps;
char h_merch[64];
EXEC SQL END DECLARE SECTION;
strncpy(h_merch, merch, sizeof(h_merch) - 1); h_merch[sizeof(h_merch)-1] = 0;
EXEC SQL SELECT mdr_bps INTO :h_bps FROM merchant WHERE merchant_id = :h_merch;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
userlog("acdb_merchant_mdr FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
*bps_out = h_bps;
return 0;
}
int acdb_upsert_merch_sum(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 merchant_summary
(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 = merchant_summary.txn_count + EXCLUDED.txn_count,
gross_amount = merchant_summary.gross_amount + EXCLUDED.gross_amount,
fee_amount = merchant_summary.fee_amount + EXCLUDED.fee_amount,
net_amount = merchant_summary.net_amount + EXCLUDED.net_amount,
updated_at = now();
if (sqlca.sqlcode < 0) {
userlog("acdb_upsert_merch_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}

View file

@ -0,0 +1,131 @@
/*
* ac_purchase_dbio.pgc - purchase/approval/ledger DB access (part of libacdbio.a).
* ECPG functions; run on the caller's XA branch. No EXEC SQL CONNECT.
*/
#include <string.h>
#include <userlog.h>
#include "ac_dbio.h"
long acdb_next_purchase_id(void)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_id;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT nextval('purchase_seq') INTO :h_id;
if (sqlca.sqlcode < 0) {
userlog("acdb_next_purchase_id FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return h_id;
}
int acdb_insert_purchase(long pid, const char *merch, long amount, long fee,
long net, const char *status, const char *channel,
const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid, h_amount = amount, h_fee = fee, h_net = net;
char h_merch[64], h_status[8], h_channel[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_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
strncpy(h_channel, channel, sizeof(h_channel) - 1); h_channel[sizeof(h_channel)-1] = 0;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate)-1] = 0;
EXEC SQL INSERT INTO purchase
(purchase_id, merchant_id, amount, fee, net, status, biz_date, channel)
VALUES (:h_pid, :h_merch, :h_amount, :h_fee, :h_net, :h_status, :h_bizdate, :h_channel);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_purchase FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}
int acdb_insert_approval(long pid, int approved)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid;
int h_ok = approved;
EXEC SQL END DECLARE SECTION;
EXEC SQL INSERT INTO approval (approval_id, purchase_id, approved)
VALUES (nextval('approval_seq'), :h_pid, :h_ok);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_approval FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}
int acdb_update_status(long pid, const char *status)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid;
char h_status[8];
EXEC SQL END DECLARE SECTION;
strncpy(h_status, status, sizeof(h_status) - 1); h_status[sizeof(h_status)-1] = 0;
EXEC SQL UPDATE purchase SET status = :h_status, updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
userlog("acdb_update_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}
int acdb_get_status(long pid, char *out)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid;
char h_status[8];
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT status INTO :h_status FROM purchase WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
userlog("acdb_get_status FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
out[0] = h_status[0];
out[1] = 0;
return 0;
}
int acdb_update_amount(long pid, long amount, long fee, long net)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid, h_amount = amount, h_fee = fee, h_net = net;
EXEC SQL END DECLARE SECTION;
EXEC SQL UPDATE purchase
SET amount = :h_amount, fee = :h_fee, net = :h_net, updated_at = now()
WHERE purchase_id = :h_pid;
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
userlog("acdb_update_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}
long acdb_insert_ledger(long pid, const char *type, long amount, const char *bizdate)
{
EXEC SQL BEGIN DECLARE SECTION;
long h_pid = pid, h_amount = amount;
char h_type[16], h_bizdate[16];
EXEC SQL END DECLARE SECTION;
strncpy(h_type, 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 INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
VALUES (nextval('ledger_seq'), :h_pid, :h_type, :h_amount, :h_bizdate);
if (sqlca.sqlcode < 0) {
userlog("acdb_insert_ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
return -1;
}
return 0;
}