TrieOH
UniventsAPIPurchases

List my purchases

Lists the authenticated user's purchases with their items, newest first. The purchases table is the record of truth (issue #61); items come from `purchase_items` — the availability ledger (D4). Read-only; no state transitions.

GET/purchases

Lists the authenticated user's purchases with their items, newest first. The purchases table is the record of truth (issue #61); items come from purchase_items — the availability ledger (D4). Read-only; no state transitions.

Authorization

bearerAuth
AuthorizationBearer <token>

JWT issued by IdentityX, signed with Ed25519

In: header

Response Body

application/json

application/json

application/json

curl -X GET "https://example.com/purchases"
{
  "code": 0,
  "message": "string",
  "error": {
    "code": "BAD_REQUEST",
    "message": "string",
    "fields": [
      {
        "field": "string",
        "message": "string"
      }
    ],
    "meta": {},
    "debug": {
      "raw_error": "string",
      "stack_trace": "string"
    }
  },
  "pagination": {
    "page": 1,
    "limit": 1,
    "total": 0,
    "has_next": true,
    "has_prev": true,
    "next_page": 0,
    "prev_page": 0
  },
  "timestamp": "2019-08-24T14:15:22Z",
  "module": "string",
  "data": {
    "purchases": [
      {
        "purchase_id": "06b23ec9-3221-4327-b592-c31d5c56b2f8",
        "edition_id": "aeda7fca-56fe-426a-8c37-1876faaaf005",
        "status": "pending",
        "status_reason": "string",
        "total_cents": 0,
        "currency": "string",
        "payment_method": "string",
        "payssage_intent_id": "00469442-a68b-4aa4-a184-b15bbc9c37fe",
        "qr_code": "string",
        "qr_code_base64": "string",
        "expires_at": "2019-08-24T14:15:22Z",
        "created_at": "2019-08-24T14:15:22Z",
        "items": [
          {
            "item_type": "ticket",
            "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",
            "quantity": 1,
            "unit_price_cents": 0
          }
        ]
      }
    ]
  }
}
{
  "code": 0,
  "message": "string",
  "error": {
    "code": "BAD_REQUEST",
    "message": "string",
    "fields": [
      {
        "field": "string",
        "message": "string"
      }
    ],
    "meta": {},
    "debug": {
      "raw_error": "string",
      "stack_trace": "string"
    }
  },
  "timestamp": "2019-08-24T14:15:22Z",
  "module": "string"
}
{
  "code": 0,
  "message": "string",
  "error": {
    "code": "BAD_REQUEST",
    "message": "string",
    "fields": [
      {
        "field": "string",
        "message": "string"
      }
    ],
    "meta": {},
    "debug": {
      "raw_error": "string",
      "stack_trace": "string"
    }
  },
  "timestamp": "2019-08-24T14:15:22Z",
  "module": "string"
}

Receive a Payssage webhook delivery POST

Ingestion endpoint called by Payssage when a payment event occurs on an intent. The body is the Payssage delivery envelope (D2) — the correlation key is `intent_id`, never a provider-specific id: ```json { "intent_id": "...", "wallet_id": "...", "provider": "mercadopago", "external_id": "<provider_payment_id>", "event_type": "payment.succeeded", "status_detail": "high_risk", "payload": { "...": "raw provider payload" } } ``` Authenticated by the `X-Payssage-Signature` header — hex (HMAC-SHA256(raw body, `PAYSSAGE_WEBHOOK_SECRET`)). A signature mismatch is rejected with 400 (Payssage does not retry). Returns 200 after successful processing so Payssage stops retrying; returns non-2xx when the purchase cannot be correlated yet (card race, D3) so Payssage retries. This is the only component that confirms payment — checkout never self-approves (D3). Public: Payssage cannot send an Authorization header.

Issue a one-time WebSocket handshake token GET

Owner-only: the caller must be the purchase's `purchaser_id` (anything else is 404 — no existence leak). Issues a fresh one-time token for the raw `WS /ws?token=...` socket: a 32-byte random value stored as its SHA-256 hash, valid 10 minutes, consumed by the first handshake. The token is a handshake-auth shim — it proves prior REST auth for this purchase and nothing more (no refresh dance, no scopes). Reconnect = fresh token: the front re-requests before the 10-minute expiry and re-opens the socket.