Unify and harden the payment flow (same API for the main site and all
consumer sites; per-client difference is only frontend_address).
- Payment gains STATUS_CANCELED. Gateways distinguish user-cancel from
failure (Mellat ResCode=17, SEP CanceledByUser, mock cancel=1) via a new
PaymentVerifyResult::canceled flag; callback sets canceled vs failed and
skips the circuit-breaker on cancel.
- Expiry job now cancels the pending payment when a booking lapses
(AppointmentExpiryService + PaymentRepository::findPendingByAppointment).
- frontend_address allowlist is read from the payment_allowed_frontend_hosts
site setting (manageable via PATCH /api/v1/admin/settings), falling back to
the ALLOWED_FRONTEND_HOSTS env var — so a new consumer site needs no code
change.
- .env: broaden CORS_ALLOW_ORIGIN to city subdomains (*.localhost /
*.clinic-pro.ddev.site) and add yazd-nobat.localhost to ALLOWED_FRONTEND_HOSTS.
- Update docs/api/payment.md and docs/api/admin.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>