=== smartEngin buy - full changelog ===

The plugin page on wordpress.org shows only the current release. Everything below
is the complete history of this code base, including the versions that were only
ever distributed from smartengin.de before the plugin joined the directory.

== Changelog ==

= 1.6.45 =
* The customer account is clearer for purchases made through CopeCart: an instalment purchase no longer appears as a subscription, a cancelled subscription no longer repeats the cancellation instructions, and the Invoices section says that CopeCart issues the invoices for those purchases.

= 1.6.44 =
* Maintenance release: shared code with smartEngin Licence & buy, where the message for an expired licence key now says “device” instead of “website” when a desktop app asks. Nothing changes for the downloads sold with this plugin.

= 1.6.43 =
* Maintenance release: shared code with smartEngin Licence & buy, where licence keys now show “Expired” once their end date has passed. Nothing changes for the downloads sold with this plugin.

= 1.6.42 =
* Fixed: after returning from a Stripe page — the subscription checkout, or a redirect method such as Klarna or Amazon Pay — the confirmation took the first slot of the card grid as if it were one more offer. Every card moved one column to the right and the last one dropped into a second row. The confirmation now sits as its own panel above the cards, and the cards stay in their row.
* Unchanged on purpose: a purchase completed inside the card still shows its confirmation there, and the single payment box used by add-ons (one column) looks exactly as before.

= 1.6.41 =
* Fixed: the “your subscription renews soon” e-mail named the wrong day and came too late — for Stripe, PayPal and CopeCart alike. A subscription licence deliberately runs two days past the charge, so it never lapses before the renewal message arrives; the reminder took that later date as the charge date. It therefore named a day two days after the real charge, and with the default of 3 days it arrived only one day before the charge. Both now count from the charge itself.
* Unchanged on purpose: the licence keeps its two days of grace, and the reminders for a cancelled subscription or a fixed-term licence still name the day access ends. A reminder that already went out for the current period is not sent a second time after the update.

= 1.6.40 =
* The CopeCart note now says HOW to cancel, not just where. Pierre went looking after 1.6.39 and found a contract-withdrawal request instead — which is a different thing entirely: a request that gets reviewed, not a cancellation. CopeCart does have a self-service cancellation for buyers; the purchase e-mail, the renewal reminder and the customer account now name it, together with the buyer's own order ID, and add the step people miss: CopeCart sends a confirmation e-mail afterwards, and the cancellation only takes effect once that link is clicked.
* Taken from CopeCart's own help article and the page itself, both read on 23 September 2026 — not assumed.

= 1.6.39 =
* Fixed: the purchase e-mail for a CopeCart subscription offered “Manage or cancel your subscription any time here” with a link that could never work. Clicking it ended on “We could not find a subscription for this link.” That door only ever led to Stripe’s portal, and the branch that built it caught everything that was not PayPal — CopeCart included. CopeCart subscriptions now get the same note the customer account already showed: they are managed at CopeCart, using the order confirmation from there.
* Fixed the same thing in the renewal reminder e-mail.
* Fixed: the “manage my subscription” form sent a magic link to PayPal and CopeCart customers too, and that link always failed on click. It now only goes out when there really is a Stripe subscription behind the address. The answer on screen stays the same neutral message either way, so it still does not reveal who has a subscription.
* Stripe and PayPal are unchanged — that is checked explicitly: the Stripe purchase e-mail must still carry the portal link, the PayPal one must still carry the PayPal instructions, and a Stripe customer must still receive the magic link.

= 1.6.38 =
* Fixed, and this was the root cause behind the previous release: CopeCart sends the renewal date as a plain day without a time (“2026-09-23”), and on the day of purchase that day is TODAY. Read as midnight it looks like the past, so the date was discarded and the licence got no end date. A day named without a time is now taken to mean the whole day.
* New: if CopeCart sends no usable date at all, the length is taken from CopeCart’s own billing interval (“frequency”) — daily, weekly, monthly, half-yearly, yearly. That is more accurate than the purchase option, where a daily subscription has to be entered as “monthly” for lack of a finer choice. The purchase option remains the last resort.
* Fixed: a CopeCart subscription licence ended at the exact second of the next charge, while the message about that charge was still on its way. It now gets the same two days of grace that Stripe and PayPal subscriptions have always had — on the first purchase and on every renewal. A subscription licence no longer ends abruptly; it is extended automatically with every payment received.
* The help text under “Licence validity (days)” said the field is ignored for subscriptions. That was never true for CopeCart, where it is the last fallback. Corrected.

= 1.6.37 =
* Fixed: a CopeCart subscription could produce a licence with no end date when no readable renewal date arrived. The length is now derived from the billing interval (monthly → 31 days, yearly → 366); one-off purchases stay unlimited.
* New: the last message received from CopeCart is shown for diagnosis — field names and technical values only, never buyer data.

= 1.6.36 =
* Fixed: CopeCart sends its subscription plan as “abonement” with ONE n, while its specification writes two. Every subscription payment was therefore logged and skipped — no order, no access. Both spellings are now accepted.

