Bang! Cart for Stripe - complete changelog
===========================================

The readme keeps only recent versions, as the directory truncates long histories.
This file is the whole story.

= 3.5.0 =
* The buyer's confirmation email now carries a Cancellation and returns section, on by default. This exists because of two rules about distance selling that bite hardest on the shops that have never heard of them. The Consumer Contracts Regulations 2013 require the contract confirmed on a durable medium, and the courts have said a link is not one - the page behind it can change after the sale, so the words themselves must be in the email. Then the penalties for missing pieces: leave out the notice of the 14-day cancellation right and the window stretches by up to twelve months; leave out that the customer pays return postage and the shop pays it instead. The default wording covers the right and how to use it, the deadlines, who pays return postage, when the right does not apply, faulty-goods rights under the Consumer Rights Act 2015, and the complaints route - complete on its own, because most shops will never edit it, which is the point. It is editable and switchable on the Emails tab, tokens included, with the consequence of switching it off spelled out beside the switch. It ships on for existing shops too: the merchant who has never seen the toggle is exactly the merchant who needs the text.
* A Policy pages card on the Settings tab: Terms of service, Privacy policy and Returns policy URLs, linked from the confirmation email under the cancellation wording. Each URL is checked when saved and a page that does not answer earns a warning rather than a refusal, because the save is how a shop records what the URL is going to be - and because a staging site behind a password fails the same check at Stripe's end without saying a word, which is worth a sentence out loud here.
* The same card documents the two Stripe Dashboard settings that put the "By placing your order, you agree to our Terms of Service and Privacy Policy" line under the Pay button: the URLs under Settings, Business, Public details, and the Display agreement to legal terms toggle under Checkout settings. Both are needed, Stripe's error when only one is done does not say which is missing, and neither can be set through the API - so the plugin says clearly what to do, which is all it can do.
* A delivery zone saved with priced postage but no countries can no longer take a posting order. The session builder attaches the postage and the country list to Stripe together, so this state used to produce a payment with no postage charged and no delivery address collected: the customer paid a total the basket never showed, and the shop owner had an order with nowhere to send it. A delivery charge that was never shown also cannot lawfully be collected, so quietly dropping the postage was the legally worst way to fail as well as the most confusing. Checkout now refuses with a clear message, before any stock or coupon hold is taken. Collection is untouched - collecting needs no countries - and checkout.php pins both halves, verified failing against 3.4.1 and passing now.
* A comment in the source now records why the basket's Checkout button may be worded freely while the law demands "Pay now" or similar on order buttons: this button only opens Stripe's payment page. The button that creates the obligation to pay is Stripe's own, on the hosted checkout, and Stripe keeps that one compliant. The comment is there for whoever one day embeds checkout in-page, at the moment the distinction starts to matter.
* The Send test emails button gained an address box, with the seller address suggested. Mail from a site to its own domain frequently never leaves the server, so the seller-only test could pass while every customer email vanished - the one address that proves delivery is one on a different domain, and now it can be typed. The address is remembered for next time. A mistyped address refuses with an error rather than quietly falling back to the seller, because "sent" reported against the wrong inbox is worse than no test at all.

= 3.4.1 =
* The Page overlay control now works in both basket placements. It sat inside the floating-only group on the Design tab, so choosing "I'll place it myself" dimmed the control - while the overlay itself still tinted the page whenever the self-placed basket opened. A shop could see its overlay and not change it without switching modes back. The control now lives outside that group and stays live in either mode; nothing about the overlay's behaviour or saved values changes.
* The floating corner button's live preview now sits in its own framed Preview panel beside the Shape, Size and Position controls - the same treatment as the checkout styling preview above it - instead of loose beneath the sliders. The whole panel dims with the floating-only group in self-placed mode, as before.

