ChainPay Docs
VERSION 1.0 · MAY 2026

ChainPay Gateway

Self-hosted, multi-chain crypto payment gateway. One codebase to accept Bitcoin, Ethereum, BNB, USDT, USDC, Litecoin, Dogecoin and more — plus Stripe + PayPal alongside.

17+ chains

BTC, ETH, BNB, ERC-20, BEP-20, TRC-20, LTC, DOGE, BCH, XRP, SOL, ALGO, XMR, Lightning.

🧩

Drop-in widget

~80 kB JavaScript bundle. One <script> tag, one host element. Inline or popup.

🔗

Hosted checkout

Every checkout gets a public URL: /c/{slug}. Share by email, QR, social.

📊

Full admin

Dashboard, ledger, customers, custom fields, branding, security, audit log.

💳

Fiat fallback

Stripe + PayPal next to crypto in the same picker. PCI burden stays with the provider.

🔐

Self-hosted

Your server, your keys. No middleman, no platform fees, no rate limits beyond your RPC.

Quick start

If you already have PHP 8.3+, MySQL, and Composer:

git clone <your-purchase> chainpay
cd chainpay
cp .env.example .env
composer install --no-dev --optimize-autoloader
npm install && npm run build
php artisan key:generate
php artisan migrate --seed
php artisan serve

Open http://localhost:8000, finish the setup wizard, log in, you're live.

Live demo — try the deployed install at chainpay.codic.online.

Installation

System requirements

ComponentMinimumRecommended
PHP8.38.4
MySQL / MariaDB8.0 / 10.68.4
Composer2.5latest
Node.js (build only)2022
Web serverNginx or ApacheNginx + PHP-FPM
Disk250 MB1 GB+ (logs, QR cache)
Memory256 MB PHP512 MB

PHP extensions required: pdo_mysql, mbstring, bcmath, gmp, openssl, curl, intl, xml, zip, gd (or imagick).

Why gmp and bcmath? ChainPay handles arbitrary-precision crypto amounts (18-decimal ETH wei, 8-decimal BTC satoshi). Native int overflows and loses precision; both extensions keep the math exact.

Install wizard (recommended)

  1. Upload the package to your web root (or a subdirectory).
  2. Set the document root to the public/ directory.
  3. Visit the URL in your browser. The install wizard auto-launches if .env is missing.
  4. Walk through 4 steps:
    1. Requirements check — wizard verifies PHP version, extensions, writable storage/.
    2. Database — enter MySQL host, database, user, password. Wizard tests connection, runs migrations + seeds.
    3. Owner account — name, email, password.
    4. Branding — product name, tagline, primary colour. (Editable later in admin.)

The wizard writes .env for you, generates APP_KEY, and finalises the install.

Manual install

# 1. Files
git clone <your-purchase> chainpay
cd chainpay
cp .env.example .env

# 2. PHP deps
composer install --no-dev --optimize-autoloader

# 3. Front-end build (only on a build host — production server doesn't need Node)
npm install
npm run build

# 4. App key + storage symlink
php artisan key:generate
php artisan storage:link

# 5. Database (edit .env first: DB_DATABASE, DB_USERNAME, DB_PASSWORD)
php artisan migrate --seed

# 6. Caches (production)
php artisan config:cache
php artisan route:cache
php artisan view:cache

Required .env keys

APP_NAME=ChainPay
APP_ENV=production
APP_KEY=                 # populated by `php artisan key:generate`
APP_DEBUG=false
APP_URL=https://your-domain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=chainpay
DB_USERNAME=chainpay
DB_PASSWORD=

CACHE_STORE=file
SESSION_DRIVER=database
QUEUE_CONNECTION=database

# Mail via Resend (preferred):
MAIL_MAILER=smtp
MAIL_HOST=smtp.resend.com
MAIL_PORT=465
MAIL_USERNAME=resend
MAIL_PASSWORD=re_xxxxxxxxxxxxxxxxxxxxxxxx
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS="hello@your-domain.com"
MAIL_FROM_NAME="${APP_NAME}"

File permissions

chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
chmod 600 .env

Nginx config (recommended)

server {
    listen 443 ssl http2;
    server_name your-domain.com;
    root /var/www/chainpay/public;
    index index.php;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    }

    location ~ /\.(?!well-known).* { deny all; }

    client_max_body_size 25M;
}

Apache? A .htaccess ships in public/. Point DocumentRoot at it and ensure mod_rewrite is on.

