=== PaywallProject changelog ===

Releases older than the ones listed in readme.txt.

= 2.2.0 =
* Feature: Reconcile Stripe payments — a Tools report that compares every live Stripe subscription against the expiry date held on the site, so a renewal that never applied is visible instead of silent. Download the CSV of the subscribers that have drifted, then repair their expiry dates from Stripe in one click. The report writes nothing, the repair only moves an expiry forward and never changes a status, and both stay hidden until you enable them under Settings → Advanced.
* Feature: Backfill groups — turning on "Group accounts" for a level no longer leaves the subscribers already on it without a group. Preview the count, the seat size and who is affected, then create the groups. Saving a level that newly enables group accounts takes you straight to the form with the level chosen.
* Improvement: Webhook events are now a checklist with a line saying when each one fires, instead of a free-text box where a typo produced a webhook that never fired. Tick "All events" to subscribe to everything; an event added by hand or by a filter is kept and shown as its own row.
* Improvement: Every webhook has its own "Save and send test" button and event picker, including the one you are adding, so you can check a single endpoint works rather than firing at all of them at once.
* Improvement: The settings page warns before you leave a tab with unsaved changes, and now puts the stored values back after a browser refills the form on reload — so what is on screen is what is saved.
* Improvement: "Change plan" now appears for subscribers on a one-time plan, which previously had no route from the account page to the plans.
* Improvement: The `subscriber.created` webhook is now sent when someone actually becomes a subscriber, not when they start checkout, so a reader who abandoned payment is no longer sent to your mailing list. It also carries status, expiry, shipping and phone, as does `subscriber.status_changed`. Existing fields are unchanged.
* Fix: A hosted-checkout subscriber was given an expiry of the next day — the payment link's lifetime, not their billing period. The expiry now comes from the subscription itself, and one-time purchases get their own correct date.
* Fix: Another plugin's failing database query inside a webhook — a mail logger, for example — was read as the site's database being down, so every hosted-checkout signup returned an error and Stripe retried it forever. A failure is now attributed to the query that caused it.
* Fix: On newer Stripe API versions the billing period is stored on the subscription item rather than the subscription, so renewals recorded the payment but left the expiry date in the past, with no error anywhere. It is now read from either place, everywhere the plugin needs it.

= 2.1.0 =
* Feature: WooCommerce integration — sell access through WooCommerce by mapping products and variations to levels on the level screen. One-time orders grant access on payment, and WooCommerce Subscriptions orders renew, cancel and expire it in step with the subscription. Guest checkout creates the customer account it needs, purchases appear in My Account, and the Paywall Project welcome email can be switched off per level so only the WooCommerce emails go out. Off until you enable it under Settings.
* Improvement: The "Admin: new subscriber" notification can be limited to chosen levels, so a free tier no longer buries the paid signups you want to see. Leave the list empty to be told about every level, as before.
* Fix: A WooCommerce Subscriptions renewal stored an expiry one billing cycle in the past, so a subscriber who had just paid could lose access days later. The expiry is now read after WooCommerce sets the new payment date, and a wrong date corrects itself on the next paid transition.
* Fix: WooCommerce access is now tied to the order's real recipient, so a replayed or older order can no longer overwrite a newer purchase.
* Fix: A subscriber with a failing card who joined a group kept their personal Stripe subscription, which carried on retrying the open invoice. Joining a group now cancels a past-due subscription as well as an active one, and the open invoice is voided with it. Group owners are still left alone, since their subscription funds the group.
* Fix: If the Stripe customer behind a subscriber had been deleted in Stripe, checkout and the billing portal both failed with a generic error and only an administrator could clear it by hand. The dead record is now replaced automatically during the same request, so the reader can subscribe or update their card without help.

