#!/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=. 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}-/" > "reconciliation/p2p-office2-archive-instructions.txt" echo "" echo "===== FULL EXECUTION COMPLETE =====" echo "Evidence: $EVID_DIR" echo "Mirror ref: $REFERENCE_NUMBER"