Skip to main content

Selling works to consumers

This page is about labeling a purchase of a digital work, not about the mechanics behind it. The package hands you facts; the wording on your buttons and receipts is yours, because wording is jurisdiction-specific and this library is not the place to keep somebody's legal text.

Not legal advice

Everything here describes what the package does. Whether it is enough for the places you sell in is a question for your own advisers. Where a rule is genuinely unsettled, this page says so rather than picking a side for you.

"Buy" is a claim, and it may not be true

Access granted by this package can be taken away — by a refund, by a lost dispute, by a legal takedown. Several jurisdictions have started treating an unqualified Buy or Purchase button as misleading for digital goods that work that way. California's AB 2426, in force since 1 January 2025, is the one most people run into first: it forbids those words for digital goods with revocable access unless the buyer is told plainly what they are getting.

So the package gives you the fact and stops there:

$decision = app(ContentAccessReader::class)->accessFor($fan, $work);

if ($decision->isRevocable()) {
// Your copy, your jurisdiction. "Buy a license", "Get access", a footnote — the package has no opinion.
}

isRevocable() is a fact about the grant model, not a jurisdiction flag. It is answered the same way whether or not you have a consumer-rights profile configured, because it describes how this package works rather than what any country requires. Only the label is jurisdictional, and the label is yours.

It is not a right of withdrawal

Worth stating on its own, because the two look interchangeable and are not:

QuestionAnswer comes from
Can this access be taken away?AccessDecision::isRevocable()
May the buyer still withdraw from the purchase?the withdrawal type frozen on the grant

They come apart in both directions. A purchase whose right of withdrawal has validly ended is still revocable — a chargeback or a takedown does not care. And a purchase that is not revocable in practice may still be inside a withdrawal window. Wiring one field to answer both eventually shows a buyer "you can still cancel this" for a sale they cannot cancel, or hides one they can.

Who the buyer is buying from

The platform is the seller. For every content flow, the contracting party toward the buyer is the platform running this package — not the creator whose work it is. That has three consequences for the screens you build:

  • The consumer-rights notices are owed by the platform.
  • A library line (my downloads) shows the platform as the party the buyer dealt with.
  • A creator on a line is provenance — where the work came from — and must not be labeled "sold by". The arrangement protects the creator's anonymity toward the buyer: a screen that named them as the seller of a work would break that, and would not be true either.

The package will not hand you a "seller" field to render, and that is deliberate: there is only one answer and it is not a value the package needs to give you.

What the package does not do here

  • It writes no legal text. No withdrawal notice, no terms, no button copy.
  • It does not label per jurisdiction. It gives one neutral fact; which words that requires where is yours to decide.
  • It does not gate on labeling. Nothing refuses to sell because a button says the wrong thing — that would be a library making a legal judgement it is in no position to make.

Recording the declarations, and repeating them on the receipt

Before a work whose right of withdrawal ends on delivery is handed over, the buyer has to make two separate declarations: that provision may begin before the withdrawal period runs out, and that beginning it ends the right. The package does not render those checkboxes and writes none of their wording — but it keeps the answers, because without a record the right never validly ended and every refund inside the window stays a claim rather than a courtesy.

Record them at your checkout, against the reference you will see again:

use Pushery\Billing\Consumer\WithdrawalConsentLedger;
use Pushery\Billing\ValueObjects\WithdrawalConsent;

app(WithdrawalConsentLedger::class)->record($buyer, $checkoutReference, new WithdrawalConsent(
consentedToImmediateProvision: $request->boolean('consent_immediate'),
acknowledgedForfeiture: $request->boolean('acknowledge_forfeiture'),
noticeVersion: 'withdrawal-notice/2026-01', // WHICH wording you showed
givenAt: now()->toImmutable(),
));

The notice version is the point of the record. A sale is governed by the words shown at the time, so a later edit must not reinterpret it — which is also why a retried checkout keeps the first answer rather than overwriting it with today's wording.

Then the confirmation has to repeat both declarations on a durable medium. Your ReceiptNotifier is handed the payment reference, and the consent is keyed on the checkout reference — different strings, so there is a lookup for exactly this:

$consent = app(WithdrawalConsentLedger::class)->forPayment($owner, $paymentReference);

It answers null for a payment that bought no add-on, for an install with no consumer-rights profile, and for a purchase made before you collected the declarations. It never answers with somebody else's: the lookup is scoped to the owner as well as the payment.

Open questions, marked as open

Two things on this page are genuinely unsettled, and the package takes no position on either:

  • Per-state disclosure rules in the US. AB 2426 is the one with a date on it. Others are moving, and they do not all say the same thing. If you sell into the US, this is a question for your advisers, not for a config key.
  • Whether security updates can be waived at all. See conformity updates. The waiver ships refused, and turning it on is a decision taken on your own advice.

Where the rest lives

The mechanics this page deliberately does not cover: