A foundational, per-user credit system. It gives every user a credit balance backed by an immutable transaction ledger, sells credits through one-time top-up packs and recurring subscription plans, and exposes a single WalletService::spend() API that all AI/SaaS tool add-ons use to charge for usage.

Per-user Credit Balance

Each user gets a wallet account with two buckets: permanent credits (purchases, bonuses, adjustments) and current-cycle subscription credits.

Immutable Ledger

Every movement is recorded as a transaction that is never updated or deleted. Corrections append an offsetting entry, so the history is fully auditable.

One-time Top-up Packs

Admin-defined credit bundles sold at a fiat price. Users buy them through the platform’s existing payment gateways.

Subscription Plans

Recurring monthly allowances with an optional discounted yearly billing option, billed through Stripe and dripped monthly.

Action Pricing

Set how many credits each tool action costs from one admin screen. Tool add-ons declare actions; you re-price them without code.

Auto-refund on Failure

Tools call spend() which reserves credits, runs the work, and automatically refunds if it throws: a failed generation never costs the user.

Use Cases

AI / SaaS Tools Platform

You run a suite of credit-consuming tools (logo builder, background remover, AI headshots, video tools).

  • The wallet provides the shared balance every tool draws from.
  • Each tool declares its actions and a default cost; you tune prices centrally at Action Pricing.
  • Users top up à la carte or subscribe to a monthly allowance.

Subscription-first Product

You want recurring revenue with a predictable monthly credit allowance.

  • Create plans (e.g. Starter / Creator / Pro) with monthly credits and an optional cheaper yearly price.
  • Credits drip monthly even on yearly billing, and reset each cycle (use-it-or-lose-it).
  • Users self-serve cancel/resume from their wallet page.

Pay-as-you-go Add-on

You only need one-time purchases, no subscriptions.

  • Create top-up packs only; leave the plans list empty.
  • Optionally grant a welcome bonus so new accounts can try the tools before paying.

Requirements

  • Larapen CMS v1.0.0 or later (requires_core: >=1.0.0)
  • PHP 8.3+
  • MySQL 8.0+
  • The wallet requires a user account (needs_user_account: true): balances are per user.
  • At least one payment gateway add-on to actually charge money (see below).
Capability Add-on(s)
One-time pack purchases stripe, paypal, paddle, momo
Recurring subscriptions stripe (recurring billing)
Foundational add-on. The wallet is addon_type: foundation and license_type: not-salable: it is not sold on its own. It ships as a dependency of the AI/SaaS tools add-ons that consume credits.

Installation

Step 1: Place & Activate the Add-on

Copy or symlink the wallet folder into extensions/addons/, then activate it from Admin → Add-ons (or run php artisan addons:sync). Activation registers the permissions and the admin menu.

Step 2: Run Migrations

php artisan migrate

This creates six wallet_-prefixed tables: wallet_accounts, wallet_packs, wallet_orders, wallet_transactions, wallet_plans, and wallet_subscriptions.

Step 3: Seed Starter Offers (Optional)

php artisan db:seed --class="Addons\Wallet\Database\Seeders\WalletSeeder"

Seeds three subscription plans (Starter / Creator / Pro, monthly + discounted yearly) and one one-time top-up pack. The seeder is idempotent (firstOrCreate).

Step 4: Assign Permissions

The add-on registers permissions under the wallet.* namespace. Assign them to roles via Admin → Roles & Permissions.

Step 5: Create Plans and/or Packs

Define your offers at Wallet → Subscription Plans and/or Wallet → Top-up Packs (or use the seeder). Then point your users at the public wallet page /wallet (add it to your site navigation as a custom menu link).

Purchase Code (License Key)

Credits Wallet is not sold separately: it ships with the products that require it, so activating it does not ask for a purchase code of its own. The purchase code you need is the one of the product you actually bought: the main application, or the paid add-on or theme that bundles Credits Wallet. This page explains where to find that code.

Our products are sold on three platforms. The way you receive a purchase code depends on where you bought the product.

Platform / Marketplace How you get the purchase code Where to find it again
bedigit.com Store
In-site purchase (Shop)
Generated automatically when the order is paid, then sent by email, either in its own license email, or inside the order confirmation email. My Account → My Licenses on bedigit.com
Gumroad Created as soon as Gumroad notifies us of the sale, then sent in a separate email, in addition to the Gumroad receipt. The license email, your Gumroad Library, and My Account → My Licenses on bedigit.com
Envato Market
CodeCanyon
Issued by Envato, not by us, and never sent by email: you download it yourself from your Envato account. Envato account → Downloads → License certificate & purchase code
Check your spam folder. For both bedigit.com Store and Gumroad purchases, the purchase code is delivered by email. Automated license emails are very often filtered, so if the message is not in your inbox, look in your spam / junk folder before contacting support, and add our sender address to your contacts or allow list.

1. bedigit.com Store (in-site purchase)

  • As soon as the order’s payment status becomes Paid, a license key is generated automatically for every licensed item in the order (one key per purchased unit: buying 3 units gives 3 distinct keys).
  • It is emailed to the address used on the order, either in a dedicated license email or inside the order confirmation email. Check your inbox and your spam / junk folder.
  • The key stays available in your account under My Account → My Licenses. Keys are masked in the list; open the license detail page to reveal and copy the full key, see the domains it is activated on, and deactivate a domain to free an activation slot.
  • The matching invoice is under My Account → My Orders.

2. Gumroad

  • A Gumroad purchase produces two separate emails: the Gumroad receipt (sent by Gumroad, giving access to the files) and a license key email (sent by bedigit.com) that contains your purchase code.
  • The license key email is generated as soon as Gumroad notifies us of the sale, so it normally arrives within seconds of the payment. Here too, check your inbox and your spam / junk folder.
  • When the Gumroad product uses Gumroad’s own license-key feature, the same key also appears in your Gumroad receipt and under Library → your purchase on gumroad.com.
  • Use the same email address on bedigit.com as on Gumroad: your keys are then linked to your account automatically and listed under My Account → My Licenses, even if you register after the purchase. You can also add a Gumroad key manually from My Account → My Gumroad Licenses.

3. Envato Market (CodeCanyon)

  • Envato purchase codes are issued and delivered by Envato Market, never emailed by us, so there is nothing to look for in your spam folder: you retrieve the code from your Envato account.
  • Log in to your Envato / CodeCanyon account, open the Downloads page, find the item, and choose License certificate & purchase code from the Download dropdown. The code is written in that certificate.
  • An Envato purchase code looks like 12345678-90ab-cdef-1234-567890abcdef (8-4-4-4-12 characters). It never changes, and renewing item support does not issue a new one.
  • Official Envato article: Where Is My Purchase Code?
Lost your purchase code? Search your mailbox (spam folder included) for “license” or “purchase code”, then check My Account → My Licenses on bedigit.com for Store and Gumroad purchases, or Downloads → License certificate on Envato. If it is still missing, open a ticket on our Help Center with your order number (Store), Gumroad sale ID or buyer email (Gumroad), or Envato username and item name (Envato).

The Credit Model

Each wallet account holds two separate credit buckets:

Bucket Column Behaviour
Permanent balance Purchases (top-up packs), bonuses, manual adjustments. Never expires.
Subscription subscription_balance The current cycle’s plan allowance. Reset every month (use-it-or-lose-it); the unused remainder is recorded as an expiry ledger entry.
Spending order. When credits are spent, the subscription bucket is drawn first, then permanent credits, so monthly plan credits are used before the ones a user paid for à la carte. The user-facing balance is the sum of both buckets.

Every change to a balance writes an immutable transaction to the ledger, with the running balance_after stored so the history can be audited without replaying it.

Configuration

Settings are managed at Admin → Wallet → Settings (stored in the settings table). Config file defaults live in config/wallet.php; database settings override them at runtime.

