A checkout flow that reveals the shipping cost only on the post-payment confirmation page — after the card is charged — violates the FTC Mail, Internet, or Telephone Order Merchandise rule and EU Consumer Rights Directive 2011/83/EU Article 6, both of which require pre-payment disclosure of total charges including shipping. Customers who reach the payment button without seeing a shipping line item are authorizing a charge they cannot verify. When the shipping amount is high enough to feel like a surprise, they dispute the charge rather than initiate a return — a chargeback is more automatic than a refund request. Even showing "Shipping: TBD" at the payment step fails the test: a placeholder is not disclosure.
High because withholding shipping cost until after payment is a consumer disclosure violation under FTC rules and EU law, and surprise shipping charges at confirmation are a leading driver of post-payment chargebacks.
Display the calculated shipping cost as a concrete line item in components/CheckoutSummary.tsx at least one step before the payment/submit button:
// components/CheckoutSummary.tsx
export function CheckoutSummary({ cart, shipping, tax, address }) {
return (
<div className="checkout-summary">
<div className="flex justify-between">
<span>Subtotal</span>
<span>${(cart.subtotal / 100).toFixed(2)}</span>
</div>
{address && shipping !== null && (
<div className="flex justify-between">
<span>Shipping to {address.state}</span>
<span>${(shipping / 100).toFixed(2)}</span>
</div>
)}
{tax > 0 && (
<div className="flex justify-between">
<span>Tax</span>
<span>${(tax / 100).toFixed(2)}</span>
</div>
)}
<div className="border-t font-bold flex justify-between">
<span>Total</span>
<span>${((cart.subtotal + (shipping ?? 0) + tax) / 100).toFixed(2)}</span>
</div>
</div>
)
}
Never render "Calculated at checkout" or null for shipping at the payment step — trigger the rate calculation as soon as a valid address is entered.
ID: ecommerce-shipping-tax.checkout-integration.shipping-preview
Severity: high
What to look for: List all checkout steps in sequence (address, shipping method, payment, review). For each step, count the number of financial line items visible (subtotal, shipping, tax, total). Classify at which step the shipping cost first becomes visible to the customer.
Pass criteria: Shipping cost is displayed as a separate line item in the checkout summary at least 1 step before the final payment/submit button. The cost must be a dynamically calculated value based on the selected address and shipping method, not a "calculated at next step" placeholder.
Fail criteria: Shipping cost is not visible until after payment processing, or the checkout summary shows "Shipping: TBD" or similar placeholder at the payment step. Must not pass when shipping is shown only in the post-payment confirmation.
Skip (N/A) when: Digital-only products with no shipping costs in the checkout flow.
Detail on fail: "Shipping cost first visible at step 4 (confirmation) — payment button is at step 3. Customer pays without seeing shipping." or "Checkout shows 'Shipping: Calculated at checkout' even on the payment step."
Cross-reference: Related to ecommerce-shipping-tax.shipping-calc.rate-calculation-accuracy (displayed cost must use accurate calculation) and ecommerce-shipping-tax.checkout-integration.tax-consistency (shipping and tax should both be visible).
Remediation: Add shipping preview to the checkout summary in components/CheckoutSummary.tsx:
// components/CheckoutSummary.tsx
export function CheckoutSummary({ cart, shipping, tax, address }) {
return (
<div className="checkout-summary">
<h3>Order Summary</h3>
<div className="flex justify-between">Subtotal: ${(cart.subtotal / 100).toFixed(2)}</div>
{address && <div className="flex justify-between">Shipping to {address.state}: ${(shipping / 100).toFixed(2)}</div>}
{tax > 0 && <div className="flex justify-between">Tax: ${(tax / 100).toFixed(2)}</div>}
<div className="border-t font-bold">Total: ${((cart.subtotal + shipping + tax) / 100).toFixed(2)}</div>
</div>
)
}