Skip to content

Finish and recover paid checkouts safely - #1853

Closed
stefan-burke wants to merge 29 commits into
mainfrom
split/staged-checkout-runtime
Closed

Finish and recover paid checkouts safely#1853
stefan-burke wants to merge 29 commits into
mainfrom
split/staged-checkout-runtime

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 17, 2026

Copy link
Copy Markdown
Member

What changed

Paid checkouts now save an unfinished attendee and booking before opening the payment page. The booking has zero quantity, so it never holds capacity.

After the provider confirms payment, the same records activate in one database transaction. Capacity, add-on stock, payment history, ticket access, personal details, and ledger entries either all finish together or none do.

If the booking cannot finish, the checkout enters a refund-only state before the provider is contacted. Refund attempts are stored so retries cannot issue a duplicate provider refund. Stripe, Square, and SumUp share the same pending, failed, and completed refund flow.

Scheduled maintenance now recovers paid and refunding checkouts when a callback or browser redirect was missed. Recovery uses durable retry times, provider-specific request budgets, and follow-up runs so one failed checkout cannot block the queue.

Payment callbacks replay stored final results even when the provider state later changes. Cancellation only offers a new checkout after the old session is confirmed closed. Cleanup keeps live checkout stages, hidden attendees, and SumUp metadata until payment or refund recovery has finished.

The design documents record the remaining release-version, key-rotation, backup, host-move, and rollback work. Those deployment safeguards are not part of this PR.

Operating limits

This supports the current running version only. Deployments, host moves, and restores do not preserve open payments. No rollback fence, stage revision, backup certification, admin payment lock, or old-version reconciliation is included.

Verification

  • Full precommit passes under Deno 2.5.6: lint, typecheck, copy checks, zero duplicate code, edge build, and 19,535 tests
  • Source line and branch coverage are both 100%
  • Checkout recovery, refund handling, maintenance, Square, and SumUp changed paths each passed 100% targeted mutation runs
  • Regression coverage includes atomic activation, capacity races, terminal replay, missed callbacks, bounded recovery, cancellation races, provider changes, pending refunds, failed refunds, and duplicate-refund prevention

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR replaces placeholder bookings with staged checkout records and zero-quantity attendees. Paid sessions activate staged attendees atomically; failed, cancelled, expired, or mismatched sessions close and refund staged checkouts. Provider contracts, webhook retries, migrations, pruning, and payment-flow tests are updated.

Changes

Staged checkout and activation

