Summary
The fan-out modal's own Payment selects relabel a recommendation's payment without re-pricing it, exactly the defect #1903 fixes in the single-purchase modal. Changing a bucket's Payment, or a per-recommendation Payment inside a bucket, writes the new payment onto the row while the loaded upfront_cost, monthly_cost and savings stay at the previous payment's values. The submit path posts those unchanged numbers.
This matters more than a display bug because the backend does not re-price. validateAndTotalRecommendations sums the submitted costs into the execution row and the approval email, and recTotalCommitment multiplies the submitted monthly cost by the submitted term for the MaxPurchaseAmount cap. The recorded, emailed and cap-checked figures therefore describe a payment option the user did not buy, while the provider charges for the one they did.
Every multi-bucket or partially incompatible selection reaches this path, so it is not a rare corner.
Location
frontend/src/recommendations.ts:4468 (bucket-level Payment select sets b.payment)
frontend/src/recommendations.ts:4559 (per-recommendation Payment select writes perRecPayments)
- consumed unchanged at
frontend/src/app.ts:534
Failure scenario
A bulk selection produces an RDS bucket seeded all-upfront at $1,000 upfront. The user changes that bucket's Payment to partial-upfront. The label updates, the row still carries upfront_cost: 1000 from the all-upfront variant, and the POST submits partial-upfront paired with the all-upfront price. Test T9 in frontend/src/__tests__/purchase-modal-submit.test.ts currently encodes this un-repriced submission as the expected behaviour, and should be inverted when this is fixed.
Suggested fix
Apply the same treatment #1903 used: swap in the loaded sibling variant for the chosen payment, scaled to the bucket's capacity, and build the select's options from priced variants only so an unpriced combination cannot be chosen. The helpers added in that PR (pricedCellVariant, applyVariantChange) are reusable here.
Related
Follow-up from #1903 and #1904, found during adversarial review of their fix. The single-purchase modal is repaired; this is the same defect class in the fan-out path, which that PR deliberately left in scope for a separate change.
A second, cosmetic item belongs with it: recommendations.ts:4453 builds the bucket Payment options from paymentOptionsFor, so an incompatible seed renders as the first option even though the bucket is marked as skipped. Harmless now that the skip label, the totals and the submit filter agree, but it should go away with the re-pricing fix.
Summary
The fan-out modal's own Payment selects relabel a recommendation's payment without re-pricing it, exactly the defect #1903 fixes in the single-purchase modal. Changing a bucket's Payment, or a per-recommendation Payment inside a bucket, writes the new payment onto the row while the loaded
upfront_cost,monthly_costandsavingsstay at the previous payment's values. The submit path posts those unchanged numbers.This matters more than a display bug because the backend does not re-price.
validateAndTotalRecommendationssums the submitted costs into the execution row and the approval email, andrecTotalCommitmentmultiplies the submitted monthly cost by the submitted term for theMaxPurchaseAmountcap. The recorded, emailed and cap-checked figures therefore describe a payment option the user did not buy, while the provider charges for the one they did.Every multi-bucket or partially incompatible selection reaches this path, so it is not a rare corner.
Location
frontend/src/recommendations.ts:4468(bucket-level Payment select setsb.payment)frontend/src/recommendations.ts:4559(per-recommendation Payment select writesperRecPayments)frontend/src/app.ts:534Failure scenario
A bulk selection produces an RDS bucket seeded all-upfront at $1,000 upfront. The user changes that bucket's Payment to partial-upfront. The label updates, the row still carries
upfront_cost: 1000from the all-upfront variant, and the POST submits partial-upfront paired with the all-upfront price. TestT9infrontend/src/__tests__/purchase-modal-submit.test.tscurrently encodes this un-repriced submission as the expected behaviour, and should be inverted when this is fixed.Suggested fix
Apply the same treatment #1903 used: swap in the loaded sibling variant for the chosen payment, scaled to the bucket's capacity, and build the select's options from priced variants only so an unpriced combination cannot be chosen. The helpers added in that PR (
pricedCellVariant,applyVariantChange) are reusable here.Related
Follow-up from #1903 and #1904, found during adversarial review of their fix. The single-purchase modal is repaired; this is the same defect class in the fan-out path, which that PR deliberately left in scope for a separate change.
A second, cosmetic item belongs with it:
recommendations.ts:4453builds the bucket Payment options frompaymentOptionsFor, so an incompatible seed renders as the first option even though the bucket is marked as skipped. Harmless now that the skip label, the totals and the submit filter agree, but it should go away with the re-pricing fix.