= 2.0.0 =
* Feature: Gift article links — subscribers can share a single article with someone who doesn't subscribe. One link, one reader, once: the first person to open it claims it, it stops working for anyone else, and it expires. Enable per level with a monthly allowance, adjustable per subscriber; gift activity is reported on the Dashboard and Analytics, with a CSV export of individual links, senders and recipients.
* Feature: Per-subscriber RSS feed — active subscribers get a personal, authenticated feed link from My Account that carries full article content, listing only the posts their own plan grants. The link can be regenerated at any time, which immediately invalidates the old one. Off by default — enable it under Settings → Advanced.
* Feature: Analytics page replaces Insights, with the Dashboard's reports over a date range you choose. The old `ppaccess-insights` URL redirects to `ppaccess-analytics` with the query string intact.
* Feature: Warm leads and missed payments worklists — who to chase before they subscribe, and who to chase before they churn. Missed payments shows the amount, the failure reason, and how long Stripe will keep retrying; both export to CSV.
* Feature: Going quiet — paying subscribers who have stopped showing up, so you can reach them before they cancel rather than after.
* Feature: Reading history — a dated record of the articles each subscriber opened, built for chargeback evidence when a card issuer asks a publisher to show the subscription was actually used. Off by default under Settings → Advanced, kept for 180 days, exportable per subscriber or across a date range.
* Feature: Failed payments are now recorded rather than only reflected in a subscriber's live status, so missed revenue can be reported on after the fact.
* Feature: Admins can change a subscriber's email address from the subscriber detail page, and the linked Stripe customer is updated with it.
* Improvement: The paywall display report now says why the paywall appeared and names the free-registration prompt separately from a subscribe prompt, which on a site leading with free registration was previously almost every row.
* Improvement: Paywalls rendered on the server are now counted. Only paywalls drawn by the browser were recorded before, which meant reports undercounted signed-in readers specifically.
* Improvement: Dashboard and Analytics charts and cards restyled — muted palette, revenue and signups drawn in one frame on two scales, and reports laid out in two columns.
* Improvement: Reader links throughout the reports now open the subscriber record rather than the WordPress user editor.
* Improvement: A subscription status re-asserted as the same value no longer appends a history entry, which previously grew a subscriber's meta on every Stripe webhook.
* Fix: "Resume" is no longer offered to a subscriber whose Stripe subscription has already finished cancelling, where it always failed; they are offered "Start new subscription" instead.
* Security: Search-engine crawler verification no longer blocks a PHP worker. Confirming a crawler needs a DNS lookup, and the previous lookup could not be timed out — measured at 16 seconds against an unresponsive resolver. Since anyone can claim to be Googlebot, a burst of forged requests could exhaust the worker pool. Lookups are now bounded and cached per address.
* Security: Values in CSV exports that begin with `=`, `+`, `-` or `@` are neutralised, so a subscriber cannot get a formula to run in a spreadsheet by putting one in a name or email field.
* Security: Reading history is registered with WordPress's personal-data export and erase tools, so a subject-access or erasure request can be answered completely.

= 1.4.5 =
* Feature: New `ppaccess_captcha_bypass` filter to bypass the captcha on specific requests, e.g. the PP Mobile app.
* Fix: Subscription cancellations now void any leftover payable invoices for that subscription, so a customer can't pay an old invoice after their access has already ended.
* Fix: Duplicate subscription-payment transactions caused by invoice and PaymentIntent both recording the same payment.

= 1.4.4 =
* Feature: Configurable multi-day renewal reminders — set multiple "days before expiry" milestones (e.g. 30, 14, 1) on the renewal_reminder email template, with a new `{days_remaining}` merge tag; each subscriber gets one reminder per milestone as it's crossed, and reminders reset on renewal.
* Feature: Attach a subscription to an existing WordPress account from Create Subscriber — an email that already belongs to a plain WP user with no paywall metadata can now be turned into a subscriber instead of hard-erroring.
* Feature: Level, status, and expiry-range filters on the Tools subscriber CSV export, in addition to the existing level preset.
* Feature: `group.member_access_changed` webhook — fires per member when a group owner's status changes or a member is removed, so downstream automations (Pabbly, newsletter lists) can react to lost or regained access.
* Feature: Remove saved webhooks from the Webhooks settings tab, with an undoable "Remove webhook" action before saving.
* Improvement: Group members now get correct access on capped (metered) levels and on paywalls restricted to specific levels — previously they were blocked or given no group access at all.
* Improvement: A free-level member's personal access is cleared when they join a group, so group-level access applies instead of the free tier.
* Improvement: The self-hosted updater now passes the plugin icon through to WordPress so it shows correctly on the Updates screen instead of the default icon.
* Improvement: The Leaky Paywall importer on the Tools page is now hidden by default, with a new "Leaky Paywall migrator" toggle under Settings → Advanced to show it while migrating.
* Fix: The lead-in fade's pull-up margin no longer gets zeroed by block-theme global styles, so the paywall card consistently tucks into the fade.