Layer / File(s) Summary
Checkout-stage persistence and schema
src/shared/db/checkout-stages.ts, src/shared/db/migrations/*, test/lib/db/checkout-stage-schema.test.ts
Adds encrypted checkout-stage persistence, provider identifiers, state constraints, refund finalization, cleanup, and migration coverage.
Staged attendee activation
src/shared/db/attendees/*, src/shared/booking-lines.ts, test/shared/db/attendees/*
Adds transactional staged activation, capacity and stock checks, canonical booking rows, staged batch creation, and filtering of staged attendees from ordinary reads.
Payment processing and staged refunds
src/features/api/payment-processing/*, src/shared/staged-checkout.ts, src/shared/refund-ledger.ts, src/shared/booking.ts
Routes paid checkout through staged creation and activation, replaces placeholder refunds, and adds replay, recovery, retry, and contention handling.

Provider and route integration

Layer / File(s) Summary
Provider contracts and implementations
src/shared/payments.ts, src/shared/payment-helpers.ts, src/shared/{stripe,square,sumup}*.ts
Adds provider checkout identifiers, close results, webhook event classification, retry outcomes, Stripe expiry, Square payment-link deletion, and SumUp checkout closure.
Webhook and cancellation handling
src/features/api/webhooks.ts, src/features/api/payment-processing/{cancel,classify}.ts, test/lib/server-payments/*, test/lib/server-webhooks/*
Closes staged checkouts on cancellation or expiry, refreshes sessions after paid races, returns retry responses, and updates refund/removal behavior.
Checkout and maintenance utilities
src/shared/now.ts, src/shared/db/prune.ts, src/shared/db/*, src/features/public/ticket-payment.ts, src/features/api/folded-booking.ts
Centralizes historical cutoffs, prunes abandoned checkout storage, shares checkout handlers, and updates booking-line and batch SQL helpers.

Test infrastructure

Layer / File(s) Summary
Staged payment simulation and regression coverage
test/test-utils/staged-payments.ts, test/test-utils/webhooks.ts, test/test-utils/settings.ts, test/features/api/payment-processing/*, test/shared/staged-checkout.test.ts
Adds callback-staging helpers and integration coverage for activation, refunds, replay, recovery, provider races, and staged persistence.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Customer
    participant TicketPayment
    participant StagedCheckout
    participant Provider
    participant Database

    Customer->>TicketPayment: submit paid checkout
    TicketPayment->>StagedCheckout: createAndHandlePaidCheckout
    StagedCheckout->>Provider: createCheckoutSession
    Provider-->>StagedCheckout: checkout identifiers and URL
    StagedCheckout->>Database: create pending staged attendee and checkout stage
    StagedCheckout-->>Customer: checkout URL
Loading
sequenceDiagram
    participant Provider
    participant Webhooks
    participant PaymentProcessing
    participant Attendees
    participant Refunds

    Provider->>Webhooks: resolve webhook session
    Webhooks->>Webhooks: classify completed or expired event
    Webhooks->>PaymentProcessing: process paid session
    PaymentProcessing->>Attendees: activate staged attendee
    alt activation succeeds
        Attendees-->>Webhooks: success
    else activation or validation fails
        PaymentProcessing->>Refunds: refund staged booking
        Refunds-->>Webhooks: terminal failure or retry
    end
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title captures the main theme of the PR: staged paid checkouts with safe recovery, closure, and refund handling.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch split/staged-checkout-runtime
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch split/staged-checkout-runtime

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
test/lib/server-webhooks/refund-helper-functions.test.ts (1)

150-180: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stage this checkout before exercising the activation failure.

The paid-session flow loads its checkout stage before activation. Without staging cs_create_boom, this test cannot reach the mocked rejection or refund assertion. Call stageStripeCallback("cs_create_boom") before the success request.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib/server-webhooks/refund-helper-functions.test.ts` around lines 150 -
180, Update the test “an unexpected uncommitted activation error refunds” to
call stageStripeCallback("cs_create_boom") after configuring the checkout
session and before handleRequest exercises the success flow, ensuring the staged
checkout reaches the mocked activateStagedAttendee failure and refund assertion.
src/features/api/payment-processing/create.ts (1)

249-259: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail when a priced booking line has no paid amount.

paidByIntentItem.get(...) is expected to resolve, but undefined silently omits pricePaid, allowing the activated booking and its ledger plan to disagree. Use a throwing lookup before activation.

As per coding guidelines, expected lookup values must throw when absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/api/payment-processing/create.ts` around lines 249 - 259, Update
the booking construction inside bookingsForOrder to use a throwing lookup for
each pricingIntent item instead of allowing paidByIntentItem.get(...) to return
undefined. Always pass the resolved pricePaid into the booking, so activation
fails before creating inconsistent booking or ledger state when the amount is
missing.

Source: Coding guidelines

test/lib/server-payments/success.test.ts (1)

220-238: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Update the stale placeholder comments.

Lines 220-236 still claim that a quantity-zero placeholder and system note are retained, while Lines 237-238 assert the opposite. Describe the current staged-refund behavior instead. As per coding guidelines, comments must explain current code and not historical implementations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib/server-payments/success.test.ts` around lines 220 - 238, Update the
comments surrounding the signed-by-us late-buyer refund scenario in the test to
describe the current staged-refund behavior reflected by
expectNoRefundPlaceholder and expectRefundedWithoutAttendee. Remove claims that
a quantity-zero placeholder, system note, or retained booking is created, while
preserving the existing assertions and response expectations.

Source: Coding guidelines

src/shared/payment-helpers.ts (1)

414-451: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require providers to supply the hosted-checkout identifier explicitly.

Defaulting providerCheckoutId to sessionId masks an omitted Square/SumUp mapping and later closes the wrong provider resource. Make this field required in readResult and let toCheckoutResult reject an absent value.

As per coding guidelines, missing required external fields must fail at the boundary and must not be defaulted.

Proposed contract fix
 export const toCheckoutResult = (
   sessionId: string | undefined,
   url: string | undefined | null,
   label: LogCategory,
-  providerCheckoutId = sessionId,
+  providerCheckoutId: string | undefined,
 ): CheckoutSessionResult => {
     readResult: (result: Result) => {
       id: string | undefined;
-      providerId?: string | undefined;
+      providerId: string | undefined;
       url: string | undefined | null;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/payment-helpers.ts` around lines 414 - 451, Require an explicit
providerCheckoutId in toCheckoutResult instead of defaulting it to sessionId,
and make the corresponding providerId field required in
makeCreateCheckoutSession’s readResult contract. Update all provider mappings to
supply the hosted-checkout identifier explicitly, while preserving rejection
when it is absent.

Source: Coding guidelines

test/lib/server-webhooks/multi-ticket-refunds.test.ts (1)

87-93: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Update refund-test descriptions for the staged-removal model.

These comments still describe the removed quantity-zero placeholder behavior, while the changed assertions prove that staged attendees are deleted after refund.

  • test/lib/server-webhooks/multi-ticket-refunds.test.ts#L87-L93: describe staged-attendee removal and one refund.
  • test/lib/server-webhooks/multi-ticket-refunds.test.ts#L133-L135: describe removal from both listings.
  • test/lib/server-webhooks/multi-ticket-refunds.test.ts#L170-L176: replace placeholder retention with staged-attendee removal.
  • test/lib/server-webhooks/multi-ticket-refunds.test.ts#L207-L213: replace placeholder retention with staged-attendee removal.
  • test/lib/server-webhooks/single-ticket-refunds.test.ts#L64-L70: describe staged-attendee removal after refund.
  • test/lib/server-webhooks/single-ticket-refunds.test.ts#L131-L137: describe staged-attendee removal in the redirect path.

As per coding guidelines, comments must explain the current code and not historical implementations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib/server-webhooks/multi-ticket-refunds.test.ts` around lines 87 - 93,
Update the comments describing refund assertions to reflect the current
staged-attendee removal behavior rather than quantity-zero placeholder
retention: in test/lib/server-webhooks/multi-ticket-refunds.test.ts ranges
87-93, 133-135, 170-176, and 207-213, describe removal from the relevant
listing(s) and the single refund; in
test/lib/server-webhooks/single-ticket-refunds.test.ts ranges 64-70 and 131-137,
describe staged-attendee removal after refund, including the redirect path.

Source: Coding guidelines

test/lib/server-webhooks/refund-skip-conditions.test.ts (1)

128-162: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the scenario named by this test.

This uses unsigned webhookMeta and asserts an ignored, non-refunded webhook. It does not exercise a signed checkout with a missing listing. Split the unsigned-ignore case or construct the signed case and assert its refund/removal outcome.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib/server-webhooks/refund-skip-conditions.test.ts` around lines 128 -
162, The test currently covers only the unsigned webhook path, not the signed
checkout with a missing listing. Update the test around checkoutSessionEvent to
construct valid signed metadata for the multi-ticket payload, then assert the
expected refund and attendee-removal outcome; move the existing unsigned
ignored/no-refund assertions into a separate test if they should remain covered.
test/lib/server-webhooks/modifier-refunds.test.ts (1)

51-55: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the obsolete quantity-zero placeholder documentation.

The assertions now require staged attendee deletion, but nearby comments still describe retaining a placeholder.

  • test/lib/server-webhooks/modifier-refunds.test.ts#L51-L55: remove or rewrite the preceding placeholder/failure-state comments.
  • test/lib/server-webhooks/modifier-refunds.test.ts#L89-L93: update the retained-placeholder description to staged-attendee deletion.
  • test/lib/server-webhooks/modifier-refunds.test.ts#L130-L137: describe deletion and the resulting absence of contact history.
  • test/lib/server-webhooks/multi-ticket-booking.test.ts#L137-L138: replace the retained-placeholder comment with the no-attendee outcome.
  • test/lib/webhook-price-signature/stored-refund-and-ignore.test.ts#L61-L72: remove references to an attendee netting to zero and a quantity-zero projected price.

As per coding guidelines, “Do not leave historical comments describing replaced or previous implementations.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib/server-webhooks/modifier-refunds.test.ts` around lines 51 - 55,
Remove or rewrite the obsolete quantity-zero and retained-placeholder comments
at test/lib/server-webhooks/modifier-refunds.test.ts:51-55, 89-93, and 130-137
to describe staged attendee deletion and the resulting absence of contact
history; update test/lib/server-webhooks/multi-ticket-booking.test.ts:137-138 to
describe the no-attendee outcome; and remove references to netting an attendee
to zero or a quantity-zero projected price in
test/lib/webhook-price-signature/stored-refund-and-ignore.test.ts:61-72. Keep
comments aligned with the current assertions and implementation, without
describing historical behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/api/webhooks.ts`:
- Around line 435-447: Update the expired/failed branch in the webhook handler
to inspect the result from tryCloseAndPurgeCheckoutStageBySession instead of
always returning a closed acknowledgement. Handle paid by freshly retrieving the
session and returning a successful response only when its paid state is
readable; return 503 when closure fails or the paid state cannot yet be read so
the webhook is retried. Handle error results with an explicit recovery path
rather than catching and continuing.

In `@src/shared/booking-lines.ts`:
- Around line 131-143: Update checkoutBookingLines so the listingById lookup
fails explicitly when no listing exists, replacing the unchecked assertion with
an immediate error that includes item.listingId. Preserve the existing line
construction for items with loaded listings.

In `@src/shared/db/checkout-stages.ts`:
- Around line 288-296: Remove the parallel checkout-stage API object and its
aliases from checkoutStagesApi, keeping the existing direct operation exports as
the single public surface. Migrate all callers of checkoutStagesApi.beginRefund,
cleanupLimit, finalizeRefund, find, loadByPaymentSession, purgePending, and
selectOldPending to the corresponding direct exports, then remove the
compatibility object.
- Around line 132-136: Update the read queries in the checkout-stage data access
flow, including the queries near the visible SELECT and the locations noted at
lines 159-164 and 205-207, to use `FROM checkout_stages AS checkout_stage`.
Qualify every selected and filtered checkout_stages column with the
`checkout_stage` alias consistently.

In `@src/shared/square.ts`:
- Around line 526-535: Update the response handling in the Square payment-link
flow around id, orderId, and url so missing required fields fail at the boundary
by throwing an explicit error instead of logging and returning null. Preserve
the successful return of { id, orderId, url } when all fields are present.

In `@src/shared/stripe.ts`:
- Line 458: Update the expires_at calculation in the Stripe session
configuration to use a buffer beyond the 30-minute minimum, such as 31 minutes,
instead of exactly 30 minutes. Keep the existing nowSeconds-based expiration
calculation unchanged otherwise.

In `@test/features/api/payment-processing/recovery.test.ts`:
- Around line 1-26: Remove the jscpd suppression directives around the import
blocks in test/features/api/payment-processing/recovery.test.ts (lines 1-26),
staged-refund-errors.test.ts (lines 1-25), staged-refunds.test.ts (lines 2-25),
and staged-runtime.test.ts (lines 1-18). Extract any genuine duplicated setup
into shared test utilities as needed, without restructuring code solely to evade
duplicate detection.

In `@test/lib/db/attendees/create-attendee-atomic.test.ts`:
- Around line 131-132: Update the test’s result handling so attendee creation
failure causes an immediate assertion failure instead of returning early. In the
success path, retain the existing getAttendeeRaw assertion for the created
attendee.

In `@test/lib/db/attendees/select.test.ts`:
- Line 14: Update the attendee select tests to stop importing or interpolating
ordinaryAttendeeCondition when constructing expected SQL; assert the literal NOT
EXISTS predicate directly in the affected exact-string cases. Add a focused unit
test for ordinaryAttendeeCondition itself covering its generated clause.

In `@test/lib/server-payments/cancel-race.test.ts`:
- Around line 1-19: Remove the jscpd:ignore-start and jscpd:ignore-end comments
from cancel-race.test.ts, then extract the genuinely repeated test setup into an
appropriate shared test helper and reuse it from this test so duplicate-code
detection passes without suppression.

In `@test/lib/server-payments/success.test.ts`:
- Around line 103-134: Update the test around handleRequest and the
closeCheckout stub to assert that the provider checkout is closed. Verify
closeCheckout is called exactly once with the expected failed session identifier
and provider checkout arguments, while preserving the existing response-status
and attendee-purge assertions.

In `@test/lib/server-reservation/deposit-basics.test.ts`:
- Around line 159-164: Extend the invalid-reservation test after the
attendee-count assertion to verify the refund mock’s invocation through
refund.calls and confirm the payment record reached its terminal refunded state.
Use the existing refund and payment-record symbols in the test, preserving the
current response and attendee-removal assertions.

In `@test/lib/server-reservation/edge-cases.test.ts`:
- Around line 112-121: Update stale test terminology to describe the current
staged-checkout removal behavior: rename the sold-out add-on test in
test/lib/server-reservation/edge-cases.test.ts:112-121 and mismatched zero-price
add-on test in test/lib/server-reservation/promo-addons.test.ts:132-140 to
mention refund and removal; replace retained-placeholder comments in
test/lib/server-webhooks/already-processed-rollback.test.ts:114-120,
test/lib/server-webhooks/can-pay-more-multi-ticket.test.ts:118-122 and :193-199,
and test/lib/server-webhooks/can-pay-more-single-ticket.test.ts:83-89 and
:119-125 with comments describing removal, including refund where applicable.

In `@test/lib/server-webhooks/custom-questions-multi.test.ts`:
- Around line 51-56: Replace the native array filter in realBookings in
test/lib/server-webhooks/custom-questions-multi.test.ts:51-56 with the
documented curried filter helper from `#fp`, adding the required documented FP
import. Apply the same `#fp` import and curried filtering style at
test/lib/server-webhooks/custom-questions-single.test.ts:39-41; preserve the
existing quantity > 0 behavior.

In `@test/lib/server-webhooks/session-resolution.test.ts`:
- Around line 128-155: Update the test “an expiry close failure keeps the stage
for scheduled retry” to load the checkout stage identified by “cs_expiry_retry”
after the webhook is ignored, and assert that it remains in its expected
retryable state. Keep the existing attendee and close-call assertions, but use
the stage lookup to detect accidental deletion.

In `@test/lib/webhook-price-signature/post-commit-recovery.test.ts`:
- Around line 281-282: Add direct assertions in the recovery test after the
repeated expectStoredRefund calls to verify that both the attendee and checkout
stage are absent. Use the test’s existing stage/attendee lookup helpers or
observable storage APIs, ensuring cleanup failures cause the test to fail while
preserving the replay and stored-failure assertions.

In `@test/shared/db/attendees/activate-staged.helpers.ts`:
- Around line 77-85: Update the exported storedActivationRows helper with a
named type describing its selected row shape, and explicitly declare its return
type as Promise of an array of that type. Keep the existing query and ordering
unchanged.

In `@test/shared/db/prune/checkout-stages.test.ts`:
- Around line 151-171: Make the test data ordering deterministic in the
“processes exactly the fixed cleanup bound” test by assigning every added stage
the same fixed createdAt value or inserting the stages sequentially instead of
using unordered Promise.all writes. Keep the cleanup-limit assertions unchanged
and ensure the expected remaining ID matches the deterministically newest stage.

In `@test/shared/square-checkout-close.test.ts`:
- Around line 157-165: Add a separate test alongside the existing wrong-order
case for closePaymentLink, using a provider response with id "other" and
cancelledOrderId "order"; assert that squareApi.closePaymentLink("link",
"order") rejects with the same wrong payment-link or order error, covering
independent validation of the returned payment-link ID.

In `@test/test-utils/settings.ts`:
- Around line 222-224: Require a valid string payment reference at both staging
boundaries: in test/test-utils/settings.ts lines 222-224, validate
payment_intent before invoking stagePaymentCallback instead of defaulting it to
an empty string; in test/test-utils/webhooks.ts lines 308-310, reject or skip
staging when session.paymentIntent is null. Preserve staging only for records
with an actual external payment identifier.

In `@test/test-utils/staged-payments.ts`:
- Around line 72-76: Update the listing lookup in the staged-payment
construction to fail when listingById lacks the referenced item.e entry, instead
of supplying fabricated customisable_days, duration_days, and listing_type
defaults. Preserve the existing listing data path for found listings and make
the boundary failure explicit before checkout metadata is created.
- Around line 107-112: Update the fallback pending checkout stage construction
in pendingCheckoutStageInsert to use fields.provider for payment provider and
fields.providerCheckoutId for providerCheckoutId, while retaining
fields.sessionId for paymentSessionId.

In `@test/test-utils/webhooks.ts`:
- Around line 160-171: Update the stale comment inside expectNoRefundPlaceholder
to describe that the helper asserts no quantity-zero placeholder records remain;
remove the outdated reference to an exactly-one-placeholder invariant while
leaving the filtering and assertion unchanged.

---

Outside diff comments:
In `@src/features/api/payment-processing/create.ts`:
- Around line 249-259: Update the booking construction inside bookingsForOrder
to use a throwing lookup for each pricingIntent item instead of allowing
paidByIntentItem.get(...) to return undefined. Always pass the resolved
pricePaid into the booking, so activation fails before creating inconsistent
booking or ledger state when the amount is missing.

In `@src/shared/payment-helpers.ts`:
- Around line 414-451: Require an explicit providerCheckoutId in
toCheckoutResult instead of defaulting it to sessionId, and make the
corresponding providerId field required in makeCreateCheckoutSession’s
readResult contract. Update all provider mappings to supply the hosted-checkout
identifier explicitly, while preserving rejection when it is absent.

In `@test/lib/server-payments/success.test.ts`:
- Around line 220-238: Update the comments surrounding the signed-by-us
late-buyer refund scenario in the test to describe the current staged-refund
behavior reflected by expectNoRefundPlaceholder and
expectRefundedWithoutAttendee. Remove claims that a quantity-zero placeholder,
system note, or retained booking is created, while preserving the existing
assertions and response expectations.

In `@test/lib/server-webhooks/modifier-refunds.test.ts`:
- Around line 51-55: Remove or rewrite the obsolete quantity-zero and
retained-placeholder comments at
test/lib/server-webhooks/modifier-refunds.test.ts:51-55, 89-93, and 130-137 to
describe staged attendee deletion and the resulting absence of contact history;
update test/lib/server-webhooks/multi-ticket-booking.test.ts:137-138 to describe
the no-attendee outcome; and remove references to netting an attendee to zero or
a quantity-zero projected price in
test/lib/webhook-price-signature/stored-refund-and-ignore.test.ts:61-72. Keep
comments aligned with the current assertions and implementation, without
describing historical behavior.

In `@test/lib/server-webhooks/multi-ticket-refunds.test.ts`:
- Around line 87-93: Update the comments describing refund assertions to reflect
the current staged-attendee removal behavior rather than quantity-zero
placeholder retention: in test/lib/server-webhooks/multi-ticket-refunds.test.ts
ranges 87-93, 133-135, 170-176, and 207-213, describe removal from the relevant
listing(s) and the single refund; in
test/lib/server-webhooks/single-ticket-refunds.test.ts ranges 64-70 and 131-137,
describe staged-attendee removal after refund, including the redirect path.

In `@test/lib/server-webhooks/refund-helper-functions.test.ts`:
- Around line 150-180: Update the test “an unexpected uncommitted activation
error refunds” to call stageStripeCallback("cs_create_boom") after configuring
the checkout session and before handleRequest exercises the success flow,
ensuring the staged checkout reaches the mocked activateStagedAttendee failure
and refund assertion.

In `@test/lib/server-webhooks/refund-skip-conditions.test.ts`:
- Around line 128-162: The test currently covers only the unsigned webhook path,
not the signed checkout with a missing listing. Update the test around
checkoutSessionEvent to construct valid signed metadata for the multi-ticket
payload, then assert the expected refund and attendee-removal outcome; move the
existing unsigned ignored/no-refund assertions into a separate test if they
should remain covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4eb1d72a-594c-46bc-9a5b-6ffbeb62107d

📥 Commits

Reviewing files that changed from the base of the PR and between ca8040e and 2750b67.

📒 Files selected for processing (138)
  • src/features/api/folded-booking.ts
  • src/features/api/payment-processing/cancel.ts
  • src/features/api/payment-processing/classify.ts
  • src/features/api/payment-processing/create.ts
  • src/features/api/payment-processing/index.ts
  • src/features/api/payment-processing/items.ts
  • src/features/api/payment-processing/pricing.ts
  • src/features/api/payment-processing/recovery-decision.ts
  • src/features/api/payment-processing/recovery.ts
  • src/features/api/payment-processing/refunds.ts
  • src/features/api/payment-processing/replay.ts
  • src/features/api/payment-processing/store-refund.ts
  • src/features/api/sms-webhook.ts
  • src/features/api/webhooks.ts
  • src/features/public/ticket-payment.ts
  • src/shared/accounting/rows.ts
  • src/shared/booking-lines.ts
  • src/shared/booking.ts
  • src/shared/db/address-cache.ts
  • src/shared/db/attendees/activate.ts
  • src/shared/db/attendees/api.ts
  • src/shared/db/attendees/capacity/checks.ts
  • src/shared/db/attendees/create-batch.ts
  • src/shared/db/attendees/create.ts
  • src/shared/db/attendees/delete.ts
  • src/shared/db/attendees/order-parents.ts
  • src/shared/db/attendees/ordinary.ts
  • src/shared/db/attendees/queries.ts
  • src/shared/db/attendees/select.ts
  • src/shared/db/attendees/tokens.ts
  • src/shared/db/checkout-stages.ts
  • src/shared/db/listing-overview-stats.ts
  • src/shared/db/migrations/2026-07-17_checkout_stage_provider_id.ts
  • src/shared/db/migrations/registry.ts
  • src/shared/db/migrations/schema/tables-attendees.ts
  • src/shared/db/modifier-usage.ts
  • src/shared/db/processed-payments.ts
  • src/shared/db/prune.ts
  • src/shared/db/questions/attendee-answers/reads.ts
  • src/shared/db/system-notes.ts
  • src/shared/now.ts
  • src/shared/payment-helpers.ts
  • src/shared/payments.ts
  • src/shared/refund-ledger.ts
  • src/shared/square-provider.ts
  • src/shared/square.ts
  • src/shared/staged-checkout.ts
  • src/shared/stripe-provider.ts
  • src/shared/stripe.ts
  • src/shared/sumup-provider.ts
  • src/shared/sumup.ts
  • test/e2e/accounting/drivers.ts
  • test/features/api/payment-processing/create.test.ts
  • test/features/api/payment-processing/recovery-decision.test.ts
  • test/features/api/payment-processing/recovery.test.ts
  • test/features/api/payment-processing/staged-refund-errors.test.ts
  • test/features/api/payment-processing/staged-refunds.test.ts
  • test/features/api/payment-processing/staged-runtime.helpers.ts
  • test/features/api/payment-processing/staged-runtime.test.ts
  • test/lib/db/attendees/create-attendee-atomic.test.ts
  • test/lib/db/attendees/delete-attendee.test.ts
  • test/lib/db/attendees/get-newest-attendees-raw.test.ts
  • test/lib/db/attendees/select.test.ts
  • test/lib/db/checkout-stage-schema.test.ts
  • test/lib/db/migration-restore/verify.test.ts
  • test/lib/db/migration-schema-guard.test.ts
  • test/lib/payment-success-helpers.ts
  • test/lib/server-api-packages.test.ts
  • test/lib/server-attendees-list.test.ts
  • test/lib/server-balance-payment-replay.test.ts
  • test/lib/server-listings/show-actions-and-activity.test.ts
  • test/lib/server-package-children.test.ts
  • test/lib/server-payments-success-basic.test.ts
  • test/lib/server-payments-success-refunds.test.ts
  • test/lib/server-payments-success-replay.test.ts
  • test/lib/server-payments/cancel-race.test.ts
  • test/lib/server-payments/cancel.test.ts
  • test/lib/server-payments/replay.test.ts
  • test/lib/server-payments/success.test.ts
  • test/lib/server-public/ticket-additional-coverage.test.ts
  • test/lib/server-qr-book.test.ts
  • test/lib/server-reservation/deposit-basics.test.ts
  • test/lib/server-reservation/edge-cases.test.ts
  • test/lib/server-reservation/helpers.ts
  • test/lib/server-reservation/promo-addons.test.ts
  • test/lib/server-webhook-dual-path.test.ts
  • test/lib/server-webhooks/already-processed-rollback.test.ts
  • test/lib/server-webhooks/can-pay-more-multi-ticket.test.ts
  • test/lib/server-webhooks/can-pay-more-single-ticket.test.ts
  • test/lib/server-webhooks/custom-questions-multi.test.ts
  • test/lib/server-webhooks/custom-questions-single.test.ts
  • test/lib/server-webhooks/customisable-days-pricing.test.ts
  • test/lib/server-webhooks/modifier-refunds.test.ts
  • test/lib/server-webhooks/multi-ticket-booking.test.ts
  • test/lib/server-webhooks/multi-ticket-refunds.test.ts
  • test/lib/server-webhooks/price-paid-calculation.test.ts
  • test/lib/server-webhooks/refund-helper-functions.test.ts
  • test/lib/server-webhooks/refund-logging.test.ts
  • test/lib/server-webhooks/refund-skip-conditions.test.ts
  • test/lib/server-webhooks/session-resolution.test.ts
  • test/lib/server-webhooks/single-ticket-refunds.test.ts
  • test/lib/server-webhooks/sumup.test.ts
  • test/lib/square/fixtures.ts
  • test/lib/square/provider.test.ts
  • test/lib/square/retrieve-refund.test.ts
  • test/lib/stripe/core.test.ts
  • test/lib/stripe/provider.test.ts
  • test/lib/stripe/webhook-setup.test.ts
  • test/lib/webhook-price-signature/helpers.ts
  • test/lib/webhook-price-signature/post-commit-recovery.test.ts
  • test/lib/webhook-price-signature/stored-refund-and-ignore.test.ts
  • test/shared/db/attendees/activate-staged-refusals.test.ts
  • test/shared/db/attendees/activate-staged.helpers.ts
  • test/shared/db/attendees/activate-staged.test.ts
  • test/shared/db/orphan-attendees.test.ts
  • test/shared/db/prune/checkout-stages.test.ts
  • test/shared/db/system-notes.test.ts
  • test/shared/now.test.ts
  • test/shared/payment-helpers/dispatch.test.ts
  • test/shared/payments.test.ts
  • test/shared/refund-ledger-placeholder.test.ts
  • test/shared/square-checkout-close.test.ts
  • test/shared/square-provider.test.ts
  • test/shared/square/payment-link-validation.test.ts
  • test/shared/square/payment-link.test.ts
  • test/shared/square/rest-transport.test.ts
  • test/shared/staged-checkout.test.ts
  • test/shared/stripe-checkout-close.test.ts
  • test/shared/sumup-checkout-close.test.ts
  • test/shared/sumup-provider.test.ts
  • test/test-utils/checkout-stages.ts
  • test/test-utils/checkout.ts
  • test/test-utils/order-journey.ts
  • test/test-utils/settings.ts
  • test/test-utils/staged-payments.test.ts
  • test/test-utils/staged-payments.ts
  • test/test-utils/system-notes.ts
  • test/test-utils/webhooks.ts
💤 Files with no reviewable changes (3)
  • src/features/api/payment-processing/recovery-decision.ts
  • test/shared/refund-ledger-placeholder.test.ts
  • test/features/api/payment-processing/recovery-decision.test.ts

Comment thread src/features/api/webhooks.ts Outdated
Comment thread src/shared/booking-lines.ts
Comment thread src/shared/db/checkout-stages.ts Outdated
Comment thread src/shared/db/checkout-stages.ts Outdated
Comment thread src/shared/square.ts
Comment thread test/shared/square-checkout-close.test.ts Outdated
Comment thread test/test-utils/settings.ts Outdated
Comment thread test/test-utils/staged-payments.ts Outdated
Comment thread test/test-utils/staged-payments.ts Outdated
Comment thread test/test-utils/webhooks.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2750b67224

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/api/payment-processing/replay.ts
Comment thread src/shared/sumup.ts Outdated
Comment thread src/features/api/payment-processing/index.ts Outdated
@stefan-burke

Copy link
Copy Markdown
Member Author

Outside-diff findings from the review are addressed in 118facfa:

  1. refund-helper-functions.test.ts: no change needed. stubRetrieveCheckoutSession already stages the session before activation, so the mocked rejection and refund assertion are reached without a duplicate explicit stage call.
  2. payment-processing/create.ts: paid amounts now use a throwing lookup, with a preparation-failure regression test.
  3. server-payments/success.test.ts: stale placeholder comments now describe staged-attendee removal and terminal replay state.
  4. payment-helpers.ts: provider checkout ids are explicit and required in every adapter; missing ids return no checkout result.
  5. Multi/single-ticket refund comments now describe removal from the relevant listings and one refund.
  6. refund-skip-conditions.test.ts: the missing-listing case now stages valid signed metadata, deletes the listing, and asserts refund plus stage/attendee removal.
  7. Modifier and stored-refund comments now describe attendee deletion and the cash-only refund ledger.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/square.ts (1)

629-646: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not recover from a wrong-resource delete response.

The deleted.id / cancelledOrderId mismatch is thrown inside the try, so the catch treats it like a delete failure. If the follow-up order lookup is already COMPLETED or CANCELED, this returns "paid"/"closed" even though Square reported the wrong payment link or order. Move the response validation outside the recovery block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/square.ts` around lines 629 - 646, The payment-link response
validation must not be handled by the delete-failure recovery in the surrounding
try/catch. In the payment-link deletion flow, retain the delete call inside the
try, but move the deleted.id and deleted.cancelledOrderId checks after the catch
so mismatched resources always throw instead of returning paid or closed based
on fetchOrder.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/api/payment-processing/create.ts`:
- Around line 254-266: Replace the native validatedItems.map call in the
payment-processing creation flow with the repository’s curried map helper,
importing it according to the documented `#fp` conventions. Also update
test/shared/sumup.test.ts lines 101-103 to use the curried `#fp` filter helper
instead of native filter; both sites require direct changes.

In `@test/lib/stripe/core.test.ts`:
- Around line 223-226: Update the expiry assertions around sessions.create to
capture one Unix timestamp before invoking checkout, then reuse it for both
bounds. Use an inclusive lower-bound comparison against the captured timestamp
plus 30 minutes, while retaining the existing upper-bound window.

In `@test/test-utils/staged-payments.ts`:
- Around line 73-77: Wrap the activation-dependent staging flow in a try/finally
that always restores listings activated by the setup, including when
requiredMapValue throws for a missing listing. Update the staged-payment test
coverage with a mixed inactive-and-missing-listing regression case, and verify
cleanup leaves no temporary active state for subsequent tests.

In `@test/test-utils/webhooks.ts`:
- Around line 116-123: Update the staged-refund webhook assertion docblock near
verifyWebhookSignature and stripeApi.refundPayment so its default error
description refers to the couldn't-complete-your-booking response instead of the
saved-your-details message; retain the override guidance for scenario-specific
messages.

---

Outside diff comments:
In `@src/shared/square.ts`:
- Around line 629-646: The payment-link response validation must not be handled
by the delete-failure recovery in the surrounding try/catch. In the payment-link
deletion flow, retain the delete call inside the try, but move the deleted.id
and deleted.cancelledOrderId checks after the catch so mismatched resources
always throw instead of returning paid or closed based on fetchOrder.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c09c3752-94ab-451f-a169-8dd9eca289e9

📥 Commits

Reviewing files that changed from the base of the PR and between 2750b67 and 118facf.

📒 Files selected for processing (60)
  • scripts/mutation/equivalent-mutants.txt
  • src/features/api/payment-processing/create.ts
  • src/features/api/payment-processing/index.ts
  • src/features/api/payment-processing/recovery.ts
  • src/features/api/payment-processing/replay.ts
  • src/features/api/payment-processing/store-refund.ts
  • src/features/api/webhooks.ts
  • src/shared/booking-lines.ts
  • src/shared/db/checkout-stages.ts
  • src/shared/payment-helpers.ts
  • src/shared/square.ts
  • src/shared/staged-checkout.ts
  • src/shared/stripe-provider.ts
  • src/shared/stripe.ts
  • src/shared/sumup-provider.ts
  • src/shared/sumup.ts
  • test/features/api/payment-processing/create.test.ts
  • test/features/api/payment-processing/recovery.test.ts
  • test/features/api/payment-processing/replay.test.ts
  • test/features/api/payment-processing/staged-refund-errors.test.ts
  • test/features/api/payment-processing/staged-refunds.test.ts
  • test/features/api/payment-processing/staged-runtime.test.ts
  • test/lib/db/attendees/select.test.ts
  • test/lib/server-payments/replay.test.ts
  • test/lib/server-payments/success.test.ts
  • test/lib/server-reservation/deposit-basics.test.ts
  • test/lib/server-reservation/edge-cases.test.ts
  • test/lib/server-reservation/promo-addons.test.ts
  • test/lib/server-webhooks/already-processed-rollback.test.ts
  • test/lib/server-webhooks/can-pay-more-multi-ticket.test.ts
  • test/lib/server-webhooks/can-pay-more-single-ticket.test.ts
  • test/lib/server-webhooks/custom-questions-multi.test.ts
  • test/lib/server-webhooks/custom-questions-single.test.ts
  • test/lib/server-webhooks/customisable-days-pricing.test.ts
  • test/lib/server-webhooks/extract-intent-redirect.test.ts
  • test/lib/server-webhooks/modifier-refunds.test.ts
  • test/lib/server-webhooks/multi-ticket-booking.test.ts
  • test/lib/server-webhooks/multi-ticket-refunds.test.ts
  • test/lib/server-webhooks/refund-skip-conditions.test.ts
  • test/lib/server-webhooks/session-resolution.test.ts
  • test/lib/server-webhooks/single-ticket-refunds.test.ts
  • test/lib/stripe/core.test.ts
  • test/lib/webhook-price-signature/helpers.ts
  • test/lib/webhook-price-signature/post-commit-recovery.test.ts
  • test/lib/webhook-price-signature/stored-refund-and-ignore.test.ts
  • test/lib/webhook-price-signature/trusted-and-mismatch.test.ts
  • test/shared/booking-lines.test.ts
  • test/shared/db/attendees/activate-staged-refusals.test.ts
  • test/shared/db/attendees/activate-staged.helpers.ts
  • test/shared/db/attendees/activate-staged.test.ts
  • test/shared/db/prune/checkout-stages.test.ts
  • test/shared/payment-helpers/dispatch.test.ts
  • test/shared/square-checkout-close.test.ts
  • test/shared/staged-checkout.test.ts
  • test/shared/sumup-checkout-close.test.ts
  • test/shared/sumup.test.ts
  • test/test-utils/settings.ts
  • test/test-utils/staged-payments.test.ts
  • test/test-utils/staged-payments.ts
  • test/test-utils/webhooks.ts
💤 Files with no reviewable changes (1)
  • test/lib/server-webhooks/extract-intent-redirect.test.ts

Comment thread src/features/api/payment-processing/create.ts Outdated
Comment thread test/lib/stripe/core.test.ts Outdated
Comment thread test/test-utils/staged-payments.ts Outdated
Comment thread test/test-utils/webhooks.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 118facfa03

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/staged-checkout.ts Outdated
Comment thread src/shared/square.ts Outdated
Comment thread src/shared/square.ts Outdated
Comment thread src/shared/staged-checkout.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/square-provider.ts (1)

115-119: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject malformed Square payment webhooks instead of returning null.

For a Square payment event, order_id and id are required provider fields. Returning null classifies malformed data as an unrelated session and may acknowledge a paid webhook without retrying it. Throw explicitly or validate the payload at this boundary.

Proposed fix
-    if (!orderId || !paymentId) return Promise.resolve(null);
+    if (!orderId || !paymentId) {
+      throw new Error("Square payment webhook is missing order_id or id");
+    }

As per coding guidelines, “Missing expected fields from external data must fail at the boundary through Valibot validation or an explicit throw.” <coding_guidelines>

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/square-provider.ts` around lines 115 - 119, Update the Square
payment webhook handling around the orderId and paymentId extraction to reject
malformed payloads when either required provider field is missing or invalid.
Replace the Promise.resolve(null) path with an explicit throw or the established
Valibot boundary validation, while preserving null handling for unrelated, valid
events.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shared/square-payments.ts`:
- Around line 48-58: Update the payment validation condition in the Square
payment helper to reject payments with any refund, including partial refunds, by
requiring refunded_money.amount to equal zero and validating its currency
against the order currency. Preserve the existing checks for payment identity,
completion status, order association, amount type/range, and currency.

In `@src/shared/staged-checkout.ts`:
- Around line 139-146: Update paidCheckoutBookingsOrNull to replace the native
intent.items.map call with the repository’s curried `#fp` map helper, adding the
helper import according to the established FP import conventions while
preserving the existing listing ID deduplication behavior.

---

Outside diff comments:
In `@src/shared/square-provider.ts`:
- Around line 115-119: Update the Square payment webhook handling around the
orderId and paymentId extraction to reject malformed payloads when either
required provider field is missing or invalid. Replace the Promise.resolve(null)
path with an explicit throw or the established Valibot boundary validation,
while preserving null handling for unrelated, valid events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bede89fa-a112-4ed3-b231-2253b7b6c89d

📥 Commits

Reviewing files that changed from the base of the PR and between 118facf and 2449879.

📒 Files selected for processing (39)
  • src/features/admin/attendee-refunds.ts
  • src/features/admin/refunds/provider.ts
  • src/features/admin/refunds/waves.ts
  • src/features/api/payment-processing/create.ts
  • src/features/api/payment-processing/index.ts
  • src/features/api/payment-processing/refunds.ts
  • src/features/api/payment-processing/replay.ts
  • src/features/api/payment-processing/store-refund.ts
  • src/features/api/webhook-types.ts
  • src/features/api/webhooks.ts
  • src/fp.ts
  • src/shared/payments.ts
  • src/shared/square-payments.ts
  • src/shared/square-provider.ts
  • src/shared/square.ts
  • src/shared/staged-checkout.ts
  • src/shared/stripe-provider.ts
  • src/shared/sumup-provider.ts
  • test/e2e/accounting/drivers.ts
  • test/features/admin/refunds/provider-batch.test.ts
  • test/features/admin/refunds/provider.test.ts
  • test/features/api/payment-processing/recovery.test.ts
  • test/features/api/payment-processing/replay.test.ts
  • test/features/api/payment-processing/staged-refund-errors.test.ts
  • test/features/api/payment-processing/staged-refunds.test.ts
  • test/lib/server-payments/replay.test.ts
  • test/lib/server-webhooks/refund-helper-functions.test.ts
  • test/lib/square/provider.test.ts
  • test/lib/square/retrieve-refund.test.ts
  • test/lib/stripe/core.test.ts
  • test/lib/stripe/provider.test.ts
  • test/shared/square-checkout-close.test.ts
  • test/shared/staged-checkout.test.ts
  • test/shared/sumup-provider.test.ts
  • test/shared/sumup.test.ts
  • test/test-utils/refund-routes.ts
  • test/test-utils/staged-payments.test.ts
  • test/test-utils/staged-payments.ts
  • test/test-utils/webhooks.ts

Comment thread src/shared/square-payments.ts Outdated
Comment thread src/shared/staged-checkout.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 244987947b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/stripe-provider.ts
Comment thread src/features/api/payment-processing/index.ts Outdated
Comment thread src/features/admin/refunds/provider.ts
@stefan-burke

Copy link
Copy Markdown
Member Author

The latest outside-diff Square finding is resolved in 24498794: successful delete-response validation now runs after the delete-only try/catch, so a wrong payment-link or order id always throws and cannot be converted into a paid/closed race result. Separate wrong-link and wrong-order regressions also assert that no recovery read occurs after a successful but invalid response. Full precommit passes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/features/admin/attendee-refunds.ts (1)

202-211: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move bulk refund messages into the locale catalog.

The updated msg text is user-facing copy in src/features/admin/attendee-refunds.ts. Add parameterized entries to src/locales/en/attendees.json and access them through t(...) instead of constructing the copy here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/admin/attendee-refunds.ts` around lines 202 - 211, Move the
user-facing refund summary text from the msg construction in the bulk refund
flow into parameterized entries in the attendees locale catalog. Add separate
translations for the remaining and completed cases, including refundedCount,
pendingCount, problemCount, errorNote, and remaining parameters as needed, then
use t(...) to select the appropriate entry while preserving the existing message
content and branching behavior.

Source: Coding guidelines

test/shared/square-provider.test.ts (1)

35-80: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the Square order state configurable.

sessionMocks always returns state: "COMPLETED" at Line 56, but the refactored test at Lines 178-183 is for an OPEN order with a COMPLETED payment. That change removes the setup for the branch being tested. Add an orderState option and pass "OPEN" for this case, or retain the inline order stub.

Also applies to: 178-183

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/shared/square-provider.test.ts` around lines 35 - 80, The sessionMocks
helper hardcodes the Square order state, preventing tests from modeling
non-completed orders. Add an orderState option to sessionMocks with the existing
completed state as its default, use it for the returned order state, and pass
"OPEN" in the test covering an open order with a completed payment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/test-utils/refund-routes.ts`:
- Around line 141-145: Update the exported withRefundMock helper signature to
explicitly declare a Promise<void> return type, while preserving its existing
parameters and implementation behavior.

---

Outside diff comments:
In `@src/features/admin/attendee-refunds.ts`:
- Around line 202-211: Move the user-facing refund summary text from the msg
construction in the bulk refund flow into parameterized entries in the attendees
locale catalog. Add separate translations for the remaining and completed cases,
including refundedCount, pendingCount, problemCount, errorNote, and remaining
parameters as needed, then use t(...) to select the appropriate entry while
preserving the existing message content and branching behavior.

In `@test/shared/square-provider.test.ts`:
- Around line 35-80: The sessionMocks helper hardcodes the Square order state,
preventing tests from modeling non-completed orders. Add an orderState option to
sessionMocks with the existing completed state as its default, use it for the
returned order state, and pass "OPEN" in the test covering an open order with a
completed payment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 14805a51-fff4-477d-b76d-63d73f1a42c5

📥 Commits

Reviewing files that changed from the base of the PR and between 2449879 and e34c3e0.

📒 Files selected for processing (31)
  • src/features/admin/attendee-refunds.ts
  • src/features/api/payment-processing/index.ts
  • src/features/api/payment-processing/pricing.ts
  • src/features/api/payment-processing/refunds.ts
  • src/features/api/payment-processing/store-refund.ts
  • src/locales/en/attendees.json
  • src/shared/db/checkout-stages.ts
  • src/shared/db/migrations/2026-07-18_checkout_stage_refund_spec.ts
  • src/shared/db/migrations/registry.ts
  • src/shared/db/migrations/schema/tables-attendees.ts
  • src/shared/payment-idempotency.ts
  • src/shared/refund-reasons.ts
  • src/shared/square-payments.ts
  • src/shared/square.ts
  • src/shared/staged-checkout.ts
  • src/shared/stripe.ts
  • test/features/api/payment-processing/create.test.ts
  • test/features/api/payment-processing/recovery.test.ts
  • test/features/api/payment-processing/staged-refund-errors.test.ts
  • test/features/api/payment-processing/staged-refunds.test.ts
  • test/lib/db/checkout-stage-schema.test.ts
  • test/lib/db/migration-schema-guard.test.ts
  • test/lib/server-refunds.test.ts
  • test/lib/stripe-mock/ports.test.ts
  • test/lib/stripe/core.test.ts
  • test/shared/db/attendees/activate-staged-refusals.test.ts
  • test/shared/db/attendees/activate-staged.test.ts
  • test/shared/db/migration-round-trip-budget.test.ts
  • test/shared/square-provider.test.ts
  • test/test-utils/checkout-stages.ts
  • test/test-utils/refund-routes.ts
💤 Files with no reviewable changes (1)
  • test/lib/stripe-mock/ports.test.ts

Comment thread test/test-utils/refund-routes.ts Outdated
@stefan-burke

Copy link
Copy Markdown
Member Author

The outside-diff malformed Square webhook finding is resolved in 42cccd3b. Square payment events now throw when either order_id or payment id is absent, so malformed paid callbacks are retried instead of acknowledged as unrelated. Unrelated non-payment events can still return null; direct regressions cover both missing-identifier cases and that unrelated-event path. Full precommit passes.

@stefan-burke

Copy link
Copy Markdown
Member Author

The outside-diff bulk-refund copy finding is resolved in e5c05a5b. The summary, error note, remaining-work prompt, and completed-batch note now come from parameterized attendees.json catalog entries and are composed without hard-coded user-facing text. The bulk route tests cover errors, remaining batches, and completed partial failures; full precommit passes.

@stefan-burke

Copy link
Copy Markdown
Member Author

The outside-diff Square fixture finding is resolved in e5c05a5b. sessionMocks now accepts an orderState with the existing COMPLETED default, and the paid-payment/open-order regression explicitly supplies OPEN, so it exercises the branch named by the test. The focused Square suite and full precommit pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

${pricePaidFromLedger(
"listingAttendee.attendee_id",
"listingAttendee.listing_id",
"listingAttendee.ledger_event_group",
"listingAttendee.id",
)},

P2 Badge Preserve paid-now amounts for deposit checkouts

When a public reservation/deposit checkout completes, bookingFactsFromOrder records sale legs for the full ticket value and a smaller payment leg for the deposit actually collected, but this reconstruction reads attendee.price_paid from the sale ledger projection. The confirmation email, admin “Amount paid”, and webhook price_paid field therefore report the full ticket price even though remaining_balance is still positive, overstating what the customer paid for any deposit booking. Keep using the per-line charged amounts for price_paid when rebuilding the just-committed entries.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/api/webhooks.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dafc381883

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/square-payments.ts Outdated
Comment thread src/shared/square-provider.ts Outdated
Comment thread src/shared/staged-checkout.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24dcf46829

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/maintenance/registry.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f94e69cb44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/square.ts
Comment thread src/features/api/payment-processing/maintenance.ts Outdated
Comment thread src/shared/payment-refunds.ts Outdated
…-runtime

# Conflicts:
#	scripts/mutation/equivalent-mutants.txt
#	src/features/api/payment-processing/index.ts
#	src/features/api/payment-processing/items.ts
#	test/features/api/payment-processing/index/refunds.test.ts
#	test/features/api/payment-processing/items.test.ts
@stefan-burke stefan-burke changed the title Finish paid checkouts safely after payment Finish and recover paid checkouts safely Jul 23, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e267accfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +89 to +90
stored.start_at === expected.startAt &&
stored.end_at === expected.endAt &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not reject stages after duration edits

When a daily listing's duration_days is changed after the checkout stage is written but before the buyer pays, the staged zero-quantity row still has the old end_at, while activation rebuilds expected from the current listing facts. This exact start_at/end_at match makes stagedLinesMatch fail and refunds the paid session as a stage_mismatch/unexpected_error, even though the later capacity check could validate the new range and the booking could be honoured. Match the immutable booking slot separately from the mutable date range, or update the range during activation.

Useful? React with 👍 / 👎.

…-runtime

# Conflicts:
#	scripts/mutation/equivalent-mutants.txt
#	src/shared/square-provider.ts
#	src/shared/square.ts
#	test/integration/server/webhooks/square.test.ts
#	test/shared/square-provider.test.ts
#	test/shared/string-entries.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6730daad5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

const session = await provider.retrieveSession(sessionId);
const session = await provider.retrieveSession(sessionId, "callback");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the staged provider for callback lookup

Fresh evidence beyond the earlier refund-provider fix is that this callback path still retrieves the session through the currently active provider before it ever looks at the stored checkout stage. When an operator changes the active provider while a hosted checkout is still open, a paid session created by the old provider is reported as “not found” here, and the buyer’s redirect cannot create the ticket even though checkout_stages records the provider that owns the session; old-provider webhooks are similarly unable to complete it until scheduled recovery eventually runs. Load the stage for staged session ids and retrieve with stage.provider instead of the current setting.

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
if (ids.length > 1) {
throw new Error(`Square order ${order.id} has multiple tenders`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle multi-tender Square recovery

When a Square order has more than one tender, this throw is reached by both pending cleanup (closePaymentLink) and paid-stage recovery (retrieveSession(..., "recovery")). recoverCheckoutStage catches the error and only defers the same row, so a checkout whose redirect/webhook were missed but whose Square order has a failed tender plus a completed tender will never be marked paid or processed by scheduled recovery. Use a bounded completed-tender lookup or a stored tender id instead of making multiple tenders a permanent retry.

Useful? React with 👍 / 👎.

logDebug("Payment", "Refund issued");
return result;
}
if (result === "pending") return result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Poll pending idempotent refunds before replaying creates

Fresh evidence after the idempotency-key fix is that a staged Stripe refund that first returns pending exits here without storing a refund id or switching future recovery to an inspect path. The next staged retry calls refunds.create again with the same stable key, and Stripe documents that same-key retries return the saved first response body (https://docs.stripe.com/api/idempotent_requests), so a refund that later succeeds can keep looking pending locally until the key ages out; after that, the replay may be treated as a new create/failure while the original refund was already accepted. Persist/inspect the pending refund instead of replaying the create request.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a742ff4dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}: CreatePaidCheckoutInput): Promise<PaidCheckoutResult> => {
const bookings = await paidCheckoutBookingsOrNull(intent);
if (bookings === null) return { type: "sold_out" };
const available = await attendeesApi.checkBatchAvailability(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat the second availability miss as sold out

Fresh evidence in this version is that paidCheckoutBookingsOrNull() can return real listing facts, but this second availability read still re-queries listings and checkBatchAvailabilityImpl throws Listing not found if the listing is deleted in that small window before provider checkout creation. That exception is not caught here, so the public submit path returns a 500 even though no checkout exists and the normal outcome should be the same handled sold-out response.

Useful? React with 👍 / 👎.

const inspectAfterFirst = provider.refundRetryMode === "inspect-after-first";
if (
inspectAfterFirst &&
!(await claimPaymentRefundAttempt(provider.type, paymentReference))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid stranding refunds before the SumUp POST

For inspect-after-first providers, if the worker dies after this durable claim succeeds but before provider.refundPayment() is actually called, every later retry takes the already-claimed branch and only inspects provider state. With SumUp that leaves a still-SUCCESSFUL transaction reported as pending forever, so staged/admin refunds can remain stuck without ever submitting the refund or surfacing a terminal failure; the claim needs proof that the non-idempotent POST started, or stale pre-submit claims need an explicit recovery/alert path.

Useful? React with 👍 / 👎.

stefan-burke added a commit that referenced this pull request Jul 24, 2026
Codex review pointed out that returning false for a refund response missing its object/id/status silently swallowed a Square contract break as an ordinary not-yet-refunded payment — the codebase's offensive-programming rule says a missing documented field is a hard no that must fail at the boundary. refundPayment now throws when the 200 response lacks a refund object, id, or status; withClient contains the throw into a logged false, so the bad response is diagnosable instead of silently false. PENDING/FAILED/unknown statuses still return false silently (only a present-but-not-confirmed status is a normal not-yet-refunded outcome). Adds a test asserting the boundary error log; the existing fails-safely tests still hold (the throw is contained to the same false outcome). The pending-result union Codex also suggested (propagate a pending refund id instead of false) is the staged-checkout/callback work this PR was told not to introduce and is recorded in TODO.md against #1853/#1905.

Refreshed the surviving equivalent-mutant line numbers in equivalent-mutants.txt (the helper removal shifted result ?? false and locations ?? []).
stefan-burke added a commit that referenced this pull request Jul 24, 2026
Codex follow-up (thread 3): the boundary throw lived inside withClient, which catches non-PaymentUserError exceptions and returns null, so it was architecturally the same as a logged false — an indirect throw-and-catch rather than a real loud failure. Replaced it with an explicit logError + return false, matching the missing-amount case just above it: the malformed response is still diagnosed at the boundary under E_SQUARE_REFUND and still fails safely to false (never reports a refund that was never confirmed), now without the swallowed-throw indirection. Propagating the throw outside withClient (a loud/errored failure) would change the contract from fail-safely-contained to fail-loudly, which is the per-path staged-checkout/callback resolution this PR was told not to introduce and is recorded in TODO.md against #1853 and #1905. Exhaustive mutation on square.ts is now 100% (286 killed, 3 genuine equivalents suppressed).
stefan-burke added a commit that referenced this pull request Jul 24, 2026
Codex review pointed out that returning false for a refund response missing its object/id/status silently swallowed a Square contract break as an ordinary not-yet-refunded payment — the codebase's offensive-programming rule says a missing documented field is a hard no that must fail at the boundary. refundPayment now throws when the 200 response lacks a refund object, id, or status; withClient contains the throw into a logged false, so the bad response is diagnosable instead of silently false. PENDING/FAILED/unknown statuses still return false silently (only a present-but-not-confirmed status is a normal not-yet-refunded outcome). Adds a test asserting the boundary error log; the existing fails-safely tests still hold (the throw is contained to the same false outcome). The pending-result union Codex also suggested (propagate a pending refund id instead of false) is the staged-checkout/callback work this PR was told not to introduce and is recorded in TODO.md against #1853/#1905.

Refreshed the surviving equivalent-mutant line numbers in equivalent-mutants.txt (the helper removal shifted result ?? false and locations ?? []).
stefan-burke added a commit that referenced this pull request Jul 24, 2026
Codex follow-up (thread 3): the boundary throw lived inside withClient, which catches non-PaymentUserError exceptions and returns null, so it was architecturally the same as a logged false — an indirect throw-and-catch rather than a real loud failure. Replaced it with an explicit logError + return false, matching the missing-amount case just above it: the malformed response is still diagnosed at the boundary under E_SQUARE_REFUND and still fails safely to false (never reports a refund that was never confirmed), now without the swallowed-throw indirection. Propagating the throw outside withClient (a loud/errored failure) would change the contract from fail-safely-contained to fail-loudly, which is the per-path staged-checkout/callback resolution this PR was told not to introduce and is recorded in TODO.md against #1853 and #1905. Exhaustive mutation on square.ts is now 100% (286 killed, 3 genuine equivalents suppressed).
stefan-burke added a commit that referenced this pull request Jul 25, 2026
Codex review pointed out that returning false for a refund response missing its object/id/status silently swallowed a Square contract break as an ordinary not-yet-refunded payment — the codebase's offensive-programming rule says a missing documented field is a hard no that must fail at the boundary. refundPayment now throws when the 200 response lacks a refund object, id, or status; withClient contains the throw into a logged false, so the bad response is diagnosable instead of silently false. PENDING/FAILED/unknown statuses still return false silently (only a present-but-not-confirmed status is a normal not-yet-refunded outcome). Adds a test asserting the boundary error log; the existing fails-safely tests still hold (the throw is contained to the same false outcome). The pending-result union Codex also suggested (propagate a pending refund id instead of false) is the staged-checkout/callback work this PR was told not to introduce and is recorded in TODO.md against #1853/#1905.

Refreshed the surviving equivalent-mutant line numbers in equivalent-mutants.txt (the helper removal shifted result ?? false and locations ?? []).
stefan-burke added a commit that referenced this pull request Jul 25, 2026
Codex follow-up (thread 3): the boundary throw lived inside withClient, which catches non-PaymentUserError exceptions and returns null, so it was architecturally the same as a logged false — an indirect throw-and-catch rather than a real loud failure. Replaced it with an explicit logError + return false, matching the missing-amount case just above it: the malformed response is still diagnosed at the boundary under E_SQUARE_REFUND and still fails safely to false (never reports a refund that was never confirmed), now without the swallowed-throw indirection. Propagating the throw outside withClient (a loud/errored failure) would change the contract from fail-safely-contained to fail-loudly, which is the per-path staged-checkout/callback resolution this PR was told not to introduce and is recorded in TODO.md against #1853 and #1905. Exhaustive mutation on square.ts is now 100% (286 killed, 3 genuine equivalents suppressed).
@stefan-burke
stefan-burke deleted the split/staged-checkout-runtime branch August 24, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant