Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
269 lines
14 KiB
Bash
Executable File
269 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Office 2 (Shamrayan) — Full runbook execution: API 3-step send → settlement confirmation → mirror JE → audit → closures.
|
|
# Usage: from repo root. Set P2P_BEARER_TOKEN, SENDER_SERVER_IP, SOURCE_ACCOUNT_NAME, SOURCE_ACCOUNT_NUMBER, APPROVER. P2P_API_KEY optional (PDF uses Bearer only).
|
|
# Optional: SKIP_POLL=1 and SETTLED=1 to skip polling; OFFICE2_BANK_SERVER_ID and/or OFFICE2_BANK_ACCOUNT_ID to skip step 1/2; BASE_URL or P2P_TRANSACTIONS_ENDPOINT (or P2P_ENDPOINT_TRANSACTIONS) to override API base/path.
|
|
# See docs/04-configuration/mifos-omnl-central-bank/OFFICE_2_SHAMRAYAN_RUNBOOK.md
|
|
|
|
set -euo pipefail
|
|
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
cd "$REPO_ROOT"
|
|
|
|
# --- CONFIG (locked) ---
|
|
export OMNL_OFFICE_ID="2"
|
|
# OMNL_AMOUNT: default-only so export OMNL_AMOUNT=100 overrides; set before Step 3
|
|
export OMNL_CURRENCY="USD"
|
|
export OMNL_TX_DATE="${OMNL_TX_DATE:-$(date +%Y-%m-%d)}"
|
|
export BASE_URL="${BASE_URL:-https://banktransfer.devmindgroup.com}"
|
|
TRANSACTIONS_ENDPOINT="${P2P_TRANSACTIONS_ENDPOINT:-${P2P_ENDPOINT_TRANSACTIONS:-/api/transactions}}"
|
|
export EVID_DIR="reconciliation/p2p-office2-$(date +%Y%m%d-%H%M%S)"
|
|
mkdir -p "$EVID_DIR"
|
|
|
|
echo "===== OFFICE 2 (SHAMRAYAN) 5B FULL EXECUTION ====="
|
|
echo "Evidence dir: $EVID_DIR"
|
|
|
|
# --- API connection test ---
|
|
echo "[0] Testing API connection..."
|
|
HTTP=$(curl -sS -o /dev/null -w "%{http_code}" --connect-timeout 10 "$BASE_URL/" 2>/dev/null || echo "000")
|
|
if [ "$HTTP" = "000" ]; then
|
|
echo "ERROR: Cannot reach $BASE_URL (connection failed)."
|
|
exit 1
|
|
fi
|
|
echo "OK: API reachable (HTTP $HTTP)."
|
|
|
|
# --- Required env (PDF: Bearer required; x-api-key optional) ---
|
|
: "${P2P_BEARER_TOKEN:?Set P2P_BEARER_TOKEN (from Shamrayan PDF / vault path omnl/offices/2/p2p)}"
|
|
: "${SENDER_SERVER_IP:?Set SENDER_SERVER_IP (public sender IP; PDF IP is example only)}"
|
|
: "${SOURCE_ACCOUNT_NAME:?Set SOURCE_ACCOUNT_NAME}"
|
|
: "${SOURCE_ACCOUNT_NUMBER:?Set SOURCE_ACCOUNT_NUMBER}"
|
|
: "${APPROVER:?Set APPROVER for mirror JE (maker-checker)}"
|
|
# P2P_API_KEY optional (PDF example does not show it; only if provider requires)
|
|
# : "${P2P_API_KEY:?Set P2P_API_KEY if required by provider}"
|
|
|
|
SENDER_SERVER_NAME="${SENDER_SERVER_NAME:-OMNL-OFF2-SHAMRAYAN}"
|
|
|
|
# --- Step 1: Create bank server (or use existing if OFFICE2_BANK_SERVER_ID set) ---
|
|
if [ -n "${OFFICE2_BANK_SERVER_ID:-}" ]; then
|
|
echo "[1] Using existing BANK_SERVER_ID (OFFICE2_BANK_SERVER_ID=$OFFICE2_BANK_SERVER_ID)"
|
|
BANK_SERVER_ID="$OFFICE2_BANK_SERVER_ID"
|
|
echo "$BANK_SERVER_ID" > "$EVID_DIR/01_bank_server.id.txt"
|
|
echo "{\"id\": $BANK_SERVER_ID, \"name\": \"$SENDER_SERVER_NAME\", \"server_ip_address\": \"$SENDER_SERVER_IP\"}" > "$EVID_DIR/01_bank_server.response.json"
|
|
else
|
|
echo "[1] POST /api/bank-servers..."
|
|
jq -n --arg name "$SENDER_SERVER_NAME" --arg ip "$SENDER_SERVER_IP" \
|
|
'{name: $name, server_ip_address: $ip}' > "$EVID_DIR/01_bank_server.request.json"
|
|
HTTP_STEP1=$(curl -sS -w "%{http_code}" -o "$EVID_DIR/01_bank_server.response.json" -X POST "$BASE_URL/api/bank-servers" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $P2P_BEARER_TOKEN" \
|
|
${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} \
|
|
--data @"$EVID_DIR/01_bank_server.request.json")
|
|
if [ "$HTTP_STEP1" = "422" ] && grep -q 'payload.*id' "$EVID_DIR/01_bank_server.response.json" 2>/dev/null; then
|
|
echo "Step 1: 422 (live API expects different schema). Retrying with id: 1..."
|
|
jq -n --argjson id 1 --arg name "$SENDER_SERVER_NAME" --arg ip "$SENDER_SERVER_IP" \
|
|
'{id: $id, name: $name, server_ip_address: $ip}' > "$EVID_DIR/01_bank_server.request.json"
|
|
HTTP_STEP1=$(curl -sS -w "%{http_code}" -o "$EVID_DIR/01_bank_server.response.json" -X POST "$BASE_URL/api/bank-servers" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $P2P_BEARER_TOKEN" \
|
|
${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} \
|
|
--data @"$EVID_DIR/01_bank_server.request.json")
|
|
fi
|
|
echo "Step 1 HTTP: $HTTP_STEP1"
|
|
if [ "$HTTP_STEP1" != "200" ] && [ "$HTTP_STEP1" != "201" ]; then
|
|
echo "ERROR: Step 1 failed (HTTP $HTTP_STEP1). To skip and use existing server: set OFFICE2_BANK_SERVER_ID=1 (or your server id). Response:"
|
|
cat "$EVID_DIR/01_bank_server.response.json" | head -c 2000
|
|
exit 2
|
|
fi
|
|
BANK_SERVER_ID=$(jq -r '.id // .payload.id // .data.id // empty' "$EVID_DIR/01_bank_server.response.json" 2>/dev/null || true)
|
|
if [ -z "$BANK_SERVER_ID" ] || [ "$BANK_SERVER_ID" = "null" ]; then
|
|
echo "ERROR: Step 1 failed (no server id in response). Response:"
|
|
cat "$EVID_DIR/01_bank_server.response.json" | jq '.' 2>/dev/null || cat "$EVID_DIR/01_bank_server.response.json"
|
|
exit 2
|
|
fi
|
|
echo "BANK_SERVER_ID=$BANK_SERVER_ID" | tee "$EVID_DIR/01_bank_server.id.txt"
|
|
fi
|
|
|
|
# --- Step 2: Create bank account (or use existing if OFFICE2_BANK_ACCOUNT_ID set) ---
|
|
if [ -n "${OFFICE2_BANK_ACCOUNT_ID:-}" ]; then
|
|
echo "[2] Using existing SOURCE_ACCOUNT_ID (OFFICE2_BANK_ACCOUNT_ID=$OFFICE2_BANK_ACCOUNT_ID)"
|
|
SOURCE_ACCOUNT_ID="$OFFICE2_BANK_ACCOUNT_ID"
|
|
echo "$SOURCE_ACCOUNT_ID" > "$EVID_DIR/02_bank_account.id.txt"
|
|
else
|
|
echo "[2] POST /api/bank-accounts..."
|
|
jq -n --argjson bs "$BANK_SERVER_ID" --arg an "$SOURCE_ACCOUNT_NAME" --arg anum "$SOURCE_ACCOUNT_NUMBER" \
|
|
'{bank_server: $bs, account_name: $an, account_number: $anum}' > "$EVID_DIR/02_bank_account.request.json"
|
|
HTTP_STEP2=$(curl -sS -w "%{http_code}" -o "$EVID_DIR/02_bank_account.response.json" -X POST "$BASE_URL/api/bank-accounts" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $P2P_BEARER_TOKEN" \
|
|
${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} \
|
|
--data @"$EVID_DIR/02_bank_account.request.json")
|
|
echo "Step 2 HTTP: $HTTP_STEP2"
|
|
if [ "$HTTP_STEP2" != "200" ] && [ "$HTTP_STEP2" != "201" ]; then
|
|
echo "ERROR: Step 2 failed (HTTP $HTTP_STEP2). To skip: set OFFICE2_BANK_ACCOUNT_ID=<id>. Response:"
|
|
cat "$EVID_DIR/02_bank_account.response.json" | head -c 2000
|
|
exit 3
|
|
fi
|
|
SOURCE_ACCOUNT_ID=$(jq -r '.id // .payload.id // .data.id // empty' "$EVID_DIR/02_bank_account.response.json" 2>/dev/null || true)
|
|
if [ -z "$SOURCE_ACCOUNT_ID" ] || [ "$SOURCE_ACCOUNT_ID" = "null" ]; then
|
|
echo "ERROR: Step 2 failed (no account id in response). Response:"
|
|
cat "$EVID_DIR/02_bank_account.response.json" | jq '.' 2>/dev/null || cat "$EVID_DIR/02_bank_account.response.json"
|
|
exit 3
|
|
fi
|
|
echo "SOURCE_ACCOUNT_ID=$SOURCE_ACCOUNT_ID" | tee "$EVID_DIR/02_bank_account.id.txt"
|
|
fi
|
|
|
|
# --- Step 3: Create transaction ---
|
|
: "${OMNL_AMOUNT:=5000000000}"
|
|
OMNL_AMOUNT="${OMNL_AMOUNT//,/}"
|
|
export OMNL_AMOUNT
|
|
P2P_CHANNEL="${P2P_CHANNEL:-Instant Server Settlement}"
|
|
P2P_BENEFICIARY_NAME="${P2P_BENEFICIARY_NAME:-}"
|
|
P2P_PURPOSE="${P2P_PURPOSE:-}"
|
|
P2P_TARGET_IBAN="${P2P_TARGET_IBAN:-}"
|
|
|
|
export IDEMPOTENCY_KEY="OFF2-SHAMRAYAN-5B-$(date +%Y%m%d)-$(date +%H%M%S)"
|
|
echo "$IDEMPOTENCY_KEY" | tee "$EVID_DIR/03_idempotency_key.txt"
|
|
echo "[3] POST $TRANSACTIONS_ENDPOINT (amount=$OMNL_AMOUNT, channel=$P2P_CHANNEL)..."
|
|
# Full payload: omit target_iban when empty; channel from env; optional beneficiary_name, purpose_of_payment
|
|
jq -n \
|
|
--argjson amt "$OMNL_AMOUNT" \
|
|
--argjson src "$SOURCE_ACCOUNT_ID" \
|
|
--arg ref "$IDEMPOTENCY_KEY" \
|
|
--arg ch "$P2P_CHANNEL" \
|
|
--arg iban "$P2P_TARGET_IBAN" \
|
|
--arg ben "$P2P_BENEFICIARY_NAME" \
|
|
--arg pur "$P2P_PURPOSE" \
|
|
'(
|
|
(if $iban != "" then {target_iban: $iban} else {} end) +
|
|
{
|
|
transaction_type: "bank_transfer",
|
|
amount: $amt,
|
|
source_account: $src,
|
|
target_swift_code: "DFCUUGKA",
|
|
target_bank_account_number: "02650010158937",
|
|
target_bank_name: "DFCU Bank Limited",
|
|
target_country: "Uganda",
|
|
provider: "SWIFT",
|
|
reference: $ref,
|
|
channel: $ch
|
|
} +
|
|
(if $ben != "" then {beneficiary_name: $ben} else {} end) +
|
|
(if $pur != "" then {purpose_of_payment: $pur} else {} end)
|
|
)' > "$EVID_DIR/03_transaction.request.json"
|
|
# Minimal payload: same but no reference; omit target_iban when empty; channel + optional beneficiary/purpose
|
|
jq -n \
|
|
--argjson amt "$OMNL_AMOUNT" \
|
|
--argjson src "$SOURCE_ACCOUNT_ID" \
|
|
--arg ch "$P2P_CHANNEL" \
|
|
--arg iban "$P2P_TARGET_IBAN" \
|
|
--arg ben "$P2P_BENEFICIARY_NAME" \
|
|
--arg pur "$P2P_PURPOSE" \
|
|
'(
|
|
(if $iban != "" then {target_iban: $iban} else {} end) +
|
|
{
|
|
transaction_type: "bank_transfer",
|
|
amount: $amt,
|
|
source_account: $src,
|
|
target_swift_code: "DFCUUGKA",
|
|
target_bank_account_number: "02650010158937",
|
|
target_bank_name: "DFCU Bank Limited",
|
|
target_country: "Uganda",
|
|
provider: "SWIFT",
|
|
channel: $ch
|
|
} +
|
|
(if $ben != "" then {beneficiary_name: $ben} else {} end) +
|
|
(if $pur != "" then {purpose_of_payment: $pur} else {} end)
|
|
)' > "$EVID_DIR/03_transaction.request.minimal.json"
|
|
|
|
HTTP_STEP3=$(curl -sS -w "%{http_code}" -o "$EVID_DIR/03_transaction.response.json" -X POST "$BASE_URL$TRANSACTIONS_ENDPOINT" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $P2P_BEARER_TOKEN" \
|
|
${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} \
|
|
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
|
|
--data @"$EVID_DIR/03_transaction.request.json")
|
|
echo "Step 3 HTTP: $HTTP_STEP3"
|
|
|
|
if [ "$HTTP_STEP3" != "200" ] && [ "$HTTP_STEP3" != "201" ]; then
|
|
echo "Retrying Step 3 with provider-exact minimal payload (no reference, channel, or Idempotency-Key)..."
|
|
HTTP_STEP3=$(curl -sS -w "%{http_code}" -o "$EVID_DIR/03_transaction.response.json" -X POST "$BASE_URL$TRANSACTIONS_ENDPOINT" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $P2P_BEARER_TOKEN" \
|
|
${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} \
|
|
--data @"$EVID_DIR/03_transaction.request.minimal.json")
|
|
echo "Step 3 retry HTTP: $HTTP_STEP3"
|
|
fi
|
|
|
|
if [ "$HTTP_STEP3" != "200" ] && [ "$HTTP_STEP3" != "201" ]; then
|
|
echo "ERROR: Step 3 failed (HTTP $HTTP_STEP3). Response:"
|
|
cat "$EVID_DIR/03_transaction.response.json" | head -c 2000
|
|
echo ""
|
|
if [ "$HTTP_STEP3" = "404" ]; then
|
|
echo "404: Provider may not have this path enabled. Confirm with provider that POST $BASE_URL$TRANSACTIONS_ENDPOINT is correct."
|
|
echo "To try an alternate path, set e.g. P2P_TRANSACTIONS_ENDPOINT=/api/v1/transactions and re-run."
|
|
fi
|
|
exit 4
|
|
fi
|
|
TX_ID=$(jq -r '.id // .payload.id // .transactionId // .data.id // empty' "$EVID_DIR/03_transaction.response.json" 2>/dev/null || true)
|
|
TX_STATUS=$(jq -r '.status // .payload.status // .data.status // empty' "$EVID_DIR/03_transaction.response.json" 2>/dev/null || true)
|
|
echo "TX_ID=$TX_ID" | tee "$EVID_DIR/03_transaction.id.txt"
|
|
echo "TX_STATUS=$TX_STATUS" | tee "$EVID_DIR/03_transaction.status.txt"
|
|
if [ -z "$TX_ID" ] && [ -z "$TX_STATUS" ]; then
|
|
echo "ERROR: Step 3 failed (no tx id/status in response). Response:"
|
|
cat "$EVID_DIR/03_transaction.response.json" | jq '.' 2>/dev/null || cat "$EVID_DIR/03_transaction.response.json"
|
|
exit 4
|
|
fi
|
|
echo "OK: Transaction submitted (TX_ID=$TX_ID, status=$TX_STATUS)."
|
|
|
|
# --- Settlement confirmation ---
|
|
if [ "${SKIP_POLL:-0}" = "1" ] && [ "${SETTLED:-0}" = "1" ]; then
|
|
echo "[4] Skipping poll (SKIP_POLL=1 SETTLED=1 — out-of-band confirmation)."
|
|
echo "SETTLED" > "$EVID_DIR/04_settlement.status.txt"
|
|
else
|
|
echo "[4] Settlement probe + polling (15s x 120 = 30 min max)..."
|
|
TRANSACTIONS_BASE="${TRANSACTIONS_ENDPOINT%/}"
|
|
for path in "$TRANSACTIONS_BASE/$TX_ID" "$TRANSACTIONS_BASE?id=$TX_ID"; do
|
|
curl -sS "$BASE_URL$path" -H "Content-Type: application/json" -H "Authorization: Bearer $P2P_BEARER_TOKEN" ${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} > "$EVID_DIR/04_settlement.response.json" 2>/dev/null || true
|
|
STATUS=$(jq -r '.status // .data.status // empty' "$EVID_DIR/04_settlement.response.json" 2>/dev/null || true)
|
|
[ -n "$STATUS" ] && break
|
|
done
|
|
POLL_INTERVAL=15
|
|
MAX_POLL=120
|
|
SETTLED=0
|
|
for i in $(seq 1 $MAX_POLL); do
|
|
[ -n "$TX_ID" ] && curl -sS "$BASE_URL$TRANSACTIONS_BASE/$TX_ID" -H "Content-Type: application/json" -H "Authorization: Bearer $P2P_BEARER_TOKEN" ${P2P_API_KEY:+ -H "x-api-key: $P2P_API_KEY"} > "$EVID_DIR/04_settlement.response.json" 2>/dev/null || true
|
|
STATUS=$(jq -r '.status // .data.status // empty' "$EVID_DIR/04_settlement.response.json" 2>/dev/null || true)
|
|
echo "$(date -Is) iteration=$i status=$STATUS" | tee -a "$EVID_DIR/04_settlement.poll.log"
|
|
case "$STATUS" in
|
|
SETTLED|COMPLETED|SUCCESS) SETTLED=1; break ;;
|
|
FAILED|REJECTED|CANCELED) echo "Settlement failed: $STATUS"; exit 5 ;;
|
|
*) sleep $POLL_INTERVAL ;;
|
|
esac
|
|
done
|
|
if [ "$SETTLED" -ne 1 ]; then
|
|
echo "ESCALATE: 30 min polling exceeded. TX_ID=$TX_ID. Obtain out-of-band confirmation then re-run with SKIP_POLL=1 SETTLED=1 for mirror only." | tee "$EVID_DIR/04_settlement.ESCALATE.txt"
|
|
exit 6
|
|
fi
|
|
echo "SETTLED" | tee "$EVID_DIR/04_settlement.status.txt"
|
|
fi
|
|
|
|
# --- Mirror JE (only after settlement) ---
|
|
echo "[5] Mirror JE (Office 2, Dr 2100 / Cr 1410, $OMNL_AMOUNT)..."
|
|
source omnl-fineract/.env 2>/dev/null || true
|
|
bash scripts/omnl/resolve_ids.sh
|
|
source ids.env
|
|
export REFERENCE_NUMBER="OFF2-SHAMRAYAN-SETTLED-${OMNL_TX_DATE//-/}-${OMNL_AMOUNT}"
|
|
REQUIRES_APPROVAL=1 APPROVER="$APPROVER" REFERENCE_NUMBER="$REFERENCE_NUMBER" TX_DATE="$OMNL_TX_DATE" OFFICE_ID="$OMNL_OFFICE_ID" CURRENCY="$OMNL_CURRENCY" DEBIT_GL_ID="$ID_2100" CREDIT_GL_ID="$ID_1410" AMOUNT="$OMNL_AMOUNT" bash scripts/omnl/omnl-je-maker.sh
|
|
PAYLOAD_FILE="reconciliation/je-${REFERENCE_NUMBER}.payload.json"
|
|
[ -f "$PAYLOAD_FILE" ] || PAYLOAD_FILE="reconciliation/je-${REFERENCE_NUMBER}_.payload.json"
|
|
PAYLOAD_FILE="$PAYLOAD_FILE" bash scripts/omnl/omnl-je-checker.sh
|
|
|
|
# --- Audit + closures + archive instruction ---
|
|
echo "[6] Post-audit (Office 2)..."
|
|
OFFICE_ID="$OMNL_OFFICE_ID" bash scripts/omnl/omnl-audit-packet-office20.sh
|
|
echo "[7] Re-lock GL closures..."
|
|
bash scripts/omnl/omnl-gl-closures-post.sh
|
|
echo "Archive off-box: $EVID_DIR and reconciliation/audit-office${OMNL_OFFICE_ID}-<timestamp>/" > "reconciliation/p2p-office2-archive-instructions.txt"
|
|
|
|
echo ""
|
|
echo "===== FULL EXECUTION COMPLETE ====="
|
|
echo "Evidence: $EVID_DIR"
|
|
echo "Mirror ref: $REFERENCE_NUMBER"
|