Setting Description Default
wallet_credit_label Singular label shown to users for the virtual currency. credit
wallet_credit_label_plural Plural label shown to users. credits
wallet_default_currency Default fiat currency for top-up packs when a pack does not set one. USD
wallet_low_balance_threshold Balance at or below which a user is considered “low” and may be nudged to top up (in credits). 20
wallet_welcome_bonus Credits granted once when an account is first created. 0 disables the welcome bonus. 0
wallet_faq_category_ids FAQ categories to display as an FAQ section on the top-up page. Empty hides it. (empty)
wallet_pricing_mixed Pricing page layout. 1 shows three combined cards (Free Trial, Subscription, Pay As You Go); 0 shows two separate sections. 0
wallet_pricing_order Which section comes first in the separate layout: packs or plans. Ignored when the mixed layout is on. packs
wallet_pricing_guest_disabled By default guests can view the pricing page. 1 hides it from visitors who are not logged in (requires login). 0
wallet_pricing_mode manual (type each price) or formula (derive prices: see Pricing Formula). manual

Additional config-file-only defaults in config/wallet.php: credits_expire (default false), credit_expiry_days (default null), and auto_create_account (default true: a wallet account is created on first access). The pricing-formula rates and tiers also have config/wallet.php defaults under the formula key (overridden per-field by the wallet_formula_* settings).

The settings page also exposes the standard Page Header overrides (hero / simple mode) for the public wallet pages.

Admin: Dashboard

The Dashboard (Wallet → Dashboard) gives an at-a-glance overview of the wallet economy.

Stats Cards

  • Wallet accounts: number of users with a wallet.
  • Credits in circulation: total spendable credits across all accounts.
  • Credits purchased (lifetime) and Credits spent (lifetime).
  • Paid orders and Revenue from top-up purchases.

Recent Activity

Panels for Recent orders and Recent usage (consumption) let you spot the latest purchases and spends without leaving the dashboard.

The dashboard also surfaces the public page link (/wallet) so you can add it to your site navigation as a custom menu link.

Admin: Accounts

Managed at Wallet → Accounts. Lists every user wallet with its user, balance, lifetime purchased, and lifetime spent.

Account Detail

Opening an account shows its balances and full transaction history for that user.

Manual Balance Adjustment

From an account, an admin with wallet.accounts.adjust can add or remove credits with an optional reason recorded on the ledger entry.

Adjustments touch permanent credits only and may not drive the balance below zero. A removal is capped at the available permanent balance.

Admin: Transactions (Ledger)

The global ledger at Wallet → Transactions is a read-only, paginated list of every credit movement across all accounts. Each row shows the user, type badge, signed amount, balance after, action key (for usage/refunds), description, and date.

Transaction Types

Type Meaning Direction
topupCredits purchased via a top-up packCredit (+)
consumeCredits spent on a tool actionDebit (−)
refundReversal of a prior consumptionCredit (+)
bonusBonus credits (welcome bonus, pack bonus)Credit (+)
adjustmentManual admin adjustment (either direction)+/−
expiryUnused subscription credits dropped at cycle resetDebit (−)
subscriptionMonthly plan allowance grantedCredit (+)

Admin: Top-up Packs

Top-up packs are one-time credit bundles. Managed at Wallet → Top-up Packs with an AJAX list and modal create/edit.

Field Description
Name (translatable)Display name of the pack.
CreditsBase credits granted on purchase.
Bonus creditsOptional extra credits (a marketing lever), granted as a separate bonus entry.
PriceFiat price charged through the payment gateway.
Currency3-letter currency code (defaults to the platform default).
Badge (translatable)Optional UI badge (e.g. “Best value”, “Popular”).
ActiveOnly active packs are shown on the top-up page.
PositionSort order.

A unique URL slug is auto-assigned from the name and used in checkout URLs.

Free Trial & formula mode. A pack priced at 0 renders as a free “Free Trial” card on the pricing page. When the Pricing Formula is enabled, the Price and Bonus credits fields disappear from this form: both are derived from the pack’s credit amount and filled automatically on save.

Admin: Subscription Plans

Recurring plans grant a monthly credit allowance. Managed at Wallet → Subscription Plans (AJAX list + modal create/edit).

Field Description
Name (translatable)Plan name (e.g. Starter, Creator, Pro).
Badge (translatable)Optional UI badge.
Description (translatable)Optional plan description.
Monthly creditsThe per-cycle allowance (granted every month, even on yearly billing).
Monthly pricePrice charged for monthly billing.
Yearly priceOptional. Leave empty to offer monthly billing only. The discount vs 12× monthly is shown automatically.
Currency3-letter currency code.
Active / PositionVisibility and sort order.
Credit policy: monthly drip, reset each cycle. Credits are granted monthly regardless of billing interval. A yearly subscription is billed once a year but still receives its monthly allowance every month. The first grant happens on activation; subsequent grants come from the daily scheduler (see Credit Scheduler), so there is never a double-grant. Unused subscription credits do not roll over.

