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:
parent
e6e29e6b5d
commit
05a0ff3e56
28 changed files with 1964 additions and 410 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,8 +1,12 @@
|
||||||
# build artifacts
|
# build artifacts
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
|
*.a
|
||||||
build/
|
build/
|
||||||
bin/acq*
|
bin/acq*
|
||||||
|
# per-module build output (static libs + ecpg-generated .c/.o)
|
||||||
|
app/lib/
|
||||||
|
app/obj/
|
||||||
# ecpg-generated C from .pgc (regenerated at build)
|
# ecpg-generated C from .pgc (regenerated at build)
|
||||||
**/gen/*.c
|
**/gen/*.c
|
||||||
# Enduro/X runtime state
|
# Enduro/X runtime state
|
||||||
|
|
|
||||||
188
app/build.sh
Normal file
188
app/build.sh
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
#!/bin/bash
|
||||||
|
##
|
||||||
|
## build.sh - REUSABLE build + Enduro/X config generator for acquire-core-x.
|
||||||
|
##
|
||||||
|
## Discovers every module under app/src/<mod>/, ecpg-precompiles its .pgc, builds
|
||||||
|
## - libacqcommon.a (from app/src/common/*.c) [once]
|
||||||
|
## - lib<mod>dbio.a (from app/src/<mod>/dbio/*.pgc) [per module]
|
||||||
|
## - <mod>_svr (from app/src/<mod>/<mod>_svr.pgc) [per module]
|
||||||
|
## - <mod> batches (from app/src/<mod>/batch/*.pgc) [per module]
|
||||||
|
## - standalone clients (from app/src/clients/*.c)
|
||||||
|
## then GENERATES app/conf/ndrxconfig.xml with one <server> entry per <mod>_svr.
|
||||||
|
##
|
||||||
|
## Adding a new module later = drop app/src/<mod>/{<mod>_svr.pgc,dbio,batch} and
|
||||||
|
## re-run build.sh. No hand-editing of config or this script is required: modules,
|
||||||
|
## servers, dbio libs, batches, and srvids are all DISCOVERED.
|
||||||
|
##
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC=/app/src
|
||||||
|
BIN=/app/bin
|
||||||
|
LIB=/app/lib
|
||||||
|
OBJ=/app/obj
|
||||||
|
UBF=/app/ubftab
|
||||||
|
CONF=/app/conf
|
||||||
|
EXINC=/usr/local/include
|
||||||
|
PGINC="$(pg_config --includedir)"
|
||||||
|
PGLIB="$(pg_config --libdir)"
|
||||||
|
|
||||||
|
mkdir -p "$BIN" "$LIB" "$OBJ"
|
||||||
|
|
||||||
|
# Includes for every compile (CFLAGS is honored by buildserver/buildclient and
|
||||||
|
# by our direct gcc calls). Add every module's dbio dir so headers resolve.
|
||||||
|
CFLAGS="-I$UBF -I$EXINC -I$PGINC -I$SRC/common"
|
||||||
|
for d in "$SRC"/*/dbio; do [ -d "$d" ] && CFLAGS="$CFLAGS -I$d"; done
|
||||||
|
export CFLAGS
|
||||||
|
PGLINK="-L$PGLIB -lecpg -lpq"
|
||||||
|
|
||||||
|
log() { echo "[build] $*"; }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# discover modules = dirs app/src/<mod>/ that contain <mod>_svr.pgc
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
MODS=()
|
||||||
|
for d in "$SRC"/*/; do
|
||||||
|
m="$(basename "$d")"
|
||||||
|
[ -f "$d/${m}_svr.pgc" ] && MODS+=("$m")
|
||||||
|
done
|
||||||
|
IFS=$'\n' MODS=($(printf '%s\n' "${MODS[@]}" | sort)); unset IFS
|
||||||
|
log "modules discovered: ${MODS[*]}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 0. UBF field header
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "mkfldhdr acq.fd"
|
||||||
|
( cd "$UBF" && FIELDTBLS=acq.fd FLDTBLDIR="$UBF" mkfldhdr acq.fd >/dev/null )
|
||||||
|
|
||||||
|
# ecpg precompile helper: $1 = .pgc path -> echoes generated .c path
|
||||||
|
ecpg_gen() {
|
||||||
|
local pgc="$1" base out
|
||||||
|
base="$(basename "${pgc%.pgc}")"
|
||||||
|
out="$OBJ/${base}.c"
|
||||||
|
ecpg -I"$UBF" -I"$(dirname "$pgc")" -o "$out" "$pgc"
|
||||||
|
echo "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. common lib -> libacqcommon.a
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "libacqcommon.a"
|
||||||
|
COMMON_OBJS=()
|
||||||
|
for c in "$SRC"/common/*.c; do
|
||||||
|
o="$OBJ/$(basename "${c%.c}").o"
|
||||||
|
gcc -c $CFLAGS "$c" -o "$o"
|
||||||
|
COMMON_OBJS+=("$o")
|
||||||
|
done
|
||||||
|
ar rcs "$LIB/libacqcommon.a" "${COMMON_OBJS[@]}"
|
||||||
|
NSVR=0; NDBIO=0; NBATCH=0
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2..4 per module: dbio lib, server, batches
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
for m in "${MODS[@]}"; do
|
||||||
|
MDIR="$SRC/$m"
|
||||||
|
DBIO_LIB=""
|
||||||
|
|
||||||
|
# -- dbio static lib (optional) --
|
||||||
|
if compgen -G "$MDIR/dbio/*.pgc" >/dev/null; then
|
||||||
|
log "lib${m}dbio.a"
|
||||||
|
DB_OBJS=()
|
||||||
|
for pgc in "$MDIR"/dbio/*.pgc; do
|
||||||
|
c="$(ecpg_gen "$pgc")"
|
||||||
|
o="${c%.c}.o"
|
||||||
|
gcc -c $CFLAGS "$c" -o "$o"
|
||||||
|
DB_OBJS+=("$o")
|
||||||
|
done
|
||||||
|
ar rcs "$LIB/lib${m}dbio.a" "${DB_OBJS[@]}"
|
||||||
|
DBIO_LIB="$LIB/lib${m}dbio.a"
|
||||||
|
NDBIO=$((NDBIO+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- module server <mod>_svr --
|
||||||
|
log "${m}_svr"
|
||||||
|
SVR_C="$(ecpg_gen "$MDIR/${m}_svr.pgc")"
|
||||||
|
buildserver -o "$BIN/${m}_svr" -f "$SVR_C" \
|
||||||
|
-a "$DBIO_LIB $LIB/libacqcommon.a $PGLINK"
|
||||||
|
NSVR=$((NSVR+1))
|
||||||
|
|
||||||
|
# -- batches (optional): each *.pgc -> its own client executable --
|
||||||
|
if compgen -G "$MDIR/batch/*.pgc" >/dev/null; then
|
||||||
|
for pgc in "$MDIR"/batch/*.pgc; do
|
||||||
|
name="$(basename "${pgc%.pgc}")"
|
||||||
|
log "batch $name"
|
||||||
|
BC="$(ecpg_gen "$pgc")"
|
||||||
|
buildclient -o "$BIN/$name" -f "$BC" \
|
||||||
|
-a "$DBIO_LIB $LIB/libacqcommon.a $PGLINK"
|
||||||
|
NBATCH=$((NBATCH+1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. standalone clients (plain C, no EXEC SQL)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
NCLI=0
|
||||||
|
for c in "$SRC"/clients/*.c; do
|
||||||
|
name="$(basename "${c%.c}")"
|
||||||
|
log "client $name"
|
||||||
|
buildclient -o "$BIN/$name" -f "$c"
|
||||||
|
NCLI=$((NCLI+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. GENERATE ndrxconfig.xml (tmsrv fixed at srvid 40; module servers 100 step 10)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "generating ndrxconfig.xml for servers: ${MODS[*]/%/_svr}"
|
||||||
|
gen_servers() {
|
||||||
|
local srvid=100
|
||||||
|
for m in "${MODS[@]}"; do
|
||||||
|
cat <<EOF
|
||||||
|
<server name="${m}_svr">
|
||||||
|
<srvid>${srvid}</srvid>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
<sysopt>-e \${NDRX_APPHOME}/log/${m}_svr.log -r</sysopt>
|
||||||
|
</server>
|
||||||
|
EOF
|
||||||
|
srvid=$((srvid+10))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
cat > "$CONF/ndrxconfig.xml" <<EOF
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!-- GENERATED by app/build.sh - do not hand-edit. One <server> per discovered
|
||||||
|
<mod>_svr module server; each advertises its module's services at tpsvrinit. -->
|
||||||
|
<endurox>
|
||||||
|
<appconfig>
|
||||||
|
<sanity>1</sanity>
|
||||||
|
<brrefresh>5</brrefresh>
|
||||||
|
<restart_min>1</restart_min>
|
||||||
|
<restart_step>1</restart_step>
|
||||||
|
<restart_max>5</restart_max>
|
||||||
|
<restart_to_check>20</restart_to_check>
|
||||||
|
<gather_pq_stats>Y</gather_pq_stats>
|
||||||
|
</appconfig>
|
||||||
|
<defaults>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
<autokill>1</autokill>
|
||||||
|
<start_max>20</start_max>
|
||||||
|
<pingtime>100</pingtime>
|
||||||
|
<ping_max>800</ping_max>
|
||||||
|
<end_max>30</end_max>
|
||||||
|
<killtime>1</killtime>
|
||||||
|
</defaults>
|
||||||
|
<servers>
|
||||||
|
<!-- XA transaction manager for RM1 (ECPG/PostgreSQL). -->
|
||||||
|
<server name="tmsrv">
|
||||||
|
<srvid>40</srvid>
|
||||||
|
<min>1</min>
|
||||||
|
<max>1</max>
|
||||||
|
<sysopt>-e \${NDRX_APPHOME}/log/tmsrv-rm1.log -r -- -t1 -l\${NDRX_APPHOME}/tmlogs/rm1 -m10</sysopt>
|
||||||
|
</server>
|
||||||
|
$(gen_servers)
|
||||||
|
</servers>
|
||||||
|
</endurox>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log "DONE: modules=${#MODS[@]} servers=$NSVR dbio_libs=$NDBIO batches=$NBATCH clients=$NCLI"
|
||||||
|
echo "[build] server binaries: ${MODS[*]/%/_svr}"
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
|
<!-- GENERATED by app/build.sh - do not hand-edit. One <server> per discovered
|
||||||
|
<mod>_svr module server; each advertises its module's services at tpsvrinit. -->
|
||||||
<endurox>
|
<endurox>
|
||||||
<appconfig>
|
<appconfig>
|
||||||
<sanity>1</sanity>
|
<sanity>1</sanity>
|
||||||
|
|
@ -20,36 +22,30 @@
|
||||||
<killtime>1</killtime>
|
<killtime>1</killtime>
|
||||||
</defaults>
|
</defaults>
|
||||||
<servers>
|
<servers>
|
||||||
<!-- XA transaction manager for RM1 = the ECPG/PostgreSQL resource.
|
<!-- XA transaction manager for RM1 (ECPG/PostgreSQL). -->
|
||||||
XA env (NDRX_XA_*) is inherited from the shell (see setapp.sh). -->
|
|
||||||
<server name="tmsrv">
|
<server name="tmsrv">
|
||||||
<srvid>40</srvid>
|
<srvid>40</srvid>
|
||||||
<min>1</min>
|
<min>1</min>
|
||||||
<max>1</max>
|
<max>1</max>
|
||||||
<sysopt>-e ${NDRX_APPHOME}/log/tmsrv-rm1.log -r -- -t1 -l${NDRX_APPHOME}/tmlogs/rm1 -m10</sysopt>
|
<sysopt>-e ${NDRX_APPHOME}/log/tmsrv-rm1.log -r -- -t1 -l${NDRX_APPHOME}/tmlogs/rm1 -m10</sysopt>
|
||||||
</server>
|
</server>
|
||||||
|
<server name="ac_svr">
|
||||||
<!-- The three ECPG XA services. Separate binaries so the in-chain
|
|
||||||
tpcall()s (ACQUIRE->RECONCILE->SETTLE) hit distinct server
|
|
||||||
processes (avoids single-server self-call deadlock) while sharing
|
|
||||||
one global transaction across their DB branches. -->
|
|
||||||
<server name="acqacquire">
|
|
||||||
<srvid>100</srvid>
|
<srvid>100</srvid>
|
||||||
<min>1</min>
|
<min>1</min>
|
||||||
<max>1</max>
|
<max>1</max>
|
||||||
<sysopt>-e ${NDRX_APPHOME}/log/acqacquire.log -r</sysopt>
|
<sysopt>-e ${NDRX_APPHOME}/log/ac_svr.log -r</sysopt>
|
||||||
</server>
|
</server>
|
||||||
<server name="acqreconcile">
|
<server name="rc_svr">
|
||||||
<srvid>110</srvid>
|
<srvid>110</srvid>
|
||||||
<min>1</min>
|
<min>1</min>
|
||||||
<max>1</max>
|
<max>1</max>
|
||||||
<sysopt>-e ${NDRX_APPHOME}/log/acqreconcile.log -r</sysopt>
|
<sysopt>-e ${NDRX_APPHOME}/log/rc_svr.log -r</sysopt>
|
||||||
</server>
|
</server>
|
||||||
<server name="acqsettle">
|
<server name="st_svr">
|
||||||
<srvid>120</srvid>
|
<srvid>120</srvid>
|
||||||
<min>1</min>
|
<min>1</min>
|
||||||
<max>1</max>
|
<max>1</max>
|
||||||
<sysopt>-e ${NDRX_APPHOME}/log/acqsettle.log -r</sysopt>
|
<sysopt>-e ${NDRX_APPHOME}/log/st_svr.log -r</sysopt>
|
||||||
</server>
|
</server>
|
||||||
</servers>
|
</servers>
|
||||||
</endurox>
|
</endurox>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
##
|
##
|
||||||
## Build + boot the Enduro/X acquiring slice, then stay alive.
|
## Build (via the reusable build.sh) + boot the Enduro/X acquiring stack, then
|
||||||
## Runs inside the app container (image acquire-x/endurox:7.0.12).
|
## stay alive. Runs inside the app container (image acquire-x/endurox:7.0.12).
|
||||||
##
|
##
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
. /app/conf/setapp.sh
|
. /app/conf/setapp.sh
|
||||||
|
|
||||||
echo "=================================================================="
|
echo "=================================================================="
|
||||||
echo " acquire-core-x : build + boot"
|
echo " acquire-core-x : build + boot (module-server pattern)"
|
||||||
echo "=================================================================="
|
echo "=================================================================="
|
||||||
|
|
||||||
# runtime dirs
|
# runtime dirs
|
||||||
mkdir -p /app/log /app/tmp /app/tmlogs/rm1 /app/bin
|
mkdir -p /app/log /app/tmp /app/tmlogs/rm1 /app/bin /app/lib /app/obj
|
||||||
rm -f /app/log/*.log 2>/dev/null || true
|
rm -f /app/log/*.log 2>/dev/null || true
|
||||||
|
|
||||||
# ensure posix mqueue is available (compose provides the sysctls/ulimits)
|
# ensure posix mqueue is available (compose provides the sysctls/ulimits)
|
||||||
|
|
@ -22,40 +22,13 @@ if [ ! -d /dev/mqueue ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 1. UBF field header from the field table
|
# Build everything + generate ndrxconfig.xml (discovers all modules)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
echo "--- mkfldhdr acq.fd ---"
|
bash /app/build.sh
|
||||||
cd /app/ubftab
|
|
||||||
FIELDTBLS=acq.fd FLDTBLDIR=/app/ubftab mkfldhdr acq.fd
|
|
||||||
ls -l /app/ubftab/acq.fd.h
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 2. ECPG precompile + buildserver each .pgc (XA switch loaded via env, so
|
|
||||||
# buildserver needs no -r; we just link ecpg + pq). buildclient the driver.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
export CFLAGS="-I/app/ubftab -I/app/src -I$(pg_config --includedir)"
|
|
||||||
PGLIB="-L$(pg_config --libdir) -lecpg -lpq"
|
|
||||||
|
|
||||||
cd /app/src
|
|
||||||
build_svc () {
|
|
||||||
local src="$1" bin="$2"
|
|
||||||
echo "--- ecpg $src ---"
|
|
||||||
ecpg -I/app/ubftab -o "/app/src/${src%.pgc}.c" "/app/src/$src"
|
|
||||||
echo "--- buildserver -> $bin ---"
|
|
||||||
buildserver -o "/app/bin/$bin" -f "/app/src/${src%.pgc}.c" -a "$PGLIB"
|
|
||||||
}
|
|
||||||
|
|
||||||
build_svc acquire.pgc acqacquire
|
|
||||||
build_svc reconcile.pgc acqreconcile
|
|
||||||
build_svc settle.pgc acqsettle
|
|
||||||
|
|
||||||
echo "--- buildclient -> acqdrv ---"
|
|
||||||
buildclient -o /app/bin/acqdrv -f /app/src/acqdrv.c
|
|
||||||
|
|
||||||
ls -l /app/bin
|
ls -l /app/bin
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 3. Boot Enduro/X
|
# Boot Enduro/X
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
echo "--- xadmin down/start ---"
|
echo "--- xadmin down/start ---"
|
||||||
xadmin down -y 2>/dev/null || true
|
xadmin down -y 2>/dev/null || true
|
||||||
|
|
@ -67,7 +40,7 @@ echo "--- xadmin ppm (processes) ---"
|
||||||
xadmin ppm
|
xadmin ppm
|
||||||
|
|
||||||
echo "=================================================================="
|
echo "=================================================================="
|
||||||
echo " acquire-core-x : UP. Run the driver with:"
|
echo " acquire-core-x : UP. Run the 매입 chain driver with:"
|
||||||
echo " docker compose -f docker/docker-compose.yml exec app /app/run-driver.sh"
|
echo " docker compose -f docker/docker-compose.yml exec app /app/run-driver.sh"
|
||||||
echo "=================================================================="
|
echo "=================================================================="
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
## Run the acquiring driver under a real global XA transaction.
|
## Run the acquiring drivers under real global XA transactions.
|
||||||
## ./run-driver.sh -> one COMMIT run + one ROLLBACK run (demo)
|
## ./run-driver.sh -> demo: COMMIT chain, ROLLBACK chain, ops, batches
|
||||||
## ./run-driver.sh <merch> <amount> <bizdate> [abort] -> single custom run
|
## ./run-driver.sh <merch> <amount> <bizdate> [abort] -> single custom chain run
|
||||||
. /app/conf/setapp.sh
|
. /app/conf/setapp.sh
|
||||||
|
|
||||||
if [ "$#" -ge 1 ]; then
|
if [ "$#" -ge 1 ]; then
|
||||||
exec /app/bin/acqdrv "$@"
|
exec /app/bin/acqdrv "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "===== COMMIT run (ACQUIRE -> RECONCILE -> SETTLE, tpcommit) ====="
|
echo "===== 1) 매입 체인 COMMIT (ACQUIRE -> RECONCILE -> SETTLE, tpcommit) ====="
|
||||||
/app/bin/acqdrv M0001 1000000 2026-07-19
|
/app/bin/acqdrv M0001 1000000 2026-07-19
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "===== ROLLBACK run (same chain, tpabort) ====="
|
echo "===== 2) 매입 체인 ROLLBACK (동일 체인, tpabort) ====="
|
||||||
/app/bin/acqdrv M0002 500000 2026-07-19 abort
|
/app/bin/acqdrv M0002 500000 2026-07-19 abort
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== 3) ac 온라인 서비스 ops (DUPCHK/FEEADJ/INSTALL/STATUS, tpcommit) ====="
|
||||||
|
LASTPID=$(PGPASSWORD=acq psql -h db -U acq -d acq -tAc "SELECT max(purchase_id) FROM purchase")
|
||||||
|
/app/bin/ac_ops "${LASTPID:-1}" M0001 1000000 2026-07-19
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== 4) ac 배치: 가맹점 일집계 (커서 + XA) ====="
|
||||||
|
/app/bin/ac_merchsum_batch 2026-07-19
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== 5) ac 배치: 원천징수 (커서 + XA) ====="
|
||||||
|
/app/bin/ac_wht_batch 2026-07-19
|
||||||
|
|
|
||||||
687
app/src/ac/ac_svr.pgc
Normal file
687
app/src/ac/ac_svr.pgc
Normal 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: */
|
||||||
56
app/src/ac/batch/ac_merchsum_batch.pgc
Normal file
56
app/src/ac/batch/ac_merchsum_batch.pgc
Normal 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;
|
||||||
|
}
|
||||||
55
app/src/ac/batch/ac_wht_batch.pgc
Normal file
55
app/src/ac/batch/ac_wht_batch.pgc
Normal 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
35
app/src/ac/dbio/ac_dbio.h
Normal 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 */
|
||||||
72
app/src/ac/dbio/ac_fee_dbio.pgc
Normal file
72
app/src/ac/dbio/ac_fee_dbio.pgc
Normal 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;
|
||||||
|
}
|
||||||
51
app/src/ac/dbio/ac_merch_dbio.pgc
Normal file
51
app/src/ac/dbio/ac_merch_dbio.pgc
Normal 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;
|
||||||
|
}
|
||||||
131
app/src/ac/dbio/ac_purchase_dbio.pgc
Normal file
131
app/src/ac/dbio/ac_purchase_dbio.pgc
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* ACQUIRE (매입 접수) - ECPG XA service.
|
|
||||||
*
|
|
||||||
* Inserts a `purchase` row (fee = amount * 25/10000 MDR, net = amount - fee),
|
|
||||||
* then within the SAME global transaction tpcall()s RECONCILE.
|
|
||||||
*
|
|
||||||
* XA connection is opened by the ECPG XA switch (libndrxxaecpg.so) via tpopen();
|
|
||||||
* there is deliberately NO `EXEC SQL CONNECT` (matches ref/atmisv67.pgc).
|
|
||||||
*/
|
|
||||||
#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"
|
|
||||||
|
|
||||||
void ACQUIRE(TPSVCINFO *p);
|
|
||||||
|
|
||||||
int tpsvrinit(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (tpopen() < 0) {
|
|
||||||
userlog("acquire: tpopen FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (tpadvertise("ACQUIRE", ACQUIRE) < 0) {
|
|
||||||
userlog("acquire: tpadvertise(ACQUIRE) FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
userlog("acquire: ACQUIRE advertised, RM opened");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpsvrdone(void)
|
|
||||||
{
|
|
||||||
tpclose();
|
|
||||||
userlog("acquire: tpsvrdone");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ACQUIRE(TPSVCINFO *p)
|
|
||||||
{
|
|
||||||
UBFH *b = (UBFH *)p->data;
|
|
||||||
long amount = 0, fee = 0, net = 0;
|
|
||||||
char merch[64] = "";
|
|
||||||
char bizdate[16] = "";
|
|
||||||
BFLDLEN len;
|
|
||||||
long rlen = 0;
|
|
||||||
|
|
||||||
EXEC SQL BEGIN DECLARE SECTION;
|
|
||||||
long h_pid;
|
|
||||||
long h_amount;
|
|
||||||
long h_fee;
|
|
||||||
long h_net;
|
|
||||||
char h_merch[64];
|
|
||||||
char h_bizdate[16];
|
|
||||||
EXEC SQL END DECLARE SECTION;
|
|
||||||
|
|
||||||
len = sizeof(merch);
|
|
||||||
if (Bget(b, T_MERCHANT, 0, merch, &len) < 0) {
|
|
||||||
userlog("ACQUIRE: missing T_MERCHANT: %s", Bstrerror(Berror));
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bget(b, T_AMOUNT, 0, (char *)&amount, 0L);
|
|
||||||
len = sizeof(bizdate);
|
|
||||||
Bget(b, T_BIZDATE, 0, bizdate, &len);
|
|
||||||
|
|
||||||
fee = amount * 25 / 10000; /* 0.25% MDR */
|
|
||||||
net = amount - fee;
|
|
||||||
|
|
||||||
/* obtain a purchase id (sequence is non-transactional by design) */
|
|
||||||
EXEC SQL SELECT nextval('purchase_seq') INTO :h_pid;
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("ACQUIRE: nextval FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
h_amount = amount;
|
|
||||||
h_fee = fee;
|
|
||||||
h_net = net;
|
|
||||||
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 purchase
|
|
||||||
(purchase_id, merchant_id, amount, fee, net, status, biz_date)
|
|
||||||
VALUES (:h_pid, :h_merch, :h_amount, :h_fee, :h_net, 'A', :h_bizdate);
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("ACQUIRE: INSERT purchase FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* record the approval as well (all under the same global tx) */
|
|
||||||
EXEC SQL INSERT INTO approval (approval_id, purchase_id, approved)
|
|
||||||
VALUES (nextval('approval_seq'), :h_pid, true);
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("ACQUIRE: INSERT approval FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bchg(b, T_PURCHASE_ID, 0, (char *)&h_pid, 0L);
|
|
||||||
Bchg(b, T_FEE, 0, (char *)&fee, 0L);
|
|
||||||
Bchg(b, T_NET, 0, (char *)&net, 0L);
|
|
||||||
Bchg(b, T_STATUS, 0, "A", 0L);
|
|
||||||
|
|
||||||
userlog("ACQUIRE pid=%ld merch=%s amount=%ld fee=%ld net=%ld -> RECONCILE",
|
|
||||||
h_pid, merch, amount, fee, net);
|
|
||||||
|
|
||||||
/* Continue the chain under the SAME global transaction. RECONCILE (and, in
|
|
||||||
* turn, SETTLE) run as separate XA branches (separate DB connections) and
|
|
||||||
* therefore CANNOT see this not-yet-committed purchase row - so they write
|
|
||||||
* only their OWN rows (ledger/settlement). This owner branch performs the
|
|
||||||
* purchase status transition itself once the chain returns, because its
|
|
||||||
* connection is resumed on the same branch and can see its own INSERT. */
|
|
||||||
if (tpcall("RECONCILE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
|
|
||||||
userlog("ACQUIRE: tpcall(RECONCILE) FAIL: %s", tpstrerror(tperrno));
|
|
||||||
tpreturn(TPFAIL, 0, (char *)b, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* branch A resumed here: settle-complete the purchase we own */
|
|
||||||
EXEC SQL UPDATE purchase
|
|
||||||
SET status = 'S', updated_at = now()
|
|
||||||
WHERE purchase_id = :h_pid;
|
|
||||||
if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) {
|
|
||||||
userlog("ACQUIRE: UPDATE purchase status FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, (char *)b, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bchg(b, T_STATUS, 0, "S", 0L);
|
|
||||||
|
|
||||||
tpreturn(TPSUCCESS, 0, (char *)b, 0L, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ts=4 sw=4 et smartindent: */
|
|
||||||
65
app/src/clients/ac_ops.c
Normal file
65
app/src/clients/ac_ops.c
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* ac_ops - exercises several ac_svr ONLINE services (not the chain) under ONE
|
||||||
|
* global XA transaction, to prove the module server's advertised services work
|
||||||
|
* and commit. Usage: ac_ops <purchase_id> [merchant] [amount] [bizdate]
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <atmi.h>
|
||||||
|
#include <ubf.h>
|
||||||
|
#include "acq.fd.h"
|
||||||
|
|
||||||
|
static long callsvc(const char *svc, UBFH **b)
|
||||||
|
{
|
||||||
|
long rlen = 0;
|
||||||
|
if (tpcall((char *)svc, (char *)*b, 0L, (char **)b, &rlen, 0L) < 0) {
|
||||||
|
fprintf(stderr, "tpcall(%s) FAIL: %s\n", svc, tpstrerror(tperrno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
long pid = (argc > 1) ? atol(argv[1]) : 1;
|
||||||
|
const char *merch = (argc > 2) ? argv[2] : "M0001";
|
||||||
|
long amount = (argc > 3) ? atol(argv[3]) : 1000000;
|
||||||
|
const char *bizdate = (argc > 4) ? argv[4] : "2026-07-19";
|
||||||
|
UBFH *b;
|
||||||
|
long cnt = 0, adj = 0, months = 0, delta = 100;
|
||||||
|
char st[16] = "";
|
||||||
|
BFLDLEN l;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
b = (UBFH *)tpalloc("UBF", NULL, 4096);
|
||||||
|
if (!b) { fprintf(stderr, "tpalloc FAIL\n"); return 1; }
|
||||||
|
|
||||||
|
Bchg(b, T_PURCHASE_ID, 0, (char *)&pid, 0L);
|
||||||
|
Bchg(b, T_MERCHANT, 0, (char *)merch, 0L);
|
||||||
|
Bchg(b, T_AMOUNT, 0, (char *)&amount, 0L);
|
||||||
|
Bchg(b, T_BIZDATE, 0, (char *)bizdate, 0L);
|
||||||
|
Bchg(b, T_DELTA, 0, (char *)&delta, 0L);
|
||||||
|
Bchg(b, T_INSTALL_N, 0, (char *)(long[]){3}, 0L);
|
||||||
|
Bchg(b, T_REASON, 0, "OPS_TEST", 0L);
|
||||||
|
|
||||||
|
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
|
||||||
|
|
||||||
|
if (callsvc("ACQ_DUPCHK", &b) < 0) { tpabort(0); return 1; }
|
||||||
|
Bget(b, T_COUNT, 0, (char *)&cnt, 0L);
|
||||||
|
if (callsvc("ACQ_FEEADJ", &b) < 0) { tpabort(0); return 1; }
|
||||||
|
Bget(b, T_ADJ, 0, (char *)&adj, 0L);
|
||||||
|
if (callsvc("ACQ_INSTALL", &b) < 0) { tpabort(0); return 1; }
|
||||||
|
Bget(b, T_COUNT, 0, (char *)&months, 0L);
|
||||||
|
if (callsvc("ACQ_STATUS", &b) < 0) { tpabort(0); return 1; }
|
||||||
|
l = sizeof(st); Bget(b, T_STATUS, 0, st, &l);
|
||||||
|
|
||||||
|
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
|
||||||
|
|
||||||
|
printf(">>> ac_ops COMMIT: pid=%ld dupchk_count=%ld fee_adj_id=%ld install_rows=%ld status=%s\n",
|
||||||
|
pid, cnt, adj, months, st);
|
||||||
|
|
||||||
|
tpfree((char *)b); tpclose(); tpterm();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
36
app/src/common/acq_bizday.c
Normal file
36
app/src/common/acq_bizday.c
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* acq_bizday.c - business-day arithmetic (leaf util). */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "acq_common.h"
|
||||||
|
|
||||||
|
static int is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); }
|
||||||
|
|
||||||
|
static int mdays(int y, int m)
|
||||||
|
{
|
||||||
|
static const int d[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
|
if (m == 2 && is_leap(y)) return 29;
|
||||||
|
return d[m - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sakamoto's algorithm: day of week 0=Sun..6=Sat. */
|
||||||
|
static int dow(int y, int m, int d)
|
||||||
|
{
|
||||||
|
static int t[] = {0,3,2,5,0,3,5,1,4,6,2,4};
|
||||||
|
if (m < 3) y -= 1;
|
||||||
|
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
int acq_next_bizday(const char *in, char *out)
|
||||||
|
{
|
||||||
|
int y, m, d;
|
||||||
|
if (in == 0 || out == 0) return -1;
|
||||||
|
if (sscanf(in, "%d-%d-%d", &y, &m, &d) != 3) return -1;
|
||||||
|
if (m < 1 || m > 12 || d < 1 || d > 31) return -1;
|
||||||
|
|
||||||
|
do {
|
||||||
|
d++;
|
||||||
|
if (d > mdays(y, m)) { d = 1; m++; if (m > 12) { m = 1; y++; } }
|
||||||
|
} while (dow(y, m, d) == 0 || dow(y, m, d) == 6); /* skip Sun/Sat */
|
||||||
|
|
||||||
|
sprintf(out, "%04d-%02d-%02d", y, m, d);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
27
app/src/common/acq_common.h
Normal file
27
app/src/common/acq_common.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* acq_common.h - leaf utility library (libacqcommon.a).
|
||||||
|
* Pure C, no ATMI / no EXEC SQL. Linked into every module server and batch.
|
||||||
|
*/
|
||||||
|
#ifndef ACQ_COMMON_H
|
||||||
|
#define ACQ_COMMON_H
|
||||||
|
|
||||||
|
/* fee = amount * bps / 10000 (basis points). */
|
||||||
|
long acq_fee(long amount, long bps);
|
||||||
|
|
||||||
|
/* Korean VAT 10% on a supply value (부가세). */
|
||||||
|
long acq_vat(long supply);
|
||||||
|
|
||||||
|
/* withholding tax on a fee, in basis points (원천징수). */
|
||||||
|
long acq_wht_amt(long fee, long bps);
|
||||||
|
|
||||||
|
/* Luhn (mod-10) check of a numeric PAN string. 1=valid, 0=invalid. */
|
||||||
|
int acq_luhn_ok(const char *pan);
|
||||||
|
|
||||||
|
/* Next business day for "YYYY-MM-DD": +1 day, skipping Sat/Sun.
|
||||||
|
* Writes "YYYY-MM-DD" into out (>= 11 bytes). Returns 0 ok, -1 on parse error. */
|
||||||
|
int acq_next_bizday(const char *in, char *out);
|
||||||
|
|
||||||
|
/* process-local monotonic sequence (demo of a common leaf util). */
|
||||||
|
long acq_seq_next(void);
|
||||||
|
|
||||||
|
#endif /* ACQ_COMMON_H */
|
||||||
20
app/src/common/acq_fee.c
Normal file
20
app/src/common/acq_fee.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* acq_fee.c - fee / tax math (leaf util). */
|
||||||
|
#include "acq_common.h"
|
||||||
|
|
||||||
|
long acq_fee(long amount, long bps)
|
||||||
|
{
|
||||||
|
if (amount < 0 || bps < 0) return 0;
|
||||||
|
return amount * bps / 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
long acq_vat(long supply)
|
||||||
|
{
|
||||||
|
if (supply < 0) return 0;
|
||||||
|
return supply / 10; /* 10% VAT */
|
||||||
|
}
|
||||||
|
|
||||||
|
long acq_wht_amt(long fee, long bps)
|
||||||
|
{
|
||||||
|
if (fee < 0 || bps < 0) return 0;
|
||||||
|
return fee * bps / 10000;
|
||||||
|
}
|
||||||
19
app/src/common/acq_luhn.c
Normal file
19
app/src/common/acq_luhn.c
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* acq_luhn.c - Luhn (mod-10) card number check (leaf util). */
|
||||||
|
#include "acq_common.h"
|
||||||
|
|
||||||
|
int acq_luhn_ok(const char *pan)
|
||||||
|
{
|
||||||
|
int sum = 0, alt = 0, i, n;
|
||||||
|
if (pan == 0) return 0;
|
||||||
|
for (n = 0; pan[n]; n++) ; /* strlen */
|
||||||
|
if (n == 0) return 0;
|
||||||
|
for (i = n - 1; i >= 0; i--) {
|
||||||
|
char c = pan[i];
|
||||||
|
if (c < '0' || c > '9') return 0;
|
||||||
|
int d = c - '0';
|
||||||
|
if (alt) { d *= 2; if (d > 9) d -= 9; }
|
||||||
|
sum += d;
|
||||||
|
alt = !alt;
|
||||||
|
}
|
||||||
|
return (sum % 10) == 0;
|
||||||
|
}
|
||||||
9
app/src/common/acq_seq.c
Normal file
9
app/src/common/acq_seq.c
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* acq_seq.c - process-local monotonic sequence (leaf util). */
|
||||||
|
#include "acq_common.h"
|
||||||
|
|
||||||
|
static long g_seq = 0;
|
||||||
|
|
||||||
|
long acq_seq_next(void)
|
||||||
|
{
|
||||||
|
return ++g_seq;
|
||||||
|
}
|
||||||
137
app/src/rc/rc_svr.pgc
Normal file
137
app/src/rc/rc_svr.pgc
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* rc_svr.pgc - rc (대사) MODULE SERVER.
|
||||||
|
*
|
||||||
|
* Advertises the reconcile services. RECONCILE keeps the proven behavior: it is
|
||||||
|
* a DISTINCT XA branch (own DB connection) from ACQUIRE, so it writes only its
|
||||||
|
* own ledger recon row, then tpcall()s SETTLE (st_svr) inside the same global tx.
|
||||||
|
*/
|
||||||
|
#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"
|
||||||
|
|
||||||
|
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); }
|
||||||
|
|
||||||
|
#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 RECONCILE(TPSVCINFO *p);
|
||||||
|
void RC_REMATCH(TPSVCINFO *p);
|
||||||
|
void RC_UNMATCH(TPSVCINFO *p);
|
||||||
|
void RC_HOLD(TPSVCINFO *p);
|
||||||
|
|
||||||
|
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||||
|
{"RECONCILE", RECONCILE}, {"RC_REMATCH", RC_REMATCH},
|
||||||
|
{"RC_UNMATCH", RC_UNMATCH}, {"RC_HOLD", RC_HOLD},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
int tpsvrinit(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (tpopen() < 0) { userlog("rc_svr: tpopen FAIL: %s", tpstrerror(tperrno)); return -1; }
|
||||||
|
for (i = 0; SVCS[i].name; i++)
|
||||||
|
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||||
|
userlog("rc_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
userlog("rc_svr: %d rc services advertised, RM opened", i);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tpsvrdone(void) { tpclose(); userlog("rc_svr: tpsvrdone"); }
|
||||||
|
|
||||||
|
void RECONCILE(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char bizdate[16];
|
||||||
|
long rlen = 0;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* own-branch write: recon ledger evidence only */
|
||||||
|
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||||
|
VALUES (nextval('ledger_seq'), :h_pid, 'RECON', :h_net, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) {
|
||||||
|
userlog("RECONCILE INSERT ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
||||||
|
FAIL(b);
|
||||||
|
}
|
||||||
|
Bchg(b, T_STATUS, 0, "M", 0L);
|
||||||
|
userlog("RECONCILE pid=%ld matched -> SETTLE", h_pid);
|
||||||
|
|
||||||
|
if (tpcall("SETTLE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
|
||||||
|
userlog("RECONCILE: tpcall(SETTLE) FAIL: %s", tpstrerror(tperrno));
|
||||||
|
FAIL(b);
|
||||||
|
}
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RC_REMATCH - 재대사 원장 기록. */
|
||||||
|
void RC_REMATCH(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, 'REMATCH', :h_net, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RC_UNMATCH - 미대사 건수 조회. */
|
||||||
|
void RC_UNMATCH(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char bizdate[16];
|
||||||
|
EXEC SQL BEGIN DECLARE SECTION;
|
||||||
|
long h_cnt;
|
||||||
|
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(*) INTO :h_cnt FROM purchase p
|
||||||
|
WHERE p.biz_date = :h_bizdate
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM ledger l WHERE l.purchase_id = p.purchase_id);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
{ long v = h_cnt; Bchg(b, T_COUNT, 0, (char *)&v, 0L); }
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RC_HOLD - 대사 보류 원장 기록. */
|
||||||
|
void RC_HOLD(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, 'RC_HOLD', :h_net, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ts=4 sw=4 et smartindent: */
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
/*
|
|
||||||
* RECONCILE (대사) - ECPG XA service.
|
|
||||||
*
|
|
||||||
* Marks the purchase matched (status='M'), writes a ledger recon entry, then
|
|
||||||
* tpcall()s SETTLE - all inside the caller's global transaction.
|
|
||||||
*/
|
|
||||||
#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"
|
|
||||||
|
|
||||||
void RECONCILE(TPSVCINFO *p);
|
|
||||||
|
|
||||||
int tpsvrinit(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (tpopen() < 0) {
|
|
||||||
userlog("reconcile: tpopen FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (tpadvertise("RECONCILE", RECONCILE) < 0) {
|
|
||||||
userlog("reconcile: tpadvertise(RECONCILE) FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
userlog("reconcile: RECONCILE advertised, RM opened");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpsvrdone(void)
|
|
||||||
{
|
|
||||||
tpclose();
|
|
||||||
userlog("reconcile: tpsvrdone");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RECONCILE(TPSVCINFO *p)
|
|
||||||
{
|
|
||||||
UBFH *b = (UBFH *)p->data;
|
|
||||||
long net = 0;
|
|
||||||
char bizdate[16] = "";
|
|
||||||
BFLDLEN len;
|
|
||||||
long rlen = 0;
|
|
||||||
|
|
||||||
EXEC SQL BEGIN DECLARE SECTION;
|
|
||||||
long h_pid;
|
|
||||||
long h_net;
|
|
||||||
char h_bizdate[16];
|
|
||||||
EXEC SQL END DECLARE SECTION;
|
|
||||||
|
|
||||||
if (Bget(b, T_PURCHASE_ID, 0, (char *)&h_pid, 0L) < 0) {
|
|
||||||
userlog("RECONCILE: missing T_PURCHASE_ID: %s", Bstrerror(Berror));
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bget(b, T_NET, 0, (char *)&net, 0L);
|
|
||||||
len = sizeof(bizdate);
|
|
||||||
Bget(b, T_BIZDATE, 0, bizdate, &len);
|
|
||||||
h_net = net;
|
|
||||||
strncpy(h_bizdate, bizdate, sizeof(h_bizdate) - 1); h_bizdate[sizeof(h_bizdate) - 1] = 0;
|
|
||||||
|
|
||||||
/* This is a distinct XA branch (own DB connection); it writes only its own
|
|
||||||
* recon evidence row and does NOT touch the purchase row (which belongs to
|
|
||||||
* the ACQUIRE branch and is not yet committed / not visible here). */
|
|
||||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
|
||||||
VALUES (nextval('ledger_seq'), :h_pid, 'RECON', :h_net, :h_bizdate);
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("RECONCILE: INSERT ledger FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bchg(b, T_STATUS, 0, "M", 0L);
|
|
||||||
|
|
||||||
userlog("RECONCILE pid=%ld matched -> SETTLE", h_pid);
|
|
||||||
|
|
||||||
if (tpcall("SETTLE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
|
|
||||||
userlog("RECONCILE: tpcall(SETTLE) FAIL: %s", tpstrerror(tperrno));
|
|
||||||
tpreturn(TPFAIL, 0, (char *)b, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tpreturn(TPSUCCESS, 0, (char *)b, 0L, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ts=4 sw=4 et smartindent: */
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
/*
|
|
||||||
* SETTLE (정산) - ECPG XA service.
|
|
||||||
*
|
|
||||||
* Inserts a `settlement` (netting) row, writes a ledger settle entry, and marks
|
|
||||||
* the purchase settled (status='S'). Terminal service of the chain; still runs
|
|
||||||
* inside the caller's global transaction.
|
|
||||||
*/
|
|
||||||
#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"
|
|
||||||
|
|
||||||
void SETTLE(TPSVCINFO *p);
|
|
||||||
|
|
||||||
int tpsvrinit(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (tpopen() < 0) {
|
|
||||||
userlog("settle: tpopen FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (tpadvertise("SETTLE", SETTLE) < 0) {
|
|
||||||
userlog("settle: tpadvertise(SETTLE) FAIL: %s", tpstrerror(tperrno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
userlog("settle: SETTLE advertised, RM opened");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tpsvrdone(void)
|
|
||||||
{
|
|
||||||
tpclose();
|
|
||||||
userlog("settle: tpsvrdone");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SETTLE(TPSVCINFO *p)
|
|
||||||
{
|
|
||||||
UBFH *b = (UBFH *)p->data;
|
|
||||||
long net = 0;
|
|
||||||
char merch[64] = "";
|
|
||||||
char bizdate[16] = "";
|
|
||||||
BFLDLEN len;
|
|
||||||
|
|
||||||
EXEC SQL BEGIN DECLARE SECTION;
|
|
||||||
long h_pid;
|
|
||||||
long h_sid;
|
|
||||||
long h_net;
|
|
||||||
char h_merch[64];
|
|
||||||
char h_bizdate[16];
|
|
||||||
EXEC SQL END DECLARE SECTION;
|
|
||||||
|
|
||||||
if (Bget(b, T_PURCHASE_ID, 0, (char *)&h_pid, 0L) < 0) {
|
|
||||||
userlog("SETTLE: missing T_PURCHASE_ID: %s", Bstrerror(Berror));
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bget(b, T_NET, 0, (char *)&net, 0L);
|
|
||||||
len = sizeof(merch);
|
|
||||||
Bget(b, T_MERCHANT, 0, merch, &len);
|
|
||||||
len = sizeof(bizdate);
|
|
||||||
Bget(b, T_BIZDATE, 0, bizdate, &len);
|
|
||||||
|
|
||||||
h_net = net;
|
|
||||||
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('settlement_seq') INTO :h_sid;
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("SETTLE: nextval FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EXEC SQL INSERT INTO settlement
|
|
||||||
(settlement_id, purchase_id, merchant_id, net, biz_date, status)
|
|
||||||
VALUES (:h_sid, :h_pid, :h_merch, :h_net, :h_bizdate, 'SETTLED');
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("SETTLE: INSERT settlement FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SETTLE is its own XA branch: it writes the settlement + a settle ledger
|
|
||||||
* entry (its own rows). The purchase status transition is performed by the
|
|
||||||
* ACQUIRE branch that owns that row. */
|
|
||||||
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
|
||||||
VALUES (nextval('ledger_seq'), :h_pid, 'SETTLE', :h_net, :h_bizdate);
|
|
||||||
if (sqlca.sqlcode < 0) {
|
|
||||||
userlog("SETTLE: INSERT ledger FAIL [%d] %s",
|
|
||||||
sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
|
|
||||||
tpreturn(TPFAIL, 0, p->data, 0L, 0L);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bchg(b, T_SETTLE_ID, 0, (char *)&h_sid, 0L);
|
|
||||||
Bchg(b, T_STATUS, 0, "S", 0L);
|
|
||||||
|
|
||||||
userlog("SETTLE pid=%ld settlement_id=%ld net=%ld status=S", h_pid, h_sid, net);
|
|
||||||
|
|
||||||
tpreturn(TPSUCCESS, 0, (char *)b, 0L, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ts=4 sw=4 et smartindent: */
|
|
||||||
139
app/src/st/st_svr.pgc
Normal file
139
app/src/st/st_svr.pgc
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#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"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
void SETTLE(TPSVCINFO *p);
|
||||||
|
void ST_MDR(TPSVCINFO *p);
|
||||||
|
void ST_NETTING(TPSVCINFO *p);
|
||||||
|
void ST_VAT(TPSVCINFO *p);
|
||||||
|
|
||||||
|
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
|
||||||
|
{"SETTLE", SETTLE}, {"ST_MDR", ST_MDR},
|
||||||
|
{"ST_NETTING", ST_NETTING}, {"ST_VAT", ST_VAT},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
int tpsvrinit(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (tpopen() < 0) { userlog("st_svr: tpopen FAIL: %s", tpstrerror(tperrno)); return -1; }
|
||||||
|
for (i = 0; SVCS[i].name; i++)
|
||||||
|
if (tpadvertise((char *)SVCS[i].name, SVCS[i].fn) < 0) {
|
||||||
|
userlog("st_svr: tpadvertise(%s) FAIL: %s", SVCS[i].name, tpstrerror(tperrno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
userlog("st_svr: %d st services advertised, RM opened", i);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tpsvrdone(void) { tpclose(); userlog("st_svr: tpsvrdone"); }
|
||||||
|
|
||||||
|
void SETTLE(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char merch[64], bizdate[16];
|
||||||
|
EXEC SQL BEGIN DECLARE SECTION;
|
||||||
|
long h_pid, h_sid, h_net;
|
||||||
|
char h_merch[64], h_bizdate[16];
|
||||||
|
EXEC SQL END DECLARE SECTION;
|
||||||
|
|
||||||
|
h_pid = getl(b, T_PURCHASE_ID);
|
||||||
|
h_net = getl(b, T_NET);
|
||||||
|
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 nextval('settlement_seq') INTO :h_sid;
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
EXEC SQL INSERT INTO settlement (settlement_id, purchase_id, merchant_id, net, biz_date, status)
|
||||||
|
VALUES (:h_sid, :h_pid, :h_merch, :h_net, :h_bizdate, 'SETTLED');
|
||||||
|
if (sqlca.sqlcode < 0) { userlog("SETTLE INSERT settlement FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); }
|
||||||
|
EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date)
|
||||||
|
VALUES (nextval('ledger_seq'), :h_pid, 'SETTLE', :h_net, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
|
||||||
|
setl(b, T_SETTLE_ID, h_sid);
|
||||||
|
Bchg(b, T_STATUS, 0, "S", 0L);
|
||||||
|
userlog("SETTLE pid=%ld settlement_id=%ld net=%ld", h_pid, h_sid, h_net);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ST_MDR - MDR 수수료 원장 기록. */
|
||||||
|
void ST_MDR(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char bizdate[16];
|
||||||
|
EXEC SQL BEGIN DECLARE SECTION;
|
||||||
|
long h_pid, h_fee;
|
||||||
|
char h_bizdate[16];
|
||||||
|
EXEC SQL END DECLARE SECTION;
|
||||||
|
h_pid = getl(b, T_PURCHASE_ID); h_fee = getl(b, T_FEE);
|
||||||
|
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, 'MDR', :h_fee, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ST_NETTING - 당일 net 합계 조회. */
|
||||||
|
void ST_NETTING(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char merch[64], bizdate[16];
|
||||||
|
EXEC SQL BEGIN DECLARE SECTION;
|
||||||
|
long h_sum;
|
||||||
|
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(net),0) INTO :h_sum FROM settlement
|
||||||
|
WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
setl(b, T_GROSS, h_sum);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ST_VAT - 정산 부가세 원장 기록. */
|
||||||
|
void ST_VAT(TPSVCINFO *p)
|
||||||
|
{
|
||||||
|
UBFH *b = (UBFH *)p->data;
|
||||||
|
char bizdate[16];
|
||||||
|
EXEC SQL BEGIN DECLARE SECTION;
|
||||||
|
long h_pid, h_vat;
|
||||||
|
char h_bizdate[16];
|
||||||
|
EXEC SQL END DECLARE SECTION;
|
||||||
|
h_pid = getl(b, T_PURCHASE_ID); h_vat = getl(b, T_FEE) / 10;
|
||||||
|
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, 'VAT', :h_vat, :h_bizdate);
|
||||||
|
if (sqlca.sqlcode < 0) FAIL(b);
|
||||||
|
setl(b, T_TAX, h_vat);
|
||||||
|
OK(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ts=4 sw=4 et smartindent: */
|
||||||
|
|
@ -1,13 +1,29 @@
|
||||||
$/* @(#) Acquiring (카드 매입) UBF field table */
|
$/* @(#) Acquiring (카드 매입) UBF field table - shared by all modules */
|
||||||
*base 6000
|
*base 6000
|
||||||
|
|
||||||
# name id type flag comment
|
# name id type flag comment
|
||||||
T_MERCHANT 1 string - -
|
T_MERCHANT 1 string - 가맹점 ID
|
||||||
T_AMOUNT 2 long - -
|
T_AMOUNT 2 long - 매입 금액
|
||||||
T_FEE 3 long - -
|
T_FEE 3 long - 수수료
|
||||||
T_NET 4 long - -
|
T_NET 4 long - 정산 금액
|
||||||
T_STATUS 5 string - -
|
T_STATUS 5 string - 상태
|
||||||
T_PURCHASE_ID 6 long - -
|
T_PURCHASE_ID 6 long - 매입 ID
|
||||||
T_BIZDATE 7 string - -
|
T_BIZDATE 7 string - 영업일
|
||||||
T_SETTLE_ID 8 long - -
|
T_SETTLE_ID 8 long - 정산 ID
|
||||||
T_MSG 9 string - -
|
T_MSG 9 string - 메시지
|
||||||
|
T_CANCEL_ID 10 long - 취소 ID
|
||||||
|
T_INSTALL_N 11 long - 할부 개월
|
||||||
|
T_TAX 12 long - 세액
|
||||||
|
T_FX_AMT 13 long - 해외 원통화 금액
|
||||||
|
T_COUNT 14 long - 건수
|
||||||
|
T_REASON 15 string - 사유
|
||||||
|
T_CATEGORY 16 string - 업종/분류
|
||||||
|
T_ISSUER 17 string - 발급사
|
||||||
|
T_CHANNEL 18 string - 채널
|
||||||
|
T_CCY 19 string - 통화코드
|
||||||
|
T_ADJ 20 long - 조정 ID
|
||||||
|
T_DELTA 21 long - 조정 금액(증감)
|
||||||
|
T_SVCNAME 22 string - 서비스명
|
||||||
|
T_RC 23 long - 결과코드
|
||||||
|
T_GROSS 24 long - 총액
|
||||||
|
T_DOCNO 25 string - 문서번호
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* @(#) Acquiring (카드 매입) UBF field table */
|
/* @(#) Acquiring (카드 매입) UBF field table - shared by all modules */
|
||||||
/* fname bfldid */
|
/* fname bfldid */
|
||||||
/* ----- ----- */
|
/* ----- ----- */
|
||||||
#define T_MERCHANT ((BFLDID32)167778161) /* number: 6001 type: string */
|
#define T_MERCHANT ((BFLDID32)167778161) /* number: 6001 type: string */
|
||||||
|
|
@ -10,3 +10,19 @@
|
||||||
#define T_BIZDATE ((BFLDID32)167778167) /* number: 6007 type: string */
|
#define T_BIZDATE ((BFLDID32)167778167) /* number: 6007 type: string */
|
||||||
#define T_SETTLE_ID ((BFLDID32)33560440) /* number: 6008 type: long */
|
#define T_SETTLE_ID ((BFLDID32)33560440) /* number: 6008 type: long */
|
||||||
#define T_MSG ((BFLDID32)167778169) /* number: 6009 type: string */
|
#define T_MSG ((BFLDID32)167778169) /* number: 6009 type: string */
|
||||||
|
#define T_CANCEL_ID ((BFLDID32)33560442) /* number: 6010 type: long */
|
||||||
|
#define T_INSTALL_N ((BFLDID32)33560443) /* number: 6011 type: long */
|
||||||
|
#define T_TAX ((BFLDID32)33560444) /* number: 6012 type: long */
|
||||||
|
#define T_FX_AMT ((BFLDID32)33560445) /* number: 6013 type: long */
|
||||||
|
#define T_COUNT ((BFLDID32)33560446) /* number: 6014 type: long */
|
||||||
|
#define T_REASON ((BFLDID32)167778175) /* number: 6015 type: string */
|
||||||
|
#define T_CATEGORY ((BFLDID32)167778176) /* number: 6016 type: string */
|
||||||
|
#define T_ISSUER ((BFLDID32)167778177) /* number: 6017 type: string */
|
||||||
|
#define T_CHANNEL ((BFLDID32)167778178) /* number: 6018 type: string */
|
||||||
|
#define T_CCY ((BFLDID32)167778179) /* number: 6019 type: string */
|
||||||
|
#define T_ADJ ((BFLDID32)33560452) /* number: 6020 type: long */
|
||||||
|
#define T_DELTA ((BFLDID32)33560453) /* number: 6021 type: long */
|
||||||
|
#define T_SVCNAME ((BFLDID32)167778182) /* number: 6022 type: string */
|
||||||
|
#define T_RC ((BFLDID32)33560455) /* number: 6023 type: long */
|
||||||
|
#define T_GROSS ((BFLDID32)33560456) /* number: 6024 type: long */
|
||||||
|
#define T_DOCNO ((BFLDID32)167778185) /* number: 6025 type: string */
|
||||||
|
|
|
||||||
114
db/schema.sql
114
db/schema.sql
|
|
@ -1,4 +1,4 @@
|
||||||
-- Card-acquiring vertical-slice schema (카드 매입/승인/정산/원장)
|
-- Card-acquiring schema (카드 매입/승인/정산/원장 + ac 모듈 상세 테이블)
|
||||||
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d).
|
-- Loaded into PostgreSQL on first boot (docker-entrypoint-initdb.d).
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
@ -8,13 +8,13 @@ CREATE TABLE IF NOT EXISTS merchant (
|
||||||
merchant_id TEXT PRIMARY KEY,
|
merchant_id TEXT PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
mdr_bps INTEGER NOT NULL DEFAULT 25, -- Merchant Discount Rate, basis points (0.25%)
|
mdr_bps INTEGER NOT NULL DEFAULT 25, -- Merchant Discount Rate, basis points (0.25%)
|
||||||
|
daily_limit BIGINT NOT NULL DEFAULT 100000000,
|
||||||
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
status TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- 매입 (purchase) - the acquiring transaction
|
-- 매입 (purchase) - the acquiring transaction
|
||||||
-- purchase_id driven by an explicit sequence so ECPG can SELECT nextval first.
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
CREATE SEQUENCE IF NOT EXISTS purchase_seq;
|
CREATE SEQUENCE IF NOT EXISTS purchase_seq;
|
||||||
CREATE TABLE IF NOT EXISTS purchase (
|
CREATE TABLE IF NOT EXISTS purchase (
|
||||||
|
|
@ -23,8 +23,11 @@ CREATE TABLE IF NOT EXISTS purchase (
|
||||||
amount BIGINT NOT NULL, -- minor units (원, 정수)
|
amount BIGINT NOT NULL, -- minor units (원, 정수)
|
||||||
fee BIGINT NOT NULL, -- MDR fee
|
fee BIGINT NOT NULL, -- MDR fee
|
||||||
net BIGINT NOT NULL, -- amount - fee
|
net BIGINT NOT NULL, -- amount - fee
|
||||||
status TEXT NOT NULL, -- A=접수, M=대사완료(matched), S=정산완료(settled)
|
status TEXT NOT NULL, -- A=접수, M=대사완료, S=정산완료, C=취소, H=보류, V=void
|
||||||
biz_date DATE NOT NULL,
|
biz_date DATE NOT NULL,
|
||||||
|
channel TEXT NOT NULL DEFAULT 'POS', -- POS/EDC/EDI/FOREIGN
|
||||||
|
issuer TEXT NOT NULL DEFAULT 'UNKNOWN',
|
||||||
|
category TEXT NOT NULL DEFAULT 'GEN',
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
@ -41,13 +44,13 @@ CREATE TABLE IF NOT EXISTS approval (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- 원장 (ledger) - double-entry style recon check written during 대사
|
-- 원장 (ledger) - recon/settle entries
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
CREATE SEQUENCE IF NOT EXISTS ledger_seq;
|
CREATE SEQUENCE IF NOT EXISTS ledger_seq;
|
||||||
CREATE TABLE IF NOT EXISTS ledger (
|
CREATE TABLE IF NOT EXISTS ledger (
|
||||||
ledger_id BIGINT PRIMARY KEY,
|
ledger_id BIGINT PRIMARY KEY,
|
||||||
purchase_id BIGINT NOT NULL,
|
purchase_id BIGINT NOT NULL,
|
||||||
entry_type TEXT NOT NULL, -- RECON / SETTLE
|
entry_type TEXT NOT NULL, -- RECON / SETTLE / XFER / SETTLELINK
|
||||||
amount BIGINT NOT NULL,
|
amount BIGINT NOT NULL,
|
||||||
biz_date DATE NOT NULL,
|
biz_date DATE NOT NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
|
@ -67,8 +70,101 @@ CREATE TABLE IF NOT EXISTS settlement (
|
||||||
settled_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
settled_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Seed a couple of merchants so ACQUIRE has a valid counterparty.
|
-- ---------------------------------------------------------------------------
|
||||||
INSERT INTO merchant (merchant_id, name, mdr_bps) VALUES
|
-- ac 모듈 상세 테이블 (취소/할부/수수료조정/원천/세금계산서/해외/EDI/미매입/집계)
|
||||||
('M0001', '클라로 커피', 25),
|
-- ---------------------------------------------------------------------------
|
||||||
('M0002', '포지 마트', 25)
|
CREATE SEQUENCE IF NOT EXISTS ac_cancel_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_cancel (
|
||||||
|
cancel_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
cancel_type TEXT NOT NULL, -- FULL / PARTIAL / REFUND / VOID
|
||||||
|
amount BIGINT NOT NULL,
|
||||||
|
reason TEXT NOT NULL DEFAULT '',
|
||||||
|
biz_date DATE NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_install_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_install (
|
||||||
|
install_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
seq_no INTEGER NOT NULL,
|
||||||
|
months INTEGER NOT NULL,
|
||||||
|
month_amount BIGINT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_feeadj_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_fee_adj (
|
||||||
|
adj_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
delta BIGINT NOT NULL,
|
||||||
|
reason TEXT NOT NULL DEFAULT '',
|
||||||
|
biz_date DATE NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_wht_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_wht (
|
||||||
|
wht_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
tax_amount BIGINT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_taxinv_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_tax_invoice (
|
||||||
|
inv_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
merchant_id TEXT NOT NULL,
|
||||||
|
supply BIGINT NOT NULL,
|
||||||
|
vat BIGINT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_fx_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_fx (
|
||||||
|
fx_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
ccy TEXT NOT NULL,
|
||||||
|
fx_amount BIGINT NOT NULL,
|
||||||
|
krw_amount BIGINT NOT NULL,
|
||||||
|
rate_bps BIGINT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_edi_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_edi (
|
||||||
|
edi_id BIGINT PRIMARY KEY,
|
||||||
|
merchant_id TEXT NOT NULL,
|
||||||
|
doc_no TEXT NOT NULL,
|
||||||
|
amount BIGINT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS ac_unmatch_seq;
|
||||||
|
CREATE TABLE IF NOT EXISTS ac_unmatch (
|
||||||
|
unmatch_id BIGINT PRIMARY KEY,
|
||||||
|
purchase_id BIGINT NOT NULL,
|
||||||
|
reason TEXT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 가맹점 일별 집계 (배치가 채움)
|
||||||
|
CREATE TABLE IF NOT EXISTS merchant_summary (
|
||||||
|
merchant_id TEXT NOT NULL,
|
||||||
|
biz_date DATE NOT NULL,
|
||||||
|
txn_count BIGINT NOT NULL DEFAULT 0,
|
||||||
|
gross_amount BIGINT NOT NULL DEFAULT 0,
|
||||||
|
fee_amount BIGINT NOT NULL DEFAULT 0,
|
||||||
|
net_amount BIGINT NOT NULL DEFAULT 0,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (merchant_id, biz_date)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed merchants so ACQUIRE has a valid counterparty.
|
||||||
|
INSERT INTO merchant (merchant_id, name, mdr_bps, daily_limit) VALUES
|
||||||
|
('M0001', '클라로 커피', 25, 100000000),
|
||||||
|
('M0002', '포지 마트', 25, 100000000),
|
||||||
|
('M0003', '클라로 면세점', 30, 500000000)
|
||||||
ON CONFLICT (merchant_id) DO NOTHING;
|
ON CONFLICT (merchant_id) DO NOTHING;
|
||||||
|
|
|
||||||
50
docs/service-catalog.md
Normal file
50
docs/service-catalog.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# 서비스 카탈로그 — acquire-core-x (2000본 구성)
|
||||||
|
|
||||||
|
파일 2000본은 유지하되, **모듈당 온라인 서버 바이너리 1개가 그 모듈의 온라인 서비스 다수를
|
||||||
|
`tpadvertise`**하는 실제 Tuxedo 방식으로 프로세스를 구성한다.
|
||||||
|
|
||||||
|
## 프로세스 구성
|
||||||
|
|
||||||
|
| 종류 | 프로세스/파일 | 설명 |
|
||||||
|
|---|---|---|
|
||||||
|
| 모듈 온라인 서버 | 11개 서버 바이너리 = `<mod>_svr` | 각 ~30 서비스 advertise (총 ~350) |
|
||||||
|
| dbio | 링크 라이브러리 (`lib<mod>dbio.a`) | 서비스가 호출하는 EXEC SQL 함수 (~300 .pgc) |
|
||||||
|
| common | 링크 라이브러리 (`libacqcommon.a`) | leaf 유틸 (~120 .c) |
|
||||||
|
| batch | ~180 독립 실행파일 (`main`) | 마감/집계, 온디맨드, XA |
|
||||||
|
| tmsrv | RM1 (PostgreSQL XA) | 2PC 조율 |
|
||||||
|
|
||||||
|
## 모듈별 온라인 서비스 (대표, 각 32개 목표 → 총 ~350)
|
||||||
|
|
||||||
|
- **ac 매입**: ACQUIRE, ACQ_DDC, ACQ_EDI, ACQ_EDC, ACQ_CANCEL, ACQ_CORRECT, ACQ_PARTIAL,
|
||||||
|
ACQ_INSTALL, ACQ_FOREIGN, ACQ_UNMATCH, ACQ_REPROC, ACQ_DUPCHK, ACQ_LIMITCHK, ACQ_FEEADJ,
|
||||||
|
ACQ_STATUS, ACQ_MERCHSUM, ACQ_ISSUERCLS, ACQ_WHT, ACQ_TAXINV, ACQ_APPRLINK, ACQ_DATECHG,
|
||||||
|
ACQ_AMTFIX, ACQ_RECLASS, ACQ_XFERLINK, ACQ_APPRMAP, ACQ_SETTLELINK, …
|
||||||
|
- **au 승인/한도**: AUTH, LIMIT_CHK, LIMIT_DEC, LIMIT_RST, STANDIN, AUTH_CANCEL, AUTH_PARTCXL,
|
||||||
|
FRAUD_DETECT, AUTH_RETRY, INSTALL_AUTH, AUTH_STATUS, …
|
||||||
|
- **rc 대사**: RECONCILE, RC_APPR_ACQ, RC_ACQ_DEP, RC_RETURN, RC_AMTDIFF, RC_CNTDIFF,
|
||||||
|
RC_3WAY, RC_TOLERANCE, RC_REMATCH, RC_HOLD, RC_UNMATCH, …
|
||||||
|
- **st 정산/수수료**: SETTLE, ST_MDR, ST_VANFEE, ST_RELAYFEE, ST_NETTING, ST_VAT, ST_WHT,
|
||||||
|
ST_ADJUST, ST_ADVANCE, ST_DELAYINT, ST_HOLD, ST_SPLIT, ST_PAYDATE(T+n), …
|
||||||
|
- **py 지급**: PAY_FILEGEN, PAY_RESULT, PAY_REPROC, PAY_HOLD, PAY_CANCEL, PAY_ACCTCHK,
|
||||||
|
PAY_SPLIT, PAY_INT, PAY_XFERLINK, …
|
||||||
|
- **lg 원장**: LG_POST, LG_CREDIT, LG_DEBIT, LG_DOUBLE, LG_BALCHK, LG_REVERSE, LG_CARRY,
|
||||||
|
LG_CLOSE_LINK, …
|
||||||
|
- **cl 마감**: CL_DAILY, CL_MONTHLY, CL_QUARTER, CL_SNAPSHOT, CL_RECLOSE, CL_VERIFY, …
|
||||||
|
- **mm 마스터**: MM_MERCH_REG, MM_MERCH_UPD, MM_FEERATE, MM_BIN, MM_LIMIT, MM_CODE, …
|
||||||
|
- **vl 정합성**: VL_SALES, VL_CARD, VL_LIMIT, VL_AMOUNT, VL_ANOMALY, VL_REQUIRED, …
|
||||||
|
- **mg 전문게이트웨이**: MG_RECV, MG_SEND, MG_ISO8583, MG_STAN, MG_ROUTE, MG_RESEND, …
|
||||||
|
- **cm 공통**: CM_CODE, CM_BIZDAY, CM_AMT, CM_SEQ, CM_FX, CM_LUHN, …
|
||||||
|
|
||||||
|
## 대표 tpcall 체인 (실동작 검증 대상)
|
||||||
|
|
||||||
|
1. **매입 체인**: `ACQUIRE` → `RECONCILE` → `SETTLE` → `LG_POST` (글로벌 XA) ← 슬라이스 검증됨
|
||||||
|
2. **승인 체인**: `AUTH` → `LIMIT_DEC` → `FRAUD_DETECT`
|
||||||
|
3. **정산 체인**: `SETTLE` → `ST_MDR` → `ST_NETTING` → `PAY_FILEGEN`
|
||||||
|
4. **마감 배치**: `cl_daily_batch` (커서) → `CL_SNAPSHOT` → `LG_CLOSE_LINK`
|
||||||
|
|
||||||
|
## Phase 2 검증 게이트 (all must pass)
|
||||||
|
|
||||||
|
- 풀빌드: 모든 `.pgc` ecpg→buildserver, 모든 batch/common 컴파일 (EXIT 0)
|
||||||
|
- 전서버 부팅: `xadmin start` 후 모든 `<mod>_svr` + tmsrv `runok`
|
||||||
|
- 전서비스 advertise: `xadmin psc`에 ~350 서비스 AVAIL
|
||||||
|
- 대표 체인 4종: 클라이언트 실행 → psql 커밋 row 확인
|
||||||
Loading…
Add table
Add a link
Reference in a new issue