Direct answer
To add order-level gift wrap at WooCommerce checkout, register one checkbox and one bounded message in WooCommerce's checkout field system, send only that choice to the server, and calculate a non-negative flat fee from the merchant's saved setting. The shopper's browser must never choose the amount, and a failed update must not create an order whose visible fields disagree with its totals.
StoreFixKit's Gift Wrap at Checkout for WooCommerce follows that narrow model. Free adds one optional service for the whole order. Pro adds a free-wrap threshold, required-message rule, and separate customer and packing labels.
Order-level gift wrap is not a product catalog
Gift wrapping can be modeled per product, per quantity, per design, or once for the whole order. Those models produce different totals and fulfillment records.
StoreFixKit implements one order-level option with one flat fee. It does not create a hidden gift product, SKU, stock quantity, image catalog, per-item selection, or design inventory. That keeps the merchant decision and rollback boundary small.
WooCommerce's Marketplace documents broader products such as Gift Wrapper for WooCommerce and WooCommerce Checkout Add-ons. Their feature sets show that product-level designs, conditional add-ons, fees, and order-level messages are established workflows, but they also show why a one-job plugin should not pretend all of those jobs are the same.
The fee must come from the server
WooCommerce's code reference documents WC_Cart::add_fee() as a cart/checkout fee API called during the woocommerce_cart_calculate_fees action. The official WC_Cart reference states that the fee has a name, amount, taxable flag, and tax class and that fees do not persist as arbitrary cart state.
The browser therefore sends only whether gift wrap is selected and the shopper's plain message. The server reads the saved merchant fee, bounds it to a non-negative value, and asks WooCommerce to calculate the fresh totals. Editing a request cannot substitute another amount.
This boundary also keeps currency formatting and order totals under WooCommerce. StoreFixKit does not manually add numbers to the HTML total or trust a hidden form input.
Checkout Block needs native fields and Store API updates
WooCommerce's Additional Checkout Fields documentation describes woocommerce_register_additional_checkout_field, the order location, supported text and checkbox types, sanitization, validation, and conditional JSON Schema. It also says fields should be registered on woocommerce_init or later.
Gift wrap and a gift message belong in order information because they describe this order rather than the customer's reusable address or account profile. The field IDs use a plugin namespace so they do not collide with another extension.
When the shopper changes the option, totals must come back from WooCommerce. The official Store API cart update guide explains that extensions should register one namespace callback and call extensionCartUpdate; extensions are not supposed to replace the client cart state with an independently calculated object.
Conditional required messages need two-sided validation
Free allows a short optional message. Pro can require the message only when gift wrap is selected. WooCommerce supports this with conditional JSON Schema evaluated in the frontend and backend.
The frontend rule gives immediate feedback, but the final server request must still be checked. Empty optional fields can take a different path than populated fields, and a failed totals update can leave stale controls on screen. StoreFixKit compares the posted native fields with the last server-confirmed gift-wrap session state before allowing the order to be created.
If they disagree, checkout asks the shopper to review the option and submit again. It does not silently accept a fee with gift wrap: no, or gift wrap: yes without the required message.
Classic checkout needs WooCommerce's update flow
Classic checkout does not use the Checkout Block field renderer, but the same server fee policy applies. The checkbox and message participate in WooCommerce's normal update_order_review flow so choosing or clearing gift wrap recalculates totals.
The classic and Block implementations should converge on the same final facts: selected or not selected, bounded message, server fee, and owned order metadata. They may use different browser events, but they cannot produce different order totals for the same cart and settings.
The full setup and diagnostic path is in the Gift Wrap at Checkout documentation.
A beginner setup that can be tested
Open WooCommerce > Gift Wrap, enable the option, write a plain label, save a small test fee, and save a message label. Add one product to a test cart and open the checkout type used by the store.
Select gift wrap and wait for WooCommerce to refresh totals. Confirm the exact fee appears once and the order total increases by the server-saved amount. Enter a message and place a test order. The confirmation and administrator order record should carry the selected state, sanitized message, and fee.
Clear the checkbox and confirm the fee disappears. Refresh the page and verify that the visible state agrees with the latest server-confirmed cart. Repeat in both classic and Block checkout when the site supports both.
Free-wrap thresholds without rewriting the cart
Pro can make the gift-wrap fee zero when the cart reaches a saved subtotal threshold. The threshold is a rule for this fee only. It does not change product prices, apply a coupon, alter shipping, edit taxes, or add a discount line.
Cart subtotals can change when quantities or coupons change, so the threshold must be evaluated during WooCommerce's normal totals calculation. The shopper should see the latest server response, not a one-time browser estimate.
Order history and uninstall behavior
The order record needs enough information for fulfillment: whether gift wrap was selected, the sanitized message, and the fee used for that order. Those facts are historical business records.
Deleting the plugin should remove current settings and shopper-session state but preserve owned historical order metadata. Removing the past message or fee context could damage fulfillment and make old order totals harder to explain. This is an intentional exception to the usual remove-all-settings uninstall rule and should be documented before installation.
Resource and security boundaries
The free plugin reads its settings on checkout, not on unrelated public pages. One small checkout-only script supports Checkout Block. There is no custom table, scheduled job, analytics call, hidden product, public StoreFixKit request, or site-wide catalog scan.
Settings writes require WooCommerce management capability and WordPress nonces. Labels and messages are sanitized on input and escaped on output. The browser sends no amount. Pro license checks cannot control the free checkout option and should not add ordinary-page queries.
The WordPress sanitizing guide and escaping guide describe the input and output boundaries used by WordPress plugins.
Testing checklist
Block checkout select and clear
Select gift wrap, verify one fee and the new total, clear it, and verify both return to the original state.
Classic checkout select and clear
Trigger the native order-review refresh and verify the same server-calculated outcomes.
Required message
With Pro's rule enabled, a selected option and blank message must not create an order. A valid bounded message must succeed.
Forged browser amount
Add an unexpected amount field to a test request. It must have no effect because no server code reads a client amount.
Failed Store API update
Interrupt one update. Controls must restore the last confirmed state or clearly ask for review; checkout must not create an inconsistent order.
Double callback or totals refresh
Repeated calculations must not add the same fee twice.
Below and above the Pro threshold
The gift-wrap fee changes according to the saved threshold without changing merchandise, coupons, shipping, or taxes.
Update and rollback
Move from the current version to the previous ZIP and forward again. Settings remain readable, checkout remains usable, and historical order metadata remains intact.
Missing WooCommerce or Free/Pro mismatch
WordPress must continue loading without a fatal error. Free remains the fallback when Pro or its license API is unavailable.
Support evidence without customer data
Capture the checkout type, WooCommerce and plugin versions, store currency, saved fee, threshold state, whether the message is required, an anonymized cart subtotal, the relevant totals response, and screenshots. Do not send customer addresses, payment data, credentials, or full license keys.
Rollback boundary
Retain previous Free and Pro ZIPs and a database backup. Deactivate both, install the previous versions, activate Free before Pro, and place a test order. Rollback must not rewrite historical orders, products, inventory, shipping, taxes, coupons, or payment settings.
FAQ
Does the shopper's browser choose the gift-wrap fee?
No. The browser sends only the choice and message. WooCommerce calculates the fee from the merchant's saved server setting.
Does it work with WooCommerce Checkout Block?
Yes, on WooCommerce 8.9 or newer. It uses native additional checkout fields and the Store API extension update mechanism.
Does it create a hidden gift product?
No. This focused plugin adds one order-level fee and does not create a product, SKU, or inventory record.
Can Pro require a gift message?
Yes. The message becomes conditionally required only when gift wrap is selected, with frontend and backend validation.
What happens when a totals update fails?
The controls return to the last server-confirmed state or checkout asks the shopper to review them. An inconsistent final order is rejected.
Does uninstall remove gift details from old orders?
No. Historical order metadata and fee totals are preserved so fulfillment and past records are not damaged.
Focused plugin
Use the one-job implementation.
Add one order-level gift-wrap checkbox, a short message, and a server-calculated flat fee to classic or Block checkout.