ajooda/laravel-ai-metering

AI usage metering and billing for Laravel apps (tokens, quotas, credits, billing).

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/ajooda/laravel-ai-metering

v1.0.1 2025-12-06 17:49 UTC

This package is auto-updated.

Last update: 2025-12-06 17:50:20 UTC


README

Latest Version Total Downloads License

A production-ready Laravel package for AI usage metering, quotas, and billing integration. Track token usage, enforce limits, and integrate with Stripe for billing.

Repository: GitHub | Packagist

Features

  • 🎯 Simple Developer Experience - Fluent API for metering AI usage
  • 📊 Usage Tracking - Automatic token and cost tracking
  • 🚦 Quota Management - Configurable limits (tokens, cost, per-plan, per-tenant)
  • 💳 Billing Integration - Stripe/Cashier support with credit-based and subscription modes
  • 🏢 Multi-Tenancy - Works with or without tenancy packages
  • 📈 Dashboard - Built-in dashboard for usage monitoring
  • 🔌 Provider Agnostic - Support for OpenAI, Anthropic, and custom providers
  • Performance - Caching, queue support, and optimized queries

Requirements

  • PHP >= 8.1
  • Laravel 10.x, 11.x, or 12.x
  • Database (MySQL, PostgreSQL, SQLite, or SQL Server) - configured in your Laravel application

Optional Dependencies

For AI provider integration, you'll need the respective SDK packages:

  • OpenAI: openai-php/laravel (for OpenAI provider)
  • Anthropic: anthropic-php/sdk (for Anthropic provider)
  • Stripe/Cashier: laravel/cashier (for Stripe billing integration)
# For OpenAI
composer require openai-php/laravel

# For Anthropic
composer require anthropic-php/sdk

# For Stripe billing
composer require laravel/cashier

Installation

Install the package via Composer:

composer require ajooda/laravel-ai-metering

Publish the configuration file:

php artisan vendor:publish --tag=ai-metering-config

Publish and run migrations:

php artisan vendor:publish --tag=ai-metering-migrations
php artisan migrate

Note: Make sure your database connection is configured in .env before running migrations. The package will use your default database connection unless specified otherwise via AI_METERING_DB_CONNECTION.

Environment Variables

The package supports various environment variables for configuration. Add these to your .env file:

# Default Provider
AI_METERING_DEFAULT_PROVIDER=openai

# Billing Configuration
AI_METERING_BILLING_DRIVER=Metrix\AiMetering\Services\Billing\NullBillingDriver
AI_METERING_OVERAGE_BEHAVIOR=block
AI_METERING_OVERAGE_SYNC_STRATEGY=batch
AI_METERING_CREDIT_OVERDRAFT=false
AI_METERING_CURRENCY=usd
AI_METERING_PAYMENT_FAILURE_GRACE_PERIOD=7

# Tenant Resolver (optional)
AI_METERING_TENANT_RESOLVER=Metrix\AiMetering\Resolvers\NullTenantResolver

# Period Configuration
AI_METERING_PERIOD_TYPE=monthly
AI_METERING_PERIOD_ALIGNMENT=calendar
AI_METERING_TIMEZONE=UTC

# Storage Configuration
AI_METERING_PRUNE_AFTER_DAYS=365
AI_METERING_DB_CONNECTION=

# Performance Configuration
AI_METERING_CACHE_LIMIT_CHECKS=true
AI_METERING_CACHE_TTL=300
AI_METERING_QUEUE_RECORDING=false
AI_METERING_BATCH_SIZE=100

# Dashboard Configuration
AI_METERING_DASHBOARD_ENABLED=true
AI_METERING_DASHBOARD_PREFIX=ai-metering
AI_METERING_DASHBOARD_GATE=

# Security Configuration
AI_METERING_VALIDATE_FEATURES=true
AI_METERING_SANITIZE_METADATA=true
AI_METERING_RATE_LIMIT=false
AI_METERING_PREVENT_RACE_CONDITIONS=true