Gateway product/price identifiers are auto-created from your plans on the first checkout (and re-created when a plan’s price changes). You do not create them in the gateway by hand.

With the Pricing Formula enabled, the Monthly price and Yearly price fields are hidden and derived from the plan’s monthly credits and the yearly-discount setting.

Admin: Top-up Orders

Managed at Wallet → Orders. Every top-up checkout creates one order row. The list shows the order number, user, status badge, gateway, amount, and credits. Opening an order shows its full detail and linked transaction(s).

Order Statuses

StatusMeaning
pendingCreated, awaiting gateway payment.
paidPayment confirmed; credits granted to the wallet.
failedPayment failed or was declined.
refundedOrder refunded.

Orders implement the platform BillableForInvoice contract, so paid top-ups can flow into the core invoice system.

Admin: Action Pricing

At Wallet → Action Pricing, pick a tool group/service and set the credit cost of each of its actions. Tool add-ons declare their actions (and a default cost) in their manifest; this screen lets you re-price every tool from one place without touching code.

  • Leave a field blank to use the add-on’s default cost for that action.
  • Overrides are read by every tool through the shared action registry at charge time.
No actions listed? No credit-priced actions are registered until you activate a tool add-on (logo builder, background remover, etc.) that declares them.

Admin: Settings

The settings page (Wallet → Settings) is organized into tabs:

  • Credit labels: singular / plural label for the virtual currency.
  • Balance & bonuses: default pack currency, low-balance threshold, the welcome bonus for new accounts, and the FAQ categories to display on the pricing page.
  • Pricing page: combine plans & packs into the three-card layout (or keep two separate sections and choose which comes first), and optionally hide the pricing page from guests.
  • Pricing formula: switch to derived pricing and configure the rates & tiers (see Pricing Formula).
  • Checkout: Sale Terms for the credit top-up checkout. By default the global sale terms (Settings → General → Sale Terms) apply; turn on Override the global sale terms to require (or not) the acceptance of a specific page on the top-up checkout and the subscription confirmation step only.
  • Page Header: hero / simple header overrides for the public wallet pages.

See Configuration for the full key reference.

Admin: Pricing Formula

Instead of typing every price by hand, the Pricing formula tab lets you set a few values and generate the price of every pack & plan: the classic “rate card” model (price falls per credit as volume grows). It is opt-in: the mode defaults to Manual, so nothing changes until you switch it on.

SettingWhat it does
Pricing modeManual = type each price (default). Formula = derive prices from the rules below; the price/bonus fields disappear from the pack & plan forms.
Base ratePrice of a single credit before any discount. Set separately for packs and plans.
Volume discount tiersBreakpoints: at/above N credits, drop the per-credit rate by X%. The tier with the largest min credits that is ≤ a row’s credits applies.
Bonus tiers (packs)At/above N credits, grant X% extra credits as a bonus.
Yearly −% (plans)Discount applied to 12 × monthly to derive the yearly price.
RoundingNo rounding (2 decimals), Whole number, or Charm (….99).

A live preview table shows the prices your current packs & plans would get from the formula as you edit it (before saving). Saving in Formula mode, or the Recalculate now button, re-derives and stores every active pack & plan price.

Existing orders are never affected. Each order snapshots the credits and amount charged at purchase time, so changing the formula (or switching modes) only changes future pricing. Switching back to Manual keeps the last derived prices, which you can then edit by hand.

Example (defaults): a base rate of 0.02/credit with −10% at 5,000 and −25% at 10,000, charm rounding → 1,000 credits = $19.99, 5,000 = $89.99 (+1,000 bonus), 10,000 = $149.99 (+2,500 bonus).

Front-end: My Wallet

The public wallet pages are for authenticated end users. They are localized (e.g. /fr/wallet) with non-localized variants for the default language.

Routes

