goldnead/statamic-app-api
A Statamic addon that exposes your site’s content through a clean, app-ready JSON API. Connect mobile apps and custom frontends to collections, entries, and taxonomies using Laravel-powered endpoints built for simple integration and flexible configuration.
This package is the HTTP/API layer around statamic-payments (and optionally statamic-teams) that exposes subscription billing to your front end — the pieces you touch day-to-day are the billing/subscriptions/{subscription}/cancel endpoint, the serialized subscription payload, and the CP settings under Checkout.
First steps:
statamic-payments >= 1.29.2. Cancellation now delegates to Support\Cancellations::cancel() rather than a local copy of the sequence, so the old behaviour is gone the moment you upgrade. Pin the constraint and run composer update statamic-payments in the same deploy.ends_at (new) and the existing ended_label, plus copied_to on the cancel response. Dump one cancelled-but-still-paid subscription and one fully-ended subscription side by side — the label semantics differ and that's the whole point of this release.POST billing/subscriptions/{subscription}/cancel returns the same answers and sends the same mail as before, so an existing integration keeps working; you're mainly re-reading the label and picking up the new response fields.The single most important mental model from this release: a cancelled subscription whose paid term still runs is not "ended". It is "cancelled, running until <end of term>" (Gekündigt, läuft bis …), and only after the term does it become Beendet am <end of term>. That mirrors Portal\Display::ending() in statamic-payments — treat that class as the source of truth for any ending-related copy you render yourself.
Render ended_label verbatim; don't rebuild the sentence. The package now produces the exact portal sentence. If your front end concatenates its own "Ends on …" string, you will diverge from the CP and from the confirmation mail. Use ended_label for display and ends_at for logic (sorting, filtering, "can I still use this?" checks, calendar entries).
Distinguish state from label. ends_at is the machine-readable field — the end of the paid term, regardless of whether the user cancelled. Use it for access checks and for deciding whether a resubscribe flow makes sense. Use ended_label only for human-facing text.
Treat cancellation as a fire-and-forget command with a read-back. The endpoint runs payments' own sequence, so it may send mail and write a log entry. Pattern:
$response = Http::post("/billing/subscriptions/{$id}/cancel");
// then re-fetch the subscription and re-render from the fresh payload
Don't optimistically set local state to "ended" — the subscription is still live until ends_at passes.
Handle copied_to explicitly. When the cancelling user's address differs from the team's billing.email, a second, separate mail goes to the team billing address and copied_to tells you where. Useful for:
Respect billing.cancellation_copy_to_team (default on). If a tenant complains about duplicate confirmation mails, this is the switch — exposed in the CP under Checkout, so it's an ops/comms decision, not a code change. Surface it in your own admin UI only if you already proxy other billing settings; otherwise just document where it lives.
Use the delegated sequence as your integration point. Because cancellation is no longer a copy, any custom hook you had bolted around the endpoint (extra logging, webhooks after cancel) should observe the result, not re-implement the steps. If you need to react to cancellation, hook the mail/log/event side in payments rather than re-running logic in your controller.
Version-gate the team copy. statamic-teams is optional; when it's absent there is no billing.email and no copy. Don't assume copied_to is populated — treat it as nullable.
via: app-api. This is the sneakiest change in the release. If you filter, alert, or build reports on that marker to separate API-initiated cancellations from portal ones, those queries now silently return nothing. Migrate to whatever discriminator payments writes, or add your own explicit log entry in your controller in addition to calling the endpoint.statamic-payments >= 1.29.2 is not advisory: the new code path calls Support\Cancellations::cancel() and the label logic mirrors Portal\Display::ending(). On an older payments version you get class-not-found or drifting copy. Add the constraint to CI so a stale lock file fails loudly.Gekündigt, läuft bis … / Beendet am … come from payments, not this package. If your app ships other locales, don't string-match on these labels — match on ends_at and on subscription state. Any test asserting on literal label text is a brittle test.ended_label values survive the deploy. Invalidate subscription caches as part of the upgrade.ends_at plus whatever cancelled-at field your payload exposes.billing.email → no copy. Empty string vs null in billing.email is worth normalising on your side before you compare.Portal\Display::ending() and Support\Cancellations are the stable seams. If you need ending copy or cancel semantics elsewhere in your app (emails, PDFs, API resources), call into those rather than duplicating this package's formatting.How can I help you explore Laravel packages today?