WooCommerce operations guide

WooCommerce Store Credit Ledger Audit: Trace Refunds, Balance Changes, and Checkout Performance

A practical WooCommerce store-credit audit for finding balance resets, missing refund actions, duplicate adjustments, and repeated cart queries before customers lose trust.

Last reviewed July 13, 202615-minute auditEvidence-bound

Direct answer

Reconstruct a customer's balance from append-only credit entries, then compare that projection with the displayed balance and the orders that consumed credit. A reliable workflow records issuance, redemption, refund, reversal, expiration, and manual adjustment as separate idempotent entries; it never treats one mutable balance field as the only source of truth.

Scope boundary

This guide documents an audit method. It does not mean a StoreFixKit plugin is available. No feature or delivery date is promised.

Observed failure signals

These three reports shaped this audit. Each report was reviewed for direct relevance and preserved with a content hash. They identify failure modes worth testing; they do not prove how often the problem occurs across WooCommerce stores.

Store Credits triggers repeated balance queries on Cart page

Store-credit balance calculations ran repeatedly while loading the cart even when the customer was not using credit.

Open the reviewed WordPress.org report

Refund to Store Credits is not working

The option to refund an order into store credit disappeared even though the plugin and related software were current.

Open the reviewed WordPress.org report

The traceable credit path

01 Create

Issue credit from a named business event

02 Record

Append an idempotent ledger entry

03 Project

Calculate the current available balance

04 Redeem

Link usage to cart and final order

05 Reverse

Restore or expire through a new entry

The displayed balance is a projection. The durable evidence is the ordered set of entries and their links to orders, refunds, and operator actions.

Reconstruct one customer's balance before changing anything

Choose a staging account or a customer case with permission. Export the visible balance, all credit transactions, related orders, refunds, coupon or account-fund records, order notes, imports, scheduled jobs, and relevant timestamps. Do not “correct” the balance first. The difference between the current projection and the expected projection is the evidence that locates the broken event.

  1. Define the credit instrument.Document whether the store uses a reusable coupon, account funds, a custom balance, or another extension. These models have different redemption and restoration behavior.
  2. List every balance event.Include issuance, purchase, manual adjustment, redemption, partial redemption, refund, cancellation, failed order, expiration, import, reversal, and migration.
  3. Normalize money.Use the order currency and integer minor units or WooCommerce decimal helpers. Never compare binary floating-point totals as the accounting record.
  4. Link entries to business objects.Each event should reference an order, refund, coupon, import batch, or operator action. A reason stored only in a support conversation is not an audit trail.
  5. Project the balance in order.Sort by durable sequence and time, apply signed entries once, and compare the result with the displayed balance.
  6. Test the same event twice.Webhook retries, double clicks, and worker restarts should resolve to the original entry through an idempotency key.
Read-only WooCommerce checks
export AUDIT_EMAIL='your-controlled-test-address'
wp wc order list --customer=42 --fields=id,status,total,date_created
wp wc order refund list 123 --fields=id,amount,reason,date_created
wp wc coupon list --search="$AUDIT_EMAIL" --fields=id,code,amount,date_expires

Store-credit extensions use different data models, so these commands establish related WooCommerce objects rather than a universal credit ledger. Back up the database before testing any adjustment or refund.

Use append-only entries and a derived balance

A single mutable balance is easy to display but weak as evidence. It cannot show whether a refund was applied twice, why an old amount returned, who adjusted the account, or which order consumed the credit. An append-only ledger answers those questions by preserving each signed movement and its source.

Entry fieldPurposePass condition
Entry IDStable reference for support and exportUnique, immutable, and never reused.
Customer or owner IDIdentifies the account whose balance movesResolved through a supported customer model.
Signed amount and currencyExpresses credit or debit preciselyNormalized to the order currency and fixed precision.
Event typeDistinguishes issuance, redemption, refund, reversal, expiration, and adjustmentFinite enum with documented accounting behavior.
Source type and source IDLinks the entry to order, refund, coupon, import, or operator eventSupport can navigate to the originating record.
Idempotency keyStops duplicate processing of one logical eventA unique constraint returns the original entry on retry.
Actor and timestampExplains when and how the change happenedAutomation, administrator, or import source is explicit.
Reversal linkCorrects an entry without deleting historyThe original remains visible and the reversing entry nets it out.
Balance projection pseudocode
entries = load_entries(customer_id, currency, ordered=True)
balance = 0

for entry in entries:
    assert unique(entry.idempotency_key)
    assert source_exists(entry.source_type, entry.source_id)
    balance += entry.amount_minor

assert balance == displayed_balance_minor

Do not delete or edit an old entry to “make the total right.” Add a reversal or correction linked to the original. If a legal retention or privacy request requires data erasure, define how personal identifiers are removed while financial and order records retain the minimum required business evidence. The official Store Credit documentation describes export and erasure behavior for its coupon-based model; another extension may differ.

Imports need the same discipline. Assign one import batch ID and one external source key per entry. An import replay should return the existing rows, not recreate last year's credits. Record a checksum or version for the source file so support can prove which input produced the balance.

Map refund-to-credit as a separate commercial action

A WooCommerce refund can return money through a compatible payment gateway or be recorded manually. Store credit is a different outcome: the customer receives future purchasing value rather than money back to the original method. The interface and audit log must make that distinction explicit.

