# Card payments

Card-on-file subscriptions are billed by LedgerBee's own billing engine. It
decides when each cycle bills, computes the amount, charges the stored card with
an off-session payment, and runs its own retry and dunning schedule. The payment
provider only moves the money — it does not run the subscription.

LedgerBee sends every buyer-facing email: the enrollment
confirmation, the advance pre-charge notice, the paid receipt, the failed-charge
and dunning notices, the card-expiry and card-update notices, and the
cancellation confirmation. Each one is transactional and fires only when its
trigger applies; the timing of each follows a card-scheme or Danish/EU
consumer-law rule, listed per email below. The payment provider sends none of
these, so any provider-side customer-email setting should stay off to avoid
sending the buyer a duplicate.

Card billing rides on the subscription system. For the underlying model — the
stable chain id, versions, timeline, and lifecycle — see
[Subscriptions](/guides/subscriptions/overview) and
[Data model](/guides/subscriptions/data-model). A card is stored during
[Embedded Checkout](/guides/embedded-checkout/overview) or when an operator
[assigns a subscription](/guides/subscriptions/assign) with a saved card. Full
request and response schemas live in the [API Reference](/api).

## The two rails

A card-on-file subscription runs on one of two payment rails. The buyer never
picks one; the tenant's connected provider stores the saved card as either a
Stripe PaymentMethod or an OnPay/Nets-Dankort subscription token. The buyer
emails, the daily card-expiry scan, the dunning and grace handling, and the
receipt content are identical on both. Cycle charges are submitted as
merchant-initiated recurring transactions on both, so the buyer's issuer sees a
subscription payment rather than a one-off. The rails differ on three points an
integrator can observe:

| Axis | Stripe Connect | OnPay / Nets-Dankort |
|---|---|---|
| Statement descriptor | Set per charge (`statement_descriptor_suffix`, default `SUBSCRIPTION`). The full descriptor — account prefix plus suffix — is capped at 22 characters. | Derived by Nets and the acquirer from the merchant agreement; not set per charge. |
| Card-updater detection | Push. The provider sends an event when the card network silently refreshes a stored card; LedgerBee updates the card and emails the buyer in near-real-time. | Pull. LedgerBee re-checks the stored card with the provider after each cleared charge and before the daily expiry scan. |
| Expiry handling | The stored expiry stays current from the updater events, so the daily expiry scan reads it directly. | The stored expiry can lag a silent network renewal, so the daily scan re-checks with the provider before acting; if that check fails, it takes no expiry action that run. |

### Amex is blocked

American Express cannot be used for subscription card-on-file on either rail. A
buyer who tries to save an Amex card gets a blocked-brand error at checkout and
at card save; direct them to a different brand. Amex's recurring-billing rules
require the cardholder to re-consent to terms changes, which an automated notice
cannot satisfy. A stored card that the network later switches to an Amex number
is refused at the next charge rather than billed.

## Buyer emails

LedgerBee sends these automated emails to the buyer over the life of a
card-on-file subscription. All are transactional — amount, date, subscription
reference, a manage/cancel link, and the tenant's logo and name. Tenants cannot
add promotional content.