MethodURLAuth?Description
GET/walletYesWallet dashboard: balance, plan panel, recent activity
GET/wallet/transactionsYesFull transaction history
GET/wallet/pricingYesBuy credits / subscribe
GET/wallet/subscribe/{plan}YesSubscription confirmation step (plan summary + sale terms)

Wallet Dashboard

  • Current balance: the sum of permanent + subscription credits, with a low-balance warning when applicable.
  • Your plan panel: shown when the user has a subscription, with the renew/end date and Cancel / Resume controls.
  • Recent activity: the latest ledger entries, with a link to the full history.

Front-end: Top-up & Subscribe

The pricing page (/wallet/pricing) presents both subscription plans and one-time packs. By default it is visible to guests (they sign in only when they buy or subscribe); the Pricing page settings can hide it from guests. Every paid card shows a per-credit cost (e.g. $ 0.0190 USD/Credit), and a pack priced at 0 appears as a free Free Trial card.

Layouts

  • Separate (default): a Subscription section and a one-time Packs section. The Section order setting chooses which comes first.
  • Combined (mixed): three cards (Free Trial, Subscription, and Pay As You Go), where the Subscription and Pay As You Go cards use a dropdown to switch tier in place (online-convert style).

Subscriptions

  • A monthly / yearly toggle switches all plan prices (and shows the yearly savings).
  • Subscribe opens a confirmation step (/wallet/subscribe/{plan}?interval=monthly|yearly, sign-in required): the plan summary and, when required, the sale terms to accept (see the Checkout settings tab). Continue to secure payment then sends the user to the gateway hosted checkout.
  • After payment, the gateway webhook activates the subscription and grants the first monthly allowance.

One-time Packs

  • Each pack shows its credits (plus any bonus) and price.
  • Buy now opens a checkout with an order summary and payment-method selection.
  • On successful payment, credits are added to the permanent balance instantly and the user is returned to a success page.

Checkout Flow (Packs)

StepRouteWhat happens
1. Choose pack/wallet/pricingBrowse available packs & plans.
2. Checkout/wallet/top-up/{pack}Order summary + pay securely.
3. PayPOST /wallet/top-up/{pack}A pending order is created and sent to the gateway.
4a. Success/wallet/top-up/order/{token}/successGateway confirms; credits granted.
4b. Cancel/wallet/top-up/order/{token}/cancelNo credits charged.

Stripe Subscriptions

Recurring billing is handled by the Stripe gateway. Configure it as follows.

Step 1: API Keys

Add your Stripe keys to .env (the names read by the Stripe add-on config):

STRIPE_PUBLIC_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx

Step 2: Webhook Endpoint

In Stripe, add a webhook pointing at:

https://YOUR_DOMAIN/wallet/webhook/subscription/stripe

Subscribe it to these events:

EventEffect
customer.subscription.createdActivates the subscription + first credit grant
invoice.paidRenewal: extends the paid period (no extra grant)
invoice.payment_failedMarks the subscription past-due
customer.subscription.updatedSyncs the “cancel at period end” flag
customer.subscription.deletedEnds the subscription, clears remaining credits

For local testing: stripe listen --forward-to https://YOUR_DOMAIN/wallet/webhook/subscription/stripe.

Products/Prices are auto-created from your plans on the first checkout and re-created when a plan’s price changes; the ids are stored on the plan. You never create them in Stripe by hand.

Credit Scheduler

The monthly allowance is dripped by a daily Artisan command. Ensure Laravel’s scheduler runs via a single system cron entry:

* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1

The add-on registers this job (daily, withoutOverlapping, onOneServer):

php artisan wallet:grant-subscription-credits

It grants the monthly allowance to every active subscription whose grant is due and whose paid period still covers it. You can run it manually at any time to grant any subscriptions that are due now.

Notifications

The add-on ships two user-facing notification types, registered centrally via addon.json and togglable at Admin → Settings → Notifications.

Type Key Recipient Trigger Channels
wallet_topup_received User A credit top-up is successfully paid mail, database
wallet_low_balance User Wallet balance runs low database
User preferences apply. Both types target the user audience and are not forced, so individual users can opt out via their notification preferences.

Updating

