Fix silent tip/gift-card money loss on pending retry; terminal checkout wire; sentinel error; docs
CRITICAL — same-amount tip retry silently never charged: - CreateTipPayment idempotency check now only short-circuits when the existing record is 'completed'. A 'pending' record (previous Square call failed) is REUSED and the charge re-attempted with the same key (Square dedups safely), instead of returning the stale pending record as 200 with a success toast and no charge. - Same fix in BuyGiftCard: pending records trigger a re-attempt, not a false-success response. Unique idempotency_key constraint means the pending record must be reused, not re-inserted. - Fixes the savepoint/rollback interaction: the nested tx (savepoint) is now committed in the reuse path so the deferred rollback doesn't undo the later status UPDATE on the same connection. - Regression test: TestTipPayment_RetryPending_ReattemptsCharge verifies a pending record + same-key retry re-attempts and completes, reusing the record (count stays 1). MAJOR — terminal checkout wire contract: - device_id now sent as checkout.device_options.device_id (Square's required shape), not a top-level field which Square rejects with 400. - 'checkout pending' detection now uses typed sentinel ErrCheckoutPending with errors.Is in both handlers, matching mock and real HTTP client. MAJOR — exp_month/exp_year omitted from card creation payload when unset (now *int with omitempty) — Square would 400 on 0/0; expiry comes from the tokenized source. Docs: - README payments/infrastructure sections corrected (Web Payments SDK claim replaced with accurate P11-backlog note; dev mock parity described) - Future Work P11 updated to reflect raw-PAN rejection is now enforced in both mock and prod (new-card flows are a documented dead end) - Added plans/p11-square-web-payments-sdk.md: full implementation plan + handoff prompt for the agent picking up P11 (Web Payments SDK nonces)
This commit is contained in:
@@ -261,7 +261,7 @@ func (m *MockClient) GetCheckout(ctx context.Context, checkoutID string) (*Payme
|
||||
}
|
||||
|
||||
if checkout.Status == "PENDING" {
|
||||
return nil, fmt.Errorf("checkout pending")
|
||||
return nil, ErrCheckoutPending
|
||||
}
|
||||
|
||||
result, ok := m.completed[checkoutID]
|
||||
|
||||
Reference in New Issue
Block a user