| Email | When it sends | Why (real-world rule) | Who receives it |
|---|---|---|---|
| Enrollment confirmation | Once, promptly after a card is stored for automatic billing (including zero-charge trial signups). Not tied to a charge. | Visa Europe recurring-agreement confirmation (within 2 business days); Dankort §29.2 subsequent written confirmation; consumer durable-medium contract confirmation. | All card-on-file buyers. A trial-disclosure block appears only for trial enrollments. |
| Pre-charge notice | Before an upcoming charge, on the effective charge date (below). One email covers up to four causes: trial conversion (first post-trial charge), infrequent cadence (longer than 6 months), amount change (the charge differs from the steady recurring amount), and terms change (a queued agreement change lands at or before the charge). | Visa Core Rules 7-day agreement-change and trial-conversion advance notice; Mastercard infrequent-transaction (over-6-month) advance reminder (7–30 day window); Danish consumer 8-day variable-amount floor; Visa variable-amount and metered disclosure. | All card-on-file buyers. The amount-change floor is 8 days for B2C (private person), 7 days for B2B. The trial-conversion cause applies only to trial subscriptions. |
| Paid receipt | At invoice dispatch, immediately after a charge settles. The receipt content rides on the invoice email, with the PDF attached. | Visa Core Rules receipt requirements; Mastercard recurring-receipt rules; Dankort/Nets recurring-receipt rules; Danish consumer-law receipt obligation. The content carries a paid-and-prepayment marker, a recurring-payment marker, the covered service period, the masked card, a portal cancel link, and — on the first paid charge after a free trial — a trial-conversion restatement. | All card-on-file buyers whose settled charge maps to a single subscription. A consolidated multi-subscription invoice gets a plain paid receipt instead. |
| Failed-charge notice — first decline | Immediately on the first decline of a card charge. Opens the grace and dunning window. | Consumer payment-failure disclosure and the card-scheme expectation that the buyer is told a recurring charge was declined and given a way to fix it. | All card-on-file buyers. The issuer decline reason is included only for trial subscriptions (a declined trial-conversion charge must state why); it is omitted otherwise. |
| Failed-charge notice — final | Once, when automatic retries are exhausted (after the grace floor — see [Failed charges](#failed-charges--the-grace-window)). Not re-sent on later manual retries. | Consumer final-disclosure and the card-scheme expectation that the buyer is told retries have stopped and the amount is now collected the normal way. | All card-on-file buyers whose retries are exhausted. Decline reason still trial-only; no retry is promised. |
| Card auto-update notice | Near-real-time when the card network's account-updater (or a manual edit at the provider) refreshes a stored card's type, mask, or expiry behind a live subscription. The buyer took no action; billing continues. | Dankort §30 — the cardholder must be told when a stored card is silently refreshed. | Buyers whose stored card backs at least one live subscription. One refresh sends one email even when the card backs several subscriptions; the subscription is named only when exactly one rides the card. |
| Cancellation confirmation | Immediately when a card-on-file subscription is cancelled. | Mastercard cancellation-acknowledgement (confirm within 7 days). | All card-on-file buyers. Copy differs for immediate versus end-of-cycle cancellation (access-until date). It confirms the card won't be charged again for this subscription; it does not claim the card was deleted. |

Invoice-rail (non-card) subscriptions don't get the enrollment, pre-charge,
failed-charge, or cancellation emails — those fire only for automatic card
billing.

### The effective charge date anchor

A pre-charge notice is timed off the effective charge date: the date the charge
actually fires, which is `nextBillingDate` minus the subscription's send-offset
days, not the calendar billing date. The send floor counts back from that date.
A notice that covers more than one cause uses the latest floor among them — 7
days for trial-conversion and terms-change, a 7-to-30-day window for infrequent
cadence, and 7 days (B2B) or 8 days (B2C) for an amount change. When the floor
can't be met, such as a trial shorter than the floor, the notice sends as early
as possible and a compliance-exception record is written (see
[Evidence & audit trail](#evidence--audit-trail)).

## Failed charges & the grace window

A failed charge does not immediately end a subscription. LedgerBee runs a
rail-agnostic dunning sequence with a 7-day grace floor before any adverse
action.

1. **First decline** — the buyer gets the first-decline email with a portal link
   to update their card, and the subscription moves to `AWAITING_PAYMENT`.
2. **Automatic retries** — the charge retries automatically on a backoff schedule
   across the grace window. Only genuine declines count toward the retry cap;
   transient errors (5xx, timeouts) are retried without counting against it.
3. **Grace floor** — no adverse action (the final notice, finalizing the invoice
   as collectible, stopping retries) happens before a 7-day floor measured from
   the first failed attempt. Exhaustion requires both the retry cap and the
   elapsed 7 days, so a buyer who exhausts retries early — repeatedly hitting
   "pay now" in the portal — still gets the full grace window.
4. **Exhaustion** — once both hold, the subscription error topic fires once with
   `errorType: dunning_exhausted`, the buyer gets the final notice, the invoice is
   finalized as collectible (booked, numbered, and entered into the normal
   accounts-receivable flow of aging, payment reminders, and manual payment), and
   automatic retries stop. The subscription stays `AWAITING_PAYMENT`.

`AWAITING_PAYMENT` is a status, not an access cut. For a subscription that has been
paid before, LedgerBee never cancels it over a failed charge — it sits in
`AWAITING_PAYMENT`, with the unpaid invoice in accounts-receivable, until the buyer
pays or you act. The one case LedgerBee does cancel is a pay-first signup whose
first charge never clears: that checkout never became a live subscription, so a
cleanup job cancels the stranded record after a grace period. Nothing was granted,
so no access is revoked.

Entitlement is yours to control. The subscription error topic gives you two
distinct signals to choose between: `errorType: charge_failed` fires on every
decline (act on it to gate access the moment a charge fails), and
`errorType: dunning_exhausted` fires once when retries are exhausted (act on it to
keep the buyer in service through the grace window and gate only if payment never
recovers). A cleared retry fires the payment-succeeded signal — the cue to
restore. See [Webhooks](#webhooks-to-mirror-payments-in-your-own-system).

## Retrying a failed charge yourself

The outstanding card invoice can be recharged on demand from three surfaces,
all against the same saved card:

- **The buyer**, from the portal's "Pay now" on the failed subscription.
- **An operator**, from the subscription's billing documents in the LedgerBee
  app.
- **You, over the API**:

```bash
curl -X POST "https://api.ledgerbee.com/api/v1/subscriptions/SUBSCRIPTION_ID/retry-payment" \
  -H "x-api-key: YOUR_API_KEY"
```

The subscription id is the stable id; there is no request body — the retry
locates the outstanding unpaid invoice itself. A manual retry ignores the
automatic ladder's backoff (it charges immediately) and works after dunning is
exhausted, re-opening the exhausted attempt. A cleared retry settles the
invoice, resumes the subscription, and fires the payment-succeeded signal. A
declined retry returns `PORTAL_RETRY_PAYMENT_DECLINED`; genuine declines still
count toward the retry cap, and the grace floor still applies before exhaustion.
`PORTAL_RETRY_PAYMENT_NOT_AVAILABLE` means there is nothing to retry (the
subscription is not awaiting payment, holds no unpaid card invoice, or has
automatic billing disabled), and `PORTAL_RETRY_PAYMENT_IN_PROGRESS` means a
concurrent charge holds the claim or the invoice was just paid — re-read the
subscription instead of retrying again. All three carry the `PORTAL_` prefix on
the wire because this endpoint shares its implementation with the buyer portal's
own retry.

## Recovering a subscription after a failed charge

Two events resume a subscription parked in `AWAITING_PAYMENT` and re-arm
automatic card billing, so the next cycle charges the saved card again. Both
flip the subscription back to active and clear the failed-charge state; they
differ only in whether the outstanding invoice is settled.

- **The invoice is paid.** When the outstanding card invoice is fully settled
  through any channel — a [retried charge](#retrying-a-failed-charge-yourself),
  a manual payment registered against it, a bank-reconciliation match, or an
  operator marking it paid — the subscription resumes automatically. This is
  the same recovery as a cleared card charge, so it fires the payment-succeeded
  signal: your cue to restore access.
- **An operator reactivates it.** An operator can resume the subscription
  directly from its detail page regardless of whether the invoice is paid — for a
  buyer who paid out of band, or to keep service running while collection
  continues. The outstanding invoice stays collectible in accounts-receivable;
  reactivation resumes billing, it does not settle the debt. This fires the
  subscription-updated signal (a status change), distinct from the payment-driven
  recovery above, so an integrator that revoked access on `dunning_exhausted` can
  restore it.

Recovery always re-arms billing from the next cycle: the spent charge attempt
belonged to the old invoice, and the next cycle cuts a fresh invoice with its own
charge against the saved card. An operator reactivation does not retry or alter
the old invoice — it stays collectible until separately paid or written off.

## Evidence & audit trail

Every notice send is recorded as audit evidence — the record set that answers a
card dispute and evidences the consumer-law obligations above.

- Each notice is sent at most once per billing cycle. The buyer never receives a
  duplicate, even when a send is retried after a transient failure.
- Each record is immutable and reproduces the exact message that was sent without
  storing the email body. Records are kept for the longer of the subscription's
  life or two years from the last charge — the longest card-scheme dispute
  window — and survive subscription or customer deletion.

Operators can see every send, suppression, consent record, and compliance
exception in the LedgerBee app, on the customer and subscription activity
timelines filtered to the Notices category.

## Webhooks to mirror payments in your own system

Subscribe to subscription webhooks to mirror these payments and gate access in
your own system. See [Webhooks](/guides/webhooks) for endpoint setup, the
signed-envelope format, signature verification, and retries, and
[Subscription webhooks](/guides/subscriptions/webhooks) for the full topic list
and error sub-types. The card-relevant signals:

- **A charge cleared** is the primary positive signal: grant or confirm access,
  mark the cycle paid, and mirror the receipt into your own records. The payload
  carries the invoice number, amount, and currency.
- **A charge failed** fires on every decline while retries run, on the
  subscription error topic with `errorType: charge_failed` (distinct from the
  undeliverable-recipient value). Act on it if you gate access the moment a charge
  fails. The subscription moves to `AWAITING_PAYMENT`, and the raw provider decline
  reason is not on the wire — the message field is a fixed human-readable string.
- **Dunning exhausted** fires once, on the subscription error topic with
  `errorType: dunning_exhausted`, when automatic retries are spent after the 7-day
  grace window. Act on it if you keep a buyer in service through dunning and revoke
  only when payment never recovers. After it, no further automatic charges run and
  the invoice is collectible through accounts-receivable.
- **A subscription recovered** after dunning is your cue to restore access you
  revoked on `dunning_exhausted`. Settling the invoice fires the payment-succeeded
  signal; an operator reactivation (invoice left unpaid) fires the
  subscription-updated signal with a status change. Either way the subscription is
  active again and the next cycle charges the saved card. See
  [Recovering a subscription after a failed charge](#recovering-a-subscription-after-a-failed-charge).
- **An invoice was produced** (the billed topic) fires once per child
  subscription when a billing run cuts its invoice, whether or not a charge
  follows. With pay-first card flows the charge follows immediately, so a billed
  event is followed by a charge-cleared or charge-failed signal.
- **The subscription ended** (the churned topic) is the definitive
  revoke-entitlement signal: stop expecting charges and tear down access. A
  scheduled cancellation emits a heads-up first (the cancellation-scheduled
  topic); the subscription keeps charging until the effective date, then churned
  fires.
- **A trial converted** (the trial-ended topic) marks the move from free trial to
  paid billing — the first real card charge follows, with the receipt's
  trial-conversion restatement.
- **Paused and resumed** suspend and restart charges. Grant entitlement on the
  started topic, not on assignment, which can be future-dated.

Reconcile every event on the stable `subscriptionId` or your own
`partnerReferenceId` (the `clientReferenceId` you pass at
[checkout binding](/guides/embedded-checkout/customer-binding)), never on the
diagnostics-only `versionId`, which changes on every structural change (see
[Data model](/guides/subscriptions/data-model#versionid-is-for-diagnostics-only)).

## Errors & failure states

| What you observe | What it means | What to do |
|---|---|---|
| Subscription error webhook with `errorType: charge_failed`; subscription is `AWAITING_PAYMENT` | A card charge (first or recurring) was declined. Retries and the grace window are running. | Choose your entitlement policy: gate access now, or keep the buyer in service while retries run. The buyer already got the first-decline email; a cleared retry fires the payment-succeeded signal. |
| Subscription error webhook with `errorType: dunning_exhausted`; final-notice email sent; invoice now collectible in accounts-receivable | Retries are exhausted — the retry cap and the 7-day grace floor have both elapsed, and automatic retries have stopped. | The terminal signal for automatic collection only: a [manual retry](#retrying-a-failed-charge-yourself) (buyer, operator, or API) can still recharge the card. Revoke access now if you kept the buyer in service through dunning. The amount is collected through accounts-receivable, and LedgerBee leaves the subscription in `AWAITING_PAYMENT` (it doesn't cancel an established subscription over a failed charge). Access can be restored later: settling the invoice or an operator reactivation resumes the subscription and re-arms next-cycle billing (see [Recovering a subscription](#recovering-a-subscription-after-a-failed-charge)). |
| Card-expired partner webhook; the card becomes charge-ineligible; buyer got a "card expired" email | The daily expiry scan flipped a stored card to expired (on OnPay, only after re-checking with the provider). | Prompt the buyer to add a new card via the portal before the next charge. |
| Card auto-update notice sent to the buyer; nothing required from you | The card network silently refreshed the stored card (type, mask, expiry). Billing continues uninterrupted. | Nothing — informational. The subscription keeps charging on the refreshed card. |
| Subscription error webhook with the undeliverable-recipient `errorType` | A document recipient for the subscription is undeliverable (for example the email hard-bounced); the document was not delivered. | Correct the recipient's contact details. This is a delivery problem, not a charge problem. |
| Blocked-card-brand error on checkout or card save | The buyer tried to save an American Express card for subscription card-on-file, which is blocked on both rails. | Ask the buyer to use a different card brand. |