= 3.4.0 =
* Multiple shipping zones. Every zone you switch on now sells: customers pick their region in the basket, and each zone prices at its own rates. The one-zone offering limit and its greyed Add zone button are gone; the Add zone button always renders on the Shipping tab.
* New Page overlay control on the Design tab, beside the floating basket settings: the colour and strength (0-90%) of the tint that dims the page while the basket is open. Defaults to the indigo the plugin has always used at 45%, so existing shops look identical until their owner chooses otherwise. Saved with the other basket settings, clamped at the handler, and the storefront CSS carries it per shop.
* A refreshed wordmark in the admin header (same size, new artwork), and the plugin's own colours follow it: the admin accent is now the wordmark's trolley blue (#2c7ec2, from #6d5ef5 indigo - one function, every control follows), the fresh-install storefront defaults (button, sale-banner text, basket summary tint, receipt fallback wordmark) wear the same blue, and the basket's selected-postage highlight now follows the shop's own accent instead of a hardcoded indigo. Every remaining lavender tint from the indigo era - hover fills, code chips, the version chip, drawer and input borders - swept to blue-tinted equivalents in both the admin and the basket, verified by a programmatic scan for purple-family hexes. Any shop that has saved its styling keeps its saved colours, exactly as always.
* All admin and storefront styling and scripts now load through WordPress's enqueue system - registered handles with wp_add_inline_style/wp_add_inline_script - instead of printed style and script tags. The only inline styles left are in the order emails (email apps require them) and the standalone order-status and mark-as-posted pages, which are complete documents served outside the theme where the enqueue system does not run.
* The readme description restated: it says what this plugin does, with no comparisons against other plugins.
* Settings that were made by an add-on that is not active are described in exactly those words, product-free: the zone note, the Discounts note, the invoice-document note and the product badge all state the fact and the way forward, and offer nothing for sale.

= 3.3.5 =
* Pressing Recount now reports the answer rather than promising it. The confirmation notice still read "the total reappears here once it is complete" - true in 3.3.3, when the press only queued a job, and still being printed by 3.3.4 with the finished figure sitting directly underneath it. Andy: "after clicking recount I had to go out of the tab and back in." He did, because the page told him to. The count had in fact finished before the page drew; only the sentence describing it was a version out of date.
* The notice now says which of four things happened: the new total and order count when the press finished the job, that counting carries on in the background when the history needs more than one request, that no single total is shown for a shop with more than one currency, or that there are no paid orders to total yet.
* The lesson is about where the fault could be seen. Every test of the recount ran the function on its own and passed, in the same release that shipped a page contradicting itself - a message and the number it describes can only disagree in one place, and that is the rendered response. The suite now presses the button through the real admin page and reads both in the same HTML.

= 3.3.4 =
* Pressing Recount, or opening Orders for the first time, now counts on the spot. 3.3.3 gave the entire job to WP-Cron, which was the wrong shape for the ordinary shop: a hundred orders is one request, and handing that to a background queue meant a button press that made no Stripe call whatsoever, a confirmation that something had started, and then a strip reading "reading your order history" until cron happened to run. On a site with DISABLE_WP_CRON set it would have read that forever. The first slice runs inline now; the queue is for the shop that needs a second slice, not the shop that needs a first one.
* A count already in progress advances on Orders tab loads whenever the queued slice is overdue. WP-Cron is not a scheduler - it runs when somebody loads a page, and not at all on sites that have turned it off - so a count that could only advance on cron could never finish there. It finishes within a minute where cron works, and within a few page views where it does not.
* Only the Orders tab spends that request. Every tab of this plugin already fetches the order list for the "to post" badge; without the restriction the first view of Products or Emails would pay for a figure it does not print. The decision is a named function rather than a comparison buried in the page, so it is pinned by a test - a claim made only in a comment beside inline code cannot be tested without rendering the whole admin, and a claim no test can reach quietly stops being true.
* The takings strip now completes a partial state rather than reading past the end of it. Handed an array missing keys it raised warnings and rendered nothing, which on a site with debug display off is a blank space and no explanation.

= 3.3.3 =
* New: what the shop has taken, all time, above the orders. Gross, in the shop's currency, with the order count beside it, and it says on its face that it is before Stripe's fees and any refunds - a figure an owner acts on should not need a footnote.
* It is counted rather than guessed, and that is the whole of the work. The obvious build - add up the orders on screen - is wrong here: the Orders tab has only ever asked Stripe for the 25 most recent, so it would have printed the same total for a shop with 25 orders and a shop with 2,500, and frozen the day the 26th arrived. There is no Stripe endpoint that returns a lifetime figure, so the history is read once, in the background, a slice per run.
* Nothing is shown until that count finishes. A half-read history is not a smaller total, it is a wrong one, and a wrong total is worse than no total because nobody can tell by looking. While it runs the strip says what it is doing and how far it has got.
* Afterwards it stays current for free. The admin already fetches recent orders on every tab of this plugin, for the "to post" badge; new orders are counted from that list, so an ordinary day costs no extra request at all. Only a shop that has taken more than 25 orders since the owner last looked falls through to a background catch-up, and that goes to cron rather than holding the page open.
* Two writers, and a boundary that is exact in both directions. The history walk takes everything created at or before the moment counting began; the observer takes everything after it. Stripe's timestamps are whole seconds, so the watermark also carries the ids counted in its own second - which is what stops two orders placed in the same second being counted twice, or one of them being dropped and never seen again.
* Only paid orders count. An async bank payment that has not cleared is not money yet, and an order that needed no payment is not takings.
* Test and live have always been separate totals. Clear test order data now removes the test one along with the rest of the test notes, and the card's wording, its enabled state and what the press actually clears are all decided by the same function - the fault 2.4.32 was written to end.
* A shop that has taken orders in more than one currency is told so, rather than shown a total that adds them together.
* Recount, beside the figure, rebuilds it from Stripe from scratch. Any cached number drifts eventually - a refund raised in the Stripe dashboard is the ordinary way - and a figure with no way to rebuild it is a figure you stop trusting.
* A Stripe error during the count is kept and shown, and retried. Counting that stops in silence is how a total sits there being quietly wrong.
* Under the bonnet: bangcart_takings_panel filters the whole strip, so an add-on can replace it with reporting without the plugin being reworked.

= 3.3.2 =
* Setting the webhook up by hand no longer breaks delayed bank payments. The "Prefer to set it up by hand?" instructions on the Stripe tab named one event to tick, checkout.session.completed, while the one-button setup beside them has always subscribed four. Anyone who followed the written route lost the two async_payment events, and with them every Bacs or bank-debit order: the money arrives, the order never completes, no confirmation email is sent and stock never moves. The instructions now list all four events, and they are built from the plugin's own list rather than retyped, so the two can never disagree again.
* The same instructions now explain customer.created. It is what lets Verify connection now prove the connection without taking a payment, and the plugin deliberately will not add it to an endpoint you built yourself - so leaving it off meant the person who most needed the diagnostic was the one person who could not run it.
* The connection check is blunter about missing async events. It previously ended with a mild "for bank methods such as Bacs, also enable..." note; it now says what actually happens if you do not - the order takes the customer's money and never completes. The check still cannot see which payment methods your Stripe account has switched on, so this remains a note rather than a failure.
* The Emails tab pointed at a button that does not exist. Its Webhook & connection card sent you to the Stripe tab to find "Check connection"; the buttons there are Verify connection now and Re-check only. Both are now named properly, along with two code comments carrying the same phantom.
* The translation template was a version behind. bang-cart-for-stripe.pot was last built before 3.3.1's own wording fixes landed, so it still offered translators "Open Settings", "the Copy button is on the Settings tab" and "press Save colours" - three strings 3.3.1 had already replaced. A translator who did the work got nothing for it: the real strings were not in the template, so they stayed English in every translated shop while three translations pointed at controls that no longer exist. The template is rebuilt from the shipped code, and now carries the four events, the real button names and everything else 3.3.1 and 3.3.2 changed.

= 3.3.1 =
* The Documentation button in the admin header is live - it opens the new documentation site at bangplugins.com/docs/cart.
* Start here: the self-placed basket step now points at the Design tab, where the shortcode's Copy button actually lives - both the wording and its Open button still said Settings after 3.3.0's tab split.
* The Email colours hint now names the real control, Save changes - there is no "Save colours" button.
* readme: the install steps describe the one-button webhook setup (adding it by hand remains a fallback); and the plugin's own PDF receipt is described instead of implying all receipts live in Stripe.
* Housekeeping: seven code comments still said "Settings tab" for controls that moved to the Design tab in 3.3.0.

= 3.3.0 =
* The basket's quantity stepper now derives its colours from the shop's own palette (a lighter and darker step of the chosen totals-box colour, with automatically readable ink) instead of one fixed tint that only matched the default shop.
* The Documentation button appears only once it has real documentation to open. Until the docs site is live (a future release points the button at it), the header shows no Documentation button at all, rather than dressing the Start here tab up under that name.
* The Settings tab has been split. Checkout styling, basket icon and placement, and the page previews now live on their own Design tab; Settings keeps the shop's identity (name, currency), open/closed status and the thank-you page; Stock messages moved to the Products tab, beside the stock levels they describe. Nothing was renamed or changed inside the cards - they only moved - and every saved value is untouched.
* One width for every explanation. Card introductions and help paragraphs sat at different maximum widths depending on which CSS class they used - some ran the full card, some stopped at 60% - which read as untidy. Every prose class now shares a single width rule, and that rule is the full card width, controlled by one token so it can only ever change everywhere at once.
* One breath below every explanation. The space between a section's introduction and the controls beneath it measured anywhere from 0px (the Design tab's intro touched its colour pickers) to 28px, depending on the paragraph's class. Every section-opening paragraph now sits a consistent 28px above what follows, from a second shared token.
* New default storefront palette in the plugin's own branding. The basket, buttons and floating basket now ship in the Bang! indigo and blue (indigo buttons and accents, white panel, soft indigo-tinted surfaces, a blue count badge, green reserved for good news like free delivery and savings) - a clean, modern base for each shop to restyle, instead of the original terracotta-and-cream personality. Everything remains configurable in Checkout styling. UPGRADE GUARD: an existing store (detected by its saved Stripe keys or order history) that never saved Checkout styling gets the old warm palette pinned into its settings on update, so a live shop's look never changes silently; only fresh installs start neutral. A styling row saved before the badge control existed also keeps its olive badge.
* Hardening release ahead of the WordPress.org submission. Public endpoints now carry a per-visitor rate limit (checkout 10 per 10 minutes, coupon checks 15 per 10 minutes, both filterable) so a script can no longer hold a shop's stock in endless 30-minute reservations or mint unbounded coupon objects in the Stripe account.
* The stock-hold system's release and extend paths now take the same database lock as reserving - an unlocked release could save a stale copy over a concurrent customer's brand-new hold.
* Coupon-cache pruning moved out of the customer's checkout request into a background cron event - one unlucky buyer could previously absorb dozens of sequential Stripe DELETE calls and time out.
* A verified webhook payment for the OTHER mode (a live Bacs payment clearing while the toggle sits on test) is now answered 503 so Stripe keeps retrying, instead of 200 - which permanently dropped the order: no email, no stock change.
* Admin actions are refresh-safe: Mark posted now posts the state it wants instead of toggling (a refresh can no longer silently un-mark an order), Resend confirmation and Send test emails carry a one-shot token (a refresh cannot double-send), and Create the page for me re-selects an existing thank-you page instead of creating a duplicate.
* A product row saved with a blank name or price now keeps its stored details and says so, instead of silently deleting the product, stock, photo and all. Deleting stays with the Delete button.
* A save whose security nonce has expired (the tab left open overnight) now says "Those changes were not saved" instead of failing silently.
* The basket's price quote carries a sequence ticket, so a slow older reply can never overwrite a newer one and re-open checkout showing a stale total.
* Order fulfilment is claim-then-confirm: a crash mid-fulfilment (slow mail server, PDF build) no longer marks the order done, so Stripe's retry completes it; concurrent duplicate deliveries are still turned away.
* Webhook failure records from requests that do not even resemble Stripe (no well-formed, current signature header) no longer count as proof the signing secret is wrong - a scripted POST can no longer turn the connection box red or goad the repair button into replacing a healthy endpoint.
* An order too long for one Stripe metadata value now continues into numbered keys and the webhook rejoins them - previously such an order paid fine and silently never decremented stock.
* Coupon redemption counts are read fresh from the database on save (a stale Discounts tab can no longer roll a counter back) and incremented under the lock (two simultaneous webhooks can no longer both write the same count).
* The currency save is whitelisted against the supported list - typing JPY (or any zero-decimal currency the arithmetic would mischarge 100x) is refused with an explanation instead of accepted.
* The plugin's PDF receipt now paginates: a long order continues onto page two with the table headings redrawn, and every page is footed "Page N of TOTAL" - totals were previously drawn invisibly off the bottom of page one.
* Cart drawer: every admin-origin string (product names, image URLs, zone and service names, coupon codes, the collection note) is HTML-escaped before rendering - "Tea & Cake <set of 2>" now renders as typed.
* The basket syncs across tabs: paying in one tab empties the basket in the others, closing an accidental double-purchase path.
* Accessibility: keyboard focus is now visible on every toggle switch and segmented control, product cards open from the keyboard like zones always did, the closed drawer is hidden from screen readers and find-in-page, label greys were darkened to meet WCAG AA contrast, and the service-swap button's broken title/aria attribute was repaired.
* Action buttons save first: Send test emails, the connection checks and Sync from Stripe now save the tab's unsaved edits before acting (and sync merges into the just-saved products, not over them) - previously the action ran against the old values and the edits were silently discarded.
* Stripe's raw error text is no longer relayed to anonymous checkout callers - customers get a plain sentence; the detail still lands on the Orders tab for the owner.
* Sync from Stripe now paginates to 1,000 products (was silently capped at 100) and its no-products message names the shop's own currency instead of GBP.
* The webhook signing secret is no longer echoed back into the page source; like the API keys, a blank field means "keep the saved one".
* The checkout failure fallback now actually shows its HTTP status code instead of "Checkout failed ()".
* The sale banner follows the shop's configured heading font instead of a hardcoded serif; the drawer's placeholder amounts use the shop's currency symbol instead of a hardcoded pound sign; after the quote retries are exhausted the message says what to do instead of promising "trying again".
* Customer-facing shipping strings and the JS-built admin controls (delete confirms, expand/collapse, zone alerts, country counts) are now translatable; the .pot was regenerated.
* Uninstall also clears the queued coupon-prune cron event.
* Author updated to Bang! Plugins (bangplugins.com).

