WP Simple Wallet self-updates
Wallet balance for WooCommerce customers. Per-user activation, admin adjustments, transaction history, and a 'Pay with wallet' gateway. HPOS compatible.
by IDEAA Lab · github.com/martinandersen84/wp-simple-wallet · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/martinandersen84/wp-simple-wallet/archive/refs/heads/main.zipShips its own WordPress updater (Plugin Update Checker), so new versions show up under Dashboard → Updates.
Wallet balance for WooCommerce customers. Per-user activation, admin adjustments, transaction history, and checkout wallet balance. HPOS compatible.
| Slug | wp-simple-wallet |
| Version | 1.5.2 |
| Author | IDEAA Lab |
| Requires WP | 6.0+ |
| Requires PHP | 7.4+ |
| WC tested up to | 9.4 |
| Text domain | wp-simple-wallet |
| License | GPL-2.0-or-later |
Features
- Per-customer wallet balance in the store currency.
- Wallet is off by default. Enable it per user via either:
- the dedicated "Wallet Customer" role created by the plugin, or
- a checkbox on the user's profile in
wp-admin.
- Negative balance support (configurable: disabled, unlimited, or capped at a maximum amount).
- Admin tools under WooCommerce → Wallets:
- List users with wallet enabled and their current balance.
- Adjust balance manually (credit or debit) with a free-form note.
- Browse the full transaction history and export it to CSV.
- Frontend Wallet tab in My Account showing the customer's balance and movements (only visible if the wallet is active).
- Wallet balance at checkout (gift-card style box):
- Appears above payment methods when the customer has wallet balance (or overdraft is allowed).
- Checkbox applies the full available balance as a discount. If it covers the entire order, no other payment method is needed.
- If the balance only covers part of the order, the remaining total is charged to whatever payment method the customer selects (Stripe, PayPal, etc.).
- Order cancellations automatically restore the wallet amount. Refunds on full-wallet orders are credited back automatically.
- Respects the per-user overdraft policy: customers with allowed negative balance can apply more than their current balance.
- HPOS (Custom Order Tables) compatible.
- OOP, internationalized (
.potprovided), no data deleted on deactivation. Cleanup on uninstall is opt-in.
Installation
- Clone or upload the plugin folder into
wp-content/plugins/wp-simple-wallet:cd wp-content/plugins git clone https://github.com/ideaalab/wp-simple-wallet.gitOr download a release ZIP and upload it from Plugins → Add new → Upload.
- Activate WP Simple Wallet from the Plugins screen. WooCommerce must be active.
- Go to WooCommerce → Wallets → Settings to configure overdraft policy and the gateway labels.
- Enable the wallet for a user:
- assign them the Wallet Customer role, or
- edit the user profile and tick Enable wallet for this user.
Updates
The plugin self-updates from this GitHub repository via the bundled plugin-update-checker. New tagged releases (vX.Y.Z) appear in the regular WordPress updates screen.
How to test
Quick functional walkthrough on a Woo store with at least one product:
- Enable a wallet. Edit a customer in
wp-admin, scroll to WP Simple Wallet and tick Enable wallet for this user. Save. - Top up the balance. Go to WooCommerce → Wallets, click Manage on that user, add a credit of e.g. 100 with note "initial top-up".
- Verify the customer view. Log in as the customer and visit My account → Wallet. You should see the balance and the credit movement.
- Full wallet payment. Add a product to cart (price ≤ 100) and go to checkout. A Wallet balance box should appear with a checkbox. Check it; the order total should drop to zero and payment methods should disappear. Place the order; the balance should drop and a new Order payment transaction should appear.
- Split payment. Add a product that costs more than the wallet balance. Check the wallet box; the total should drop by the balance amount. Select another payment method for the remainder and place the order. Both the wallet debit and the gateway charge should succeed.
- Refund (full wallet). Open a full-wallet order in
wp-adminand issue a refund. The wallet should be credited back automatically. - Cancel. Place an order using the wallet, then change the order status to Cancelled in
wp-admin. The wallet amount should be restored automatically. - Negative balance. In Settings, enable Allow negative balance and set a Max negative balance (e.g. 50). Set the customer balance to 0. Go to checkout; the wallet box should appear. Check it; the full order total should be applied from wallet. Place an order that exceeds the cap: it should be rejected at validation.
- HPOS. Enable WooCommerce → Settings → Advanced → Features → High-Performance Order Storage and repeat steps 4–6. Orders are stored in the HPOS tables; wallet flow keeps working.
- Export. From WooCommerce → Wallets → Transactions click Export CSV.
Developer API (for other plugins)
WP Simple Wallet exposes a small procedural API so other plugins (royalty systems, top-ups, point conversions, ticketing, etc.) can move money in and out of customer wallets in one line.
All API functions are loaded when the plugin boots. Guard your calls with
function_exists() so your code keeps working if the wallet is disabled:
if ( ! function_exists( 'wsw_credit' ) ) {
return; // WP Simple Wallet is not active.
}
Functions
| Function | Returns |
|---|---|
wsw_is_active( $user_id ) |
bool |
wsw_set_active( $user_id, $active = true ) |
void |
wsw_get_balance( $user_id ) |
float |
wsw_credit( $user_id, $amount, $note = '', $args = [] ) |
int\|WP_Error (tx id) |
wsw_debit( $user_id, $amount, $note = '', $args = [] ) |
int\|WP_Error (tx id) |
wsw_can_debit( $user_id, $amount, $args = [] ) |
true\|WP_Error |
wsw_get_transactions( $args = [] ) |
array of rows |
wsw_get_settings() |
array |
$args supports:
| Key | Type | Notes |
|---|---|---|
type |
string | Custom transaction type slug (max 64 chars). Default: credit / debit. Use stable identifiers like royalty_payout, relay_topup. |
source |
string | Slug of the calling plugin, e.g. wp-royalties. Stored in a dedicated column and shown in the admin transactions table. |
order_id |
int | Related WooCommerce order, if any. |
created_by |
int | User to record as the author. Defaults to current user. |
force |
bool | Bypass the "max negative balance" cap on debits. Use sparingly. |
Example: pay royalties into the wallet from wp-royalties
add_action( 'wpr_royalty_due', function ( $user_id, $amount, $period ) {
if ( ! function_exists( 'wsw_credit' ) || ! wsw_is_active( $user_id ) ) {
return;
}
$tx = wsw_credit(
$user_id,
$amount,
sprintf( 'Royalties for %s', $period ),
array(
'type' => 'royalty_payout',
'source' => 'wp-royalties',
)
);
if ( is_wp_error( $tx ) ) {
error_log( 'Royalty credit failed: ' . $tx->get_error_message() );
}
}, 10, 3 );
Example: charge a Relay extra against the wallet
$result = wsw_debit(
$user_id,
9.90,
'Relay Express upgrade (order #1234)',
array(
'type' => 'relay_extra',
'source' => 'wp-relay-extras',
'order_id' => 1234,
)
);
if ( is_wp_error( $result ) ) {
// Show the error to the customer: insufficient balance, overdraft cap, etc.
wc_add_notice( $result->get_error_message(), 'error' );
}
Hooks
do_action( 'wsw_balance_changed', $user_id, $delta, $balance_after, $type, $tx_id, $args )— fires after every balance change.apply_filters( 'wsw_can_debit', $result, $user_id, $amount, $args )— let another plugin veto or approve a debit.apply_filters( 'wsw_transaction_type_label', $label, $type )— provide human labels for your custom types.
Security model
The wallet API is PHP-level only — there are no HTTP, REST or AJAX endpoints that allow third parties to credit or debit balances. A remote attacker cannot call wsw_credit() or wsw_debit() over the network. Server-side, the surface is:
- Admin UI: every action checks
current_user_can( 'manage_woocommerce' )plus a WordPress nonce. This includes balance adjustments, CSV export, enabling/removing wallets, the user-search AJAX endpoint, and the user profile checkbox. - Checkout: the wallet toggle AJAX checks a nonce. The wallet debit is validated before order creation (
woocommerce_after_checkout_validation) and only executed after payment succeeds (woocommerce_payment_complete). The intent is stored in order meta so off-site gateways (PayPal, 3-D Secure) cannot be exploited by session manipulation. - Refunds: credited automatically from the
woocommerce_order_refundedaction (full-wallet orders only), with double-credit protection via order meta. Order cancellations also restore the wallet amount. - Customer-facing pages: read-only. The "Wallet" tab in My Account never accepts input that changes the balance.
What about other plugins?
Any PHP code running on your site (themes, plugins, mu-plugins, snippets, scheduled tasks) can call the API. This is the same trust boundary that already applies to every WordPress plugin: a malicious plugin can access the database, alter orders, exfiltrate customer data, or empty Stripe — the wallet API doesn't widen this surface, it just exposes a clean way to do something a malicious plugin could already do by writing to usermeta directly. The defence is the same as for the rest of your stack:
- Only install plugins you trust.
- Keep an audit log: every wallet movement is recorded with a
sourceslug (the calling plugin's identifier), the order it was tied to, the user who triggered it, and a free-form note. Review the Transactions tab periodically. - Reconcile balances: the sum of all transactions for a user should equal their
_wsw_balance. A drift indicates code wrote to the meta without going through the API. - Wire the
wsw_can_debitfilter to a fraud-detection plugin if you want to veto suspicious patterns (e.g. amount > threshold, source not in your allowlist).
Concurrency
wsw_debit() reads the balance, checks the rule, writes the new balance and inserts the transaction. There is no row-level lock, so two simultaneous debits on the same user (the same millisecond) could both pass the eligibility check and produce a small overdraft. For typical e-commerce volume this is theoretical; for high-throughput integrations (mass payout scripts, automated billing) put your work behind a queue or wrap calls in a LOCK TABLES/SELECT ... FOR UPDATE flow.
Hardening checklist
- Disable file editing in
wp-config.php:define( 'DISALLOW_FILE_EDIT', true ); - Restrict who has
manage_woocommerceto the people who actually need it. - 2FA on admin accounts.
- Keep WP, WooCommerce, and this plugin on the latest version (the bundled update-checker pulls from GitHub).
Changelog
1.5.2
- Fix: the wallet is now debited immediately when the order is placed (like a gift card), not when external payment is confirmed. Previously, BACS (bank transfer) orders never debited the wallet because
payment_completedoes not fire until the admin confirms receipt. If the order later fails or is cancelled, the balance is restored automatically. - Invoice fix: the order total is restored to the full pre-wallet amount once the gateway has finished processing, so invoicing plugins compute the correct tax base and IVA. A "Paid from wallet" and "Charged to payment method" row appear in the order totals (admin, emails, My Account) to document the payment split.
1.5.1
- The wallet no longer adds a fee line item to the order. Previously the negative fee distorted the tax base for invoicing plugins (e.g. base imponible = 8.00 instead of 20.00).
- Rename: "Discounted from wallet" → "Paid from wallet" — the checkout review row and all references now reflect that the wallet is a payment method, not a discount.
- Set balance: admin balance adjustments now support a "Set balance to" option that sets the wallet to an absolute value. Internally it calculates and records the credit or debit for the difference.
1.5.0
- Breaking (internal): the wallet no longer adds a negative cart fee. Instead it reduces the payment total via the
woocommerce_calculated_totalfilter, which fires after all taxes have been computed and locked in. This means VAT/IVA is always preserved — the wallet is treated as a payment method, not a discount. Invoices now show the correct tax obligation regardless of how much the wallet covers. - A visual Discounted from wallet row is injected in the order review table (before the Total line) so the customer can see the deduction.
- When the order is created, a non-taxable fee line item is added to the order object so that line totals balance for invoicing plugins.
- The wallet box now reads the applied amount from the WC session (set during
woocommerce_calculated_total) instead of from a cart fee that no longer exists.
1.4.3
- Fix: the wallet fee now includes VAT/IVA. Previously the fee was calculated from cart-level getters that returned stale (zero) tax values during
woocommerce_cart_calculate_fees, so the deduction missed taxes — making them vanish from the order and producing an incorrect invoice. The fee now reads item-levelline_tax(always current) and falls back to shipping-rate tax data, ensuring the wallet covers the gross total including all taxes. - Fiscal correctness: the wallet is a payment method, not a discount. VAT/IVA is preserved on the order even when the wallet covers 100 % of the total — invoices will reflect the correct tax obligation.
1.4.2
- Fix: the wallet box now reads the applied amount directly from the cart fee instead of from the WC session. This eliminates any possible mismatch between the fee line in the order review and the amount shown in the wallet box (e.g. after switching shipping methods on v1.4.0/1.4.1).
- Labels: clearer checkout copy — the fee line is now labelled Discounted from wallet, the box header reads Current wallet balance, and the applied state says This will be deducted from your wallet.
1.4.1
- Fix: the wallet box now stays in sync during WooCommerce AJAX checkout refreshes. Previously, checking or unchecking the wallet had no visible effect after the first interaction because the checkout template's
woocommerce_review_order_before_paymenthook does not fire during AJAX updates. The box is now delivered as a custom WC fragment (woocommerce_update_order_review_fragments), so it re-renders on everyupdate_checkoutcycle. - UX: when the wallet discount is active, the box shows the applied amount and an explicit Remove link instead of a checked checkbox, making it obvious how to undo the discount.
1.4.0
- Breaking: the "Pay with wallet" WooCommerce payment gateway has been replaced by a gift-card-style wallet box that appears above payment methods at checkout. Customers check a box to apply their wallet balance; the remaining total (if any) is paid with any other gateway (Stripe, PayPal, bank transfer, etc.). If the wallet covers the full order, no payment method is needed.
- Split payments: customers can now use part of their wallet balance and pay the rest with a different method — the main feature gap the old gateway had.
- Overdraft at checkout: customers with allowed negative balance see the wallet box even when their balance is zero or negative, and can apply credit up to their overdraft limit.
- Order cancellation: wallet amounts are automatically restored when an order is cancelled.
- Validation: the wallet amount is re-validated right before the order is created, preventing race conditions (e.g. balance changed between page load and submit).
- Async-gateway safe: wallet intent is stored in order meta before payment, so off-site gateways (PayPal redirect, Stripe 3-D Secure) work correctly even when the WC session is lost.
- Refund policy: for full-wallet orders (no gateway charged) refunds are auto-credited. For split orders, the gateway handles its refund; the admin adjusts the wallet portion manually if needed.
- Removed: gateway title/description settings (no longer applicable).
1.3.3
- Admin UX: separate settings from actions on the user detail page. Overdraft limits sit at the top as a regular settings form (saved with Save limits); the balance adjustment is now wrapped in a visually distinct action card with its own Apply adjustment button. The 1.3.2 unified form was confusing because an adjustment is a one-shot transaction, not a setting.
1.3.2
- Admin UX: the user detail page now has a single Save changes button that persists overdraft limits and applies the optional balance adjustment in one shot. Limits always save; the adjustment is skipped if the amount field is left empty, and reported separately if it fails.
1.3.1
- Checkout: the balance is no longer appended to the gateway title. Instead, the gateway description supports a
{balance}placeholder that is replaced with the customer's current wallet balance. Default description: "Use your wallet balance to pay for this order. Available: {balance}". The hint is shown next to both the plugin's Gateway description setting and WooCommerce's own gateway Description field.
1.3.0
- Checkout: the "Pay with wallet" payment method now shows the customer's current balance in parentheses, e.g. Pay with wallet (€42.30).
- Per-user overdraft limits: each customer can now have their own Allow negative balance and Max negative balance values from their wallet detail page in
WooCommerce → Wallets. Empty fields fall back to the store defaults. Effective values and their source ("user" vs "default") are shown inline.
1.2.2
- Fix: the Wallet menu icon no longer fights theme styling. Only
font-familyandcontentare emitted on.woocommerce-MyAccount-navigation-link--wallet > a::before, so margins/alignment match the rest of the menu items.
1.2.1
- Fix: 404 on
/my-account/wallet/after plugin upgrades. Rewrite rules are now flushed automatically once per version. - New setting: My Account menu position — choose where the Wallet link appears (First, after Dashboard/Orders/Downloads/Addresses/Payment methods/Account details, or Last).
- New setting: Show menu icon — adds a Dashicons glyph next to the Wallet label as a generic fallback for themes that don't style our custom endpoint. The glyph (default
\f18e, the "money" icon) is configurable. Filterwsw_account_menu_icon_csslets themes override the entire CSS.
1.2.0
- My Account: redesigned wallet tab with a prominent balance card and a styled, responsive movements table.
- Admin: new Create wallet for user modal with live user search (AJAX, by name/login/email).
- Admin: new Remove button per row that disables the wallet and downgrades the Wallet Customer role to Customer; balance and history are preserved.
- New
Security modelsection in the README.
1.1.0
- New procedural API for other plugins:
wsw_credit(),wsw_debit(),wsw_can_debit(),wsw_get_balance(),wsw_get_transactions(),wsw_is_active(),wsw_set_active(),wsw_get_settings(). - New
sourcecolumn on the transactions table to track which plugin originated each movement (shown in the admin list and CSV export). - New filters:
wsw_can_debit,wsw_transaction_type_label. Newargspayload onwsw_balance_changed. - Custom transaction types supported (up to 64 chars). DB upgrade runs automatically on plugin load.
1.0.0
- Initial release.