Skill: franvinas/edgeos-applications (version 1.0.2)

--- name: edgeos-applications description: Apply to an EdgeOS popup city and buy tickets through OpenClaw. Use when users ask to authenticate by email OTP, submit/check popup applications, retrieve accepted applications and attendees, browse active products, purchase with checkout links, or settle crypto payments via /agent/buy-ticket (x402 + USDC on Base, optional AgentKit discount). --- # OpenClaw Popup Application (v1) Run the EdgeOS application flow with a strict, reliable sequence: 1. Authenticate user via OTP once per session. 2. Persist and reuse JWT for all subsequent calls in that session. 3. Resolve target popup. 4. Collect required application fields. 5. Submit application. 6. Check status on demand. 7. After acceptance, guide product selection and payment. Read these references before executing: - `{baseDir}/references/conversation-flow.md` - `{baseDir}/references/flow.md` - `{baseDir}/references/api-contract.md` (API source of truth) Before collecting fields, read workspace `USER.md` and use it as prefill context when values are relevant and trustworthy for the current user. ## Runtime configuration This v1 skill is intentionally pinned to PROD backend values (no user setup required). Values are defined once in: - `{baseDir}/scripts/env.sh` Do not ask users to configure runtime values in v1. Use the values from `scripts/env.sh` consistently for auth and API calls. ## Script-first rule Prefer scripts in `{baseDir}/scripts` over ad-hoc curl construction: - `auth_request_otp.sh` - `auth_login.sh` (persists JWT in local state file) - `submit_application.sh` - `check_application_status.sh` (supports `--application-id` or `--citizen-id` + `--popup-city-id`) - `payment_preview.sh` - `payment_create.sh` - `payment_status.sh` - `buy_ticket_challenge.sh` (x402 challenge from `/agent/buy-ticket`) - `buy_ticket_submit.sh` (x402 submit with `PAYMENT-SIGNATURE`, optional `AGENTKIT`) Only fall back to raw curl if a script cannot handle the case. ## Data collection policy (strict minimal questions) Use this exact sequence: 1. Determine required + optional application fields. 2. Fill all required fields and all optional fields using source priority (below), taking reasonable defaults only where policy allows. 3. Ask only for required fields that are still missing after source resolution. 4. Build a full field review covering all form questions and values (required + optional), excluding only internal workflow fields like `status`. 5. For every unresolved field, ask the user and label it as required or optional. 6. After all fields are resolved (or explicitly skipped if optional), ask if the user wants changes or submit as-is; submit only after explicit approval. ### Source priority (authoritative order) 1. `USER.md` (primary source for all mappable fields) 2. Authenticated API profile/application context 3. Current session and prior user turns 4. Channel sender metadata inference If a field is present in `USER.md`, prefer that value unless the user explicitly overrides it in the current conversation. Additional rules: - For names specifically, infer before asking (profile -> known identity -> sender display name parsing). - Infer gender from pronouns/known context by default; ask only if completely unknown. - Infer `local_resident` from known residence/location context by default (example: user based in Buenos Aires => not local resident for Edge Esmeralda). - Treat `duration` as required and normalize it to one of: `1 week`, `1 weekend`, `2 weeks`, `a few days`, `full length`. - Never re-ask a field that is already known unless the user asks to change it. - Treat generic confirmations ("yes", "ok") as approval signals, not as field values. - Infer optional booleans from user context when plausible (e.g., technical builder profile => `builder_boolean=true`; investor intent absent => `investor=false`). - If boolean inference is not possible, ask the user (marking as optional) instead of silently defaulting. - Do not invent personal free-text answers (e.g., goals). Ask when unknown (marking as optional). - Required fields must always be resolved before submit. ## Session auth handling - After successful OTP login, persist JWT using `auth_login.sh` and reuse it for all subsequent API requests. - Runtime scripts auto-load JWT from per-email state files under `{baseDir}/scripts/.state/`. - State is keyed by email; set `SESSION_EMAIL` when running scripts to select the correct token. - Do not request OTP again while JWT works. - On unauthorized responses, reload JWT from script state and retry once before asking for new OTP. - Only re-authenticate when an API call still returns unauthorized/invalid token (401) after retry, or token is truly missing. ## Crypto payment support (x402) When the user chooses to pay with crypto, the skill uses `POST /agent/buy-ticket` (x402 protocol). The skill gets payment requirements from the server, provides them to the agent, and the agent signs the USDC transfer with whatever wallet it has configured (CDP, MCP tool, etc.). The skill does NOT handle wallet signing — it provides the data and the agent handles signing. World ID-verified agents (via AgentKit) receive a 10% discount automatically when the `AGENTKIT` header is included with a valid signed payload. ## Guardrails - Never expose full OTP codes or JWTs. - Never force server-owned status transitions. - Never create a payment unless application status is `accepted`. - Never submit an application for a popup that has already ended (`end_date < now`). - Keep prompts short and ask one question at a time. - Confirm before final submission. - Do not show internal IDs (product IDs, popup IDs, attendee IDs, citizen IDs, payment IDs) unless the user explicitly asks. - Present products in user-friendly terms (name, what it includes, price), then map to IDs internally. - Never expose wallet private keys or raw transaction signatures in chat output. - When presenting x402 payment info, show human-readable amounts (e.g. "$10.00 USDC") not atomic units. - Convert USDC atomic amounts: divide by 1,000,000 for display (e.g. "1000000" → "$1.00"). ## Outputs to return - current `status` - concise next step guidance - include IDs only when operationally useful or when explicitly requested by the user On duplicate submissions, return existing application status instead of surfacing a raw failure.