= 3.2.9 =
* The storefront large-order enquiry button now calls the drawer's close() on click, before the link is followed. Same tab as 3.2.8, but a link to an anchor on the same page would leave the open basket covering whatever it scrolled to; closing first clears the way. A link to another page unloads anyway, so closing is harmless there. Uses the same close() the Escape key and close button use, so focus is handed back correctly.

= 3.2.8 =
* Per-zone large-order overrides are now opt-in per zone. Each zone gained an "ol_on" flag and a switch ("Give this zone its own large-order message"); the message/link/label fields sit in a hidden panel revealed by the switch, mirroring the collection-note pattern. At quote time the zone's wording is used ONLY when its switch is on - otherwise every field falls straight through to the shop-wide Large orders setting, so a zone left on the default can never carry a stale override it can no longer see. Saved values are kept when the switch is off (hidden, not wiped), so re-enabling brings them back. Before this, the three fields showed in every zone permanently - a wall of text duplicating the shop-wide card right below the zones.
* The storefront large-order enquiry button opens in the same tab (dropped target="_blank"/rel), matching a normal in-shop link.

= 3.2.7 =
* Discount stacking, as a shop choice (off by default). A new toggle on the Discounts tab - "Let a discount code apply on top of an automatic discount" - changes how the discounts combine. Off: the four goods discounts (quantity, sale, order-value, code) contest and only the single biggest applies, as before. On: the AUTOMATIC discounts still contest among themselves (only the best of quantity/sale/order-value applies), and then the customer's code is taken off on top of what that leaves. So a basket gets at most one automatic discount plus a code, mirroring the modern norm (Shopify combines a code with an automatic discount by merchant choice; WooCommerce gates combining per coupon). The code is worked out on the already-reduced goods and a fixed code is capped there, so the total can never fall below zero. Free delivery stays a separate axis. Under the hood the automatic discount is applied exactly as before (a percentage reprices the line, a fixed amount is a lump), and the stacked code is folded into the single Stripe coupon so the charge stays correct - the money path is unchanged when the toggle is off. The "One discount per order" callout rewords itself to match whichever mode is on. Verified: existing suite unchanged (1543 pass), plus targeted checks for sale+code, fixed-tier+percent-code, no-code, and the zero floor.
* Genericised the plugin's example vocabulary. The running "jar" example - a leftover of the plugin's origins - is gone from every user-facing string, hint, placeholder and code comment; the examples now speak of "items", because the shop could be selling anything. The languages/.pot template was regenerated to match, and the (unused) internal CSS class .bangcart-jar-remove became .bangcart-item-remove.

= 3.2.6 =
* The click-to-copy chips from 3.2.5 now also cover {placeholder} tokens, not just [shortcodes]. The {limit} and {over} chips in the Large orders help text upgrade to click-to-copy the same way, so they can be dropped straight into the message. The enhancer's regex now matches either a bracketed shortcode with our prefix or a {word} token; the Emails-tab tokens are skipped (they carry data-token and their own copy handler), so nothing is wired up twice.
* Fixed the cramped fields on the Large orders card. The Enquiry link, Button wording and the matching per-zone inputs were given class bangcart-input-sm, whose rule is width:110px;max-width:110px - so an inline max-width override could not widen them and they sat at 110px. Dropped the -sm class and set width:100% with a sensible max (460px for the URL, 360px for the label), and gave each fieldgrid column a flex basis so the two sit side by side and fill the card. The message boxes now fill to 640px (shop-wide) / 560px (per-zone).

= 3.2.5 =
* Click-to-copy for every shortcode shown in the admin, wherever it appears. The dedicated Copy controls (product button/price, the cart code on Settings, the thank-you and sale-banner blocks) are unchanged. On top of them, a small progressive-enhancement script upgrades every OTHER place a shortcode is shown - the ones set inline in help text - into a click-to-copy chip: a dotted underline and pointer to signal it, a "Copied!" flash for feedback, keyboard-accessible (role=button, tabindex, Enter/Space). It reads the rendered DOM, so no shortcode is duplicated into markup and no translated string is touched. It only ever upgrades real shortcodes (text matching our bracketed prefixes), so email tokens like {order} and CSS selectors like .bangcart-price are left alone.

= 3.2.4 =
* Editable over-limit message with an optional enquiry route. When a basket exceeds a zone's Maximum units per order and that zone offers no collection, the cart used to dead-end on a fixed "We can post up to X items... Please remove N to continue." The shop can now reword that message (with {limit} and {over} placeholders for the numbers) and, optionally, set an enquiry link so an oversized order becomes a button through to a contact page rather than a dead-end - the same don't-dead-end spirit as the existing collection escape hatch. Settings live on the Shipping tab: one shop-wide default, plus an optional per-zone override for any of the three fields (a blank zone field falls back to the shop-wide one). The enquiry URL is esc_url_raw'd on save and re-checked in the cart so only http(s)/relative links ever render; the button is a real element beneath the message, never HTML injected into admin-entered text. Both the shop's own limit and an add-on's unit-band ceiling flow through the same rule, so both are covered.