# Logging Configuration
AI_METERING_LOGGING_ENABLED=true
AI_METERING_LOG_LEVEL=info
AI_METERING_LOG_FAILURES=true

# Feature Flags
AI_METERING_SOFT_DELETES=false
AI_METERING_AGGREGATION_TABLES=false

Note: All environment variables are optional and have sensible defaults. You only need to set them if you want to override the default values.

Quick Start

After installation, you can start using the package immediately:

  1. Create a plan (optional, for quota management):
use Metrix\AiMetering\Models\AiPlan;

$plan = AiPlan::create([
    'name' => 'Basic Plan',
    'slug' => 'basic',
    'monthly_token_limit' => 100000,
    'monthly_cost_limit' => 10.00,
    'is_active' => true,
]);
  1. Create a subscription (optional, if using plan-based billing):
use Metrix\AiMetering\Models\AiSubscription;

$subscription = AiSubscription::create([
    'billable_type' => User::class,
    'billable_id' => $user->id,
    'ai_plan_id' => $plan->id,
    'billing_mode' => 'plan',
    'started_at' => now(),
    'renews_at' => now()->addMonth(),
]);
  1. Start metering AI usage:
use Metrix\AiMetering\Facades\AiMeter;

$response = AiMeter::forUser($user)
    ->billable($user)
    ->usingProvider('openai', 'gpt-4o-mini')
    ->feature('chat')
    ->call(function () {
        // Your AI provider call here
        return OpenAI::chat()->create([...]);
    });

Tip: If you're not using plans or subscriptions, you can still track usage. The package will work with just the billable entity.

Configuration

The configuration file is located at config/ai-metering.php. Key settings:

Providers

Configure AI providers and their pricing:

'providers' => [
    'openai' => [
        'class' => \Metrix\AiMetering\Services\Providers\OpenAiProvider::class,
        'models' => [
            'gpt-4o-mini' => [
                'input_price_per_1k' => 0.00015,
                'output_price_per_1k' => 0.00060,
            ],
        ],
    ],
],

Billing

Configure billing driver and behavior:

'billing' => [
    'driver' => \Metrix\AiMetering\Services\Billing\CashierBillingDriver::class,
    'overage_behavior' => 'block', // 'block', 'charge', 'allow'
    'overage_sync_strategy' => 'batch', // 'immediate', 'batch'
    'credit_overdraft_allowed' => false,
],

Period Settings

Configure usage period calculation:

'period' => [
    'type' => 'monthly', // 'monthly', 'weekly', 'daily', 'yearly', 'rolling'
    'alignment' => 'calendar', // 'calendar' or 'rolling'
    'timezone' => 'UTC',
],

Basic Usage

Metering AI Usage

Use the AiMeter facade to wrap your AI provider calls. The package works with any AI provider SDK - you just need to wrap your existing calls:

use Metrix\AiMetering\Facades\AiMeter;
use OpenAI\Laravel\Facades\OpenAI; // Requires: composer require openai-php/laravel

$response = AiMeter::forUser(auth()->user())
    ->billable(auth()->user())
    ->usingProvider('openai', 'gpt-4o-mini')
    ->feature('email_reply')
    ->call(function () {
        return OpenAI::chat()->create([
            'model' => 'gpt-4o-mini',
            'messages' => [
                ['role' => 'user', 'content' => 'Write a polite reply...'],
            ],
        ]);
    });

// Access the response
$aiResponse = $response->getResponse();

// Access usage information
$usage = $response->getUsage();
echo "Tokens used: " . $usage->totalTokens;
echo "Cost: $" . $response->getUsage()->totalCost;

// Check limits
if ($response->isApproachingLimit()) {
    // Handle approaching limit
}

Manual Usage Tracking

For providers that don't return usage automatically:

$response = AiMeter::forUser($user)
    ->billable($user)
    ->usingProvider('manual', 'custom-model')
    ->withManualUsage([
        'input_tokens' => 450,
        'output_tokens' => 900,
        'total_tokens' => 1350,
    ])
    ->call(function () use ($client) {
        return $client->doStuff();
    });

With Metadata

Add metadata for better tracking:

$response = AiMeter::forUser($user)
    ->billable($user)
    ->usingProvider('openai', 'gpt-4o-mini')
    ->feature('support_reply')
    ->withMeta([
        'ticket_id' => $ticket->id,
        'customer_id' => $customer->id,
    ])
    ->call(fn () => OpenAI::chat()->create([...]));

Idempotency

Prevent duplicate usage records:

$response = AiMeter::forUser($user)
    ->withIdempotencyKey('unique-request-id-123')
    ->call(fn () => OpenAI::chat()->create([...]));

Plans & Quotas

Creating Plans

use Metrix\AiMetering\Models\AiPlan;

$plan = AiPlan::create([
    'name' => 'Pro Plan',
    'slug' => 'pro',
    'monthly_token_limit' => 1000000,
    'monthly_cost_limit' => 100.00,
    'overage_price_per_1k_tokens' => 0.01,
    'is_active' => true,
]);

Creating Subscriptions

use Metrix\AiMetering\Models\AiSubscription;

$subscription = AiSubscription::create([
    'billable_type' => User::class,
    'billable_id' => $user->id,
    'ai_plan_id' => $plan->id,
    'billing_mode' => 'plan', // or 'credits'
    'started_at' => now(),
    'renews_at' => now()->addMonth(),
]);

Limit Overrides

Override limits for specific periods:

use Metrix\AiMetering\Models\AiUsageLimitOverride;

AiUsageLimitOverride::create([
    'billable_type' => User::class,
    'billable_id' => $user->id,
    'period_start' => now()->startOfMonth(),
    'period_end' => now()->endOfMonth(),
    'token_limit' => 2000000, // Double the plan limit
]);

Credits

Credit-Based Billing

Set up credit wallets:

use Metrix\AiMetering\Models\AiCreditWallet;

$wallet = AiCreditWallet::firstOrCreate(
    [
        'billable_type' => User::class,
        'billable_id' => $user->id,
    ],
    [
        'balance' => 0,
        'currency' => 'usd',
    ]
);

// Add credits
$wallet->addCredits(100.00, 'top-up', ['payment_id' => 'pay_123']);

// Check balance
if ($wallet->hasSufficientBalance(50.00)) {
    // Proceed with usage
}

Using Credits Mode

$response = AiMeter::forUser($user)
    ->billable($user)
    ->billingMode('credits')
    ->usingProvider('openai', 'gpt-4o-mini')
    ->call(fn () => OpenAI::chat()->create([...]));

Multi-Tenancy

The package supports multi-tenancy without coupling to a specific package.

Custom Tenant Resolver

Create a tenant resolver:

namespace App\Resolvers;

use Metrix\AiMetering\Contracts\TenantResolver;

class CustomTenantResolver implements TenantResolver
{
    public function resolve(mixed $context = null): mixed
    {
        // Return the current tenant
        return tenant(); // or your tenancy package's method
    }
}

Register it in config/ai-metering.php:

'tenant_resolver' => \App\Resolvers\CustomTenantResolver::class,

Using with Tenants

$response = AiMeter::forUser($user)
    ->forTenant($tenant)
    ->billable($tenant) // Bill the tenant, not the user
    ->usingProvider('openai', 'gpt-4o-mini')
    ->call(fn () => OpenAI::chat()->create([...]));

Billing Integrations

Stripe/Cashier Integration

The package integrates with Laravel Cashier for Stripe billing.

  1. Install Cashier:
composer require laravel/cashier
  1. Configure your billable model:
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}
  1. Set billing driver in config:
'billing' => [
    'driver' => \Metrix\AiMetering\Services\Billing\CashierBillingDriver::class,
],

Overage Sync

Overage charges can be synced to Stripe:

Immediate sync:

'overage_sync_strategy' => 'immediate',

Batch sync (recommended for high volume):

'overage_sync_strategy' => 'batch',

Then run the sync command:

php artisan ai-metering:sync-stripe-overages

Middleware

Protect routes with quota enforcement:

use Illuminate\Support\Facades\Route;

Route::middleware(['auth', 'ai.quota'])->group(function () {
    Route::post('/ai/chat', [AiController::class, 'chat']);
});

The middleware:

  • Checks usage limits before allowing the request
  • Returns 429 (Too Many Requests) if limit exceeded
  • Adds response headers: X-Remaining-Tokens, X-Remaining-Cost, X-Usage-Percentage

Dashboard

Access the dashboard at /ai-metering (configurable):

  • View usage statistics
  • Monitor quotas and limits
  • See breakdown by provider/model
  • Filter and search usage history

Customizing Dashboard Access

Configure in config/ai-metering.php:

'dashboard' => [
    'enabled' => true,
    'prefix' => 'ai-metering',
    'middleware' => ['web', 'auth'],
    'gate' => 'viewAiMetering', // Optional gate for authorization
],

Artisan Commands

Usage Report

php artisan ai-metering:report
php artisan ai-metering:report --month=2024-01
php artisan ai-metering:report --billable-type="App\Models\User" --billable=1

Cleanup Old Usage

php artisan ai-metering:cleanup
php artisan ai-metering:cleanup --days=90
php artisan ai-metering:cleanup --dry-run

Sync Stripe Overages

php artisan ai-metering:sync-stripe-overages
php artisan ai-metering:sync-stripe-overages --limit=50

Validate Configuration

php artisan ai-metering:validate

Migrate Plans

php artisan ai-metering:migrate-plan "App\Models\User" 1 "pro-plan"
php artisan ai-metering:migrate-plan "App\Models\User" 1 "pro-plan" --from-plan="basic-plan"

Sync Plans

php artisan ai-metering:sync-plans

List all active plans in the system.

Aggregate Usage

php artisan ai-metering:aggregate
php artisan ai-metering:aggregate --period=month
php artisan ai-metering:aggregate --period=week --billable-type="App\Models\User"

Pre-computes usage aggregates for dashboard performance (requires aggregation_tables feature enabled in config).

Querying Usage

Use Eloquent scopes to query usage records:

use Metrix\AiMetering\Models\AiUsage;
use Carbon\Carbon;

// Query by billable entity
$usage = AiUsage::forBillable($user)->get();

// Query by provider
$usage = AiUsage::byProvider('openai')->get();

// Query by model
$usage = AiUsage::byModel('gpt-4o-mini')->get();

// Query by feature
$usage = AiUsage::byFeature('support_reply')->get();

// Query within a period
$start = Carbon::now()->startOfMonth();
$end = Carbon::now()->endOfMonth();
$usage = AiUsage::inPeriod($start, $end)->get();

// Combine scopes
$usage = AiUsage::forBillable($user)
    ->byProvider('openai')
    ->byFeature('email_reply')
    ->inPeriod($start, $end)
    ->get();

Plan Lifecycle

Plan Changes

When a user changes plans mid-period:

use Metrix\AiMetering\Models\AiSubscription;

$subscription = AiSubscription::where('billable_id', $user->id)->first();

// Update to new plan
$subscription->update([
    'ai_plan_id' => $newPlan->id,
    'previous_plan_id' => $oldPlan->id, // Track previous plan
]);

// Usage before plan change counts against old plan
// Usage after plan change counts against new plan

Subscription Expiration

Check subscription status:

$subscription = AiSubscription::where('billable_id', $user->id)->first();

if ($subscription->isActive()) {
    // Subscription is active
}

if ($subscription->isExpired()) {
    // Subscription has expired
}

if ($subscription->isInGracePeriod()) {
    // Subscription is in grace period (still active)
}

if ($subscription->isInTrial()) {
    // Subscription is in trial period
}

Grace Periods

Configure grace periods in subscriptions:

$subscription = AiSubscription::create([
    'billable_type' => User::class,
    'billable_id' => $user->id,
    'ai_plan_id' => $plan->id,
    'ends_at' => now()->subDay(), // Expired yesterday
    'grace_period_ends_at' => now()->addDays(7), // Grace period for 7 days
]);

During grace period, the subscription is still considered active and limits apply.

Period Configuration

The package supports various period types and alignments:

Period Types

  • monthly: Calendar month (1st to last day) or rolling 30 days
  • weekly: Calendar week (Monday-Sunday) or rolling 7 days
  • daily: Calendar day (00:00-23:59) or rolling 24 hours
  • yearly: Calendar year or rolling 365 days
  • rolling: Rolling period from subscription start

Period Alignment

  • calendar: Period aligns to calendar boundaries (e.g., 1st of month)
  • rolling: Period starts from a specific date and rolls forward

Using Period Class

use Metrix\AiMetering\Support\Period;

// Create period from config
$period = Period::fromConfig(config('ai-metering.period'));

// Get period start/end
$start = $period->getStart(); // Current period start
$end = $period->getEnd(); // Current period end (exclusive)

// Check if date is in period
if ($period->contains(Carbon::now())) {
    // Date is within current period
}

// Get next/previous periods
$nextPeriod = $period->getNext();
$previousPeriod = $period->getPrevious();

Events

Listen to usage events:

use Metrix\AiMetering\Events\AiUsageRecorded;
use Metrix\AiMetering\Events\AiLimitApproaching;
use Metrix\AiMetering\Events\AiLimitReached;

Event::listen(AiUsageRecorded::class, function ($event) {
    // Handle usage recorded
});

Event::listen(AiLimitApproaching::class, function ($event) {
    // Send notification when approaching limit
});

Event::listen(AiLimitReached::class, function ($event) {
    // Handle limit reached
});

Available events:

  • AiUsageRecorded - When usage is recorded
  • AiLimitApproaching - When usage exceeds 80% of limit
  • AiLimitReached - When hard limit is reached
  • AiProviderCallFailed - When provider call fails
  • AiOverageCharged - When overage is charged
  • AiPlanChanged - When plan changes
  • AiCreditsAdded - When credits are added
  • AiCreditsDeducted - When credits are deducted
  • AiSubscriptionExpired - When subscription expires

Extending Providers

Create custom provider implementations:

namespace App\Providers;

use Metrix\AiMetering\Contracts\ProviderClient;
use Metrix\AiMetering\Support\ProviderUsage;

class CustomProvider implements ProviderClient
{
    public function call(callable $callback): array
    {
        $response = $callback();
        
        // Extract usage from response
        $usage = new ProviderUsage(
            inputTokens: $response->input_tokens,
            outputTokens: $response->output_tokens,
            totalTokens: $response->total_tokens,
            totalCost: $response->cost,
        );
        
        return [
            'response' => $response,
            'usage' => $usage,
        ];
    }
}

Register in config:

'providers' => [
    'custom' => [
        'class' => \App\Providers\CustomProvider::class,
    ],
],

Performance

Caching

Limit checks are cached by default:

'performance' => [
    'cache_limit_checks' => true,
    'cache_ttl' => 300, // seconds
],

Queue Support

Record usage asynchronously for better performance:

'performance' => [
    'queue_usage_recording' => 'default', // Queue name or false
],

Important: If you enable queue recording, make sure your queue worker is running:

php artisan queue:work

Or use a process manager like Supervisor for production environments. See Laravel Queue Documentation for more details.

Batch Recording

Record multiple usages efficiently:

use Metrix\AiMetering\Services\UsageRecorder;

$recorder = app(UsageRecorder::class);

$usages = [
    [
        'billable_type' => User::class,
        'billable_id' => $user->id,
        'provider' => 'openai',
        'model' => 'gpt-4o-mini',
        'total_tokens' => 100,
        'total_cost' => 0.1,
        'occurred_at' => now(),
    ],
    // ... more usage records
];

$recorded = $recorder->recordBatch($usages);

Security Best Practices

Dashboard Authorization

Protect dashboard access with Gates:

// In AuthServiceProvider
Gate::define('viewAiMetering', function ($user) {
    return $user->isAdmin(); // Your authorization logic
});

Configure in config/ai-metering.php:

'dashboard' => [
    'gate' => 'viewAiMetering', // Gate name for authorization
],

Input Validation