path: _meta.json
content: ownerId: kn7edvd78g03asvtekxc6axdan8294fy
slug: edgeos-applications
version: 1.0.1
publishedAt: 1772727521709

path: references/api-contract.md
content: # API Contract (EdgeOS Applications + Ticket Purchase)

Use this file as the canonical contract for this skill runtime.
Base URL: `https://api-citizen-portal.simplefi.tech`.

## Auth

### 1) Request OTP code
- **POST** `/citizens/authenticate`
- Body: `{"email":"[REDACTED_SECRET]","use_code":true}`

### 2) Exchange OTP for JWT
- **POST** `/citizens/login?email=<urlencoded>&code=<6digit>`
- Success: `{"access_token":"...","token_type":"bearer"}`

Re-auth rule:
- Re-authenticate only on `401` (expired/invalid token).

## Applications + attendees

### 3) List applications
- **GET** `/applications`
- Header: `Authorization: Bearer <JWT>`
- Find the target application where `status == "accepted"`.
- Key fields: `id`, `popup_city_id`, `status`.

### 4) Get application details
- **GET** `/applications/{application_id}`
- Header: `Authorization: Bearer <JWT>`
- Use `attendees[].id` for ticket purchase.

## Products

### 5) List products
- **GET** `/products?popup_city_id=<id>`
- Header: `Authorization: Bearer <JWT>`
- Keep only `is_active == true`.
- Key fields:
  - `id`, `name`, `price`, `category`, `attendee_category`, `insurance_percentage`
  - `min_price` / `max_price` for variable-price products

## Payments (x402 via /agent/buy-ticket)

### 6) First request (expect 402 challenge)
- **POST** `/agent/buy-ticket`
- Header: `Authorization: Bearer <JWT>`
- Body:
  - `application_id`
  - `products[]` items with `product_id`, `attendee_id`, `quantity`
  - optional: `coupon_code`, `insurance`, `custom_amount` (required for variable-price products)
- No `PAYMENT-SIGNATURE` header in this first call.
- Expected: **402 Payment Required**

From `accepts[0]`, extract:
- `amount` (USDC atomic units, 6 decimals)
- `payTo`
- `network` (expected `eip155:8453`)
- `asset` (USDC on Base)
- `maxTimeoutSeconds`
- `extra` (`name`, `version`, `assetTransferMethod`)

The same payload is also present in `PAYMENT-REQUIRED` header (base64 JSON).