Method 1: Admin Panel Upload (Recommended)

  1. Download the latest .zip of the add-on.
  2. Go to Admin → Add-ons and click Upload.
  3. Select the .zip. A confirmation shows the current and new version numbers; click Replace.
  4. Go to Admin → System Update (/admin/update) to apply any pending migrations.

Method 2: Manual File Replacement

  1. Replace the extensions/addons/wallet directory with the new version.
  2. Run php artisan migrate.
  3. Clear caches: php artisan config:clear, view:clear, route:clear.
Back up first. Always back up your database before running migrations on a production system. Because every AI/SaaS tools add-on depends on the wallet, do not remove the wallet while those tools are still active.

Uninstallation

Switching an add-on off without losing anything is a deactivation: go to Admin panel → Add-ons, find Credits Wallet and click Deactivate.

  • Its routes, views, admin menu entries and permissions stop being registered, and its front-end pages stop answering.
  • Its database tables and all the data they hold are kept, and its files stay under extensions/addons/wallet/. Nothing is deleted.
  • The purchase code recorded at activation is kept too, so activating the add-on again does not ask for it.
  • Deactivation is refused while another active add-on depends on this one: deactivate that add-on first.

Click Activate on the same card to switch it back on. Pending migrations are re-run, assets are republished, and the add-on picks up exactly where it left off.

Removing

Removing is permanent and destroys the add-on's data. The Remove button only appears on a deactivated add-on, so removal is always two steps:

  1. Deactivate Credits Wallet (see Uninstallation).
  2. Click Remove on its card and confirm the prompt.

The admin panel then, in one pass:

  • runs the add-on's uninstall hook, if it ships one, while its code is still on disk;
  • revokes the permissions declared in its addon.json;
  • rolls back its migrations (this drops its database tables and every row they hold) and purges its entries from the migrations table, so a later reinstall migrates from scratch;
  • deletes its published assets: public/addons/wallet/, public/vendor/wallet/ and storage/app/public/addons/wallet/;
  • deletes the add-on directory extensions/addons/wallet/;
  • deletes its row in the addons table (the recorded purchase code goes with it) and clears the application cache.
This cannot be undone. Back up your database before removing an add-on whose data you may still need: installing it again later creates empty tables, not your old content.

Removal is refused, with an explanatory message and before anything is destroyed, when the add-on is still active, when another active add-on depends on it, or when the web server (PHP) user cannot delete extensions/addons/wallet/. In that last case, give that user write permission on the directory and on its parent, then try again.

Deleting the folder over FTP or SSH is not equivalent: the add-on's tables, its entries in the migrations table and its addons row are all left behind, and its card stays in the list. Use Remove in the admin panel instead.

Troubleshooting

Top-up page shows no packs or plans

  • Ensure at least one pack/plan exists and is marked active, or run the seeder.
  • Plans only show a yearly option when a yearly price is set; otherwise they are monthly-only.

“No payment method is available” at checkout

  • Install and configure at least one payment gateway add-on (stripe, paypal, paddle, momo) for one-time packs.
  • Subscriptions require the Stripe gateway specifically.

Subscription paid but no credits appeared

  • Confirm the Stripe webhook is registered at /wallet/webhook/subscription/stripe and subscribed to customer.subscription.created.
  • Verify STRIPE_WEBHOOK_SECRET matches the endpoint’s signing secret.
  • Check storage/logs/laravel.log for webhook errors.

Monthly credits not renewing

  • Ensure the system cron runs php artisan schedule:run every minute.
  • Run php artisan wallet:grant-subscription-credits manually to grant subscriptions due now.
  • Credits are only granted while the subscription is active and its paid period still covers the drip.

A user was charged for a failed generation

  • Tools should charge via WalletService::spend(), which auto-refunds on failure. A direct debit() without a wrapping spend() will not auto-refund: check the tool add-on’s integration.

Top-up notification not received

  • Check that wallet_topup_received is enabled in Settings → Notifications and that the user has not opted out.
  • Verify your mail configuration in .env and check the failed_jobs table.

Credits Wallet v1.0.0: Part of the Larapen CMS platform.

© BeDigit. All rights reserved.

Was this article helpful?

Thank you for your feedback!

Still need help? Create a support ticket

Create a Ticket
Sep 28, 2026