The package validates:

  • Feature names (alphanumeric + underscore)
  • Token/cost values (non-negative)
  • Metadata sanitization

Always validate user input before passing to AiMeter:

$validated = $request->validate([
    'feature' => 'required|string|max:100|regex:/^[a-zA-Z0-9_]+$/',
]);

Data Privacy (GDPR)

Delete all usage for a user:

use Metrix\AiMetering\Models\AiUsage;

// Delete all usage records
AiUsage::where('billable_type', User::class)
    ->where('billable_id', $user->id)
    ->delete();

// Or delete by user_id
AiUsage::where('user_id', $user->id)->delete();

Anonymize old usage data:

AiUsage::where('occurred_at', '<', now()->subYears(2))
    ->update([
        'billable_type' => null,
        'billable_id' => null,
        'user_id' => null,
        'meta' => null,
    ]);

Error Handling

The package throws specific exceptions:

use Metrix\AiMetering\Exceptions\AiLimitExceededException;
use Metrix\AiMetering\Exceptions\AiCreditsInsufficientException;

try {
    $response = AiMeter::forUser($user)->call(fn () => OpenAI::chat()->create([...]));
} catch (AiLimitExceededException $e) {
    // Handle limit exceeded
} catch (AiCreditsInsufficientException $e) {
    // Handle insufficient credits
}

Webhook Handling

Stripe Webhooks

The package integrates with Laravel Cashier's webhook handling. Listen to Cashier events:

// In EventServiceProvider
protected $listen = [
    \Laravel\Cashier\Events\WebhookReceived::class => [
        \App\Listeners\HandleStripeWebhook::class,
    ],
];

Handle subscription updates:

use Laravel\Cashier\Events\WebhookReceived;
use Metrix\AiMetering\Models\AiSubscription;

public function handle(WebhookReceived $event)
{
    if ($event->payload['type'] === 'customer.subscription.updated') {
        // Update AiSubscription based on Stripe subscription
        $stripeSubscription = $event->payload['data']['object'];
        
        AiSubscription::where('stripe_id', $stripeSubscription['id'])
            ->update([
                'ends_at' => Carbon::createFromTimestamp($stripeSubscription['current_period_end']),
            ]);
    }
}

API Reference

AiMeter Facade

AiMeter::forUser($user)              // Set user for usage tracking
    ->forTenant($tenant)              // Set tenant (optional)
    ->billable($billable)             // Set billable entity
    ->usingProvider($provider, $model) // Set AI provider and model
    ->feature($feature)                // Set feature name
    ->billingMode($mode)              // Set billing mode ('plan' or 'credits')
    ->withMeta($meta)                 // Add metadata (array)
    ->withIdempotencyKey($key)        // Set idempotency key
    ->withManualUsage($usage)         // Set manual usage data
    ->call($callback)                  // Execute AI call and record usage

MeteredResponse

$response->getResponse()        // Original provider response
$response->getUsage()           // ProviderUsage object
$response->getLimitCheck()      // LimitCheckResult object
$response->getRemainingTokens() // ?int - Remaining tokens in period
$response->getRemainingCost()   // ?float - Remaining cost in period
$response->isApproachingLimit() // bool - Usage > 80% of limit
$response->isLimitReached()     // bool - Hard limit reached

ProviderUsage

use Metrix\AiMetering\Support\ProviderUsage;

$usage = new ProviderUsage(
    inputTokens: 100,
    outputTokens: 200,
    totalTokens: 300,
    inputCost: 0.00015,
    outputCost: 0.00030,
    totalCost: 0.00045,
    currency: 'usd'
);

// Access properties
$usage->inputTokens;   // ?int
$usage->outputTokens;  // ?int
$usage->totalTokens;   // ?int
$usage->inputCost;     // ?float
$usage->outputCost;    // ?float
$usage->totalCost;     // ?float
$usage->currency;      // ?string

// Convert to array
$array = $usage->toArray();

// Create from array
$usage = ProviderUsage::fromArray($array);

LimitCheckResult

use Metrix\AiMetering\Support\LimitCheckResult;

