Plugin documentation

Advance Invoices for WordPress & WooCommerce

Create PDF invoices and estimates in WordPress, email them, share a private link where customers view and pay, take Stripe and PayPal payments, record partial payments and issue receipts. WooCommerce is optional.

Version 1.1.1 WordPress plugin Paid plugin Requires WordPress 6.4 Requires PHP 7.4 Tested up to 7.1 Updated 6 Oct 2026

1. Overview

Advance Invoices for WordPress & WooCommerce is a small invoicing application inside WordPress. You create invoices, optionally estimates, produce them as A4 PDF files, email them, share a private link with the customer, record or collect payments and issue numbered PDF receipts. This page documents version 1.1.0.

On the Plugins screen the plugin is listed under the same name, and its folder is wphub-advance-invoice. In the admin menu it appears as Invoices (and Estimates once you switch estimates on). Everything it stores uses the prefix wphub_invoice_.

It is written for freelancers, agencies and small businesses that want invoicing inside WordPress, and for WooCommerce stores that want a numbered PDF invoice on their order emails. WooCommerce is optional. Without it you type each customer in by hand and everything else works the same.

How it works

  1. You enter your company details, currencies and numbering under Invoices → Settings.
  2. You create a document under Invoices → Add New: a customer, one or more items, optional discount, shipping and tax. The server recalculates every total when you save, preview or print, so the figures on the PDF never come from the browser.
  3. One renderer (the bundled Dompdf library) turns the document into a PDF. The preview, the download and the email attachment all come from it, so they match.
  4. You email the PDF from the editor, or share the document's private link. The customer opens the link without logging in to view the document, download the PDF and, for invoices, pay with Stripe or PayPal.
  5. Every payment is a row in a ledger. The invoice status (Partially Paid, Paid) follows the ledger, and each payment gets a numbered PDF receipt.

At a glance

Invoices and estimates

Two separate systems with their own screens, numbering, statuses and tables. Estimates are optional and off by default; Convert to Invoice creates a new invoice and leaves the estimate's content unchanged.

Five PDF designs

Classic, Modern, Compact, Minimal and Business, for invoices and estimates. Add your logo, preview any design with your own company details, and pick a design for each document.

Private client page

Each document has a link containing a long random token. Customers view it, download the PDF, pay an invoice or accept or decline an estimate. Drafts are not visible and every page is marked noindex.

Stripe and PayPal

Customers pay on Stripe's or PayPal's own hosted page. Partial payments, a payment ledger, refunds recorded from the gateways' webhooks, and numbered PDF receipts.

WooCommerce when you want it

Search customers, orders and products, import an order into an invoice, attach the invoice PDF to customer order emails, create an invoice for each order at the statuses you choose, and keep it in step with cancellations and refunds. Orders are only read.

Safe numbers

Totals are calculated on the server with fixed rounding rules. Document numbers are claimed with one atomic database update and protected by a unique index, so parallel requests never produce a duplicate.

What it does not do

  • It does not work out tax for you. Tax is a percentage you type per item (and for shipping), added on top of the net amount. There are no tax tables, tax classes or tax-inclusive prices.
  • It does not convert currencies. A document's currency only changes the symbol, position, decimals and separators.
  • It does not send reminders, create recurring invoices, add late fees or issue credit notes. The daily job only changes statuses and sends no email. A WooCommerce refund becomes a negative credit line on the original invoice.
  • It does not keep copies of PDF files. A PDF is generated each time it is previewed, downloaded, emailed or fetched from the client page.
  • It has no customer login area, no shortcodes, no blocks, no REST API and no WP-CLI commands.
  • It supports Stripe and PayPal only (another gateway can be added in code, see Hooks).
  • One address per document (billing). A customer's shipping address can be copied into the fields instead, but it is not printed as a second block.
  • The bundled PDF font (DejaVu Sans) has no Chinese, Japanese or Korean glyphs, and the templates set no right-to-left direction.

2. Requirements

ItemRequirement
WordPress6.4 or later (header Requires at least: 6.4); tested up to 7.1.
PHP7.4 or later. The extensions dom and mbstring are required for PDFs: the plugin checks for them and, when one is missing, shows the notice "PDF generation is unavailable." with "PDF generation needs the PHP extension(s): dom, mbstring. Ask your host to enable them." (only the missing names are listed). The Settings screen also advises gd for logos; the plugin does not check for it.
DatabaseMySQL or MariaDB. The code uses GET_LOCK(), LAST_INSERT_ID() and transactions.
WooCommerceOptional. The header declares WC requires at least: 9.0 and WC tested up to: 11.1; the code itself only checks that WooCommerce is loaded, not its version. High-Performance Order Storage (HPOS) compatibility is declared with WooCommerce's custom_order_tables feature.
Other pluginsNone required. If another plugin has already loaded the Dompdf library in the same request, that copy is reused instead of the bundled one (PHP cannot declare a class twice). WooCommerce Subscriptions is used only when present.
Stripe / PayPalOptional. A Stripe account (secret key) and/or a PayPal account (REST app client ID and secret) for online payments. Your server must be able to make outgoing HTTPS requests to api.stripe.com and to api-m.paypal.com (api-m.sandbox.paypal.com in sandbox mode), and Stripe and PayPal must be able to reach your site's address to deliver webhooks.
EmailDocuments and receipts are sent with wp_mail(), so any SMTP plugin you use applies.
WP-CronThe daily maintenance job (Overdue, Expired, clean-up of temporary files) is a WordPress cron event. It runs when WordPress cron runs.
LicenceOptional. The key only unlocks update packages, see Installation.

3. Installation

Install and activate

  1. Download the plugin zip from your account on wpexpertshub.com.
  2. In WordPress open Plugins → Add New → Upload Plugin, choose the zip and click Install Now, then Activate.
  3. Open Invoices → Settings and fill in Company, Currency and Invoice.
  4. Optional but recommended: activate your licence (next section) to receive updates.

The plugin works as soon as it is activated. No licence key is checked anywhere in the invoicing code: the bundled client only handles activation, deactivation and update information.

Activate your licence (updates)

The plugin bundles the WpExperts Hub licence and update client (version 2.0.0 of the client). One screen manages every WpExperts Hub plugin on the site that bundles it.

  1. Open Plugins → WpExperts Hub Licences, or click Licence under the plugin's name on the Plugins screen. Both are shown to users who can manage_options.
  2. Paste the key from your purchase email (it is also shown under My Account → Downloads on wpexpertshub.com) and click Activate licence. Only letters, digits and hyphens are kept from what you paste.
  3. The card for the plugin shows Active and the key in masked form (the first four and last four characters). New versions then appear in the normal WordPress updates screen and on the Plugins screen, and install like any other plugin update.
  4. Lost your key? Open the line Can't find your key? Email it to me on the same screen, enter the email address used for the purchase and click Send licence key.
  5. Before you move the plugin to another live site, click Deactivate licence. The licence is removed from this site even when the licence server cannot be reached.

Without an active licence the plugin keeps working. No update package is offered, and under an available update WordPress shows "Automatic update is unavailable for this plugin. Activate your licence to enable updates." While a licence is missing, the Plugins screen also shows a notice to administrators: "Activate your WpExperts Hub licence to receive updates for: ...". What the client sends to wpexpertshub.com, and when, is listed under Privacy.

Update information is fetched in one batched request and cached for 12 hours in the site transient wpxh_licence_check_v2. If the licence server cannot be reached, the previous answer is kept and the check is retried after one hour. If the server reports that the licence is no longer active for this site, the card changes to Not active at the next update check.

What activation creates

  • Eight database tables (see Database tables), created with WordPress's dbDelta().
  • Two capabilities, wphub_invoice_manage and wphub_invoice_settings, granted to Administrator (both) and, when that role exists, Shop manager (manage only). See Who can do what.
  • The settings options with their defaults (one option per settings tab, plus the currency option) and the number counters for invoices, estimates and receipts.
  • The daily cron event wphub_invoice_daily, first due one hour after activation.

Nothing is created for estimates until you switch them on, apart from their table and counter. No pages, posts, roles or user accounts are created.

Updating

With an active licence, update from the WordPress updates screen. You can also upload a newer zip under Plugins → Add New → Upload Plugin and choose Replace current with uploaded. On the first request after an update the plugin compares the stored plugin and database versions with its own and, when they differ, re-runs the table definitions, capability grants, default settings and the cron schedule. Existing settings and documents are kept.

Version 1.1.0 renamed the plugin's internal names (tables, options, capabilities, the cron event, admin page URLs, hooks and CSS classes). On a site that still holds data from 1.0.0, the plugin moves that data to the new names once, automatically. Old admin page URLs are redirected (HTTP 301) and client links and webhook URLs that were already sent keep working. Hooks and filters were not given aliases.

Multisite

The plugin has no network settings. The schema routine runs per site on init, so each site gets its own tables, options and capabilities the first time a request reaches it with the plugin active. Deleting the plugin runs the uninstall routine for every site of the network.

4. Quick start

  1. Activate the plugin. Open Invoices → Settings → Company, enter your company name and address and choose a logo.
  2. On the Currency tab tick the currencies you need and choose one default.
  3. On the Invoice tab set the number prefix, starting number, "Due in (days)", default payment terms and footer. On General choose the default invoice design and click Preview under a design to see a sample PDF with your own details.
  4. Open Invoices → Add New. Fill in the customer, add items, then click Save Draft.
  5. Click Preview to check the PDF. Click Send Invoice to email it with the PDF attached; a draft becomes Sent.
  6. When the customer pays by bank transfer or cash, open the invoice and use Payments → Record a payment. The status and the balance follow.
  7. To take card payments, open Payments in Settings, switch on Online payments, enable Stripe and/or PayPal, enter the keys and add the webhook URLs shown there in the gateway's dashboard. Make a test payment first.
  8. With WooCommerce, open the WooCommerce tab to attach the invoice PDF to order emails or to create an invoice for every order.

5. Features

Invoices and estimates

The plugin manages two kinds of document. They share the editor, the PDF designs, the client page and the email routine, but they have their own database tables, number sequences, statuses, list screens and settings tab.

ItemInvoiceEstimate
MenuInvoices → All Invoices / Add New / SettingsEstimates → All Estimates / Add New, shown only while estimates are switched on
Second dateDue dateValid until
Extra fieldsPurchase order number, payment terms, WooCommerce order link, link to the source estimateTerms, link to the converted invoice
Default numberINV-000001EST-000001
PaymentsLedger, partial payments, receipts, Stripe and PayPalNone. A customer can accept or decline instead.
StatusesDraft, Sent, Pending Payment, Partially Paid, Paid, Overdue, Refunded, CancelledDraft, Sent, Accepted, Declined, Expired, Converted, Cancelled

Switching estimates on or off off by default

Open Invoices → Settings → General and tick Enable estimates (quotes) in the Estimates row. While estimates are off:

  • The Estimates menu, the Estimate settings tab, the default estimate design row and the estimate subject and body fields on the Email tab are not shown, and the Payments tab describes client pages for invoices only.
  • The estimate list and editor pages are not registered, so WordPress answers their old URLs with "Sorry, you are not allowed to access this page." The estimate actions (download, preview, duplicate, delete, convert, regenerate link, design preview, saving) stop with "Estimates are switched off. Turn them on in Invoices → Settings → General." Estimate client links answer "This page is not available", which also closes the PDF download and the Accept and Decline buttons for them. Sending an estimate by email is refused with "Estimates are switched off." The daily job leaves estimates alone.
  • Nothing is deleted. Existing estimates, the estimate email wording and the estimate design choice come back when you switch estimates on again. The row shows how many estimates are being kept.

Sites that already had estimates when they updated to 1.1.0 start with estimates off as well; there is no migration.

The editor

Open Invoices → Add New, or click a number in the list. The page title is "Create Invoice" for a new document and "Edit Invoice INV-000001" for a saved one. The same screen with estimate wording edits estimates.

Top bar

ButtonWhat it does
PreviewRenders the form as it is on screen, without saving, and opens the PDF in a new tab. It uses the same renderer as the download. Fields that are still invalid are previewed as typed. A new document previews with the number shown in the form; nothing is claimed.
Send Invoice / Send EstimateOpens the email dialog (see Emailing documents). It is disabled until the document has been saved. The email attaches the last saved version; unsaved changes trigger a warning in the dialog.
Save & Generate PDFSaves, then reloads the editor and starts the PDF download in a hidden frame. The history gets the line "Saved and PDF generated."
Save Draft / Save changesThe primary button. For a new document or a draft it reads Save Draft and forces the status to Draft whatever the status box says. For any other status it reads Save changes. Ctrl/Cmd + S clicks it.

A badge reading "Unsaved changes" appears as soon as you edit anything, and the browser warns before you leave the page with unsaved changes. If a save fails validation, nothing is lost: the values you typed are kept for five minutes and shown again with the error message.

Details

FieldRules
Invoice number / Estimate numberRead-only ("Numbers are assigned automatically.") unless editable numbers are on. See Numbering. A number you type can have at most 63 characters (the box accepts 64, but the server then refuses it with "The number may only contain letters, digits and - _ / # . and spaces.").
Invoice date / Estimate dateRequired date. Defaults to today in the site's time zone.
Due date / Valid untilOptional date. Prefilled with the issue date plus "Due in (days)" (invoices) or "Validity period (days)" (estimates) when you create the document. It is not recalculated when you change the issue date. It cannot be earlier than the issue date: "The due date cannot be earlier than the invoice date." / "The "valid until" date cannot be earlier than the estimate date."
Reference numberFree text, up to 191 characters. Printed in the PDF details block.
Purchase order numberInvoices only. Up to 191 characters.

Customer

Choose Manual customer and type the details, or, when WooCommerce is active, WooCommerce customer and search. The search box needs at least two characters and returns up to 20 matches, found by WooCommerce's own customer search and also by billing first name, last name, company and email. Selecting a customer fills the fields below and records the WooCommerce user ID; every field stays editable. If the customer has a shipping address, a link Use the customer's shipping address instead appears and copies it into the same fields.

FieldLimit
Full name, Company191 characters each. At least one of the two is required: "Enter a customer name or company."
Email191 characters, must be a valid address ("The customer email address is not valid."). Optional, but needed to email the document and its receipts.
Phone64 characters.
Address, Address line 2191 characters each.
City, State / province100 characters each.
Postcode32 characters.
Country100 characters. With WooCommerce active the field suggests WooCommerce's country names; any text is accepted.
Tax / VAT number64 characters.

The document keeps its own copy of these details. Changing the customer's WooCommerce profile later does not change existing documents. On the PDF the company name is the first line when a company is given, with the person's name under it.

Items