= 1.6.35 =
* CopeCart refunds and chargebacks are now acted on: a full refund marks order and licence as refunded and withdraws access; a partial refund deliberately takes nothing away.
* No credit note is written — CopeCart issued the invoice and cancels it itself.

= 1.6.34 =
* Fixed: when a later CopeCart payment arrived without a readable date, access stayed expired although the customer had paid. Such a payment now falls back to the purchase option’s own length, counted from today.

= 1.6.33 =
* Fixed: a cancelled CopeCart subscription did not record its end date on the order, so the shop still treated it as auto-renewing and the customer account said “renews automatically” instead of “cancels on …”.

= 1.6.32 =
* A failed recurring charge from CopeCart is now passed on like one from Stripe or PayPal, so an add-on can start its usual grace period instead of access ending on the last paid day.
* Help & documentation now explains subscriptions and instalments through CopeCart, including the “Licence validity (days)” fallback that is easy to miss.

= 1.6.31 =
* CopeCart can now sell subscriptions and instalments. The first payment delivers immediately; each further instalment adds to the same order, so the list shows what has actually been received.
* A renewal extends the access period; a cancellation takes effect at the end of the paid period. If CopeCart’s date cannot be read, the purchase option’s own length applies instead of a guess.
* Refunds and chargebacks from CopeCart are still only logged — that is the next stage.

= 1.6.30 =
* The offer card now shows ONE “Buy now” button; Card/SEPA and PayPal appear after the click, beside the e-mail and address fields. Before, both buttons stood on the card and again under the fields. No extra click — the first click always only opened the fields.
* “Button: Buy at CopeCart” is now “Button: Buy now” and colours that first button on both routes.

= 1.6.29 =
* “Square corners everywhere” now really takes every rounding off the offer area — card, badge and check marks included, not just the buttons.
* The thank-you box has its own shadow switch for a flat look.
* With “CopeCart: button only” the redirect notice is no longer printed, because you write the surrounding text yourself there.

= 1.6.28 =
* Card design now covers the CopeCart button too (colour and icon), plus three new switches: square buttons, no card shadow, and “CopeCart: button only” — which drops the card so only the buy button appears, for pages where you write the description yourself.
* Fixed: an embedded purchase box never received the card design’s corner radius, shadow or font sizes.
* Documented what a buyer sees for a CopeCart purchase option, in Help & documentation and in the built-in manual.

= 1.6.27 =
* New: a thank-you page for buyers coming back from a reseller such as CopeCart. The new “smartEngin Thank-you box” block (shortcode [sels_thanks]) greets them by name, shows order number and amount, prints the note CopeCart requires, and offers the field that mails a secure link to their purchases. Wording, colour and square corners are all yours.
* The box never hands out the file itself — what a reseller appends to the address is not signed, so anyone could type it. The file travels by e-mail. The box also wipes the name and e-mail out of the address bar afterwards, and the language switch no longer carries them into its links.

= 1.6.26 =
* Fixed: the payment check reported every clean CopeCart order as “Paid, but no invoice” and offered to create one — for a CopeCart sale the missing invoice is correct, and that button promised the one thing that must never happen.
* The revenue chart now shows test purchases as a thin dashed grey line, so you can see that a test arrived without it counting as revenue. The green line still shows real sales only.

= 1.6.25 =
* The log now tells two very different CopeCart problems apart: “no_signature” (nothing signed the notification) and “bad_signature” (signed with a different secret). Until now both looked the same.
* Corrected setup instructions: CopeCart creates the IPN connection in the account first and only then switches it on at the product — if that second step is missing, no notification is ever sent. And the shared secret simply has to be identical on both sides, whichever side you start on.

= 1.6.24 =
* The CopeCart check on a purchase option now also confirms success: a green “Address and product ID belong together”, and an explicit note when the address cannot be read at all. Silence used to mean both “all good” and “could not check”.

= 1.6.23 =
* The shop now checks the CopeCart order address against the CopeCart product ID and warns when the two do not belong together — otherwise a typo means the buyer pays for one product at CopeCart while this purchase option delivers another. No request to CopeCart is needed: CopeCart states that the product ID is the eight-character combination inside the product URL. Where to find that ID is now explained in Help & docs.

= 1.6.22 =
* Saving a purchase option now stays in the purchase-option form instead of jumping back to the product — otherwise a warning shown on save was gone in the same second. The button beside Save is now called “Back”.

= 1.6.21 =
* Safeguard against the CopeCart mistake that costs money silently: a purchase option that sends buyers to CopeCart without a CopeCart product ID (or two options sharing one) delivers nothing, and nobody notices — CopeCart's own confirmation did arrive. The form now warns while you type, and a notice in the admin area lists the affected options.
* A CopeCart payment that still could not be assigned is reported in the admin area with the product ID CopeCart sent, instead of sitting in the log only. Correct it and have CopeCart deliver that notification again — it then works.

