diff --git a/scripts/taler-monitoring/check_e2e.sh b/scripts/taler-monitoring/check_e2e.sh index 7da4dc6..644c134 100755 --- a/scripts/taler-monitoring/check_e2e.sh +++ b/scripts/taler-monitoring/check_e2e.sh @@ -775,26 +775,35 @@ else: return 1 } -# One variable payment amount +# One payment: e2e_one_pay AMOUNT [SUMMARY] [TAG] +# SUMMARY defaults to "monitoring pay $AMOUNT"; TAG defaults from amount. e2e_one_pay() { PAY_AMT="$1" - local tag - tag=$(printf '%s' "$PAY_AMT" | tr '.:' '__') - section "e2e · pay $PAY_AMT" - e2e_over && { warn "pay" "budget exhausted — skip $PAY_AMT"; return 1; } + local PAY_SUM="${2:-monitoring pay ${PAY_AMT}}" + local tag="${3:-}" + if [ -z "$tag" ]; then + tag=$(printf '%s' "$PAY_AMT" | tr '.:' '__') + fi + # shell-safe tag for files + tag=$(printf '%s' "$tag" | tr -c 'A-Za-z0-9._-' '_') + section "e2e · pay $PAY_AMT · $PAY_SUM" + e2e_over && { warn "pay" "budget exhausted — skip $PAY_AMT ($PAY_SUM)"; return 1; } if [ -z "${MPW:-}" ]; then warn "pay $PAY_AMT" "no merchant token" return 1 fi AUTH="Authorization: Bearer secret-token:${MPW}" + # JSON-escape summary for curl -d + local SUM_JSON + SUM_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$PAY_SUM" 2>/dev/null || printf '"%s"' "$PAY_SUM") curl -skS -m 15 -o "$SCRATCH/ord-$tag.json" -X POST \ -H "$AUTH" -H 'Content-Type: application/json' \ - -d "{\"order\":{\"summary\":\"monitoring pay ${PAY_AMT}\",\"amount\":\"${PAY_AMT}\",\"fulfillment_message\":\"ok\"},\"create_token\":true}" \ + -d "{\"order\":{\"summary\":${SUM_JSON},\"amount\":\"${PAY_AMT}\",\"fulfillment_message\":\"ok\"},\"create_token\":true}" \ "${MERCHANT_PUBLIC}/instances/${INST}/private/orders" 2>"$SCRATCH/ord-$tag.err" || true if ! grep -q order_id "$SCRATCH/ord-$tag.json" 2>/dev/null; then if [ "$E2E_REMOTE" != "1" ] && koopa_ssh_ok; then koopa_ssh_run 20 \ - "curl -skS -m 12 -X POST -H 'Authorization: Bearer secret-token:${MPW}' -H 'Content-Type: application/json' -d '{\"order\":{\"summary\":\"monitoring pay\",\"amount\":\"${PAY_AMT}\",\"fulfillment_message\":\"ok\"},\"create_token\":true}' 'https://127.0.0.1:9010/instances/${INST}/private/orders'" \ + "curl -skS -m 12 -X POST -H 'Authorization: Bearer secret-token:${MPW}' -H 'Content-Type: application/json' -d '{\"order\":{\"summary\":${SUM_JSON},\"amount\":\"${PAY_AMT}\",\"fulfillment_message\":\"ok\"},\"create_token\":true}' 'https://127.0.0.1:9010/instances/${INST}/private/orders'" \ >"$SCRATCH/ord-$tag.json" 2>"$SCRATCH/ord-$tag.err" || true fi fi @@ -810,12 +819,12 @@ except Exception: ' "$SCRATCH/ord-$tag.json" 2>/dev/null || true) if [ -z "$OID" ]; then if is_auth_kyc_body "$SCRATCH/ord-$tag.json"; then - e2e_abort_auth "merchant-order" "merchant auth/KYC on pay $PAY_AMT" + e2e_abort_auth "merchant-order" "merchant auth/KYC on pay $PAY_AMT ($PAY_SUM)" fi - warn "pay $PAY_AMT" "order create failed — $(head -c 80 "$SCRATCH/ord-$tag.json" | tr '\n' ' ')" + warn "pay $PAY_AMT ($PAY_SUM)" "order create failed — $(head -c 80 "$SCRATCH/ord-$tag.json" | tr '\n' ' ')" return 1 fi - ok "merchant order $OID ($PAY_AMT)" + ok "merchant order $OID ($PAY_AMT · $PAY_SUM)" curl -skS -m 12 -o "$SCRATCH/ord-det-$tag.json" -H "$AUTH" \ "${MERCHANT_PUBLIC}/instances/${INST}/private/orders/${OID}" 2>/dev/null || true PAYURI=$(python3 -c 'import json,sys @@ -826,15 +835,18 @@ except Exception: print("") MH=$(python3 -c 'from urllib.parse import urlparse; print(urlparse("'"$MERCHANT_PUBLIC"'").hostname or "taler.hacktivism.ch")' 2>/dev/null || echo "taler.hacktivism.ch") PAYURI="taler://pay/${MH}/instances/${INST}/${OID}/?c=${OTOK}" fi + # normalize default HTTPS port (wallets accept both) + PAYURI=$(printf '%s' "$PAYURI" | sed 's/:443\//\//g; s/:443?/?/g') if [ -z "$PAYURI" ]; then - warn "pay $PAY_AMT" "no pay URI for $OID" + warn "pay $PAY_AMT ($PAY_SUM)" "no pay URI for $OID" return 1 fi + ok "taler_pay_uri ready ($tag)" if ! wcli_pay handle-uri --yes "$PAYURI" >"$SCRATCH/pay-$tag.out" 2>&1; then - warn "pay $PAY_AMT" "handle-uri failed — $(tail -c 100 "$SCRATCH/pay-$tag.out" | tr '\n' ' ')" + warn "pay $PAY_AMT ($PAY_SUM)" "handle-uri failed — $(tail -c 100 "$SCRATCH/pay-$tag.out" | tr '\n' ' ')" return 1 fi - ok "wallet handle pay $PAY_AMT" + ok "wallet handle pay $PAY_AMT ($PAY_SUM)" local r for r in 1 2 3; do wcli_pay run-until-done >"$SCRATCH/pay-run-$tag.out" 2>&1 || true @@ -847,11 +859,130 @@ except Exception: print("") d=json.load(open(sys.argv[1])) sys.exit(0 if d.get("paid") is True or str(d.get("order_status","")).lower()=="paid" else 1) ' "$SCRATCH/ord-paid-$tag.json" 2>/dev/null; then - ok "payment settled $PAY_AMT (order $OID)" + ok "payment settled $PAY_AMT ($PAY_SUM · order $OID)" return 0 fi done - warn "pay $PAY_AMT" "not settled for order $OID" + warn "pay $PAY_AMT ($PAY_SUM)" "not settled for order $OID" + return 1 +} + +# GOA shop samples from merchant landing (taler.hacktivism.ch/intro/) +# id|product_name|amount — keep in sync with configs/merchant-landing/index.html +# Landing QR encodes taler://pay-template/…/{id}; popup creates live pay via public POST templates/{id}. +: "${E2E_SHOP_PRODUCTS:=orbit-sticker|Orbit sticker pack|GOA:2 +nebula-coffee|Nebula coffee|GOA:5 +voidwave-playlist|Voidwave playlist|GOA:8 +comet-cap|Comet cap|GOA:15 +shuttle-pass|Shuttle day pass|GOA:25 +beacon-badge|Beacon badge|GOA:3 +relay-pin|Relay pin|GOA:4 +eclipse-shades|Eclipse shades|GOA:12 +star-chart|Star chart print|GOA:7 +rainbow-pill|Rainbow pill (sample/joke)|GOA:4.2 +blue-or-red-pill|Blue or red pill (sample/joke)|GOA:1.5}" + +# Settlement payto shown on landing shop popup (static) +: "${E2E_SHOP_PAYTO:=payto://x-taler-bank/bank.hacktivism.ch/goa-demo-cp4zqk?receiver-name=GOA%20Demo%20Shop%20cp4zqk}" + +# Pay one shop product the way the public landing does: +# POST /instances/{inst}/templates/{id} body {} → order_id+token → taler_pay_uri +# Also checks pay-template URI shape (what qrencode PNGs encode). +# Usage: e2e_one_pay_public_template PRODUCT_ID PRODUCT_NAME AMOUNT +e2e_one_pay_public_template() { + local pid="$1" + local pname="${2:-$1}" + local pamt="$3" + local tag + tag=$(printf 'shop_%s' "$pid" | tr -c 'A-Za-z0-9._-' '_') + section "e2e · shop product · $pname ($pid) · $pamt" + e2e_over && { warn "shop $pname" "budget exhausted — skip"; return 1; } + + local MH + MH=$(python3 -c 'from urllib.parse import urlparse; print(urlparse("'"$MERCHANT_PUBLIC"'").hostname or "taler.hacktivism.ch")' 2>/dev/null || echo "taler.hacktivism.ch") + local TPL_URI="taler://pay-template/${MH}/instances/${INST}/${pid}" + local TPL_HTTPS="${MERCHANT_PUBLIC}/instances/${INST}/templates/$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$pid")" + info "shop $pname" "pay-template URI $TPL_URI" + info "shop $pname" "public template POST $TPL_HTTPS" + info "shop $pname" "settlement payto $E2E_SHOP_PAYTO" + + # Public create (no merchant secret) — same as shop-pay.js + curl -skS -m 15 -o "$SCRATCH/tpl-$tag.json" -X POST \ + -H 'Content-Type: application/json' \ + -d '{}' \ + "$TPL_HTTPS" 2>"$SCRATCH/tpl-$tag.err" || true + if ! grep -q order_id "$SCRATCH/tpl-$tag.json" 2>/dev/null; then + if [ "$E2E_REMOTE" != "1" ] && koopa_ssh_ok; then + koopa_ssh_run 20 \ + "curl -skS -m 12 -X POST -H 'Content-Type: application/json' -d '{}' 'https://127.0.0.1:9010/instances/${INST}/templates/${pid}'" \ + >"$SCRATCH/tpl-$tag.json" 2>"$SCRATCH/tpl-$tag.err" || true + fi + fi + OID=$(python3 -c 'import json,re,sys;t=open(sys.argv[1]).read() +try: print(json.loads(t).get("order_id") or "") +except Exception: + m=re.search(r"\"order_id\"\s*:\s*\"([^\"]+)\"",t); print(m.group(1) if m else "") +' "$SCRATCH/tpl-$tag.json" 2>/dev/null || true) + OTOK=$(python3 -c 'import json,re,sys;t=open(sys.argv[1]).read() +try: print(json.loads(t).get("token") or "") +except Exception: + m=re.search(r"\"token\"\s*:\s*\"([^\"]+)\"",t); print(m.group(1) if m else "") +' "$SCRATCH/tpl-$tag.json" 2>/dev/null || true) + if [ -z "$OID" ] || [ -z "$OTOK" ]; then + warn "shop $pname ($pid)" "public template POST failed — $(head -c 100 "$SCRATCH/tpl-$tag.json" 2>/dev/null | tr '\n' ' ')" + return 1 + fi + ok "shop $pname" "public order $OID (template $pid)" + + local statusUrl="${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")" + curl -skS -m 12 -o "$SCRATCH/tpl-st-$tag.json" "$statusUrl" 2>/dev/null || true + PAYURI=$(python3 -c 'import json,sys +try: + d=json.load(open(sys.argv[1])) + print(d.get("taler_pay_uri") or "") +except Exception: print("") +' "$SCRATCH/tpl-st-$tag.json" 2>/dev/null || true) + if [ -z "$PAYURI" ]; then + PAYURI="taler://pay/${MH}/instances/${INST}/${OID}/?c=${OTOK}" + fi + PAYURI=$(printf '%s' "$PAYURI" | sed 's/:443\//\//g; s/:443?/?/g') + if ! printf '%s' "$PAYURI" | grep -q '^taler://pay/'; then + warn "shop $pname ($pid)" "bad pay URI: $PAYURI" + return 1 + fi + ok "shop $pname" "taler_pay_uri $PAYURI" + + if ! wcli_pay handle-uri --yes "$PAYURI" >"$SCRATCH/pay-$tag.out" 2>&1; then + warn "shop $pname ($pid)" "handle-uri failed — $(tail -c 120 "$SCRATCH/pay-$tag.out" | tr '\n' ' ')" + return 1 + fi + ok "shop $pname" "wallet accepted pay URI" + + local r AUTH="" + if [ -n "${MPW:-}" ]; then + AUTH="Authorization: Bearer secret-token:${MPW}" + fi + for r in 1 2 3; do + wcli_pay run-until-done >"$SCRATCH/pay-run-$tag.out" 2>&1 || true + wcli_pay transactions >"$SCRATCH/tx-$tag.out" 2>&1 || true + if [ -n "$AUTH" ]; then + curl -skS -m 8 -o "$SCRATCH/ord-paid-$tag.json" -H "$AUTH" \ + "${MERCHANT_PUBLIC}/instances/${INST}/private/orders/${OID}" 2>/dev/null || true + if python3 -c 'import json,sys +d=json.load(open(sys.argv[1])) +sys.exit(0 if d.get("paid") is True or str(d.get("order_status","")).lower()=="paid" else 1) +' "$SCRATCH/ord-paid-$tag.json" 2>/dev/null; then + ok "shop $pname" "payment settled ($pamt · order $OID)" + return 0 + fi + 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; then + ok "shop $pname" "payment settled via wallet tx ($pamt · order $OID)" + return 0 + fi + done + warn "shop $pname ($pid)" "not settled for order $OID" return 1 } @@ -974,10 +1105,65 @@ if [ "$PAY_OK" != "1" ]; then fi fi +# --------------------------------------------------------------------------- +section "e2e · GOA shop products (merchant landing catalog)" +# --------------------------------------------------------------------------- +# Public template POST + wallet pay — product id|name|amount (landing QR = pay-template). +SHOP_OK=0 +SHOP_OK_N=0 +SHOP_FAIL_N=0 +SHOP_SKIP_N=0 +SHOP_REPORT="" +if [ "$E2E_REMOTE" = "1" ] || [ "${CUR:-}" != "GOA" ]; then + info "goa-shop" "SKIPPED (remote or non-GOA currency)" +else + info "goa-shop" "public template pays for each sample (id · product name · amount)" + info "goa-shop payto" "$E2E_SHOP_PAYTO" + while IFS= read -r line; do + [ -z "$line" ] && continue + case "$line" in \#*) continue ;; esac + e2e_over && { warn "goa-shop" "time budget low — stopping product pays"; break; } + pid=$(printf '%s' "$line" | cut -d'|' -f1) + pname=$(printf '%s' "$line" | cut -d'|' -f2) + pamt=$(printf '%s' "$line" | cut -d'|' -f3) + [ -z "$pid" ] || [ -z "$pamt" ] && continue + [ -z "$pname" ] && pname="$pid" + av_now=$(wallet_avail_num) + pay_n=$(python3 -c 'import sys; print(float(sys.argv[1].split(":",1)[-1]))' "$pamt" 2>/dev/null || echo 0) + if ! python3 -c "import sys; sys.exit(0 if float(sys.argv[1]) + 1e-12 >= float(sys.argv[2]) else 1)" "$av_now" "$pay_n" 2>/dev/null; then + warn "goa-shop $pname ($pid)" "skip — insufficient avail ${CUR}:${av_now} (need ${pay_n})" + SHOP_SKIP_N=$((SHOP_SKIP_N + 1)) + SHOP_REPORT="${SHOP_REPORT}${SHOP_REPORT:+ }${pname}=SKIP(bal)" + continue + fi + set +e + e2e_one_pay_public_template "$pid" "$pname" "$pamt" + pc=$? + set -e + if [ "$pc" = "0" ]; then + SHOP_OK=1 + SHOP_OK_N=$((SHOP_OK_N + 1)) + PAY_OK=1 + PAY_OK_N=$((PAY_OK_N + 1)) + SHOP_REPORT="${SHOP_REPORT}${SHOP_REPORT:+ }${pname}=OK" + else + SHOP_FAIL_N=$((SHOP_FAIL_N + 1)) + SHOP_REPORT="${SHOP_REPORT}${SHOP_REPORT:+ }${pname}=FAIL" + fi + done </dev/null || echo "(n/a)")" info "scratch" "$SCRATCH" # Success if we had coins and at least one pay, OR coins + only pay skips (nothing affordable)