Displaying an annual plan as '$24/month' without disclosing the $290 annual charge is a documented deceptive pattern under FTC Negative Option Rule (2025): the consumer commits to a $290 lump-sum charge while believing they authorized a $24 monthly payment. The FTC's Dot Com Disclosures guidance (2013) classifies missing-period qualifiers as material omissions. California ARL reinforces this by requiring the 'initial subscription fee' be clearly stated. A missing billing period label on a $29 price is the lowest-friction compliance gap in this category — one additional span element fixes it.
Low because the omission is typically isolated to label text and does not itself trigger the charge, but it feeds the misrepresentation chain that leads to chargebacks when the period is misunderstood.
Standardize every price display component to render billing period and, for annual plans, the actual annual charge. In components/PriceDisplay.tsx:
// Annual plan shown as monthly equivalent — must also show annual total
return (
<div className="price-display">
<div>
<span className="price">${monthlyEquivalent}</span>
<span className="period">/ month</span>
</div>
<p className="annual-note text-sm text-gray-600">
Billed as ${annualPrice}/year
</p>
</div>
)
Run a search across src/ for bare price strings (e.g., \$29) not followed by /month or /year to catch any component that bypasses the shared display component. Report the count of pricing surfaces audited.
ID: subscription-compliance.pre-purchase.price-per-period
Severity: low
What to look for: Look at every pricing display in the application: the pricing page, upgrade modals, plan comparison tables, and checkout confirmation pages. For each price display, check whether the billing period is explicitly labeled adjacent to the price. Common patterns that pass: "$29/month," "$290/year," "$29 per month," "billed monthly at $29." Common patterns that fail: "$29" with no period, "from $29" without clarification, annual price displayed as a monthly equivalent ("$24/month" when billed as $290/year lump sum) without also showing the annual total. For annual plans shown as monthly equivalents, check whether the actual annual charge amount is also clearly stated — showing "$24/month" without "$290 billed annually" is deceptive because users may expect a monthly charge. Count every pricing display in the application (pricing page, checkout, upgrade prompts, in-app purchase screens) and enumerate which show the price-per-period vs. which show only total price or are ambiguous.
Pass criteria: Every price display includes the billing period as a label directly adjacent to the price. Annual plans shown as monthly equivalents also display the actual annual charge amount. No price is shown without a period qualifier. Report even on pass: "X of Y pricing displays show clear price-per-period with billing frequency." Threshold: at least 1 prominent price-per-period display on every pricing surface.
Fail criteria: Price is shown without a billing period label. Annual plan is displayed only as a monthly equivalent without the actual annual charge. A plan page shows "$29" with the billing period in a separate section requiring scrolling. Do NOT pass if the price is only shown in small print or requires expanding a disclosure section — it must be prominent and immediately visible.
Skip (N/A) when: The application has no subscription or recurring billing.
Cross-reference: The free-trial-terms check verifies that trial-to-paid transitions show the price the consumer will pay after the trial ends.
Detail on fail: Example: "Annual plan shown as '$24/mo' with no disclosure that this is billed as $290/year. Users may expect a $24 monthly charge." or "Pricing card shows '$29' with no '/month' or billing period label anywhere on the card.".
Remediation: Standardize price display components to always include the period and, for annual plans, the actual charge:
// components/PriceDisplay.tsx
type PriceDisplayProps = {
monthlyPrice: number // in dollars
annualPrice: number // in dollars, actual annual charge
billingPeriod: 'monthly' | 'annual'
}
export function PriceDisplay({ monthlyPrice, annualPrice, billingPeriod }: PriceDisplayProps) {
if (billingPeriod === 'monthly') {
return (
<div className="price-display">
<span className="price">${monthlyPrice}</span>
<span className="period">/ month</span>
</div>
)
}
const monthlyEquivalent = (annualPrice / 12).toFixed(2)
return (
<div className="price-display">
<div>
<span className="price">${monthlyEquivalent}</span>
<span className="period">/ month</span>
</div>
{/* Always show actual annual charge when displaying annual as monthly */}
<p className="annual-note">
Billed as ${annualPrice}/year
</p>
</div>
)
}