Wajub changelog

Every release of the plugin, newest first.

= 1.4.1 =
* **Separate payment methods:** An order is no longer refused with "Your payment details were refreshed" at the first "Place order" when the Mobile Money and Card forms loaded at the same moment, or the checkout updated the address while they did. Each method's payment is now kept apart from the WooCommerce session, which every request saves whole, so one can no longer erase the other.

= 1.4.0 =
* **Separate payment methods:** Two new WooCommerce payment methods, "Mobile Money" and "Card", each with Wajub's payment form inside your checkout, in the classic and Blocks checkouts. A Mobile Money payer enters their number and approves the payment on their phone; a card payer enters their card, without leaving your store. Both are off until you turn them on, in the setup assistant ("Separate methods") or in WooCommerce → Settings → Payments ("Wajub Mobile Money", "Wajub Card"): an existing store sees no change.
* **Separate payment methods:** Each method takes payments of its own kind only, so an order placed with "Card" is paid by card. Each has its title, description and logos (operators for Mobile Money, Visa and Mastercard for Card), and shows only when your Wajub account offers it for the order. A Mobile Money payer chooses their operator from a list with each one's logo, the operator of their number already selected; a setting finds it from the number instead. The form opens in the language set for the Wajub method and takes your checkout's own font, sizes, colours and rounding, and its light or dark look ("Payment form colours": "System" now follows your site, not the customer's device). It loads without moving the page: a placeholder in your fields' style holds its place, and a code, approval or 3-D Secure step grows it smoothly.
* **Separate payment methods:** With Mobile Money or Card on, the Wajub method becomes "Other payment methods": it offers only what they do not (PayPal, wallets, bank transfer…), and shows only when your Wajub account has one for the order, so no way to pay shows twice. A title or description you wrote yourself is kept.
* **Separate payment methods:** The payer places the order, then the payment goes through in the form. A declined payment can be tried again in the same form, with no new order. "Use another payment method" gives the checkout back, and "Continue on the secure Wajub page" finishes the same payment on Wajub's page. A payer who leaves while Wajub is still confirming goes to the order page, which keeps checking, and is never offered a second payment. When the cart changes before the order is placed, the payment follows the new total.
* **Separate payment methods:** Wajub's payment page takes over without JavaScript, when the form cannot load (blocked, or refused by the embedding restriction of your Wajub account), and on the order's payment page; it then offers that method only. A form left open too long, or replaced in another tab, is refreshed with a message, and nothing is charged.
* **Separate payment methods:** A Mobile Money number the form refuses, or a card provider's window the browser blocked, is explained in the payment box, and the payer can fix it and try again. A payer whose card provider or operator takes over the whole page comes back to the order. In Live, that needs HTTPS: the setup assistant and Settings warn a store served over plain HTTP. While the environment in use has no key, neither checkout loads anything for these methods (Wajub.js, scripts, styles).
* **Separate payment methods:** Webhooks, the payer's return, the order confirmation page (which tells a Mobile Money payer what to do on their phone), "Check status" in the order's "Wajub payment" box and refunds from WooCommerce work for these orders as for the Wajub method. An order moved to another method, then paid by an earlier Wajub payment after all, goes back to the method that took it. Once the order is placed, its payment carries the order's reference and reads "Order #123" in the Wajub Dashboard, with the order's ID in its metadata; before that, it reads "Order at <your site name>".
* **Connect with Wajub:** In the setup assistant and in Wajub → Settings, approve this site in the Wajub Dashboard and Wajub creates its key: nothing to copy. The site shows a code to compare with the Dashboard's, saves the key with the same checks as a pasted key, and registers the webhook. Sandbox comes first, and "Connect Live" when you are ready. Settings offers "Connect" or "Reconnect" for each environment and says when a key came from Wajub. Pasting keys stays available ("Paste keys instead").
* **Connect with Wajub:** Only the owner, an administrator or a developer of your Wajub account can approve, after a security code. The approval page starts on Sandbox and on a key restricted to what the plugin needs (payments, refunds and webhooks, read and write); the approver chooses the environment, "Restricted" or "Full access", and a key that lasts 90 days or does not expire. The site has 15 minutes to collect an approved key. A request started in another tab replaces the first, whose page says so.
* **Connect with Wajub:** A key's expiry date shows in Settings, the setup assistant and the Overview. From 14 days before it, the site's administration e-mail address gets one e-mail per key, and a notice offers to connect again in one click for the key of the environment in use; once the date has passed, that notice and the connection status say the key expired. A key Wajub still accepts after that date is no longer shown as expired.
* **Connect with Wajub:** The site tells you when you approved another environment than the one asked for, when a restricted key may lack a permission the plugin needs, or when it belongs to another business than the one the site used. Reconnecting replaces the key and deletes the one a previous connection gave when Wajub allows it; otherwise it tells you to delete it in the Wajub Dashboard. A copy of the site (`WP_ENVIRONMENT_TYPE` other than `production`, or another address) never deletes it, since the store it was copied from may still use it. A key you pasted is never deleted.
* **Environments:** Each environment pays with its own key only. Sandbox without a sandbox key no longer takes real payments with the live key: it takes no payment until you add a sandbox key or switch to Live. Wajub is then hidden at WooCommerce checkout, the other integrations and the pay buttons refuse to start a payment, and a red notice in the admin ("Wajub takes no payments.") offers both. "Wajub · Real payments" no longer shows in the admin bar, and WooCommerce's Wajub settings read "Sandbox — no payments".
* **Environments:** On update, each site of a network left in Sandbox with only a live key, which took real payments with it, is handled once. A production site switches to Live, where it keeps taking the same payments, and its administrators are told once ("Wajub is now set to Live."). A copy whose `WP_ENVIRONMENT_TYPE` is `local`, `development` or `staging` stays in Sandbox and asks for its sandbox key, so it never charges real customers.
* **Environments:** MemberPress, LifterLMS, GiveWP 3, Easy Digital Downloads 3 and Charitable treat a Wajub payment as a test or a live one from the key Wajub takes it with, not from their own test mode, so a real payment is never recorded as a test. LifterLMS no longer shows a test mode setting for Wajub: Wajub → Settings chooses.
* **Setup and settings:** The setup assistant's checkout step offers "Separate methods" beside Redirect, Overlay and Inline, with Mobile Money and Card ticked as your Wajub account offers them for your store's currency. "Also offer the other methods your Wajub account has (PayPal, wallets…)" is on by default: left on, the Wajub method becomes "Other payment methods"; turned off, the Wajub method itself is turned off. Choosing Redirect, Overlay or Inline turns the separate methods off and the Wajub method back on. The step gives the address to add if you restricted where Wajub can be embedded.
* **Setup and settings:** The "Wajub is not offered at WooCommerce checkout" notice and the Overview's checklist count the separate methods: a store that offers only one of them is not told to turn Wajub on, and their links open WooCommerce's list of payment methods.
* **Setup and settings:** The links to your keys, in the setup assistant and in Settings, give the whole way to them in the Wajub Dashboard: Settings → Developers → API keys. When Wajub refuses a key as you check it or test the connection, the message says why: a key Wajub does not know, or one rolled, deleted or expired; this server's IP address missing from the key's IP Whitelist; or API access restricted for your account. These checks no longer blame the key's permissions, which they do not need.
* **Setup and settings:** The key fields read "Sandbox API key" and "Live API key": they hold the restricted key "Connect with Wajub" gives as well as a key you paste. When Wajub refuses to register the webhook, the message says why: a key it does not know, or one rolled, deleted or expired; this server's IP address missing from the key's IP Whitelist; API access restricted for your account; or a restricted key without the webhooks permission (read and write).
* **WooCommerce checkout and integrations:** A customer never pays twice, and pays only what is left. A Wajub payment short of the order's total (an instalment payment closed early, an order edited since) counts toward it: the customer pays the rest from the order's payment page, and the order is paid once its payments cover its total. An open instalment payment is resumed instead of a new one starting, or closed keeping what it collected. Refunds from WooCommerce are split across the order's payments. In the other integrations, a payment short of what is due still completes nothing, on the payer's return as with webhooks. A fee the payer bore never makes up for a total raised after the payment started.
* **WooCommerce checkout and integrations:** An unpaid order is never cancelled by WooCommerce's hold-stock cleanup while one of its Wajub payments went through but could not count yet (Wajub did not answer when its refunds were read, or the key may not read them): it stays pending, the merchant is told in its notes, and the payment is read again at each cleanup. An earlier payment attempt found to have gone through after another payment paid the order adds one note: the payment, what it collected, and to refund it from the Wajub Dashboard if the customer did not mean to pay twice.
* **WooCommerce checkout and integrations:** A Wajub payment in another currency than the order's still counts nothing and the customer is still asked for the order's total; the merchant now reads, once per payment, what it collected in its own currency and to refund it in the Wajub Dashboard. "Check status" says so too, and says when the key may not read a partly refunded payment's refunds and how to fix it, instead of "Wajub could not be reached".
* **Refunds:** A refund started from WooCommerce while Wajub is in Sandbox, on an order paid in Live, is refused with a message instead of refunding a Sandbox payment while WooCommerce records a refund of the order. A Sandbox refund is no longer recorded on an order paid in Live. A refund made in the Wajub Dashboard after "Refund manually" in WooCommerce adds only what was not recorded yet, and the note says so.
* **Order notes:** The order says what is left to pay when the payment of the rest cannot start, and why nothing new started when Wajub answered neither the close of an instalment payment nor a read of it. "Part of this order is already paid" names the method to choose as listed. An order given back to a Wajub method keeps the title it had under it.
* **Shortcodes and Gravity Forms:** [wajub_pay], [wajub_pay_content] and the success and processing pages no longer say "Payment received" for a payment that collected less than it asked: they say how much was paid of what was due and whom to contact, and locked content stays locked.
* **Separate payment methods:** The form also takes the colours of a theme that writes them in oklch(), lab() or color() (Tailwind CSS 4, block themes), and on a site drawn over a picture or a gradient it is light or dark as the site's text shows. Without JavaScript, the payment box leaves no empty space. While the form loads, the keyboard and screen readers pass it by; after "Try again" the focus moves into it once it shows, unless the customer is typing or clicking elsewhere.
* **Separate payment methods:** The Wajub method is offered for what its payment asks (the order on its payment page, Blocks included, or the rest of an order paid in part), and never starts a payment that could offer Mobile Money or Card twice. In Sandbox, with both separate methods on, its settings say it seldom shows (no PayPal nor wallets in Sandbox). A default title or description saved from an admin in French reads as the default in the storefront's language.
* **Checkout and admin:** MTN MoMo, Moov Money and M-Pesa logos drawn from Wajub's brand kit, Express Union Mobile among the Mobile Money logos, PayPal on a badge of its own and Apple Pay outlined in black. In Wajub → Settings → Integrations, WP Simple Pay shows its own logo, and a card's text no longer leaves a single word on its last line.
* **Privacy and uninstall:** Deleting the plugin also removes the settings of the Mobile Money and Card methods, the record of which keys "Connect with Wajub" gave (their fingerprint, never the key) and the record of which updates ran. The keys it created stay in your Wajub account: delete them in the Wajub Dashboard. Erasing a payer's data keeps which WooCommerce method took the payment. The readme lists what the site sends the Wajub Dashboard when you connect, and what the new methods send Wajub.
* **For developers:** New WooCommerce gateways `wajub_mobile_money` and `wajub_card`. New filters `wajub_fields_prepare_limits` (`['ip' => 10, 'site' => 30]` requests a minute) and `wajub_pairing_enabled` (`false` hides "Connect with Wajub"). In Sandbox without a sandbox key, `WajubPay\API\Client` has no key (`isConfigured()` is `false`) instead of the live key.
* **Security:** A GiveWP return link can no longer change a donation's status. The return applies only to the donation GiveWP signed it for and made with Wajub, never moves a completed or refunded donation, and asks Wajub for the payment instead of reading the status in the address; GiveWP's failure page no longer marks the donation failed when it is opened, or opened again later. A payment not final yet lands on the receipt.
* **Security:** The return links of GiveWP, Charitable, Gravity Forms, MemberPress and Tutor LMS are each signed for their own integration. Links sent before the update still work for 7 days.
* **Checkout:** The "Secured by Wajub" line, under the Wajub methods and the pay buttons, is off by default; turn it on in Wajub → Settings ("Show “Secured by Wajub” to customers"). Without a key, the WooCommerce block checkout no longer loads Wajub.js. On an account whose payment page is on its own domain, Easy Digital Downloads, LifterLMS, Tutor LMS, Charitable and older GiveWP forms now send the payer there instead of to the WordPress admin. A course whose title contains "]" no longer breaks its pay button, and malformed return addresses no longer cause an error page.
* **Checkout:** The brands' own logos (MTN MoMo, Orange Money, Wave, Moov Money, Airtel Money, M-Pesa, Visa, Mastercard, PayPal, Apple Pay, Google Pay), each at its own shape, and a logo for each Wajub method in WooCommerce's Payments settings instead of a grey card.
* **Admin:** Wajub → Settings → Integrations shows each plugin's own logo (bundled with the plugin, nothing loaded from elsewhere), cards of the same height with a one-sentence description and a short note for what an integration does not support, and the plugins installed on the site first. In the block editor, Mobile Money and Card no longer show as incompatible with the Checkout block, and the editor no longer loads Wajub.js.
* **Admin:** Every Wajub notice, those about a missing or expiring key included, appears only on the Wajub pages, the Dashboard, the Plugins screen, and the settings and orders or donations of WooCommerce and the other plugins Wajub takes payments for. A new install shows "Finish setting up Wajub" instead of a red error on every screen, on the Plugins screen and the Wajub pages only; once dismissed, it does not come back.
* **Shortcodes:** What you write between `[wajub_pay_content]` tags is filtered as post content: text, images, videos, audio, embeds and the shortcodes it holds stay as they are, and a script or a style typed in it is removed.
* **For developers:** New filters `wajub_show_secured_line` and `wajub_send_dispute_email`. The `wajub_webhook_invalid_signature` and `wajub_webhook_missing_secret` actions pass why the delivery was refused and the environment it claims, no longer its unverified body. The payment, refund and dispute data the webhook actions and filters receive is cleaned first: its texts are plain text, its numbers, booleans and nulls unchanged. Charitable's `charitable_gateway_wajub_name` filter is now `wajub_charitable_gateway_name`.
* **WooCommerce checkout and integrations:** An order cancelled, by you or by WooCommerce's hold-stock cleanup, has its Wajub payment that nobody started paying cancelled with Wajub too, a moment later, so that nobody pays a cancelled order. One being paid, one that went through, or one paid in instalments is not cancelled (Wajub cannot stop a payment the operator or the bank is confirming): the order says to refund it from the Wajub Dashboard should it take money. An order kept by the hold-stock cleanup because Wajub is still approving its payment says so once.
* **Refunds:** When Wajub refuses a refund as more than it can refund, and nothing of it went through, the order's payments' refunds are read: one made in the Wajub Dashboard whose webhook never reached the site is recorded as its webhook would have, and the refund box says what was caught up. Wajub's reasons read "(HTTP 422)" rather than "(http_422, HTTP 422)".
* **Webhooks:** A site at a local address is given the exact `wajub listen --forward-to …` command for its webhook address, and where to paste the secret it prints. A payment the site cancelled is never noted afterwards as one the customer did not complete.

= 1.3.0 =
* **Setup and settings:** A setup assistant opens once the plugin is activated (not after a bulk, network or WP-CLI activation, nor when a key is already saved) and takes you through four steps: connect your keys, set up webhooks, choose how customers pay, then test and go live. It leaves the Wajub menu once the site is connected, and Settings links back to it ("Run the setup assistant again").
* **Setup and settings:** Keys are checked before they are kept. A key pasted in the wrong field, a publishable key, or a key Wajub refuses is not saved, and the message says why. A saved key or signing secret is no longer printed into the page, as 1.2.1 did in the settings form's HTML: it shows masked, and "Replace" changes it. The key the site takes payments with cannot be removed by mistake.
* **Setup and settings:** Webhooks set themselves up. "Register automatically" in Settings, or "Set up webhooks automatically" in the setup, registers this site with Wajub for each environment that has a key and saves the signing secret Wajub returns. A local or private address, or a Live site without https, gets the steps to do it by hand. Settings says when Wajub still sends webhooks to an older address of the site, and "Get a new signing secret" replaces the saved ones.
* **Setup and settings:** Sandbox is always visible: the environment shows on every Wajub screen, "Wajub · Sandbox" shows in the admin bar for administrators (in the admin and on the storefront), and a notice warns when a production site is still in Sandbox. Switching to Live asks you to confirm, and is refused while no live key is saved.
* **Setup and settings:** Sandbox selected with no sandbox key: as before, the plugin then takes payments with the live key, so they are real. This is now shown instead of hidden: a red notice on every admin screen, "Wajub · Real payments" in the admin bar, Live in the Overview and in Transactions, and no "Test mode" badge at checkout. A Live site is not switched to Sandbox while it has no sandbox key, since only the label would change.
* **Setup and settings:** "Test connection" checks your key with Wajub when you ask, and the last answer is kept with its age. The admin no longer calls Wajub each time a Wajub page opens.
* **Overview and Transactions:** Wajub → Overview replaces the Dashboard. It shows what you collected, successful payments, the success rate, pending payments and refunds over the last 7, 30 or 90 days, compared with the period before; the payment methods your customers use; the latest payments; and the status of the connection, the webhooks and the environment. A checklist guides a site that is not ready yet.
* **Overview and Transactions:** Wajub → Transactions: search by reference, payment ID or email; filter by status, source, environment and dates; sort by date or amount; set the rows per page in Screen Options. Each payment has a page of its own with its method, customer, refunds, metadata and the data Wajub sent, and links to its order, donation or entry and to the Wajub Dashboard. For a Sandbox payment, a note says the Wajub Dashboard opens it only once switched to Sandbox.
* **Overview and Transactions:** "Export CSV" downloads the transactions that match the filters.
* **Overview and Transactions:** Payments now keep their method (MTN Mobile Money, Orange Money, Wave, card…) and their Wajub payment ID, and refunds are listed as transactions of their own, linked to their payment. Payments recorded before 1.3.0 show "Other" as their method.
* **WooCommerce checkout:** The payment method shows the logos you choose (MTN, Orange, Airtel, Moov, M-Pesa, Wave, Visa, Mastercard; by default those of the store's currency), a line that says how the payment happens, and "Secured by Wajub", in the classic and Blocks checkouts. Its default name is now "Mobile Money or card": a store still showing the old default, "Wajub", shows the new one, and a name you wrote is kept.
* **WooCommerce checkout:** Behaviour change: when the customer cancels a payment or lets it expire, the order stays pending, with a private note, instead of becoming failed. WooCommerce no longer emails the customer and you about a failed order the customer can still pay; with "Hold stock" set, WooCommerce cancels it if it is never paid. A payment Wajub refuses still fails the order.
* **WooCommerce checkout:** No double charge. When a customer places the order again, or pays it from its payment page, the Wajub payment the order already holds is checked first: if it went through, the customer goes to the order; if it is still open, it is cancelled before a new one starts. Checkout also refuses an order the customer has paid meanwhile ("Your payment for this order has already gone through").
* **WooCommerce checkout:** Inline and overlay: after a successful payment, the store confirms it before the order page opens, so that page no longer shows a paid order as waiting. A customer who closes the window while Wajub is still processing goes to the order page, which keeps checking, instead of getting the form back. "Use another payment method" leaves the payment step. If Wajub.js cannot load (blocked, offline), the customer goes to Wajub's payment page instead of seeing an error.
* **WooCommerce checkout:** On the order confirmation page, while Wajub confirms a payment, a panel says what is happening (for Mobile Money in redirect mode: approve on your phone, enter your PIN), updates by itself, and after a while offers "Check again" and "Choose another payment method". The customer sees the Wajub payment ID, with a copy button. An expired payment is called expired, not cancelled.
* **WooCommerce checkout:** A "Wajub payment" box on the order screen shows the payment's status, method, amount, payment ID and reference, environment and the reason it failed, with "Check status" (for orders paid with Wajub) and "View in Wajub".
* **WooCommerce checkout:** Orders paid another way: a Wajub attempt that ends no longer fails an order whose customer then chose another method (bank transfer, cash on delivery); a note says so. A Wajub payment that goes through afterwards switches the order back to Wajub, with a note not to accept the other payment as well. A late failure of an earlier attempt no longer fails an order whose customer started a new one, a second payment of a paid order is noted with advice to refund it from the Wajub Dashboard, and the Dashboard refund of such a payment is noted on the order rather than recorded as a refund of it.
* **Pay buttons and integrations:** `[wajub_pay]` and `[wajub_pay_content]` use your theme's button style and read "Pay 25 000 FCFA", "Donate" or "Unlock for 2 500 FCFA". Donation forms offer preset amounts (`amounts="1000,2500,5000"`, or amounts suited to the currency), `[wajub_pay_content]` takes `button_text` and `class`, and a logged in payer finds their email and name filled in. A form that cannot work (no key, no amount) shows visitors a short notice, and administrators what to fix.
* **Pay buttons and integrations:** Back from paying, the pay form and `[wajub_pay_message]` show the payment as Wajub reports it: received, being confirmed (the page updates by itself), cancelled, expired or failed, with "Try again". A success page no longer thanks a payer for a payment that failed. A payment this site did not create, such as one from a Payment Link, shows "We couldn't look up this payment" until its webhook arrives.
* **Pay buttons and integrations:** Wajub.js loads only for pay forms that open the payment in the page (inline, overlay): redirect forms, the default of `[wajub_pay]`, and every course button no longer load it. If it cannot load, the payer goes to Wajub's payment page. After a failed in-page payment, "Pay" starts a new payment instead of reopening the failed one.
* **Pay buttons and integrations:** GiveWP: the webhook now completes donations; in 1.2.1 a donation was completed only if the donor came back to the site. Easy Digital Downloads 3: a payment Wajub confirms after the order was marked failed now completes it, with a note, and a failure reported late never fails an order that is already paid.
* **Pay buttons and integrations:** Gravity Forms: the Wajub add-on now loads (in 1.2.1 it did not appear in Gravity Forms), and the payer's return is handled: the entry is marked paid, and the payer lands on your "Payment successful", "Payment not completed" or "Payment processing" page instead of your home page. A payment that cannot start is explained to the payer instead of the confirmation. Entries get a "View or refund in Wajub" link, and the subscription settings, which Wajub does not take, are hidden.
* **Pay buttons and integrations:** Gravity Forms: forms can send "Payment Completed" notifications, once, whether the payer's return or the webhook comes first. A refund made in the Wajub Dashboard is recorded on the entry and sends "Payment Refunded": the entry becomes Refunded once everything is refunded, and stays Paid, with a note, after a partial refund.
* **Pay buttons and integrations:** LearnDash, LifterLMS and Tutor LMS ask a logged out visitor to log in to buy a course (and to create an account when registration is open) instead of showing nothing. The Wajub pay option now shows with Tutor LMS 2 and later, once per course, and not to enrolled students or on courses not for sale. LifterLMS and Tutor LMS course buttons charge in the LMS's currency, not always in XAF.
* **Pay buttons and integrations:** Easy Digital Downloads, GiveWP, Charitable and LifterLMS name the method "Mobile Money or card". When a payment cannot start, payers read why in words they can act on, in every integration; WooCommerce keeps the API's own reason in a private order note, keys masked. Back from a cancelled, expired or failed payment, Easy Digital Downloads, GiveWP, Gravity Forms and LifterLMS payers are told which, with a link back to the form. Charitable starts one Wajub payment per donation (1.2.1 could start two). MemberPress returns payers to the account page set in MemberPress, not `/account/`. LifterLMS's transaction link opens the payment in the Wajub Dashboard.
* **Pay buttons and integrations:** Amounts read the way your visitors write them ("25 000 FCFA", "12,50 €", "₦"), and BIF, DJF, GNF, ISK, KMF and XPF are counted without decimals, as Wajub counts them. New installs get return pages titled "Payment successful", "Payment not completed" and "Payment processing"; existing pages are not renamed.
* **Pay buttons and integrations:** A refund made in the Wajub Dashboard is now recorded by Easy Digital Downloads, GiveWP, Charitable, MemberPress, Tutor LMS and LifterLMS, as WooCommerce and Gravity Forms already were: the record is marked refunded once everything is refunded, and gets a note otherwise. A refund started from WordPress is not recorded a second time when its webhook arrives, and a refund whose call times out after Wajub has confirmed it is recorded as made instead of showing an error.
* **Pay buttons and integrations:** Easy Digital Downloads 3: each refund you make in EDD, full or partial, is sent to Wajub with its own amount. A partial refund in EDD used to refund the whole payment in Wajub, and later ones sent nothing. The refund that refunds the order in full, or a status set to Refunded where EDD makes no refund order, asks Wajub for what is left of the payment.
* **Pay buttons and integrations:** Charitable donations now keep their Wajub payment, so webhooks complete them and refunds find the payment, and "Refund in gateway" on a donation now sends the refund to Wajub. Tutor LMS: an order's Wajub payment is saved with the order in Tutor's own records (it could be saved on, or deleted with, a post that had the same number).
* **Webhooks, security and reliability:** A webhook settles an order, donation, entry, membership or course order only with a payment this site created for it, and only once that payment collected the full amount in the right currency; otherwise the record is left as it is, with a note. The public pay form route keeps only what a pay form sends, so a payment started from it can no longer name one of your orders, and it returns only to pages of this site.
* **Webhooks, security and reliability:** Return links have a key of their own (`wajub_callback_secret`): renewing or replacing a webhook signing secret no longer sends returning customers back to the checkout. Links sent before the update keep working for seven days.
* **Webhooks, security and reliability:** Behaviour change: the pay forms, the payment status checks and the return pages are rate limited per visitor address, and answer `429` beyond the limit. Return pages ask Wajub only about payments this site created. Behind a proxy that hides visitors' addresses, all visitors share one limit.
* **Webhooks, security and reliability:** Settings and the Overview show when the last webhook update arrived and from which environment, and what to do when one is refused (no secret saved, wrong secret, server clock). A stray request to the webhook address no longer shows your webhook as failing: it shows as failing only after repeated refusals with no update accepted since.
* **Webhooks, security and reliability:** Lighter on Wajub and on your server: the WooCommerce pages that follow a payment check it less often; the order box's scripts load only on the order screen; the Transactions list no longer loads its orders one by one; and the check for the payments table no longer runs on every page load. A network activation sets up each site on its first visit.
* **Webhooks, security and reliability:** A webhook is trusted for the environment of the signing secret that verifies it. A Sandbox-signed delivery never pays, fails or refunds anything while Live is in use: it is acknowledged, and recorded as Sandbox. A Live-signed delivery is real money, and is acted on even while Sandbox is in use, except on a local, development or staging copy of the site (`WP_ENVIRONMENT_TYPE` other than `production`), whose orders are copies of real ones. A delivery whose content names another environment than its signature is ignored. Dispute e-mails and order notes say whether the dispute is Live or Sandbox.
* **Privacy, uninstall and WordPress.org:** Tools → Export Personal Data and Erase Personal Data include "Wajub payments". Erasing removes the payer's email address and the stored payment details, keeps the amount, status, method, date and references, and a later webhook does not write them back. Access a person paid for is kept. Settings → Privacy offers suggested text for your privacy policy.
* **Privacy, uninstall and WordPress.org:** The payments table now keeps the last payment details Wajub sent, which can include the payer's name, phone number and addresses. They show on the payment's page, and the export and erase tools cover them.
* **Privacy, uninstall and WordPress.org:** Deleting the plugin removes what it stored on every site of a network: settings, keys and secrets, the payments table, temporary data, Wajub's Gravity Forms feeds, and the record of which accounts paid for which content (a reinstall does not bring it back). Return pages you never edited go to the trash. Orders, donations, entries and their `_wajub_*` details are kept.
* **Privacy, uninstall and WordPress.org:** The readme lists the external services the plugin uses (the Wajub API, Wajub.js and Wajub's payment page), what it sends them, and Wajub's terms and privacy policy. Charitable no longer causes WordPress's "translation loading was triggered too early" notice.
* **Accessibility, languages and RTL:** Admin screens: the results of "Test connection", webhook registration, "Check status" and the copy buttons are announced to screen readers; status badges have stronger contrast; links that open a new tab say so; and each link of the setup checklist names its action ("Add your sandbox key").
* **Accessibility, languages and RTL:** Checkout and pay forms: while an in-page payment runs, the rest of the form is out of reach of the keyboard and screen readers; progress is announced; focus is visible; animations stop for visitors who ask for reduced motion; and Windows high contrast mode is respected. Right-to-left stores (Arabic, Hebrew) now lay out correctly.
* **Accessibility, languages and RTL:** The Wajub checkout opens in your site's language when Wajub offers it (French, English, Spanish, Portuguese, Arabic), in every integration and in redirect mode too. In WooCommerce, "Payment form language" is now a list; a value typed in earlier, such as `fr_FR`, reads as that language.
* **Accessibility, languages and RTL:** The plugin is translated into French (`languages/wajub-fr_FR`), for merchants and payers alike. Every text is translatable, counts use plural forms, and a translation template ships in `languages/wajub.pot`.
* **For developers:** New action `wajub_refund_succeeded` (`$paymentReference`, `$refund`, `$source`, `$sourceId`), fired once per refund made in the Wajub Dashboard or in WordPress. New filters: `wajub_return_retry_url` (the "Try again" link of a return notice), `wajub_return_notice_in_content`, and `wajub_webhook_events` (the events the plugin subscribes its webhook to).
* **For developers:** The pay forms and the WooCommerce checkout read the same CSS custom properties, set on any parent: `--wajub-accent`, `--wajub-radius`, `--wajub-border`, `--wajub-surface`, `--wajub-success`, `--wajub-error` and `--wajub-warning`, plus `--wajub-info`, `--wajub-button-bg`, `--wajub-button-color` and `--wajub-gap` for the pay forms. The list is at the top of `assets/css/frontend.css` and `assets/css/checkout.css`.
* **For developers:** The admin scripts no longer need jQuery.

= 1.2.1 =
* Returns: every integration reads the returning payer's payment by its Wajub ID (an order or donation made before 1.2.1 is found by its reference), and leaves a payment that is not final yet to the webhook, instead of failing the order.
* Refunds: refunds started from WordPress are accepted again, a refund made in the Wajub Dashboard is recorded in WooCommerce, and a WooCommerce partial refund is no longer recorded twice.
* Paid content and courses: `[wajub_pay_content]` unlocks once paid, at the price its page signs (a pay form changed in the browser is refused; clear the page cache after updating). LearnDash, LifterLMS and Tutor LMS course buttons enrol only the logged in account that paid at least the course's price.
* Also: MemberPress sends its receipts, Tutor Ecommerce keeps its Wajub IDs under keys of its own, GiveWP 3 keeps the Wajub payment ID and reads `partially_refunded`, and the legacy webhook address answers `403` to a refused delivery.

= 1.2.0 =
* WooCommerce Subscriptions is no longer declared as a supported feature — it was never actually implemented (no renewal charge), which could silently break recurring revenue. Only one-time payments are supported.
* Inline and overlay checkout now happen directly on the checkout page — customers no longer leave the checkout to reach a separate "pay for order" page.

= 1.1.0 =
* Maintenance release, with no change to the plugin's actions, filters or settings.

= 1.0.0 =
* Initial release with WooCommerce, GiveWP, EDD, LMS, Gravity Forms, and shortcodes.
