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) |
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 |
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?
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. |
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.
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 |
|---|---|---|
topup | Credits purchased via a top-up pack | Credit (+) |
consume | Credits spent on a tool action | Debit (−) |
refund | Reversal of a prior consumption | Credit (+) |
bonus | Bonus credits (welcome bonus, pack bonus) | Credit (+) |
adjustment | Manual admin adjustment (either direction) | +/− |
expiry | Unused subscription credits dropped at cycle reset | Debit (−) |
subscription | Monthly plan allowance granted | Credit (+) |
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. |
| Credits | Base credits granted on purchase. |
| Bonus credits | Optional extra credits (a marketing lever), granted as a separate bonus entry. |
| Price | Fiat price charged through the payment gateway. |
| Currency | 3-letter currency code (defaults to the platform default). |
| Badge (translatable) | Optional UI badge (e.g. “Best value”, “Popular”). |
| Active | Only active packs are shown on the top-up page. |
| Position | Sort order. |
A unique URL slug is auto-assigned from the name and used in checkout URLs.
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 credits | The per-cycle allowance (granted every month, even on yearly billing). |
| Monthly price | Price charged for monthly billing. |
| Yearly price | Optional. Leave empty to offer monthly billing only. The discount vs 12× monthly is shown automatically. |
| Currency | 3-letter currency code. |
| Active / Position | Visibility and sort order. |
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
| Status | Meaning |
|---|---|
pending | Created, awaiting gateway payment. |
paid | Payment confirmed; credits granted to the wallet. |
failed | Payment failed or was declined. |
refunded | Order 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.
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.
| Setting | What it does |
|---|---|
| Pricing mode | Manual = type each price (default). Formula = derive prices from the rules below; the price/bonus fields disappear from the pack & plan forms. |
| Base rate | Price of a single credit before any discount. Set separately for packs and plans. |
| Volume discount tiers | Breakpoints: 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. |
| Rounding | No 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.
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
| Method | URL | Auth? | Description |
|---|---|---|---|
| GET | /wallet | Yes | Wallet dashboard: balance, plan panel, recent activity |
| GET | /wallet/transactions | Yes | Full transaction history |
| GET | /wallet/pricing | Yes | Buy credits / subscribe |
| GET | /wallet/subscribe/{plan} | Yes | Subscription 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)
| Step | Route | What happens |
|---|---|---|
| 1. Choose pack | /wallet/pricing | Browse available packs & plans. |
| 2. Checkout | /wallet/top-up/{pack} | Order summary + pay securely. |
| 3. Pay | POST /wallet/top-up/{pack} | A pending order is created and sent to the gateway. |
| 4a. Success | /wallet/top-up/order/{token}/success | Gateway confirms; credits granted. |
| 4b. Cancel | /wallet/top-up/order/{token}/cancel | No 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:
| Event | Effect |
|---|---|
customer.subscription.created | Activates the subscription + first credit grant |
invoice.paid | Renewal: extends the paid period (no extra grant) |
invoice.payment_failed | Marks the subscription past-due |
customer.subscription.updated | Syncs the “cancel at period end” flag |
customer.subscription.deleted | Ends the subscription, clears remaining credits |
For local testing:
stripe listen --forward-to https://YOUR_DOMAIN/wallet/webhook/subscription/stripe.
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 audience and are not forced,
so individual users can opt out via their notification preferences.
Updating
Method 1: Admin Panel Upload (Recommended)
- Download the latest
.zipof the add-on. - Go to Admin → Add-ons and click Upload.
- Select the
.zip. A confirmation shows the current and new version numbers; click Replace. - Go to Admin → System Update (
/admin/update) to apply any pending migrations.
Method 2: Manual File Replacement
- Replace the
extensions/addons/walletdirectory with the new version. - Run
php artisan migrate. - Clear caches:
php artisan config:clear,view:clear,route:clear.
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:
- Deactivate Credits Wallet (see Uninstallation).
- 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
migrationstable, so a later reinstall migrates from scratch; - deletes its published assets:
public/addons/wallet/,public/vendor/wallet/andstorage/app/public/addons/wallet/; - deletes the add-on directory
extensions/addons/wallet/; - deletes its row in the
addonstable (the recorded purchase code goes with it) and clears the application cache.
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/stripeand subscribed tocustomer.subscription.created. - Verify
STRIPE_WEBHOOK_SECRETmatches the endpoint’s signing secret. - Check
storage/logs/laravel.logfor webhook errors.
Monthly credits not renewing
- Ensure the system cron runs
php artisan schedule:runevery minute. - Run
php artisan wallet:grant-subscription-creditsmanually 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 directdebit()without a wrappingspend()will not auto-refund: check the tool add-on’s integration.
Top-up notification not received
- Check that
wallet_topup_receivedis enabled in Settings → Notifications and that the user has not opted out. - Verify your mail configuration in
.envand check thefailed_jobstable.
Credits Wallet v1.0.0: Part of the Larapen CMS platform.
© BeDigit. All rights reserved.