Repository navigation
Releases
1.31.0
Changed
- Settings: the addon now declares
settingsNavParent()([section, name]of its own Control Panel navigation entry). With statamic-brand-context 1.16 or newer its settings show as the sub-item "Einstellungen" under that entry; addons without a navigation of their own share one "Addon-Suite" page under Settings. Older brand-context versions ignore the method and keep the previous layout.
Upgrading
- No migration, no new config key.
1.30.0
Fixed
- A Mollie webhook no longer answers 200 when Mollie itself cannot be asked (network error,
timeout, 5xx, 429, 408).MollieGateway::fetch()now throwsProviderUnavailablefor those,
and the webhook endpoint answers 503. Mollie redelivers on anything but a 200 (10 calls over
26 hours, https://docs.mollie.com/reference/webhooks); the 200 used to end that, and the buyer
had paid for an order that was never fulfilled. A 404 stays an answer. - A double click on „buy" no longer creates two payments.
Checkout::start()gives a repeated
start of the same basket by the same buyer (same address, products, quantities, discount,
return URL, methods) the open payment of the last ten minutes back, with the same checkout
URL. The claim is a unique index on the newpayments.checkout_key(works on SQLite, where
lockForUpdate()does nothing), and two requests in flight at once end up on one row.
Every provider create call also carries anidempotencyKey(Mollie:Idempotency-Key
header), so a retry after a lost answer gets the first payment back.
Upgrade
- Run
php artisan migrate(addspayments.checkout_key, nullable, unique). - A custom
PaymentGatewaynow receivesidempotencyKeyin thecreatePayment()payload.
Drop it or send it as your provider's idempotency header; do not pass it on in a request body.
1.29.4
1.29.3
Fixed
- The Suite licence notice no longer appears inside a framed control panel (an add-on's
overlay, a live preview). There it covered the frame's own controls, for example Inline
Edit's Save button. It still appears in the control panel itself.
1.29.2
Fixed
- [fix] A cancelled agreement whose paid term still runs no longer reads „Beendet am " next to „Bezahlt bis ". The portal says „Gekündigt · Läuft bis
" while the term runs and „Beendet am " afterwards. New
Subscription::endsAt()(end of the paid term where it lies after the stop, else the stop) and
Portal\Display::ending($subscription, standalone: false);standalone: truegives the
sentence on its own line („Gekündigt, läuft bis 26.09.2027") for an app.
Added
- [new]
Cancellations::cancel($subscription, ?$email, array $copies = []): further addresses get the
same confirmation as a mail of their own (a team's billing address), each once, never the
recipient again, logged withmeta.copy. A copy that fails changes nothing else;
CancellationOutcome::$copiedTolists the addresses reached.
1.29.1
Changed
- The cancellation sequence (provider first, confirmation in Textform
CancellationConfirmed,
entry in the payment's log) is one public call now:
app(Support\Cancellations::class)->cancel(Subscription $subscription, ?string $email = null): Support\CancellationOutcome.
The portal'sCancellationControlleruses it; an app API ending the same agreement can too,
without rebuilding the three steps. Who may cancel stays the caller's decision. The outcome
carriesstatus(cancelled,already_ended,busy,failed),cancelled(),moment,
until,confirmationSent,name,email. No behaviour change in the portal.
1.29.0
Added
- [new]
anredesetting (sie|du, envSTATAMIC_PAYMENTS_ANREDE, on the settings screen per brand):
how the German buyer-facing texts address the buyer. Portal, checkout refusals, withdrawal and
cancellation pages, their mails, reminders and dunning all follow it.dulines ship in
lang/de/du/. Finding from ChoirLive: account and team pages said "du", the portal after cancelling
said "Ihr Vertrag".
Changed
- Settings screen descriptions (Control Panel) now say "du" like the rest of this addon's CP texts.
Upgrading
- Default is
sie: nothing a buyer reads changes until you pickdu. No migration.
1.28.0
Findings from the ChoirLive end-to-end check.
Upgrading
- New config key
display_timezone(STATAMIC_PAYMENTS_DISPLAY_TIMEZONE), also on the settings
screen per brand. Null keeps today's behaviour for most sites: it falls back tolegal.timezone,
thenstatamic.system.display_timezone, thenapp.timezone. No migration. Do not change
app.timezoneto fix a displayed time: stored timestamps carry no zone and would all shift. - Portal texts: new keys
cancel_paid_until,cancel_effect_until,cancelled_until,
mail_cancelled_until;cancel_effectandmail_cancelled_bodyno longer say "immediately".
Sites with overridden translations should review them.
Fixed
-
[fix] Control Panel, buyer column: a purchase
foran Eloquent user without a morph alias
(App\Models\User) read "App\ Models\ User Lara Muster". It now reads "User Lara Muster", and a
purchase for the buyer themselves shows no second name at all (list, subscription list, detail). -
[fix] Cancellation page: "Beginn" showed
starts_at, the day the provider's rhythm starts (for an
annual plan one year after the purchase). It now shows when the contract began. "Nächste
Abbuchung" is replaced by "Bezahlt bis". -
[fix] Cancellation wording: "Die Kündigung wirkt sofort" contradicted the checkout (end of the paid
term). Page, confirmation screen and confirmation mail now say: no further charge, the contract
ends when the paid period runs out on . Matches whatcloseFor()does with the access. -
[fix] Times in the shop's zone: the cancellation screen and mail, order pages, pause and switch
notes and the § 312k/§ 356a receipts formatted inapp.timezone, so a UTC server wrote
"19:07 Uhr" for 21:07 in Berlin. All go throughLocalTimenow; storage stays UTC. -
[fix] Subscription line name: "ChoirLive Chortarif (jährlich) — jährlich" named the rhythm twice.
A product name that already contains the rhythm word gets no suffix (Subscriptions::lineLabel(),
whole word only: "halbjährlich" is not "jährlich"). This is the name the invoice line carries.
Documented
- The portal needs no confirmed user account: the link goes to the address on the order and
following it proves the address. A buyer who never confirmed the site's account mail can still
see orders and cancel (testPortalWithoutConfirmedAccountTest).
1.27.0
Upgrading
- No migration, no new permission, no new config key. Payments without
forbehave as before,
except that a refund, chargeback, pause or end now touches only the grants of that purchase. - A caller that put
entitlement_subjectintometaitself (statamic-teams until its next
release) now gets anInvalidArgumentException; passforinstead. - A subclass of
EntitlementsBridgethat overridesgrantSlug(),grantLine()orextendFor()
must widenstring $subjecttomixed $subject: the subject is now resolved before it gets there.
Added
- [new] Buying for someone else (a team):
$details['for']. A saved Eloquent model, a Statamic user
or aSubjectReference, checked at the till (resolvable type, existing record; otherwise the
address, with a warning). Stored asmeta.entitlement_subject = {type, id}, which is now a
reserved key: passing it insidemetathrows. The entitlements bridge then grants, renews,
pauses, closes and revokes for that subject instead of the buyer's address. - [new] Refund, chargeback, pause and end touch only this purchase's grants:
source = statamic-paymentsandsource_refamong the payment's and its subscription's provider ids
(current, previous, first payment). Before, a refund revoked every grant of the buyer on that
product, including a second purchase and one made by hand. - [new] A renewal extends this subscription's own grant instead of calling entitlements'
renew(),
which took any grant of the subject and could push a grant made by hand (or another
subscription's) forward. Never shortens, firesEntitlementRenewed, grants only when the
subscription has no grant of its own. Same for a pause inkeepmode. - [new] A price change that restarts the agreement at the provider remembers the old provider id, so its
grants and late cycles are still found. - [new] A subscription keeps billing and subject data of its first payment: an allow-list
(entitlement_subject,team_id,team_uuid,paid_by,company,address,
address_fields,vat_id), extendable withSubscriptions::inheritMeta(...). Cycles, follow-up
offers and resumed checkouts keep the subject too. Agreements created before this release find
the subject on their first payment. - [new] Team billing address for the invoice. A
meta.addressin the fields statamic-teams writes
(company,name,line1,line2,postal_code,city,country; one of them is enough)
becomes the text statamic-invoices prints;meta.companyis filled from it when not set, the
fields stay undermeta.address_fields. An address given as text, or with fields of its own
(street, …), is kept as it was. - [new] Control Panel: payments and subscriptions show "Bought for: Team …" in the listing and the
detail; names are loaded once per page. The search finds a payment or subscription by the team's
name. The payment detail shows the company when it differs from the name.
1.26.0
Upgrading
- No migration, no new permission.
- With statamic-webhook-manager 2.10 the payment and subscription moments appear there as
triggers. Nothing to do if you want that. To switch it off, set
statamic-payments.webhook_manager.enabledtofalse(envSTATAMIC_PAYMENTS_WEBHOOK_MANAGER).
Without the webhook manager nothing changes.
Added
- [new] Webhook Manager triggers. With
goldnead/statamic-webhook-managerinstalled, the twenty
payment and subscription moments appear there as triggers under the automations handles
(payments.paid…payments.subscription_ended), labelled in German and English. Each body is a
chosen list of fields, never a model: no card data, no mandate, no provider responses, no
thank-you or portal token, the IP only as its /24 or /48 network. Every body carriesevent,
occurred_at,brand(id,handle),subject_typeandsubject_id; the full list per
trigger is in the README. Delivered in the brand of the row, so a provider webhook or the
reminder command reaches the right brand's hooks.SubscriptionCycleFailedand
PaymentCommunicationLoggedare not offered, as in automations. - [new] Every body carries
event_id(sha1of handle and what makes the moment unique: the object,
its own time or reference), the same when the same moment is told twice, so receivers can drop
duplicates.occurred_atand the manager's event time are the moment's own time (paid_at,
refunded_at,ended_at…), not the time of sending. - [new] Handed over after the surrounding database transaction commits, never after a rollback (the
dunning run ends a subscription inside one). - [new] A row naming a brand that cannot be set is not delivered at all (logged), instead of going out
through the current brand's hooks. - [new] The README says it plainly: order between triggers is not guaranteed; sort by
occurred_at,
deduplicate byevent_id. - [new] Config
webhook_manager.enabled(STATAMIC_PAYMENTS_WEBHOOK_MANAGER, default on). - [new] The coupling is optional: composer
suggest, the manager's classes are checked by name before
anything that implements its interface is loaded, and registration retries at the end of the
booted queue. A new test boots the addon in its own process with the manager and brand-context
hidden from the autoloader.