Each row has a description, an SKU or reference, a quantity (hours in hourly mode), a unit price (hourly rate), a discount, a tax percentage and a calculated total. Drag the handle to reorder rows, use the copy icon to duplicate a row and the bin icon to remove it (removing the last row clears it instead). With WooCommerce active, Add a WooCommerce product searches products and variations by name or SKU (two characters or more, up to 15 hits; variable parents are skipped because their variations are listed) and adds a row with the product name, SKU and price. Only published and private products are offered.

FieldRules
DescriptionUp to 2,000 characters, line breaks kept. Required for any row that has a price, discount or SKU. A row with no description, SKU, price or discount is ignored.
SKU / refUp to 191 characters. Printed only when at least one row has one.
Qty / HoursA number, not negative. Printed without trailing zeros, up to four decimals.
Unit price / Hourly rateA number. It may be negative, which gives a credit line.
DiscountA fixed amount for the line, not negative. It is capped at the line's gross amount.
Tax %0 to 100. "The tax rate of item N must be between 0 and 100."
Rows200 at most (filter wphub_invoice_max_items): "A document can have at most 200 items." Each row posts six fields, so a very long document can reach PHP's max_input_vars limit (default 1000) before it reaches 200 rows. At least one row is needed: "Add at least one item."

A value that is not a number gives "Item N contains a value that is not a number." Type amounts with a full stop as the decimal separator and no thousands separator; a comma is not accepted.

Totals, notes and terms

  • Discount: choose Fixed amount or Percent and a value (not negative; a percentage cannot exceed 100).
  • Shipping / other charges: an optional label (up to 100 characters, printed on the PDF; "Shipping" when blank), an amount (not negative) and its own tax percentage (0 to 100).
  • The tax line uses your Tax label setting. Totals update live in the browser, and are recalculated on the server when you save, preview or generate the PDF: "Totals are recalculated on the server when you save, preview or generate the PDF."
  • Notes (up to 5,000 characters), Payment terms (invoices, up to 1,000) or Terms (estimates, up to 5,000), and Footer text (up to 2,000). A new document starts with the defaults from the Invoice or Estimate tab.

Sidebar

  • Status: any status of that document type. Choosing Paid or Partially Paid by hand changes the status only; it does not create a payment (see Statuses).
  • Currency: the enabled currencies, plus the document's own currency if it was disabled later. Amounts are not converted: "Amounts are not converted when you change the currency." Saving a currency that is disabled, other than the one the document already uses, fails with "The selected currency is not enabled."
  • Pricing mode: Qty × price or Hourly. In hourly mode the columns read Hours and Hourly rate and the maths is hours × rate, but the PDF shows only the description and the amount. Hours and rate are never printed.
  • PDF design: five cards with thumbnails.
  • Links: Download PDF, Duplicate, Delete (with a confirmation) and, on estimates, Convert to Invoice.

Below those, saved documents show a Client page card, a Payments card (invoices), a Related box, the Email history (last 20 attempts) and the History (last 30 events).

How totals are calculated

The maths is the same on the server (which decides) and in the browser (which previews):

  1. Line gross = quantity × unit price. Line net = gross − the line discount (never more than a positive gross).
  2. Subtotal = the sum of the line nets.
  3. The document discount (a fixed amount, or a percentage of the subtotal) is capped at the subtotal and shared across the lines with a positive net, in proportion to their net amounts. The last such line takes the rounding remainder, so the shares always add up exactly.
  4. Line tax = (line net − its share of the discount) × the line's tax rate. Shipping carries its own tax rate.
  5. Total = subtotal − document discount + shipping + all tax.

Every step is rounded to the currency's number of decimals (0 to 4), half away from zero. The rounding writes the value with ten decimals and lets the next digit decide, so older and newer PHP versions (PHP's own round() changed in 8.4) and the browser's JavaScript give the same last digit. The PDF lists tax as one line per distinct rate, for example "Tax (10%)", using your tax label.

ExampleOne item of 60,000 with a fixed document discount of 5,000 and 18% tax: the subtotal is 60,000, the discount 5,000, the taxable amount 55,000, the tax 9,900 and the total 64,900.

Numbering

Invoices, estimates and receipts have independent sequences. A number is the prefix, then the counter padded with zeros to the chosen width: prefix INV-, padding 6 and counter 42 give INV-000042. A counter longer than the padding is not truncated.

  • Claiming: the next number is taken with a single atomic update of the counter row, before the document is written, and the unique index on the document number is the final guard. If a number collides (for example one typed by hand elsewhere), the plugin claims the next one and tries again, up to 15 attempts in all. A save that fails after the number was claimed leaves a gap in the sequence.
  • Starting number: saving the Invoice or Estimate tab raises the counter to the starting number if it is lower. Lowering the setting never lowers the counter, and existing numbers are not changed.
  • Next number: the settings tab and the editor show the next number without using it. The preview skips numbers that already exist (it looks ahead up to 50).
  • Editable numbers (off by default): with Editable invoice numbers or Editable estimate numbers ticked, the number field in the editor can be changed. A number you type must start with a letter or digit, contain only letters, digits, spaces and the characters - _ / # . and be at most 63 characters, and it must be unused: "That number is already used by another document." A number equal to the one suggested when the page opened (the form carries it in a hidden field, suggested_number) is treated as automatic and claimed fresh, so two people creating documents at the same time never collide.
  • Receipts use their own counter starting at 1, with the prefix and padding from the Payments tab (default RCT-000001). One receipt number is claimed for every payment that is recorded.
  • Duplicates and converted estimates always receive a fresh automatic number.

Allowed prefix characters are letters, digits and - _ / # ., up to 16 characters; any other character you type is dropped. The PDF file name is the number with spaces and slashes replaced by hyphens, and with other special characters such as # removed (WordPress's sanitize_file_name()), for example INV-000042.pdf.

Statuses

Invoice statuses

