DEV Community

chenwengui23
chenwengui23

Posted on

Your AI said the trial was free. The card was charged $79 on day 15.

#ai

A fitness studio software company — 11 people, roughly 3,800 accounts — found this one in an app-store review, which is the most expensive place to find anything. A gym owner had signed up for what the pricing page calls a 14-day free trial, no credit card required. On day 6 she asked the in-app assistant a simple question: "If I keep using this after the trial, what happens?"

The agent answered in four seconds:

"Your 14-day trial is completely free — no charges during the trial. You can explore everything risk-free!"

Every word was about the trial. None of it was about what happens after the trial, which is that the account auto-converts to a $79/month plan unless cancelled, and the card she'd added for a paid add-on on day 2 gets charged first. On day 15 the charge landed. She wrote a one-star review whose first sentence was "they told me it was free."

Then they pulled 90 days of logs. 1,930 conversations mentioned the trial. In 411 of them the agent made a promise about charges — "free," "no charge," "you won't be billed," "risk-free." In 96 of those 411 — 23% — the promise was false the moment the auto-conversion terms were included. The agent had never lied. It had answered a question about a window while a clock was running outside it.

The support lead's first instinct was to write a canned reply and move on. The second instinct, after finance weighed in, was to recognize what this actually was: the same class of bug as confirming a cancellation that hasn't happened — reporting one system's truth while a second system holds the obligation. Trial conversations describe the trial. Billing conversations carry the conversion. The agent had never read the second one.

A trial is not a state. It's a countdown with a trigger.

The word "trial" sounds like a product feature. In the billing system it's a transition waiting to fire, and every deployment I've audited has at least four versions of it that all sound identical from inside a chat:

Trial variant What's true What "it's free" hides
no_card_required Trial ends, access stops, nothing billed Nothing — genuinely safe
card_on_file Auto-converts at trial end unless cancelled The charge on day 15
freemium_capped Free tier forever, paid features gate off Which features go dark, and when
sample_with_return Physical sample "free," return shipping or restocking fee is not The $12 label or 15% fee

Only the first is safe to celebrate. The other three all get described as "free" by agents, because "free" is the sentence that ends the conversation fastest — and the gap between what the agent said and what the card experienced is exactly the shape of a chargeback.

Here's what makes this worse than an ordinary misunderstanding: the customer asked. In 61 of the 96 false promises, the human had explicitly asked some version of "will I be charged?" That's not a model misreading an ambiguous message. That's a direct question answered with the part of the truth that closes the ticket.

And it hides asymmetrically, the way the cancellation bug did. The customer who asked, got "free," and then got charged doesn't open a support ticket — they leave a review, dispute the charge, or quietly churn. Support's dashboard shows 411 smooth trial conversations. Nobody joins that table to the billing one until the reviews say "scam."

Step 1: Make the trial a record with a clock

You can't gate on terms the agent can't see. Give the trial the same treatment as a subscription lifecycle — a queryable record with the fields that decide what may be said:

{
  "trial_record": {
    "account_id": "acct_55203",
    "trial_type": "no_card_required | card_on_file | freemium_capped | sample_with_return",
    "started_at": "2026-10-28T09:14:00Z",
    "ends_at": "2026-11-11T09:14:00Z",
    "card_on_file": true,
    "conversion_terms": {
      "plan_id": "pro_monthly",
      "price": 79,
      "currency": "USD",
      "charge_at": "ends_at",
      "requires_opt_in": false
    },
    "cancellation_deadline": "2026-11-11T09:14:00Z",
    "conversion_consent_event_id": null
  },
  "rules": {
    "say_free_only_when": "trial_type = 'no_card_required' AND card_on_file = false",
    "card_on_file_requires_disclosure": true,
    "disclosure_must_include": ["price", "charge_date", "cancel_path", "cancel_deadline"],
    "days_remaining_required_in_any_trial_answer": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Two fields carry the weight. card_on_file_requires_disclosure is the kill switch for the whole class of bug: if a card exists, the word "free" may not travel alone. It must arrive with the price it converts to, the date it converts, and the way out. That one rule would have prevented all 96 false promises at the fitness vendor, because every one of them was a card-on-file trial described in no-card language.

days_remaining_required_in_any_trial_answer exists because trials have a property most policies don't: the honest answer changes every day. "The trial is free" is true on day 1 and deceptive on day 12, when the only useful fact is "it converts to $79 on Friday unless you cancel." Requiring the day count forces the answer to be anchored in time, which is where trial truth lives.

conversion_consent_event_id is the audit hook: if the vendor later claims the customer opted in, there should be an event ID. Null means auto-conversion, which means the disclosure duty is at its highest.

Step 2: The disclosure gate

Before any sentence containing "free," "no charge," or "you won't be billed," the agent must resolve the trial record and route:

{
  "trial_disclosure_gate": {
    "trigger": "outbound_message_contains_cost_promise_about_trial",
    "detect": ["free", "no charge", "won't be billed", "risk-free", "no cost", "on the house"],
    "resolve": "lookup_trial_record_by_account_then_read_type_and_card",
    "branches": {
      "no_card_trial": "confirm_free_and_state_what_happens_when_trial_ends",
      "card_on_file_trial": "state_conversion_price_date_and_cancel_path_before_any_free_language",
      "freemium_capped": "name_which_features_gate_off_and_when",
      "sample_with_return": "state_return_shipping_or_restocking_terms_verbatim",
      "trial_already_converted": "state_converted_at_date_and_current_plan_never_say_trial",
      "no_trial_found": "route_to_billing_never_describe_terms_from_memory"
    },
    "forbidden": [
      "free_language_on_card_on_file_trial_without_conversion_disclosure",
      "answering_will_i_be_charged_with_only_the_trial_window",
      "describing_converted_account_as_still_in_trial",
      "saying_cancel_anytime_without_the_deadline_and_channel"
    ],
    "style_rule": "the_trial_window_is_not_the_whole_truth"
  }
}
Enter fullscreen mode Exit fullscreen mode

The card_on_file_trial branch is where the wording does all the work. "It's free!" feels helpful; the honest version feels longer and lands better: "Your trial runs through Nov 11 — 5 days left. On Nov 12 it converts to the Pro plan at $79/month, charged to the Visa ending 4412 you added on Oct 30. You can cancel anytime before Nov 11 from Settings → Billing, and I'll confirm it in writing if you do. Want me to send you the cancel link now?"

That's 74 words instead of 11. Notice what it does that the short version doesn't: it names the card, names the date, and offers the exit — which converts a billing ambush into a decision the customer is allowed to make.

Read trial_already_converted too: agents love saying "your free trial" to people whose trial ended three weeks ago. Once converted, the trial vocabulary is forbidden — the account has a plan, a price, and an invoice history, and describing it as a trial makes every later answer wrong.

And no_trial_found: a lookup against the wrong email returning nothing is not license to recite the pricing page from memory.

Step 3: Replace the four phrases that generate reviews

Ban the behavior and the model routes around it. Give it replacements:

Never say Say instead
"Your trial is completely free!" "Your trial is free through Nov 11. On Nov 12 it converts to Pro at $79/month on your Visa ending 4412."
"You won't be charged during the trial." "Correct — no charge during the trial. The first charge is Nov 12, $79, unless you cancel before Nov 11."
"Cancel anytime!" "You can cancel before Nov 11 in Settings → Billing — after Nov 12 the plan is active and standard billing applies."
"It's risk-free, explore everything!" "Everything's unlocked through Nov 11. After that, reporting and integrations move to the paid plan at $79/month."

The fourth one matters more than it looks. Freemium conversations never produce a charge, so they never produce a chargeback — they produce something quieter: a customer who built a workflow on a feature that gated off, told their peer group the product "took it away." "Free" was true; the sentence was still the most expensive one in the log.

Step 4: Audit for promised-free-then-charged

The bug is invisible from support's side, so audit it from the join between promises and charges:

-- cost promises in trial conversations vs what the card actually experienced
SELECT t.conversation_id,
       t.promised_at,
       t.promise_text,
       b.charged_at,
       b.amount,
       t.trial_type,
       t.card_on_file
FROM trial_cost_promises t
JOIN billing_events b USING (account_id)
WHERE t.promised_at > now() - interval '7 days'
  AND b.charged_at BETWEEN t.promised_at AND t.promised_at + interval '30 days'
  AND t.card_on_file = true
  AND t.disclosure_given = false;
Enter fullscreen mode Exit fullscreen mode

Every row is a review that hasn't been written yet — a promise of "free" followed by a charge with no disclosure in between, while the customer can still be told the truth by a human instead of a one-star review.

# Check Method Healthy threshold
1 "Free" promises on card-on-file trials without disclosure Query above 0
2 "Will I be charged?" answered without conversion terms Transcript pattern match, 5 sampled/day 0
3 Converted accounts still addressed as "trial" Trial vocabulary vs plan state join 0
4 Trial-end charges with no disclosure in preceding 14 days Billing events vs disclosure log 0
5 Reviews/mentions citing "told me it was free" Review + social monitor, weekly Trend down

Check 4 is the widest net: it catches the customers who never asked, were never told, and never complained — the majority, in every deployment where this has been measured.

The numbers, for the internal pitch

  • 23% of trial cost promises were false under auto-conversion terms (96 of 411) at the fitness vendor. Across four trial-heavy deployments I've audited, the range was 17–29%.
  • 61 of the 96 followed an explicit "will I be charged?" — the highest-severity slice, because intent to deceive can't even be argued. It was a direct question answered narrowly.
  • The fix was two fields, six branches, one SQL join. No new service. card_on_file_requires_disclosure alone would have prevented all 96.
  • After the gate: 60 days, zero undisclosed charges following a cost promise. Median trial-conversation length went up 40 seconds. Trial-to-paid conversion went down 2.1 points — because some customers, given the truth and the exit, took the exit. They were the customers writing reviews and filing disputes.
  • Review sentiment recovered. "Told me it was free" citations went from 4–6 per month to zero in the 60 days after shipping.

The vendor shipped the gate on a Tuesday. The gym owner got a call from the founder the same week — the $79 refunded, the review addressed honestly. She updated it to four stars and, per the founder, is still a customer. That's the good ending. The ordinary ending is the 96 other accounts, most of whom never said anything, and whose renewal decisions were made in the gap between what the agent promised and what the card learned.


If you want the working versions — the trial record schema, the disclosure gate config, the four-phrase replacement table, and the promise-vs-charge audit query I run on every deployment with a trial — it's here: https://whop.com/agentfast-04b6/

Top comments (0)