A pay-per-view URL shortener — guests and members shorten links, each visit passes through a 5-second interstitial ad page with human verification, and link owners earn a configurable CPM on every valid view. Built on Laravel 12 with a Blade + Tailwind storefront and a full Filament 3 admin panel.
This is an adf.ly-style link monetization platform. A user shortens a long URL; anyone who opens the short link lands on an interstitial page that shows ad slots and a 5-second countdown gated by a captcha. When the visit clears verification — captcha passed, not a duplicate from the same IP within the dedup window, and not the owner clicking their own link — the link owner is credited at the configured per-1000-views rate. Earnings accumulate in a wallet that users can cash out through MoMo, ZaloPay, or PayPal, with payouts approved manually by an admin.
The public-facing app (shorten form, dashboard, interstitial, payouts, referrals, leaderboard) is built with plain Blade controllers and views; all back-office administration runs on a separate Filament 3 panel at /admin.
- Shorten URLs with a random slug or a custom alias, optional password protection, and a guest mode (anonymous shorten from the landing page, rate-limited to 10/min)
- Bulk create, clone, CSV export, per-link stats, and QR code generation (
endroid/qr-code) - Public REST API —
POST /api/v1/shortenauthenticated with per-user API tokens that members manage from their dashboard
- 5-second interstitial ad page with ad slots served from admin-managed ad campaigns (impressions and clicks are tracked)
- Human verification via Cloudflare Turnstile, with a graceful fallback to a custom "I'm not a robot" checkbox when no Turnstile keys are configured (demo/dev mode)
- IP deduplication over a configurable window (
ip_dedup_hours, default 24h) and anti-self-click protection so owners can't farm their own links - Configurable CPM earnings — owners are credited
rate_per_1000_views / 1000per valid view (default rate 5000, editable in admin settings) - Referral commissions — a configurable percentage (
referral_rate_percent, default 10%) of a referred user's earnings is credited to the referrer
- Wallet with transaction ledger; payout requests via MoMo, ZaloPay, or PayPal, with balance held on request and refunded on rejection
- User dashboard with KPI cards and click/earnings charts
- Premium upgrade, promo codes, and a public leaderboard of top earners
- Authentication via email/password (Laravel Breeze) and Google OAuth (Laravel Socialite)
- Multi-language UI (Vietnamese + English) with a cookie-based locale switcher
- CRUD resources for users, roles, short links, ad campaigns, payout requests, settings, blacklisted domains, tags, promo codes, announcements, FAQs & FAQ categories, email templates, captcha questions, support tickets, reported links, and audit logs (19 Filament resources)
- Dashboard widgets: stats overview, revenue trend, ad performance, user growth, clicks chart, pending payouts, and a top-earners table
- Payout moderation (mark paid with a transaction reference, or reject with a note that refunds the held balance)
| Layer | Technology |
|---|---|
| Language | PHP 8.2+ |
| Framework | Laravel 12 |
| Admin panel | Filament 3 |
| Database | MySQL 8 (MariaDB 10.6+ also works) |
| Frontend | Blade, Tailwind CSS, Alpine.js, Vite |
| Auth | Laravel Breeze (email/password) + Laravel Socialite (Google OAuth) |
| Human verification | Cloudflare Turnstile (with checkbox fallback) |
| Extras | endroid/qr-code (QR codes), blade-ui-kit/blade-heroicons (icons) |
| Testing | Pest 4 |
- PHP 8.2+ and Composer 2.x
- Node.js 20+
- MySQL 8 (or MariaDB 10.6+)
git clone https://github.com/DucMinhNe/URL-Shorten.git
cd URL-Shorten
composer install
npm install
cp .env.example .env
php artisan key:generate
# Create the database, then set DB_* in .env
mysql -u root -e "CREATE DATABASE url_shorten CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# Run migrations and seed demo data
php artisan migrate --seed
# Build front-end assets
npm run build
# Serve
php artisan serveThe app is served at http://localhost:8000; the admin panel lives at http://localhost:8000/admin.
For background work (wallet credits, scheduled tasks) run the queue worker and scheduler:
php artisan queue:work
php artisan schedule:run # or a cron entry: * * * * * php artisan schedule:runGoogle OAuth — create an OAuth client in the Google Cloud Console with redirect URI http://localhost:8000/auth/google/callback, then set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env.
Cloudflare Turnstile — the app accepts the checkbox fallback when unconfigured. To enable real verification, register at Cloudflare Turnstile and set TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY in .env.
app/
├── Filament/
│ ├── Resources/ # 19 admin CRUD resources
│ └── Widgets/ # 7 dashboard widgets
├── Http/Controllers/ # Blade frontend: Home, ShortLink, Redirect,
│ │ # Interstitial, Dashboard, Payout, Referral,
│ │ # Premium, Leaderboard, ApiToken, Profile, Locale
│ └── Api/ShortenController # public REST shorten endpoint
├── Models/ # Eloquent models (ShortLink, Click, AdCampaign,
│ │ # PayoutRequest, WalletTransaction, IpViewLog, …)
├── Services/ # Business logic: ShortLinkService, ClickTrackingService,
│ │ # CaptchaService, AdServingService, WalletService,
│ │ # PayoutService, SettingService
└── Providers/Filament/ # AdminPanelProvider (panel registration)
routes/
├── web.php # storefront + interstitial routes
├── api.php # POST /api/v1/shorten
└── auth.php # Breeze auth routes
resources/views/ # Blade views (home, dashboard, links, payout,
# interstitial, referral, premium, leaderboard, legal)
database/
├── migrations/ # schema
├── factories/
└── seeders/ # demo data
- Shorten — a guest posts to
/shorten(or a member uses/links), the server generates a slug or uses the custom alias and stores the link. - Visit —
GET /{slug}resolves the link (prompting for a password if protected), then renders the interstitial with ad slots, a 5-second countdown, and the captcha. - Verify — after the countdown and captcha,
POST /{slug}/verifyvalidates the captcha, applies IP dedup and anti-self-click checks, credits the owner on a valid view, and returns the destination URL for the client-side redirect. - Cash out — a user submits a payout request from
/payout; an admin approves or rejects it from the Filament panel.