Cron + queue worker

ChainPay needs a heartbeat to monitor the chain, refresh exchange rates, retry webhooks, and prune stale transactions. Two pieces:

1. Cron (required)

On a VPS / dedicated server:

# Laravel scheduler — fires every job at its declared cadence
* * * * * cd /var/www/chainpay && php artisan schedule:run >> /dev/null 2>&1

On Hostinger shared hosting:

  1. hPanel → Advanced → Cron Jobs
  2. Click Create a new cron job
  3. Choose Type → "Custom"
  4. Run interval: every minute (* * * * *)
  5. Command:
/opt/alt/php84/usr/bin/php /home/USER/domains/YOURDOMAIN/public_html/chainpay/artisan schedule:run >/dev/null 2>&1

Replace USER with your hosting username and YOURDOMAIN with your domain.

On other shared hosts (cPanel, Plesk, DirectAdmin): same idea — use the panel's cron UI to fire php artisan schedule:run every minute, with the absolute path to your PHP 8.3+ binary.

2. Queue worker — choose one

Option A — Long-running worker (best for VPS, high volume)

php artisan queue:work --sleep=3 --tries=3 --max-time=3600

Run via systemd or supervisor for resilience:

# /etc/systemd/system/chainpay-worker.service
[Unit]
Description=ChainPay queue worker
After=network.target

[Service]
User=www-data
Restart=always
WorkingDirectory=/var/www/chainpay
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-time=3600

[Install]
WantedBy=multi-user.target
sudo systemctl enable chainpay-worker
sudo systemctl start chainpay-worker

Option B — Sync mode (best for shared hosting)

If your host doesn't allow long-running processes (most shared hosts don't), set QUEUE_CONNECTION=sync in .env. Jobs run inline within whatever process triggers them — the cron tick is the worker. Simpler, slightly slower per-request, no daemon needed.

# .env
QUEUE_CONNECTION=sync

What the scheduler does

JobCadencePurpose
chainpay.transactions.check-pendingevery minutePolls every pending tx against the chain via configured RPCs/explorers
chainpay.exchange-rates.refreshevery 15 minRefreshes fiat→crypto rates from Coinbase/Binance/Bitstamp
chainpay.transactions.purge-staleevery 5 minMarks expired any pending tx past its expires_at
chainpay.webhooks.retry-pendingevery minuteRetries failed merchant webhooks per their backoff schedule
chainpay.retention.applydaily 03:30Prunes audit log + webhook delivery history per retention settings

Verifying the cron is alive

# Should show recent timestamps
tail -f storage/logs/laravel.log

# Should show the scheduler dispatching jobs
php artisan schedule:list
If storage/logs/laravel.log has no new entries after 5 minutes of the cron supposedly being on, the cron isn't actually running. Most common cause on shared hosts: wrong PHP binary path, or the panel's cron UI is set to a higher interval than "every minute".

Alchemy API key (required for ETH + BSC + Solana)

Public RPCs can't deliver the address-history lookups the chain monitor needs at production rates. Alchemy's free tier covers Ethereum, BSC, and Solana mainnet.

  1. Sign up free at alchemy.com — no credit card.
  2. Create a new app (any name; pick "Ethereum mainnet" — the same key works across all their chains).
  3. Copy the API key (the part after /v2/ in the HTTPS URL).
  4. In ChainPay admin → Settings → Blockchains:
    • Alchemy API key → paste → save (powers ETH + BSC + ERC-20 + BEP-20)
    • Solana RPC URL → paste https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY → save (uses the same Alchemy key, just a different endpoint per chain)

That single key powers BNB, ETH, USDT-ERC20, USDC-ERC20, USDT-BEP20, BUSD, SOL, any SPL tokens, and any custom ERC-20 / BEP-20 tokens.

Demo mode

For marketplace demos / sales pages where prospective buyers should be able to browse the admin without breaking anything, flip the toggle:

# .env
APP_DEMO_MODE=true

