Recurring Charge Readback

Reads back what a subscribe button really commits to: today's charge, first real charge, price step-ups and the cancel-by time.

StopsFree trial quietly became a paid plan

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/recurring-charge-readback.json

The files land in your project. They're yours to edit.

Usage

recurring-charge-readback-usage.tsx
import { RecurringChargeReadback, type RecurringChargeReadbackProps } from "@/components/ui/recurring-charge-readback"

<RecurringChargeReadback id={id} {...current.props} buttonLabel={current.button} now="2026-10-14T10:00:00+05:30" timeZone="Asia/Kolkata" />

What it catches

  • Amount charged today, including small card checks
  • Date of the first real charge after a trial
  • Intro price ending and the step-up amount
  • Cancel-by time, with time zone, using cancelNoticeHours
  • Button wording ("free", a monthly price on yearly billing) that contradicts the charge
  • Optional India e-mandate notes (24-hour pre-debit alert, approval above a limit)

Props

RecurringChargeReadback props
PropTypeDefault
amount* (required)

The regular price per period.

number—
interval

Billing period unit, e.g. "month" or "year".

ChargeInterval"month"
intervalCount

Periods per renewal, e.g. 3 with `interval="month"` for quarterly.

number1
trialEndsAt

When a trial ends and the first real charge lands.

Date | string—
trialAmount

Charged today for the trial, e.g. a ₹2 card check. Defaults to 0.

number0
intro

An intro price for the first paid periods: `{ amount: 99, periods: 3 }`.

{ amount: number; periods: number }—
cancelNoticeHours

How many hours before a renewal a cancellation must land.

number24
buttonLabel

The text on the subscribe button next to this, so a mismatch ("Start free", "₹399/mo") is caught.

string—
mandateNotes

Show India's e-mandate notes (24-hour pre-debit alert, OTP above the limit). Defaults on for INR.

booleancurrency === "INR"
mandateLimit

Per-charge amount above which Indian banks need approval for each renewal (RBI e-mandate).

number15000
now

The moment of subscribing. Pass it for deterministic rendering; otherwise read after mount.

Date | string—
currency

ISO 4217 currency code. INR turns on the e-mandate notes by default.

string"INR"
locale

Locale for money and date formatting.

string"en-IN"
timeZone

Time zone for every date and time shown. Defaults to the viewer's.

string—
billingTimeZone

Zone whose calendar the biller renews on. Defaults to `timeZone`, so a plan bought at 00:30 IST on the 1st renews on the 1st, not the 31st. Pass "UTC" for billers that count in UTC.

string—
messages

Reword or translate any sentence.

Partial<typeof RECURRING_CHARGE_MESSAGES>—

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Renders as a polite live region; give your subscribe button aria-describedby pointing at its id
  • Warnings use an icon plus text; icons are aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Charge the amounts and dates your billing system computes, not the client's; send the required pre-debit notices from the server or issuer.

  • Only reads back the plan you describe; it does not charge or schedule anything
  • Month arithmetic depends on billingTimeZone; pass the zone your biller renews in
  • The e-mandate notes describe RBI rules as of the verified date; they do not apply outside India

Rules

Rules last verified:

Informational. Confirmed: issuer pre-transaction notice at least 24 hours before debit; recurring debits up to ₹15,000 without AFA (₹1,00,000 for insurance premiums, mutual fund subscriptions and credit card bills; the default mandateLimit covers only the general case). Check the official source. Copied components don't update themselves, so re-check after rule changes (see updating a component).

What it stops

Free trial quietly became a paid plan

Also known as

free trial · subscription · recurring billing · cancel anytime · intro price · paywall · pricing · subscribe button

Dependencies

Pairs well with