### 7) Sign EIP-3009 TransferWithAuthorization
Use EIP-712 typed data:
- Domain:
  - `name: "USD Coin"`
  - `version: "2"`
  - `chainId: 8453`
  - `verifyingContract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
- Type: `TransferWithAuthorization`
- Message:
  - `from`: user wallet
  - `to`: `payTo`
  - `value`: `amount`
  - `validAfter`: `now - 600`
  - `validBefore`: `now + maxTimeoutSeconds`
  - `nonce`: random 32-byte hex

### 8) Submit payment
- Re-send **same** `POST /agent/buy-ticket` body
- Add header `PAYMENT-SIGNATURE` with base64 JSON:
  - `x402Version`, `resource`, `accepted`, `payload.signature`, `payload.authorization`
- Optional: add `AGENTKIT` header for discount proof

Success:
- **200** with approved payment object (`source: "x402"`)
- `PAYMENT-RESPONSE` header contains base64 JSON with tx hash (`transaction`) and `network`

## AgentKit optional discount

If `extensions.agentkit` is present in 402 response:
- Build SIWE (EIP-4361) message from challenge (`domain`, `uri`, `version`, `nonce`, `issuedAt`, `resources`)
- Sign with `personal_sign` (EIP-191)
- Send `AGENTKIT` header (base64 JSON) with fields:
  - `domain`, `address`, `uri`, `version`, `chainId`, `type`, `nonce`, `issuedAt`, `signature`, `resources`

If verification succeeds and wallet is registered, discount is applied before payment verification.
Sign EIP-3009 for the discounted amount.

## Guardrails

- Never create or settle payment when application is not `accepted`.
- Keep internal IDs hidden unless user asks.
- Never expose OTP/JWT or raw private key material.
- Display USDC amounts in human units for chat (`atomic / 1_000_000`).
- For the second `/agent/buy-ticket` call, keep body identical to first call.


path: references/conversation-flow.md
content: # Conversation Flow (v1)

## Contract
- Keep messages short.
- Ask one question at a time.
- Confirm critical choices before submit.
- Pre-fill fields already known.
- Ask only for missing required fields.
- Do not show internal IDs unless user asks.

## Step 1 — Authenticate
1. If JWT exists and works, skip OTP.
2. Otherwise ask email.
3. Trigger OTP via `scripts/auth_request_otp.sh <email>`.
4. Ask for 6-digit code.
5. Exchange code via `scripts/auth_login.sh <email> <code>`.
6. Persist authenticated email (`SESSION_EMAIL`) for subsequent script calls.

Re-auth:
- Re-auth only after real `401`.

## Step 2 — Find accepted application
1. `GET /applications`.
2. Select accepted application.
3. Confirm target popup if multiple accepted applications exist.
4. Fetch `GET /applications/{application_id}` to resolve attendees.

## Step 3 — Product selection
1. Fetch products for popup: `GET /products?popup_city_id=<id>`.
2. Show active products in user language: name + price + relevant constraints.
3. Ask user to choose product(s) and attendee mapping.
4. Ask payment mode with wallet-awareness and discount context, using this wording:
   - "You can get a checkout link, or if you have a wallet configured I can pay directly via x402 (USDC on Base). Paying via x402 with a World ID verified agent gets you a 10% discount. Do you have a wallet set up?"

## Step 4 — Payment flow

### A) Crypto (x402)
1. Run `buy_ticket_challenge.sh --payload-file <payment-body.json>` (expect 402).
2. Parse challenge (`amount`, `payTo`, `network`, `asset`, `maxTimeoutSeconds`).
3. Tell user/agent exactly what will be signed.
4. Sign EIP-3009 TransferWithAuthorization.
5. Run `buy_ticket_submit.sh --payload-file <same-payment-body.json> --payment-signature-b64 <...>`.
6. If AgentKit challenge exists, include `--agentkit-b64 <...>` for discount.
7. Return success with settled amount + tx hash.

### B) Checkout link
1. `payment_preview.sh` and confirm total.
2. `payment_create.sh` and return checkout URL.
3. `payment_status.sh` only if user asks later.

## Error UX
- 401 → "Session expired, let's re-authenticate."
- First 402 → expected challenge, continue.
- Second 402 → "Payment verification failed" + actionable checks.
- 400 → ask user to correct only invalid input.
- 500 → explain settlement failure and offer retry.


path: references/flow.md
content: # EdgeOS API Flow (v1)

Base settings:
- source `{baseDir}/scripts/env.sh`
- `BASE_URL` comes from that file
- Auth header: `Authorization: Bearer $JWT`

## 1) Request OTP
Use script: `scripts/auth_request_otp.sh <email>`
(under the hood: `POST /citizens/authenticate` with `use_code=true`)

## 2) Exchange OTP for JWT
Use script: `scripts/auth_login.sh <email> <6digit>`
(under the hood: `POST /citizens/login?email=<urlencoded>&code=<6digit>`)

`auth_login.sh` persists `access_token` into script state under `scripts/.state/` keyed by email.
Other scripts auto-load token for `SESSION_EMAIL` when `JWT` env is not provided.
Do not request a new OTP unless API returns 401/unauthorized or JWT is missing.

## 3) Resolve accepted application
- `GET /applications`
- Select application with `status=accepted`
- Store `application_id` and `popup_city_id`

## 4) Resolve attendees
- `GET /applications/{application_id}`
- Extract `attendees[].id` for each purchase line

## 5) Product retrieval (accepted only)
- `GET /products?popup_city_id=<id>`
- Keep only active products (`is_active=true`)
- Support fixed-price and variable-price products (`min_price` / `max_price` + `custom_amount`)

## 6) Checkout-link flow (SimpleFi)
- Preview: `scripts/payment_preview.sh`
- Create: `scripts/payment_create.sh`
- Status: `scripts/payment_status.sh`

## 7) Crypto flow (x402 via /agent/buy-ticket)
When user chooses crypto payment:

**Step 1 — Get payment challenge:**
- Use script: `scripts/buy_ticket_challenge.sh --payload-file <payment-create-body.json>`
- (under the hood: `POST /agent/buy-ticket` with JWT + payment body, no `PAYMENT-SIGNATURE`)
- Expect 402

Parse from 402 JSON:
- `accepts[0].amount` (USDC atomic)
- `accepts[0].payTo`
- `accepts[0].network`
- `accepts[0].asset`
- `accepts[0].maxTimeoutSeconds`
- `accepts[0].extra`
- optional `extensions.agentkit` challenge/discount

**Step 2 — Sign EIP-3009:**
- Domain uses USD Coin v2 on Base (chainId 8453)
- Sign `TransferWithAuthorization(from,to,value,validAfter,validBefore,nonce)`

**Step 3 — Submit payment:**
- Use script: `scripts/buy_ticket_submit.sh --payload-file <same-body.json> --payment-signature-b64 <...> [--agentkit-b64 <...>]`
- (or pass `--payment-signature-file` / `--agentkit-file` JSON files and let the script base64-encode)
- Re-send exact same body to `POST /agent/buy-ticket`
- Include `PAYMENT-SIGNATURE` header (base64 x402 payload)
- Include `AGENTKIT` header when discount proof is available
- Success returns approved payment + transaction hash in `PAYMENT-RESPONSE`

## 8) Error handling
- `401`: re-auth and retry
- first `402`: expected challenge
- second `402`: payment verification failure (balance/signature/timing/amount)
- `400`: invalid request (wrong ids/status/body constraints)
- `500`: settlement/on-chain failure; retry after wallet/state checks


path: scripts/auth_login.sh
content: #!/usr/bin/env bash
set -euo pipefail

source "$(dirname "$0")/env.sh"
EMAIL="${1:-}"
CODE="${2:-}"

if [[ -z "$EMAIL" || -z "$CODE" ]]; then
  echo '{"ok":false,"error":"Usage: auth_login.sh <email> <otp_code>"}'
  exit 1
fi

EMAIL_ENC="$(EMAIL="$EMAIL" python3 -c 'import urllib.parse,os; print(urllib.parse.quote(os.environ["EMAIL"], safe=""))' )"

body=$(curl -sS -X POST "$BASE_URL/citizens/login?email=$EMAIL_ENC&code=$CODE")

jwt=$(echo "$body" | grep -o '"access_token"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n1 | sed 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/')

if [[ -z "${jwt:-}" ]]; then
  echo '{"ok":false,"error":"No access_token in response"}'
  exit 1
fi

save_jwt_to_state "$jwt" "$EMAIL"
printf '{"ok":true,"token_saved":true,"email":"%s"}\n' "$EMAIL"


path: scripts/auth_request_otp.sh
content: #!/usr/bin/env bash
set -euo pipefail

source "$(dirname "$0")/env.sh"
EMAIL="${1:-}"

if [[ -z "$EMAIL" ]]; then
  echo '{"ok":false,"error":"Usage: auth_request_otp.sh <email>"}'
  exit 1
fi

body=$(curl -sS -X POST "$BASE_URL/citizens/authenticate" \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"$EMAIL\",\"use_code\":true}")

message=$(echo "$body" | grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n1 | sed 's/.*"message"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/')
printf '{"ok":true,"message":"%s"}\n' "${message:-otp_sent}"


path: scripts/buy_ticket_challenge.sh
content: #!/usr/bin/env bash
set -euo pipefail

# Get x402 payment challenge for /agent/buy-ticket
#
# Requires:
#   JWT (or SESSION_EMAIL + saved state token)
# Args:
#   --payload-file <path-to-json>

source "$(dirname "$0")/env.sh"
JWT="${JWT:-}"
load_jwt_from_state
PAYLOAD_FILE=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --payload-file) PAYLOAD_FILE="$2"; shift 2 ;;
    *) echo "Unknown arg: $1" >&2; exit 2 ;;
  esac
done

if [[ -z "$JWT" ]]; then
  echo '{"ok":false,"error":"JWT is required in env"}'
  exit 1
fi
if [[ -z "$PAYLOAD_FILE" || ! -f "$PAYLOAD_FILE" ]]; then
  echo '{"ok":false,"error":"--payload-file is required and must exist"}'
  exit 1
fi

HDR_FILE=$(mktemp)
RESP_FILE=$(mktemp)
HTTP_CODE=$(mktemp)

curl -sS -X POST "$BASE_URL/agent/buy-ticket" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT" \
  --data-binary "@$PAYLOAD_FILE" \
  -D "$HDR_FILE" \
  -o "$RESP_FILE" \
  -w '%{http_code}' > "$HTTP_CODE"

code=$(cat "$HTTP_CODE")
body=$(cat "$RESP_FILE")
payment_required=$(grep -i '^PAYMENT-REQUIRED:' "$HDR_FILE" | tail -n1 | sed -E 's/^PAYMENT-REQUIRED:[[:space:]]*//I' | tr -d '\r')

rm -f "$HDR_FILE" "$RESP_FILE" "$HTTP_CODE"

if [[ "$code" != "402" ]]; then
  safe_body=$(BODY="$body" python3 - <<'PY'
import json, os
print(json.dumps(os.environ.get('BODY','')))
PY
)
  printf '{"ok":false,"http":%s,"error":"expected_402_payment_required","response_body":%s}\n' "$code" "$safe_body"
  exit 1
fi

printf '%s' "$body" | python3 - "$code" "$payment_required" <<'PY'
import json, sys

http = int(sys.argv[1])
payment_required = sys.argv[2]
raw = sys.stdin.read().strip() or '{}'

try:
    data = json.loads(raw)
except Exception:
    data = {}

accept = (data.get('accepts') or [{}])[0] if isinstance(data.get('accepts'), list) else {}
resource = data.get('resource') or {}
extra = accept.get('extra') or {}
agentkit = (data.get('extensions') or {}).get('agentkit') or {}
mode = agentkit.get('mode') or {}

out = {
    'ok': True,
    'http': http,
    'x402_version': data.get('x402Version'),
    'resource_url': resource.get('url'),
    'amount_atomic': accept.get('amount'),
    'pay_to': accept.get('payTo'),
    'network': accept.get('network'),
    'asset': accept.get('asset'),
    'max_timeout_seconds': accept.get('maxTimeoutSeconds'),
    'asset_name': extra.get('name'),
    'asset_version': extra.get('version'),
    'asset_transfer_method': extra.get('assetTransferMethod'),
    'discount_type': mode.get('type'),
    'discount_percent': mode.get('percent'),
    'payment_required_header_b64': payment_required,
    'challenge': data,
}

print(json.dumps(out, separators=(',', ':')))
PY


path: scripts/buy_ticket_submit.sh
content: #!/usr/bin/env bash
set -euo pipefail

# Submit x402 payment for /agent/buy-ticket
#
# Requires:
#   JWT (or SESSION_EMAIL + saved state token)
# Args:
#   --payload-file <path-to-json>                 (must be identical to challenge request body)
#   --payment-signature-b64 <base64-json>
#     OR --payment-signature-file <path-to-json>  (raw JSON; script base64-encodes it)
#   [--agentkit-b64 <base64-json>]
#   [--agentkit-file <path-to-json>]              (raw JSON; script base64-encodes it)

source "$(dirname "$0")/env.sh"
JWT="${JWT:-}"
load_jwt_from_state
PAYLOAD_FILE=""
PAYMENT_SIGNATURE_B64=""
PAYMENT_SIGNATURE_FILE=""
AGENTKIT_B64=""
AGENTKIT_FILE=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --payload-file) PAYLOAD_FILE="$2"; shift 2 ;;
    --payment-signature-b64) PAYMENT_SIGNATURE_B64="$2"; shift 2 ;;
    --payment-signature-file) PAYMENT_SIGNATURE_FILE="$2"; shift 2 ;;
    --agentkit-b64) AGENTKIT_B64="$2"; shift 2 ;;
    --agentkit-file) AGENTKIT_FILE="$2"; shift 2 ;;
    *) echo "Unknown arg: $1" >&2; exit 2 ;;
  esac
done

if [[ -z "$JWT" ]]; then
  echo '{"ok":false,"error":"JWT is required in env"}'
  exit 1
fi
if [[ -z "$PAYLOAD_FILE" || ! -f "$PAYLOAD_FILE" ]]; then
  echo '{"ok":false,"error":"--payload-file is required and must exist"}'
  exit 1
fi

if [[ -z "$PAYMENT_SIGNATURE_B64" && -z "$PAYMENT_SIGNATURE_FILE" ]]; then
  echo '{"ok":false,"error":"Provide --payment-signature-b64 or --payment-signature-file"}'
  exit 1
fi
if [[ -n "$PAYMENT_SIGNATURE_FILE" && ! -f "$PAYMENT_SIGNATURE_FILE" ]]; then
  echo '{"ok":false,"error":"--payment-signature-file must exist"}'
  exit 1
fi
if [[ -n "$AGENTKIT_FILE" && ! -f "$AGENTKIT_FILE" ]]; then
  echo '{"ok":false,"error":"--agentkit-file must exist"}'
  exit 1
fi

if [[ -z "$PAYMENT_SIGNATURE_B64" && -n "$PAYMENT_SIGNATURE_FILE" ]]; then
  PAYMENT_SIGNATURE_B64=$(base64 -w 0 "$PAYMENT_SIGNATURE_FILE")
fi
if [[ -z "$AGENTKIT_B64" && -n "$AGENTKIT_FILE" ]]; then
  AGENTKIT_B64=$(base64 -w 0 "$AGENTKIT_FILE")
fi

HDR_FILE=$(mktemp)
RESP_FILE=$(mktemp)
HTTP_CODE=$(mktemp)

curl_cmd=(curl -sS -X POST "$BASE_URL/agent/buy-ticket"
  -H "Content-Type: application/json"
  -H "Authorization: Bearer $JWT"
  -H "PAYMENT-SIGNATURE: $PAYMENT_SIGNATURE_B64"
  --data-binary "@$PAYLOAD_FILE"
  -D "$HDR_FILE"
  -o "$RESP_FILE"
  -w '%{http_code}')

if [[ -n "$AGENTKIT_B64" ]]; then
  curl_cmd+=( -H "AGENTKIT: $AGENTKIT_B64" )
fi

"${curl_cmd[@]}" > "$HTTP_CODE"

code=$(cat "$HTTP_CODE")
body=$(cat "$RESP_FILE")
payment_response=$(grep -i '^PAYMENT-RESPONSE:' "$HDR_FILE" | tail -n1 | sed -E 's/^PAYMENT-RESPONSE:[[:space:]]*//I' | tr -d '\r')

rm -f "$HDR_FILE" "$RESP_FILE" "$HTTP_CODE"

if [[ "$code" -lt 200 || "$code" -ge 300 ]]; then
  safe_body=$(BODY="$body" python3 - <<'PY'
import json, os
print(json.dumps(os.environ.get('BODY','')))
PY
)
  printf '{"ok":false,"http":%s,"error":"buy_ticket_submit_failed","response_body":%s}\n' "$code" "$safe_body"
  exit 1
fi

printf '%s' "$body" | python3 - "$code" "$payment_response" <<'PY'
import base64, json, sys

http = int(sys.argv[1])
payment_response_b64 = sys.argv[2]
raw = sys.stdin.read().strip() or '{}'

try:
    data = json.loads(raw)
except Exception:
    data = {}

header_data = {}
if payment_response_b64:
    try:
        header_data = json.loads(base64.b64decode(payment_response_b64).decode('utf-8'))
    except Exception:
        header_data = {}

out = {
    'ok': True,
    'http': http,
    'payment_id': data.get('id'),
    'status': data.get('status'),
    'amount': data.get('amount'),
    'currency': data.get('currency'),
    'source': data.get('source'),
    'external_id': data.get('external_id'),
    'transaction': header_data.get('transaction') or data.get('external_id'),
    'network': header_data.get('network'),
    'payment_response_header_b64': payment_response_b64,
    'response': data,
}

print(json.dumps(out, separators=(',', ':')))
PY


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector:  ()