ActionMoney movementCredit movementEvidence required
Automatic gateway refundGateway sends funds backNone unless separately authorizedWooCommerce refund, gateway reference, order note.
Manual refundRecorded in WooCommerce; operator returns funds elsewhereNone unless separately authorizedManual refund record and external transfer evidence.
Refund to store creditNo gateway refund for that amountPositive credit entryRefund decision, customer communication, ledger entry, order note.
Order cancelled after credit redemptionDepends on other payment methodsRestore the eligible redeemed amountReversal linked to the original redemption.
Partial order editRecalculate only the changed amountRestore or consume the exact differenceBefore and after order totals and linked adjustment.

When the refund-to-credit control disappears, check the extension is active, the order and refund are eligible, the administrator has the required capability, the expected order screen is in use, and the credit instrument is configured. High-Performance Order Storage and the newer order screen can expose integrations that relied on legacy post hooks. Capture PHP errors, WooCommerce logs, screen options, and extension compatibility declarations before reinstalling or editing the database.

  • Prevent double compensation.The same refund amount must not produce both a gateway refund and store credit unless an administrator explicitly performs two separate actions.
  • Bind the credit entry to the refund.Use the WooCommerce refund ID or a stable business event ID as part of the idempotency key.
  • Handle partial refunds.Use the final approved refund amount, taxes, and shipping policy rather than assuming the full order total.
  • Record restocking separately.Restoring product inventory and restoring customer credit are different effects, even when one refund initiates both.
  • Communicate the outcome.The customer should see whether value returned to the payment method or became store credit, including expiry and restrictions.

Keep balance calculation off unrelated cart paths

One reviewed support report traced repeated balance-sum queries to a store-credit module during a cart request even when the shopper was not using credit. The exact implementation belongs to that reported setup, but the audit principle is general: expensive balance work should run only when the current request needs it, and one request should not recompute the same balance repeatedly.

Request conditionExpected workFailure signalPass condition
Guest cartNo customer balance lookupLedger aggregation for user ID zero or unknownZero store-credit queries unless guest credit is an explicit feature.
Logged-in cart, credit UI hiddenAt most a lightweight eligibility checkFull history sum and account-menu renderingNo unbounded ledger scan.
Credit UI displayedOne current balance read per request contextSame aggregate query repeated by multiple componentsA request-scoped value is reused.
Credit appliedValidate available amount and calculate totalsBalance recomputed recursively during total calculationBounded calls with deterministic totals.
Balance event committedInvalidate the relevant projection or cacheGlobal cache purge or stale customer valueOnly the affected customer and currency refresh.

Use Query Monitor or a staging profiler to group duplicate queries by caller. Measure query count and duration for guest cart, logged-in cart, credit displayed, credit applied, checkout, and My Account. A cache can help, but it must be invalidated on issuance, redemption, refund, reversal, expiration, import, and manual adjustment. A stale financial balance is worse than a modestly slower correct one.

Request-scoped projection pseudocode
if not customer_is_logged_in():
    return no_credit_ui

if request.balance_already_loaded:
    return request.balance

balance = load_projected_balance(customer_id, currency)
request.balance = balance
return balance

For long histories, maintain a projection with a checkpoint entry rather than summing an unbounded table on every cart. Rebuild the projection from immutable entries in a background integrity job and compare it with the live projection. Alert on a mismatch; do not automatically “fix” financial data without retaining the discrepancy and review outcome.

Pass conditions for a production store-credit workflow

ControlTestPass condition
Ledger reconstructionRebuild a balance from all entries for one customer and currency.The result equals the displayed balance and every entry has a valid source.
Duplicate eventSubmit the same issuance or refund event twice.One ledger entry exists and the second call returns the original result.
Refund pathRun automatic refund, manual refund, and refund-to-credit cases separately.Each produces only the documented money and credit movements.
Cancellation restoreCancel an order that consumed part of the credit.The eligible amount is restored once through a linked reversal.
Import replayRun the same historical credit import twice.No duplicate entries or balance increase occurs.
Cart query budgetProfile guest, logged-in, visible-credit, and applied-credit carts.Balance work runs only where required and is reused within the request.
Privacy exportRequest the customer's data export and documented erasure path.The customer-facing credit data is included or explicitly documented, with required records handled lawfully.

Repeat the suite after WooCommerce order-storage changes, refund-extension updates, coupon changes, imports, subscription integration changes, and migrations. A trustworthy system can answer: what created this credit, what consumed it, what restored it, which customer saw it, and why the displayed amount is correct right now.

FAQ

Why is one balance field not enough for store credit?

A mutable total cannot explain how it changed, prevent duplicate event processing, or support a reliable reversal. A ledger preserves the sequence and source of every balance movement.

How should a refund to store credit be recorded?

Create a credit entry linked to the refund and order, assign a unique idempotency key, record the operator or automation source, and add an order note. Do not silently replace the gateway refund path.

What can cause old store-credit amounts to reappear?

Possible causes include stale caches, imports that overwrite the current value, duplicated scheduled jobs, restored backups, or code that recalculates from an incomplete period. Compare ledger entries, job logs, imports, and database restore times.

How do you keep store credit from slowing every cart request?

Skip balance work for guests and flows that do not need credit, load only the active customer's entries, calculate once per request, cache carefully, and invalidate on a balance event rather than recomputing from an unbounded history repeatedly.

Is this guide announcing a StoreFixKit store-credit plugin?

No. The candidate remains in market validation. The linked research form records merchant evidence and price intent without collecting payment.

Sources and review boundary

Technical recommendations rely on official WooCommerce, WordPress, or Action Scheduler documentation. Public support reports are used only as observed pain signals. StoreFixKit has not converted those reports into frequency, revenue, or adoption claims.

Official technical references

Observed merchant reports

Merchant research

Does this failure happen in your store?

Share one concrete example, your current workaround, monthly order range, and acceptable annual price. The response is reviewed as market evidence; it does not create a purchase or reservation.