StatusMeaning and how it is set
DraftThe default for a new invoice. Customers cannot open the link of a draft (people who can manage invoices can, to preview it). Sending a draft by email changes it to Sent.
SentIssued. Set by emailing a draft, or by hand. Invoices created from WooCommerce orders start as Sent.
Pending PaymentSet by hand only. It is treated like Sent for overdue handling and online payment.
Partially PaidSet from the ledger when the net amount paid is above zero but below the total.
PaidSet from the ledger when the net amount paid reaches the total (within half of the currency's smallest unit). You can also choose it by hand, which changes the status only.
OverdueSet by the daily job for Sent, Pending Payment and Partially Paid invoices whose due date has passed, when Automatic status updates is on. History line: "Marked overdue automatically: the due date has passed."
RefundedSet when a WooCommerce order is fully refunded and the automatic order sync is on (or by hand). Finished: nothing is shown as due.
CancelledSet by hand, by a bulk action, or when the WooCommerce order is cancelled (automatic order sync). Finished: nothing is shown as due, and the invoice cannot be paid online.

The ledger decides Paid and Partially Paid every time a payment is recorded, changed or deleted, and again after you edit an invoice that has payments (for example to change its total), unless that same save changed the status on purpose. When the net amount falls to zero, a Paid or Partially Paid invoice goes back to Sent. Cancelled, Refunded and zero-total invoices are never changed by the ledger.

Estimate statuses

StatusMeaning and how it is set
DraftDefault. Not visible to customers.
SentSet by emailing a draft, or by hand. Only a Sent estimate can be accepted or declined on the client page.
Accepted / DeclinedSet when the customer clicks Accept this estimate or Decline, or by hand.
ExpiredSet by the daily job for Sent estimates whose "valid until" date has passed (when Automatic status updates is on), or by hand.
ConvertedSet when you use Convert to Invoice, or by hand.
CancelledSet by hand.

PDF designs

Five designs are available for invoices and for estimates. The design is stored on each document; new documents use the default from Invoices → Settings → General. Changing the default does not change existing documents. Documents created from WooCommerce orders use the design on the WooCommerce tab, and the PDF on order emails always renders with that tab's design whatever is stored.

DesignLook
1 – ClassicLogo (or company name) top left with the company details in two columns, a large document title and number top right, an accent rule, "Bill to" and "Invoice details", a striped table with an accent header row and an accent bar for the total. Notes and terms sit in boxes. Estimates get a "Valid until" call-out.
2 – ModernA coloured top bar, a light "INVOICE No. …" title with the date under it, the logo, then airy From and Bill to columns. Due date, reference, PO and order go under the customer. A hairline table with a numbered first column (01, 02 …) and a small-print second line for multi-line descriptions, a totals panel with a large amount, and a pill under it ("DUE DATE …", "PAID", "CANCELLED", "REFUNDED" or "VALID UNTIL …"). Payment terms sit left of the totals; if there are none, your company name does. It uses its own fixed colours for invoices and estimates.
3 – CompactA one-line header, a ruled three-column grid (From, Bill to, details), a dense bordered table, terms and notes on the left and a totals box on the right. Body text of 8 pt, the smallest of the five.
4 – MinimalA large plain title, a small logo tile (the first letter of the company name when there is no logo), a key/value block, From and Bill to side by side and a one-line headline: "<amount> due <date>", "<amount> paid", "<amount> – Cancelled" or, for estimates, "<amount> valid until <date>". Payment terms go under the headline. Hairline table, plain totals and an "Amount due" line.
5 – BusinessLogo left, title and company block right. Below, "Bill to" on the left; on the right the document number and dates, a PAID badge and "Amount due" (estimates show "Estimate total"). A grey-header table with the SKU under each description, ruled totals ending in "Amount due". Terms, notes and a "Payment details" box (your Additional information lines) sit on the left. The footer ends with the company name and the current year.

What every design shares:

  • Page and font: A4 portrait (filter wphub_invoice_paper_size), UTF-8, DejaVu Sans, which includes the rupee, euro, pound and yen signs. "Page X of Y" is drawn at the bottom right of every page in small grey type.
  • Long documents: the table header repeats on every page and rows are not split across pages.
  • Footer: your document footer text is fixed at the bottom of every page. When it is empty, designs 1 to 4 print the company name instead.
  • Logo: the Media Library image chosen under Settings → Company is embedded as data, with no remote request. Only JPEG, PNG and GIF files are used; any other image type prints no logo. A file larger than 1.5 MB is replaced by its "large" size when WordPress made one.
  • Columns: SKU, Discount and Tax columns appear only when at least one row has an SKU, a discount or a tax rate (design 5 prints the SKU under the description). In hourly mode the Qty and Unit price columns are hidden.
  • Totals: Subtotal; Discount (with its percentage when the discount is a percentage); the shipping line; one tax line per rate; then "Total due" (invoices) or "Estimate total". When payments exist, "Amount paid" and "Balance due" follow. Designs 4 and 5 label the total "Total", list "Amount paid" and end with "Amount due" (the balance) instead of "Balance due".
  • Status marks: a Paid invoice carries a PAID mark in the style of the design. A Cancelled or Refunded invoice gets a "Status" row in the details block; designs 4 and 5 show 0 as the amount due, design 4's headline reads "<amount> – Cancelled" (or Refunded) and design 2's pill names the status.
  • Estimates: all five designs end with "Accepted by", "Signature" and "Date" lines. Classic and Compact use teal for estimates and navy for invoices.

To see a design before you have any document, open Invoices → Settings → General and click Preview under a design card. A sample PDF opens in a new tab, using your real company details and logo with a built-in sample customer and sample items, and the default currency; the status is Sent, so Paid and partly paid variants are not shown. Nothing is saved and the number printed is only the next number, not used. The WooCommerce tab design picker has the same links.

Developers can replace a design's template from a theme or filter the data it receives, see Template files.

Where PDFs are written and who can reach them

The plugin never keeps invoice, estimate or receipt PDFs. A PDF is generated when it is asked for and streamed straight to the browser.

UseWhat happensWho can get it
Preview and download in the adminGenerated and streamed. A download is noted in the history ("PDF downloaded."), a preview is not.Logged-in users with wphub_invoice_manage, through a link carrying a nonce tied to the action, the document type and the document ID.
Client page "Download PDF" and the receipt linksGenerated and streamed.Anyone who has the document's private link while client pages are on. See Client page.
Email attachmentThe PDF is written to a temporary folder named wphub-invoice- followed by 12 random characters, inside WordPress's temporary directory. It is deleted as soon as wp_mail() returns (for WooCommerce order emails, when the request ends).Whoever can read that directory on the server for those moments.
LeftoversIf a request dies before the clean-up, the daily job removes plugin temporary folders older than 24 hours.Nobody. Only the plugin and the server's own processes touch these folders.

The temporary directory is the one WordPress 7.1.2 chooses with get_temp_dir(): the constant WP_TEMP_DIR when defined, otherwise the system temporary directory, then PHP's upload_tmp_dir, then the wp-content folder, then /tmp/. On a server where the system temporary directory is not writable, WordPress falls back to wp-content, which is inside the web root; define WP_TEMP_DIR to a folder outside it if that applies to you. Dompdf is told to use the same directory for its own working files.

The only other file the plugin writes is Dompdf's font cache in wp-content/uploads/wphub-advance-invoice/fonts (created the first time a PDF is rendered, with an index.php guard file in it and in its parent folder). It holds no customer data and is removed when the plugin's data is deleted on uninstall.

If the PDF library or a PHP extension is missing, documents still save; preview, download and email fail with the PDF engine's message ("PDF generation needs the PHP extension(s): dom, mbstring. Ask your host to enable them." or "The bundled Dompdf library is missing (vendor/ folder). Reinstall the plugin.") and a failed email is logged. "PDF generation is unavailable." is the heading of the warning shown on the plugin screens.

Emailing documents

Click Send Invoice (or Send Estimate) in the editor, or Send email under a number in the list, which opens the editor with the dialog already open. The dialog is titled "Send Invoice by email" and says "The PDF is generated and attached automatically."

FieldRules
ToOne or more addresses separated by commas, at most five. Each must be valid: ""x" is not a valid email address." Duplicates are removed. Empty: "Enter at least one recipient email address." More than five: "You can send to at most 5 addresses at once." Prefilled with the customer's email.
SubjectRequired ("Enter an email subject."). Tags and line breaks are removed.
MessageRequired ("Enter a message."). Plain text: it is sent as paragraphs and any HTML tags you type are removed.

Subject and message are prefilled from the templates on the Email tab with the variables replaced for this document. You can edit them each time. If the template contains neither {view_link} nor {pay_link} and client pages are on, the dialog appends a line: "View and pay online: …" for an invoice when online payments are on, "View online: …" for an invoice otherwise, and "View and respond online: …" for an estimate. The saved template is not changed.

What is sent

  • The message goes through wp_mail() with the content type HTML, so your SMTP or mail-log plugin sees an ordinary WordPress email. The PDF (named like INV-000042.pdf) is the only attachment.
  • Headers: From: is the Email tab's From name and From email when the address is valid (quotes, angle brackets and line breaks are removed from the name), and Reply-To: is added when set. Otherwise WordPress's defaults apply. The filter wphub_invoice_email_headers can change them.
  • A successful send adds "Emailed to …" to the history and a row to the email history, and a Draft becomes Sent ("Status changed to Sent after emailing the document."). The dialog says "Email sent." and the page reloads.
  • A failed send shows the reason in the dialog (the message from wp_mail_failed, or "WordPress could not send the email."), adds a "Failed" row with the reason to the email history and "Email to … failed." to the history. The status is not changed.

Variables

VariableValue
{customer_name}The customer's name, or the company when no name is given.
{company_name}Your company name (Settings → Company).
{view_link}The document's client page link. Empty when client pages are off.
{pay_link}Invoices. The same link as {view_link}.
{invoice_number}, {invoice_total}, {invoice_date}, {due_date}Invoices. The total is formatted in the invoice's currency; dates use your date format.
{balance_due}, {amount_paid}Invoices. Calculated from the payment ledger. An invoice you mark Paid by hand has no ledger payment, so {balance_due} still shows the whole total.
{estimate_number}, {estimate_total}, {estimate_date}, {valid_until}Estimates.
{receipt_number}, {payment_amount}, {payment_date}Receipt emails only. Receipt emails also understand every invoice variable above.

A variable that is not in the list is left as typed.

The client page

Every invoice and estimate has a private page at https://your-site/?wphub_invoice_doc=TOKEN. The token is i_ (invoice) or e_ (estimate) followed by 48 hexadecimal characters from 24 random bytes. It is created with the document. Customers never log in: the token in the link is the access right. The editor's Client page card shows the link with Copy, Open client page and Regenerate link.

CarefulAnyone who has the link can see that document, so send it only to your customer. Regenerate link replaces the token at once and every link you sent before stops working ("A new client link was created. Links you sent before no longer work.").

What the customer sees

  • Your logo (or company name), the document title and number, a status pill and the amount due (or "Total paid" or "Total" for a settled invoice, "Estimate total" for an estimate) with the due date or valid-until date.
  • Your details, the customer's details and the document details.
  • For invoices that can be paid: a Pay online card (see Online payments).
  • For a Sent estimate that has not expired: Accept this estimate and Decline. After the answer, the page cannot be answered again. An expired estimate shows "This estimate has expired. Please ask for an updated one."
  • The items, totals, payment terms or terms, and notes. The document footer text is not shown on the page, only on the PDF.
  • For invoices with payments: a Payments table with each receipt number as a download link.
  • Download PDF and Print buttons. The print style hides the buttons and the pay card.

Rules and protections

  • Switch client pages off under Settings → Payments → Client pages (default on). Then every link answers "This page is not available", the {view_link} variable is empty and the WooCommerce "Download invoice" button is not offered.
  • A draft answers 404 to visitors. People who can manage invoices see it, so you can preview a draft's page. A wrong, malformed or replaced token also answers 404 with the same neutral page ("This page is not available. The link may have expired or been replaced. Please contact the sender for a new one."), which reveals nothing about whether a token ever existed.
  • Every response carries noindex in a meta tag and in an X-Robots-Tag header, Referrer-Policy: no-referrer, X-Frame-Options: DENY (the page cannot be shown in a frame) and no-cache headers. The plugin also defines DONOTCACHEPAGE so page-cache plugins that honour it skip the page. Your theme is not used: the page is standalone HTML with inline styles and makes no external requests, and the plugin sets no cookies.
  • The Pay, Accept and Decline buttons post a WordPress nonce that contains the document's token. For a visitor who is not logged in, a WordPress nonce in WordPress 7.1.2 lasts between 12 and 24 hours and is not tied to a user, so it protects against forged requests and stale pages, not against someone who holds the link. An expired form shows "This page has expired. Please reload it and try again."
  • Estimates can be answered only while their status is Sent, and the owner is emailed (when Notify me is on). The history records "Accepted by the customer on the client page." or "Declined by the customer on the client page."

Online payments (Stripe and PayPal) off by default

Customers pay on Stripe's or PayPal's own hosted page. The plugin has no card fields and never sees card details. Nothing is offered until you switch on Online payments and configure at least one gateway.

Set up Stripe

  1. Open Invoices → Settings → Payments. Tick Online payments ("Show a Pay button on invoice client pages") and Enable Stripe.
  2. Paste your Secret key. It must look like sk_test_… or sk_live_… (a restricted key rk_test_… or rk_live_… with Checkout Sessions write access also works). Publishable keys (pk_…) are not accepted. A TEST or LIVE badge appears next to the Stripe heading once the key is accepted; the mode comes from the key's prefix.
  3. In the Stripe dashboard (Developers → Webhooks) add the endpoint shown in Stripe webhook URL and select the events checkout.session.completed, checkout.session.async_payment_succeeded and charge.refunded. Paste the endpoint's signing secret (whsec_…) into Webhook signing secret.
  4. Make a payment with test keys before you switch to live keys.

Set up PayPal

  1. On the same tab tick Enable PayPal. Sandbox mode is on by default: leave it on while testing with sandbox credentials and switch it off with your live credentials. A SANDBOX or LIVE badge shows once PayPal is enabled and has a client ID and a secret.
  2. Enter the Client ID and the Client secret from the PayPal Developer dashboard (Apps & Credentials).
  3. Optional but recommended: create a webhook in PayPal with the URL shown under PayPal webhook URL, subscribe it to PAYMENT.CAPTURE.COMPLETED and PAYMENT.CAPTURE.REFUNDED, and enter its Webhook ID. Without a Webhook ID every PayPal notification is rejected; payments are then recorded only when the customer comes back to your site.

The secret fields never show the saved value. A saved field shows "•••••••••••• (saved – leave empty to keep)" and a "✓ Saved" mark; leaving it empty keeps the stored value. There is no button to erase a saved secret: type a new one, or untick the gateway's Enable box.

What the customer experiences

  1. The customer opens the invoice link. If the invoice can be paid, a Pay online card offers one button per available gateway: "Pay with Card (Stripe)" and "Pay with PayPal". The card is shown when the status is not Draft, Paid, Cancelled or Refunded, a balance remains, Online payments is on, and at least one gateway is enabled and configured.
  2. When partial payments are allowed and the balance is above the smallest partial payment, the customer chooses "Pay the full balance (…)" or "Pay a different amount" and types an amount (digits with an optional decimal point and up to four decimals).
  3. The plugin checks the amount and starts the checkout. Stripe receives a Checkout Session for one line item named "Invoice INV-000042 – Your Company" for the amount in the invoice currency, with the customer's email prefilled when it is valid. PayPal receives an order with intent CAPTURE, no shipping address and the button text "Pay Now". The history records "The customer started an online payment of … with …."
  4. The customer pays on the gateway's page and returns to the invoice. The plugin checks the payment with the gateway itself (it fetches the Stripe session with your secret key, or captures the approved PayPal order) and never trusts the address in the browser. It then records the payment in the ledger.
  5. The page says "Thank you! Your payment was received. A receipt is available below." If the gateway has not finished (PayPal "pending", an asynchronous Stripe method) it says "Your payment is being processed. You will receive a receipt as soon as it is confirmed."; the webhook records the payment when it arrives. If the customer cancels, "The payment was cancelled. You have not been charged."

Rules for the amount

  • The amount cannot exceed the balance: "The amount cannot be more than the balance due (…)."
  • A partial amount needs Partial payments on: "Please pay the full balance." It must also be at least the smaller of the balance and Smallest partial payment (default 1): "The smallest partial payment is …." The balance itself is always payable, so the last payment may be smaller than the minimum.
  • Stripe and PayPal apply their own minimum charge amounts. The plugin does not know them; the gateway rejects the request, the history records the gateway's message under "payment failed", and the customer sees "The payment could not be started. Please try again in a moment or contact us."
CarefulAmounts are sent to the gateways in minor units calculated from the Decimals value you set for that currency on the Currency tab (2 for USD, 0 for JPY by default). Keep the standard number of decimals for any currency you take online payments in.

How a payment is recorded safely

  • A payment is only ever recorded from the gateway's own confirmation (the verified return, or a signed webhook), from an administrator or, with the WooCommerce options on, from a paid WooCommerce order.
  • Both paths can fire for the same payment. The ledger has a unique key on gateway plus transaction ID, so a repeated or parallel delivery returns the existing row and counts nothing twice.
  • Stripe webhooks are authenticated by the Stripe-Signature header: an HMAC-SHA-256 of the timestamp and the raw body with your signing secret, compared in constant time, with a tolerance of 300 seconds either way. With no signing secret saved, nothing is accepted. PayPal webhooks are verified by calling PayPal's own verification endpoint with your Webhook ID.
  • The webhook endpoint (?wphub_invoice_webhook=stripe or =paypal) accepts only POST requests up to 1 MB (a larger body gets the "Unknown gateway" answer) and answers JSON with a status and a short message, for example "Recorded", "Already recorded", "Invalid signature" (HTTP 400), "POST only" (405) or "Unknown gateway" (404).
  • A payment whose currency differs from the invoice's currency is refused ("The payment currency does not match the invoice currency.").
  • The plugin never issues a refund. Refunds you make in Stripe or PayPal reach the invoice through the webhooks (see The payment ledger).

The gateway API requests use the WordPress HTTP API with a 25-second timeout and the user agent WPHubAdvanceInvoice/1.1.1; https://your-site. Which wallets and card brands appear is decided by your Stripe or PayPal account; the plugin does not choose payment method types. The Settings labels read "Accept cards, Apple Pay and Google Pay with Stripe Checkout" and "Accept PayPal (and cards through PayPal)".

Test firstStart with Stripe test keys or PayPal sandbox credentials and make a test payment before you go live. Include a partial payment and, if you can, a refund, so that you see the invoice status, the receipt and the webhook all work on your own server.

The payment ledger and partial payments

The Payments card in the invoice editor is the single source of truth for money. It shows Paid and Balance due, and one entry per payment: the amount, the method and date, the note, "Refunded …" when part of it was refunded, a link with the receipt number that downloads the receipt PDF, and a bin icon that deletes the payment.

Record a payment by hand

Open Record a payment in the card (bank transfer, cash, cheque and so on).

FieldRules
Amount (currency)Prefilled with the balance. Must be greater than zero ("Enter a payment amount greater than zero."). The form does not stop you from recording more than the balance; the invoice then counts as Paid and the balance shows zero.
MethodBank transfer, Cash, Cheque, Card (offline) or Other.
DateA valid date, default today ("Enter a valid payment date."). Stored at 12:00 on that day.
Note (optional)Up to 200 characters in the form.
Email the receipt to the customerTicked when the Email receipts setting is on. A receipt is only emailed when this box and that setting are both on.

Recording a payment claims a receipt number, writes the history line "Payment of … received (…). Receipt …." and recalculates the invoice. The message is "Payment recorded."

How the invoice follows the ledger

  • Net paid is the sum of each completed payment minus what was refunded on it. The balance is the total minus net paid, never below zero.
  • Net paid at or above the total (less half of the currency's smallest unit) makes the invoice Paid; above zero but below the total makes it Partially Paid; back to zero returns a Paid or Partially Paid invoice to Sent.
  • Deleting a payment ("The payment was deleted and the invoice recalculated.") makes no call to Stripe or PayPal. Use it only to correct a wrong manual entry. A deleted payment's receipt number is not reused.
  • Refunds made in Stripe (charge.refunded) or PayPal (PAYMENT.CAPTURE.REFUNDED or PAYMENT.CAPTURE.REVERSED) are written to the payment as "Refunded …" and the status follows: a fully paid invoice with a refund becomes Partially Paid. A refund can never exceed the payment.
  • The invoice list shows "Balance …" under the amount of a part-paid invoice. The client page and the PDF show the paid amount and the balance.
  • Deleting an invoice also deletes its payments.

Receipts

Every payment gets a numbered PDF receipt (default RCT-000001). The receipt uses the Minimal look and has no design choice. It shows the receipt number, the invoice number, the date paid, From and Bill to, the headline "<amount> paid on <date>", the items and totals with the amount paid, a payment history table with this payment highlighted (refunds are marked) and, while a balance remains, "Balance remaining".

  • Admin: click the receipt number in the ledger.
  • Customer: the receipt number in the Payments table of the client page.
  • Email: with Email receipts on, the customer gets the receipt as an attachment after each payment, using the Receipt email subject and message. It goes to the payer's email from the gateway when valid, otherwise to the customer's email on the invoice. With no valid address nothing is sent and nothing is logged. The attempt, success or failure, is added to the email history.
  • Payments recorded from a WooCommerce order (see PDF on order emails) never send a receipt email; WooCommerce has its own emails.

When a payment arrives through Stripe or PayPal and Notify me is on, you also receive a plain-text email, "Payment received: … for invoice …", with the customer, amount, method, receipt number, balance and a link to the invoice. It goes to the company email under Settings → Company, or to the site admin email when that is not valid. Manual payments and WooCommerce payments do not send it.

Estimates, accepting and converting

With estimates switched on, the Estimates menu works like the invoice screens. Send an estimate, and the customer can Accept this estimate or Decline on the client page. You are emailed when Notify me is on ("Estimate EST-000001 was accepted" or "declined", with the total and a link). Neither answer converts anything automatically.

Convert to Invoice (a button in the editor sidebar and a link in the list) creates a new draft invoice and leaves the estimate's content untouched:

  • Copied: customer and WooCommerce customer link, currency (even if that currency has been disabled since), pricing mode, reference, discount, shipping, notes and all items.
  • Fresh or from settings: a new invoice number, today as the invoice date, the due date from "Due in (days)", the payment terms and footer from the Invoice tab and the default invoice design. The estimate's terms are not copied.
  • The estimate gets the status Converted and a link to the new invoice. The invoice gets a link back and the history line "Created from estimate …". Each shows the other in its Related box (the invoice's box shows the estimate while estimates are on).
  • Converting an estimate that is already Converted asks "This estimate was already converted. Create another invoice from it?" and makes another invoice.
  • Deleting either document clears the link on the other.

Currencies

Seven currencies are built in: US Dollar, Euro, British Pound, Indian Rupee, Australian Dollar, Canadian Dollar and Japanese Yen. Enable the ones you need and choose one default under Invoices → Settings → Currency (see the Currency tab). Every document can use any enabled currency. A currency only changes how amounts are displayed and rounded; nothing is converted. An existing document keeps working with a currency that you disable later. The filter wphub_invoice_currency_definitions adds more.

The invoice and estimate lists

Open Invoices → All Invoices (or Estimates → All Estimates). Until the first document exists the screen shows "Create your first invoice" with an Add new invoice button and, for people who can change settings, a link "First, add your company details and logo".

Summary tiles

Above the list, four tiles show the number of documents and the amounts per currency (largest first, up to three currencies, then "+N more"). A tile with a single status links to the list filtered on it. The tiles add up each document's total: a Partially Paid invoice counts at its full total, not at its balance.

InvoicesCounts
OutstandingSent, Pending Payment, Partially Paid and Overdue
OverdueOverdue
PaidPaid
DraftsDraft
EstimatesCounts
OpenSent
AcceptedAccepted
ConvertedConverted
Declined / expiredDeclined and Expired

Columns, search, filters and sorting

  • Columns (invoices): Invoice (the number, with row actions), Customer (with the company underneath), Date, Due date, Amount (with "Balance …" under a part-paid invoice), Currency, Status, WooCommerce order (a link to the order), PDF (a button) and Email ("Not sent", or "Sent N times, last …"). Estimates show Valid until instead of Due date and Converted invoice instead of WooCommerce order.
  • Row actions: Edit, Preview (the PDF in a new tab), Download PDF, Send email, Duplicate, Delete (with a confirmation), and on estimates Convert to invoice.
  • Status views above the table show All and each status that has documents, with counts.
  • Search ("Search invoices"): matches part of the number, customer name, company, email and reference, and for invoices also the WooCommerce order number and the purchase order number.
  • Filters: a currency drop-down ("All currencies"), a month drop-down ("All dates", the last 36 months that have documents, by invoice date) and a Filter button.
  • Sorting: click Invoice, Customer, Date, Due date, Amount, Currency or Status. The default order is newest first. The list shows 20 documents per page; this is not a setting.

Bulk actions

Tick rows and choose Delete or Mark as … followed by any status of that type, then Apply. Messages: "N documents deleted.", "N documents updated." and, when no row is ticked, "Select at least one document." "Mark as" changes the status only. It creates no payment, sends no email and does not touch the ledger (so "Mark as Paid" does not record a payment). Each change is written to the history.

CarefulBulk delete asks for no confirmation, and deleting an invoice also deletes its items, history, email history and payments. There is no trash and no undo.

Duplicate creates a new draft with a new number and today's date, copies the customer, items, amounts, reference, notes, terms, footer and design, recalculates the due date or validity date from your settings, and does not copy payments, the WooCommerce order link or the estimate link. If the document's currency has been disabled since, the duplicate is refused with "The selected currency is not enabled." and you land on the list; enable the currency again first.

History and email log

Every saved document shows two logs in the editor sidebar. History (the latest 30 events) records who did what and when: created, updated, status changes (with the old and new status, and a reason for automatic ones), PDF downloaded, emailed or email failed, link regenerated, payment started, payment failed (with the gateway's message, visible only to you), payment received, payment deleted, refund recorded, converted, duplicated, and WooCommerce events such as "Created automatically from WooCommerce order #N." and "WooCommerce refund #R (…) added as a credit line." Automatic events are attributed to no user. Email history (the latest 20 attempts) lists each recipient, date and subject, "Sent" or "Failed" and the error.

Both logs belong to the document. They are deleted with it and are not pruned otherwise; there is no retention setting.

WooCommerce integration

WooCommerce is optional. The plugin treats it as active when the WooCommerce class and the wc_get_order() and WC() functions exist. Orders and products are only read. An invoice stores its own copy of the order's data and never depends on the product again. The only things written to a WooCommerce order are optional private order notes (see Order notes and logs).

FeatureWith WooCommerceWithout WooCommerce
Customer"WooCommerce customer" with search, plus "Manual customer"Manual customer only
Create from an order"Create from a WooCommerce order" card in the invoice editorNot shown
Products"Add a WooCommerce product" search in the items cardNot shown
Settings → WooCommerce tabShownNot shown
Invoice box on order screensShownNot applicable
AJAX search and importWorkAnswer "WooCommerce is not active."
Currency default at activationThe store currency, if it is one of the seven built-in currenciesUSD

If you deactivate WooCommerce later, automatic invoices, email attachments and the order-screen features stop, and the list shows an order link as plain text ("#1234"). Invoices that came from orders are kept.

Customers, products and importing an order

In the invoice editor, Create from a WooCommerce order searches orders by number (digits, with or without a leading #), customer name or email. One character is enough; up to 15 results show as "#number – customer – date – status – total". Choosing one (and confirming "Importing replaces the current items. Continue?" when the items are not empty) fills the form. The order is not changed, and nothing is saved until you click Save.

  • Customer: billing name, company, phone, address (country and state codes are turned into names), email and the WooCommerce customer ID. A guest order becomes a manual customer; a registered customer becomes a WooCommerce customer. If the billing name and company are empty, the shipping name is used. The shipping address is offered through Use the customer's shipping address instead.
  • Items: one row per product line, with the product name and its chosen variation attributes on following lines, the SKU, quantity, unit price = line subtotal ÷ quantity, discount = subtotal − total, and tax rate = the line's tax ÷ its total × 100. Fees become rows of quantity 1.
  • Shipping: taken from the order's shipping lines (label, amount, and the rate derived from its tax).
  • Other fields: the invoice date is the order date, the due date is that date plus "Due in (days)", the reference is "Order #N", the notes come from the Invoice tab, the pricing mode is Qty × price, and the order total, date and currency are remembered with the invoice. The order link shows as "WooCommerce Order #N → Invoice …".
  • Currency: the order's currency when it is enabled in Settings → Currency. Otherwise the default currency is used and a warning explains that the amounts were imported unchanged.
  • Warnings shown above the form: the order uses a currency that is not enabled; "The invoice total (…) differs from the WooCommerce order total (…)", usually tax rounding or refunds; and "An invoice already exists for this order: …."

Tax rates are derived from the order's tax amounts, so a total can differ from the order by a cent; the warning tells you when it does. The search results and the data returned are only available to users with the manage capability.

Invoice PDF on WooCommerce order emails off by default

Open Invoices → Settings → WooCommerce and tick Auto attach ("Attach the invoice (PDF) to WooCommerce order emails"). Choose the Design template and tick the emails that should carry the PDF: Order on-hold, Processing order (ticked by default), Completed order (ticked by default) and Customer invoice / order details. Whether an email is sent at all remains a WooCommerce setting under WooCommerce → Settings → Emails.

  • The plugin hooks WooCommerce's woocommerce_email_attachments filter at priority 20. It adds a PDF only for the ticked customer emails, only when the email's object is a real shop order (never for admin emails such as New order, and never for a WooCommerce Subscriptions object).
  • One order, one invoice. The first time an email needs it, the invoice is created from the order and takes the next invoice number, even when "Auto record orders" is off. Every later email of that order carries the same invoice. A hand-made invoice linked to the order is reused. Two emails sent at the same moment (Processing and Completed, say) cannot make two invoices: a database lock per order makes the second wait.
  • The invoice is created with the status Sent, the invoice date set to the order date and a due date of that date plus "Due in (days)". An order on hold keeps its due date. The document is attributed to no user (the shopper who happens to be checking out is not recorded).
  • The PDF always renders with the design chosen on this tab (the stored design is not rewritten), so changing the setting changes every later email. A theme override of the design template applies as everywhere.
  • When Paid orders is on (default) and the order is paid, the order's payment is recorded on the invoice with no receipt email, so the PDF says PAID instead of showing a due date. "Paid" here means the order status is Processing or Completed, WooCommerce's default list of paid statuses in WooCommerce 11.1.2, which stores can change with the filter woocommerce_order_is_paid_statuses. An invoice made while the order was on hold turns Paid when the order becomes paid.
  • Free orders (default on): orders with a total of zero get no invoice and no attachment.
  • If the PDF cannot be built (no PDF library, say) the email is still sent and the order gets a private note "The invoice PDF could not be attached to the email: …".
  • The temporary PDF is deleted when the request ends. A mail-queue plugin that sends after the request has ended needs the filter wphub_invoice_wc_keep_attachments returning true; the files then stay until the daily clean-up (older than 24 hours).

Filters let you veto a single order, for example by payment method: wphub_invoice_wc_attach_invoice for the attachment and wphub_invoice_wc_record_invoice for automatic recording (see Hooks).

Orders as invoices (automatic records) off by default

Tick Auto record orders ("Create an invoice automatically for every WooCommerce order") and choose Create it when the order is: any WooCommerce order status except Cancelled, Failed, Refunded and the draft statuses (statuses added by other plugins are listed too). Processing and Completed are ticked by default. If you tick none, saving shows a warning and no invoice is created.

  • The invoice is created the moment the order reaches a ticked status, once per order, with the next invoice number and the design from the tab. It appears under Invoices like any other. "Pending payment" and "On hold" create an invoice with a due date; "Processing" and "Completed" mean the customer has paid, so with Paid orders on the invoice is marked Paid.
  • Orders that already have an invoice, made by hand or earlier, are left alone. Zero-total orders are skipped while Free orders is on. An order created in code can reach its status before its total is calculated, so an order skipped for a zero total is looked at again once when the request ends.
  • An order that becomes paid after its invoice was made (for example on hold, then Processing) has the payment recorded on the existing invoice. This never creates an invoice by itself.
  • The reference is "Order #N" (see the subscription format below), the history reads "Created automatically from WooCommerce order #N.", and the order gets a note "Invoice X created from this order." Any import warnings are added to a second note.
  • Errors in any of this are caught, written to the WooCommerce log and noted on the order; they never stop a checkout or a status change.

Cancellations, refunds and subscriptions

These three switches only act while Auto record orders is on. Cancelled orders and Refunds act only on orders that already have an invoice; Subscriptions decides whether subscription orders are invoiced at all. All three are on by default (they stay hidden until Auto record is ticked).

SwitchWhat happens
Cancelled orders (sync_cancel)When the order is cancelled, its invoice becomes Cancelled. Payments already recorded stay in the ledger. When the order is reinstated, the same invoice (same number) comes back as Sent and the ledger then makes it Paid or Partially Paid. Only an invoice that the plugin itself cancelled is restored: one you cancelled by hand stays cancelled.
Refunds (sync_refund)Each full or partial refund adds a negative line "Refund #ID" (the reason on a second line) so the invoice total is what the customer really paid. The line follows the refund's own tax split; if that would not lower the total by exactly the refund, the plugin uses one untaxed line for the whole amount. The refund is also written to the invoice's WooCommerce payment on the ledger, so a paid invoice stays Paid. When nothing is left to refund, the invoice shows Refunded. Deleting a refund in WooCommerce removes its line and undoes the ledger entry, and a Refunded invoice comes back. In WooCommerce 11.1.2, setting an order to Refunded by hand makes WooCommerce create a full refund; the plugin treats it like any other refund and, if the order later moves to another status while nothing remains refundable, keeps the invoice Refunded until that refund is deleted.
Subscriptions (sync_subscriptions)With WooCommerce Subscriptions active, the initial order and every renewal are ordinary orders and get their own invoice at the ticked statuses, referenced as "Subscription #12 (renewal) – Order #34" (the kinds are initial order, renewal, resubscribe and switch). The subscription itself is never invoiced. Untick the switch to leave subscription orders without automatic invoices.
Not tested against the real pluginThe Subscriptions part uses the plugin's functions wcs_get_subscriptions_for_order(), wcs_order_contains_renewal(), wcs_order_contains_resubscribe(), wcs_order_contains_switch() and wcs_order_contains_subscription() when they exist. The plugin's readme says it was tested with stand-ins, not with WooCommerce Subscriptions itself. Test one renewal on your own setup first.

A refunded or cancelled invoice shows nothing as due on the PDF, the client page, the order box and the list.

The Invoice box on the order screen

Every WooCommerce order has an Invoice box in the sidebar (both with and without High-Performance Order Storage), for people who can manage invoices. With an invoice it shows the number (a link to the editor), the status, the total, the amount paid, the due date, Download PDF and Open invoice. The invoice shown is the newest one that is not cancelled. Without an invoice it shows "This order has no invoice yet." and Create invoice.

Create invoice makes the invoice at once and returns to the order: it ignores the status list and the free-order rule because you asked for it, uses the design from the WooCommerce tab, records the payment when the order is paid (if Paid orders is on) and attributes the invoice to you. If the order already has one, the notice says "This order already has invoice …."

Download invoice for customers off by default

Tick Customer download to show an Invoice button in My Account → Orders and a Download invoice (PDF) button on the order page (after WooCommerce's order details table). It appears only for orders that already have an invoice, never for a draft or cancelled invoice. The button is the invoice's private client link with the PDF action, so Client pages must be on in the Payments tab; otherwise no button is shown. If another invoicing plugin adds its own button, customers will see both.

Order notes and logs

Order notes (default on) adds a private note to the order when an invoice is created, attached to an email, restored, cancelled, marked refunded or updated for a refund, for example Invoice INV-000042 attached to the "Processing order" email. The same switch controls the failure notes. Errors thrown while the plugin processes an order are written to the WooCommerce log (source wphub-advance-invoice) whatever this switch says.

Other invoice plugins

This plugin adds its PDF at priority 20 and does not remove attachments added by anyone else. If you also run another plugin that attaches an invoice to the same emails, such as the free PDF Invoice for WooCommerce with its own email list ticked, customers receive two PDFs. Untick the emails in one of the two plugins. The two plugins can share one site: if the other plugin has already loaded Dompdf, this plugin reuses that copy.

Automatic tasks

One WordPress cron event, wphub_invoice_daily (daily), does three things. It is scheduled at activation (first run one hour later) and re-scheduled when the plugin upgrades its stored version; deactivating the plugin removes it.

  1. Marks Sent, Pending Payment and Partially Paid invoices with a due date before today as Overdue, when Automatic status updates is on. Up to 500 invoices per run. No email is sent.
  2. With estimates on and the same setting, marks Sent estimates with a valid-until date before today as Expired (up to 500 per run).
  3. Deletes the plugin's temporary PDF folders that are more than 24 hours old.

The date used is today in the site's time zone. Nothing is ever marked Paid automatically by this job.

Who can do what

The plugin adds two capabilities. They are granted to roles when the plugin is activated or upgraded.

CapabilityAllowsGranted to
wphub_invoice_manageEverything on documents: lists, editor, preview, download, send, duplicate, delete, convert, record and delete payments, the Invoice box on orders, and previewing a draft's client page.Administrator and Shop manager
wphub_invoice_settingsThe Invoices → Settings screen and saving it.Administrator only

The Shop manager role exists only with WooCommerce. A role that does not exist when the capabilities are granted does not get them later; deactivate and reactivate the plugin to grant them again. The filter wphub_invoice_default_roles changes the list of roles; every role you add through it receives both capabilities, only the role named shop_manager is limited to the first. Deleting the plugin removes both capabilities from every role, even when you keep the data.

Every admin action that changes something checks the capability and a nonce bound to the action, the document type and the document ID, so a link made for another document or action is refused. The AJAX endpoints check the capability and one shared nonce. The licence screen needs manage_options. Client pages need neither (the token is the credential), and the webhooks are authenticated by signature.

6. Screens and menu paths

The menu Invoices sits at position 56 with the dashicon "media-text"; Estimates (position 57, dashicon "clipboard") appears only while estimates are on. Every plugin screen needs the capability shown. Their style sheet and scripts are loaded on these screens only.

ScreenMenu pathAddressCapability
Invoice listInvoices → All Invoicesadmin.php?page=wphub-invoiceswphub_invoice_manage
Invoice editorInvoices → Add New (or click a number)admin.php?page=wphub-invoice, with &id=N to editwphub_invoice_manage
SettingsInvoices → Settings (also the "Settings" link under the plugin's name on the Plugins screen)admin.php?page=wphub-invoice-settings&tab=general (tabs general, company, currency, invoice, estimate, payments, woocommerce, email)wphub_invoice_settings
Estimate listEstimates → All Estimatesadmin.php?page=wphub-estimateswphub_invoice_manage, estimates on
Estimate editorEstimates → Add New (page title "Add New Estimate")admin.php?page=wphub-estimatewphub_invoice_manage, estimates on
LicencesPlugins → WpExperts Hub Licencesplugins.php?page=wpexperts-hub-licencesmanage_options
Order Invoice boxWooCommerce order edit screen, sidebarThe order's own screenwphub_invoice_manage
Client pageNone (a private link)/?wphub_invoice_doc=TOKEN on your home addressNone

The Settings screen is titled "Invoice settings" while estimates are off and "Invoice & Estimate settings" while they are on. The tabs run General, Company, Currency, Invoice, Estimate (only while estimates are on), Payments, WooCommerce (only when WooCommerce is active) and Email, except that the WooCommerce tab comes last when estimates are off. While you edit a saved document, the menu keeps "All Invoices" (or "All Estimates") highlighted. Admin notices ("Saved.", "Settings saved.") are queued for your next plugin screen, or for the next admin screen of any kind when the action started on the WooCommerce order screen.

When the PDF library is unavailable, a warning "PDF generation is unavailable." with the reason is shown on every plugin screen.

7. Settings

Open Invoices → Settings. Each tab is its own form and is stored in its own option, so saving one tab never changes another. After saving you see "Settings saved." or, when the plugin adjusted something, "Settings saved, with changes:" followed by the reasons. Only users with wphub_invoice_settings can open the screen. Fields that are missing from a stored option fall back to the defaults shown here.

General

Option wphub_invoice_general.

Setting (label)KeyDefaultWhat it does
Estimates ("Enable estimates (quotes)")estimates_enabledOffSwitches the whole estimate system on or off. See Switching estimates on or off. The filter wphub_invoice_estimates_enabled can override it.
Default invoice designinvoice_design1The design new invoices start with, 1 to 5 (Classic, Modern, Compact, Minimal, Business). Each card has a Preview link.
Default estimate designestimate_design1Same for estimates. Shown only while estimates are on; the saved value is kept while it is hidden.
Date formatdate_formatd M YHow dates appear on PDFs, the client page, emails and the lists. One of d M Y (04 Oct 2026), j F Y (4 October 2026), d/m/Y (04/10/2026), m/d/Y (10/04/2026), Y-m-d (2026-10-04) or d.m.Y (04.10.2026). Number and currency formats are set per currency.
Tax labeltax_labelTaxThe name printed for tax, for example Tax, VAT or GST. The form allows 30 characters. If it is blank, PDFs print "Tax".
Automatic status updatesauto_statusOnThe daily job marks unpaid invoices as Overdue (and sent estimates as Expired, while estimates are on) once their date has passed. An invoice only becomes Paid or Partially Paid when a payment is recorded.
PDF engine(information)NoneShows which Dompdf is used ("Dompdf 3.1.6 (bundled with this plugin)." for the bundled copy; another text when a copy loaded by another plugin or a custom location is used) or what is missing, and the PHP version.
Uninstall: "Delete all plugin data when the plugin is deleted"delete_dataOffWhen on, deleting the plugin removes every invoice, estimate, email log, payment and setting for good. Ticking it asks for confirmation in the browser.
"I understand that this cannot be undone."delete_data_confirmOffThe second confirmation. Without it the first box is refused on saving ("Delete data on uninstall was not enabled: tick the confirmation box as well."), and untick of the first clears the second.
CarefulWith both uninstall boxes ticked, deleting the plugin removes every invoice, estimate, payment, log and setting permanently. Leave them unticked unless you really want that. Deactivating the plugin never deletes anything. See Deactivation and uninstall for exactly what uninstall removes.

Company

Option wphub_invoice_company. These details are printed on every PDF and client page. The filter wphub_invoice_company can adjust them. Text fields are cleaned with WordPress's text-field sanitiser; there is no length limit beyond what the database stores.

Setting (label)KeyDefaultWhat it does
Company namenameThe site titleRequired: saving it empty keeps the old value and reports "The company name is required." Printed as the sender on every document and available as {company_name} in emails.
Legal namelegal_nameEmptyWhen set, designs 1, 4 and 5, the receipt and the client page print it in place of the company name in the address block. Design 3 prints both; design 2 does not print it.
Logologo_idNoneAn image from the Media Library (Choose logo, Change logo, Remove). PNG, JPG or GIF; a wide image about 600 px wide works best. Only image attachments are accepted. See PDF designs for how it is embedded.
Address, Address line 2, City, State / province, Postcode, Countryaddress_1, address_2, city, state, postcode, countryEmptyPrinted as lines: the two address lines, then "city, state postcode", then the country.
PhonephoneEmptyPrinted on the PDF designs (not on the client page or the receipt).
EmailemailThe site admin emailMust be a valid address; an invalid entry is not saved and the old value stays (""x" is not a valid email address and was not saved."). Printed on documents and used as the address for "Payment received" and estimate-answer notifications.
WebsitewebsiteThe site addressPrinted on the PDF designs without http:// or https://.
Tax / VAT numbertax_idEmptyPrinted as "Tax ID: …".
Registration numberreg_numberEmptyPrinted as "Reg. no. …".
Additional informationextraEmpty"One item per line, printed under your company details (for example bank details)." Design 5 prints these lines in its "Payment details" box instead.

Currency

Option wphub_invoice_currencies: the default code and, per currency, enabled, symbol, position, decimals, decimal_sep and thousand_sep. The table lists the seven built-in currencies with a live preview of 1234567.891 in each row.

CurrencySymbolPositionDecimalsEnabled at first
US Dollar (USD)$Left2Yes
Euro (EUR)€Left2Yes
British Pound (GBP)£Left2Yes
Indian Rupee (INR)₹Left2Yes
Australian Dollar (AUD)A$Left2No
Canadian Dollar (CAD)C$Left2No
Japanese Yen (JPY)¥Left0No
ColumnAllowed values
SymbolUp to 8 characters. Empty falls back to the currency code.
PositionLeft ($99), Left with space ($ 99), Right (99$) or Right with space (99 $).
Decimals0 to 4. Totals are rounded to this number of decimals at every step.
Decimal sep.. or , (a currency with decimals always has one).
Thousands sep.,, ., (space), ' or (none).
EnabledTick to offer the currency on documents.
DefaultOne radio button. New documents use it.

Rules, enforced when you save and in the browser while you edit:

  • At least one currency must stay enabled; otherwise your previous selection is kept ("At least one currency must stay enabled. Your previous selection was kept.").
  • Exactly one currency is the default and it must be enabled. Choosing a default enables it. A default that is not enabled is reset ("The default currency must be one of the enabled currencies. It was reset.").
  • The decimal and thousands separators must differ; if they do not, the thousands separator is changed ("…: the decimal and thousands separators must differ; the thousands separator was changed.").
  • At first activation, USD, EUR, GBP and INR are enabled; the default is the WooCommerce store currency when WooCommerce is active and the currency is one of the seven (it is enabled too), otherwise USD.

Invoice

Option wphub_invoice_invoice.

Setting (label)KeyDefaultWhat it does
Number prefixprefixINV-Up to 16 characters from letters, digits and - _ / # .; anything else is dropped.
Starting numberstart_number11 to 999,999,999. "The next number is never lower than this. Existing numbers are not changed." Saving raises the counter to it; it never lowers the counter.
Number paddingpadding61 to 12 digits, padded with zeros ("6 gives 000001").
Next number(information)NoneShows the number the next invoice will most likely get. Using the page does not consume it.
Editable invoice numbers ("Allow changing the number on a document")allow_number_editOffLets you type a number in the editor. "Numbers always have to be unique." See Numbering.
Due in (days)due_days300 to 3650. "Default due date = invoice date + this many days."
Default payment termspayment_termsPayment is due within 30 days of the invoice date.One line of text that starts every new invoice's Payment terms (stored up to 1,000 characters per invoice).
Default notesnotesEmptyStarts every new invoice's Notes. Also used for invoices created from orders.
Default footerfooterThank you for your business.Starts every new invoice's Footer text.

Estimate

Option wphub_invoice_estimate. This tab exists only while estimates are on.

Setting (label)KeyDefaultWhat it does
Number prefixprefixEST-As for invoices.
Starting numberstart_number11 to 999,999,999.
Number paddingpadding61 to 12.
Next number(information)NoneThe number the next estimate will most likely get.
Editable estimate numbersallow_number_editOffAs for invoices.
Validity period (days)validity_days300 to 3650. "Default 'valid until' = estimate date + this many days."
Default termstermsThis estimate is valid until the date shown. Prices may change after that date.Starts every new estimate's Terms.
Default notesnotesEmptyStarts every new estimate's Notes.
Default footerfooterWe look forward to working with you.Starts every new estimate's Footer text.

Payments

Option wphub_invoice_payments. See The client page and Online payments for how these work together.

Setting (label)KeyDefaultWhat it does
Client pagesclient_pagesOnGives every invoice (and estimate, while estimates are on) a private online page, used by emails, receipts and the WooCommerce download button. Off: every link answers "This page is not available".
Online payments ("Show a Pay button on invoice client pages")online_paymentsOffThe master switch for the Pay button. It needs at least one enabled and configured gateway.
Partial payments ("Let customers pay part of the balance")allow_partialOnLets the customer choose "Pay a different amount" on the client page.
Smallest partial paymentmin_partial10 to 1,000,000,000, in the invoice currency. "The last payment may be smaller": the balance itself is always payable. 0 allows any positive amount.
Enable Stripe ("Accept cards, Apple Pay and Google Pay with Stripe Checkout")stripe_enabledOffOffers Stripe Checkout when the key below is valid.
Secret keystripe_secretEmptyStarts with sk_test_ or sk_live_; a restricted rk_ key with Checkout Sessions write access also works. At least eight characters follow the prefix. Never shown again after saving; empty keeps the stored key.
Webhook signing secretstripe_webhook_secretEmptyStarts with whsec_. Needed to accept Stripe webhooks. Never shown again.
Stripe webhook URL(information)NoneRead-only address to paste into Stripe: your home address followed by /?wphub_invoice_webhook=stripe.
Enable PayPal ("Accept PayPal (and cards through PayPal)")paypal_enabledOffOffers PayPal when the client ID and secret are filled in.
Sandbox modepaypal_sandboxOnUses PayPal's sandbox (api-m.sandbox.paypal.com) instead of live payments. Switch it off when you enter live credentials.
Client IDpaypal_client_idEmptyFrom PayPal Developer → Apps & Credentials.
Client secretpaypal_secretEmptyNever shown again; empty keeps the stored secret.
Webhook IDpaypal_webhook_idEmptyOptional but recommended; without it PayPal notifications are rejected. Events PAYMENT.CAPTURE.COMPLETED and PAYMENT.CAPTURE.REFUNDED.
PayPal webhook URL(information)NoneYour home address followed by /?wphub_invoice_webhook=paypal.
Receipt number prefixreceipt_prefixRCT-Up to 16 characters, as for invoice prefixes.
Receipt number paddingreceipt_padding61 to 12.
Next receipt number(information)NoneThe next receipt number (starts at 1).
Email receipts ("Email the customer a receipt (PDF) for every payment")send_receiptOnThe wording is in the Email tab. Off stops all receipt emails, including the editor's checkbox.
Notify me ("Email me when a customer pays online or answers an estimate")notify_adminOnSent to the company email, or to the site admin email when that is not valid.

The Stripe and PayPal keys are stored in this option in the database like any other plugin setting. They are never printed back into the page, never shown on a public page and never put in error messages.

WooCommerce

Option wphub_invoice_woocommerce. The tab appears only when WooCommerce is active. Rows for a section appear only while its main switch is ticked. See WooCommerce integration for what each does.

Setting (label)KeyDefaultWhat it does
Auto attach ("Attach the invoice (PDF) to WooCommerce order emails")attach_invoiceOffAttaches the order's invoice PDF to the ticked customer emails; creates the invoice if the order has none. Saving it on with no email ticked shows "Automatic invoices are on, but no email is selected: nothing will be attached until you tick at least one email."
Design templatedesign11 to 5. The design of the PDF on order emails and of invoices created from orders. Shown while Auto attach is ticked.
Attach to these emailsemailsProcessing order, Completed orderAny of customer_on_hold_order (Order on-hold), customer_processing_order (Processing order), customer_completed_order (Completed order) and customer_invoice (Customer invoice / order details). Customer emails only.
Auto record ordersauto_recordOffCreates an invoice automatically for every WooCommerce order at the statuses below.
Create it when the order isrecord_statusesProcessing, CompletedAny WooCommerce status except Cancelled, Failed, Refunded and drafts. Saving Auto record with none ticked shows "Automatic invoice records are on, but no order status is selected: no invoice will be created until you tick at least one status."
Cancelled orderssync_cancelOnCancel the invoice when the order is cancelled. Needs Auto record.
Refundssync_refundOnUpdate the invoice when the order is refunded (full or partial). Needs Auto record.
Subscriptionssync_subscriptionsOnInvoice WooCommerce Subscriptions orders (initial and renewals) and name the subscription on the invoice. Needs Auto record. A note appears under it when WooCommerce Subscriptions is not active.
Paid orders ("Show the invoice as Paid when the WooCommerce order is paid")mark_paidOnRecords the order's payment on the invoice (no receipt email). Applies to attached and automatically recorded invoices.
Free orders ("Skip orders with a total of zero")skip_freeOnNo invoice and no attachment for zero-total orders.
Customer downloadaccount_downloadOffShows an Invoice button in My Account → Orders and a Download invoice (PDF) button on the order page. Needs Client pages on.
Order notesorder_notesOnAdds private notes to the order when an invoice is created, attached or updated.
Order screen(information)NoneThe Invoice box is always on for people who can manage invoices; there is no switch.

Email

Option wphub_invoice_email. Emails are sent with wp_mail(). The Variables row lists the placeholders per email type (the estimate group only while estimates are on); see Emailing documents.

Setting (label)KeyDefaultWhat it does
From namefrom_nameThe site titleUsed in the From header. Quotes, angle brackets and line breaks are removed.
From emailfrom_emailThe site admin emailMust be valid: an invalid entry is not saved and the old value stays. When the field is empty, no From header is added.
Reply-to emailreply_toEmptyAdds a Reply-To header when valid.
Invoice email subjectinvoice_subjectInvoice {invoice_number} from {company_name}Prefills the Send dialog.
Invoice email bodyinvoice_body(see below)Plain text.
Estimate email subjectestimate_subjectEstimate {estimate_number} from {company_name}Shown only while estimates are on; kept while hidden.
Estimate email bodyestimate_body(see below)As above.
Receipt email subjectreceipt_subjectReceipt {receipt_number} for invoice {invoice_number}Used for every receipt email.
Receipt email bodyreceipt_body(see below)Plain text.

Default bodies:

Invoice:
Hello {customer_name},

Please find attached invoice {invoice_number} for {invoice_total}, dated {invoice_date}. Payment is due by {due_date}.

Thank you,
{company_name}

Estimate:
Hello {customer_name},

Please find attached estimate {estimate_number} for {estimate_total}, dated {estimate_date}. It is valid until {valid_until}.

Kind regards,
{company_name}

Receipt:
Hello {customer_name},

Thank you! We received your payment of {payment_amount} on {payment_date} for invoice {invoice_number}. Your receipt is attached.

Remaining balance: {balance_due}

Kind regards,
{company_name}

Subjects are single-line text. Bodies are plain text; HTML tags are removed when you save.

8. Developer reference

The plugin registers no shortcodes, no blocks, no REST routes and no WP-CLI commands. Its extension points are the actions and filters below, template overrides, the admin-post and AJAX actions, and the gateway registry. The PHP classes (WPHub_Invoice_*) are autoloaded from includes/ and includes/admin/ and can be called from your code, but they are the plugin's own internals, not a documented API, so check any call you depend on after an update.

Hooks

Actions the plugin fires

ActionArgumentsWhen
wphub_invoice_document_createdint $id, string $typeAfter an invoice or estimate was saved for the first time (editor, duplicate, conversion, WooCommerce automatic invoices). $type is invoice or estimate.
wphub_invoice_document_updatedint $id, string $typeAfter a document was saved again (editor, WooCommerce refund lines).
wphub_invoice_document_status_changedint $id, string $type, string $new_status, string $old_statusWhenever the status is changed by code, not by the editor form: bulk actions, the daily job, the payment ledger, emailing a draft, conversion, a customer accepting or declining an estimate and the WooCommerce sync. The editor's own save fires _updated instead.
wphub_invoice_document_deletedint $id, string $typeAfter a document and its items, logs and (invoices) payments were deleted.
wphub_invoice_document_emailedint $id, string $type, string[] $recipientsAfter a document email was accepted by wp_mail(). Receipt and notification emails do not fire it.
wphub_invoice_estimate_convertedint $estimate_id, int $invoice_idAfter an estimate was converted into an invoice.
wphub_invoice_payment_recordedint $payment_id, int $invoice_idAfter a ledger row was inserted and the invoice recalculated, before the receipt email.
wphub_invoice_payment_refundedint $payment_id, float $amountAfter refund data was written to a payment. $amount is the total refunded on that payment, not the latest refund.

Filters the plugin applies

FilterArguments (first is filtered)Purpose
wphub_invoice_estimates_enabledbool $enabledForce estimates on or off regardless of the setting.
wphub_invoice_default_rolesstring[] $rolesRoles that receive the capabilities when the plugin activates or upgrades. Default administrator, shop_manager.
wphub_invoice_companyarray $companyThe company details used on documents (the keys of the Company tab).
wphub_invoice_max_itemsint $maxMaximum rows per document. Default 200.
wphub_invoice_currency_definitionsarray $defsBuilt-in currencies keyed by code, each with name, symbol, position, decimals, decimal_sep and thousand_sep. A currency added here appears on the Currency tab, disabled until you enable it.
wphub_invoice_gatewaysWPHub_Invoice_Gateway[] $gatewaysThe payment gateway registry, keyed by gateway ID (stripe, paypal).
wphub_invoice_gateway_supports_currencybool $supported, string $currency, string $gateway_idWhether a gateway can charge a currency. Default true for every currency.
wphub_invoice_pdf_viewarray $view, string $type, array $docThe view model every PDF template and the client page read.
wphub_invoice_template_filestring $file, string $designThe template path of a design (after the theme override lookup). Not applied to the receipt template.
wphub_invoice_paper_sizestring $sizePaper size passed to Dompdf. Default A4 (portrait).
wphub_invoice_dompdf_options\Dompdf\Options $optionsThe Dompdf options. The plugin sets remote files off, embedded PHP off, JavaScript off, default font DejaVu Sans, temporary directory and font cache.
wphub_invoice_dompdf_autoloadersstring[] $pathsComposer autoloader files that may provide Dompdf. Default: the bundled vendor/autoload.php.
wphub_invoice_email_headersstring[] $headers, string $type, array $docHeaders of document emails (not receipts or notifications).
wphub_invoice_wc_attach_invoicebool $attach, WC_Order $order, string $email_idReturn false to skip the PDF attachment for an order email.
wphub_invoice_wc_record_invoicebool $record, WC_Order $orderReturn false to skip automatic recording of an order.
wphub_invoice_wc_keep_attachmentsbool $keepReturn true to leave the temporary PDFs of order emails for the daily clean-up instead of deleting them at shutdown.
wpxh_licence_server, wpxh_licence_sslverifystring $server / bool $verifyThe licence client (not the invoicing code). Change the server address or the certificate check; the constant WPXH_LICENCE_SERVER sets the default address.

Examples:

// No invoice PDF on cash-on-delivery order emails.
add_filter( 'wphub_invoice_wc_attach_invoice', function ( $attach, $order, $email_id ) {
	if ( 'cod' === $order->get_payment_method() ) {
		return false;
	}
	return $attach;
}, 10, 3 );

// Do not create automatic invoices for cash-on-delivery orders.
add_filter( 'wphub_invoice_wc_record_invoice', function ( $record, $order ) {
	return 'cod' === $order->get_payment_method() ? false : $record;
}, 10, 2 );

// Offer PayPal only for GBP and USD invoices.
add_filter( 'wphub_invoice_gateway_supports_currency', function ( $supported, $currency, $gateway_id ) {
	if ( 'paypal' === $gateway_id && ! in_array( $currency, array( 'GBP', 'USD' ), true ) ) {
		return false;
	}
	return $supported;
}, 10, 3 );

// Letter paper instead of A4 (the built-in designs are laid out for A4).
add_filter( 'wphub_invoice_paper_size', function () {
	return 'letter';
} );

// React to a recorded payment.
add_action( 'wphub_invoice_payment_recorded', function ( $payment_id, $invoice_id ) {
	$payment = WPHub_Invoice_Payments::get( $payment_id );
	error_log( sprintf( 'Invoice %d received %s via %s', $invoice_id, $payment['amount'], $payment['gateway'] ) );
}, 10, 2 );

Recording a payment from your own code

WPHub_Invoice_Payments::record( $invoice_id, $args ) adds a ledger row, claims a receipt number, recalculates the invoice and, depending on send_receipt and the Email receipts setting, emails the receipt. When send_receipt is true and the gateway is anything other than manual, it also sends you the "Payment received" email if Notify me is on. It returns array( 'id' => int, 'duplicate' => bool ) or a WP_Error. The arguments are amount (required, greater than zero), gateway (default manual), txn_id (an empty value becomes a random manual-… ID; a repeated gateway plus transaction ID returns the existing row with duplicate true), method_label, payer_email, note, paid_at (Y-m-d or Y-m-d H:i:s), currency (must equal the invoice's currency) and send_receipt (default true).

$result = WPHub_Invoice_Payments::record( 123, array(
	'amount'       => 50.00,
	'gateway'      => 'manual',
	'method_label' => 'Bank transfer',
	'paid_at'      => '2026-10-04',
	'note'         => 'Matched from the bank statement',
	'send_receipt' => false,
) );

Adding a gateway

Extend the abstract WPHub_Invoice_Gateway and add an instance with the wphub_invoice_gateways filter. You implement id(), label(), is_available() and create_payment( array $doc, $amount, array $urls ), which returns the hosted payment page address or a WP_Error. The pay form, amount validation and the gateway buttons then work for it. The built-in return handling and the webhook endpoint (?wphub_invoice_webhook=) only know stripe and paypal; your gateway must record the payment itself, for example with WPHub_Invoice_Payments::record() from its own callback.

Core and WooCommerce hooks the plugin uses

HookPriorityWhat the plugin does
plugins_loaded20Starts the plugin.
init1, 5, 20 and default1: the webhook endpoint. 5: the schema and capability upgrade. 20: one woocommerce_order_status_{status} hook per ticked status. Default: translations and the licence client (admin, cron and WP-CLI requests only).
template_redirect1Serves client pages (?wphub_invoice_doc=) and ends the request.
admin_menu, admin_enqueue_scripts, admin_notices, submenu_file (10, 2 args), plugin_action_links_{basename}DefaultMenus, assets on plugin screens only, queued notices, highlighting, the Settings link. submenu_file returns the list slug while a document is open.
load-{list page hook}DefaultProcesses the bulk actions of the invoice and estimate lists before any output.
admin_page_access_deniedDefault301-redirects old admin page URLs from 1.0.0 to the current ones.
add_meta_boxesDefaultThe Invoice box on WooCommerce order screens.
before_woocommerce_initDefaultDeclares compatibility with custom_order_tables (HPOS).
woocommerce_email_attachments20 (4 args)Appends the invoice PDF path to the attachments array. It adds to the array and never replaces what other plugins attached.
woocommerce_order_status_{status}20Creates the invoice for each ticked status (Auto record only).
woocommerce_order_status_changed25 and 3025: restores a cancelled or refunded invoice. 30: records a payment on an existing invoice when the order becomes paid.
woocommerce_order_status_cancelled, woocommerce_order_status_refunded, woocommerce_order_refunded, woocommerce_refund_deleted20The cancel and refund sync.
woocommerce_my_account_my_orders_actions, woocommerce_order_details_after_order_table20The customer's Invoice buttons.
shutdown5 and defaultRe-checks zero-total orders (5) and deletes temporary PDFs.
wp_mail_failedDefaultAdded only while a document or receipt email is being sent, to capture the failure reason.

The licence client adds admin_menu, admin_init, admin_notices, pre_set_site_transient_update_plugins (adds the plugin to the update transient), plugins_api (priority 20; answers the "View details" request for this plugin's slug itself), http_request_args and http_request_host_is_external (both only relax checks for a local development licence server such as .local or .test), upgrader_process_complete (clears its cache) and in_plugin_update_message-{basename}.

Admin-post and AJAX actions

All need a logged-in user. Admin-post URLs are admin-post.php?action=…. The GET actions take type (invoice or estimate), id and _wpnonce, where the nonce action is wphub_invoice_{action}_{type}_{id}.

ActionMethod and parametersWhat it does
wphub_invoice_downloadGETDownloads the PDF (logs "PDF downloaded.").
wphub_invoice_previewGETShows the PDF inline.
wphub_invoice_duplicateGETCreates a new draft copy.
wphub_invoice_deleteGETDeletes the document.
wphub_invoice_convertGET, type=estimateConverts an estimate into an invoice.
wphub_invoice_add_paymentPOST: type=invoice, id, pay_amount, pay_method (bank, cash, cheque, card, other), pay_date, pay_note, pay_receiptRecords a manual payment.
wphub_invoice_delete_paymentGET plus pidDeletes a ledger row of that invoice.
wphub_invoice_receiptGET plus pidDownloads a receipt PDF.
wphub_invoice_regen_linkGETReplaces the client token.
wphub_invoice_preview_designGET plus design (1 to 5), id=0Sample PDF of a design with sample data; the nonce is bound to the type and id 0. An unknown design gives 400 "Unknown design."
wphub_invoice_savePOST, nonce wphub_invoice_save_{type}_{id}The editor's save; submit_action is draft, save or save_pdf.
wphub_invoice_preview_formPOST, field _wphub_invoice_preview_nonce (action wphub_invoice_preview_{type}_{id})Renders the unsaved form as a PDF.
wphub_invoice_save_settingsPOST: group, settings[...], nonce wphub_invoice_settings_{group}Saves one settings tab. Needs wphub_invoice_settings.
wphub_invoice_wc_createGET: order_id, nonce wphub_invoice_wc_create_{order_id}Creates an invoice from an order (order screen button).

The AJAX actions (admin-ajax.php, logged-in only, no nopriv variants) take the field nonce (action wphub_invoice_ajax) and answer JSON with wp_send_json_success() or wp_send_json_error(). All need wphub_invoice_manage; the first four also need WooCommerce ("WooCommerce is not active." with HTTP 400 otherwise).

ActionParametersResponse
wphub_invoice_search_customersterm (two characters or more)results: up to 20 customers with id, label, fields and shipping.
wphub_invoice_search_orderstermresults: up to 15 orders (id, number, customer, date, status, total).
wphub_invoice_search_productsterm (two characters or more)results: up to 15 products or variations (id, description, sku, price).
wphub_invoice_import_orderorder_idfields, items, shipping, warnings.
wphub_invoice_send_emailtype, id, to, subject, bodymessage "Email sent." or an error message.

Front-end routes

All are on your home address and are handled by the plugin before WordPress renders a page.

RequestWhat it does
GET /?wphub_invoice_doc=TOKENThe client page.
&wphub_invoice_action=pdfDownloads the document's PDF.
&wphub_invoice_action=receipt&pid=NDownloads receipt PDF N; the payment must belong to this invoice, otherwise 404.
POST ?wphub_invoice_doc=TOKEN with wphub_invoice_action=pay|accept|declinePay (gateway, amount_mode=full|custom, amount), accept or decline. Needs the nonce field _wpnonce (action wphub_invoice_pub_TOKEN).
&wphub_invoice_return=stripe|paypal|cancelWhere the customer comes back from the gateway. Stripe also sends session_id; PayPal sends token.
POST /?wphub_invoice_webhook=stripe|paypalGateway notifications. JSON answer {"status":…,"message":…}.

Tokens must match ^[ie]_[a-f0-9]{48}$ and the prefix must match the document type. Pages from 1.0.0 that used the old parameter names keep working.

Template files

PDF templates are PHP files that receive the view model in $v and echo HTML for Dompdf. Copy a file to your theme (or child theme) to override it:

Plugin fileOverride path in the theme
templates/pdf/design-1.php (Classic), design-2.php (Modern), design-3.php (Compact), design-4.php (Minimal), design-5.php (Business)wphub-advance-invoice/pdf/design-N.php, where N is 1 to 5
templates/pdf/receipt.phpwphub-advance-invoice/pdf/receipt.php
templates/public/document.php (the client page)Not overridable.

The lookup uses locate_template(), so a child theme wins over its parent. The filter wphub_invoice_template_file can then change a design's path. Dompdf supports a limited part of CSS: the bundled templates use tables for layout, no flexbox or grid, thead { display: table-header-group } for repeated headings and page-break-inside: avoid on rows. Escape everything you print.

The view model $v contains: type, is_invoice, design, hourly, currency, status, is_paid, is_finished, status_label, labels (every printed label), palette, number, company (name, legal, logo as a data URI, address lines, phone, email, website, tax_id, reg, extra lines), customer (name, contact, address lines, email, phone, tax_id), details (label and value pairs), date, date2, columns (sku, qty, price, discount, tax), items (n, description, sku, qty, unit_price, discount, tax_rate, amount), totals (label, value, type of row, total or balance), total, headline, amount_due, notes, terms, footer and page_text. The receipt template also gets receipt (number, date, amount, method, headline, history, balance, has_balance) and extra labels. Amounts are already formatted text.

Database tables

Eight tables with your WordPress prefix (shown as wp_), created with dbDelta() in the site's default character set and collation. Money columns are decimal(20,4). The schema version is stored in wphub_invoice_db_version (1.1.0).

wp_wphub_invoice_invoices and wp_wphub_invoice_estimates

Both share these columns:

ColumnTypeNotes
idbigint unsigned, auto incrementPrimary key.
doc_numbervarchar(64)Unique.
statusvarchar(20)Default draft. Invoices: draft, sent, pending, partial, paid, overdue, refunded, cancelled. Estimates: draft, sent, accepted, declined, expired, converted, cancelled.
customer_typevarchar(20)manual or woocommerce.
wc_customer_idbigint unsignedWordPress user ID of the WooCommerce customer, or 0.
customer_name, customer_company, customer_email, customer_phonevarchar(191), 191, 191, 64Snapshot of the customer.
customer_address_1, customer_address_2, customer_city, customer_state, customer_postcode, customer_country, customer_tax_idvarchar(191), 191, 100, 100, 32, 100, 64Snapshot of the address and tax ID.
currencyvarchar(10)Currency code.
pricing_modevarchar(10)overall or hourly.
issue_datedateThe document date.
referencevarchar(191)Free-text reference.
designvarchar(10)1 to 5.
public_tokenvarchar(64)Client page token (indexed).
discount_type, discount_valuevarchar(10), decimalfixed or percent.
shipping_label, shipping_amount, shipping_tax_ratevarchar(100), decimal(20,4), decimal(10,4)Optional shipping or other charge and its tax rate.
subtotal, discount_total, tax_total, totaldecimal(20,4)Calculated by the server.
notes, footer, metalongtext, text, longtextmeta is JSON: wc_order_total, wc_order_date, wc_order_currency, imported_from and the flag wc_auto_status (cancelled or refunded, set when the WooCommerce sync changed the status).
created_by, created_at, updated_atbigint, datetime, datetimecreated_by is 0 for automatic invoices.

Invoices add wc_order_id, wc_order_number (varchar 64), amount_paid (the net of the ledger, maintained only by the payment code), source_estimate_id, due_date, po_number and payment_terms. Estimates add converted_invoice_id, valid_until and terms. Indexes: status, issue date, the second date, customer name and email, WooCommerce customer and order, the linked ID and public_token.

Items, logs, counters and payments

TableColumns
wphub_invoice_invoice_items, wphub_invoice_estimate_itemsid, invoice_id or estimate_id, sort_order, description (text), sku (191), quantity, unit_price, discount, tax_rate (decimal 10,4), tax_amount, line_total. In hourly mode quantity is hours and unit_price is the rate. line_total is the net line after its discount.
wphub_invoice_email_logid, doc_type, doc_id, recipient (191), subject (255), status (sent or failed), error_message, user_id, created_at.
wphub_invoice_historyid, doc_type, doc_id, event (40), message, user_id, created_at.
wphub_invoice_countersdoc_type (primary key: invoice, estimate, receipt), next_number.
wphub_invoice_paymentsid, invoice_id, gateway (manual, stripe, paypal, woocommerce), txn_id (191), receipt_number (64), amount, refunded, currency, status (completed), method_label (100), payer_email (191), note, paid_at, created_by, created_at. A unique key on (gateway, txn_id) makes recording idempotent.

Options, transients, locks and constants

NameKindHolds
wphub_invoice_general, _company, _invoice, _estimate, _email, _payments, _woocommerceOptions (not autoloaded)One array per settings tab. The keys are listed under Settings.
wphub_invoice_currenciesOptiondefault and items[code] with the currency rows.
wphub_invoice_version, wphub_invoice_db_versionOptionsStored plugin version (1.1.1) and schema version (1.1.0); a mismatch with either triggers the upgrade routine, which is safe to repeat. It runs once on the first admin or front-end request after updating from 1.1.0, because the plugin version changed.
wphub_invoice_legacy_doneOptionFlag that the 1.0.0 name migration ran.
_wphub-advance-invoice_licence_key, _wphub-advance-invoice_key_statusOptionsThe licence key (plain text) and its status (active).
wphub_invoice_flash_{user_id}Transient, 300 sUp to the last five admin notices for that user.
wphub_invoice_form_{user_id}_{type}_{id}Transient, 300 sThe form you typed after a failed save, shown once.
wphub_invoice_pp_{md5}TransientThe PayPal access token, for its expires_in time minus 60 seconds (at least 60, default 600).
wpxh_licence_check_v2Site transient, 12 hLicence and update information (one hour after a failed check).
wpxh_licence_msg_{user_id}Transient, 120 sThe result message shown on the licence screen.
wphub_invoice_wc_{order_id}, wphub_invoice_legacy_migrationMySQL named locksHeld while an order's invoice is created, and while the one-time rename runs.
WPHUB_INVOICE_VERSION, _DB_VERSION, _FILE, _DIR, _URLConstantsDefined by the plugin; not for you to define.
WP_TEMP_DIR, WPXH_LICENCE_SERVERConstants you may defineWordPress's temporary directory (where email PDFs are written) and the licence server address.

The plugin stores no post meta, user meta or term meta and registers no post types, taxonomies, rewrite rules or user roles. The only WooCommerce data it writes is private order notes.

Cron events

wphub_invoice_daily (recurrence daily): overdue invoices, expired estimates and temporary-file clean-up, see Automatic tasks. To run it by hand, trigger the event with a cron tool of your choice; there is no WP-CLI command of its own.

File layout

wphub-advance-invoice/
  wphub-advance-invoice.php   main file, autoloader, licence registration
  uninstall.php               uninstall routine
  readme.txt
  includes/                   document, settings, currency, numbering, PDF, email, payments,
                              gateways, public page, WooCommerce classes
  includes/admin/             menus, editor, lists, settings screen, AJAX
  templates/pdf/              design-1.php, design-2.php, design-3.php, design-4.php,
                              design-5.php, receipt.php
  templates/public/           document.php (client page)
  assets/css, assets/js       admin.css, common.js, editor.js, settings.js
  licence/                    WpExperts Hub licence and update client
  languages/                  wphub-advance-invoice.pot (no translations bundled)
  vendor/                     bundled Dompdf 3.1.6 and its libraries (Composer)

The class loader maps WPHub_Invoice_Foo_Bar to includes/class-wphub-invoice-foo-bar.php (or the same name under includes/admin/). The text domain is wphub-advance-invoice; translations are loaded from languages/ on init.

9. Privacy

What is stored and where

  • Documents (database tables wp_wphub_invoice_*): for every invoice and estimate, the customer's name, company, email, phone, address, tax or VAT number and, when chosen from WooCommerce, the WordPress user ID; the items, amounts, dates, notes and the client page token. Documents keep this snapshot until you delete them.
  • Payments: amount, currency, gateway, transaction ID, receipt number, method label, a note, the payer's email address (from the gateway, or the customer's email for manual payments), the date and the ID of the user who recorded it.
  • History and email history: event text, the IDs of the users who acted, the recipients, subject and failure reason of every email attempt. Failed gateway checkouts are logged with the gateway's message.
  • Settings (options): your company details, email templates and, in wphub_invoice_payments, the Stripe secret key, Stripe webhook signing secret, PayPal client ID, PayPal client secret and PayPal webhook ID. WordPress has no secret store, so these sit in the options table as plain text, like any other plugin setting. They are never printed into a page, a URL or an error message. The licence key is stored the same way.
  • Short-lived data: transients listed under Options, transients, locks and constants, including a PayPal access token.
  • Files: no PDF is kept. A PDF written for an email attachment exists briefly in WordPress's temporary directory, in a folder named wphub-invoice- plus random characters (see Where PDFs are written). Dompdf's font cache lives in wp-content/uploads/wphub-advance-invoice/fonts.
  • WooCommerce: private order notes (for example "Invoice INV-000042 attached to the "Processing order" email."). Nothing else is written to orders, products or customers.

Cookies

The plugin sets no cookies, on the client page or anywhere else. The client page does not load scripts or fonts from other sites; it contains a small inline script that shows or hides the custom amount field.

Data sent to other services

ToWhenWhat is sent
https://wpexpertshub.com/wp-json/wphub-licence/v1/ (the licence server)When you click Activate licence or Deactivate licence; when you ask for your key by email; and when WordPress checks for plugin updates or shows plugin details and the 12-hour cache is empty or out of date (in wp-admin, in WP-Cron and in WP-CLI requests only; front-end requests, client pages and webhooks never load the client).Activate and deactivate: the plugin slug (wphub-advance-invoice), the licence key and your site address (site_url()). Email me my key: the slug and the email address you typed. Update check: your site address and, for every WpExperts Hub plugin on the site that bundles this client, its slug, licence key and installed version. WordPress adds its usual User-Agent (the WordPress version and your site address) and the server sees your server's IP address. No customer, order, invoice or visitor data is sent.
Stripe (api.stripe.com)Only when online payments and Stripe are on: when a customer starts a payment, when the customer returns from Stripe.To create a Checkout Session: the amount and currency, a line item named "Invoice NUMBER – Your Company", the customer's email address (if valid), the invoice ID and number, your site address, a description, and the success and cancel addresses (which contain the document's private token). To verify a return: the session ID, with your secret key as credentials.
PayPal (api-m.paypal.com, or api-m.sandbox.paypal.com in sandbox mode)Only when online payments and PayPal are on: when a customer starts a payment, when the customer returns, when a webhook arrives and when an access token is needed.Your client ID and secret to get an access token; an order with the amount and currency, a reference and custom ID (the invoice ID), a description ("Invoice NUMBER – Your Company"), your company name as the brand name and the return and cancel addresses (which contain the token); the order ID to capture it; and, to verify a webhook, the webhook's headers, its body and your Webhook ID.
The recipients you chooseWhen you send a document or when receipts and notifications are emailed.The PDF and message, through your site's mail setup (wp_mail()).

Stripe and PayPal send webhook notifications to your site. They contain payment details. The plugin stores the payer's email address from Stripe's session or webhook and from PayPal's capture response when the customer returns; it does not store one from a PayPal webhook. Customers enter card details only on the gateways' own pages. Dompdf is configured not to fetch remote files, so a PDF never triggers a request to another site.

Retention, export and erasure

  • Nothing is deleted automatically except temporary PDF folders older than 24 hours and expiring transients. Documents, payments, history and email history stay until you delete the document; deleting a document removes its items, history, email history and (invoices) payments, but not WooCommerce order notes. Check your own record-keeping duties before deleting invoices.
  • The plugin registers no personal-data exporter or eraser and adds no text to the privacy policy guide, so WordPress's Export Personal Data and Erase Personal Data tools do not include data held in its tables. Use the list search (by name, company, email or reference) to find a person's documents.

Deactivation and uninstall

  • Deactivating removes only the daily cron event. All data, settings and capabilities stay.
  • Deleting the plugin always removes the cron event and both capabilities from every role. It deletes everything else only when Delete all plugin data when the plugin is deleted and its confirmation box were both ticked under Settings → General (both are off by default). In that case it drops the eight tables; deletes the options wphub_invoice_general, _company, _invoice, _estimate, _email, _payments, _woocommerce, _currencies, _db_version and _version; deletes every transient starting wphub_invoice_; removes anything still stored under the names used by 1.0.0; and deletes the uploads/wphub-advance-invoice folder (the font cache). On multisite it does this for every site.
  • Left behind even then: the licence options _wphub-advance-invoice_licence_key and _wphub-advance-invoice_key_status, the licence cache wpxh_licence_check_v2, the option wphub_invoice_legacy_done, the private notes on WooCommerce orders and any stale temporary PDF folders in the temporary directory.

10. Troubleshooting

ProblemLikely cause and fix
"PDF generation is unavailable." appears on plugin screens, or a download fails with the same engine message ("The PDF could not be generated: …" when the library is present but rendering itself fails).PHP lacks the dom or mbstring extension, or the bundled vendor/ folder was removed. Open Invoices → Settings → General: the PDF engine row says which extensions are missing or that the Dompdf library is missing. Ask your host to enable the extensions, or reinstall the plugin. If the row says Dompdf was "already loaded by another plugin", that copy is the one in use; if it is old, generation can fail until that plugin updates it.
The logo does not print on the PDF.Only JPEG, PNG and GIF files are embedded. A WebP, SVG or other image prints nothing, and so does a file that is missing or unreadable. Choose another image under Settings → Company → Logo. The Settings screen also advises the PHP gd extension for logos.
Chinese, Japanese, Korean or right-to-left text prints as empty boxes or in the wrong order.The bundled font, DejaVu Sans, has no CJK glyphs and the templates set no right-to-left direction. The PDF is still created and amounts print. The designs can be changed with template overrides, see Template files.
There is no Estimates menu.Estimates are off by default. Tick Enable estimates (quotes) under Settings → General. An estimate URL opened while they are off gives "Sorry, you are not allowed to access this page."
A shop manager sees Invoices but not Settings.Only Administrators receive wphub_invoice_settings. Shop managers receive wphub_invoice_manage. See Who can do what. A role created after the plugin was activated needs the plugin to be deactivated and activated again to receive the capabilities.
The number field in the editor is read-only ("Numbers are assigned automatically.").Tick Editable invoice numbers (or Editable estimate numbers) on the Invoice (or Estimate) tab. Numbers you type must be unused: "That number is already used by another document." The format allowed is letters, digits, spaces and - _ / # .: "The number may only contain letters, digits and - _ / # . and spaces."
There is a gap in the invoice numbers.A number is claimed before the document is written, so a save that fails afterwards leaves a gap. Deleting a document does not give its number back. The starting-number setting raises the counter but never lowers it.
Saving shows "Enter a customer name or company.", "Add at least one item." or another message and the form returns with your values.These are validation messages. Your input is kept for five minutes. Others you may meet: "The customer email address is not valid.", "The due date cannot be earlier than the invoice date.", "Item N needs a description.", "The quantity of item N cannot be negative.", "A document can have at most 200 items." and "Enter a valid number for "field"." (amounts use a full stop, no thousands separators).
A very long invoice loses rows when saved.Each row posts six fields and PHP stops reading form fields after max_input_vars (default 1000), which can happen before the 200-row limit. Raise max_input_vars in your PHP configuration.
The editor says "Your session has expired. Reload the page and try again." when searching or sending.The AJAX nonce has expired because the page was left open for a long time. Reload the page. Unsaved typing is lost, so save first when you can.
The customer, order or product search shows nothing.WooCommerce must be active; without it the search boxes are not shown. Customers and products need two characters, orders one. Product search lists only published and private products; variable parents are skipped in favour of their variations.
The email fails ("WordPress could not send the email.") or the customer never receives it.The plugin uses wp_mail(). Open the Email history card of the document: a failed attempt shows the reason from WordPress. Check your SMTP plugin and the From address on the Email tab. If the From email is not a valid address, no From header is added and WordPress's default sender is used.
The client link shows "This page is not available".One of: Client pages is off; the document is still a Draft; the link was regenerated; the link was copied incompletely; or it is an estimate and estimates are off. Open the document and use the link in its Client page card.
There is no Pay online card on the client page.All of these must be true: client pages are on; Online payments is ticked; at least one gateway is enabled and configured (for Stripe a secret key matching sk_test_, sk_live_ or rk_; for PayPal a client ID and a secret); the invoice is not Draft, Paid, Cancelled or Refunded; a balance remains; and the document is an invoice. The TEST/LIVE or SANDBOX badge next to the gateway heading shows whether the key is accepted.
A customer clicking Pay sees "Please choose a payment method." or "The payment could not be started. Please try again in a moment or contact us."The first means no gateway was available when the form was posted. The second means Stripe or PayPal refused the request: open the invoice's History and read the "checkout could not be started" line, which holds the gateway's message (wrong key, an amount below the gateway's minimum, a currency your account cannot take, a failed connection).
The customer paid but the invoice was not updated.The return to your site records the payment; the webhook is the backup for customers who close the tab. Check that you added the webhook URL from the Payments tab in the gateway's dashboard with the events listed there, and that the signing secret (Stripe) or Webhook ID (PayPal) is saved. The customer may see "We could not confirm your payment yet. If you were charged, the payment will appear here shortly and you will receive a receipt." when the plugin could not verify the payment on return. Look at the gateway's webhook delivery log: the answer text tells you why ("Invalid signature", "Unknown payment", "Not paid yet"). Your site must accept POST requests at /?wphub_invoice_webhook=stripe or =paypal; a firewall, maintenance mode or a page cache that blocks them breaks delivery.
Stripe webhooks answer "Invalid signature".The saved signing secret does not belong to that endpoint (test and live endpoints have different secrets), the secret is empty, the request body was altered by a proxy, or the server clock is more than five minutes off.
The invoice History says "PayPal rejected the API credentials."The client ID and secret are wrong, or they belong to the other mode: sandbox credentials only work with Sandbox mode ticked, live credentials only with it unticked. Sandbox mode is on by default.
PayPal webhooks are rejected.The Webhook ID is empty or belongs to the other mode (sandbox versus live), or the sandbox setting does not match the credentials.
An invoice stays Sent after the customer paid by bank transfer.The plugin cannot see bank transfers. Open the invoice and use Payments → Record a payment. Choosing the status Paid by hand changes the label only and creates no payment.
An invoice marked Paid by hand shows the full total in {balance_due}, or went back to Sent or Partially Paid after an edit.The balance and the status are calculated from the ledger. A hand-set Paid has no ledger payment. Editing an invoice that has payments (a changed total, a deleted payment) re-checks the status against the ledger unless you changed the status in that same save. Record the payment instead of setting the status.
The receipt email was not sent.Receipts are sent only when Email receipts is on (and, for a manual payment, the box in the form is ticked), when a valid email address is known, and never for payments recorded from a WooCommerce order. The email history shows failed attempts; nothing is logged when there is no address.
No "Payment received" email reached me.That email is sent only for Stripe and PayPal payments, when Notify me is on, to the company email (or the site admin email if that is invalid).
The invoice was not marked Overdue.The daily job runs through WP-Cron, so it needs WordPress cron to run. It only touches Sent, Pending Payment and Partially Paid invoices that have a due date earlier than today, and only when Automatic status updates is on. Drafts and Paid invoices are never changed.
The WooCommerce tab is missing.WooCommerce is not active. The tab, the order import, the product search and the Invoice box need it.
An order did not get an invoice.Check, in order: Auto record orders is on and the status is ticked (an invoice is created when the order reaches the status); the order has a total above zero or Free orders is off; the order does not already have a non-cancelled invoice; it is not a subscription order while Subscriptions is off; no code vetoes it with wphub_invoice_wc_record_invoice. The order notes ("Invoice … created from this order." or the reason) and WooCommerce → Status → Logs (source wphub-advance-invoice) show what happened.
An order email has no invoice attached, and the order note says "The invoice PDF could not be attached to the email: …".The note gives the reason, for example "PDF generation needs the PHP extension(s): dom, mbstring. Ask your host to enable them." The email is sent regardless. The email must also be ticked on the WooCommerce tab, the order must have a total above zero (with Free orders on), and it must be a customer email (admin emails never get the PDF).
Customers receive two PDFs with their order email.Another invoicing plugin attaches its own PDF to the same emails. Untick the email in one of the two plugins.
The invoice total differs by a cent from the WooCommerce order, or a warning appears when importing an order.Tax rates are derived from each line's tax amounts and rounded, so rounding can differ. The import warning says by how much. Review the amounts before saving.
The "Download invoice" button is not shown to customers.Needs Customer download on, Client pages on, and an invoice for the order that is neither a Draft nor Cancelled.
The dates print in the wrong format.Choose another Date format under Settings → General. It applies to PDFs, the client page, emails and the lists.
A currency cannot be chosen, or "The selected currency is not enabled." appears.Enable it under Settings → Currency. At least one currency must stay enabled and the default must be one of them.
The licence key does not activate.The message from the licence server is shown on the licence screen. Check that you pasted the whole key, that the licence is not already in use on the number of sites your plan allows (deactivate it on the old site first), and that your server can reach wpexpertshub.com. Use "Can't find your key? Email it to me" if you lost it.
No update appears although a new version exists.The licence must be active for an update package to be offered. Update information is cached for 12 hours (and for one hour after a failed check); activating or deactivating a licence clears the cache.

11. FAQ

Does it need WooCommerce?

No. Without WooCommerce you type customers in by hand and everything else works the same. WooCommerce adds customer, order and product search, importing an order, the invoice PDF on order emails, automatic invoices and the Settings → WooCommerce tab.

Does it change my WooCommerce orders?

No. Orders and products are only read, and an invoice keeps its own copy of the data. The only thing written to an order is an optional private order note, which you can switch off under Settings → WooCommerce → Order notes.

Are estimates included?

Yes, but they are off by default. Switch them on under Invoices → Settings → General → Estimates. Switching them off again hides them without deleting anything.

Which payment methods can customers use?

Stripe Checkout and PayPal. The cards and wallets offered depend on what is enabled in your Stripe and PayPal accounts; the plugin does not choose them. Bank transfer, cash, cheque and offline card payments are recorded by hand. Run a test payment in Stripe test mode or the PayPal sandbox before you go live.

Is card data safe?

Customers pay on Stripe's or PayPal's own page. The plugin has no card fields and never sees card details. It stores only the keys and secrets you enter, in the options table.

Can a customer pay part of an invoice?

Yes, when Partial payments is on. You set the smallest amount; the last payment may be smaller. The invoice becomes Partially Paid and the balance shows on the client page and the PDF.

Are invoices marked Paid automatically?

Yes, when a Stripe or PayPal payment, a payment you record, or (with the setting on) a paid WooCommerce order covers the total. Choosing the status Paid by hand changes the status only and creates no payment.

Does it send overdue reminders or create recurring invoices?

No. The daily job changes unpaid invoices past their due date to Overdue and sends no email. There are no recurring invoices, late fees or credit notes.

Does it calculate tax or convert currencies?

No. Tax is a percentage you enter per item and for shipping, with a label such as VAT or GST. Currencies change the symbol and format only; no amount is converted. Prices are net: tax is added on top.

Can I show more than one tax rate?

Yes. Each item has its own rate and the PDF lists one tax line per rate, for example "VAT (20%)" and "VAT (5%)".

Does the PDF support the rupee sign and other scripts?

Yes for the rupee, euro, pound and yen signs and for Latin, Greek and Cyrillic text in the bundled DejaVu Sans font. Chinese, Japanese and Korean text is not supported, and right-to-left text is not set up.

Can the invoice be attached to WooCommerce emails?

Yes. Under Settings → WooCommerce tick Auto attach, choose a design and tick Order on-hold, Processing order, Completed order and/or Customer invoice / order details. Admin emails never get the PDF, and an order keeps one invoice number.

What happens to the invoice when an order is cancelled or refunded?

With Auto record orders on, the invoice follows the order. A cancelled order cancels its invoice (and it comes back with the order), a refund adds a negative line so the total is what the customer paid, and a fully refunded order marks the invoice Refunded.

Does it work with WooCommerce Subscriptions?

Subscription orders are normal orders, so the first order and every renewal can get their own invoice. The subscription itself is not invoiced. This part has not been tested against the real Subscriptions plugin; test one renewal first.

Where are the PDFs stored, and who can open them?

Nowhere. PDFs are made when asked for. In the admin only users with the manage capability can open them; on the client page, anyone who holds the document's private link can. An email attachment exists for a moment in the temporary directory. See Where PDFs are written and who can reach them.

Who can open the invoice link?

Anyone who has the link, so send it only to your customer. The token is long and random, pages are not indexed by search engines, drafts are never shown, and Regenerate link replaces it with one click.

Can I change the PDF layout or the paper size?

Yes. Copy a design file into your theme under wphub-advance-invoice/pdf/ to change it, or use the filter wphub_invoice_paper_size for another paper size. See Template files.

Can I restart the numbering each year?

Not from the settings. You can change the prefix (for example INV-2027-, which allows letters, digits and - _ / # .), but the counter keeps counting; raising the starting number lifts it and lowering it never does.

Can I export invoices or send many at once?

There is no export, no bulk email and no bulk "create invoices for existing orders" button. The list offers bulk delete and bulk status changes. A single order can get an invoice from its order screen.

Why does the Outstanding tile not match the balances I am owed?

The tiles add up each document's total. A Partially Paid invoice counts at its full total, not at its balance.

What happens if I uninstall it?

Deactivating deletes nothing. Deleting the plugin keeps your invoices and settings unless you ticked Delete all plugin data when the plugin is deleted and its confirmation box first. Capabilities and the cron event are always removed. See Deactivation and uninstall.

Does it work without a licence?

Yes. It works as soon as it is activated. The licence key only unlocks update packages; without it the plugin offers no update and says so.

Does it contact other servers?

Only wpexpertshub.com for the licence and updates (the plugin slug, version, your licence key and your site address, never customer or order data) and, if you switch on online payments, Stripe and PayPal. See Data sent to other services.

How do I get updates?

Activate your licence under Plugins → WpExperts Hub Licences, then update from the WordPress updates screen. You can also download the new zip from My Account → Downloads and upload it under Plugins → Add New → Upload Plugin with Replace current with uploaded. Your settings and data are kept.

Does it work with the free PDF Invoice for WooCommerce plugin?

They can be active together. Both can attach a PDF to the same order emails, so tick each email in only one of them. If the free plugin has loaded Dompdf first, this plugin reuses that copy. See the free plugin's documentation.

12. Changelog

1.1.1

  • Fixed: the invoice editor passed the type "estimate" to its script (a page-name check left over from the 1.1.0 rename never matched), so Send Invoice, the list's Send email action and the automatic PDF download after Save & Generate PDF did nothing, and when an estimate with the same number existed the Send dialog was filled with that estimate and mailed it. Invoices and estimates now each load their own document.

1.1.0

  • Changed: every internal name now uses the plugin's real name instead of a two-letter abbreviation: admin page URLs (wphub-invoices, wphub-estimates, wphub-invoice-settings), database tables (wp_wphub_invoice_*), options, hooks and filters (wphub_invoice_*), classes and CSS classes. Existing data is moved automatically on update, old admin URLs redirect, and client links and webhook URLs that were already sent keep working
  • New: Settings → General → Estimates switch (off by default): the Estimates menu, settings tab, emails, client-page Accept / Decline and the daily expiry job only exist while it is on; nothing is deleted when it is off
  • Fixed: previewing the unsaved form of an existing estimate no longer logs a PHP warning
  • New: Settings → WooCommerce tab: auto attach the invoice PDF to WooCommerce order emails (choose the design and the emails), auto record WooCommerce orders as invoices at the order statuses you pick (paid orders show as Paid, free orders can be skipped), an Invoice box with a Create invoice button on every order, an optional Download invoice button in My Account, and switchable private order notes
  • New: with Auto record orders on, a cancelled order cancels its invoice (restored with the order), refunds add a credit line and keep a paid invoice Paid, a fully refunded order marks the invoice Refunded (new status), and WooCommerce Subscriptions orders are invoiced with the subscription named on them
  • Changed: nothing is shown as due on a cancelled or refunded invoice (PDF, client page, order box, list)
  • Fixed: notices queued by an action that lands on a non-plugin screen (the order screen) are shown there instead of waiting for the next invoice page
  • New: private client page for every invoice and estimate (view, download PDF, pay, accept or decline)
  • New: Stripe and PayPal payments with signed webhooks
  • New: payment ledger, partial payments, Partially Paid status and balance due on pages, PDFs and the invoice list
  • New: PDF receipts, emailed and downloadable
  • New: Payments settings tab, email variables {view_link}, {pay_link}, {balance_due}, {amount_paid}, receipt email template
  • New: "Preview" on every design card in Settings → General opens a sample PDF without creating a document
  • Fixed: table headings such as "Unit price" no longer break onto two lines in any PDF design
  • Fixed: the invoice / estimate details block in the Classic, Modern and Compact designs is compact instead of pushing labels and values to opposite edges
  • Improved: Company logo field with preview, empty state and proper Choose / Change / Remove buttons; roomier settings forms; email variables grouped by email type; consistent list alignment and icon alignment in buttons
  • Changed: editing the total of an invoice with payments keeps its status in line with the payments

1.0.0

  • First release.

13. Support

Email support@wpexpertshub.com. To get a quick answer, include:

  • Your order ID (or the email address used for the purchase).
  • The plugin version (1.1.0 for this page), your WordPress version and your PHP version. For WooCommerce questions, the WooCommerce version too.
  • What you did, what you expected and what happened, with the exact message you saw. For payment problems, the invoice's History lines and the gateway's webhook delivery result are the most useful; for email problems, the document's Email history; for order problems, the order notes and the wphub-advance-invoice entries in the WooCommerce logs.
  • What you already tried. Never send your Stripe or PayPal secret keys or your licence key in an email.