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.
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 reportRefund 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 reportStore Credit Amounts Resetting
Updated store-credit balances for some customers reset to their prior-year values across multiple sites.
Open the reviewed WordPress.org reportThe traceable credit path
Issue credit from a named business event
Append an idempotent ledger entry
Calculate the current available balance
Link usage to cart and final order
Restore or expire through a new entry
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.
- 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.
- List every balance event.Include issuance, purchase, manual adjustment, redemption, partial redemption, refund, cancellation, failed order, expiration, import, reversal, and migration.
- Normalize money.Use the order currency and integer minor units or WooCommerce decimal helpers. Never compare binary floating-point totals as the accounting record.
- 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.
- Project the balance in order.Sort by durable sequence and time, apply signed entries once, and compare the result with the displayed balance.
- Test the same event twice.Webhook retries, double clicks, and worker restarts should resolve to the original entry through an idempotency key.
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 field | Purpose | Pass condition |
|---|---|---|
| Entry ID | Stable reference for support and export | Unique, immutable, and never reused. |
| Customer or owner ID | Identifies the account whose balance moves | Resolved through a supported customer model. |
| Signed amount and currency | Expresses credit or debit precisely | Normalized to the order currency and fixed precision. |
| Event type | Distinguishes issuance, redemption, refund, reversal, expiration, and adjustment | Finite enum with documented accounting behavior. |
| Source type and source ID | Links the entry to order, refund, coupon, import, or operator event | Support can navigate to the originating record. |
| Idempotency key | Stops duplicate processing of one logical event | A unique constraint returns the original entry on retry. |
| Actor and timestamp | Explains when and how the change happened | Automation, administrator, or import source is explicit. |
| Reversal link | Corrects an entry without deleting history | The original remains visible and the reversing entry nets it out. |
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.
| Action | Money movement | Credit movement | Evidence required |
|---|---|---|---|
| Automatic gateway refund | Gateway sends funds back | None unless separately authorized | WooCommerce refund, gateway reference, order note. |
| Manual refund | Recorded in WooCommerce; operator returns funds elsewhere | None unless separately authorized | Manual refund record and external transfer evidence. |
| Refund to store credit | No gateway refund for that amount | Positive credit entry | Refund decision, customer communication, ledger entry, order note. |
| Order cancelled after credit redemption | Depends on other payment methods | Restore the eligible redeemed amount | Reversal linked to the original redemption. |
| Partial order edit | Recalculate only the changed amount | Restore or consume the exact difference | Before 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 condition | Expected work | Failure signal | Pass condition |
|---|---|---|---|
| Guest cart | No customer balance lookup | Ledger aggregation for user ID zero or unknown | Zero store-credit queries unless guest credit is an explicit feature. |
| Logged-in cart, credit UI hidden | At most a lightweight eligibility check | Full history sum and account-menu rendering | No unbounded ledger scan. |
| Credit UI displayed | One current balance read per request context | Same aggregate query repeated by multiple components | A request-scoped value is reused. |
| Credit applied | Validate available amount and calculate totals | Balance recomputed recursively during total calculation | Bounded calls with deterministic totals. |
| Balance event committed | Invalidate the relevant projection or cache | Global cache purge or stale customer value | Only 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.
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
| Control | Test | Pass condition |
|---|---|---|
| Ledger reconstruction | Rebuild a balance from all entries for one customer and currency. | The result equals the displayed balance and every entry has a valid source. |
| Duplicate event | Submit the same issuance or refund event twice. | One ledger entry exists and the second call returns the original result. |
| Refund path | Run automatic refund, manual refund, and refund-to-credit cases separately. | Each produces only the documented money and credit movements. |
| Cancellation restore | Cancel an order that consumed part of the credit. | The eligible amount is restored once through a linked reversal. |
| Import replay | Run the same historical credit import twice. | No duplicate entries or balance increase occurs. |
| Cart query budget | Profile guest, logged-in, visible-credit, and applied-credit carts. | Balance work runs only where required and is reused within the request. |
| Privacy export | Request 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
- WooCommerce: Refunding OrdersDistinguish automatic gateway refunds, manual refunds, partial refunds, order notes, and restocking.
- WooCommerce: Store CreditReview coupon application, restoration, order recalculation, privacy, and customer account behavior.
- WooCommerce: Store Credit v2Confirm how credit is restored after cancellation, failure, refund, or order edits.
- WooCommerce developer docs: CRUD objectsUse supported order, coupon, and customer data objects rather than direct metadata mutation.
- WooCommerce: How to issue store credit for a refundMap the operator and customer steps required for a refund-to-credit workflow.
Observed merchant reports
- Store Credits triggers repeated balance queries on Cart pageOne public report; it does not establish frequency or market size.
- Refund to Store Credits is not workingOne public report; it does not establish frequency or market size.
- Store Credit Amounts ResettingOne public report; it does not establish frequency or market size.
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.