Commit graph

3 commits

Author SHA1 Message Date
senke
40bc9100ba fix(connect): defensive empty-id guard + admin retry test asserts persistence
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
Post-A self-review surfaced two gaps:

1. `StripeConnectService.CreateTransfer` trusted Stripe's SDK to
   return a non-empty `tr.ID` on success (`err == nil`). The
   invariant holds in practice, but an empty id silently persisted
   on a completed transfer leaves the row permanently
   un-reversible — which defeats the entire point of item A.
   Added a belt-and-suspenders check that converts `(tr.ID="",
   err=nil)` into a failed transfer.

2. `TestRetryTransfer_Success` (admin handler) exercised the retry
   path but didn't assert that StripeTransferID was persisted after
   a successful retry. The worker path and processSellerTransfers
   both had the assertion; the admin manual-retry path was the
   third entry into the same behavior and lacked coverage. Added
   the assertion.

Decision on scope: v1.0.6.2 added a partial UNIQUE on
stripe_transfer_id (WHERE IS NOT NULL AND <> '') in migration 981,
matching the v1.0.6.1 pattern for refunds.hyperswitch_refund_id.
The combination of (a) the DB partial UNIQUE and (b) this defensive
guard means there is now no code or data path that can persist an
empty transfer id while claiming success.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:03:37 +02:00
senke
2583c92d4f refactor(connect): persist stripe_transfer_id on create + retry — v1.0.7 item A
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
TransferService.CreateTransfer signature changes from (...) error to
(...) (string, error) — the caller now captures the Stripe transfer
identifier and persists it on the SellerTransfer row. Pre-v1.0.7 the
stripe_transfer_id column was declared on the model and table but
never written to, which blocked the reversal worker (v1.0.7 item B)
from identifying which transfer to reverse on refund.

Changes:
  * `TransferService` interface and `StripeConnectService.CreateTransfer`
    both return the Stripe transfer id alongside the error.
  * `processSellerTransfers` (marketplace service) persists the id on
    success before `tx.Create(&st)` so a crash between Stripe ACK and
    DB commit leaves no inconsistency.
  * `TransferRetryWorker.retryOne` persists on retry success — a row
    that failed on first attempt and succeeded via the worker is
    reversal-ready all the same.
  * `admin_transfer_handler.RetryTransfer` (manual retry) persists too.
  * `SellerPayout.ExternalPayoutID` is populated by the Connect payout
    flow (`payout.go`) — the field existed but was never written.
  * Four test mocks updated; two tests assert the id is persisted on
    the happy path, one on the failure path confirms we don't write a
    fake id when the provider errors.

Migration `981_seller_transfers_stripe_reversal_id.sql`:
  * Adds nullable `stripe_reversal_id` column for item B.
  * Partial UNIQUE indexes on both stripe_transfer_id and
    stripe_reversal_id (WHERE IS NOT NULL AND <> ''), mirroring the
    v1.0.6.1 pattern for refunds.hyperswitch_refund_id.
  * Logs a count of historical completed transfers that lack an id —
    these are candidates for the backfill CLI follow-up task.

Backfill for historical rows is a separate follow-up (cmd/tools/
backfill_stripe_transfer_ids, calling Stripe's transfers.List with
Destination + Metadata[order_id]). Pre-v1.0.7 transfers without a
backfilled id cannot be auto-reversed on refund — document in P2.9
admin-recovery when it lands. Acceptable scope per v107-plan.

Migration number bumped 980 → 981 because v1.0.6.2 used 980 for the
unpaid-subscription cleanup; v107-plan updated with the note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 13:08:39 +02:00
senke
56f2e9f9c3 test(admin): add admin transfer handler tests 2026-02-23 23:35:11 +01:00