From bc785f846626e6ce038f988c1364b7b54693feb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Mon, 31 Aug 2026 21:18:31 +0200 Subject: [PATCH] fix 1.26.1: public order settle uses StatusPaidResponse --- CLI-AUTOMATION-NOTES.md | 12 ++++++------ VERSION | 2 +- VERSIONS.md | 1 + check_e2e.sh | 39 +++------------------------------------ check_zero_pay.sh | 12 +----------- lib.sh | 22 ++++++++++++++++++++++ 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/CLI-AUTOMATION-NOTES.md b/CLI-AUTOMATION-NOTES.md index 628608a..5a542b1 100644 --- a/CLI-AUTOMATION-NOTES.md +++ b/CLI-AUTOMATION-NOTES.md @@ -228,15 +228,15 @@ Sources: `https://bank.hacktivism.ch/intro/stats.json`, exchange/merchant intro | **Workaround** | Create CHF:0 `stickers` template on stage.my (merchant token). Until then phase ERROR is correct. | | **Wanted** | Same public template as prod on stage. | -### 9c. TESTPAYSAN token-pay: public order JSON has no `"paid":true` +### 9c. public GET paid = `StatusPaidResponse` (no `paid` / `order_status`) | | | |--|--| -| **Seen** | 2026-08-30 magikoopa e2e `mon09529`; OIDs `2026.242-0K4GJPET664XG`, `Y4T1SDEEZ5S3R`, `XWNC7P1VH21FP`; `token-pay paid1=FAIL paid2=FAIL free1=FAIL` while suite exit 0 | -| **Area** | **cli** / **ops** | -| **Problem** | After `handle-uri --yes --choice-index=0\|1`, `check_e2e.sh` settle polls the **public** merchant order JSON (`?token=`). That body often has `refunded:false` and **no** `"paid":true` / `"order_status":"paid"` during the poll window. Private `GET …/private/orders/OID` later shows `paid=true`; kiosque `issued=2 used=2`. Parser treats missing field as unpaid. | -| **Workaround** | Settle against **instance private** `order_status=paid` (kiosque token), not public JSON. Keep poll ≥20×2s. Do not trust `handle-uri` exit 0 alone (expired/failed still exit 0). | -| **Wanted** | Public order GET with `order_status` for the pay token; wallet-cli non-zero on Payment expired/failed. | +| **Seen** | TESTPAYSAN token-pay 2026-08-30/31; e2e WARN not settled; body `{refunded:false, refund_taken:…}` | +| **Area** | **ops** | +| **Problem** | Suite required `paid` / `order_status` on **public** GET. Spec does not (`get-orders-ORDER_ID.rst` `StatusPaidResponse`: refund_* only). `order_status:"paid"` is **private** (`CheckPaymentPaidResponse`). | +| **Workaround** | `merchant_order_json_is_paid`: refunded/refund_taken, no `taler_pay_uri`. Do not trust `handle-uri` exit 0. | +| **Wanted** | wallet-cli non-zero on Payment expired/failed. | ### 9d. Merchant `/keys` parse fail → 2010 then recover from DB diff --git a/VERSION b/VERSION index 5ff8c4f..dd43a14 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.26.0 +1.26.1 diff --git a/VERSIONS.md b/VERSIONS.md index c64f86d..cb1174e 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.26.1** | 2026-08-31 | **Bugfix:** public order settle accepts `StatusPaidResponse` (`refunded`/`refund_taken`); `paid`/`order_status` not on public GET. | | **v1.26.0** | 2026-08-30 | **Feature:** `--wallet-db PATH` / `--wallet-migrate` (taler-wallet-cli / taler-harness names) for e2e, ladder, zero-pay. TESTPAYSAN: one Quadlet `stage-testpaysan`; e2e token-pay via env `E2E_TOKEN_*` / `--choice-index`. Stack READMEs `README_testpaysan.md` / `README_mytops.md`. | | **v1.24.0** | 2026-08-30 | **Feature / zero-pay:** public-template pays as phase `zero-pay` (`check_zero_pay.sh`). Default merchant **stage.my.taler-ops.ch** (CHF:0 stickers). Wrapper `pay_talersticker_zero.sh` COUNT=100. TESTPAYSAN: empty POST `{}` + auto-confirm of bank wires (host `auto-confirm-withdrawals`). Not in default 4h PHASES (use COUNT=1). | | **v1.23.9** | 2026-08-30 | **Bugfix / versions:** `INSIDE_PODMAN=1` on stagepaysan still used GOA container names → empty `dpkg-query` → **versions.host-04** ERROR on `/monitoring_err`. Stage-lfp names apply in host-podman too; `run-fp-stage-monitoring.sh` exports `INSIDE_*_CTR`. | diff --git a/check_e2e.sh b/check_e2e.sh index 3804291..7596691 100755 --- a/check_e2e.sh +++ b/check_e2e.sh @@ -1200,16 +1200,7 @@ except Exception: print("") fi if grep -qiE 'payment|paid|Payment' "$SCRATCH/tx-$tag.out" 2>/dev/null \ || grep -qiE 'done|paid|success|Payment' "$SCRATCH/pay-$tag.out" 2>/dev/null \ - || python3 -c 'import json,sys -for p in sys.argv[1:]: - try: - d=json.load(open(p)) - if d.get("paid") is True or str(d.get("order_status","")).lower()=="paid": - sys.exit(0) - except Exception: - pass -sys.exit(1) -' "$SCRATCH/ord-paid-$tag.json" "$SCRATCH/ord-pub-$tag.json" 2>/dev/null; then + || merchant_order_json_is_paid "$SCRATCH/ord-paid-$tag.json" "$SCRATCH/ord-pub-$tag.json" 2>/dev/null; then ok "payment settled $PAY_AMT ($PAY_SUM · order $OID)" metrics_report_coins "after-pay-${tag}" || true metrics_record_flow spent "$PAY_AMT" || true @@ -1340,16 +1331,7 @@ except Exception: print("") "${MERCHANT_PUBLIC}/instances/${INST}/private/orders/${OID}" 2>/dev/null || true fi curl -skS -m 8 -o "$SCRATCH/ord-pub-$tag.json" "$statusUrl" 2>/dev/null || true - if python3 -c 'import json,sys -for p in sys.argv[1:]: - try: - d=json.load(open(p)) - if d.get("paid") is True or str(d.get("order_status","")).lower()=="paid": - sys.exit(0) - except Exception: - pass -sys.exit(1) -' "$SCRATCH/ord-paid-$tag.json" "$SCRATCH/ord-pub-$tag.json" 2>/dev/null \ + if merchant_order_json_is_paid "$SCRATCH/ord-paid-$tag.json" "$SCRATCH/ord-pub-$tag.json" 2>/dev/null \ || grep -qiE 'payment|paid|Payment' "$SCRATCH/tx-$tag.out" 2>/dev/null \ || grep -qiE 'done|paid|success|Payment' "$SCRATCH/pay-$tag.out" 2>/dev/null; then ok "shop $pname" "payment settled ($pamt · order $OID)" @@ -1773,22 +1755,7 @@ except Exception: print("")' "$create_f" 2>/dev/null || true) curl -skS -m 8 -o "$SCRATCH/token-st-$tag.json" \ "${MERCHANT_PUBLIC}/instances/${inst}/orders/$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$oid")?token=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$otok")" \ 2>/dev/null || true - if python3 -c 'import json,sys -try: - d=json.load(open(sys.argv[1])) - st=str(d.get("order_status") or d.get("status") or "").lower() - if d.get("paid") is True or st=="paid": - raise SystemExit(0) -except FileNotFoundError: - pass -except SystemExit: - raise -except Exception: - pass -raise SystemExit(1) -' "$SCRATCH/token-st-$tag.json" 2>/dev/null \ - || grep -qE '"paid"[[:space:]]*:[[:space:]]*true|"order_status"[[:space:]]*:[[:space:]]*"paid"' \ - "$SCRATCH/token-st-$tag.json" 2>/dev/null; then + if merchant_order_json_is_paid "$SCRATCH/token-st-$tag.json" 2>/dev/null; then ok "token-pay $tag" "paid choice=$choice oid=$oid" return 0 fi diff --git a/check_zero_pay.sh b/check_zero_pay.sh index 1f57b68..fb53894 100755 --- a/check_zero_pay.sh +++ b/check_zero_pay.sh @@ -223,17 +223,7 @@ order_public() { } order_is_paid() { - python3 -c 'import json,sys -try: - d=json.load(open(sys.argv[1])) -except Exception: - raise SystemExit(1) -if d.get("paid") is True or str(d.get("order_status","")).lower()=="paid": - raise SystemExit(0) -if "refunded" in d or "refund_amount" in d: - raise SystemExit(0) -raise SystemExit(1) -' "$1" + merchant_order_json_is_paid "$1" } wallet_handle_done() { diff --git a/lib.sh b/lib.sh index ea41433..eab70c7 100755 --- a/lib.sh +++ b/lib.sh @@ -22,6 +22,28 @@ taler_mon_resolve_wallet_db() { printf '%s\n' "$p" } +# Merchant order JSON is paid? Public GET: StatusPaidResponse (refunded / refund_taken). +# Spec does not put paid / order_status there (those are private CheckPaymentPaidResponse). +merchant_order_json_is_paid() { + python3 -c ' +import json, sys +for p in sys.argv[1:]: + try: + d = json.load(open(p)) + except Exception: + continue + if not isinstance(d, dict): + continue + if d.get("paid") is True or str(d.get("order_status") or "").lower() == "paid": + raise SystemExit(0) + if d.get("taler_pay_uri"): + continue + if "refunded" in d or "refund_amount" in d or "refund_taken" in d: + raise SystemExit(0) +raise SystemExit(1) +' "$@" +} + # Load host env once (before any := defaults) if [ "${_TALER_MON_ENV_LOADED:-0}" != "1" ]; then _mon_cfg="${TALER_MONITORING_ENV:-${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring/env}"