WC Cancel Order Pro
Customers request full or item-level cancellations of WooCommerce orders from My Account or an email link. You approve or decline, refund through the gateway, manually or as store wallet credit, and can take a cancellation fee.
Version 4.10.0 WooCommerce plugin Paid plugin Requires WordPress 6.7 Requires PHP 7.4 Tested up to 7.1 Updated 6 Oct 2026
1. Overview
WC Cancel Order Pro adds a cancellation workflow to WooCommerce. A customer asks to cancel a whole order, or only some items and quantities, from My Account → Orders. A guest does the same from a link in the order email. You review every request on one screen and approve or decline it. When you approve, you choose how to refund: through the payment gateway, as a manual refund, or as store wallet credit. The plugin creates the WooCommerce refund record, can restock the items and can take a cancellation fee off the refund.
This page documents version 4.10.0. In the admin menu and in the WooCommerce settings the plugin appears as "WC Cancel". It is the paid version of WC Cancel Order and uses the same settings, request table and statuses (see WC Cancel Order and WC Cancel Order Pro together).
How it works
- The customer clicks Cancel Request on an eligible order. If partial cancellation is on, a popup first asks "Full Cancellation" or "Partial Cancellation".
- The customer picks a reason (from your list), may add details, and for a partial request chooses the items and quantities.
- For most statuses the request waits for you: the order moves to Cancel Request (whole order) or Partial Cancellation Request (items), and the store is emailed. For statuses you list under "Allow immediate full cancellation", a full cancellation happens at once with no approval.
- You open WooCommerce → WC Cancel, read the request, choose a refund method and approve it, or decline it.
- The order moves to the status you chose, stock can be restored, the refund is recorded and the customer is emailed.
At a glance
Full and partial
Cancel the whole order or only chosen items and quantities. One Cancel Request button offers both when both are possible.
Approval screen
Full and partial requests in one list with type, items, status, refund and fee, status links, search, and bulk approve and decline.
Three refund methods
Automatic gateway refund, manual refund or wallet credit, with transaction ID, amount and date recorded on the request.
Store wallet
Wallet credit, a Wallet tab for customers, an admin wallet overview, and a Store Wallet payment method.
Cancellation fee
A fixed amount or a percentage of the order total, shown before the customer confirms and taken off the refund.
Rules you control
Order statuses, user roles, a time limit, your own reasons, the status after approval and after decline, and instant cancellation for chosen statuses.
What it does not do
- It does not refund anything until you enable at least one refund method. With none enabled, an approval only changes the order and no refund is made.
- It does not refund on an instant cancellation. An instant cancellation changes the order status only.
- It does not create a cancellation fee as an order fee line. The fee is the part of the amount that is not refunded.
- It does not need a licence to work. A licence only unlocks update packages (see Licence and updates).
- It has no REST routes, WP-CLI commands, blocks, cron events or user roles of its own.
2. Requirements
| Item | Requirement |
|---|---|
| WordPress | 6.7 or later (tested up to 7.1). The plugin header also carries Requires Plugins: woocommerce, which WordPress reads for its plugin-dependency check. |
| PHP | 7.4 or later. |
| WooCommerce | 8.0 or later (tested up to 11.1). WooCommerce must be active and installed in the folder woocommerce: the plugin starts only when woocommerce/woocommerce.php is in the active plugins list (site-wide, or network-wide on multisite). Without WooCommerce the plugin does nothing and shows no message, including no licence screen. |
| Permalinks | Pretty permalinks (anything except Plain) for the guest cancellation page. WordPress only provides the request path the guest page matches when rewrite rules are in use (checked in WordPress 7.1.2, WP::parse_request). The customer Wallet tab is a WooCommerce My Account endpoint: with Plain permalinks WooCommerce links to it as a query variable (checked in WooCommerce 11.1.2, wc_get_endpoint_url()); with pretty permalinks its rewrite rule must have been flushed (see Troubleshooting). |
| Database | MySQL or MariaDB with InnoDB. Wallet and approval locks use GET_LOCK(); a table row in the options table is the fallback when it is unavailable. |
| Refunds | Automatic gateway refunds need a payment gateway that supports refunds. Wallet credit needs an order placed by a logged-in customer. |
| Optional plugins | WooCommerce Subscriptions (cancels the parent order's subscriptions on approval), Ultimate Member (button in its WooCommerce orders tab) and WPML (the plugin ships a wpml-config.xml). None is needed. |
| Server | Outbound HTTPS to wpexpertshub.com for licence activation and update checks only. |
| Free plugin | WC Cancel Order (free) must not be active. Pro deactivates it. |
Declared compatibility with WooCommerce features: High-Performance Order Storage (custom_order_tables), Cart and Checkout blocks (cart_checkout_blocks), analytics, new navigation, product block editor and marketplace. While Pro is active and the free plugin's folder is present, Pro also declares the free plugin's own compatibility list (the same six, plus order attribution and HPOS full-text search indexes) on the free plugin's behalf, so WooCommerce does not flag it as incompatible.
3. Installation
- Make sure WooCommerce is installed and active.
- Open Plugins → Add Plugin → Upload Plugin, choose the zip file of WC Cancel Order Pro from your purchase, click Install Now, then Activate Plugin.
- If the free WC Cancel Order plugin was active, WordPress shows the notice "WC Cancel Order (free) has been deactivated because WC Cancel Order Pro is now active. Only the Pro version is needed." Your settings and requests carry over.
- Open Plugins → WpExperts Hub Licences, paste your licence key and click Activate licence (see Licence and updates).
- Open WooCommerce → Settings → WC Cancel and review every setting. On a first install the defaults let Processing and On hold orders cancel at once; see Settings.
- Optional: check the Store Wallet payment method under WooCommerce → Settings → Payments.
What activation creates
- The tables
{prefix}wc_cancel_orders,{prefix}wc_cancel_partial_requests,{prefix}wc_cancel_partial_itemsand{prefix}wc_cancel_wallet_txns(an existingwc_cancel_orderstable from the free plugin is kept and extended). See Database tables. - The options
wc_cancel_db_versionandwc_cancel_pro_schema_version, and a one-time flagwc_cancel_pro_flush_rewritethat makes WordPress refresh its rewrite rules on the next request so the My Account Wallet tab works at once. - Three order statuses registered at run time (not stored): Cancel Request, Partial Cancellation Request and Partially Cancelled.
- Seven WooCommerce emails and one payment method, "Store Wallet". Their settings are saved when you save them in WooCommerce.
- On the Plugins screen, two links under the plugin name: Settings and Activate License (it reads Deactivate License once a key is active).
It creates no pages and no scheduled events. The guest cancellation page is a virtual page served at /guest-cancel-req/. It deactivates the free WC Cancel Order plugin when it is active.
Licence and updates
WC Cancel Order Pro bundles the WpExperts Hub licence and update client (version 2.0.0 of the client). One screen, Plugins → WpExperts Hub Licences (capability manage_options), manages the licences of every WpExperts Hub plugin on the site that bundles the client; each plugin gets a card with an Active or Not active label and its installed version.
- Find your licence key in your purchase email, or in My Account → Downloads on wpexpertshub.com.
- Open Plugins → WpExperts Hub Licences, find the card "WC Cancel Order Pro", paste the key into the box and click Activate licence. Only letters, digits and hyphens are kept from what you paste. The server's answer is shown on the card.
- To deactivate, click Deactivate licence on the same card. Do this before moving the plugin to another live site. The key and the Active status are removed from this site even if the server cannot be reached; the card then reads "Licence removed from this site."
- If you cannot find your key, open "Can't find your key? Email it to me" on the card, enter the email address used for the purchase and click Send licence key.
- Updates arrive through the normal WordPress updates screen (Dashboard → Updates and the Plugins screen). The client adds the plugin to WordPress's update data and answers the "View details" popup with a link to the plugin page, documentation and changelog.
- The update information is cached for 12 hours in the site transient
wpxh_licence_check_v2. If the server cannot be reached, the previous data is kept and the check is tried again after an hour. Saving or removing a licence, and finishing an update, clear the cache. - Without an active licence an available update shows "Automatic update is unavailable for this plugin. Activate your licence to enable updates." and the Plugins screen shows the notice "Activate your WpExperts Hub licence to receive updates for: WC Cancel Order Pro." with a "Manage licences" link. The client uses the update package address the server returns, and the server decides whether to return one for a site (that part is not in the plugin).
- The plugin works without a licence. Nothing in the plugin checks the licence status except the Activate License and Deactivate License link text on the Plugins screen.
- If the server later reports that the licence is no longer active for the site, the client marks it inactive on the site.
- What is sent to wpexpertshub.com, and when, is listed under Privacy.
Updating and what is kept
Update from the WordPress updates screen. Updating keeps settings, requests, wallet balances and email settings. After an update, the first request runs a schema check (once per plugin version, option wc_cancel_pro_schema_version): it creates any missing table, adds missing columns to the request tables, and adds missing indexes. Deactivating the plugin removes nothing.
Multisite
The plugin starts when WooCommerce is active on the site or network-wide. Each site has its own tables (its own prefix), settings, wallet balances and requests. The schema check runs on each site's first request after activation or update, so every site gets its tables. When Pro is network-activated it deactivates the free plugin network-wide. On deletion, uninstall.php repeats the clean-up for every site (see Privacy).
4. Quick start
- Activate the plugin, then activate your licence under Plugins → WpExperts Hub Licences.
- Open WooCommerce → Settings → WC Cancel. Under Common Settings, choose the order statuses that may request a cancellation, edit the reasons and the popup note.
- Under Full Cancellation, decide which statuses may cancel at once. Clear Allow immediate full cancellation for order statuses if you want to approve every request yourself.
- Under Refund (Partial & Full Cancellation), tick the refund methods you will use and choose the default. Tick Auto restock if refunds should put items back in stock.
- Optional: tick Enable partial cancellation under Partial Cancellation, and set up a Cancellation Fee. Click Save changes.
- Open WooCommerce → Settings → Emails and check the recipient of "Cancellation Request Received" and "Partial Cancellation Request Received".
- Place a test order, sign in as the customer, and click Cancel Request in My Account → Orders.
- Open WooCommerce → WC Cancel, click Approve Request, choose a refund method and click Confirm.
5. Features
The Cancel Request button
The plugin adds an order action called Cancel Request (action key wc-cancel-order) through the WooCommerce filter woocommerce_my_account_my_orders_actions at priority 100. WooCommerce 11.1.2 uses that filter for the buttons in My Account → Orders and for the "Actions" row of the order page. The plugin also removes WooCommerce's own Cancel action from that list. The final list passes through the filter wc_cancel_order_btn.
Which popup opens depends on the order:
| Situation | What the customer gets |
|---|---|
| The order's status is in "Allow immediate full cancellation for order statuses" | The full cancellation popup. The cancellation happens at once (see Immediate cancellation). Partial cancellation is not offered for these statuses, even when it is enabled. |
| The status is in "Allow cancellation requests for order statuses", partial cancellation is on, and at least one item still has a quantity that can be cancelled | A choice popup titled "Cancellation Options" with two buttons: "Full Cancellation" (Cancel the entire order.) and "Partial Cancellation" (Cancel specific items or quantities.). |
| The status is in "Allow cancellation requests for order statuses" and partial cancellation is off, or nothing is left to cancel | The full cancellation popup. The request waits for your decision. |
The same button is added in other places: in the WooCommerce Subscriptions subscription view and subscriptions list, and in the Ultimate Member orders tab (see Integrations), and on the guest page.
Hiding the button with wc_cancel_order_btn does not stop a request. The server checks the order again when the popup is submitted, but it does not use that filter.
The popup scripts and styles load only where they are needed: the orders, view-order, order-received, wc-wallet, view-subscription and subscriptions endpoints, the guest page, and pages whose content contains [wc_cancel_order_details] or the Ultimate Member account shortcode. Use the filter wc_cancel_order_init_script to load them elsewhere, and the actions wc_cancel_order_before_script and wc_cancel_order_after_script to run code around the enqueue.
Who can cancel, and when
The Cancel Request button appears, and the server accepts a request, only when all of these are true:
- Status. The order's status is in Allow cancellation requests for order statuses or in Allow immediate full cancellation for order statuses. A partial request needs the first list.
- Time limit. If Hide cancel button after a time limit is on and a limit above 0 is set, the order is not older than the limit. Age is counted from the order's creation date.
- No earlier decision. The order has no full request that was approved or declined, and no partial request that is pending or declined. After a declined request of either kind, the button never returns for that order. After an approved partial request the button can return if the order's new status (by default Partially Cancelled) is added to the allowed statuses and some quantity is left.
- Role. A logged-in visitor must have one of the roles selected in Customer roles (default Administrator and Customer). A logged-out visitor, for example a guest using the email link, needs Allow guest cancellation; the role setting does not apply to logged-out visitors.
- Ownership. The visitor is the customer of the order, or holds the order's key (the cancel key from the guest link, or WooCommerce's own order key).
A shop manager, for example, is not in the default roles; add the role if staff should be able to use the button on their own orders.
Full cancellation request
The popup is titled "Request Order Cancellation" with the line "Order #" and the order number. It contains, from top to bottom:
- Your note from Note shown in the cancel popup, if any, preceded by an asterisk.
- A list of radio buttons headed "Please select a reason for cancellation.", built from Cancellation reasons. Nothing is shown when you define no reasons.
- A text box headed "Additional details", depending on Additional text field: shown at once ("Display always"), never ("Disable"), or only after a chosen reason is selected.
- When a fee applies, a card "You will be charged X as a cancellation fee." (see Cancellation fee).
- Two buttons: Close and Confirm Cancellation.
The popup checks the rules and shows "Cancellation reason required!" or "Additional details required!" before it sends. The server checks them again, so a request that bypasses the popup is refused with the same message. A reason is only required when Require a cancellation reason is on and at least one reason is defined. The text box is only required when Require text input is on and the box is shown (always in "Display always" mode; only after the matching reason in a reason mode; never in "Disable" mode). The server keeps the first 500 characters of the reason and 5,000 of the details.
When a request that waits for approval is accepted, the plugin, under a lock for the order:
- Reads the order fresh from the database and runs the checks above.
- Saves the reason in the order meta
_wc_cancel_reason, the details in_wc_cancel_additional_txt, and the current status in_wc_cancel_prev_status. - Writes the request row (
is_approved= 0). - Sets the order status to Cancel Request with the order note "Order Status updated by Wc Cancel Order." The status change sends the "Cancellation Request Received" email to the store.
- Fires the action
wc_cancel_request.
On success the popup shows "Cancellation request sent successfully." and the page reloads after about 1.5 seconds. Errors keep the popup open and show a message, for example "Your cancellation request is being processed. Please try again in a moment." (another request for the same order held the lock for 10 seconds) or "Something went wrong. Please check your connection and try again."
admin-ajax.php. Following it sends the request straight away when nothing is required of the customer: for an order in an immediate-cancellation status that means the order is cancelled with no confirmation. Make sure the script loads on every page where the button appears.Immediate cancellation
For orders whose status is in Allow immediate full cancellation for order statuses, a full cancellation needs no approval. When the customer confirms, the plugin:
- Saves the reason, details and previous status as above, and writes the request row already approved (
is_approved= 1). - Sends the "Cancellation Request Received" email to the store, so you still hear about it.
- Sets the order to the status chosen in Status after full cancellation is approved (default Cancelled), with the order note "Order Status updated by Wc Cancel Order."
- Sends the "Cancellation Request Approved" email to the customer (once; an admin who cancels an order by hand does not trigger it).
- When WooCommerce Subscriptions is active, cancels the subscriptions of the parent order.
- Fires the action
wc_cancel_request.
An instant cancellation does not refund anything and does not apply a fee. Stock goes back through WooCommerce only: WooCommerce 11.1.2 restores the stock of an order that had reduced it when the order becomes Cancelled, Pending or Failed. If you choose another status for Status after full cancellation is approved, nothing restores the stock.
Partial cancellation
Partial cancellation is off until you tick Enable partial cancellation. The customer then sees the choice popup, or goes straight to the partial popup. The partial popup, "Request Partial Cancellation", loads the cancellable items from the server and shows:
- Your note, a table with the columns "Product" and "Quantity", and for each item a number box with + and − buttons that stop at 0 and at the quantity still available, with "/ N" showing that maximum.
- When at least one refund method is enabled for the order: a line "Estimated refund" with the amount, and "(after the cancellation fee)" when a fee applies. The estimate uses the gross value of one unit (line total plus tax, divided by the ordered quantity); the amount actually refunded is worked out on approval.
- The reasons and additional text box as in a full request, the fee card, and the buttons Close and Submit Request.
The quantity available for an order line is the ordered quantity minus the quantity in approved partial requests, minus the quantity refunded outside the plugin (for example on the order screen), minus the quantity in other pending partial requests. Lines with nothing available are not listed. This means a quantity you refunded by hand can no longer be requested.
On submit the server refuses: no quantity chosen ("Please select a quantity to cancel."), a quantity that is not a whole positive number such as -2 or 1.5 ("Invalid quantity."), a quantity above the available quantity ("Requested quantity exceeds available quantity."), or a missing reason or text under the same rules as a full request. Otherwise, under a lock for the order, it:
- Writes a row in
{prefix}wc_cancel_partial_requests(statuspending, the reason, the details and the date) and one row per item in{prefix}wc_cancel_partial_itemswith the requested quantity and the line's total and tax at that moment. - Saves the current status in
_wc_cancel_prev_statusand sets the order to Partial Cancellation Request with the order note "Partial cancellation request submitted." - Adds the order note "Partial cancellation request #N submitted for X item(s)."
- Fires the action
wc_cancel_partial_requestand sends the "Partial Cancellation Request Received" email to the store.
The customer sees "Partial cancellation request submitted successfully." and the page reloads. While the request is pending, the order is in Partial Cancellation Request, which is never one of the allowed statuses, so no second request can be started.
Guest cancellation
With Allow guest cancellation on (the default), the plugin adds a block to WooCommerce customer emails (hook woocommerce_email_customer_details, priority 999): the heading "Want to cancel this order?" and a link "Cancel Order". In plain-text emails it prints the heading and the URL. It is not added to emails sent to the store, nor to the plugin's own seven emails.
The plugin evaluates the order as a logged-out visitor, so the link appears only if a guest could really cancel: guest cancellation on, the status allowed, the time limit not passed, no earlier decision, and (for partial) at least one item left. The link is:
https://example.com/guest-cancel-req/?key=THE_KEY
The key is the order meta _wc_cancel_key, written at checkout. When an order has none (it was placed before the plugin was installed), the link uses WooCommerce's own order key, and the guest page accepts that key too.
The page is virtual: titled "Order Details", with the shortcode [wc_cancel_order_details] as its content, shown in your theme's page layout. It displays the order with WooCommerce's order page template, including the Cancel Request button, and the popups work as for a logged-in customer. With a wrong key, or with guest cancellation off, it shows "Sorry, this order is invalid and no details found."
On the order-received (thank-you) page a guest can cancel too; the page's key is WooCommerce's order key, which the plugin accepts.
Approving and declining
All decisions are made on WooCommerce → WC Cancel (see The Cancellation Requests list). Each row has View Request, and pending rows have Approve Request and Decline Request. Each button opens a popup.
View Request
Full request ("Cancellation Request Detail"): Cancellation Reason, Additional details, Date, and once decided the banner "Cancellation Request Approved." with the Refund line (amount and method), Cancellation fee, Gateway and Transaction ID (for gateway refunds) and Date, or "Cancellation Request Declined." with its Date. Partial request ("Partial Cancellation Request Detail"): Order, Reason, Additional details (when given), Requested on, the table "Requested Items" (Product, Qty) and, once decided, an "Approved" banner with the refund details and the date, or a "Declined" banner with the date.
Approve a full request
The popup "Approve Cancellation Request ?" shows the request details. When at least one refund method is enabled for the order it also shows "Refund amount:" (what is still refundable on the order, minus the fee), "Refund method:" with one radio button per enabled method, and "Cancellation fee:" when a fee applies. The default method is pre-selected. When none is enabled, none of this is shown and the request is approved without a refund. On Confirm the plugin, under a lock for the order:
- Checks that the order is still in Cancel Request. If not: "This order has no pending cancellation request."
- Checks the chosen method against the enabled methods (see Refunds); if it is not available, it uses the default method, or none.
- Works out the amount: the amount still refundable on the order (
get_remaining_refund_amount()) minus the fee, never below 0. The fee is the smaller of the fee setting and that amount. - When the amount is above 0, creates the WooCommerce refund record (for the remaining quantity of every product line, so stock can be restored when Auto restock is on) and, for the gateway method, asks the gateway to refund; for the wallet method, credits the customer's wallet.
- Records the decision on the request row (approved, decision date, refund method, amount, fee, refund ID, gateway transaction ID and gateway name) and in the order meta
_wc_cancel_request_data. The request row's date is refreshed to the moment of the decision. - Adds the order note "Full cancellation request #N approved. Refunded X via METHOD." (with "Cancellation fee: Y." when there is one), or "Full cancellation request #N approved. No refund was issued."
- Sets the order to the status from Status after full cancellation is approved (default Cancelled) and sends the "Cancellation Request Approved" email. When the refund was a wallet credit it also sends the "Wallet Credit Received" email. The plugin sends the approval email itself, once, even if WooCommerce or another plugin changed the status in between.
- When WooCommerce Subscriptions is active, cancels the subscriptions of the parent order. Then it fires the action
wc_cancel_full_request_approved.
Approve a partial request
The popup "Approve Partial Cancellation Request ?" shows the request details and the requested items, then "Refund amount:", "Cancellation fee:" and "Refund method:" in the same way. On Confirm the plugin, under a lock for the request:
- Checks that the request is still pending ("Request already processed.") and has items ("Request has no items."), and that each quantity is still available ("Quantity for PRODUCT is no longer available.").
- Works out each line's refund: the line total (and each tax rate's share) for the units cancelled so far including this request, rounded to the store's price decimals, minus the same figure for the units cancelled before. Rounding the running total keeps the requests for one line adding up to exactly the rounded line total.
- Subtracts the fee (the smaller of the fee setting and the amount) and creates the refund like a full approval, with the line quantities, amounts and taxes per tax rate.
- Marks the request approved, with the decision date, method, amount, fee, refund ID, transaction ID, gateway name and the approved quantity of each item.
- Sets the order status: Status when fully cancelled when every item of the order is now cancelled or refunded, otherwise Status when partially cancelled.
- When Auto restock is on and no WooCommerce refund was created (no refund method enabled, or the fee used up the refund), puts the cancelled units back in stock itself, only as far as stock was reduced for that line, and adds the order note "Restocked PRODUCT (cancelled units back in stock, new stock level: N)."
- Adds the order note "Partial cancellation request #N approved. Refunded X via METHOD." (or "... No refund was issued.") and sends the "Partial Cancellation Request Approved" email, plus "Wallet Credit Received" for a wallet credit.
Decline
The popups "Decline Cancellation Request ?" and "Decline Partial Cancellation Request ?" show the request details with Close and Confirm. A full decline records the request as declined, sets the order to the status from Status when a request is declined with the order note "Cancellation Request Declined." and sends the "Cancellation Request Declined" email. A partial decline does the same with the note "Partial cancellation request declined." plus "Partial cancellation request #N declined.", and the "Partial Cancellation Request Declined" email. If Status when a request is declined is "Restore the status before the request", the order returns to the status saved when the customer asked; when none is saved, or it is Cancel Request, Partial Cancellation Request, Cancelled, Refunded, Failed or no longer exists, the order goes to Processing. The filter wc_cancel_decline_status can change the result.
If two people click at the same moment, the second sees "This request is being processed right now. Please wait a moment and reload the list." After a successful decision the list reloads.
Bulk approve and decline
The requests list has the bulk actions Approve Request and Decline Request, for full and partial requests together. After the action a notice reports how many were approved or declined, and a warning lists the messages of the ones that failed (for example a row that was already decided).
- Bulk approval uses the Default refund method for every request. If that method is not enabled for an order (for example the default is Manual refund but only Wallet credit is enabled), that request is approved without a refund, even when other methods are enabled.
- Bulk approval issues the same refund, restock, status and email steps as single approval.
- Rows that cannot be decided any more are not skipped silently: they come back as errors.
Refund methods
A method is offered on the approval popup only when it is enabled under Refund methods on approval and, where needed, available for that order. The filter wc_cancel_refund_methods can change the list.
| Method | Offered when | What happens |
|---|---|---|
| Automatic gateway refund | The setting is on and the order's payment gateway supports refunds. | The plugin creates the WooCommerce refund record, then calls the gateway's refund function directly. It records the gateway's transaction ID (from the gateway's answer, else the refund record, else the order's own transaction ID) and the gateway name. If the gateway reports a failure, the refund stays on the order as a manual refund, an order note says the money was not returned through the gateway ("Automatic gateway refund of X failed (REASON). The refund was recorded as a manual refund; please refund the customer manually."), and the request records the method as Manual refund. |
| Manual refund | The setting is on. | The plugin creates the WooCommerce refund record only. You pay the customer yourself. |
| Wallet credit | The setting is on and the order has a customer account (not a guest order). | The plugin creates the WooCommerce refund record and adds the amount to the customer's store wallet, with a ledger entry and the "Wallet Credit Received" email. |
| No refund | No method is enabled, or the net amount is 0. | The request is approved, the order note says "No refund was issued.", and no refund record is created. |
- Each refund is rounded to the store's price decimals and never exceeds what WooCommerce still allows to refund on the order.
- The plugin keeps control of the final status: while it creates the refund it blocks WooCommerce from switching a fully refunded order to Refunded (filter
woocommerce_order_fully_refunded_status, priority 999, returns no status for that call only), so your chosen approval status is applied and the approval email is sent. - An order paid with the Store Wallet method can be refunded from the order screen: the amount returns to the customer's wallet and the order gets the note "X refunded to the customer's store wallet."
Cancellation fee
When Enable cancellation fee is on, the fee is shown to the customer and taken off the refund. It is only shown and used when a refund method is available for the order.
- Fixed amount: the amount in Cancellation fee amount.
- Percentage: the percentage in Cancellation fee (% of order total) of the order's total (not of the cancelled items), rounded to 2 decimals.
- A fee of 0 or less is treated as no fee.
- The popup shows "You will be charged X as a cancellation fee." before the customer confirms. The approval popup shows "Cancellation fee:" and the net "Refund amount:". The fee is also in the order note, the approval email ("Cancellation fee:" line), the request detail popup and the requests list.
- The fee is charged per approved request: a full approval takes it once; every partial approval takes it again. The fee never exceeds the refund amount, so a small refund can be reduced to 0, in which case no refund record is created.
- No fee is taken when no refund is issued (no method enabled, or "No refund").
Stock
Two things can restore stock, and the plugin makes sure the same units are not restored twice:
- WooCommerce itself. In WooCommerce 11.1.2 an order that reduced stock gets it back when it becomes Cancelled (and Pending or Failed). So a full cancellation to Cancelled restores stock whether or not Auto restock is on. Partial statuses such as Partially Cancelled do not trigger this.
- Auto restock. When on, the refund the plugin creates restores the refunded quantities. That lowers the line's reduced-stock record, so WooCommerce's own restore on Cancelled does not add the same units again. For partial approvals without a refund, the plugin restocks the units itself (see above).
Restocking applies only to products that manage stock.
Store wallet
The store wallet is a credit balance kept per customer. It is filled by wallet refunds and spent through the Store Wallet payment method.
Balance and ledger
- The balance is stored in the user meta
_wc_cancel_wallet_balanceand cannot go below 0. Wallet amounts are rounded to 2 decimals when stored, whatever the store's price decimals (the ledger columns hold 4). Every credit and debit is also written to{prefix}wc_cancel_wallet_txnswith its type (credit or debit), amount, the balance after, a note (up to 255 characters), the order and the date. - Credits come from wallet refunds on approvals and from refunds of orders paid with the wallet. Debits come from paying with the wallet.
- Every change runs under a lock per customer and re-reads the real balance, so two payments at the same moment cannot spend the same money. A payment that cannot get the lock within 10 seconds is refused; a credit still goes through.
Store Wallet payment method
The gateway (ID wc_cancel_wallet) appears under WooCommerce → Settings → Payments with the fields Enable/Disable ("Enable store wallet payment method", default on), Title (default "Store Wallet") and Description (default "Pay using your store wallet balance."). It supports products and refunds. At checkout it is available only when the shopper is logged in, has a balance above 0, and the balance covers the whole total of the cart or of the order being paid; partial wallet payments are not possible. The description shows "Available Balance: X".
On payment it checks that the order belongs to the logged-in shopper, re-checks the balance under the lock, debits the total with the note "Payment for order #N", adds the order note "Paid in full using store wallet (X).", marks the order paid (WooCommerce then reduces stock) and empties the cart. Messages: "You must be logged in to use your store wallet.", "Your store wallet balance is empty.", "Your store wallet balance (X) is less than the order total (Y). Please choose another payment method to complete your order." and "Your store wallet balance no longer covers this order. Please choose another payment method to complete your order."
Customer wallet page
Customers get a Wallet tab in My Account (endpoint wc-wallet), placed after Orders. It shows "Wallet Balance", "Available Balance", the amount, the hint "Store credit from your cancelled orders. Choose "Store Wallet" at checkout to use it." and the table "Transactions" with Date, Type (Credit or Debit), Order (a link when the order is the customer's), Amount (+ or −), Balance and Note. It lists the latest 50 transactions and says "Showing your latest 50 transactions." when there are that many. With none it says "No wallet transactions yet. Refunds credited to your wallet will appear here."
Admin wallet overview
See The Wallet screen.
Order statuses
| Status (slug) | Meaning |
|---|---|
Cancel Request (wc-cancel-request) | A full cancellation request is waiting for a decision. Shared with the free plugin. |
Partial Cancellation Request (wc-partial-request) | A partial request is waiting for a decision. |
Partially Cancelled (wc-partial-cancelled) | The default status after an approved partial request that leaves some items. You can pick another in Status when partially cancelled. |
All three are public, appear in the status links of WooCommerce → Orders with a count, and are offered in the order status drop-down. The two request statuses are never offered as allowed or result statuses in the plugin's settings. WooCommerce's internal Draft status is never offered either.
When you change the status yourself
If you move an order out of Cancel Request by hand (order screen, bulk action, another plugin), the plugin settles the request row if it is still pending: approved when the new status is the approval status, Cancelled or Refunded; declined for any other status except Failed and Trash, which leave the row alone. It then sends the Approved email (new status equals the approval status) or the Declined email (any status except Refunded, Failed, Trash and Cancelled). Moving an order into Cancel Request by hand sends the "Cancellation Request Received" email to the store. Before version 4.10.0 a request moved out of Cancel Request by hand stayed Pending for ever. Partial requests are not settled this way: only the Approve and Decline buttons change them. An order moved into Cancel Request by hand has no request row, so it is not listed on the requests screen (the menu bubble still counts it); move it out of Cancel Request by hand to settle it.
Pro saves the status to restore when the customer submits a request. Moving an order into Cancel Request by hand does not save one, so "Restore the status before the request" then uses whatever an earlier request stored, or Processing.
Emails
The plugin adds seven emails to WooCommerce → Settings → Emails. Their content comes from WooCommerce's email header and footer, the order details table, the order meta and the customer details, plus the parts described below.
| Email (ID) | Sent to | When | Default subject | Default heading |
|---|---|---|---|---|
Cancellation Request Received (wc_cancel_request_received) | The recipients you set, default the site admin email. | A full request is received (order moves to Cancel Request), or an immediate cancellation happens. | [{site_title}]: Order (#{order_number}) cancellation request received. | Order cancellation request received |
Cancellation Request Approved (wc_cancel_request_approved) | The order's billing email. | A full request is approved, or an immediate cancellation happens, or the order moves from Cancel Request to the approval status. | [{site_title}]: Order (#{order_number}) cancellation request approved. | Order cancellation request approved |
Cancellation Request Declined (wc_cancel_request_declined) | The order's billing email. | A full request is declined, or the order leaves Cancel Request for another status (not Refunded, Failed, Trash, Cancelled or the approval status). | [{site_title}]: Order (#{order_number}) cancellation request declined. | Order cancellation request declined |
Partial Cancellation Request Received (wc_cancel_request_partial_received) | The recipients you set, default the site admin email. | A partial request is submitted. | [{site_title}]: Partial cancellation request (#{order_number}) received. | Partial cancellation request received |
Partial Cancellation Request Approved (wc_cancel_request_partial_approved) | The order's billing email. | A partial request is approved. | [{site_title}]: Partial cancellation request (#{order_number}) approved. | Partial cancellation request approved |
Partial Cancellation Request Declined (wc_cancel_request_partial_declined) | The order's billing email. | A partial request is declined. | [{site_title}]: Partial cancellation request (#{order_number}) declined. | Partial cancellation request declined |
Wallet Credit Received (wc_cancel_wallet_credit) | The order's billing email. | A refund is credited to the wallet (wallet method on a full or partial approval). | [{site_title}]: Wallet credit received for order (#{order_number}). | Wallet credit received |
- The full "Received" and the partial "Received" emails contain a link "Review this request" to WooCommerce → WC Cancel filtered to Pending. The three full-cancellation emails (Received, Approved, Declined) also print the customer's reason ("Cancellation Reason:") and details ("Additional Details:", not when the text box is set to Disable).
- The full Approved email shows "Refund:" with the amount and method, and "Cancellation fee:" when a refund was recorded. The partial Approved email lists the "Cancelled Items" (product and approved quantity), the refund and the fee. The partial Received email lists the "Requested Items", reason and details. The wallet email says "A refund of X has been credited to your store wallet for order (#N)." and "You can use this wallet balance as store credit on your next checkout."
- Refund methods are named in words ("Wallet credit", "Manual refund", "Automatic gateway refund"), not by stored codes. Plain-text customer emails contain no admin links and no SKUs.
- The two "Received" emails have the fields Enable/Disable, Recipient(s), Subject, Email heading and Email type. The other five use WooCommerce's standard email fields (Enable/Disable, Subject, Email heading, Additional content, Email type, and any extra fields your WooCommerce version adds). The plugin's templates do not print "Additional content", so it has no visible effect on them.
- Subject and heading accept
{order_date},{order_number}, and WooCommerce's{site_title},{site_address}and{site_url}. - The customer-facing partial Declined email contains code for an "Admin note", but the request table has no such column, so that line never appears.
- The plugin sends each full-cancellation email once per order in a PHP request, even when more than one hook would trigger it.
Integrations
- WooCommerce Subscriptions. The Cancel Request button is added to the subscription view (filter
wcs_view_subscription_actions, using the subscription's parent order) and to the subscriptions list (actionwoocommerce_my_subscriptions_actions). On a full approval or an instant cancellation the plugin cancels the subscriptions of the parent order withWC_Subscriptions_Manager::cancel_subscriptions_for_order(). Without WooCommerce Subscriptions nothing happens. This integration was not tested here because WooCommerce Subscriptions is not installed on the development site. - Ultimate Member. The button is added through the action
um_woocommerce_orders_tab_actions, and the popup assets load on pages containing theultimatemember_accountshortcode. This was not tested here. - HPOS. The requests list and the key lookups use the HPOS tables when High-Performance Order Storage is on, and the posts tables otherwise. Order links use WooCommerce's own edit URL.
- Cart and Checkout blocks. The cancel key is written for orders placed with either checkout, and the Store Wallet method has a blocks integration (see Store wallet).
- Translations and WPML. Text domain
wc-cancel-order-pro. The plugin ships a template (wc-cancel-order-pro.pot) and translations for en_US, es_ES, fr_FR, ko_KR, nl_NL and pl_PL.wpml-config.xmlmarks the order meta_wc_cancel_reason,_wc_cancel_additional_txtand_wc_cancel_request_dataas translatable, and the settings keysreason-optionsandconfirm-noteas admin text. This was not tested against WPML here.
Order screens
- Order edit screen, items table: two extra columns, "Cancelled Qty" (the quantity in approved partial requests, or a dash) and "Remaining Qty" (ordered quantity minus approved and externally refunded quantity). They appear on every order. Shipping, fee and refund rows get empty cells so the columns stay aligned.
- Order edit screen, meta box "Partial Cancellation History": a table with the columns Request, Items (with the approved quantity), Status (Pending, Approved or Declined), Reason (with the details underneath), Refund (amount and method, or "No refund"), Cancellation Fee and Date. With no partial requests it says "No partial cancellation requests."
- Customer order page: next to a line's quantity, once something was cancelled, the plugin adds "(Remaining: X · Cancelled: Y)".
- Order notes record each step (see the messages quoted above).
Protection against double processing
The plugin takes named locks (MySQL GET_LOCK(), with a row in the options table named wcc_lock_ plus a hash as the fallback; the fallback row expires after 120 seconds if a request crashes):
| Lock | Protects | Wait |
|---|---|---|
order-ID | A customer's full or partial request for one order. | Up to 10 seconds, then "Your cancellation request is being processed. Please try again in a moment." |
full-ID | Approving or declining a full request. | None; the second person sees "This request is being processed right now. Please wait a moment and reload the list." |
request-ID | Approving or declining a partial request. | None; same message. |
wallet-USERID | Every wallet credit and debit for one customer. | Up to 10 seconds (a payment is refused if it cannot get the lock; a credit still goes through). |
After taking a lock, the order is read again from the database, not from a cache. The effect: a double click, two tabs or two admins produce one request, one refund and one email.
WC Cancel Order and WC Cancel Order Pro together
WC Cancel Order (the free plugin, folder wc-cancel-order, version 3.7.0) and WC Cancel Order Pro (folder wc-cancel-order-pro, version 4.10.0) are two builds of one cancellation workflow. They read and write the same settings option, the same request table, the same order status and the same email settings. A store can therefore move from one to the other without entering its data again. Only one of them runs at a time.
Which plugin runs
- Pro is active: the free plugin does not start. Its main file starts the plugin only when WooCommerce is active and
wc-cancel-order-pro/wc-cancel-order-pro.phpis not in the list of active plugins. - Pro switches the free plugin off. When you activate Pro, it deactivates the free plugin and shows the notice "WC Cancel Order (free) has been deactivated because WC Cancel Order Pro is now active. Only the Pro version is needed." Pro repeats the check on every request (on
init, priority 5), so activating the free plugin again while Pro is active has no lasting effect. - Going back to the free plugin: deactivate Pro, then activate the free plugin. Settings and data stay in the database and the free plugin carries on with them.
- Keep both folders named as shipped. The checks above look for the folder names
wc-cancel-orderandwc-cancel-order-pro.
Data both plugins use
| Item | What it holds | Used by the free plugin | Used by Pro |
|---|---|---|---|
Option wc_cancel_settings | One array with all settings. Saved with autoload off. | Five keys: req-status, text-required, confirm-note, guest-cancel, delete-data. | 27 keys: those five and 22 more (listed in the Pro documentation). |
Table {prefix}wc_cancel_orders | One row per order that had a full cancellation request. {prefix} is the database table prefix of the site. is_approved is 0 (pending), 1 (approved) or 2 (declined). | Columns id, order_id, user_id, is_approved, cancel_request_date and cancel_date. | Those six plus decision_date, refund_method, refund_amount, cancel_fee, refund_id, refund_txn_id and refund_gateway. Pro adds the missing columns to a table the free plugin created. |
Option wc_cancel_db_version | The plugin version that last checked the tables. | Writes 3.7.0. When the stored value differs, it runs its table check on the next admin request. | Writes 4.10.0 whenever it creates its tables. |
Order status wc-cancel-request (Cancel Request) | An order waiting for a decision on a full cancellation request. | Registers and uses it. | Registers and uses it. Pro also registers wc-partial-request and wc-partial-cancelled. |
Order meta _wc_cancel_key, _wc_cancel_additional_txt, _wc_cancel_request_data, _wc_cancel_prev_status | The key for the guest link, the customer's text, the decision (approved or declined, heading and date) and the status the order had before the request. | Reads and writes all four. | Reads and writes the key, the text and the previous status. It writes the decision too, but reads it from the request table, not from this meta. Pro also writes _wc_cancel_reason (the reason the customer chose). |
Email settings woocommerce_wc_cancel_request_received_settings, woocommerce_wc_cancel_request_approved_settings, woocommerce_wc_cancel_request_declined_settings | Enabled, recipient, subject, heading and type of the three emails with the IDs wc_cancel_request_received, wc_cancel_request_approved and wc_cancel_request_declined. | Sends these three emails. | Sends the same three, with the same IDs and the same stored settings, plus four more of its own. |
| Shared names | The menu WooCommerce → WC Cancel (page wc_cancel), the settings tab WooCommerce → Settings → WC Cancel (wc_cancel_settings), the AJAX actions wc_cancel_request and wc-cancel-request, the shortcode [wc_cancel_order_details], the guest page /guest-cancel-req/, the filters wc_cancel_order_btn, wc_cancel_order_init_script, wc_cancel_order_admin_action and wc_cancel_settings, and the action wc_cancel_request. | Provides all of them. | Provides all of them. |
Pro alone creates the tables {prefix}wc_cancel_partial_requests, {prefix}wc_cancel_partial_items and {prefix}wc_cancel_wallet_txns, the user meta _wc_cancel_wallet_balance, the options wc_cancel_pro_schema_version and wc_cancel_pro_flush_rewrite, and the settings of its Store Wallet payment method and its four extra emails. The free plugin never reads them.
The five settings both plugins read
| Key | Label in the free plugin | Label in Pro | Default while nothing is stored | Difference |
|---|---|---|---|---|
req-status | Allow cancellation requests for order statuses | Allow cancellation requests for order statuses | Pending payment, Processing, On hold (both) | Same meaning. In Pro, statuses listed in "Allow immediate full cancellation for order statuses" win over this list. |
text-required | Require cancellation details | Require text input | Free: on. Pro: off. | Both mean that the customer must fill in the free-text box. A saved value (on or off) is shared as it is. |
confirm-note | Note shown in the cancel popup | Note shown in the cancel popup | Empty (both) | The free plugin keeps basic HTML (post-content tags). Pro removes all HTML tags when it saves. |
guest-cancel | Allow guest cancellation | Allow guest cancellation | On (both) | Same meaning. |
delete-data | Delete all data when the plugin is deleted | Delete all data when the plugin is deleted | Off (both) | Same meaning. See "Deleting the plugins" below. |
How saving works
- When you save WooCommerce → Settings → WC Cancel in the free plugin, it merges its five keys into the stored array. Keys that only Pro knows are kept.
- When you save the same screen in Pro, it replaces the stored array with the 27 keys it knows.
Moving from Pro back to the free plugin
- Everything Pro stored stays in the database but only Pro can use it. The free plugin does not register the statuses
wc-partial-requestandwc-partial-cancelled, has no Store Wallet payment method, and does not show partial requests or refund details. - Customers keep their wallet balances in the database, but they cannot spend them while Pro is inactive, because the Store Wallet payment method belongs to Pro.
Deleting the plugins
- Deleting either plugin from the Plugins screen removes nothing unless Delete all data when the plugin is deleted is ticked (it is off by default).
- Each plugin's
uninstall.phpalso removes nothing while the other plugin's folder is still in the plugins directory: the free plugin looks forwc-cancel-order-pro, Pro looks forwc-cancel-order. - The free plugin removes only the table
{prefix}wc_cancel_orders, its options (wc_cancel_settings,wc_cancel_db_versionand the three email settings above) and its lock rows. It does not know the Pro tables or the wallet balances. If you used Pro before and delete the free plugin last with the setting ticked, those Pro data remain. - To remove everything, tick the setting, delete the free plugin first (nothing is removed because Pro is still installed), then delete Pro (it removes all of its data because the free plugin is gone).
- On multisite, each plugin repeats this for every site of the network.
Free and Pro compared
| Feature | WC Cancel Order (free) | WC Cancel Order Pro |
|---|---|---|
| Cancel Request button in My Account and on the order page | Yes | Yes |
| Guest cancellation from a link in order emails | Yes | Yes |
| Cancel Request order status, requests list with Pending, Approved and Declined links and search | Yes | Yes |
| Approve or decline one request at a time | Yes | Yes |
| Bulk approve and decline | No | Yes |
| Cancel a whole order | Yes, always after your approval | Yes, after your approval or at once for statuses you choose |
| Cancel single items or quantities (partial cancellation) | No | Yes (off until you enable it) |
| What the customer fills in | One text box, "Cancellation details", required by default | A list of reasons you define (radio buttons) and an optional text box, each required or not |
| Note in the popup | Yes, basic HTML allowed | Yes, plain text |
| Limit by user role, limit by order age | No | Yes |
| Status after approval | Cancelled | You choose (default Cancelled) |
| Status after decline | The status before the request (Processing when none is stored) | A status you choose (default Processing) or the status before the request |
| Refund when you approve | No. Approving cancels the order; you refund from the order screen. | Gateway, manual or store wallet refund |
| Cancellation fee, store wallet, Store Wallet payment method | No | Yes |
| Stock | WooCommerce puts stock back when the order becomes Cancelled | The same, plus an "Auto restock" option for refunds |
| Emails | Three | Seven |
| WooCommerce Subscriptions and Ultimate Member buttons | No | Yes |
| Cancelled and remaining quantities on order screens | No | Yes |
Documentation of the other plugin: WC Cancel Order and WC Cancel Order Pro.
6. Screens
The Cancellation Requests list
Open WooCommerce → WC Cancel (page wc_cancel, capability manage_woocommerce). The menu item shows a bubble with the number of requests waiting: orders in Cancel Request plus pending partial requests. The count is cached for one minute in the transient wc_cancel_pending_count and cleared whenever a request changes. The screen is titled "Cancellation Requests" and has three tabs: Cancellation Requests, Wallet and Settings (a link to the settings screen).
- Status links: All, Pending, Approved and Declined, each with a count. The counts respect the type filter and the search.
- Filter and search: a drop-down of request types (All Cancellation Requests, Full Cancellation, Partial Cancellation) with a Filter button, and a "Search orders" box that uses WooCommerce's own order search (order number, name, email, address and item names; a number is also tried as an order ID).
- Columns: a checkbox for bulk actions, Order (number and buyer name, linked to the order), Type (Full or Partial), Requested Items ("Full order" or one "Product × quantity" line per item), Date, Status (Pending, Approved or Declined), Refund (amount, the method in brackets and "− fee" when a fee was taken, or a dash), Cancellation Fee and Actions.
- Actions: View Request on every row; Approve Request and Decline Request on pending rows (a full row also needs its order to be in Cancel Request). The filter
wc_cancel_order_admin_actionlets code change them. - Bulk actions: Approve Request and Decline Request (see Bulk approve and decline).
- Order: full and partial requests are merged, newest first, 20 per page. Instant cancellations appear as approved full requests with no refund.
- Date: for a full request that was approved or declined from the plugin, the date is refreshed to the moment of the decision; for partial requests it stays the request date. Dates are shown in the site's timezone.
- Orders in the trash and orders that no longer exist are not listed.
- Empty list: "No cancellation requests yet. When a customer asks to cancel an order it will show up here for you to approve or decline." With a filter or search: "No cancellation requests match these filters."
The Wallet screen
The Wallet tab (admin.php?page=wc_cancel&tab=wallet) lists every customer with any wallet activity (a balance, or a credit or debit on record), highest balance first, 20 per page. It has no search. Columns: Customer (name linked to the user's edit screen, email underneath), Available Balance, Total Received, Total Used, Wallet Orders (the number of distinct orders in the ledger) and Actions with View Wallet. With nothing to show: "No wallet activity found."
View Wallet opens "Wallet Details" for one customer, with a "Back to Wallet List" button, the customer's name and email (or "User #ID"), the "Available Balance" and two tables:
- Wallet Received: Order, Date, Items and Amount, for every credit. When a credit matches an approved wallet partial request (same order and amount, within 0.01) the Items cell lists each product, quantity and its refund; otherwise it shows the ledger note or "Wallet credit". Empty: "No wallet credits found."
- Wallet Used: Order, Date, Amount, Note and Balance After, for every debit. Empty: "No wallet payments found."
The detail view needs manage_woocommerce and a nonce (action wc-cancel-wallet-view) that the View Wallet button carries. Without it the screen shows "Invalid security token."
Other screens
| Screen | Where | What it is for |
|---|---|---|
| WC Cancel settings | WooCommerce → Settings → WC Cancel | All plugin settings (see Settings). |
| Store Wallet | WooCommerce → Settings → Payments | The payment method's title and description. |
| Emails | WooCommerce → Settings → Emails | The seven emails. |
| WpExperts Hub Licences | Plugins → WpExperts Hub Licences | Licence activation and update status. |
| Orders | WooCommerce → Orders | Status links for the three plugin statuses; the order edit screen has the quantity columns and the Partial Cancellation History box. |
| My Account → Orders, order page | Customer side | The Cancel Request button and popups. |
| My Account → Wallet | Customer side | Balance and transactions. |
| Guest cancellation page | /guest-cancel-req/?key=... | The order page for a guest, with the Cancel Request button. |
7. Settings
All settings are on one screen: WooCommerce → Settings → WC Cancel (also reachable from the Settings link on the Plugins screen and the Settings tab of the requests screen). The heading reads "Wc Cancel Order Setting" and the fields are grouped under six headings: Common Settings, Refund (Partial & Full Cancellation), Cancellation Fee, Full Cancellation, Partial Cancellation and Data. Saving needs the manage_woocommerce capability. The values are stored in the option wc_cancel_settings (autoload off) as one array of 27 keys, and the whole array is replaced on every save.
Saving validates every value. Order statuses must exist, roles must exist, numbers become numbers and unusable values fall back to the default instead of being stored. The "Default" column below is what applies while nothing has been saved for that key.
Common Settings
"These settings apply to both full and partial cancellation requests."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Allow cancellation requests for order statuses | req-status | Pending payment, Processing, On hold | Multiple select. Orders in these statuses get the Cancel Request button and are the only ones the server accepts requests for (partial requests use only this list). Offered: every status except Cancelled, Refunded, Failed, Cancel Request, Partial Cancellation Request and Draft; Completed and Partially Cancelled can be selected. Unknown statuses are dropped on save, and clearing the list leaves nobody able to request. The offered list can be changed with the filter wc_cancel_order_status. A status also selected under immediate cancellation is treated as immediate. |
| Status when a request is declined | decline | Processing (wc-processing) | Select. "Restore the status before the request" (wc-cancel-previous) or any status except Cancel Request, Partial Cancellation Request and Draft. Applies to full and partial declines. An unknown value is saved as Processing. The offered list can be changed with wc_cancel_default_status. |
| Customer roles | role | Administrator, Customer | Multiple select of all WordPress roles. Logged-in visitors need one of these roles to see the button and to submit a request. Roles that do not exist are dropped, and an empty selection is saved as Administrator plus Customer. The setting does not apply to logged-out visitors. |
| Cancellation reasons | reason-options | Three lines: "The order was placed by mistake.", "I ordered wrong product." and "Other." | Text area, one reason per line. They appear as radio buttons. Each line is trimmed and cut to 200 characters, empty lines and duplicates are removed, and HTML tags are removed. Leave the box empty for no reasons. |
| Require a cancellation reason | reason | On | Checkbox. The customer must pick a reason. It is only enforced (in the popup and on the server) when at least one reason is defined. |
| Additional text field | text-input | Display always (display-always) | Select. "Display always" shows the text box at once; "Disable" (disable-always) never shows it; one of your reasons, listed under "Show only when this reason is selected", shows it only when the customer picks that reason. The list of reasons updates as you type in the reasons box. A value that is not one of these is saved as "Display always". |
| Require text input | text-required | Off | Checkbox. The customer must fill in the additional text box. It applies only when the box is shown (always in "Display always" mode, only after the chosen reason in a reason mode) and is enforced in the popup and on the server. Shared with the free plugin, where it is called "Require cancellation details" and is on by default. |
| Hide cancel button after a time limit | hide-cancel | Off | Checkbox. Turns the time limit on and shows the Time limit row. |
| Time limit | time-interval and time-period | No limit; 0, and the period list starts on Minutes | A whole number of 0 or more (negative numbers are saved as 0; the code sets no upper limit) and a unit: Minutes (minutes), Hour (hour), Day (day), Month (month) or Year (year). After that time since the order was created, the button disappears and the server refuses requests. 0 means no limit. An invalid unit is saved as Hour. Only shown while the checkbox above is ticked. |
| Note shown in the cancel popup | confirm-note | Empty | Text area. Plain text shown at the top of the popup with an asterisk. HTML tags are removed when you save (the free plugin keeps basic HTML). The text is trimmed. |
| Allow guest cancellation | guest-cancel | On | Checkbox. When on, logged-out visitors can use the email link and the guest page, and the link is added to customer emails. It does not affect logged-in customers. |
Refund (Partial & Full Cancellation)
"These settings control the refund issued when an admin approves a cancellation request (partial or full)."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Refund methods on approval: "Automatic gateway refund (if payment gateway supports refunds)" | partial-refund-gateway | Off | Checkbox. Offers the gateway method on orders whose payment gateway supports refunds. |
| Refund methods on approval: "Manual refund" | partial-refund-manual | Off | Checkbox. Offers the manual method (a refund record only). |
| Refund methods on approval: "Wallet credit" | partial-refund-wallet | Off | Checkbox. Offers wallet credit on orders placed by a logged-in customer. |
| Default refund method | partial-refund-default | Manual refund (manual) | Select: Manual refund, Automatic gateway refund (gateway) or Wallet credit (wallet). Pre-selected on the approval popup and used for bulk approvals, only when that method is enabled for the order. Another value is saved as manual. |
| Auto restock | partial-restock | Off | Checkbox. Restocks the cancelled quantities on approval (only for products that manage stock). See Stock. |
If no method is ticked, requests are approved with no refund and the refund options are hidden from the approval popup and the estimate and fee from the customer popup.
Cancellation Fee
"Charge the customer a cancellation fee when a cancellation is approved. The fee is deducted from the refund (the customer receives refund minus fee). If no fee is enabled, no charge is applied."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Enable cancellation fee | cancel-fee-enable | Off | Checkbox. Shows the fee to the customer and takes it off refunds. The other fee fields appear when it is ticked. |
| Fee type | cancel-fee-type | Fixed amount (fixed) | Select: Fixed amount or Percentage of order total (percent). Another value is saved as fixed. |
| Cancellation fee amount | cancel-fee-amount | 0 | Number, step 0.01, minimum 0, in the store currency. Used when the type is Fixed amount. Negative values are saved as 0. |
| Cancellation fee (% of order total) | cancel-fee-percent | 0 | Number, step 0.01, from 0 to 100. Used when the type is Percentage of order total. Values are clamped to 0 to 100 on save. |
Full Cancellation
"These settings apply only to full order cancellation."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Allow immediate full cancellation for order statuses | cancel-status | Processing, On hold | Multiple select with the same offered statuses as Allow cancellation requests. Orders in these statuses are cancelled at once, with no approval and no refund. It wins over the requests list when a status is in both. Clear it so that every request waits for you. |
| Status after full cancellation is approved | approval | Cancelled (wc-cancelled) | Select of every status except Cancel Request, Partial Cancellation Request and Draft. The order gets this status when you approve a full request and when a customer cancels immediately. An unknown value is saved as Cancelled. |
Partial Cancellation
"These settings apply only to partial (per-item) cancellation requests."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Enable partial cancellation | partial-enable | Off | Checkbox. Lets customers cancel single items or quantities. Needs at least one status in Allow cancellation requests for order statuses. |
| Status when fully cancelled | partial-full-status | Cancelled (wc-cancelled) | Select (same offered list as the approval status). Applied when an approved partial request leaves no item uncancelled. An unknown value is saved as Cancelled. |
| Status when partially cancelled | partial-partial-status | Partially Cancelled (wc-partial-cancelled) | Select (same offered list). Applied when an approved partial request leaves some items. An unknown value is saved as Partially Cancelled. |
Data
"What happens to the plugin's data when the plugin is deleted."
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Delete all data when the plugin is deleted | delete-data | Off | Checkbox. When on, deleting the plugin removes the settings, the request history, every customer's wallet balance and the wallet ledger. Leave it off to keep them: wallet balances are money your customers are owed. Nothing is removed while the free plugin is still installed. See Privacy. |
Settings that live elsewhere
- Store Wallet payment method: WooCommerce → Settings → Payments → Store Wallet. Fields: Enable/Disable (default on), Title (default "Store Wallet") and Description (default "Pay using your store wallet balance."). Stored in
woocommerce_wc_cancel_wallet_settings. - The seven emails: WooCommerce → Settings → Emails (see Emails). Stored in
woocommerce_wc_cancel_request_received_settings,woocommerce_wc_cancel_request_approved_settings,woocommerce_wc_cancel_request_declined_settings,woocommerce_wc_cancel_request_partial_received_settings,woocommerce_wc_cancel_request_partial_approved_settings,woocommerce_wc_cancel_request_partial_declined_settingsandwoocommerce_wc_cancel_wallet_credit_settings. - Licence: Plugins → WpExperts Hub Licences (see Licence and updates).
Email fields
| Field | Emails | Default | What it does |
|---|---|---|---|
| Enable/Disable | All seven | Enabled | Turn an email off to stop sending it. |
| Recipient(s) | Both "Received" emails | Empty (uses the site admin email) | Comma-separated addresses. The other five emails go to the order's billing email and have no recipient field. |
| Subject, Email heading | All seven | Empty (uses the default) | Override the default subject and heading. Placeholders {order_date}, {order_number}, {site_title}, {site_address}, {site_url}. |
| Email type | All seven | HTML | The formats WooCommerce offers. |
8. Developer reference
Everything below is for version 4.10.0. The plugin has no REST routes, WP-CLI commands, blocks, cron events, custom post types or user roles. (The licence client calls a REST route on wpexpertshub.com; see Privacy.)
Shortcode
[wc_cancel_order_details] has no attributes. It reads the query parameter key (cleaned with sanitize_key) and prints WooCommerce's order page for the order whose _wc_cancel_key equals it, or whose WooCommerce order key equals it, when Allow guest cancellation is on. With no key it prints nothing; with an unknown key, or while guest cancellation is off, it prints "Sorry, this order is invalid and no details found." It returns its output instead of printing it. The virtual page /guest-cancel-req/ uses it. You can place it on your own page, which loads the popup scripts there, but the page must be opened with ?key=.
AJAX endpoints
Customer actions are open to logged-out visitors (wp_ajax_nopriv_) and protected by nonces and the ownership checks described under Who can cancel, and when. Admin actions need manage_woocommerce; without it they call wp_die(-1).
| Action | Who | Parameters | Response |
|---|---|---|---|
wc_cancel_request | Customers and guests | order_id, _wpnonce (nonce action wc-cancel-request), order_key, reason (first 500 characters used), additional_details (first 5,000 used), wcc_ajax (a boolean value). The button's link also carries order_num, wcc_opt (full, partial or both, only used by the script) and cancel_fee (fee text for the popup). | With wcc_ajax: JSON {"res": true, "message": "...", "fragments": {...}} on success, or {"res": false, "message": "..."}. Without it: a redirect to the referring page or My Account orders. |
wc_cancel_partial_items | Customers and guests | order_id, order_key, _wpnonce (nonce action wc-cancel-partial). | JSON {"res": true, "items": {itemId: {"name", "available", "unit"}}, "fee": {"enabled", "amount", "text"}, "refund": bool, "currency": {...}}, or {"res": false, "message": "..."}. |
wc_cancel_partial_request | Customers and guests | order_id, order_key, qty[ITEM_ID] (whole numbers; 0 or empty entries are ignored), reason, additional_details, wcc_ajax, _wpnonce (action wc-cancel-partial). | JSON {"res": true|false, "message": "..."}. Without wcc_ajax: a redirect. |
wc-cancel-request | Admin | req (view, approve or decline), order_id, mode (approve with view shows the approval form), refund_method (manual, gateway, wallet or none, with approve), _wpnonce (nonce action wc-cancel-backend), wcc_ajax. | JSON {"reload": bool, "html": "..."}. Without wcc_ajax: a redirect to admin.php?page=wc_cancel. |
wc_cancel_partial_view | Admin | request_id, mode (view or approve), _wpnonce (action wc-cancel-partial-backend). | JSON {"reload": false, "html": "..."}. |
wc_cancel_partial_approve | Admin | request_id, refund_method, wcc_ajax, _wpnonce (action wc-cancel-partial-backend). | JSON {"reload": bool, "html": "..."}. Without wcc_ajax: a redirect. |
wc_cancel_partial_decline | Admin | request_id, wcc_ajax, _wpnonce (action wc-cancel-partial-backend). | JSON {"reload": bool, "html": "..."}. Without wcc_ajax: a redirect. |
The button's link is a plain URL to admin-ajax.php, so the request still reaches the server if the script fails (see the caution under Full cancellation request).
Actions and filters
| Hook | Type and arguments | When |
|---|---|---|
wc_cancel_request | Action. $order_id | After a full request is stored (order in Cancel Request), and after an immediate cancellation (order already in the approval status). |
wc_cancel_partial_request | Action. $order_id, $request_id | After a partial request is stored and the order is in Partial Cancellation Request, before the email is sent. |
wc_cancel_full_request_approved | Action. $order_id, $refund_method, $refund_amount | At the end of a full approval (single or bulk). $refund_method is the method that was validated before the refund (manual, gateway, wallet or none); a failed gateway refund is still reported as gateway here while the request row stores manual. $refund_amount is the refunded amount as a float. |
wc_cancel_order_before_script, wc_cancel_order_after_script | Actions. No arguments | Just before and after the popup scripts and styles are enqueued on a page that needs them. |
woocommerce_email_wc_cancel_reason | Action. $order, $sent_to_admin, $plain_text, $email | Inside the full-cancellation email templates. The plugin hooks its own output ("Cancellation Reason:" and "Additional Details:") at priority 10. |
wc_cancel_order_btn | Filter. $actions, $order | After the Cancel Request action is built for an order. The action's URL carries the nonce. |
wc_cancel_order_init_script | Filter. $init (bool) | Decides whether the popup scripts and styles load on the current page. |
wc_cancel_order_admin_action | Filter. $actions | The buttons of the Actions column in the requests list (each with url, name and action, a CSS class). |
wc_cancel_order_status | Filter. $statuses (slug with wc- prefix => label) | The statuses offered in the two multi-selects (allowed and immediate statuses). It changes what the screen offers, not what a save accepts. |
wc_cancel_default_status | Filter. $statuses | The statuses offered for decline, approval and the two partial result statuses. |
wc_cancel_decline_status | Filter. $status (without wc-), $order | The status a declined order (full or partial) is set to. |
wc_cancel_refund_methods | Filter. $methods (key => label, keys gateway, manual, wallet), $order | The refund methods available for an order, for the approval popup, the fee and estimate display, and validation. |
wc_cancel_settings | Filter. $settings (WooCommerce settings fields) | The fields of the WC Cancel settings tab. |
wpxh_licence_server, wpxh_licence_sslverify | Filters. $server (URL string); $verify (bool) | The licence server address (default https://wpexpertshub.com) and whether to verify its certificate (default on, except for hosts ending in .local, .test, .localhost, localhost or 127.0.0.1). |
These names belong to Pro. The free plugin has wc_cancel_settings_order_status and wc_cancel_declined_status where Pro has wc_cancel_order_status and wc_cancel_decline_status, so code written for the free names does nothing under Pro.
Take refund methods away for expensive orders:
add_filter( 'wc_cancel_refund_methods', function ( $methods, $order ) {
if ( $order->get_total() > 500 ) {
unset( $methods['wallet'] );
}
return $methods;
}, 10, 2 );
React to a full approval:
add_action( 'wc_cancel_full_request_approved', function ( $order_id, $method, $amount ) {
error_log( sprintf( 'Order %d cancelled, refund %s via %s', $order_id, $amount, $method ) );
}, 10, 3 );
Send declined orders to On hold:
add_filter( 'wc_cancel_decline_status', function ( $status, $order ) {
return 'on-hold';
}, 10, 2 );
Hide the button for large orders (the server still accepts a request that is sent without the button):
add_filter( 'wc_cancel_order_btn', function ( $actions, $order ) {
if ( $order->get_total() > 500 ) {
unset( $actions['wc-cancel-order'] );
}
return $actions;
}, 10, 2 );
PHP helpers
These public methods are what the plugin's own screens use. They are not a documented stable API; check them against the version you run.
WC_Cancel_Order_Pro::instance()->approve_full_request( $order_id, $method )anddecline_full_request( $order_id ): returntrueor aWP_Errorwhose message is the text shown to the admin. They take the same locks as the screen.WC_Cancel_Partial::approve_request( $request_id, $method )anddecline_request( $request_id ): the same for partial requests.WC_Cancel_Partial::wallet_balance( $user_id )returns the balance as a float.wallet_credit( $user_id, $amount, $order_id = 0, $note = '' )andwallet_debit( ... )change it under the customer lock and return the new balance;wallet_debit_covered( ... )debits only when the whole amount is covered and returns a boolean;wallet_transactions( $user_id, $limit = 50 )returns ledger rows.WC_Cancel_Partial::get_cancellable_items( $order ),get_cancellation_fee( $order )andrefund_enabled_methods( $order ).
$result = WC_Cancel_Order_Pro::instance()->approve_full_request( 123, 'manual' );
if ( is_wp_error( $result ) ) {
error_log( $result->get_error_message() );
}
Constants
WC_CANCEL_DIR: the plugin folder path. Defined if not already defined.WC_CANCEL_VERSION:4.10.0. Used as the version of scripts and styles and written towc_cancel_db_versionandwc_cancel_pro_schema_version.WC_CANCEL_SC_FOOTER: defaults totrue, passed as the "in footer" argument when scripts are registered. Define it asfalseinwp-config.phpto load them in the page header.WPXH_LICENCE_SERVER: optional, the licence server address; the filterwpxh_licence_servercan still change it.
Capabilities
The requests and wallet screens, their AJAX actions and saving the settings need manage_woocommerce. The licence screen and its forms need manage_options. Customer requests need no capability: they depend on the nonce, the order's customer (or key), the role setting and the other rules. The plugin adds no capabilities and no roles.
Database tables
All tables are InnoDB with the utf8 character set; {prefix} is the site's table prefix. Dates are stored in GMT. The requests list also reads WooCommerce's own order tables ({prefix}wc_orders with HPOS, or the posts table) and WooCommerce's order meta tables to find the guest key.
{prefix}wc_cancel_orders (full requests, one row per order)
| Column | Type | Meaning |
|---|---|---|
id | bigint, primary key | Request ID. |
order_id | bigint, indexed | The order. |
user_id | bigint | The user who sent the request, or the admin who decided it when no row existed (0 for a guest). |
is_approved | tinyint, default 0 | 0 pending, 1 approved, 2 declined. |
cancel_request_date | datetime, nullable, indexed | When the request was recorded; refreshed when a full request is decided from the plugin. |
cancel_date | timestamp, nullable | Created but not written by the plugin. |
decision_date | datetime, nullable | When the request was approved or declined. |
refund_method | varchar(20), nullable | manual, gateway, wallet or none. |
refund_amount | decimal(20,4), default 0 | Amount refunded after the fee. |
cancel_fee | decimal(20,4), default 0 | The fee taken. |
refund_id | bigint, nullable | The WooCommerce refund ID. |
refund_txn_id | varchar(190), nullable | The gateway's refund transaction ID. |
refund_gateway | varchar(190), nullable | The gateway's name. |
{prefix}wc_cancel_partial_requests
| Column | Type | Meaning |
|---|---|---|
id | bigint, primary key | Request ID (the "#N" in notes and emails). |
order_id | bigint, indexed | The order. |
user_id | bigint, default 0, indexed | The order's customer (0 for a guest order). |
status | varchar(20), default pending, indexed | pending, approved or declined. |
reason, additional_text | text, nullable | What the customer chose and wrote. |
request_date | datetime, indexed | When it was submitted. |
decision_date | datetime, nullable | When it was decided. |
refund_method, refund_amount, cancel_fee, refund_id, refund_txn_id, refund_gateway | as above | The refund recorded on approval. |
email_sent | tinyint, default 0 | Set to 1 on approval; not read by the plugin. |
{prefix}wc_cancel_partial_items
| Column | Type | Meaning |
|---|---|---|
id | bigint, primary key | Row ID. |
request_id | bigint, indexed | The partial request. |
order_id | bigint | The order. |
order_item_id | bigint, indexed | The order line. |
product_id, variation_id | bigint, default 0 | The product and variation at the time. |
qty_requested, qty_approved | int, default 0 | Quantity asked for and quantity approved. |
item_total, item_tax | decimal(20,4) | The line's total and tax when the request was made. |
{prefix}wc_cancel_wallet_txns
| Column | Type | Meaning |
|---|---|---|
id | bigint, primary key | Entry ID. |
user_id | bigint, indexed | The customer. |
order_id | bigint, default 0 | The related order. |
type | varchar(10), default credit | credit or debit. |
amount, balance_after | decimal(20,4) | The movement and the balance after it. |
note | varchar(255) | For example "Payment for order #N". |
txn_date | datetime | When it happened. |
Options, meta, transients and statuses
- Options:
wc_cancel_settings,wc_cancel_db_version,wc_cancel_pro_schema_version,wc_cancel_pro_flush_rewrite(one-time flag), the eight WooCommerce option names listed under Settings that live elsewhere, and the licence options_wc-cancel-order-pro_licence_keyand_wc-cancel-order-pro_key_status(the Plugins screen link text reads the status option, whose name is built from the plugin folder name). - Order meta:
_wc_cancel_key,_wc_cancel_reason,_wc_cancel_additional_txt,_wc_cancel_request_data(array withapproved,headanddate) and_wc_cancel_prev_status. - User meta:
_wc_cancel_wallet_balance. - Transients:
wc_cancel_pending_count(60 seconds),wc_cancel_pro_deactivated_free(30 seconds, drives the notice about the free plugin),wpxh_licence_msg_USERID(120 seconds, the licence screen's message) and the site transientwpxh_licence_check_v2(12 hours). Object cache groupwc_cancel_order, keywc_cancel_declined_ORDERID, holds whether an order has a blocking request. - Order statuses:
wc-cancel-request,wc-partial-request,wc-partial-cancelled. - Rewrite endpoint:
wc-wallet(EP_ROOT | EP_PAGES), also registered with WooCommerce as a query var so the endpoint title and menu highlighting work. - Payment gateway ID:
wc_cancel_wallet; blocks integration namewc_cancel_wallet. - Lock rows: options named
wcc_lock_plus a hash, used only whenGET_LOCK()is unavailable.
Email templates
The templates are in templates/emails/, with plain-text versions in templates/emails/plain/. To override one, copy it to your theme's woocommerce/emails/ folder (WooCommerce looks for yourtheme/woocommerce/ followed by the template name):
admin-request-received.php,customer-request-approved.php,customer-request-declined.phppartial-admin-request-received.php,partial-customer-request-approved.php,partial-customer-request-declined.phpcustomer-wallet-credit.php- and the same seven names under
plain/.
For example: yourtheme/woocommerce/emails/customer-request-approved.php and yourtheme/woocommerce/emails/plain/customer-request-approved.php. The comments at the top of two customer templates give other names (admin-request-approved.php and admin-request-declined.php); the names above are the ones WooCommerce uses.
File layout
wc-cancel-order-pro/
wc-cancel-order-pro.php main file, class WC_Cancel_Order_Pro
uninstall.php, readme.txt, wpml-config.xml
licence/class-wpxh-licence-client.php licence and update client
classes/
class-wc-cancel-dashboard.php requests list (full + partial)
class-wc-cancel-partial.php partial requests, refunds, wallet, locks
class-wc-cancel-partial-dashboard.php older list class, not used by any screen
class-wc-cancel-wallet-dashboard.php admin Wallet tab
class-wc-gateway-wc-cancel-wallet.php Store Wallet payment method
class-wc-cancel-wallet-blocks-support.php blocks integration
class-wc-cancel-guest.php, class-wc-cancel-order-details.php guest page
class-wc-cancel-sql.php, class-wc-cancel-uninstall.php
class-wc-cancel-request-received.php / -approved.php / -declined.php
class-wc-cancel-request-partial-received.php / -approved.php / -declined.php
class-wc-cancel-wallet-credit.php
includes/ dashboard.php, settings.php, wc-cancel-options.php
templates/emails/ (+ plain/)
assets/ css (admin, front, modal), js (admin, front, wc-payment-method-wc-cancel-wallet), fonts
languages/ .pot, en_US, es_ES, fr_FR, ko_KR, nl_NL, pl_PL
Core and WooCommerce hooks the plugin uses
active_plugins(filter, read by the plugin): the main file reads the active plugins (and, on multisite, the network-activated ones) to decide whether to start.init: priority 0 loads the translations, 5 deactivates the free plugin, 10 registers the licence client, 20 registers thewc-walletendpoint, 99 flushes rewrite rules once after activation, 999 registers the three statuses.plugins_loaded: the schema check.woocommerce_loaded: loads the settings (oninitpriority 0 wheninithas not run yet) and registers the settings tab throughwoocommerce_settings_tabs_array(priority 50),woocommerce_settings_tabs_wc_cancel_settings,woocommerce_update_options_wc_cancel_settingsand the custom field typewoocommerce_admin_field_wc_cancel_setting.woocommerce_update_options: saves the settings; it checks thewoocommerce-settingsnonce andmanage_woocommerceand returns quietly otherwise, so it never stops other plugins' saves.woocommerce_my_account_my_orders_actions(priority 100),um_woocommerce_orders_tab_actions(100),wcs_view_subscription_actionsandwoocommerce_my_subscriptions_actions: the Cancel Request button. The first also removes WooCommerce's owncancelaction from the list.woocommerce_after_order_details: on the guest page, prints the button block when WooCommerce did not already print the action.woocommerce_order_status_changed(priority 999): emails, request settling and clearing the pending count.woocommerce_checkout_order_processedandwoocommerce_store_api_checkout_order_processed(both priority 1): write the guest key.woocommerce_email_classes(priority 999) andwoocommerce_email_customer_details(priority 999): register the emails and add the guest link.woocommerce_payment_gateways,woocommerce_blocks_loaded,woocommerce_blocks_payment_method_type_registrationandwoocommerce_update_options_payment_gateways_wc_cancel_wallet: the Store Wallet method.woocommerce_get_query_vars,woocommerce_endpoint_wc-wallet_title,woocommerce_account_menu_itemsandwoocommerce_account_wc-wallet_endpoint: the Wallet tab.woocommerce_admin_order_item_headers,woocommerce_admin_order_item_values,woocommerce_order_item_quantity_htmlandadd_meta_boxes: the quantity columns, the customer quantity text and the history box.woocommerce_order_fully_refunded_status(priority 999): added only while the plugin creates a refund, and removed straight after. It returns no status, so WooCommerce does not switch the order to Refunded before the plugin sets the approval status.the_posts(main query only): serves the virtual guest page.pre_get_shortlink(priority 999): gives that page the shortlinkguest-cancel-req.wc_order_statuses,woocommerce_screen_ids(priority 999),admin_menu,admin_notices,admin_enqueue_scripts(priority 999),wp_enqueue_scripts(priorities 10 and 20),before_woocommerce_init(compatibility declarations) andplugin_action_links_wc-cancel-order-pro/wc-cancel-order-pro.php.- The licence client adds
pre_set_site_transient_update_plugins(puts the plugin into WordPress's update data),plugins_api(priority 20, answers the "View details" popup for the plugin's slug),http_request_argsandhttp_request_host_is_external(only for a local development licence server),upgrader_process_complete,in_plugin_update_message-wc-cancel-order-pro/wc-cancel-order-pro.php,admin_menu,admin_initandadmin_notices.
The form fields in the popups are named wc-cancel-reason (radio buttons) and wc-cancel-additional-text (text box); the requests list uses request_status, filter and s as query parameters and wcc_request[] for the bulk checkboxes (values like full:ORDER_ID and partial:REQUEST_ID); the approval popup posts refund_method.
9. Privacy
What is stored
- In the request tables: order IDs, user IDs, the customer's reason and text for partial requests, the items and quantities, decisions and dates, and the refund method, amount, fee, refund ID, gateway name and gateway transaction ID.
- In order meta: the reason and text of a full request (
_wc_cancel_reason,_wc_cancel_additional_txt), the guest key (_wc_cancel_key), the decision (_wc_cancel_request_data) and the previous status (_wc_cancel_prev_status). The text can contain personal data, depending on what the customer writes. - Wallet: each customer's balance (user meta
_wc_cancel_wallet_balance) and a ledger ({prefix}wc_cancel_wallet_txns) with user ID, order ID, type, amount, balance after, a note and the date. These are money records. - In order notes: a note for each step, including refund amounts and gateway failures.
- Licence: the licence key (option
_wc-cancel-order-pro_licence_key, stored as text with autoload off) and its status. - In emails: the customer's reason and text go to the store recipients and to the order's billing email.
Cookies and browser storage
The plugin sets no cookies and uses no browser storage.
Data sent to wpexpertshub.com
Cancellation, refund, wallet and customer data never leave your site through this plugin. The only outbound requests are the licence and update client's JSON requests to https://wpexpertshub.com/wp-json/wphub-licence/v1/ (or the address set with WPXH_LICENCE_SERVER or the wpxh_licence_server filter). WordPress adds its usual user-agent header, which contains the WordPress version and your site address, and the server sees your server's IP address.
| Request | When | What is sent |
|---|---|---|
check | Whenever WordPress rebuilds its plugin update data or someone opens the plugin's "View details" popup, and the cached answer is missing, older than 12 hours (1 hour after a failed check) or no longer matches the plugins and licence keys on the site. | Your site address (site_url()) and, for every WpExperts Hub plugin on the site that bundles the client, its slug, its licence key (empty if none) and its installed version. |
activate | When you click Activate licence. | The plugin slug, the key you entered and your site address. |
deactivate | When you click Deactivate licence. | The plugin slug, the stored key and your site address. |
send-key | When you submit "Can't find your key? Email it to me". | The plugin slug and the email address you typed. |
| Update package | When you update the plugin from the updates screen. | WordPress downloads the package from the address the server returned; no extra data is added by the plugin. |
The server's answer (version, tested and required versions, a package address and the licence state) is cached for 12 hours. Nothing else is sent: no order data, no customer data, no settings and no wallet data.
The guest link
The guest link contains a key that identifies one order. Whoever holds the link can see the order details shown on the guest page and can cancel, or request cancellation of, the order. The link goes to the order's billing email address. Switching off Allow guest cancellation disables it.
Retention and removal
- Request rows, wallet ledger rows, wallet balances and order meta are kept until you delete them. The plugin registers no personal data exporter or eraser. To remove a customer's text, delete the order or the meta key, or the rows, with a database tool or code.
- Deactivating removes nothing.
- Deleting the plugin removes nothing by default. With Delete all data when the plugin is deleted ticked, and when the free WC Cancel Order plugin is not installed,
uninstall.phpdrops the four tables, deletes the optionswc_cancel_settings,wc_cancel_db_version,wc_cancel_pro_schema_version,wc_cancel_pro_flush_rewriteand the eight WooCommerce option names for the wallet method and the seven emails, deletes every customer's_wc_cancel_wallet_balanceuser meta, and deletes transients whose names start withwc_canceland the lock rows. On multisite it does this for every site. - It does not delete order meta or order notes, the licence options, or the site transient
wpxh_licence_check_v2. To remove the local licence key, deactivate the licence on Plugins → WpExperts Hub Licences before deleting the plugin. - See WC Cancel Order and WC Cancel Order Pro together for the order in which to delete both plugins.
10. Troubleshooting
| Problem | Likely cause and fix |
|---|---|
| The plugin does nothing and shows no message | WooCommerce is not active, or is not installed in the folder woocommerce. The plugin starts only when woocommerce/woocommerce.php is active. Without it there is also no licence screen. |
| No Cancel Request button on an order | Check, in this order: the order's status is in Allow cancellation requests for order statuses (or the immediate list); the time limit has not passed; no request for the order was declined, or fully decided; for a logged-in customer, their role is in Customer roles (a shop manager is not by default); for a logged-out visitor, Allow guest cancellation is on; your theme still prints WooCommerce's order actions. |
| The customer does not get the Partial Cancellation choice | Partial cancellation is off; the order's status is not in Allow cancellation requests for order statuses (or it is in the immediate list, which always wins); or no item has a quantity left to cancel (quantities already cancelled, refunded or in a pending partial request are not available). |
| Orders are cancelled at once without my approval | The order's status is in Allow immediate full cancellation for order statuses. By default that list holds Processing and On hold. Clear it, or remove the statuses you want to approve yourself, and save. |
| Clicking the button cancels or requests with no popup, or does nothing | The popup script did not load. Without it the button is a plain link: when nothing is required of the customer it sends the request at once; otherwise the server refuses it and returns to the same page. Check the browser console and that the theme calls wp_footer(), or load the assets with wc_cancel_order_init_script. |
| "Cancellation reason required!" or "Additional details required!" | The setting Require a cancellation reason or Require text input applies and the customer left the field empty. The server checks them too. |
| "Cancellation request could not be processed. Please try again." | The server refused the request: the security token expired (a cached page can carry an old one; reload), the order's status is not allowed, the time limit passed, the customer's role is not allowed, a request was already decided, or the visitor is not the order's customer and has no valid key. |
| "Your cancellation request is being processed. Please try again in a moment." | Another request for the same order held the lock for more than 10 seconds. Wait and try again. |
| A declined order went to Processing | Status when a request is declined defaults to Processing. Choose "Restore the status before the request" or another status. With "Restore", a request that no longer has a saved status, or whose previous status was Cancelled, Refunded, Failed or a request status, also goes to Processing. |
| An approval created no refund | No refund method is enabled, the net amount was 0 (the fee was as large as the refund), or the approval was a bulk approval and the default refund method is not enabled for that order. The order note says "No refund was issued." Enable a method under Refund methods on approval and approve one by one to choose the method. |
| Order note "Automatic gateway refund of X failed (...)" | The payment gateway rejected or could not process the refund. The refund is on the order as a manual refund and the request records Manual refund, but the customer has not been paid. Refund them manually or fix the gateway and refund again. |
| Wallet credit is not offered | The setting Wallet credit is off, or the order was placed by a guest (no customer account to hold the balance). |
| The Store Wallet payment method is not shown at checkout | The shopper is logged out, has no balance, or the balance is below the order total (it only pays the whole total); the method is disabled under WooCommerce → Settings → Payments; or the store uses block checkout and the method's settings have never been saved. Open its settings and click Save changes once. |
| The My Account Wallet tab shows a 404 page | With pretty permalinks the endpoint needs its rewrite rule. After activation the plugin refreshes the rewrite rules once; if the tab still fails, open Settings → Permalinks and click Save Changes. |
| "Quantity for PRODUCT is no longer available." | Between the request and your approval, the order changed: units were refunded or cancelled by another request. Decline the request, or ask the customer to submit a new one. |
| "This order has no pending cancellation request." or "Request already processed." | Someone already decided the request, or the order's status changed. Reload the list. |
| "This request is being processed right now." | Another admin, or a double click, is deciding the same request. Wait, then reload the list. |
| The guest link shows the home page or a 404 page | Permalinks are set to Plain. Choose any other structure under Settings → Permalinks. |
| The guest page says "Sorry, this order is invalid and no details found." | The key does not match an order, or Allow guest cancellation is off. |
| The order email has no "Want to cancel this order?" link | Guest cancellation is off; no cancel is possible for a guest (status, time limit, or an earlier decision); or the email is one of the plugin's own emails or an admin email, which never carry the link. |
| Stock did not go back | WooCommerce restores stock only for an order that reduced it, when the order becomes Cancelled, Pending or Failed. For a partial cancellation, or an approval status other than those, turn on Auto restock. Products that do not manage stock are never restocked. |
| The menu bubble count is wrong for a minute | The count is cached for one minute and is cleared when a request changes. It is shown only to users with manage_woocommerce. |
| The free WC Cancel Order plugin keeps switching itself off | This is intended. Pro deactivates it on every request. See WC Cancel Order and WC Cancel Order Pro together. |
| Licence activation fails | Read the message on the card. "Please enter your licence key." means the box was empty. "The licence server returned an unexpected response (HTTP N). Please try again later." and connection errors mean the server could not be reached or answered with something that is not JSON: check that your server can make outbound HTTPS requests to wpexpertshub.com. Otherwise the server's own message explains why the key was refused. |
| No update appears | The plugin's update answer is cached for up to 12 hours (the cache is cleared when you activate or deactivate a licence and when any update finishes). Without an active licence, an available update shows "Automatic update is unavailable for this plugin." Check that the licence card says Active. |
11. Frequently asked questions
Can customers cancel only some items of an order?
Yes. Tick Enable partial cancellation. The customer chooses items and quantities, and you approve or decline the request. The order then gets the status you set for partially or fully cancelled orders.
Does a request wait for my approval?
Yes, except for orders in the statuses under Allow immediate full cancellation for order statuses, which are cancelled at once. That list is Processing and On hold by default, so review it.
How are refunds issued when I approve?
Enable the methods you want (automatic gateway, manual, wallet credit) and choose one in the approval popup. If no method is enabled, the request is approved without a refund.
Is there a cancellation fee?
Optionally. Set a fixed amount or a percentage of the order total. The customer sees it before confirming and it is taken off the refund. It is taken once for each approved request.
How does the store wallet work?
A wallet credit adds the refunded amount to the customer's balance, which they see in the Wallet tab of My Account. The Store Wallet payment method pays an order only when the balance covers the whole total. Refunding an order paid with the wallet credits the wallet again. Wallet credit is only offered for orders placed by a logged-in customer.
Can guests cancel their orders?
Yes, while Allow guest cancellation is on. A "Want to cancel this order?" link is added to the customer's order emails and opens a page for that order. Guests cannot receive wallet credit.
Does it work with WooCommerce Subscriptions and Ultimate Member?
The plugin adds the button to the subscription view and the Ultimate Member orders tab, and cancels the parent order's subscriptions on approval. These integrations were not tested against those plugins here.
Can I control who cancels and for how long?
Yes: order statuses, user roles and a time limit in minutes, hours, days, months or years after the order was placed.
Can a request be approved twice and refunded twice?
No. Approvals and declines are processed one at a time per request and per order. If two people click Approve together, the second is told the request was already processed.
Can a customer spend the same wallet balance twice?
No. Every wallet payment and credit runs under a lock for that customer and re-reads the real balance, and the wallet only pays when it covers the whole order.
I refunded part of an order myself. What happens to cancellation requests?
Refunded quantities are taken into account. Customers can only request the units still left, and an approval never refunds more than WooCommerce says is still refundable.
Will an instant cancellation refund the customer?
No. It changes the order status only. Refund from the order screen if needed.
Can I use Pro and the free plugin together?
Not at the same time. Activating Pro deactivates the free plugin and Pro uses the same settings and requests. See "WC Cancel Order and WC Cancel Order Pro together".
What is removed when I delete the plugin?
Nothing, unless you tick Delete all data when the plugin is deleted. The data (requests, wallet balances and transactions, settings) is shared with the free plugin and is kept while that plugin is installed.
Do I need a licence for the plugin to work?
No. The plugin works without one. The licence only lets WordPress download updates.
How do I activate my licence?
Open Plugins → WpExperts Hub Licences, paste the key from your purchase email (or from My Account → Downloads on wpexpertshub.com) and click Activate licence. New versions then appear on the normal updates screen.
How do I get help?
Email support@wpexpertshub.com with your order ID and a short description of the issue.
12. Changelog
4.10.0 - 06/10/2026
- Fix - Approving the same request twice at the same moment (a double click, two admins, two browser tabs) issued the refund twice - for a full cancellation up to several times the order total. Approvals and declines are now serialised per request / order, and a second click gets "already processed".
- Fix - Paying with the Store Wallet on two checkouts at the same moment could spend the same balance twice, and wallet credits arriving together could overwrite each other. Every wallet change now runs under a per-customer lock and re-reads the real balance; the wallet pays only when it covers the whole order.
- Fix - Quantities refunded outside the plugin (an admin refund on the order screen) were not taken into account: customers could request cancellation of units that were already refunded, and the approval then failed. Cancellable, Remaining and Cancelled quantities now follow the WooCommerce refunds. The "Cancelled Qty / Remaining Qty" shown to customers on the order page now actually appears (it was hooked to a filter that does not exist).
- Fix - A fractional price (for example 333.4 in a store without decimals, or a total that does not divide evenly) lost a cent / yen on every partial approval, so a fully cancelled order was never fully refunded, and the last approval could abort with "Invalid refund amount". Refunds now round the running total of each line and never exceed what WooCommerce says is still refundable.
- Fix - A partial request needing a reason was refused with "Cancellation reason required!" when no reasons were configured (nothing to choose in the popup). Quantities such as -2 were silently turned into 2; they are now refused. "Require text input" is enforced by the server for full and partial requests.
- Fix - Plain-text "Request approved" / "Request declined" emails were built as admin emails and showed the customer a link into wp-admin and the product SKUs.
- Fix - Approving a partial request with no refund (no refund method enabled, or a fee larger than the refund) did not put the cancelled units back in stock. It now does (when Auto restock is on), without double-restocking later.
- Fix - A request an admin moved out of "Cancel Request" by hand stayed "Pending" in the requests list for ever. It is now recorded as approved or declined.
- Fix - A bad or expired security token on the approve / decline popups reloaded the page or did nothing without a message; they now explain what happened. Popups no longer spin for ever when the request fails (offline, expired page, server error).
- Fix - Settings are validated on save: unknown / final order statuses, unknown roles, text-input modes, negative time limits and fees, and fee percentages over 100 are no longer stored. WooCommerce's internal "Draft" status is no longer offered. The status of an order is looked up by its slug, not its translated label.
- Fix - Two requests for the same order at the same moment created two request rows / emails; order lookups after a lock now read the database, not a stale cache.
- New - Cancellation Requests list: status links (All / Pending / Approved / Declined) with counts, order search (number, name, email), a "pending" bubble on the WC Cancel menu item, a Settings tab, and a proper empty state.
- New - The cancellation popup shows an estimated refund (minus the fee) as quantities change, has + / - steppers that stop at the available quantity, a close button, a label for screen readers, focus that returns to the Cancel Request button, and a layout for phones and right-to-left languages.
- New - The "request received" email to the shop links straight to the pending requests; the "approved" email for full cancellations shows the refund, method and fee; refund methods are named in words ("Wallet credit"), not stored codes.
- New - Wallet page: empty state, order links, responsive transactions table, credit / debit labels.
- New - "Delete all data when the plugin is deleted" (off by default) with an uninstall routine that never removes anything while the free WC Cancel Order plugin is installed (the tables and options are shared).
- Tweak - Tested up to WooCommerce 11.1. Real-browser, real-HTTP and concurrency tests added to the development harness.
4.9.1 - 27/09/2026
- Security - New licence client: licence data is exchanged as JSON only (the old client unserialized server responses) and a failed server request can no longer cause a fatal error.
- Enhancement - One "Plugins → WpExperts Hub Licences" screen manages the licences of all WpExperts Hub plugins (previously only one plugin's licence page could be opened when several were installed). Existing activations are kept.
- Enhancement - Updates are delivered through the standard WordPress update screen for sites with an active licence; "Email me my key" added.
- Fix - Approving a full cancellation with a refund of the whole remaining amount (no fee) let WooCommerce switch the order to "Refunded" first, so the "Cancellation Request Approved" email was never sent. The plugin now keeps control of the final status and always sends it.
- Fix - The "Wallet credit received" email showed a credit of 0.00 for full cancellations; it now shows the credited amount.
- Fix - Wallet credit is no longer offered for guest orders (there is no customer account to hold the balance, so the money was lost).
- Fix - Partial approvals passed the refund tax as a single number; it is now sent per tax rate as WooCommerce expects (older WooCommerce versions could fatal).
- Fix - A failed automatic gateway refund now adds an order note saying the money was not returned and the refund was recorded as manual.
- Fix - "Customer roles" was ignored for logged-in users whenever guest cancellation was enabled; roles now apply to logged-in users and the guest setting only to logged-out visitors.
- Fix - Cancelling any order from the admin in an "immediate cancellation" status sent customers a "cancellation request approved" email; it is now only sent when the customer cancelled.
- Fix - Declined emails are also sent when a Cancel Request order is moved back to another status by hand.
- Fix - Translation loading was triggered too early (WordPress 6.7+ "_load_textdomain_just_in_time" notice on every request).
- Fix - PHP 8.2 dynamic-property deprecations (licence and partial handler properties, WC_Cancel_Order_Details::$key).
- Fix - Schema checks (DESC / SHOW COLUMNS / information_schema queries) ran on every page load; they now run once per plugin version.
- Fix - Older installs created wc_cancel_orders.cancel_date as TIMESTAMP NOT NULL without a default, which breaks request inserts on strict-mode MySQL; the column is now nullable.
- Fix - Activating Pro while the free plugin was active booted the free class in that request, so Pro's activation routine (tables, rewrite rules, free deactivation) did not run; Pro now always boots its own class.
- Fix - Wallet endpoint rewrite rules are flushed after activation once the endpoint is registered (the Wallet tab could 404 until permalinks were re-saved); the endpoint is also registered with WooCommerce so it gets its page title and menu highlighting.
- Fix - Settings save no longer calls check_admin_referer() on every woocommerce_update_options call (could wp_die outside the settings form).
- Fix - The guest cancellation page replaced the posts of every query on that URL; it now only affects the main query. The [wc_cancel_order_details] shortcode returns its output instead of printing it above the content, and no second Cancel Request button is printed when WooCommerce already shows one.
- Fix - Guest cancel link in emails: plain-text emails get a plain URL (not HTML), valid markup, and the link only appears when guest cancellation is enabled.
- Fix - Cancel time limit: no fatal for orders without a creation date, and the limit is compared in real timestamps.
- Fix - Admin order items table: shipping, fee and refund rows now get the extra "Cancelled Qty / Remaining Qty" cells so columns stay aligned.
- Fix - Partial-request admin view now checks its nonce; request details are escaped.
- Fix - Store Wallet: no redundant stock-reduction calls after payment; hidden (classic and block checkout) when the balance does not cover the order total.
- Fix - Cancellation reasons are split on any line ending and empty lines are ignored; the reason-specific text box no longer shows before its reason is chosen.
- Fix - "Require a cancellation reason" is also enforced on the server for full requests.
- New - Bulk "Approve Request" / "Decline Request" on the merged Cancellation Requests list (full and partial; approvals use the default refund method).
- New - "Restore the status before the request" option for declined requests (full and partial).
- New - Store Wallet supports refunds: refunding an order paid with the wallet (or approving its cancellation with an automatic refund) credits the wallet.
- New - Admin Wallet details list every wallet credit (full cancellations and wallet refunds included, not only partial requests).
- Tweak - Tested up to WordPress 7.1 and WooCommerce 11.1; Requires PHP lowered to 7.4.
4.9 - 15/09/2026
- Tweak - The Cancellation Requests admin list no longer scans and sorts the entire request history on every page load. It now reads only the top rows it actually needs from each of the full/partial request tables (using new indexes on their date columns) and merges them in PHP, instead of unioning and sorting the whole table before applying the page limit. Same rows, same order, same filters, same pagination — just no longer slower as request history grows.
- Fix - Added the missing date-column indexes (
cancel_request_date,request_date) that make the above possible; existing sites get them automatically via the upgrade routine, no action needed.
4.8 - 15/09/2026
- Fix - Added the missing
user_idindex on the partial-cancellation-requests table. The wallet dashboard's "Wallet Received" / "Wallet Debited" history filters this table byuser_idon every page view; without an index this was a full table scan that gets slower as request volume grows. New installs get the index automatically; existing sites get it added on the next page load via the upgrade routine (no manual action needed). - Tweak - Added a small
ensure_index()helper so future index additions can be rolled out to existing installs the same safe, idempotent way the plugin already handles new columns.
4.7 - 12/08/2026
- New - Cancellation fee feature: admins can charge a fixed amount or percentage of order total as a cancellation fee. The fee is shown to the customer in the cancellation popup and deducted from the refund on approval.
- New - "Cancellation Fee" settings section with enable toggle, fee type (fixed/percent), amount, and percentage fields.
- New - Fee appears in customer cancellation popup (both full and partial), admin approve popup, order notes, customer approval email, and Cancellation Requests dashboard.
- New - Fee is calculated on full order total and deducted from the refund amount for each cancellation request.
- New - Harness commands
set-feeandcancel-feefor testing fee scenarios. - Tweak - Harness updated to 141 assertions covering fee scenarios.
4.6 - 12/08/2026
- New - Admins can now choose a Refund method (Automatic gateway refund / Manual refund / Wallet credit) in the Approve Cancellation Request popup for full cancellations, matching partial cancellation.
- New - Approving a full cancellation request now creates a WooCommerce refund for the full remaining order total and records the refund method, amount, gateway transaction ID, and decision date on the request.
- New - Cancellation Requests list and the request detail popup show the refund amount + method for approved full cancellations.
- Tweak - Refund method settings (methods, default method, auto-restock) moved into a single common "Refund (Partial & Full Cancellation)" section; they now govern both partial and full cancellation approvals.
- New - When no refund method is enabled, approving a cancellation request issues no refund, the Refund method: options are hidden from the approval popup, and the request records "No refund was issued."
4.5 - 12/08/2026
- Fix - Gateway refund transaction ID and gateway name recorded in Partial Cancellation Request Detail popup.
- Fix - Refund amount + currency symbol alignment and refund method layout polished in approve popup.
- Tweak - Full & partial request popups now consistently show Approved/Declined banner with date & time.
- Fix - Gateway refund transaction ID capture hardened: falls back through the gateway result (
transaction_id/refund_id/id), the WC refund record, then the order's recorded transaction ID, so refunds that return success without an ID still surface the transaction shown in Payments -> Transactions. - Fix - Fixed dead CSS selectors for the Gateway / Transaction ID rows in the request detail popup (missing leading dot).
13. Support
Email support@wpexpertshub.com. Please include:
- Your WC Cancel Order Pro order ID.
- The plugin version (this page documents 4.10.0), your WordPress, PHP and WooCommerce versions, and whether High-Performance Order Storage is on.
- What you did, what you expected and what happened, with any message shown and any order note the plugin added.
- What you already tried, for example switching to a default theme or disabling other plugins.
Do not post your licence key in public places. The free plugin is documented separately at wpexpertshub.com/docs/wc-cancel-order.