1. Go to this page and download the library: Download square1/laravel-mpp library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
square1 / laravel-mpp example snippets
Route::get('/resource', MyPaidResource::class)
->middleware('mpp:0.50,USD');
#[RequiresPayment(amount: '5.00', currency: 'USD', grants: 10, scope: 'report.basic')]
public function report()
{
// One $5 payment grants 10 accesses.
}
use Illuminate\Support\Facades\Route;
Route::get('/resource', fn () => response()->json(['result' => 'SOME_DATA']))
->middleware('mpp:1.00,USD');
namespace App\Mpp;
use Illuminate\Http\Request;
class StripeCustomerResolver
{
public function resolve(Request $request): ?string
{
return $request->user()?->stripe_customer_id;
}
}
use Square1\Mpp\Attributes\DiscoveryInfo;
use Square1\Mpp\Attributes\RequiresPayment;
#[RequiresPayment(amount: '0.50', currency: 'USD', methods: ['tempo', 'stripe'])]
#[DiscoveryInfo(
summary: 'Clip a video',
description: 'Returns a clip of the source video, starting at the requested offset.',
priceNote: ['tempo' => 'On-chain, per clip.', 'stripe' => 'Card, per clip.'],
tags: ['media'],
request: ClipRequest::class,
response: ['type' => 'object', '
Route::post('/clip', fn () => /* … */)
->middleware('mpp:0.50,USD')
->discovery(summary: 'Clip a video', priceNote: 'Per clip, whatever its length.');
use Square1\Mpp\Discovery\ProvidesSchema;
final class ScoreSchema implements ProvidesSchema
{
public static function schema(): array
{
return [
'type' => 'object',
'
final class ClipResult
{
/** @param list<Scoreline> $scorelines */
public function __construct(
public readonly string $url,
public readonly ClipFormat $format, // a backed enum
public readonly ClipSource $source, // another data object
public readonly ?string $session, //
use Square1\Mpp\Attributes\RequiresPayment;
class ReportController
{
#[RequiresPayment(amount: '5.00', currency: 'USD', grants: 10, scope: 'report.basic')]
public function __invoke()
{
// ...
}
}
Route::get('/report', ReportController::class)->middleware('mpp');
#[RequiresPayment(amount: '0.50', currency: 'USD')]
public function latest()
{
// ...
}
Route::get('/report', ReportController::class)
->middleware('mpp:scope=report');
#[RequiresPayment(scope: 'resource')]
public function latest()
{
// Amount, currency, grants, and method come from config.
}
// config/mpp.php
'pricing' => [
'resolvers' => [
'tiered' => [\App\Mpp\Pricing\TieredPrice::class, 'price'],
],
// Apply to every gated route, before any route-specific resolvers.
'global' => [],
],
namespace App\Mpp\Pricing;
use Illuminate\Http\Request;
use Square1\Mpp\Payment\PaymentSpec;
class TieredPrice
{
/** @return array<string, mixed>|null */
public function price(Request $request, PaymentSpec $spec): ?array
{
return match ($request->user()?->tier) {
'pro' => ['amount' => '2.00'],
'partner' => ['amount' => '2.00', 'grants' => 20, 'scope' => 'report.partner'],
'staff' => ['free' => true],
default => null, // leave the route's own price alone
};
}
}
// $5 is the LIST PRICE. A caller pays it when `tiered` returns null.
// Recognised tiers get a discount off it.
Route::get('/report', ReportController::class)
->middleware('mpp:5.00,USD,scope=report,pricing=tiered');
#[RequiresPayment(amount: '5.00', scope: 'report', pricing: ['tiered'])]
public function show() { /* ... */ }
// Or on a price_book entry, so every route using the entry inherits it.
'price_book' => [
'report.basic' => ['amount' => '5.00', 'currency' => 'USD', 'pricing' => ['tiered']],
],
// The route owns the default price. The resolver can overwrite it.
->middleware('mpp:5.00,USD,scope=report,pricing=tiered');
// The resolver owns the price.
->middleware('mpp:scope=report,pricing=tiered');
return ['free' => true]; // yes, serve this one for nothing
return ['amount' => '0']; // throws
public function price(Request $request, PaymentSpec $spec): ?array
{
return match ($request->user()?->tier) {
// Only the price. Every other value on the route stays.
'pro' => ['amount' => '2.00'],
// Price, currency, bundle size and credit pool, all at once.
'partner' => [
'amount' => '18.00',
'currency' => 'EUR',
'grants' => 25,
'scope' => 'everything.partner',
],
// No charge. The same method, and the same return type. `free` is
// another key. Do not add an 'amount' beside it, or the pair throws.
'staff' => ['free' => true],
// No opinion. This is NOT free: the price of the route stays.
default => null,
};
}
// config/mpp.php
'preconditions' => [
'checks' => [
'postexists' => [\App\Mpp\Checks\PostExists::class, 'check'],
'usernotblocked' => [\App\Mpp\Checks\UserNotBlocked::class, 'check'],
],
// Run on every gated route, before any route-specific checks.
'global' => ['usernotblocked'],
],
namespace App\Mpp\Checks;
use App\Models\Post;
use Illuminate\Http\Request;
use Square1\Mpp\Payment\PaymentSpec;
use Symfony\Component\HttpFoundation\Response;
class PostExists
{
public function check(Request $request, PaymentSpec $spec): ?Response
{
return Post::find($request->route('post'))
? null
: response()->json(['error' => 'No such post.'], 404);
}
}
Route::get('/posts/{post}', ShowPost::class)
->middleware('mpp:1.00,USD,scope=post.view,preconditions=postexists');
#[RequiresPayment(amount: '1.00', scope: 'post.view', preconditions: ['postexists'])]
public function show() { /* ... */ }