Handle Mandate Verification

Complete the gateway mandate signing flow after /pay returns awaiting_verification.

curl -X POST "https://api.catalystpay.com/api/v1/payment-sessions/ps_abc123def456/verify/" \
  -H "Content-Type: application/json" \
  -d '{"verification_id": "vnd_verify_abc123"}'
import requests

result = requests.post(
    f"https://api.catalystpay.com/api/v1/payment-sessions/{token}/verify/",
    json={"verification_id": verification_id},  # Optional
).json()

if result["status"] == "completed":
    print(f"Payment verified. Transaction: {result['transaction']['id']}")
elif result["status"] == "failed":
    code = result["error_details"]["code"]
    if result["retry_allowed"]:
        print(f"Verification failed ({code}) — customer can retry payment")
    else:
        print(f"Verification failed ({code}) — no retries remaining")
const result = await fetch(
  `https://api.catalystpay.com/api/v1/payment-sessions/${token}/verify/`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ verification_id: verificationId }),
  }
).then(r => r.json());

if (result.status === 'completed') {
  console.log(`Payment verified. Transaction: ${result.transaction.id}`);
} else if (result.status === 'failed') {
  const code = result.error_details.code;
  if (result.retry_allowed) {
    // Redirect back to payment form for retry
    showRetryForm(code);
  } else {
    showTerminalFailure(code);
  }
}

/verify/ returns the same enriched envelope as /pay/ — branch on the top-level status.

Response (verified):

{
  "object": "payment_session.result",
  "session_token": "ps_abc123def456ghi789jkl012mno345",
  "status": "completed",
  "livemode": true,
  "created_at": "2026-04-15T11:01:59.803200Z",
  "processed_at": "2026-04-15T11:05:21.441823Z",
  "order_number": "ORD-44E833D6",
  "verification_url": null,
  "error": null,
  "retry_allowed": null,
  "transaction": {
    "id": "be57acb8-2c70-4af0-9ce0-a1e98b615641",
    "merchant_transaction_id": "TXN-20260415-BF60E17A",
    "gateway_reference": "116567486",
    "tenant_reference_id": "merchant-order-42",
    "status": "approved",
    "type": "sdd_sale"
  },
  "order": {
    "id": "f8a91004-5b21-4712-b40a-1c8da8aa822b",
    "order_number": "ORD-44E833D6",
    "status": "COMPLETED"
  },
  "subscription": null,
  "lead": { "id": "39efe9b8-29a6-482b-9376-e9501ff3579d" },
  "amount": "79.00",
  "currency": "EUR",
  "payment_method": {
    "type": "sepa_debit",
    "iban_last4": "3100",
    "iban_country": "DE",
    "bic": "DEUTDEDBKOE",
    "bank_name": "Deutsche Bank",
    "holder_name": "Max Mustermann"
  },
  "sepa": {
    "mandate_reference": null,
    "sequence_type": "FRST",
    "creditor_identifier": null
  },
  "adapter": { "name": "Vendo Services" },
  "payment_gateway": {
    "id": "b9e19a9e-4f54-4bec-b100-8995ea904ba4",
    "name": "merchant VENDO",
    "descriptor": "VendoStore*Merchant EU",
    "test_mode": false
  },
  "error_details": null
}

Response (verification failed):

status is failed and error_details is populated. Branch on error_details.code (stable enum) and retry_allowed to decide whether to show a retry form.

{
  "object": "payment_session.result",
  "status": "failed",
  "error": "Verification failed",
  "retry_allowed": true,
  "transaction": { "status": "declined", "...": "..." },
  "error_details": {
    "code": "mandate_rejected",
    "message": "SEPA mandate rejected",
    "gateway_code": "MD01",
    "gateway_message": "Mandate not signed"
  }
}

The verification_id field is optional. If omitted, CatalystPay uses the verification reference stored from the original /pay response. Only sessions in AWAITING_VERIFICATION status accept /verify calls. See Adapters for details on which gateways require verification.