= 1.4.3 =
* Fix: The paywall card's top border is no longer hidden by the lead-in fade gradient, and block themes (e.g. Blocksy) no longer add a gap between the fade and the card.

= 1.4.2 =
* Feature: Blocked email domains for free registration — list domains under Paywall → Free registration to stop those addresses from completing a free sign-up; blocked readers can still subscribe to a paid plan, and subdomains of a blocked domain are covered too.
* Feature: Saved level-filter presets — save the Subscribers list level selection as a named preset shared with all admins, apply or delete presets from the level dropdown, and export just a preset's subscribers from the Tools CSV download.
* Feature: `Email me a sign-in link` on free registration — returning readers who forgot their password can request a passwordless sign-in link (30-minute, single-use) from the sign-in step, and it works even when requested on one device and opened on another.
* Improvement: The lead-in fade now also appears on server-rendered paywalls — logged-in metered or capped readers see the same fade-out into the upgrade box as anonymous readers — and the paywall card tucks into the fade without a gap.
* Fix: Stripe webhooks are no longer lost during a database outage — if a query fails while handling an event, the plugin now returns a retryable error so Stripe redelivers it once the database recovers, instead of silently acknowledging it.

= 1.4.1 =
* Feature: Change plan from the account page — subscribers on an active recurring paid plan get a "Change plan" action on `[ppaccess_account]`, and the subscribe page grid now marks their current plan.
* Feature: Per-category paywall copy — override the heading and subheading of the free-registration and subscribe boxes for specific categories, with add/remove rows on the Paywall settings page and a category preview dropdown; when a post has several categories the primary category wins.
* Feature: Free window — optionally keep rule-restricted posts free for a set number of days after publish (Restrictions → Lead-in and bypass), then gate them normally; posts explicitly set to require access still gate immediately, and no metered view is spent during the window.
* Improvement: The Subscribers list level filter is now a checkbox dropdown so you can filter by several levels at once — for example all print tiers together; old single-level filter links keep working.

= 1.4.0 =
* Feature: Subscriber-only commenting — a new comment gate restricts who can post comments to active subscribers, optionally limited to specific levels, with configurable logged-out and upgrade messages. Enable it under Restrictions → Comments; comment moderators can always post.
* Feature: Lapsed-subscriber access — optionally let past-due, canceled, or expired members who were on a paid plan keep the access of a chosen free level instead of being blocked. It only grants access, leaves their status and level untouched, and is fully reversible.
* Feature: Passwordless sign-in recovery at checkout — a returning subscriber who forgot their password can request an emailed sign-in link that returns them straight to the checkout they were on, logged in at the payment step.
* Feature: Gift redemption now collects a shipping address when the gifted level ships something — pre-filled for signed-in recipients, marked required when the level requires it, and validated before the recipient account is created.
* Improvement: Direct free sign-up forms now honor the level's configured fields (name, phone, company, billing, shipping) instead of only first/last/email, so free print and mailing tiers can collect a shipping address; the flow no longer redirects back to the subscribe page and saves profile fields before activation.
* Improvement: Custom email templates now accept full HTML — paste a complete responsive email with `<!DOCTYPE>`, `<head>`, and `<style>` (media queries, `!important`) and it is stored intact instead of being stripped and dumped out as visible CSS. Applies to the global and per-level templates for editors with the unfiltered-HTML capability.
* Improvement: Metered articles now render inline for logged-in readers, so embeds, maps, and inline scripts inside gated content load correctly — no more stuck loading skeletons or "x is not defined" errors, and no flash of hidden content before the paywall.
* Improvement: The free-registration slide-up prompt has been restyled NYT-style — the article fades to white into the form panel instead of a dark scrim, with a subscription-options callout, an "or" divider, larger inputs, full style-preset support, and a fix for the panel getting stuck when dragged down on mobile.
* Improvement: Accepting a group invitation now cancels the member's own personal subscription so they aren't billed twice, and fires the `subscriber.created` webhook so invited members reach your integrations.
* Fix: Subscription payments no longer record a duplicate transaction row — Stripe's 2025 API moved the payment-intent field the dedup relied on, and the webhook now reads it from the new location.
* Fix: Recurring-level subscribers with no Stripe subscription — manually created, imported, check-payment, or complimentary — now expire on schedule instead of staying active forever.
* Fix: A canceled subscriber is no longer reverted to past_due by Stripe webhooks.
* Fix: Custom transactional email templates no longer send the welcome email twice.
* Fix: Checkout no longer fails with an opaque "502 Could not create Stripe customer" when a shipping address is entered without a name — first/last name is now required alongside shipping, and Stripe's real error message is surfaced.
* Fix: Form inputs no longer trigger iOS Safari's zoom-on-focus on mobile.
* Security: Hardened group-invitation acceptance against emailed-link replay (each link is now single-use) and against a member cancelling or orphaning a group they already own.

= 1.3.0 =
* Feature: Pay-what-you-want levels — a new "Pay what you want" payment type lets buyers enter their own amount at checkout, with configurable minimum, maximum, and suggested defaults, billed either one-time (a donation) or on a recurring interval. Works for new signups and plan changes, with the server always re-clamping the entered amount authoritatively.
* Feature: Admin email notifications for three new conditions — payment succeeded, subscription canceled, and subscription expired — each an individually toggleable, editable template under PaywallProject → Emails. The expired notice can be scheduled a set number of days before or after the expiry date.
* Feature: Search subscribers by name — the Subscribers list search now matches first, last, and display name (not just email and IDs), auto-submits as you type, and a new quick-jump autocomplete on the subscriber detail page links straight to any other subscriber.
* Feature: Per-level CTA button label — override the subscribe grid button text for each level, defaulting to "Subscribe" when left blank.
* Improvement: Customer merge tags (`{phone}`, `{shipping_address}`, `{billing_address}`) now work in every email template, not just admin notifications, and are filled in automatically from the subscriber.
* Improvement: The email template picker is grouped into labelled sections — Subscriber emails, Admin notifications, Gift emails, and Group accounts — for easier scanning.
* Improvement: The `{admin_url}` email merge tag has been renamed to `{subscriber_url}` for clarity; `{admin_url}` keeps working as a backward-compatible alias.
* Improvement: The default renewal reminder email copy has been rewritten for manual (non-recurring) renewals — an accurate "this plan doesn't renew automatically" message with a manage-account link and a proper greeting.
* Improvement: Pay-what-you-want level cards now show a "From" price (or "Pay what you want" when no suggested amount is set) instead of $0.00, and the checkout amount field snaps to the enforced minimum or maximum on entry.
* Fix: Pay-what-you-want (donation) levels are now exempt from Stripe Tax by default — a donation has no fixed taxable price and was blocking checkout with a tax-address error; a new `ppaccess_level_taxable` filter lets publishers re-enable tax per level.
* Fix: The cancel-and-recheckout notice no longer always reads "trial" — it now reflects whether the current subscription is a trial or an active plan.
* Security: Group-member invite keys are now generated with a cryptographically secure random generator instead of a predictable value.

= 1.2.0 =
* Feature: New `[ppaccess_login]` shortcode — a standalone login form styled like the account page, with a smart post-login redirect back to where the visitor came from.
* Feature: New `[ppaccess_members_only]` shortcode — wraps content so it is visible only to active subscribers.
* Feature: New `[ppaccess_guests_only]` shortcode — hides the wrapped content from logged-in users.
* Feature: Account page now offers a self-service password change in the profile form and a full forgot-password reset flow.
* Feature: Account shortcode now renders nested shortcodes inside its content.
* Feature: Admin transaction detail page — inspect a single transaction with a paid/free status split and the subscriber's billing address.
* Feature: Per-level welcome email — each subscription level can define its own welcome email subject and body, falling back to the global template.
* Feature: Tag-based paywall bypass — posts carrying selected tags always bypass restrictions, configured with a searchable tag picker in the Restrictions admin (mirrors the existing bypass-by-category).
* Feature: Subscribers are now logged in automatically after completing Stripe hosted checkout.
* Feature: Plan descriptions are parsed into paragraphs and bulleted feature lists for richer plan display.
* Improvement: The inline paywall now renders server-side when the access decision is final, removing the brief client-side flash; the free-registration slide-up continues to work on the JavaScript path.
* Improvement: Free, Subscribe, and Upgrade paywall box copy now allows HTML, and the heading/subhead render on both card templates with newlines converted to line breaks.
* Improvement: Paywall lead-in length is now measured by word count instead of HTML element count, for more consistent truncation.
* Improvement: Leaky Paywall migrator now batches transactions, migrates group accounts, and retries on failure for more reliable large imports.
* Improvement: Slack notifications now show the subscription level name instead of the numeric level ID.
* Improvement: The paywall "Log in here" link now points at the configured `[ppaccess_login]` page.
* Improvement: Levels admin table now displays each level's ID for use in shortcode `level` attributes.
* Improvement: Inline checkout is limited to card payments for more reliable processing, and the "send a magic link" fallback was removed from the paywall OTP sign-in flow.
* Fix: Renewal reminder emails are now sent only to non-recurring subscribers — recurring subscriptions renew automatically and were receiving reminders they did not need.

= 1.1.0 =
* Fix: Free registration box settings (heading, subhead, CTA label/URL, show login) not persisting on save — fields were never registered in SettingsDescriptors so values were silently discarded.
* Fix: Expiration cron wrongly expiring never-expire accounts — empty/0 expiry values were cast to 0 by MySQL and matched the `expires < now` query; a lower-bound guard and a PHP safety check now skip those accounts.
* Fix: Subscriber bulk delete silently doing nothing for non-Stripe subscribers; per-row Delete action added to the subscribers table.
* Fix: Subscriber delete now removes all `_ppaccess_*` meta from the WP user instead of deleting the WP account; delete actions are blocked for subscribers with an active Stripe subscription.
* Fix: Incorrect proration amount shown when switching across billing intervals (e.g. yearly → monthly) with a coupon — prorated charge is now read from Stripe's invoice-preview line items instead of being recalculated from the old plan's period.
* Fix: Surcharge price cache key omitted billing interval, causing stale surcharge prices after an interval change and Stripe rejections on plan updates.
* Improvement: Stripe Tax invalid-address error now returns a clear "check your ZIP/postal code" message at checkout instead of the generic subscription failure; card postal code is forwarded server-side and used as a tax location fallback for card-only levels.