// Properties
$result->allowed;            // bool - Is usage allowed?
$result->approaching;        // bool - Is usage approaching limit (>80%)?
$result->hardLimitReached;   // bool - Has hard limit been reached?
$result->remainingTokens;    // ?int - Remaining tokens
$result->remainingCost;      // ?float - Remaining cost
$result->usagePercentage;    // float - Usage percentage (0-100)

// Factory methods
$result = LimitCheckResult::allowed(
    remainingTokens: 1000,
    remainingCost: 50.0,
    usagePercentage: 50.0
);

$result = LimitCheckResult::limitReached();
$result = LimitCheckResult::unlimited();

Model Relationships

// AiSubscription
$subscription->plan;              // BelongsTo AiPlan
$subscription->previousPlan;      // BelongsTo AiPlan (previous plan)
$subscription->billable;          // MorphTo (User, Tenant, etc.)

// AiPlan
$plan->subscriptions;             // HasMany AiSubscription

// AiUsage
$usage->billable;                 // MorphTo (User, Tenant, etc.)

// AiCreditWallet
$wallet->billable;                // MorphTo
$wallet->transactions;            // HasMany AiCreditTransaction

// AiCreditTransaction
$transaction->wallet;             // BelongsTo AiCreditWallet

// AiOverage
$overage->billable;               // MorphTo
$overage->usage;                  // BelongsTo AiUsage (if ai_usage_id set)

Model Methods

// AiPlan
$plan->hasUnlimitedTokens();      // bool
$plan->hasUnlimitedCost();        // bool
$plan->allowsOverage();           // bool

// AiSubscription
$subscription->isActive();        // bool
$subscription->isExpired();       // bool
$subscription->isInTrial();       // bool
$subscription->isInGracePeriod(); // bool

// AiCreditWallet
$wallet->addCredits($amount, $reason, $meta);     // AiCreditTransaction
$wallet->deductCredits($amount, $reason, $meta); // AiCreditTransaction
$wallet->hasSufficientBalance($amount);           // bool

// AiOverage
$overage->isSynced();             // bool
$overage->markAsSynced($stripeId); // void

Troubleshooting

Common Issues

"Provider 'xxx' is not configured"

Make sure the provider is configured in config/ai-metering.php:

'providers' => [
    'xxx' => [
        'class' => \Your\Provider\Class::class,
    ],
],

"AI usage limit exceeded" but limits seem fine

  • Check if you're using the correct period (calendar vs rolling)
  • Verify the subscription is active: $subscription->isActive()
  • Clear cache: php artisan cache:clear
  • Check for period-specific overrides

Credits not deducting

  • Ensure billing mode is set to 'credits': ->billingMode('credits')
  • Verify wallet exists: AiCreditWallet::firstOrCreate([...])
  • Check if overdraft is allowed in config
  • Look for exceptions in logs

Usage not being recorded

  • Check if queue is enabled and jobs are processing
  • Verify database connection in config
  • Check logs for recording errors
  • Ensure idempotency keys are unique

Dashboard not accessible

  • Verify dashboard is enabled: 'dashboard.enabled' => true
  • Check middleware configuration
  • Verify Gate authorization if configured
  • Check route prefix matches config

Debug Mode

Enable detailed logging:

'logging' => [
    'enabled' => true,
    'level' => 'debug', // Use 'debug' for detailed logs
    'log_failures' => true,
],

Check logs in storage/logs/laravel.log for detailed information.

Health Check

Check package health:

php artisan ai-metering:validate

This validates:

  • Configuration
  • Database connectivity
  • Data integrity
  • Orphaned records

Testing

composer test

Run with coverage:

composer test-coverage

Migration Guide

Version 1.0

This is the initial release. No migrations needed.

For future versions, migration guides will be documented here. See CHANGELOG.md for detailed version history.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Before contributing:

  1. Read the codebase structure
  2. Follow PSR-12 coding standards
  3. Write tests for new features
  4. Update documentation

License

The MIT License (MIT). Please see License File for more information.

Support

For issues and questions, please open an issue on GitHub.