= 3.2.3 =
* Bang! Cart now sits high in the left-hand admin menu, just below Dashboard (menu position 3.7), instead of down at position 56 by Comments. The fractional position claims a near-top slot without clobbering a core item or another plugin that grabbed the same integer.
* New quick-access node in the top WordPress toolbar (admin and front end): a Bang! Cart item with the sidebar's cart icon, opening the shop, with a dropdown of Orders, Products, Settings and Stripe. Capability-gated to manage_options, so only shop managers see it. No live order badge by design - order counts come from Stripe over the network, and a toolbar badge would risk a remote call on every page load when its cache is cold.

= 3.2.2 =
* A clean bill of health from WordPress's official Plugin Check tool: 228 errors and 106 warnings, now zero. Every echoed style value is escaped, every translatable string with a placeholder carries a translators note, every form field is unslashed and sanitised at first touch, and the few deliberate exceptions (token-authenticated email links, the uninstall sweep, the migration's one-time scan) now say in code exactly why they are what they are.
* Fixed along the way: the Start here checklist's placement scan now also recognises the pre-rename shortcode names, so a shop built before 3.2.0 is no longer told its button is missing.
* Tested up to WordPress 7.0. The readme changelog now keeps recent versions only; the full history back to 2.0.0 ships in changelog.txt.

= 3.2.1 =
* The webhook section's hand-setup note now links straight to the Developers - Webhooks page of your Stripe dashboard, in the mode that matches the key above it - the same courtesy the API-key boxes got in 3.1.11.

= 3.2.0 =
* The plugin is now Bang! Cart for Stripe - a new name, same shop. Everything renames itself: settings saved under the old internal prefix are carried over automatically the first time the new version loads, and nothing needs re-entering.
* Old links keep working forever: order-status and receipt links in emails already sent, shortcodes already pasted into pages, and the webhook address stored in Stripe's dashboard all answer exactly as before.
* If you added the old override constant to wp-config.php, it is still honoured.

= 3.1.12 =
* The Copy price shortcode button now actually hides on customer-set-price products. 3.1.9 set the hidden attribute; the buttons' own display rule was quietly overruling it - the same CSS trap the save bar once fell into, now closed for every admin button.

= 3.1.11 =
* The Stripe tab now says where your keys actually live: direct links to the test and live API-keys pages of your Stripe dashboard, and a line for first-timers - creating a Stripe account is free, and test payments work while Stripe checks your details. The Start here checklist sets the same expectation.

= 3.1.10 =
* The buyer-footer example is now one any shop could use ("Thank you for supporting a small shop") instead of the sample shop's own line.
* The closed-shop message box runs the full width of the card - it holds a 200-character sentence and was sized for a postcode.

= 3.1.9 =
* The Copy price shortcode button no longer offers itself on customer-set-price products - the shortcode has no price to show there, so the button could only copy an apology. It reappears the moment the Custom price tick is removed.

= 3.1.8 =
* The header version chip can name a companion add-on beside this plugin, through a small filter the add-on answers. Alone, it shows its own version, as before.

= 3.1.7 =
* The One-discount-per-order callout has its accent left border back - the colour variable it asked for was never defined, so the border quietly computed to nothing.
* New save-bar palette: the unsaved bar is a muted purple (#645986) and the saved confirmation a soft green (#72b47a).

= 3.1.6 =
* More breathing room beneath the amber warnings - the banner sat 4px above the content below it, and the in-zone notes were similarly cramped.

= 3.1.5 =
* When the shop was at its then one-zone limit, the Add zone button showed greyed with a one-line reason instead of vanishing - a missing button reads as a bug, a greyed one reads as an answer. (The limit itself was removed in 3.4.0: every zone sells.)
* The amber shipping warnings are shorter, and the way out - choose Flat rate or First item + each additional - is bold, since the long version was going unread.

= 3.1.4 =
* A new zone now arrives agreeing with itself. Add zone used to blindly deselect the options of the first select it found in the clone - the pricing-mode select - so the box displayed "Flat rate" while the cloned quantity panels stayed on show, and the two only reconciled on the next change. The clone now keeps its source's mode (a copy is a copy), clears only the countries list, and settles its panels the moment it is created.

= 3.1.3 =
* A freshly added zone now obeys its own "How postage is priced" picker immediately. The Add-zone clone restamped its field names but not its zone-number attributes, so choosing a mode on the new zone toggled the wrong zone's price panels and the right editor only appeared after a save. Display-only (what you saved was always correct), but an editor that ignores your choice until you save reads as broken. Duplicated element ids on cloned zones fixed by the same restamp.

= 3.1.2 =
* The header wordmark is larger - 62px tall on desktop (was 45px), scaling to 44px on narrow screens where the old size crowded the shop name off the row.

= 3.1.1 =
* New wordmark in the admin header; an add-on can swap its own badged logo in through a filter.

= 3.1.0 =
* Four more features moved out to a separate add-on, joined by hooks: multiple delivery zones (returned to this plugin in 3.4.0), customer-set prices (deposits, donations, invoices), Stripe-issued PDF invoices, and the automatic discounts (quantity tiers and order-value rows). The shop-wide sale, coupon codes with every option, the plugin's own free PDF receipt, stock, emails and the whole money loop stay right here.
* Everything an add-on owns degrades the same deliberate way when it is off: nothing is guessed, nothing is deleted, the shop keeps selling what the plugin can price, and each tab says in amber exactly what is inactive. Saving any tab with the add-on off preserves its every setting untouched.
* New: a Duplicate button on each of your own products. One recipe in three sizes is one product duplicated twice, not three typed from scratch. The copy is a new, unsaved product - nothing is stored until you press Save.
* A fresh shop starts with no automatic discount rules; the example zone and products are unchanged.

= 3.0.2 =
* The dormant-zone badge now reads "POSTAGE PAUSED" - the outcome, plainly, in the style of the OFF badge. The banner above it carries the why and the way out.

= 3.0.1 =
* The dormant-zone warning is now unmissable. The Shipping tab wears the plugin's own amber banner naming the zones whose stored pricing mode nothing present can price, each affected zone carries a badge on its header (visible even collapsed), and the note inside the zone is an amber panel that leads with the consequence.

= 3.0.0 =
* Postage priced by number of units and by basket value (the band modes) moved to a separate add-on, joined by hooks; the plugin itself prices postage flat or first-item-plus-extras, with collection and free-over unchanged.
* A zone saved with a band mode keeps every setting when the add-on that prices it is not active. It quotes no postage (collection still works), the Shipping tab says exactly why, and the prices return whole the moment the add-on is active again. Saving the tab never deletes a band table.
* A fresh shop's example zone now prices flat, so the free plugin's out-of-the-box shop is complete on its own.
* Nothing else moved: zones, collection, free postage over a spend or quantity, discounts, codes, stock, emails and the rest are unchanged.

= 2.6.5 =
* The header now has real vertical padding at every width. It previously relied on a minimum height to centre its content, which only worked while everything fit on one row; on tablets, landscape phones and narrow desktop windows the row wrapped and the logo sat flush against the top of the card.
* The phone logo is back to a sensible size (160px). The previous release made it larger than the desktop logo, which crowded a phone screen.

= 2.6.4 =
* The gap above the header is now reliable on every device. It was a collapsing top margin that disappeared depending on what WordPress rendered above it; it is now non-collapsing padding on the plugin wrapper, so the gap is always there.
* The logo is larger on tablet and phone widths, where it previously stayed at its small desktop size.

= 2.6.3 =
* The Save changes bar is now a deep indigo instead of white, so it holds the eye while there are unsaved changes rather than blending into the admin background. The saved confirmation deepens to a solid green to match its weight.

= 2.6.2 =
* Start here: choosing to place the basket yourself adds a fifth checklist step - Place the basket trigger on your pages - measured by the same scan as the settings warning, and setup does not complete until it is found (a paid order still completes setup regardless). In floating mode nothing changes.
* If the basket is switched to self-placed after setup is complete, the missing-trigger warning appears on Start here as well as Settings - one shared sentence for both.

= 2.6.1 =
* The basket settings card is reorganised: placement leads (it decides what the rest means), the card is now "Basket icon and placement", and the floating-only controls - Shape, Size, Position and the preview - dim and lock while "I'll place it myself" is chosen. Icon and colours stay live in both modes, because the shortcode wears them too. Saved values are never reset by saving in self-placed mode.

= 2.6.0 =
* New: choose where the floating basket sits - bottom right (default), bottom left, middle right or middle left. Bottom left suits sites whose chat widget owns the right corner; the middle positions give the side-tab pattern.

= 2.5.2 =
* The inline basket trigger keeps its colour through focus, hover and active states, where themes repaint buttons - it no longer changes colour after the basket closes and hands focus back to it.

= 2.5.1 =
* The inline basket trigger now wears the Button colour and hover colour from your settings, as the customiser promises; add color="inherit" to the shortcode to take the surrounding text colour instead.
* The Shape and Size sliders sit level.

= 2.5.0 =
* New: place the basket yourself. A [bangcart_cart size="28"] shortcode renders the basket trigger anywhere - headers, builder layouts, widgets - opening the same drawer, wearing your chosen icon and count bubble, and inheriting the surrounding text colour.
* New: Basket placement setting - keep the floating corner button (default, unchanged) or hide it and place the shortcode yourself. A gentle warning appears if the corner is off and no shortcode can be found on your pages.
* New: floating basket size slider (40-80px) with live preview; the icon and count bubble scale in proportion.

= 2.4.32 =
* Clear test order data is now enabled whenever it has anything to clear: emailed-order notes, the delivery log, an error note, or the Start here checklist - not only orders marked posted. The card's stored-notes count now includes emailed orders, matching its own description.

= 2.4.31 =
* Start here: the explanation and step notes now use the card's full width, matching every other tab's hint text.

= 2.4.30 =
* Clear test order data now also resets the Start here checklist, which re-measures from what remains: a site with only test history returns to the fresh-install experience; a live shop's evidence re-completes it immediately.

= 2.4.29 =
* New Start here tab: explains the plugin's model (your pages, our buttons, Stripe's checkout) and walks a four-step setup checklist measured from real state - key, product, a button found on your pages, webhook verified.
* Fresh installs land on Start here; the default becomes Orders once the checklist is green or a paid order exists.
* The Documentation button points at the Start here tab until a real docs site exists.