= 1.0.0 =
* Security: Fail-closed webhook secret validation — unsigned events are now rejected instead of processed.
* Feature: Stripe Payment Element as an optional alternative to the classic card element.
* Feature: Stripe Tax support for recurring and one-time subscriptions.
* Feature: Stripe Address Element for tax estimation at checkout.
* Feature: One-click Stripe webhook setup from Settings → Stripe tab.
* Feature: Group Accounts — owners can invite members, manage seats, and switch plans with automatic group resizing.
* Feature: Gift subscriptions — purchase and redeem gift access codes.
* Feature: Coupon support with `ppaccess_coupon` URL param, lifecycle fixes, and proration accuracy improvements.
* Feature: Plan-change preview showing prorated amount due before confirming a switch.
* Feature: Terms of Service banner and checkbox on checkout, configurable per level.
* Feature: Per-level shipping surcharge based on subscriber postal code.
* Feature: Stripe Tax applied to shipping surcharges.
* Feature: Bypass paywall by post category, with category filter UI in Restrictions admin.
* Feature: Per-post lead-in override — set the number of visible paragraphs before the paywall on individual posts.
* Feature: Slide-up free registration form with server-side content truncation.
* Feature: IP exception management with delete functionality.
* Feature: Option to hide a level from the subscription/levels page.
* Feature: Drag-to-reorder levels with visual level cards.
* Feature: Option to disable the direct sign-up link per level.
* Feature: Custom color controls for paywall boxes.
* Feature: Self-hosted plugin updater — receives updates from paywallproject.com without the WordPress.org directory.
* Feature: Leaky Paywall migrator — import subscribers, levels, and shipping metadata with per-user dry-run details.
* Feature: Subscriber CSV export and import, including phone number field.
* Feature: Phone number display and editing on the admin subscriber detail page.
* Feature: Admin can create a new Stripe subscription from the subscriber detail page.
* Feature: Admin Stripe sync — syncs active/canceled status, expiry date, and shipping address to Stripe.
* Feature: Admin email notifications on plan switch for all subscription types.
* Feature: Support for multiple admin notification email addresses.
* Feature: `{shipping_address}` merge tag for admin new-subscriber email notifications.
* Feature: Renewal reminder emails with deduplication via cron.
* Feature: Subscriber webhooks include `first_name`, `last_name`, and `level_id` in all payloads.
* Feature: `ppaccess_subscriber_created` webhook fires on free registration.
* Feature: `charge.refunded` included in webhook setup event list.
* Feature: Daily database pruner for logs and tightened Insights date range.
* Feature: Interactive admin charts with tooltips, legends, and improved accessibility.
* Feature: Cancel subscription confirmation modal on My Account page.
* Feature: Loading spinner on checkout buttons to prevent double-submit.
* Improvement: My Account page redesigned with card-based layout.
* Improvement: Levels admin page redesigned with sectioned layout.
* Improvement: Checkout and level admin pages use consistent `ppaccess-form-row` layout with live checkout preview.
* Improvement: Group Accounts admin page uses `ppaccess-card` styling; Subscribers table columns reordered (email before name).
* Improvement: Restrictions admin page gains category filter input and grid layout for bypass sections.
* Improvement: Mobile styling improvements for admin area.
* Improvement: PaywallProject menu moved to the bottom of the WP admin sidebar.
* Improvement: `pending_cancel` subscriptions no longer show a cancel link; a status note is shown instead.
* Improvement: Plan-change banner skipped when `amount_due` is zero or negative.
* Improvement: Same-plan checkout skips proration and applies coupon without re-billing.
* Fix: Payment Element blank fields in stepped checkout.
* Fix: Payment Element blank for logged-in users.
* Fix: `setup_intent_already_succeeded` blocking Payment Element checkout.
* Fix: Potential double-charge for one-time levels with Payment Element.
* Fix: Tax estimate falling back to shipping address correctly with Payment Element.
* Fix: Plan-change proration preview coupon accuracy.
* Fix: Plan-change preview returning 502 for discounted subscriptions.
* Fix: `fresh_checkout` returned (200) instead of 400 for subscribers without a Stripe subscription on plan-change preview.
* Fix: Double `public/` in nonce URL from restriction resolver.
* Fix: Checkout token consumed by coupon check before subscribe submit.
* Fix: Cron jobs not firing and renewal reminder deduplication.
* Fix: `push_update` TypeError when transient is not an object.
* Fix: Group membership ended message shown correctly even when a subscriber record exists.
* Fix: Fatal error in `add_member` when first name is empty and user lookup fails.
* Fix: Blank account page for group owners without a subscriber record.
* Fix: Blank page on Add Member — redirect always runs after nonce validates.
* Fix: Preserve subscription status when expiration date is unchanged in admin save.
* Fix: Level ID synced via webhook metadata and `update_subscription_price`.
* Fix: UTF-8 mangling in lead-in content and Leaky Paywall migrator gateway detection.
* Fix: Lead-in override of 0 now correctly shows no content before the paywall.
* Fix: Leaky Paywall migration no longer overwrites PaywallProject paywall messaging and settings.

= 0.1.0 =
* Initial development release.