Then php artisan config:clear. The admin UI now:

  • Shows a persistent gradient banner: "You're viewing a live demo. Browse anything — saves are disabled."
  • Allows all GET requests (browsing, viewing dashboards)
  • Blocks every POST/PUT/PATCH/DELETE in /admin/* with a flash error toast: "This is a live demo — purchase the script for full access."
  • Whitelists /logout so visitors can sign out

Forms still render so visitors see the full feature surface — the save button shows the upgrade message instead of persisting.

To make real admin changes on a demo install: SSH in, set APP_DEMO_MODE=false in .env, run php artisan config:clear, do the work, set it back. No code change.

Demo mode does NOT affect public surfaces — the widget, hosted checkout, Pay page, and webhook receivers all remain fully functional. Demo visitors can complete real transactions end-to-end.

Admin guide

This is a screen-by-screen walkthrough of the admin panel. Log in at https://<your-domain>/login with the owner account you created during install.

Dashboard /admin

The landing page after login. Six regions:

  1. Headline tiles — revenue today (with delta vs. yesterday), confirmed transactions, pending transactions, customer count.
  2. Revenue & volume trend — 30-day dual-series area + line chart. Drag to zoom.
  3. Status mix donut — pending/confirmed/expired/refunded breakdown.
  4. Gateway split bar — revenue per gateway (crypto/Stripe/PayPal/etc.).
  5. Activity feed + Top customers — last 10 transactions and lifetime-spend leaderboard. Each row is clickable.
  6. Install health strip — 8 indicators: cache, queue, mail, SSL, scheduler, storage, rates, wallets.

Wallets /admin/wallets

The crypto receiving addresses payments will land in.

  • Add wallet: pick a cryptocurrency, paste the address, optionally label it. The system validates the address against the chain's encoding (Base58Check for BTC, EIP-55 for ETH, Bech32 for Lightning, etc.).
  • Activate / deactivate to gate which wallets the customer-facing widget can route payments to.
  • Soft-delete to retire a wallet without losing audit history.
Customers will only see cryptos that have at least one active, non-deleted wallet. Forgetting to add a wallet is the #1 reason a coin doesn't appear in the widget.

Custom tokens /admin/custom-tokens

For ERC-20 / BEP-20 / TRC-20 tokens not in the default catalogue.

  • Networkethereum, bsc, or tron. Determines which RPC handles confirmations.
  • Contract address — must be the token contract (NOT a wallet). EIP-55 checksum auto-applied for EVM.
  • Decimals — most ERC-20s are 18 or 6. Confirm against the contract on Etherscan.
  • Pricing — either Fixed rate (e.g. 1 for a USD-pegged stablecoin), or Rate URL — a JSON endpoint returning {"price": <number>}. The rate engine polls every 5 minutes.

Transactions /admin/transactions

Read-mostly ledger. Filter by status, gateway, date range, customer.

  • Show page displays the full lifecycle: created, payment-seen, confirmation count, confirmed, refunded.
  • Refund (top-right) — initiates an on-chain refund to the source address (BTC/ETH only in v1.0). Requires admin password confirmation.
  • Manually mark confirmed — escape hatch for chains without RPC support. Use sparingly; logged in audit.

Customers /admin/customers

Auto-created from the email a customer enters at checkout.

  • Show page lists all transactions for that customer, lifetime spend, and merchant-private notes.
  • Notes — internal-only, never shown to the customer. Searchable in the index.
  • VAT number — recorded for invoice generation if the merchant operates in EU.

Checkouts /admin/checkouts

Reusable payment links / widget configurations.

  • Title, description, image — what the customer sees.
  • Price — fixed amount, or "pay what you want" (leave blank), or "fixed currency input".
  • Allowed cryptocurrencies — whitelist. Empty = all enabled.
  • Custom fields — add text/email/phone/select/checkbox fields to capture extra info at checkout.
  • Branding overrides — button text, success/failure copy, optional QR toggle.
  • Layoutinline or popup.
  • Hosted link — every checkout gets a public URL: https://<your-domain>/c/<slug>. Copy and share.

Settings /admin/settings

Tabbed. Changes are live — no deploy needed.

TabWhat's there
GeneralProduct name, tagline, support email, default fiat currency.
BrandingLogo, favicon, colours, custom CSS / JS.
MailSMTP / Resend, FROM address. Test-send button.
NotificationsAdmin recipients, alerts for failed logins, oversize refunds, low-rate-source health.
SecurityForce-HTTPS-on-admin, IP allowlist (CIDR), session timeout, 2FA enforcement.
MaintenanceGlobal checkout pause, custom message, stale-pending cleanup cron.
RatesPreferred source order (Coinbase / Binance / Bitstamp / custom), refresh interval.
GatewaysStripe + PayPal credentials, webhook endpoints, fees.

Users /admin/users

Owner-only by default. Owner can promote others to:

  • Admin — full management except role changes for other admins.
  • Agent — read-only access; cannot edit or destroy.

Per-user actions (per-row dropdown):

  • Force password reset — flags the user; their next login routes through password change.
  • Re-send invitation — for users whose invite email got lost.
  • Suspend / Reinstate — disables login without deleting; uses locked_until set 1 year ahead.
  • Login as user — admin steps into another user's session for support. A persistent banner appears across the entire UI; click "Return to your account" to swap back.
  • Delete — soft-delete; can be restored from DB.
Owner protection: the owner account cannot be suspended, deleted, demoted, or impersonated from the UI. This prevents an admin from locking the install out.

Widget integration

The ChainPay widget is a standalone JavaScript bundle (~80 kB gzipped) you embed on any website to accept payments. Two layouts:

LayoutWhen to use
inlineReplaces a <div> on your page with the checkout form
popupA button opens a centered modal

1. Get a checkout slug

In admin → Checkouts → "New checkout". Configure price, allowed cryptos, copy. Save. Note the slug (e.g. pro-license-bundle).

2. Drop the script tag

<script src="https://your-chainpay-domain.com/widget.js" defer></script>

3. Add the host element

Inline layout:

<div data-cpg-checkout="pro-license-bundle" data-cpg-layout="inline"></div>

Popup layout:

<button data-cpg-checkout="pro-license-bundle" data-cpg-layout="popup">
    Buy now — $19.99
</button>

4. (Optional) Pre-fill customer info

<div
  data-cpg-checkout="pro-license-bundle"
  data-cpg-layout="inline"
  data-cpg-customer-email="customer@example.com"
  data-cpg-customer-first-name="Ada"
  data-cpg-customer-last-name="Lovelace"
></div>

5. Listen for events

window.addEventListener('chainpay:created', (e) => {
    console.log('Transaction created:', e.detail.reference);
});

window.addEventListener('chainpay:confirmed', (e) => {
    console.log('Payment confirmed:', e.detail);
    // Trigger your success flow — fulfilment, redirect, analytics
});

window.addEventListener('chainpay:failed', (e) => {
    console.warn('Payment failed:', e.detail.status);
});

6. Styling

The widget ships with self-contained CSS (CSS variables, no Tailwind). Override via:

/* Brand colours */
.cpg-form { --cpg-accent: #ec4899; --cpg-accent-2: #f43f5e; }

/* Or override individual classes */
.cpg-submit { border-radius: 0; }

Hosted checkout

If you don't want to embed the widget, every checkout you create automatically gets a public URL you can share by email, link, QR code — anywhere.

https://<your-domain>/c/<slug>

Example: https://chainpay.codic.online/c/demo-product

What the page looks like

A polished, single-purpose checkout that mounts the same widget as the embed:

  • Branding header (logo, product name, tagline)
  • Product image (if uploaded)
  • The full widget form (price, crypto picker, custom fields, email)
  • "Powered by" footer

After the customer submits, they're redirected to the Pay page (/pay/{reference}) which shows the QR, address, countdown, and confirmation rings.

Use cases

ScenarioHow
Email an invoicePaste the hosted link
QR code on a printed receiptGenerate a QR pointing at the hosted URL
Twitter / IG bio linkUse the URL directly
Donation pageMake a checkout with accepts_currency_input=true
Multi-tier pricingCreate 3 checkouts, share three links

REST API

ChainPay exposes a small REST API for programmatic access. Two surfaces:

  1. Public widget API — unauthenticated, what widget.js itself calls.
  2. Authenticated API — Sanctum bearer tokens for server-to-server use.

Authentication

Generate a token in admin → Settings → API tokens (or via tinker):

$user = App\Models\User::find(1);
$token = $user->createToken('cli', ['transactions:read', 'transactions:refund'])->plainTextToken;

Use it:

Authorization: Bearer <token>

Token scopes (abilities)

AbilityWhat it allows
transactions:readList + show transactions
transactions:refundIssue on-chain refunds
customers:readList + show customers
checkouts:readList checkouts
wallets:readList wallet addresses (no private keys)

Public widget endpoints

GET /api/checkouts/{slug}

Returns the checkout config + active gateways + active cryptos for the widget to render.

{
  "checkout": {
    "reference": "01j8a2...",
    "slug": "demo-product",
    "title": "ChainPay Demo Product",
    "price_minor": 1999,
    "currency_code": "USD",
    "show_qr": true,
    "button_text": null,
    "custom_fields": []
  },
  "options": {
    "cryptocurrencies": [
      { "code": "btc", "name": "Bitcoin", "ticker": "BTC", "decimals": 8, "network": "bitcoin" }
    ],
    "gateways": ["crypto", "stripe"]
  }
}

POST /api/checkouts/{slug}/sessions

{
  "gateway": "crypto",
  "cryptocurrency_code": "btc",
  "customer": {
    "email": "buyer@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace"
  },
  "custom_fields": { "company": "Acme Inc" },
  "amount_fiat_minor": 1999
}

Returns { "redirect_url": "/pay/01k..." }.

GET /pay/{reference}/status

Polled every 5s by the Pay page. Returns the transaction snapshot WITHOUT the QR SVG (kept lean).

Rate limiting

Public widget endpoints are throttled at 60 requests/minute per IP. Authenticated endpoints at 120 req/min per token.

Webhooks

ChainPay POSTs to your endpoint when a transaction's state changes. Use this to fulfil orders, send receipts, update your CRM.

Events

EventWhen
transaction.createdA new payment session was created
transaction.payment_seenThe blockchain mempool has the inbound transfer
transaction.confirmingFirst confirmation reached
transaction.confirmedMin confirmations reached — money is yours
transaction.expiredWindow closed without payment
transaction.underpaidCustomer sent less than required
transaction.refundedRefund issued
transaction.failedGeneric failure

Signature verification

Every request carries an X-ChainPay-Signature header — an HMAC-SHA256 of the raw body using your signing secret.

$signature = $_SERVER['HTTP_X_CHAINPAY_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, $secret);

if (! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('invalid signature');
}

$payload = json_decode($body, true);
// process the event...
http_response_code(200);
Use hash_equals(), NOT == or ===. Constant-time comparison prevents timing attacks.

Retries

If your endpoint returns anything other than 2xx, ChainPay retries with exponential backoff: 30s → 2 min → 10 min → 1 hour → 6 hours, then gives up.

Blockchain coverage

ChainPay ships drivers for the most-asked-for chains. Coverage breaks into three tiers based on what each driver can do.

Tier 1 — Full custody (deposit + refund + monitoring)

ChainCoinNotes
BitcoinBTCNative + SegWit (bech32). Tiered fees. Source-address refund.
EthereumETHEIP-155 signing. ERC-20s (USDT, USDC, LINK, SHIB, BAT). EIP-1559 fee fallback.
BNB Smart ChainBNB, BUSD, USDT-BEP20EVM-compatible — same code path as Ethereum, different chain ID.

Tier 2 — Auto-confirm (deposit + monitoring, manual refund)

These chains auto-confirm customer payments end-to-end via their explorer/RPC integrations. The only thing they don't do is push outgoing refunds — refunds happen manually from the merchant's own wallet (or via the v1.1 send-side update).

ChainCoinAuto-confirmNotes
SolanaSOL, SPL tokensSolana JSON-RPC (getSignaturesForAddress). Use Alchemy/Helius for production.
TronTRX, USDT-TRC20TronGrid public API.
XRP LedgerXRPXRPL public RPC.
AlgorandALGOAlgorand Indexer.
LitecoinLTCSame UTXO matcher as BTC.
DogecoinDOGEVia Blockchair.
Bitcoin CashBCHVia Blockchair.
MoneroXMROpt-in only — requires view-key on the wallet for incoming detection.

On-chain refund for these chains is deferred to v1.1. Until then, the merchant initiates refunds manually from their own wallet, then marks the ChainPay transaction refunded in admin.

Tier 3 — Skeleton

ChainCoinStatus
Bitcoin LightningBTC-LNBOLT11 invoices generate; LND/CLN integration in v1.1

Confirmation thresholds

ChainDefault min_confirmations~Time
Bitcoin2~20 min
Ethereum12~3 min
BSC15~45 sec
Tron19~1 min
Solana32~13 sec
Litecoin4~10 min
Dogecoin6~6 min
XRP1~3-5 sec

Stripe + PayPal

In addition to crypto, ChainPay can route payments through traditional rails.

Stripe

  1. Create a Stripe account.
  2. Get your publishable key and secret key from the Stripe dashboard.
  3. In ChainPay → Settings → Gateways → Stripe, paste keys, pick Live or Test, save.
  4. Configure the webhook endpoint in Stripe: URL https://<your-domain>/webhooks/stripe, subscribe to payment_intent.succeeded, payment_intent.payment_failed, charge.refunded.
  5. Copy the signing secret (whsec_…) into ChainPay's webhook field.

Stripe charges per their published rates (e.g. 2.9% + 30¢ in the US). ChainPay does NOT add markup.

PayPal

  1. Create a PayPal Business account.
  2. Create an App in the Developer Dashboard to get a Client ID + Secret.
  3. In ChainPay → Settings → Gateways → PayPal, paste keys, pick Live or Sandbox, save.
  4. Webhook URL: https://<your-domain>/webhooks/paypal — subscribe to PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.REFUNDED.

Test cards (Stripe)

CardBehaviour
4242 4242 4242 4242Successful charge
4000 0000 0000 0002Decline
4000 0027 6000 31843D Secure required

Troubleshooting

The first place to look is storage/logs/laravel.log. Most errors include a stack trace and a request ID.

tail -f storage/logs/laravel.log

Common issues

"500 server error" right after install

  • APP_KEY= empty → run php artisan key:generate
  • storage/ or bootstrap/cache/ not writable → chmod -R 775 and chown to web user
  • DB credentials wrong in .envphp artisan migrate:status surfaces the connection error
  • Cached config out of date → php artisan config:clear && php artisan config:cache

Customer paid but transaction stuck on "pending"

  1. Confirm the wallet address in admin matches the chain (not a typo, not a wrong network).
  2. Look up the transaction hash on the chain's explorer — did it land on chain?
  3. Check the queue worker is running: ps aux | grep "queue:work". The chain monitor runs as a queued job.
  4. If the chain RPC is rate-limiting, you'll see retries in laravel.log. Switch to a paid RPC.

"Crypto picker is empty"

The customer-facing widget only shows cryptos where:

  1. The crypto is is_active=true in the catalogue
  2. At least one active, non-deleted wallet exists for it
  3. The crypto code is in the checkout's allowed_cryptocurrencies whitelist (or whitelist is empty)

Forgetting to add a wallet is the #1 cause.

"Email is not arriving"

  1. Confirm MAIL_* keys in .env are correct
  2. Test: php artisan tinkerMail::raw('test', fn($m) => $m->to('you@x.com')->subject('test'));
  3. If using Resend / Postmark / SES, check the provider's dashboard for bounces / blocks

"I can't log in even with the correct password"

  • users.locked_until — if set in the future, you're suspended. Clear via tinker.
  • force_password_reset_at — if set, you're routed through reset.
  • email_verified_at — if null and verification is enforced, login redirects to verification.

Forgot owner password

php artisan tinker
$u = User::where('role', App\Enums\UserRole::Owner)->first();
$u->password = Hash::make('new-password-here');
$u->save();

Changelog

1.0.0 — 2026-05-05

Initial public release.

Core

  • Multi-chain crypto payment gateway: BTC, ETH, BNB, ERC-20 (USDT/USDC/LINK/SHIB/BAT), BEP-20, TRC-20, TRX, LTC, DOGE, BCH, XRP, SOL, ALGO, XMR, BTC-LN.
  • Custom-token admin: add any ERC-20 / BEP-20 / TRC-20 without code changes.
  • Multi-source rate engine with fallback (Coinbase / Binance / Bitstamp / custom JSON).
  • BIP-21 / EIP-681 wallet-scannable URIs for QR codes.

Widget + checkout

  • Embeddable widget (~80 kB gzipped) — popup + inline layouts.
  • Hosted checkout page at /c/{slug}.
  • Cryptomus-style crypto picker with real coin logos.
  • Live amount-aware submit button.
  • Pay page with twin progress rings (expiration + confirmations).
  • BaconQrCode QR generation, click-to-copy address, polling status updates.
  • MetaMask / Web3 wallet connect (EVM chains).

Admin

  • Dashboard with charts, activity feed, install health strip.
  • Wallets, Transactions, Customers, Checkouts, Custom Tokens, Users, Settings.
  • User management: invite, suspend, "Login as user" impersonation, role changes.
  • Branding, Security, Maintenance, Rates, Gateways tabs.

Fiat gateways

  • Stripe (live + test) with PaymentIntents + webhook validation.
  • PayPal (live + sandbox) with order capture + webhook validation.

Roadmap (v1.1)

  • On-chain refund for Tron, Solana, XRP, Algorand
  • Full Lightning LND/CLN integration
  • EIP-1559 fee model
  • Verifone driver
  • Public API write endpoints
  • Audit log UI