= 2.4.28 =
* Orders: each order now offers two controls - Mark posted, and a ... menu holding Resend confirmation, Receipt and Open in Stripe. The menu works without JavaScript.
* Orders: every row control wears the plugin's one utility button style; the three drifting one-off styles are deleted.
* The resend's confirm step now genuinely works without JavaScript.
* Admin: the "Set up webhook automatically" and "Create the page for me" buttons regain their intended size - their size rule was being silently overridden.

= 2.4.27 =
* Fixed: the resend confirmation was asking before you pressed it. Every order row showed "Resend confirmation" and "Send it again? Yes, resend / Cancel" at the same time. The confirmation is hidden until you press Resend, as intended.

= 2.4.26 =
* New: resend a customer's confirmation email from the Orders tab. It sits under their address, asks before it sends, and rebuilds the email from Stripe so it carries the current receipt. The customer's copy only - not your own notification.
* The Orders row now speaks one language: Mark posted, Receipt and Stripe all wear the plugin's own button styling instead of the grey outlines two of them had inherited. Posted goes quiet once an order is done.
* Fixed, properly this time: the admin ran off the side of the page on real installations. The layout was asking how wide the BROWSER WINDOW was, when what matters is how much room is left after WordPress's admin menu takes its 160 pixels - so every breakpoint fired late, and folding the menu (which changes the space without changing the window) could not be handled at all. The layout now measures the content itself. 2.4.25 fixed this only in a test that had no admin menu in it.
* The test suite renders the admin inside WordPress's own chrome, with the menu present, and checks every tab at every width from 1900 to 380 - and with the menu expanded, folded and hidden.

= 2.4.25 =
* Fixed: the admin ran off the side of the page on screens between roughly 1120 and 1250 pixels wide - a laptop at 1200, or a browser window that had been made a bit narrower. The Orders table needs about 960px for its eight columns and the "On this page" rail beside it takes 180 more, so between those widths there was room for neither; the rail now moves above the content at 1260px instead of 1100, which is what it already did on smaller screens. A 1280 laptop cleared the gap by 28 pixels, which is why it went unnoticed.
* The test suite now checks every admin tab at every width from 1900 down to 380 pixels, rather than at a handful of chosen widths. The bug above sat exactly between two of the widths that were being checked.

= 2.4.24 =
* New: download any order's receipt from the Orders tab. The last column is now "Open" and holds a Receipt button with the Stripe link beneath it. The file is named the same way the customer's copy is - "Amas-Rustic-Pantry-Receipt-20260717-OWMC3L4P.pdf" - and is built fresh from Stripe, so it always matches the order. Owner only.
* If your Order documents setting is "Stripe invoice", no Receipt button appears: Stripe raises and numbers that document, and a second one from the plugin would give the same order two different reference numbers. Use the Stripe link. With documents switched off entirely, you still get the download - your customers just do not.
* Fixed: the Orders table ran off the side of the page between roughly 910 and 953 pixels wide - a narrow band just above where it turns into cards, so it affected a resized window or a tablet in landscape. It now turns into cards at 1000px, before it can overflow.
* Fixed: admin buttons that are links no longer rely on WordPress's own stylesheet to remove their underline; the plugin says so itself.

= 2.4.23 =
* The receipt attached to a confirmation email is now named after your shop and the order - "Amas-Rustic-Pantry-Receipt-20260717-OWMC3L4P.pdf" instead of "bangcart-6AdCIC3X1w-Receipt-20260717-OWMC3L4P.pdf". A receipt is a document somebody files and looks for a year later; it should say whose shop it is and which order it is, and nothing else. The random part was only ever there to stop two receipts colliding in a temporary folder, and that guard now lives in the folder name where a customer never sees it.
* Accented shop names now carry across properly (Cafe rather than Caf). A name written in an alphabet with no Latin letters at all - Japanese, Greek, Arabic - gets "Receipt-<number>.pdf", because a mangled name is worse than none.

= 2.4.22 =
* The page previews now have a card of their own - "Preview your pages" - instead of sitting inside Checkout styling, and they appear in the "On this page" list like every other section. A preview is something you look at, not a setting, and it was sitting in the middle of the styling controls.

= 2.4.21 =
* New: preview buttons on the Settings tab, under Checkout styling. See the order confirmation, the order status page and the Mark as shipped page with your own colours and an example order made from your own products - without taking a payment. The confirmation opens your real thank-you page inside your theme; the other two open standalone, because that is how a customer meets them. Nothing is charged, nothing is saved, and a preview's Mark as shipped button is dead.
* Fixed: the Mark as shipped page ignored your styling. It had a fixed cream background and a white card, so a shop with a dark palette got a cream page with one button in its colours. It now uses your Panel background and Accent like every other page, and matches the order-status page's look.
* Fixed: the Mark as shipped page showed the raw Stripe session id ("Order cs_test_a1kb9mjjqvc...") instead of the order reference every other surface uses. It now shows the same number as the receipt, the emails and both order pages.
* The Mark as shipped page also says "collected" rather than "shipped" for a collection order, and its wording is now translatable.

= 2.4.20 =
* The order-status page is calmer: the card's drop shadow is gone, and the page behind it is now your Panel background exactly rather than a slightly deeper tint of it. The card is a hairline frame on a single flat page instead of a panel floating on a backdrop.
* The colour is still computed from your own Panel background, so a dark shop still gets a dark status page - it is only the tint that has gone, not the setting behind it.

= 2.4.19 =
* "Verify connection now" now shows a spinner while it waits. The check waits for Stripe to deliver a real signed event, which usually takes seconds but can take up to a minute and a half - and the box said "Checking the connection..." and then sat perfectly still, so it looked like it had hung when it was working normally.
* The box also answers faster: it now asks for the result about twice as often, so a connection that has already gone green stops sitting on the news.

