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>
47 lines
2.4 KiB
SQL
47 lines
2.4 KiB
SQL
-- v1.0.7 item A: add stripe_reversal_id column to seller_transfers.
|
|
-- Prepares ground for item B (async reversal worker): when a refund-driven
|
|
-- reversal succeeds, the worker persists the Stripe reversal id here so
|
|
-- the operation is idempotent (a replayed webhook skips the reversal call
|
|
-- if this column is populated) and auditable against the Stripe dashboard.
|
|
--
|
|
-- This migration ships with item A because item B's worker is the next
|
|
-- commit and we want the column in place before the code that writes it.
|
|
-- Nullable — pre-v1.0.7 transfers will never have a reversal id.
|
|
--
|
|
-- Companion column stripe_transfer_id already exists (pre-v1.0.7), but was
|
|
-- never written to until item A: the TransferService.CreateTransfer
|
|
-- signature changed to return the Stripe transfer id, which is now
|
|
-- persisted by processSellerTransfers, TransferRetryWorker, and
|
|
-- admin_transfer_handler.
|
|
|
|
ALTER TABLE seller_transfers
|
|
ADD COLUMN IF NOT EXISTS stripe_reversal_id VARCHAR(255);
|
|
|
|
-- Partial UNIQUE index so a given Stripe reversal id cannot collide across
|
|
-- rows, while still allowing many NULL/empty rows (the common case: only
|
|
-- refunded transfers carry a reversal id). Mirrors the pattern landed in
|
|
-- v1.0.6.1 for refunds.hyperswitch_refund_id.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_seller_transfers_stripe_reversal_id
|
|
ON seller_transfers(stripe_reversal_id)
|
|
WHERE stripe_reversal_id IS NOT NULL AND stripe_reversal_id <> '';
|
|
|
|
-- Same pattern for stripe_transfer_id — previously declared without an
|
|
-- index, now populated by item A so worth indexing for reconciliation
|
|
-- lookups. Partial because pre-v1.0.7 rows carry empty values.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_seller_transfers_stripe_transfer_id
|
|
ON seller_transfers(stripe_transfer_id)
|
|
WHERE stripe_transfer_id IS NOT NULL AND stripe_transfer_id <> '';
|
|
|
|
-- Visibility: how many historical rows lack a stripe_transfer_id? These
|
|
-- are the rows that the backfill CLI (cmd/tools/backfill_stripe_transfer_ids)
|
|
-- will target. Acceptable to leave NULL where Stripe has no match — see
|
|
-- axis-1 P2.9 for the admin-triggered recovery path.
|
|
DO $$
|
|
DECLARE v_count INTEGER;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO v_count
|
|
FROM seller_transfers
|
|
WHERE status = 'completed'
|
|
AND (stripe_transfer_id IS NULL OR stripe_transfer_id = '');
|
|
RAISE NOTICE 'v1.0.7 item A: % completed seller_transfer(s) have no stripe_transfer_id and need backfill (see cmd/tools/backfill_stripe_transfer_ids)', v_count;
|
|
END $$;
|