= 1.6.20 =
* A CopeCart sale now becomes a real order: on a completed one-off payment the shop creates the order, issues the licence key or releases the download, unlocks a course through its add-on — and sends its own purchase e-mail (on by default, switchable off; it is the only place a buyer sees their key or link, and it names no invoice of this shop).
* Still no invoice of this shop for a CopeCart sale — CopeCart is the reseller and handles invoice, VAT and dunning. The order list now says "at CopeCart" instead of a dash and shows what CopeCart actually pays out under the amount the buyer paid.
* The CopeCart product ID on a purchase option is what leads a notification back to the right option. Missing, or used twice, nothing is delivered on purpose and the log says why. A notification can now take effect only once, even if CopeCart delivers it twice in the same second.
* Instalments, subscriptions, refunds and chargebacks are verified and logged but not yet acted on.

= 1.6.19 =
* CopeCart can now be switched on per purchase option: each option gets the order page at CopeCart and the CopeCart product ID. As soon as an address stands there, that option shows a button leading to CopeCart instead of the payment buttons, while every other option keeps selling through Stripe or PayPal. The button is a plain link — no JavaScript needed — and emptying the field brings the option straight back.

= 1.6.18 =
* Help & docs now explains CopeCart: a new section walks through the vendor account, the notification address, the events to subscribe to, the shared secret, and the free test order that checks the whole chain without spending money. It also states plainly what a CopeCart sale does not do here — no invoice of this shop (CopeCart is the seller of record and handles VAT and dunning) and no cancellation from the customer account. `SELS_COPECART_SECRET` can now hold the secret in wp-config.php.

= 1.6.17 =
* CopeCart can now be connected as an external sales channel. First step: the shop accepts and verifies CopeCart's payment notifications (IPN). Settings → Payment providers has a new section with the notification address to enter at CopeCart and the shared secret; every notification is checked against that secret over the untouched request body and rejected before a single field is read if it does not match. Not yet in this version: no order is created and nothing is unlocked — that is the next step. Nothing changes for Stripe and PayPal.

= 1.6.16 =
* The purchase box now shows that something is happening: a spinner covers it while the payment runs (after "Pay now" there can be several seconds of silence while the card is checked), and it swallows further clicks so nobody pays twice.
* One step less in an embedded purchase box: a box that stands on its own shows the e-mail and address fields straight away, so the payment method is chosen once. In a list of offer cards the fields still open on the first click.
* New for add-on developers: `data-sels-stage` (choose | address | pay | busy | done) and a bubbling `sels:stage` event tell an add-on which stage the box is in.

= 1.6.15 =
* A test purchase can no longer be mistaken for real turnover. Every order now records whether it was made in test or in live mode — until now a purchase made with Stripe in test mode or PayPal in the sandbox looked exactly like a real one. The orders list tags them, a filter shows test purchases, real ones and the ones not classified yet, and each row can be marked by hand (the only way to sort out older purchases).
* New tick box under Settings → General: keep test purchases out of the revenue chart and out of the VAT summary. It only affects purchases known to be tests, so no figure changes behind your back when you update, and the VAT summary says how many documents it left out.

