Use Pagible Cashier when you want to sell a subscription, a one-time purchase, or both for pages in your public site. You install one payment provider, assign a named access value to a pricing package, and restrict the pages with that same value. Verified provider webhooks grant, renew, and revoke access automatically.
Paid Frontend Page Access with Stripe, Paddle or Mollie
Setup at a glance
Pagible automates the local integration while you retain control of application code, provider dashboards, products, and CMS content.
Setup responsibilities
Application setup
Before you start
Your application needs PagibleAI CMS, Laravel authentication, a named login route, and a stored Eloquent user. Set APP_KEY and a public HTTPS APP_URL before configuring webhooks. Checkout sends guests to the named login route and resumes after they sign in.
Install one payment provider
Choose exactly one provider package. Composer prevents you from installing competing Pagible or upstream Cashier drivers in the same application.
# Stripe
composer require aimeos/pagible-cashier-stripe
# OR Paddle
composer require aimeos/pagible-cashier-paddle
# OR Mollie
composer require aimeos/pagible-cashier-mollie
The selected package registers its Cashier driver, checkout integration, webhook listeners, migrations, and frontend assets. There is no separate provider setting to maintain.
Configure your user model
Add Pagible's CashierAccess trait and your provider's Billable trait to the application user model. Stripe and Mollie use this model setup:
use Aimeos\Cms\Concerns\CashierAccess;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use CashierAccess;
use Billable;
}
For Paddle, import Laravel\Paddle\Billable instead. Do not add access to $fillable or update users.access yourself. Pagible reserves, hides, casts, and guards this payment-derived authorization data.
Define the access values you sell
Register the values editors may assign to pricing packages and restricted pages. Put a fixed catalog in an application service provider for a simple installation:
use Aimeos\Cms\Access;
public function boot(): void
{
Access::using(fn() => [
'course-buyers',
'premium-members',
]);
}
The callback may return different values for each tenant. Use the same exact value in the pricing package and on every page that package unlocks.
If you need editor-managed roles, an authorization package, or custom catalog callbacks, use the more sophisticated setup in Frontend Access Control for PagibleAI CMS.
Payment provider setup
Configure Stripe
APP_URL=https://example.com
STRIPE_KEY=pk_...
STRIPE_SECRET=sk_...
After configuring the public APP_URL, STRIPE_KEY, and STRIPE_SECRET, clear cached configuration and run the installer. Accept the default yes answer when it offers to create the registered webhook. The adapter supplies all required payment, subscription, refund, and dispute events.
php artisan config:clear
php artisan cms:install:cashier
For a non-interactive deployment, request webhook creation explicitly:
php artisan cms:install:cashier --webhook --no-interaction
After Stripe creates the endpoint, retrieve its signing secret from the Stripe dashboard and add it to your environment:
STRIPE_WEBHOOK_SECRET=whsec_...
Reload cached configuration, then confirm that the complete local setup is ready:
php artisan config:clear
php artisan cms:cashier:check
Create recurring and/or one-time Stripe prices in Stripe. Use each price_... ID in the matching CMS price.
Configure Paddle
PADDLE_CLIENT_SIDE_TOKEN=...
PADDLE_API_KEY=...
PADDLE_WEBHOOK_SECRET=...
# Use a Paddle sandbox account locally or in staging
PADDLE_SANDBOX=true
Create a public notification destination for https://example.com/paddle/webhook. Include Cashier's normal event set and make sure it contains transaction.completed, subscription.created, subscription.canceled, adjustment.created, and adjustment.updated. Copy the destination's endpoint secret into PADDLE_WEBHOOK_SECRET. Create recurring and/or one-time Paddle prices and use their pri_... IDs in the CMS.
Configure Mollie
# Test account
MOLLIE_KEY=test_...
# Use a live key in production
# MOLLIE_KEY=live_...
Enable the payment methods you want to offer in Mollie. Pagible derives opaque webhook route segments from APP_KEY; keep all generated Cashier Mollie webhook routes public. Run Laravel's scheduler continuously because the adapter schedules cashier:run every five minutes for subscription renewals. If you rotate APP_KEY, keep the previous key in APP_PREVIOUS_KEYS until old subscriptions and webhook retries have ended.
Complete the local installation
Run the installer once after the user model, access catalog, and initial provider configuration are ready. If you already followed the Stripe sequence above, this step is complete:
php artisan config:clear
php artisan cms:install:cashier
cms:install:cashier detects the installed provider, checks that users.access is not owned by your application, publishes the provider and adapter migrations, and runs all outstanding migrations by default. Use --no-migrate only when your deployment pipeline runs migrations separately, and add --force when production migrations must run non-interactively.
After adding any secret returned by the provider dashboard, reload configuration and verify readiness:
php artisan config:clear
php artisan cms:cashier:check
The readiness check verifies everything it can inspect locally, including routes, model traits, migrations, the access catalog, API credentials, and webhook secrets. Its Developer next steps section lists dashboard work and purchase-flow tests that still require you.
Sell access to pages and files
Add subscriptions and one-time prices
Add a pricing element to a public sales page. Create a package such as Membership, select premium-members as its frontend access value, and add up to five prices. You can offer a subscription, a one-time purchase, or both.
Provider price fields
price_...subscriptiononcepri_...subscriptiononce19.000 interval; type onceFor every provider, use a three-letter uppercase currency code. Stripe and Paddle ignore the CMS billing interval because their price object controls recurrence. The amount, displayed price, and unit fields control the pricing display; the provider reference remains authoritative for the charge. Set the package target to a local URL beginning with /, preferably a public account or payment-status page, then publish the pricing content.
Restrict the pages you sell
- Open the page being sold in the CMS admin.
- Choose Access and select Restricted.
- Add the same access value used by the pricing package, such as
premium-members. - Apply the change. Apply it recursively when the page's descendants should require the same purchase.
- Keep the sales and return pages public.
The access change requires page:publish and access:view. A verified one-time payment grants permanent access until a full refund or chargeback. A subscription grants access through its provider-confirmed paid period. Scheduled cancellation keeps access until that paid period ends.
Protect files on restricted pages
Pagible configures the private file disk as Laravel's non-public local disk by default. Enable Protect with page access for each paid download. Set CMS_PRIVATE_DISK only when you want to use another non-public Laravel disk; it must be different from the public disk. Pagible delivers local private files through its access-controlled route and uses temporary URLs for supported remote private disks.
Bundled themes generate page-aware protected URLs automatically. In a custom Blade template, use cmsasset($page, $file) so Pagible can authorize the file against its page. Do not use cmsurl() for a protected file.
Test before accepting live payments
Complete the whole flow in the provider's test or sandbox environment:
- A guest is sent to your named login route and checkout resumes after sign-in.
- A subscription webhook grants access and a paid user can open the restricted page.
- A one-time payment grants access.
- Cancellation preserves access through the paid period.
- A full refund and a dispute or chargeback revoke access.
- An unauthorized user cannot open protected pages or private files.
- Failed webhook processing returns a non-success response and the provider retries it.
Monitor failed and exhausted webhook deliveries in production. Provider retries are the recovery path when synchronous access updates fail.
Troubleshooting
The installer reports that users.access already exists
Pagible Cashier reserves users.access for payment-derived grants. If the column was not created by the Pagible Cashier migration, rename or remove the application-owned column before migrating. Do not force the installer past this ownership check.
The readiness check still reports missing configuration
Run php artisan config:clear, then run php artisan cms:cashier:check again. Follow each failed check for the user-model traits, APP_KEY, APP_URL, named login route, database migrations, access catalog, provider credentials, and webhook secret. The Developer next steps list contains the remaining dashboard and test tasks.
Stripe did not create the webhook
Configure an absolute public APP_URL, STRIPE_KEY, and STRIPE_SECRET, clear cached configuration, and confirm the Cashier webhook route is registered. Then run php artisan cms:install:cashier --webhook. Retrieve the endpoint signing secret from Stripe and set STRIPE_WEBHOOK_SECRET.
Checkout does not start or reports an invalid price
Publish the pricing page or shared pricing element. Confirm that the package access value exists in the active tenant's access catalog, the target URL begins with /, and the selected price has the correct payment type, currency, and provider reference. Stripe references start with price_; Paddle references start with pri_; Mollie references are decimal amounts such as 19.00.
Payment succeeded but the restricted page still returns 403
Access is granted only after a verified webhook succeeds. Check the provider delivery and application log, fix any signing-secret or processing error, and let the provider retry or replay the event. Confirm that the pricing package and restricted page use the same exact access value and tenant. Do not write users.access manually.
Paddle payments, refunds, or chargebacks do not update access
Confirm that /paddle/webhook is public, PADDLE_WEBHOOK_SECRET matches the notification destination, and the destination includes transaction.completed, subscription.created, subscription.canceled, adjustment.created, and adjustment.updated.
Mollie subscriptions do not renew
Run Laravel's scheduler continuously. Pagible schedules cashier:run every five minutes to create renewal payments. Also confirm that MOLLIE_KEY is valid and the generated opaque Mollie webhook routes remain public.
Cancellation did not remove access immediately
A scheduled cancellation keeps access through the provider-confirmed paid period. Access ends when that period expires. Provider-confirmed immediate cancellation, subscription deletion, full refund, or chargeback revokes the affected grant.
A protected file is public or cannot be opened
Enable Protect with page access for the file and keep the private disk different from the public disk. In custom themes, generate the URL with cmsasset($page, $file), not cmsurl(). The user must be able to open the published page that contains the file.
Provider references
Use these package guides when you need implementation details: