veza/veza-backend-api/migrations
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
..
archive chore(release): v0.942 — Compress (migration consolidation procedure, mark script) 2026-03-02 19:05:54 +01:00
rollback chore: cleanup old e2e tests, playwright configs, reorganize down migrations 2026-03-18 11:35:26 +01:00
000_mark_consolidated.sql fix(release): v1.0.1 — Conformité complète ROADMAP checklist 2026-03-03 20:17:54 +01:00
001_extensions_and_types.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
010_auth_and_users.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
011_cleanup_refresh_tokens.sql docs: update walkthrough with launch instructions and test credentials 2026-01-04 01:44:23 +01:00
012_add_user_social_links.sql stabilisation commit 2026-01-04 01:44:23 +01:00
020_create_sessions.sql refonte: backend-api go first; phase 1 2025-12-12 21:34:34 -05:00
020_z_migration_rename_records.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
021_rbac_and_profiles.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
030_files_management.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
040_streaming_core.sql stabilizing veza-backend-api: phase 1 2025-12-16 11:23:49 -05:00
041_streaming_analytics.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
042_media_processing.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
043_analytics_events.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
044_playlist_share_links.sql [BE-DB-005] be-db: Create migration for playlist_share_link table 2025-12-24 15:09:44 +01:00
045_user_follows.sql [BE-DB-006] be-db: Create migration for user_follows table 2025-12-24 15:10:34 +01:00
046_user_blocks.sql [BE-DB-007] be-db: Create migration for user_blocks table 2025-12-24 15:11:32 +01:00
047_notifications.sql [BE-DB-008] be-db: Create migration for notifications table 2025-12-24 15:12:11 +01:00
048_search_indexes.sql [BE-DB-009] be-db: Add indexes for search queries 2025-12-24 15:13:03 +01:00
049_composite_indexes.sql [T0-002] fix(rust): Corriger erreurs compilation Rust 2026-01-04 01:44:20 +01:00
050_data_validation_constraints.sql [FIX] Fix migration errors for missing tables 2026-01-04 01:44:13 +01:00
051_legacy_chat.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
052_stats_views.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
053_audit_triggers.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
060_job_queue.sql feat(backend-worker): persist job queue in postgres 2025-12-06 13:32:32 +01:00
069_groups_and_members.sql chore(migrations): add 069, 089, 090, 091 for v0.302 2026-02-21 05:47:14 +01:00
070_create_social_tables.sql feat: Visual masterpiece - true light mode & premium UI 2026-01-11 02:32:21 +01:00
075_create_webhooks.sql fix(webhooks): add DB migration and avoid 500 toast on developer portal 2026-02-10 21:11:32 +01:00
076_create_gear_items.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
077_create_live_streams.sql Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy 2026-02-14 17:23:32 +01:00
078_add_missing_indexes.sql perf(db): add missing indexes for file_id and cover_art_file_id 2026-02-14 18:32:05 +01:00
080_add_payment_fields.sql chore: consolidate CI, E2E, backend and frontend updates 2026-02-17 16:43:21 +01:00
081_create_playback_analytics.sql fix(backend): remediation plan — tests, playback_analytics, job queue, gamification 2026-02-17 16:01:45 +01:00
082_create_api_keys.sql feat(developer): add API keys backend (Lot C) 2026-02-20 00:18:36 +01:00
083_add_user_banner.sql feat(profile): add profile banner (B1) 2026-02-20 14:56:25 +01:00
084_add_track_lyrics.sql feat(tracks): add lyrics model and endpoints (E3) 2026-02-20 15:36:28 +01:00
085_add_track_tags.sql feat(tracks): add suggested tags endpoint and UI (E4) 2026-02-20 15:38:51 +01:00
086_add_pg_trgm.sql feat(search): add pg_trgm extension for fuzzy search 2026-02-20 18:34:50 +01:00
087_queue_sessions.sql feat(queue): add queue_sessions and shared_queue_items models 2026-02-20 18:39:33 +01:00
088_user_presence.sql feat(presence): migration 088 user_presence (P1.1) 2026-02-21 05:22:33 +01:00
089_group_join_requests.sql feat(groups): S2.1-S2.5 request join, invite, roles, feed groups, my groups 2026-02-21 05:48:59 +01:00
090_push_subscriptions.sql chore(migrations): add 069, 089, 090, 091 for v0.302 2026-02-21 05:47:14 +01:00
091_user_presence_invisible.sql chore(migrations): add 069, 089, 090, 091 for v0.302 2026-02-21 05:47:14 +01:00
092_group_invitations.sql feat(groups): S2.1-S2.5 request join, invite, roles, feed groups, my groups 2026-02-21 05:48:59 +01:00
093_notification_preferences.sql feat(notifications): N1.1-N1.3 Web Push subscription, send on events, preferences 2026-02-21 16:41:39 +01:00
094_create_products.sql feat: backend, stream server & infra improvements 2026-03-18 11:36:06 +01:00
094_user_presence_rich.sql feat(presence): P2.1 rich presence, P2.2 invisible mode 2026-02-21 16:47:09 +01:00
095_products_enrichment.sql feat(marketplace): add migrations 095-097 for products enrichment, previews, images 2026-02-22 14:05:19 +01:00
096_product_previews.sql feat(marketplace): add migrations 095-097 for products enrichment, previews, images 2026-02-22 14:05:19 +01:00
097_product_images.sql feat(marketplace): add migrations 095-097 for products enrichment, previews, images 2026-02-22 14:05:19 +01:00
098_product_licenses.sql feat(marketplace): add migration 098 product_licenses, ProductLicense model, GET /licenses/mine 2026-02-22 14:16:24 +01:00
099_promo_codes.sql feat(v0.501): Sprint 4 -- Cloud frontend + Gear advanced 2026-02-22 18:30:49 +01:00
099_z_create_orders.sql feat: backend, stream server & infra improvements 2026-03-18 11:36:06 +01:00
100_orders_discount.sql feat(v0.501): Sprint 4 -- Cloud frontend + Gear advanced 2026-02-22 18:30:49 +01:00
101_product_reviews.sql feat(marketplace): add product_reviews migration 2026-02-22 16:04:14 +01:00
102_license_revoked.sql feat(marketplace): add license revoked_at migration 2026-02-22 16:18:01 +01:00
103_tracks_waveform.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
104_user_folders.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
105_user_files.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
106_user_storage_quotas.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
107_gear_is_public.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
108_gear_images.sql feat(v0.501): Sprint 1 -- infrastructure foundations 2026-02-22 18:10:25 +01:00
109_read_receipts.sql feat(chat): Sprint 1 -- migrations, models, repositories for chat rewrite 2026-02-22 20:38:20 +01:00
110_delivered_status.sql feat(chat): Sprint 1 -- migrations, models, repositories for chat rewrite 2026-02-22 20:38:20 +01:00
111_message_reactions.sql feat(chat): Sprint 1 -- migrations, models, repositories for chat rewrite 2026-02-22 20:38:20 +01:00
112_messages_extra_columns.sql feat(chat): Sprint 1 -- migrations, models, repositories for chat rewrite 2026-02-22 20:38:20 +01:00
113_messages_fts.sql feat(chat): Redis rate limiter, persistent presence, PostgreSQL full-text search 2026-02-22 21:17:51 +01:00
114_seller_stripe_accounts.sql feat(seller): add seller_stripe_accounts migration and model 2026-02-23 22:11:11 +01:00
115_seller_transfers.sql feat(commerce): add 115_seller_transfers migration 2026-02-23 22:54:56 +01:00
116_seller_transfers_retry.sql feat(marketplace): add migration 116 — retry columns for seller_transfers 2026-02-23 23:30:41 +01:00
117_live_streams_go_live.sql feat(live): add migration 117 and model fields for Go Live 2026-02-24 09:51:21 +01:00
118_user_preferences.sql feat(users): add user_preferences migration with appearance fields 2026-02-25 09:45:03 +01:00
119_cloud_file_versions.sql feat(db): add migrations 119-122 for cloud versions, gear warranty/documents/repairs 2026-02-25 13:30:49 +01:00
120_gear_warranty.sql feat(db): add migrations 119-122 for cloud versions, gear warranty/documents/repairs 2026-02-25 13:30:49 +01:00
121_gear_documents.sql feat(db): add migrations 119-122 for cloud versions, gear warranty/documents/repairs 2026-02-25 13:30:49 +01:00
122_gear_repairs.sql feat(db): add migrations 119-122 for cloud versions, gear warranty/documents/repairs 2026-02-25 13:30:49 +01:00
123_invalidate_tokens_rs256.sql v0.9.1 2026-03-05 19:22:31 +01:00
124_room_invitations.sql v0.9.7 2026-03-06 18:52:08 +01:00
125_follow_counts_triggers.sql stabilisation commit: while implementing v0.10.5 2026-03-09 19:36:33 +01:00
126_tags_genres_discover.sql feat(v0.10.1): Tags & Genres discover - F351-F355 2026-03-09 01:52:56 +01:00
127_moderation_keywords.sql feat(v0.10.3): Commentaires & Interactions Sociales - F201-F215 2026-03-09 10:30:47 +01:00
128_track_reposts.sql feat(v0.10.3): Commentaires & Interactions Sociales - F201-F215 2026-03-09 10:30:47 +01:00
129_playlist_editorial.sql stabilisation commit: while implementing v0.10.5 2026-03-09 19:36:33 +01:00
132_quiet_hours.sql stabilisation commit: while implementing v0.10.5 2026-03-09 19:36:33 +01:00
133_notification_grouping.sql stabilisation commit: while implementing v0.10.5 2026-03-09 19:36:33 +01:00
134_weekly_digest_prefs.sql feat(v0.10.5): Notifications complètes — F551-F555 2026-03-10 10:02:21 +01:00
900_triggers_and_functions.sql P0: stabilisation backend/chat/stream + nouvelle base migrations v1 2025-12-06 11:14:38 +01:00
910_create_audit_logs.sql stabilizing apps/web: THIRD BATCH - FIXED Playwright 2025-12-21 18:55:51 -05:00
920_add_performance_indexes.sql [BE-DB-001] backend-database: Add database indexes for performance-critical queries 2025-12-23 01:47:33 +01:00
930_add_missing_foreign_keys.sql [BE-DB-002] backend-database: Add foreign key constraints where missing 2025-12-23 01:48:33 +01:00
931_add_refresh_tokens_updated_at.sql docs: update walkthrough with launch instructions and test credentials 2026-01-04 01:44:23 +01:00
932_add_user_deletion_fields.sql feat(users): account deletion hardening with anonymization, S3 cleanup, session revocation 2026-02-25 19:51:21 +01:00
933_reports.sql feat(admin): moderation queue with reports CRUD 2026-02-25 19:53:04 +01:00
934_announcements.sql feat(admin): global announcements CRUD and public banner endpoint 2026-02-25 19:55:21 +01:00
935_feature_flags.sql feat(admin): feature flags CRUD with DB persistence 2026-02-25 19:56:24 +01:00
936_oauth_states_pkce.sql release(v0.902): Sentinel - PKCE OAuth, token encryption, redirect validation, CHAT_JWT_SECRET 2026-02-26 19:49:15 +01:00
936_webrtc_flag.sql chore(release): v0.971 — Phantom (gamification removal, WebRTC Beta, limits doc) 2026-03-02 19:25:37 +01:00
940_performance_indexes_v0951.sql chore(release): v0.951 — Loadtest (500 req/s, 1000 WS, 50 uploads, perf indexes) 2026-03-02 19:22:38 +01:00
941_notification_prefs_defaults_v0105.sql feat(v0.10.5): Notifications Complètes (F551-F555) 2026-03-10 10:09:32 +01:00
942_create_co_listening_sessions.sql feat: backend, stream server & infra improvements 2026-03-18 11:36:06 +01:00
943_create_track_stems.sql feat(v0.10.7): Collaboration Temps Réel F481-F483 2026-03-10 13:34:16 +01:00
944_create_data_exports.sql feat(gdpr): v0.10.8 portabilité données - export ZIP async, suppression compte, hard delete cron 2026-03-10 13:57:04 +01:00
945_creator_analytics_v0110.sql feat(v0.11.0): F381-F385 database migrations and models for creator analytics 2026-03-10 16:21:01 +01:00
946_advanced_analytics_v0111.sql feat(v0.11.1): F396-F399 database migrations for advanced analytics 2026-03-10 17:12:01 +01:00
947_moderation_advanced_v0112.sql feat(v0.11.2): F411-F420 database migrations and models for advanced moderation 2026-03-10 17:41:38 +01:00
948_marketplace_complete_v0120.sql feat(v0.12.0): F252-F254 database migrations for marketplace completion 2026-03-10 18:51:26 +01:00
949_subscription_plans_v0121.sql feat(v0.12.1): database migrations for subscription plans 2026-03-10 19:36:29 +01:00
950_distribution_platforms_v0122.sql feat(v0.12.2): database migrations for distribution platforms 2026-03-10 19:54:00 +01:00
951_education_courses_v0123.sql feat(v0.12.3): database migrations for education courses 2026-03-11 09:44:54 +01:00
960_performance_indexes_v0124.sql feat: backend, stream server & infra improvements 2026-03-18 11:36:06 +01:00
970_password_login_history_v0130.sql feat(v0.13.0): conformité features partielles — CAPTCHA, password history, login history, SMS 2FA 2026-03-12 09:31:50 +01:00
971_security_advanced_v0133.sql feat(v0.13.3): complete - Polish Sécurité Avancée 2026-03-13 10:09:01 +01:00
972_seller_kyc_v0135.sql feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E 2026-03-13 14:57:19 +01:00
973_support_tickets_v0135.sql feat(v0.13.5): polish marketplace & compliance — KYC, support, payout E2E 2026-03-13 14:57:19 +01:00
974_track_likes_updated_at.sql feat: backend — config, handlers, services, logging, migration 2026-03-23 15:46:57 +01:00
975_add_two_factor_columns.sql fix(backend): add password change endpoint and 2FA migration 2026-03-25 23:39:28 +01:00
976_platform_settings.sql fix(middleware): persist maintenance flag via platform_settings table 2026-04-16 14:57:06 +02:00
977_users_promoted_to_creator_at.sql feat(backend,web): self-service creator role upgrade via /settings 2026-04-16 18:35:07 +02:00
978_refunds_table.sql feat(backend,marketplace): refund reverse-charge with idempotent webhook 2026-04-17 02:02:57 +02:00