= 1.6.14 =
* An add-on can now put its own reference on the invoice: the description line goes through the `sels_item_label` filter together with the order it belongs to (smartEngin MeetMix writes the appointment's booking number next to the name). The label shown at Stripe and PayPal deliberately does not carry the order — it is stored per purchase option and reused for the next buyer. Without an add-on the description stays "Product – Purchase option" exactly as before.

= 1.6.13 =
* Add-ons that hold something for the length of a purchase — a booked appointment, for example — can now stop a payment before it goes through. Until now a buyer who took too long at PayPal could lose the held slot while the payment went through anyway. A PayPal payment is only collected after the add-on has been asked whether the purchase is still wanted, and an open Stripe payment can be called off. Nothing changes for plugins and files sold directly. New seams for add-ons: the filter `sels_may_capture`, the method `SELS_Checkout::abort_payment()` and the action `sels_order_failed`.

= 1.6.12 =
* The buyer's e-mail address can be kept off the invoice — a new tick box under Settings → Invoicing. It stays switched on, so nothing changes until you switch it off. On invoices below the address threshold, where no billing address is on file, the address is kept so "Billed to" is not empty.

= 1.6.11 =
* The sentence about the right of withdrawal at checkout can now carry a link to the page that explains it. Put the word to be linked in square brackets in the wording and enter the address underneath — no HTML anywhere. The link opens in a new tab and does not tick the consent box.

= 1.6.10 =
* The plugin speaks German again when your site is set to German. Its translation files were left out of the package on 16.08.2026 because a plugin in the directory gets its translations from translate.wordpress.org — but the strings were never entered there, so German stood at 0 % and no language pack was ever built. The result: English screens although all 1356 strings had long been translated. The package now ships the German files itself again, filtered down to the strings this edition actually uses. As soon as a language pack exists on wordpress.org it takes precedence automatically, and the bundled files can come out again. Other languages are welcome at translate.wordpress.org.

= 1.6.9 =
* Purchase box, customer account, subscription portal and partner pages now keep their shape on themes that style every button, link and heading of a page. Themes and page builders such as Astra with Elementor write global rules; those outweighed the plugin's own, and the buy box arrived with oversized buttons, lost rounding, foreign colours and text blurred behind a text shadow. Measured against the CSS of a real Astra/Elementor site: the box used to grow 64 % in height with 30 of its 36 elements altered — now none of the plugin's own layout is overridden and the remaining 8 % come from the website's font. Nothing in the markup changed: same classes, same data attributes, same scripts, so add-ons that embed the checkout box keep working unchanged.
* The purchase box now uses the font of the website it sits on, the way the customer account and the partner forms already did. It used to ask for its own font, which other websites do not have installed: the browser picked a substitute while the card's heading alone kept the website's font.

= 1.6.8 =
* Maintenance release of the shared code base. Nothing changes in this edition: the fix concerns the message panel of the partner programme, which is not part of smartEngin buy.

= 1.6.7 =
* Works with a strict Content Security Policy. The plugin no longer writes any script code into the pages your customers see: the checkout box, the customer account, the subscription portal and the print button on invoices now read their settings from data attributes on the page and run from their own script files. A policy without 'unsafe-inline' for scripts no longer blocks them — before, the buy buttons, the magic-link request and the portal simply did nothing under such a policy, and the print button was dead.
* Pressing Enter in the e-mail field of the customer account or the subscription portal can no longer put the e-mail address into the browser's address bar, even when scripts are blocked.
* Nothing else changed: same REST endpoints, same security stamps (nonces), same texts. Add-ons that embed the checkout box keep working without changes.

= 1.6.6 =
* The read-only AI connection now closes itself completely. The connection code always expired on its own, but the "Allow the AI connection" tick stayed on, which reads as "open" and left the endpoints answering requests for no reason. The whole connection now switches itself off: the moment the code expires, and 24 hours after being switched on (or the code revoked) with no code in place. The screen says when and why. Nothing to remember, nothing to switch off by hand.
* Fixed: every call to that connection was written to its access log twice, so the list showed thirty rows for fifteen visits. WordPress asks a permission callback a second time after answering, only to build the Allow: header, and the plugin counted that as a second visit. The same doubling made a wrong code count twice, locking a caller out after five wrong attempts instead of the ten documented. The answer is now worked out once per request. Nothing about who is let in has changed.
* Fixed: after paying with a redirect payment method the buyer came back to the wrong page. The return address was built from the path only, so anything after the question mark was dropped. It now keeps the query string, minus this plugin's own return markers. The order itself was never at risk — it is finalized by the payment_intent.succeeded webhook either way.
* Fixed: the box shown after such a return did not carry an add-on's success address, so the wrong confirmation could be shown. It now inherits it from the checkout box on the page; the same applies to the return from a subscription checkout.
* Klarna, Amazon Pay, Bancontact, eps and every other Stripe payment method that redirects the buyer now work for one-off purchases. Stripe refuses to confirm such a payment without a return address and none was given, so those buyers only ever saw an English error message. The shop now hands Stripe a return address (built on the server, checked against your own domain) and finishes the purchase when the buyer returns; if they close the browser instead, the payment_intent.succeeded webhook completes the order as before. Nothing to set up. Card payments and subscriptions are unchanged.
* The payment markers Stripe appends on the way back, the payment secret among them, are removed from the address bar right away.
* Fixed, same root cause as the previous entry in a second place: the duplicate-request key for creating a recurring price did not include the gateway mode, so the first live subscription after a switch from test mode failed. The mode is now part of the key; the protection against double-clicks is unchanged.
* Fixed: no subscription could be bought after switching a payment gateway from test/sandbox to live. The cached recurring Stripe price and PayPal billing plan did not record which mode they were created in, so the test object stayed cached after the switch and was then sent with the live key, and every subscription attempt failed at the last step. The mode is now part of the cache signature. One-off purchases were never affected.
* The invoice CSV export now applies the same spreadsheet formula protection as the other exports to its text columns: buyer-typed values (name, address, e-mail) starting with = + - @ get a leading apostrophe, so Excel/LibreOffice can no longer run them as formulas. Number columns keep their minus and stay summable.
* The credit-note e-mail now reassures the customer about the money: unless agreed otherwise, the refund was initiated through the payment method used for the purchase, and depending on the bank or payment provider it can take a few days to arrive. (If you have written your own credit-note template, add the sentence there yourself — your text always wins over the built-in one.)
* Security hardening (wordpress.org review round of 26 Aug 2026). Every value the plugin prints is now escaped at the very point of output; plugin-built HTML fragments pass through wp_kses() with an explicit allow-list (sels_allowed_html, extendable by add-ons via the filter of the same name).
* The DE/EN language switcher only acts on links carrying its own security nonce. A plain ?sels_lang= link from a foreign page no longer changes anything — neither the cookie nor a logged-in member's saved language (CSRF fix). The switcher's own links carry the nonce automatically.
* Database queries taking a list of ids now bind every id through $wpdb->prepare() placeholders.
* File exports (CSV, JSON backup, Markdown guide) are written to the output stream instead of being echoed; the CSV keeps its spreadsheet formula protection and its bytes are unchanged.
* The price shortcode is called [sels_preis], with [sels_price] as its English alias. Both names carry the plugin's four-character prefix, so no other plugin can claim them. The backend screen "Prices in texts" shows the ready-made line for every purchase option to copy.
* File storage: a code path that copied a deliverable from disk instead of receiving it as an upload has been removed; it was unreachable, as every call site passes a real upload. Deliverables are stored through WordPress' own uploader (wp_handle_upload) into a protected folder inside the uploads directory, resolved at runtime with wp_upload_dir() and closed to direct access.
* Package storage moved to the plugin-specific uploads sub-directory (uploads/smartengin-buy/packages). Existing files are migrated automatically and safely on the first request after the update; stored references are plain file names, so nothing in the database changes.
* The protected directory's guard files (.htaccess and index.php) are ensured before every write into it, not only on activation.
* This edition sells digital files and courses. It contains no executable software-update infrastructure: no update endpoint, no update token, no update package upload, and no code that hands update information to WordPress. A few database columns inherited from the shared code base (such as the stored version or package file name) are kept as passive compatibility data so a site can move between smartEngin editions without losing anything — nothing in this edition acts on them.
* This plugin never changes another plugin's active state. If smartEngin Licence & buy is active, activating this one is refused with a message asking you to deactivate that plugin first — the decision stays yours, and nothing is deleted either way.
* Removed the last remnants of software-update plumbing inherited from the shared code base (an update download token and its special case in the download limit). This edition sells digital files and courses; it serves no software updates, and every download is counted normally.
* Removed the last dead references to the paid edition's licence and update endpoints: the REST hook that would have registered them, the "Server has moved to" setting that only they use, and the comments describing them. None of it could run here; it is simply gone now.
* No functional changes intended: rendered pages and export files were compared byte for byte before release.

= 1.4.11 =
* The product form now explains its fields: what belongs in each one and what happens if a value is wrong. (The update fields themselves belong to the paid package.)

= 1.4.6 =
* Fixed: a credit note could end up with NO currency at all — the amount then stood there as a bare number on the credit note itself, in the invoice list and in the customer account. It happened when the invoice being credited had an incomplete stored snapshot; the credit note inherits that snapshot, and the currency was taken from it without a fallback. A credit note now takes the currency of the invoice it corrects, and a document can no longer be written without one.

= 1.4.5 =
* Tested with WordPress 7.1 (released 19.08.2026) and marked as such. Everything was actually run against 7.1, not just read: purchase, invoice, credit note, customer account, subscription portal, all backend screens and all server endpoints. Nothing WordPress removed or retired in 7.0/7.1 is used anywhere in the plugin.
* Fixed for WordPress 7.1: the editor now always builds the page preview inside its own frame, and offer-card styling only reached that frame by accident, which WordPress rebuked in the browser console on every editor load. The styling is now registered the way WordPress asks for, so the preview keeps its look for good. Customer-facing pages were never affected.
* Smaller correction: an empty call to the PayPal notification endpoint (typically a scanner, never PayPal itself) wrote two "deprecated" lines into the error log. Payments were never affected; the log stays quiet now.
* Fixed: this package’s own help text named the paid package as „smartEngin buy“ — that is, as itself — in the very sentences explaining why this package issues no licence keys. The short overview also promised a licence key, which this package never hands out; it now correctly says a protected download link is e-mailed.

= 1.4.4 =
* Fixed, and this one silently cost money and paperwork: for FILE products and for add-on products (e.g. courses from smartEngin Courses, appointments from smartEngin MeetMix) a Stripe refund was never recognised. Completing the purchase erased the payment id Stripe later refers to, so the refund notification found no order: the order stayed on "paid" and NO credit note was written. PayPal refunds were not affected. The payment id now survives the purchase, and every product type is refunded correctly. Please note: orders completed BEFORE this update have already lost the id — refunding them still needs the credit note to be written by hand under Invoices.

= 1.4.3 =
* Fixed, and this is the one to update for: on the offer cards, NO message ever reached the buyer. Every hint and every error the card wanted to show ("please enter your e-mail", "please fill in your billing address", "this coupon works with card payment", "payment cancelled", any error from the payment provider) was swallowed — and some of them broke the click outright, so the button simply did nothing and the buyer was left staring at an unchanged page with no idea what was wrong. Two different helper functions in the card's JavaScript had ended up with the same name, so the one that displays the card's messages was replaced by the one that belongs to the coupon field. Anything that only warns or explains was affected; taking payment itself was not, which is why it stayed unnoticed for so long. All messages appear again.

= 1.4.2 =
* Fixed: a refunded SUBSCRIPTION charge was not recognised — Stripe refunds of renewal charges found no order (only the first payment's id is stored), and PayPal's PAYMENT.SALE.REFUNDED event was not handled at all. Both now find the charge through its own renewal invoice: the customer gets a credit note for exactly that charge, and the order and the subscription keep running. If you take PayPal subscriptions, add PAYMENT.SALE.REFUNDED to your webhook subscription in the PayPal dashboard — the instructions under Settings and Help & docs now list it.
* Refunds that arrive for a payment this site cannot match are now written to the security/activity log instead of being dropped silently, so a missing credit note has a visible cause.

= 1.4.1 =
* Fixed, and worth updating for on its own: deleting a product stopped halfway with a critical error — a leftover call into the licence module, which this package does not carry. The product's purchase options were already deleted at that point while the product itself remained. Deleting a product works again, cleanly and completely.
* Fixed, and this one touched money and paper: a subscription sold with the smartEngin Courses add-on (or a file subscription) ran the WHOLE first purchase again on every renewal charge. The customer received the purchase-confirmation e-mail again with every charge, a coupon with a limited quota was counted as redeemed again every time — and the renewal itself got NO invoice of its own, so money arrived without a document. A renewal now writes its own invoice per charge and sends no repeat mail. Reloading the thank-you page after a subscription purchase could trigger the same repeat; that is closed too.
* Fixed: the thank-you page and the payment provider's server notification often arrive within the same second, and both could complete the same order — two confirmation mails and a coupon counted twice. The database now picks exactly one winner; the second arrival answers from the stored order.
* Fixed: a PARTIAL refund (say 5 of 49 euro, out of goodwill) was treated like a full one — the order was marked refunded, a course customer lost access, and the credit note could be written over the full invoice amount. Only a full refund revokes now; a partial refund gets a credit note over exactly the refunded amount and everything else stays.
* Fixed: a coupon on a subscription CART with card payment reached the order and the invoice but never Stripe — the customer's card was charged the full first rate while the paperwork said the discounted one. The discount now reaches Stripe exactly as it does in the single checkout (first charge only). The same cart with PayPal would have applied the discount to EVERY rate silently; that combination now gets the same clear message as the single checkout: pay by card, or subscribe without the code.
* Fixed: a fixed-amount coupon (say 10 EUR) applied its number to purchase options in ANY currency — 10 EUR became 10 USD. It now refuses options priced in a different currency; percentage coupons are unaffected.
* Fixed: two customers buying in exactly the same moment could collide on the running order number, and the second one saw a server error instead of a purchase. The second order now simply takes the next free number.
* The readme now says what happens when you switch to smartEngin Licence & buy (the two packages deactivate each other on activation, all data stays), and no longer claims a bundled German translation — translations come from translate.wordpress.org.

= 1.4.0 =
* Uploads now go through WordPress' own uploader (wp_handle_upload). Your files still land in a
  protected folder outside the media library and are still stored under a neutral name, so no web
  server ever serves or runs them — only a signed download link does. What changes is the list of
  formats: this plugin now offers the 45 file types WordPress itself recognises. Formats it does not
  know — e-books (epub, mobi), 3D models (stl, blend), fonts, CAD drawings, installers and most
  design source files — can no longer be uploaded here.
* Fixed: opening Help & docs produced a PHP warning on any site running in debug mode, and the
  briefing it builds for an AI assistant carried the literal word "Array" where the per-product
  file overview should have been. Both had the same cause. That overview now reads as an indented
  list per product, so the assistant gets a real answer where it previously got nothing.
* Translations now come from translate.wordpress.org instead of being bundled with the plugin.
  That is how translations reach a plugin hosted here: generated for every locale, open to
  community contributions and delivered by WordPress itself as language packs. Until the strings have
  been entered there, the plugin shows English.
* Fixed, and this one bit anyone moving up to the paid package: deleting this plugin after
  installing "smartEngin Licence & buy" damaged the plugin they had just switched to. Both are
  built from one code base and share everything — the same tables, the same settings, the same
  signing key, the same permission — and this plugin's uninstall routine took those with it. The
  menu vanished for every role, and the key that signs download links was regenerated, so every
  download link already sitting in a customer's purchase e-mail stopped working. Sales data itself
  was never touched. An uninstall now checks whether the sibling package is still installed and, if
  so, leaves everything alone. Removing the last of the two still cleans up as before.
* Fixed, and this one could have cost you sales without a trace: the dashboard reported everything
  green while one payment provider was live and the other was still in test mode. The "Ready to go
  live" check only asked whether SOME provider was live, so a shop taking real money through PayPal
  while Stripe sat in test mode looked perfectly configured — and a customer paying by card was sent
  into Stripe's test environment. The check now looks at every enabled provider and turns amber
  while one of them is still testing, naming which one.
* Fixed: the dashboard item "Every product has a file to deliver" counted appointment and event
  products as products without a file. Those deliver no file by design — their add-on provides
  the service — so the item could never go green, no matter what you uploaded, and its link sent
  you to a product screen with nothing to find.
* New: the product list has a "File" column showing how many files each product actually hands
  over, including anything hanging on a purchase option. Green when a buyer gets something, red
  and clickable when they would not, and a plain dash where there is nothing to check. Hovering
  the number says what it is made of. Until now you were told that SOME product had nothing to
  deliver, never which one.
* The same column catches a trap nothing used to show: a purchase option ticked "Free download"
  that has no file of its own does not hand out something free instead of the paid product — it
  hands out the paid product itself, for nothing. The column turns red and names the option.
* An add-on now says whether it draws offer cards, and the product screen follows that. Where cards
  are drawn (smartEngin Courses: one per course, plus one per bundle), the sort order, the
  "featured" and "sellable" ticks and the full card design stay exactly where they were. Where only
  the payment box is embedded, those fields are left out because they could not reach a buyer. Same
  for the right-of-withdrawal tick: it disappears only where the add-on demonstrably asks for the
  waiver itself.
* A product that another smartEngin plugin maintains now shows only the settings that actually
  do something for it. Everything that could never take effect is left out of the form instead of
  sitting there greyed out: the download fields, sort order, the flags, the extra-file list, the
  shortcode box and the card settings that only apply to an offer card. Whatever is hidden keeps
  its stored value, so nothing is lost and nothing is quietly reset when you save.
* Fixed, and this one was invisible: the payment box such a plugin embeds always used the default
  button colours. Whatever you set under Card design reached your own offer cards but never that
  box, so the same product looked different in two places. It now takes the product's colours and
  icons for the Card/SEPA and PayPal buttons.
* The right-of-withdrawal tick is no longer offered on such a product. A plugin selling a service
  (an appointment) asks for the waiver itself, before checkout, under the rule that fits a
  service — this tick is written for digital content. With both active the buyer would have been
  asked twice, the second time with the wrong wording. The form now says who handles it.
* Fixed: a product's reminder wording was silently emptied whenever the product was saved from a
  screen without the Reminders tab. Anything written there was gone with no warning; it survives now.
* New: other smartEngin plugins can now sell through this checkout. A purchase can carry a
  reference of its own, a new `sels_order_created` action hands it over the moment the pending
  order exists, and a buy box can name where the buyer goes after paying. Built for smartEngin
  MeetMix, which uses it to sell appointments: your visitor books a time, pays here, and the
  appointment becomes firm — with your invoice, your coupons and your tax settings, from one
  place. Nothing changes for the products you sell yourself.
* New product type "appointment" for such add-ons: it delivers neither a licence key nor a
  file, because the service itself is what was bought. It is created by the add-on, never in
  the product form, and a save there can no longer turn it into something else by accident.
  An add-on registers its own type, and the product form then names that type and explains
  who owns it, instead of offering a choice that does not apply. Such a product also gets its
  own badge in the product list rather than being labelled "Licence".
* Fixed: when another plugin saved one of your products or purchase options, every field it did
  not mention was reset — a VAT rate to 0, a description to empty, and on a purchase option even
  "active", so the option could disappear from your offer cards with nothing explaining why. What
  is not mentioned now stays as it is. Saving from the product form is unaffected.
* Safer: on a product an add-on maintains, the price, VAT rate and name are shown but no
  longer editable here. They used to be plain fields — a change looked like it had worked and
  was quietly overwritten on the next purchase, which is worse than a field you cannot touch.
  You now see the value together with a note saying who maintains it.
* New: the Settings screen now states, right in its header, which language your customers are served in — and it reads the answer from your site instead of just explaining it. WordPress has two language switches and they are easy to confuse: "Settings → General → Site Language" decides everything a customer sees (offer cards, checkout, invoices, the account page and every e-mail), while the language in your own user profile only changes the admin screens YOU look at. When the two differ, the header says so plainly, so a language question can be settled in seconds instead of being mistaken for a bug.

= 1.3.1 =
* The built-in AI briefing (Help & docs → "Copy setup briefing for an AI assistant", and the read-only REST guide) now describes exactly THIS package. It no longer explains licence keys, activation limits, trials or the partner program — concepts that exist only in the paid package — and no longer lists a "Licences" menu entry that does not exist here. An AI assistant guided by it can no longer send you looking for screens this plugin does not have.

= 1.2.1 =
* FIX: the new language switcher won the click but lost the page. A signed-in course customer could see it set to English while the text around it stayed German — *smartEngin Courses* keeps a language preference of its own, and depending on which plugin loaded first, the wrong one decided. One rule settles it now: a click on the switcher wins immediately and updates the member-area preference with it; without a click, a signed-in course customer keeps the language they chose in the member area. One visitor, one language, whichever way you switch.

= 1.2.0 =
* New: a small "Deutsch · English" switcher in the customer area — top right on the magic-link form, on the account page after signing in, and on the subscription self-service form. It starts in the language of the website and remembers the visitor's choice for a year, which helps when a site sells in German but the occasional customer reads English, or the other way round. The choice also applies to the e-mails this plugin sends that visitor, such as the magic-link e-mail. Mail templates you wrote yourself are never touched.
* FIX: e-mails to customers triggered from the backend (re-sending an invoice link, issuing a credit note, announcing a new file to subscribers) were worded in the ADMIN's profile language, not in the language of the website. An admin who runs wp-admin in English would unknowingly send English mails to the German customers of a German site. All customer mails now follow the language of the website; the "send test to me" buttons show exactly what the customer would get.
* FIX: on German sites, the column above the invoice numbers (customer account, backend invoice lists) said "Anzahl" — the translation had confused number-of-things with number-of-the-invoice. It now says "Nummer".

= 1.1.6 =
* The pointers to the two sibling plugins now show their names in the house style: bold, "smart" and the trailing part dark blue, "Engin" green.
* FIX: every backend screen carried the badge "smartEngin Licence & buy" — the name of the separate paid plugin. It now says "smartEngin buy", the plugin you actually installed.
* FIX: the product form showed a red note about the licence products of the separate paid plugin, which read as if you already owned that plugin and had missed a step. This package has no licence type at all, so the note was wrong twice over. It is gone.
* In its place, and only where something is genuinely missing, there is now a short pointer to the two plugins that go with this one: smartEngin Licence & buy (sell your own software with licence keys) and smartEngin Courses (sell online courses). They sit where the gap is actually felt — the product form — and once more in Help & docs. Both disappear as soon as you have the plugin in question. No dashboard banners, no pop-ups, nothing dismissible to click away.

= 1.1.4 =
* Many more file types can be sold: documents and e-books, Office and data files, archives (ZIP, RAR, 7Z, TAR…), images, audio, video, design source files (PSD, AI, EPS, Figma, Sketch, XD), software installers (EXE, MSI, DMG, PKG, DEB, APK), 3D models (STL, OBJ, FBX, GLB…), CAD drawings (DWG, DXF, STEP…) and fonts (TTF, OTF, WOFF, WOFF2) — around 70 types in total. Web and code files (PHP, HTML, JS, CSS) are deliberately not accepted on their own; they are sold as a ZIP, which is how templates and code bundles ship anyway.
* The file dialog now offers exactly what the server accepts. Until now it was a hand-written list that had drifted apart from the check, so it hid types (CSV, SVG, WebP…) that would have been taken.
* Hardening: uploaded product files are now stored with a neutral ".bin" extension, the real one kept inside the name. The package folder is locked by an .htaccess, which Apache honours but nginx ignores — there a stored SVG could be opened straight from the browser and would run any script inside it. No web server executes or renders a ".bin", so the protection no longer depends on the server software. Customers still receive the correct file name; existing files are renamed automatically on update, and are moved back untouched if anything about the rename fails.

= 1.1.3 =
* Every hint that names a backend click-path now says "smartEngin buy → …" — the menu this plugin actually has. Before, many hints (help page, notices, the AI briefing) said "Licence & buy → …", the menu name of the paid package, sending you to look for an entry that does not exist here.
* First round of wording fixes for the same reason: the Dashboard subtitle, the Products page description, the order-deletion texts and the 100 %-coupon hint no longer speak of licences — this package issues no licence keys, it sells files and courses. The paid package keeps its wording.
* Removed a stale, unused copy of the AI briefing (docs/AI-SETUP-GUIDE.md, frozen at 0.50.0). The live briefing under Help & docs and the REST /guide endpoint are always current and remain the real sources.
* Only wording and that one dead file changed — no data, no setting and no purchase is affected.

= 1.1.2 =
* The plugin no longer shows two things it cannot do: the "Licences" figure on the Dashboard and the "Licence server address" setting. Both belong to the paid "smartEngin Licence & buy" package and had stayed visible here. Nothing else changed — no data, no setting and no purchase is affected.

= 1.0.3 =
* FIX: on the offer cards, the coupon field stood open from the start — right under its own "Redeem a coupon code" link, which made no sense to look at. Clicking the link only made the LINK disappear, and reloading brought it back. The field is meant to stay closed until the customer asks for it, exactly like the e-mail field.
* Cause, worth naming because it is invisible from the outside: the markup did say "hidden", but the browser puts that instruction in its own weakest rule book, and a single line of the plugin's own styling overruled it. One guard line per front-end area settles it for good, including anything added later.
* No behaviour changed — nothing was ever mis-sent or mis-counted, only shown.

= 1.0.1 =
* FIX: buying from a page served by a page cache could fail for good. The security stamps baked into the sales page outlive any cache: a stale page answered every purchase with "your session expired", and reloading fetched the same stale page again. The checkout script now fetches fresh stamps on load — through a channel page caches never store — and again when the visitor starts interacting, so a tab left open overnight buys just as reliably.
* FIX: "remove all data" on uninstall now really removes all data. The settings (including payment-provider credentials) survived it before; the transient sweep also covers every counter now instead of a stale list.
* Dashboard: the revenue curve is summed per sale day in the database instead of loading every paid order into memory — same numbers, and the page stays fast when the shop has grown to thousands of orders.
* readme: documented what to check when the site runs behind Cloudflare or another proxy (real visitor addresses must be restored by the host, or the purchase brake and the security log see one shared address).

= 1.0.0 =
* First public release of the free sales core: products, purchase options, cart, Stripe and PayPal, invoices with German small-business support, coupons, customer account and reminders.