= 2.4.18 =
* The seller notification email now names the order the same way everything else does. It still ended with the raw Stripe session id ("Order cs_test_a1kb9mjjqvc3RKhA0XRp8Zr7..."), while the customer's confirmation, the receipt attached to that same email, the thank-you page and the order-status page all showed the tidy reference ("Order 20260717-CLMVRSZY") - so a shop owner reading a customer's question could not match the two. 2.4.5 moved every surface onto one reference and missed this line.

= 2.4.17 =
* During a sale, the old struck-through price is now dimmed slightly so the new price reads first. Before, both were at full strength and competed with each other.
* It is dimmed rather than recoloured, so it works whatever your palette is, and it is written so that any rule of your own overrides it - you do not need !important, and the plugin will not fight you.

= 2.4.16 =
* The Products tab now says what the price shortcode can do. The copy buttons hand over the plain shortcode, as they should, but nothing anywhere told you the font, size, weight, colour, tag and class options existed - so they may as well not have. There is now a "Styling the price, and other options" fold on the tab listing every one with an example, plus the markup the shortcode renders, so you can write your own CSS against it.
* It also states the trap that would otherwise cost you an hour: anything set on the shortcode becomes an inline style and beats your stylesheet, so style a price on the shortcode or in your builder, not both.

= 2.4.15 =
* The price shortcode now takes font, size, weight and colour: `[bangcart_price id="jar" font="heading" size="1.6rem" weight="700" colour="accent"]`. Size accepts px, rem, em, pt or % - a bare number means pixels. Weight takes 100 to 900, or normal/bold. Font takes "heading" or "text" (your two Fonts zones from Checkout styling), or the name of any font your page already loads.
* Set nothing and it still inherits whatever it sits in, exactly as before.
* Note: a named font only renders if your theme or page builder is already loading it - the plugin asks for it, it cannot fetch it. The two zone names are always safe, because the plugin serves those itself.

