veza/veza-backend-api/internal/handlers
senke 92cf6d6f76 feat(backend,marketplace): refund reverse-charge with idempotent webhook
Fourth item of the v1.0.6 backlog, and the structuring one — the pre-
v1.0.6 RefundOrder wrote `status='refunded'` to the DB and called
Hyperswitch synchronously in the same transaction, treating the API
ack as terminal confirmation. In reality Hyperswitch returns `pending`
and only finalizes via webhook. Customers could see "refunded" in the
UI while their bank was still uncredited, and the seller balance
stayed credited even on successful refunds.

v1.0.6 flow
  Phase 1 — open a pending refund (short row-locked transaction):
    * validate permissions + 14-day window + double-submit guard
    * persist Refund{status=pending}
    * flip order to `refund_pending` (not `refunded` — that's the
      webhook's job)
  Phase 2 — call PSP outside the transaction:
    * Provider.CreateRefund returns (refund_id, status, err). The
      refund_id is the unique idempotency key for the webhook.
    * on PSP error: mark Refund{status=failed}, roll order back to
      `completed` so the buyer can retry.
    * on success: persist hyperswitch_refund_id, stay in `pending`
      even if the sync status is "succeeded". The webhook is the only
      authoritative signal. (Per customer guidance: "ne jamais flipper
      à succeeded sur la réponse synchrone du POST".)
  Phase 3 — webhook drives terminal state:
    * ProcessRefundWebhook looks up by hyperswitch_refund_id (UNIQUE
      constraint in the new `refunds` table guarantees idempotency).
    * terminal-state short-circuit: IsTerminal() returns 200 without
      mutating anything, so a Hyperswitch retry storm is safe.
    * on refund.succeeded: flip refund + order to succeeded/refunded,
      revoke licenses, debit seller balance, mark every SellerTransfer
      for the order as `reversed`. All within a row-locked tx.
    * on refund.failed: flip refund to failed, order back to
      `completed`.

Seller-side reconciliation
  * SellerBalance.DebitSellerBalance was using Postgres-only GREATEST,
    which silently failed on SQLite tests. Ported to a portable
    CASE WHEN that clamps at zero in both DBs.
  * SellerTransfer.Status = "reversed" captures the refund event in
    the ledger. The actual Stripe Connect Transfers:reversal call is
    flagged TODO(v1.0.7) — requires wiring through TransferService
    with connected-account context that the current transfer worker
    doesn't expose. The internal balance is corrected here so the
    buyer and seller views match as soon as the PSP confirms; the
    missing piece is purely the money-movement round-trip at Stripe.

Webhook routing
  * HyperswitchWebhookPayload extended with event_type + refund_id +
    error_message, with flat and nested (object.*) shapes supported
    (same tolerance as the existing payment fields).
  * New IsRefundEvent() discriminator: matches any event_type
    containing "refund" (case-insensitive) or presence of refund_id.
    routes_webhooks.go peeks the payload once and dispatches to
    ProcessRefundWebhook or ProcessPaymentWebhook.
  * No signature-verification changes — the same HMAC-SHA512 check
    protects both paths.

Handler response
  * POST /marketplace/orders/:id/refund now returns
    `{ refund: { id, status: "pending" }, message }` so the UI can
    surface the in-flight state. A new ErrRefundAlreadyRequested maps
    to 400 with a "already in progress" message instead of silently
    creating a duplicate row (the double-submit guard checks order
    status = `refund_pending` *before* the existing-row check so the
    error is explicit).

Schema
  * Migration 978_refunds_table.sql adds the `refunds` table with
    UNIQUE(hyperswitch_refund_id). The uniqueness constraint is the
    load-bearing idempotency guarantee — a duplicate PSP notification
    lands on the same DB row, and the webhook handler's
    FOR UPDATE + IsTerminal() check turns it into a no-op.
  * hyperswitch_refund_id is nullable (NULL between Phase 1 and
    Phase 2) so the UNIQUE index ignores rows that haven't been
    assigned a PSP id yet.

Partial refunds
  * The Provider.CreateRefund signature carries `amount *int64`
    already (nil = full), but the service call-site passes nil. Full
    refunds only for v1.0.6 — partial-refund UX needs a product
    decision and is deferred to v1.0.7. Flagged in the ErrRefund*
    section.

Tests (15 cases, all sqlite-in-memory + httptest-style mock provider)
  * RefundOrder phase 1
      - OpensPendingRefund: pending state, refund_id captured, order
        → refund_pending, licenses untouched
      - PSPErrorRollsBack: failed state, order reverts to completed
      - DoubleRequestRejected: second call returns
        ErrRefundAlreadyRequested, not a generic ErrOrderNotRefundable
      - NotCompleted / NoPaymentID / Forbidden / SellerCanRefund
      - ExpiredRefundWindow / FallbackExpiredNoDeadline
  * ProcessRefundWebhook
      - SucceededFinalizesState: refund + order + licenses + seller
        balance + seller transfer all reconciled in one tx
      - FailedRollsOrderBack: order returns to completed for retry
      - IsRefundEventIdempotentOnReplay: second webhook asserts
        succeeded_at timestamp is *unchanged*, proving the second
        invocation bailed out on IsTerminal (not re-ran)
      - UnknownRefundIDReturnsOK: never-issued refund_id → 200 silent
        (avoids a Hyperswitch retry storm on stale events)
      - MissingRefundID: explicit 400 error
      - NonTerminalStatusIgnored: pending/processing leave the row
        alone
  * HyperswitchWebhookPayload.IsRefundEvent: 6 dispatcher cases
    (flat event_type, mixed case, payment event, refund_id alone,
    empty, nested object.refund_id)

Backward compat
  * hyperswitch.Provider still exposes the old Refund(ctx,...) error
    method for any call-site that only cared about success/failure.
  * Old mockRefundPaymentProvider replaced; external mocks need to
    add CreateRefund — the interface is now (refundID, status, err).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 02:02:57 +02:00
..
account_deletion_handler.go fix(v0.12.6): apply all pentest remediations — 36 findings across 36 files 2026-03-14 00:44:46 +01:00
admin_transfer_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
admin_transfer_handler_test.go test(admin): add admin transfer handler tests 2026-02-23 23:35:11 +01:00
announcement_handler.go feat: backend — config, handlers, services, logging, migration 2026-03-23 15:46:57 +01:00
announcement_handler_test.go test(v0.803): unit tests for CCPA, reports, announcements, feature flags 2026-02-25 20:02:24 +01:00
api_flow_test.go stabilizing veza-backend-api: phase 1 2025-12-16 11:23:49 -05:00
api_key_handler.go feat(developer): add API keys backend (Lot C) 2026-02-20 00:18:36 +01:00
audit.go api-contracts: update backend handlers to use wrapped format 2026-01-15 17:32:02 +01:00
audit_test.go stabilisation commit A 2026-01-07 19:39:21 +01:00
auth.go feat(v0.12.6.2): enforce MFA for admin/moderator + align refresh token TTL to 7 days 2026-03-12 06:53:27 +01:00
auth_fuzz_test.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
auth_handler_test.go fix(backend,infra): send real verification emails + fail-loud in prod 2026-04-16 14:52:46 +02:00
auth_integration_test.go ci: bump Go to 1.25 and fix goimports drift in 3 files 2026-04-14 17:02:09 +02:00
avatar_handler.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
avatar_handler_test.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
bitrate_handler.go api-contracts: update backend handlers to use wrapped format 2026-01-15 17:32:02 +01:00
bitrate_handler_test.go fix: resolve stream server compilation errors and integrate chat stability fixes 2026-01-04 01:44:22 +01:00
chat_attachment_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
chat_handler.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
chat_handler_test.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
chat_reaction_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
chat_search_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
chat_websocket_handler.go fix(v0.12.6): apply all pentest remediations — 36 findings across 36 files 2026-03-14 00:44:46 +01:00
cloud_handler.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
co_listening_handler.go feat(v0.10.7): Collaboration Temps Réel F481-F483 2026-03-10 13:34:16 +01:00
co_listening_websocket_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
comment_handler.go fix(v0.12.6.1): remediate remaining 15 MEDIUM + LOW pentest findings 2026-03-12 06:13:38 +01:00
comment_handler_test.go chore: consolidate CI, E2E, backend and frontend updates 2026-02-17 16:43:21 +01:00
common.go refactor(backend): J3 — remove 3 deprecated unused handlers 2026-04-14 18:11:07 +02:00
config_reload.go v0.9.4 2026-03-05 23:03:43 +01:00
config_reload_test.go incus deployement fully implemented, Makefile updated and make fmt ran 2026-01-13 19:47:57 +01:00
csrf.go state-ownership: delete unused optimisticStoreUpdates.ts file 2026-01-15 19:26:53 +01:00
csrf_test.go chore(v0.102): consolidate remaining changes — docs, frontend, backend 2026-02-20 13:02:12 +01:00
dashboard.go release(v0.903): Vault - ORDER BY whitelist, rate limiter, VERSION sync, chat-server cleanup, Go 1.24 2026-02-27 09:43:25 +01:00
dashboard_test.go chore(release): v0.922 — Greenlight (handler tests: dashboard, presence) 2026-03-02 12:30:51 +01:00
distribution_handler.go feat: backend, stream server & infra improvements 2026-03-18 11:36:06 +01:00
distribution_handler_test.go feat(v0.12.2): F501-F510 distribution service, handler, and routes 2026-03-10 19:54:26 +01:00
education_handler.go feat(backend,web): single source of truth for upload-size limits 2026-04-16 19:37:37 +02:00
education_handler_test.go feat(v0.12.3): F276-F305 video upload, HLS transcoding, education tests 2026-03-11 19:20:48 +01:00
error_contract_test.go fix: stabilize builds, tests, and lint across all stacks 2026-04-05 16:48:07 +02:00
error_response.go v0.9.8 2026-03-06 19:13:16 +01:00
feature_flag_handler.go feat: backend — config, handlers, services, logging, migration 2026-03-23 15:46:57 +01:00
feature_flag_handler_test.go test(v0.803): unit tests for CCPA, reports, announcements, feature flags 2026-02-25 20:02:24 +01:00
frontend_log_handler.go api-contracts: update backend handlers to use wrapped format 2026-01-15 17:32:02 +01:00
frontend_log_handler_test.go stabilisation commit A 2026-01-07 19:39:21 +01:00
gdpr_export_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
gear_handler.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
health.go feat(v0.701): AdminTransfers page/route, MSW, stories, Deep Health, API ref, docs, scope v0.702 2026-02-23 23:42:02 +01:00
health_deep_test.go feat(v0.701): AdminTransfers page/route, MSW, stories, Deep Health, API ref, docs, scope v0.702 2026-02-23 23:42:02 +01:00
health_p1_test.go refonte: backend-api go first; phase 1 2025-12-12 21:34:34 -05:00
health_test.go fix: resolve stream server compilation errors and integrate chat stability fixes 2026-01-04 01:44:22 +01:00
hls_handler.go [T0-006] test(backend): Ajout tests pour hls_handler 2026-01-04 01:44:22 +01:00
hls_handler_test.go chore(v0.102): consolidate remaining changes — docs, frontend, backend 2026-02-20 13:02:12 +01:00
kyc_handler.go feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E 2026-03-13 14:57:19 +01:00
live_health_handler.go feat(backend,web): surface RTMP ingest health on the Go Live page 2026-04-16 23:52:36 +02:00
live_health_handler_test.go feat(backend,web): surface RTMP ingest health on the Go Live page 2026-04-16 23:52:36 +02:00
live_stream_callback.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
live_stream_handler.go feat(v0.10.6): Livestreaming basique F471-F476 2026-03-10 10:21:57 +01:00
login_history_handler.go feat(v0.13.3): complete - Polish Sécurité Avancée 2026-03-13 10:09:01 +01:00
marketplace.go feat(backend,marketplace): refund reverse-charge with idempotent webhook 2026-04-17 02:02:57 +02:00
marketplace_handler.go v0.9.8 2026-03-06 19:13:16 +01:00
marketplace_test.go fix: stabilize builds, tests, and lint across all stacks 2026-04-05 16:48:07 +02:00
metrics.go adding initial backend API (Go) 2025-12-03 20:29:37 +01:00
metrics_aggregated.go stabilisation commit A 2026-01-07 19:39:21 +01:00
metrics_aggregated_test.go stabilisation commit A 2026-01-07 19:39:21 +01:00
metrics_test.go fix: resolve stream server compilation errors and integrate chat stability fixes 2026-01-04 01:44:22 +01:00
metrics_test.go.disabled fix(backend-tests): enable room_handler_test and resolve metric collisions 2025-12-06 12:53:15 +01:00
notification_handlers.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
notification_handlers_test.go feat(v0.10.5): Notifications complètes — F551-F555 2026-03-10 10:02:21 +01:00
oauth_handlers.go v0.9.4 2026-03-05 23:03:43 +01:00
oauth_handlers_test.go release(v0.902): Sentinel - PKCE OAuth, token encryption, redirect validation, CHAT_JWT_SECRET 2026-02-26 19:49:15 +01:00
password_reset_handler.go fix(backend,infra): send real verification emails + fail-loud in prod 2026-04-16 14:52:46 +02:00
password_reset_handler_test.go incus deployement fully implemented, Makefile updated and make fmt ran 2026-01-13 19:47:57 +01:00
payout_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
payout_handler_test.go feat(v0.12.0): F252-F254 marketplace service enhancements 2026-03-10 18:52:06 +01:00
playback_analytics_handler.go chore(v0.102): consolidate remaining changes — docs, frontend, backend 2026-02-20 13:02:12 +01:00
playback_analytics_handler_test.go chore(v0.102): consolidate remaining changes — docs, frontend, backend 2026-02-20 13:02:12 +01:00
playback_websocket_handler.go refactor(backend): J3 — remove 3 deprecated unused handlers 2026-04-14 18:11:07 +02:00
playback_websocket_handler_test.go incus deployement fully implemented, Makefile updated and make fmt ran 2026-01-13 19:47:57 +01:00
playlist_collaboration_integration_test.go [BE-TEST-010] test: Add integration tests for playlist collaboration 2025-12-25 01:39:43 +01:00
playlist_error_helper.go adding initial backend API (Go) 2025-12-03 20:29:37 +01:00
playlist_error_helper_test.go STABILISATION: phase 3–5 – API contract, tests & chat-server hardening 2025-12-06 17:21:59 +01:00
playlist_export_handler.go stabilisation commit: while implementing v0.10.5 2026-03-09 19:36:33 +01:00
playlist_export_handler_test.go [T0-006] test(backend): Ajout tests pour frontend_log_handler 2026-01-04 01:44:22 +01:00
playlist_handler.go feat(v0.10.4): Playlists collaboratives - F136, F140, F141, F143, F145 2026-03-09 16:49:05 +01:00
playlist_handler_integration_test.go stabilizing veza-backend-api: phase 1 2025-12-16 11:23:49 -05:00
playlist_handler_test.go feat(v0.10.4): Playlists collaboratives - F136, F140, F141, F143, F145 2026-03-09 16:49:05 +01:00
playlist_track_handler_integration_test.go stabilizing veza-backend-api: phase 1 2025-12-16 11:23:49 -05:00
presence_handler.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
presence_handler_test.go chore(release): v0.922 — Greenlight (handler tests: dashboard, presence) 2026-03-02 12:30:51 +01:00
privacy_handler.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
profile_handler.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
profile_handler_integration_test.go [BE-API-002] api: Implement playlist collaborators endpoints 2025-12-23 01:41:43 +01:00
profile_handler_test.go [BE-TEST-007] test: Add unit tests for webhook handlers 2025-12-25 01:32:54 +01:00
profile_handler_test.go.disabled fix(backend-tests): enable room_handler_test and resolve metric collisions 2025-12-06 12:53:15 +01:00
queue_handler.go feat(queue): add backend queue API with CRUD operations 2026-02-19 23:44:44 +01:00
queue_session_handler.go v0.9.4 2026-03-05 23:03:43 +01:00
report_handler.go fix(v0.12.6.1): remediate remaining 15 MEDIUM + LOW pentest findings 2026-03-12 06:13:38 +01:00
report_handler_test.go test(v0.803): unit tests for CCPA, reports, announcements, feature flags 2026-02-25 20:02:24 +01:00
response.go STABILISATION: phase 3–5 – API contract, tests & chat-server hardening 2025-12-06 17:21:59 +01:00
role_handler.go [T0-006] test(backend): Ajout tests pour role_handler 2026-01-04 01:44:22 +01:00
role_handler_test.go incus deployement fully implemented, Makefile updated and make fmt ran 2026-01-13 19:47:57 +01:00
room_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
room_handler_test.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
search_handlers.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
search_handlers_test.go feat(search): add autocomplete suggestions endpoint and UI (G3) 2026-02-20 16:54:17 +01:00
sell_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
session.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
session_test.go feat(auth): enrich sessions page with history and revoke (A4) 2026-02-20 14:52:20 +01:00
settings_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
settings_handler_test.go incus deployement fully implemented, Makefile updated and make fmt ran 2026-01-13 19:47:57 +01:00
social.go fix(v0.12.6.1): remediate remaining 15 MEDIUM + LOW pentest findings 2026-03-12 06:13:38 +01:00
social_group_handler.go style(backend): gofmt -w on 85 files (whitespace only) 2026-04-14 12:22:14 +02:00
social_test.go chore(release): v0.931 — Cursor (cursor-based pagination, performance baseline) 2026-03-02 12:35:49 +01:00
status_handler.go STABILISATION: phase 3–5 – API contract, tests & chat-server hardening 2025-12-06 17:21:59 +01:00
status_handler_test.go fix: resolve stream server compilation errors and integrate chat stability fixes 2026-01-04 01:44:22 +01:00
stream_events_handler.go v0.9.8 2026-03-06 19:13:16 +01:00
subscription_handler.go fix(v0.12.6.1): remediate remaining 15 MEDIUM + LOW pentest findings 2026-03-12 06:13:38 +01:00
subscription_handler_test.go feat(v0.12.1): subscription plans service, handler, and routes 2026-03-10 19:36:57 +01:00
support_handler.go feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E 2026-03-13 14:57:19 +01:00
support_handler_test.go feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E 2026-03-13 14:57:19 +01:00
system_metrics.go state-ownership: delete unused optimisticStoreUpdates.ts file 2026-01-15 19:26:53 +01:00
system_metrics_test.go state-ownership: delete unused optimisticStoreUpdates.ts file 2026-01-15 19:26:53 +01:00
system_metrics_test.go.disabled fix(backend-tests): enable room_handler_test and resolve metric collisions 2025-12-06 12:53:15 +01:00
tag_handler.go v0.9.8 beta 2026-03-07 00:54:35 +01:00
tag_handler_test.go feat(v0.802): frontend Cloud/Gear, MSW, docs, scope v0.803, archive 2026-02-25 14:00:58 +01:00
track_stem_handler.go feat(v0.10.7): Collaboration Temps Réel F481-F483 2026-03-10 13:34:16 +01:00
two_factor_handler.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
two_factor_handler_test.go fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings 2026-03-12 05:40:53 +01:00
upgrade_creator_handler.go feat(backend,web): self-service creator role upgrade via /settings 2026-04-16 18:35:07 +02:00
upgrade_creator_handler_test.go feat(backend,web): self-service creator role upgrade via /settings 2026-04-16 18:35:07 +02:00
upload.go feat(backend,web): single source of truth for upload-size limits 2026-04-16 19:37:37 +02:00
upload_test.go fix(security): add ownership check to GetUploadStatus handler (IDOR fix) 2026-02-22 17:30:30 +01:00
validate.go security: create useFormValidation hook for pre-validation 2026-01-15 20:06:30 +01:00
validation_test.go refonte: backend-api go first; phase 1 2025-12-12 21:34:34 -05:00
webauthn_handler.go feat(v0.13.3): complete - Polish Sécurité Avancée 2026-03-13 10:09:01 +01:00
webhook_handlers.go chore(docs): add v0.101 diagnostic baseline 2026-02-19 16:08:05 +01:00
webhook_handlers_test.go [BE-TEST-007] test: Add unit tests for webhook handlers 2025-12-25 01:32:54 +01:00