Files
baya-monorepo/mvp/blocker-phases/04-payment-timezone.md
T
2026-08-02 20:37:14 +03:30

2.2 KiB

Phase 04 — The 30-minute payment countdown can lie

Blocker: blockers.md § "Payments" (the timezone item). Depends on: nothing. Small and contained. Related: 13-booking-lifecycle.md's "today's visits" fix needs a consistent timezone decision — resolve that the same way once you land this.


Root cause, precise. BookingRequest.PaymentDeadlineAt/NurseResponseDeadlineAt are DateTime (not DateTimeOffset, deliberately, per a comment about SQLite test-provider compatibility — BookingRequest.cs:53-60). The value is written correctly as UTC (AcceptBookingRequestCommand.Handler.cs:41-50), but SQL Server's datetime2 carries no timezone, and EF's SQL Server provider returns Kind = Unspecified on read — nothing resets it to Utc (BookingRequestConfig.cs:19-26 has no .HasConversion(...) for these two properties, unlike BaseEntity.CreatedAt/ModifiedAt, which correctly use DateTimeOffset). System.Text.Json therefore serializes it without a trailing Z. Date.parse() on the client (client/src/components/CountdownTimer/CountdownTimer.tsx:84) reads a Z-less ISO string as local time, not UTC — for a Tehran browser (UTC+3:30) that's ~3.5 hours added to the true deadline, so the countdown shows hours more time than actually remains and expires while still showing time left.

The fix. Add an EF Core value converter (new file, e.g. server/src/Infrastructure/Baya.Infrastructure.Persistence/ValueConversion/UtcDateTimeConverter.cs, same pattern as the existing EncryptedStringConverter.cs): convertFromProvider: v => DateTime.SpecifyKind(v, DateTimeKind.Utc) (no-op on write). Apply it to both PaymentDeadlineAt and NurseResponseDeadlineAt in BookingRequestConfig.cs:19-26. No migration needed — same column type, only the in-memory Kind tag changes on read.

Flag: the same defect class (a DateTime read back from datetime2 losing Kind) likely recurs anywhere else a server-frozen instant feeds a client countdown (e.g. dispute_window_ends_at, BNPL settled_at) — worth a grep for other DateTime (not DateTimeOffset) properties before considering this class of bug fully closed, not just the one already reported.