= 2.4.14 =
* The price shortcode can now be given a look, for pages where the builder offers no styling of its own (Elementor's Shortcode widget, for one, styles only through its Pro-only Custom CSS field). `[bangcart_price id="jar" tag="h3"]` renders it as a heading, so your theme's own h3 styling arrives with it; `font="heading"` or `font="text"` uses one of your two Fonts zones from Checkout styling; `colour="accent"`, `colour="button"` or any hex sets the colour; `class="your-class"` adds your own class alongside `.bangcart-price`. Set none of them and it inherits whatever it sits in, exactly as before.
* Note: anything set on the shortcode becomes an inline style, and an inline style beats a stylesheet - so style a price in the shortcode OR in your builder, not both, or the builder's panel will appear to do nothing.
* Fixed: "Copy price shortcode" on the Products tab wore WordPress's stock blue instead of the plugin's own button styling, and stood 6px taller than the button beside it.

= 2.4.13 =
* New: a price shortcode - `[bangcart_price id="your-product-id"]` - so a product page shows the price from your Products tab instead of a figure typed into the page that goes stale the moment you change it. Each product now has a "Copy price shortcode" button beside its button shortcode.
* While a shop-wide sale is running it shows the old price struck through and the sale price beside it, because during a 20% sale the jar genuinely is the lower price - that is what the basket charges. Add `was="no"` to print just the current price. Quantity and order-value discounts deliberately do not appear: they come off the order, not off one jar, so a page cannot honestly promise them.
* Style it with `.bangcart-price`, `.bangcart-price-was` and `.bangcart-price-now`. The plugin adds no styling of its own, so it inherits whatever it is dropped into.
* A custom-price product has no price to show - the customer types the amount - so the shortcode says so to you, and shows customers nothing, rather than passing the minimum off as a price.
* If you run a page cache, purge it after changing a price: the cached page will show the old figure until you do. The amount charged is never affected - the basket always asks the server.

= 2.4.12 =
* The Products tab's copy button reads "Copy button shortcode" again. 2.4.11 shortened it to "Copy shortcode" because the old label described HTML as a shortcode - but now that it genuinely is one, the word "button" is the useful part: it says what the shortcode makes, and so what the thing you are pasting on your page will do.

= 2.4.11 =
* The Add to basket button is now a shortcode - `[bangcart_button id="your-product-id"]`. The Products tab's copy button was labelled "Copy Button Shortcode" but copied raw HTML, which needs a different block in the editor - so anyone following the instruction pasted it where WordPress printed it as text. Buttons you have already pasted as HTML still work exactly as before; nothing on your shop needs changing.
* Every shortcode the plugin offers is now presented the same way: a line saying what to do with it, the shortcode in a field you can read and select, and a Copy button. That covers the Add to basket button, the sale banner, and the thank-you page - the last of which previously had to be retyped by hand from a sentence.
* A shortcode with a product id your shop does not sell now tells you so, on the page, where only a logged-in administrator can see it. Customers see nothing rather than a button that fails when pressed.
* Fixed the sale-banner help text, which told every shop owner the blank message would read "2024ff everything" instead of "20% off everything".

= 2.4.10 =
* The thank-you page is now centred - the card and the writing inside it - instead of sitting to the left of a centred page heading. The item list keeps its two columns so the prices still line up.
* The logo is gone from the thank-you page. It is a shortcode on one of your own pages, so your theme's header is already showing your logo directly above it. The order-status page keeps its logo, because that page renders on its own with no theme around it.
* The greeting and the Continue shopping button now use the heading font you chose under Checkout styling, like the basket's own heading, rather than falling back to your theme's body font.
* The card's drop shadow is gone; it keeps its hairline border.
* "If it has not arrived within a few minutes, check your spam folder" and "Questions about your order?" each sit on their own line now.

= 2.4.9 =
* A discount code used on an order made only of custom-price items (a deposit, a balance, a booking fee) no longer says "Better discount applied". Custom-price amounts are never discounted, so there was nothing for the code to reduce and no better discount to compare it with - the basket now says "Not valid on this order", which is what actually happened. A code beaten by one of your own bigger discounts still says "Better discount applied", as before.
* The floating basket button now keeps the colour you chose whatever the theme around it does. Some themes style every button on the page, and because closing the basket deliberately returns focus to the floating button, a theme's focus colour could sit on it for as long as the page was open. This completes the 2.4.8 fix, which covered the buttons inside the basket but not the floating button itself.
* Fixed a PHP warning raised on every page of a shop selling a custom-price product saved without a price of its own - harmless in normal use, but with debug display switched on it printed into the cart's own markup.

= 2.4.8 =
* The basket's close, quantity and remove buttons now keep the shop's own colours whatever the theme around them does. Some themes style every button on the page, including ours - and because opening the basket moves focus to the close button (part of the keyboard support added in 2.4.6), a theme's focus colour could paint the close button the moment the basket opened. The basket's buttons now hold their own appearance in every state. Shoppers using a keyboard still get a clear focus ring, drawn in your accent colour; a shopper using a mouse sees no ring at all.

= 2.4.7 =
* Fixed a PDF receipt that did not add up on orders with a fixed (money-off) discount. A £-off discount is applied as a Stripe coupon, which reduces each line's price and reports the reduction separately; the receipt was listing the already-reduced goods AND subtracting the discount again, so its Subtotal minus Discount plus Shipping came out lower than the Total it showed - by exactly the discount. The receipt now lists the goods at full price with the discount as its own line, so every figure reconciles. Percentage discounts, the confirmation emails and the thank-you and status pages were already correct; only the receipt on money-off orders was affected. (Found in the plugin review.)

= 2.4.6 =
* The basket drawer is now a proper accessible dialog for keyboard and screen-reader shoppers. Opening it moves focus into the panel; the floating button announces whether the basket is open (aria-expanded) and that it controls a dialog; pressing Escape closes it; closing hands focus back to the floating button instead of dropping it at the top of the page; and while the basket is open, Tab is kept inside it rather than wandering to the page behind. This meets the modal-dialog expectations of WCAG 2.4.3 and 4.1.2.

= 2.4.5 =
Fixes from a full plugin review.
* One order now has one reference everywhere. The confirmation emails were still quoting the raw Stripe session id (like "cs_test_b1lPVe") while the thank-you page, order-status page and PDF receipt had all moved to the tidy "20260716-XXXXXXXX" number - so the receipt attached to an email could name a different reference than the email itself. The emails now use the same number as every other surface.
* A product deleted from your shop no longer lingers as a phantom line in a returning customer's basket. Before, a basket saved before you removed a product would show that line - priced - while the server left it out of the total, so the basket appeared to add up wrong. Such lines are now removed the moment the basket loads.
* A saved quantity larger than the stock now on hand is trimmed to what is left (or removed if the product has since sold out), instead of showing an impossible quantity and then blocking checkout with no visible reason.

= 2.4.4 =
* The order-status page's backdrop now follows the shop instead of a fixed cream: it is a slightly deeper tint of your Panel background, computed automatically - so a shop themed in any palette gets a matching page with no new setting to maintain. Every other colour on the thank-you and status pages already followed Checkout styling; this was the last hardcoded brand colour.

= 2.4.3 =
* The order-status page now shows a town-level delivery hint - "Delivering to Cambridge" - so a customer can reassure themselves you have roughly the right place, without a full home address sitting on a link that lives in an email and can be forwarded. It uses the town, or the outward part of a UK postcode (the "CB1" of "CB1 1AA") when there is no town, or the country, and shows nothing when none of those is safe to reveal. This follows how the major platforms treat an order page opened without logging in - full name and address only behind a login, which this plugin does not have.

= 2.4.2 =
* The order-status page (the "Check order status" link in confirmation emails) now wears the shop too: logo masthead, the card in your basket-panel colour, the items and total in your totals-box colour, the status heading in your accent, and a "Visit the shop" button. It also now names the order the way the receipt and thank-you page do - "Order 20260716-XXXXXXXX" instead of a raw cs_ session id - so one order has one name everywhere a customer meets it, and support conversations quote one reference.
* Deliberately unlike the thank-you page, it still shows no customer name and no address: that link lives in an email indefinitely, so it only reveals what a stale or forwarded link may safely reveal - status, items, total, delivery method.
* When Stripe cannot be reached, the page keeps its calm wording and falls back to the short session reference rather than showing nothing.

= 2.4.1 =
* The thank-you page is dressed as the shop: your logo at the top (the one from the Emails tab, with the shop name in your accent as the fallback), the order in a card using your basket-panel colour, the items in your totals-box colour, the order reference as a quiet pill with the order date beside it, and the delivery heading in your accent.
* New on the page: how it was paid - "Paid by Visa ending 4242" - which is what a customer glances for when checking the right card took the charge. A pending bank payment says "Paying by bank transfer" and its explanation now sits in a soft amber note rather than plain text.
* .bangcart-thanks joined the CSS-classes list under Checkout styling, for anyone styling the page beyond its defaults.

= 2.4.0 =
* New: a thank-you page. Until now a paying customer was returned to the home page with no acknowledgement at all. Put the [bangcart_thank_you] shortcode on any page - or press "Create the page for me" on the Settings tab - pick the page, and customers land there after paying. It greets them by first name and shows what they ordered, the postage service and delivery address (or the collection note), the order reference (the same number the PDF receipt prints), where the confirmation email is going with a check-spam nudge, a questions line with your shop email, and a Continue shopping button in your colours. Everything is fetched fresh from Stripe by the session in the address, so the page says what was actually paid.
* The details carry guardrails: they render only for a real, completed payment and only for 24 hours after it - after that the same link shows a warm generic thank-you with nothing personal. The page is never cached and never indexed while it carries an order. A delayed bank payment (such as Bacs) reads as "placed and your items are reserved", never falsely "confirmed", and names when the email will come.
* Shops that set no page keep today's behaviour exactly, and the basket still empties on return either way.

= 2.3.2 =
* Spacing in the Floating basket card is designed, not inherited: a section-opening hint now leaves proper air before the controls that follow it (a new lead-hint style, also applied to the Fonts note), the "Match the checkout buttons" tick gets clear space beneath its colour picker as a general rule rather than a local patch, and the icon and shape columns stop stretching on wide screens, so the colour pickers sit with their row instead of drifting to the far edge.

= 2.3.1 =
* The floating basket's follow-the-buttons checkbox moved up under the Button colour picker it controls, reworded to "Match the checkout buttons" - the same pattern as the closed bar's "Match the shop" ticks. Its old wording ("follows your other buttons") and position (orphaned below the whole row) both dated from when the section lived inside Checkout styling, where the buttons it meant were one row above.

= 2.3.0 =
* The Settings tab is reorganised so each thing has one home and the page tells five short stories instead of one long one. "Shop basics" holds the name and currency together; "Shop status" shows just the open/closed switch, with everything about the closed state (message, bar colours, preview) folded behind one line - it opens itself while the shop is closed; "Checkout styling" covers the checkout journey; the floating basket gets a card of its own with its live preview; and stock messages close the page. The "On this page" list follows the new order.
* New: the floating basket has a shape. A corner-radius slider runs from 0 (square) through rounded square to 28 (the full circle it has always been), previewed live and applied on the storefront. Existing shops keep the circle until they move the slider.
* Helper text is tiered: the sentence that prevents a mistake stays visible (such as "Set Stripe to the same currency"); the longer explanations move behind small "More about..." disclosures rather than being deleted. The custom-font instructions now live under Fonts, where they belong, not under Button labels.

= 2.2.7 =
* The "Last webhook received" line now updates live during a connection check, in the same breath as the status box and the header chip, instead of waiting for a page load. It is built by one server-side helper used by both the page and the poll, so the two can never drift apart - and when the very first webhook a site ever receives arrives mid-check, the line appears by itself.

= 2.2.6 =
* Fixed: after the wrong-secret recovery, "Last webhook received" went on showing the old signature failure beneath a green "Connected and verified" box. Two causes, both addressed: a verified event that needs no fulfilment (the connection check's customer.created) now updates the last-webhook record like any other arrival, and a record from before the key or secret last changed is labelled as history - "before the key or secret last changed" - rather than reading like a current problem.

= 2.2.5 =
* The webhook section is designed rather than accumulated. The intro is two sentences instead of a paragraph; "Set up webhook automatically" wears the plugin's own accent (it was WordPress's default blue leaking through - and red was considered and rejected, because red in this design means error, never "important") with proper room around it; and the whole manual path - endpoint URL, instructions, signing secret - folds away behind "Prefer to set it up by hand?", so the screen tells one set-up story unless you ask for the other.

= 2.2.4 =
* The header chip now updates in the same breath as the status box while a connection check is in flight - when the box turns green mid-poll, the chip turns green with it, rather than waiting for the next page load. The chip's words travel in the same payload as the box's, from the same state machine.

= 2.2.3 =
* Fixed: the "Connect the webhook" link in the admin-wide warning still pointed at the Emails tab - the webhook's home before 2.1.0 - so the link led away from the setting it named. It now opens the Stripe tab. The site-address-changed warning's "re-check the connection" link had the same stale target and got the same fix.
* Admin notices now sit evenly between the header and the content. The old spacing floated them 38px below the header and 4px above the content - top-heavy and cramped at once.

= 2.2.2 =
* The "Stripe connected" chip in the admin header now tells the same truth as the Stripe tab's status box - both read one state machine, so the header can never say "connected" while the box says "Not connected" on the same screen. The chip has three honest states: green connected (a real event verified), amber "almost there", red not connected - and hovering an amber or red chip shows the full reason.
* Fixed: with the plugin's own endpoint in place but its saved secret proven wrong by a real event, "Set up webhook automatically" reported the webhook "already healthy" and kept the bad secret - a dead end wearing a success notice. It now replaces its own endpoint and captures a fresh secret, exactly as the red status promises. A secret that has verified a real event is never replaced merely because a stale twin endpoint is also sending events.
* The "Stripe says:" prefix no longer wraps the plugin's own directions - it only ever belonged on a genuine Stripe error, which already carries it.

= 2.2.1 =
* The Stripe tab now saves the way every other tab does: the floating "Unsaved changes" bar appears the moment a key, secret or the Test/Live toggle changes, wherever the page is scrolled, with Save and Discard right there. The old "Save and check connection" button - low on the page and easy to miss - is gone; the floating bar's Save still runs the connection check in the same action. Leaving the page with unsaved keys now warns first, which the old layout never did.
* "Verify connection now" and "Re-check only" remain as action buttons by the status box - they run checks rather than save settings, so they do not belong in a save bar.
* Fixed: pressing "Verify connection now" with a freshly pasted signing secret now saves that secret first, exactly as Re-check does - before, it would have tested the old secret and answered a question you were no longer asking.

= 2.2.0 =
* New: "Set up webhook automatically". One button creates the webhook endpoint in your Stripe account through the API and captures its signing secret straight from Stripe's reply, so there is nothing to copy and a mispasted secret becomes impossible. The plugin only ever touches an endpoint it created itself - one made in the Stripe dashboard is never edited, disabled or deleted; if one already points at the site, the button explains the two ways forward instead of creating a twin that would deliver every event twice. Manual setup stays available below it.
* New: "Verify connection now". Stripe has no "send a test event" API, but it sends a real signed event whenever anything happens in the account - so the button makes the smallest possible something happen: it creates a clearly-labelled temporary customer record and deletes it again at once (free, no payment, works in live mode too). The status box watches for the event and updates by itself: green when the signature verifies, red naming the exact cause when it fails or never arrives. No more waiting for a real sale to find out.
* A check that times out is diagnosed, not shrugged at: the plugin asks Stripe's events API what became of the very event it triggered, and says which side failed - "Stripe is still trying to deliver it and this site is not accepting it" (URL, host or blocked /wp-json/) or "Stripe reports it delivered but this plugin never saw it" (something else answering the webhook URL).
* Check connection now catches near-miss endpoint URLs - https vs http, www vs bare, a trailing slash - and names the exact difference, instead of reporting that no webhook exists. These differences are invisible to the eye and made every delivery silently miss.
* Check connection warns when Stripe reports recent payment events that were not successfully delivered to a webhook - the "orders are being missed right now" signal - and points at Verify connection now to pin down why.
* If an event arrives signed with a different secret AFTER the connection has been verified, the box stays green and explains the real cause - almost always a stale second webhook destination left in Stripe - instead of wrongly declaring the saved secret wrong.
* The amber "Almost there" state is no longer passive. Every message now says the one next thing to press, and the moment of genuine uncertainty (endpoint fine, secret saved, no event yet) ends with a button press instead of a wait.

= 2.1.0 =
* The floating basket button can be styled on the Settings tab: choose its icon (cart, bag or basket, each outline or filled), give it its own colour or let it follow your other buttons, and set the colour of the item-count badge. The badge number switches between dark and white on its own so it stays readable on any colour. Existing shops keep the outline cart in the button colour until they change it.
* A shop with no name set now shows "The Online Store" rather than the plugin's own name, so a forgotten name no longer reads as "Bang! Cart for Stripe" in order emails.
* The shop name in the admin header is now a link, with a pencil icon, that jumps straight to the name field and highlights it.
* Fixed: a newly added quantity-discount row had no "Applies to" product picker until the tab was saved, because the picker was copied from an existing row and a shop adding its first rule had none to copy. The picker is now authored once and cloned, so it appears the instant a row is added.
* New: the plugin can attach its own PDF receipt to the order emails, with no Stripe invoicing fee. A single "Invoice / receipt document" picker on the Emails tab chooses None, Stripe's invoice (which carries Stripe's per-order fee), or the plugin's free receipt - so a customer never gets two documents. The receipt uses your email logo (with the shop name as a text fallback) and a new Business address setting, and its number is built from the order, so a refund is traced through your Orders tab rather than Stripe's numbering. The document is generated with no bundled library. The picker shows only the guidance for the option you've chosen, rather than all three at once.
* New: test and live Stripe keys are both stored, with a Test/Live switch on the Stripe tab. Each mode keeps its own key and its own webhook signing secret, so you can test and then go live without ever re-entering a key. Your existing single key migrates to the correct side automatically, and each box is validated so a live slot can never hold a test key by mistake.
* New: the active mode is shown everywhere - a Test/Live pill in the admin header on every tab, and a line above the Orders list stating whether the orders shown come from your test or live Stripe account.
* New: a warning appears if the site's address changes after the webhook was set up (moving domain, or a staging copy of a live site). Stripe still calls the old address, so nothing is emailed or decremented until the endpoint URL is updated and the connection re-checked - a state the ordinary "webhook not connected" warning cannot detect on a cloned database.
* Fixed: the receipt logo now resolves by its path under the uploads folder, so a stored logo URL that still carries an old domain (after a site move) still finds the file rather than failing.
* The whole Stripe connection now lives in one place on the Stripe tab: mode toggle, test and live keys, the webhook endpoint URL, the signing secret and Check connection - the webhook setup moved here from the Emails tab. A plain-language status box says whether you're connected and, if not, exactly why - for example "your live key is saved, but there is no live webhook signing secret" - instead of a bare "not connected". The webhook secret is filed under the mode its box was showing, so flipping the toggle can never misfile it.
* The connection status can no longer show a stale or dishonest "connected". Green, "Connected and verified", now appears only when a real Stripe event has reached the site and its signature checked out against the exact key, secret and mode in use - the one thing that proves the secret is right. It is the signature that proves it, not a completed sale, so you do not need a real order to go green: Stripe's own "Send test webhook" button on the endpoint fires a signed event that turns the box green if the secret matches, without taking a payment. A passing Check connection, which only confirms the endpoint exists and a secret is saved, shows amber "Almost there" instead, because Stripe never reveals a webhook secret and so a check cannot prove the secret is correct. Every check and verification is fingerprinted against the key, secret and mode it ran against, so editing any of them drops the status back and no earlier pass can vouch for a changed config. A wrong secret turns the box red the moment a real event fails its signature check, and the reason is stated plainly. Saving also runs the check in the same action.
* Test and live webhooks are now told apart properly. Both kinds of Stripe event arrive at the same address, so an incoming webhook is checked against each saved signing secret in turn; the one that matches proves the event is real and shows which mode it belongs to, whatever the Test/Live toggle happens to be set to. A test payment can no longer be judged against the live secret (or the reverse) and wrongly reported as a signature failure, and an event only ever proves its own mode's connection - so a successful test webhook never turns the live status green. Only the mode the shop is actually selling in fulfils orders: an event for the other mode is acknowledged and its connection recorded, but it sends no email and touches no stock, so a test payment can never affect a live shop's inventory. Each mode keeps its own webhook history, so the live status never shows a webhook that arrived in test.
* The Test/Live control is now a labelled toggle rather than radio buttons.

= 2.0.0 =
* Quantity discounts can stop at a quantity, not only start at one. Leave "Up to" empty for the old behaviour.
* Quantity discounts can apply to chosen products instead of all of them.
* Fixed: a quantity discount gave away more than it said. £1 off three jars charged £1.02 off, because the line was rounded down to a whole penny per jar. Lines are priced exactly now and expressed to Stripe as whole pence, so the discount is the figure you typed.
* Fixed: the basket's Subtotal showed the price after discounts and then listed the discounts below it, so the rows did not add up to the total.
* Fixed: the basket worked out its own discount rows in the browser instead of using the shop's figures, and the two had drifted - a custom-price line could show a discount it never received.
* Saving now confirms itself on the save bar instead of pushing the page down with a notice.
* The admin works on a phone: tables become cards, the header fits, and every tab is reachable without a swipe.
* Translation template updated.
