crovver/crovver-php

Official PHP SDK for the Crovver subscription management platform

Maintainers

Package info

github.com/crovver/crovver-php

pkg:composer/crovver/crovver-php

Statistics

Installs: 18

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.4.0 2026-05-05 17:40 UTC

This package is auto-updated.

Last update: 2026-05-05 17:41:17 UTC


README

Official PHP SDK for Crovver — subscription management, feature entitlements, and billing for SaaS applications.

Requirements

  • PHP 8.0+
  • Composer

Installation

composer require crovver/crovver-php

Quick Start

use Crovver\CrovverClient;
use Crovver\CrovverConfig;

$client = new CrovverClient(new CrovverConfig(
    apiKey: 'your-api-key'
));

Configuration

Option Type Default Description
apiKey string required Your Crovver API key
baseUrl string https://app.crovver.com Crovver base URL
timeout int 30 HTTP request timeout in seconds
maxRetries int 3 Max retry attempts for retryable errors
debug bool false Log all requests and responses
logger callable|null null Custom logger — receives (string $msg, array $ctx)
$client = new CrovverClient(new CrovverConfig(
    apiKey: 'your-api-key',
    timeout: 15,
    maxRetries: 3,
    debug: true,
    logger: fn(string $msg, array $ctx = []) => error_log("[Crovver] $msg"),
));

API Reference

Tenant Management

Tenants represent your customers (organisations in B2B, users in D2C).

use Crovver\Types\CreateTenantRequest;

// Create a tenant
$response = $client->createTenant(new CreateTenantRequest(
    externalTenantId: 'org_123',
    name: 'Acme Corp',
    externalUserId: 'user_456',
    ownerEmail: 'admin@acme.com',   // optional
    ownerName: 'Jane Doe',          // optional
    slug: 'acme-corp',              // optional
    metadata: ['plan' => 'trial'],  // optional
));

echo $response->tenant->id;
echo $response->tenant->externalTenantId;

// Get a tenant by external ID
$response = $client->getTenant('org_123');
echo $response->tenant->name;
echo $response->tenant->isActive ? 'Active' : 'Inactive';

// Access members and organisation context
foreach ($response->members as $member) {
    echo $member->externalUserId . '' . $member->role;
    echo $member->email;
}
echo $response->organization['type']; // "b2b" or "d2c"

CreateTenantRequest parameters:

Parameter Type Required Description
externalTenantId string Yes Your system's tenant identifier
name string Yes Display name
externalUserId string Yes Owner's user ID in your system
ownerEmail string|null No Owner email
ownerName string|null No Owner display name
slug string|null No URL-safe identifier
metadata array|null No Arbitrary key-value pairs

Plans

$response = $client->getPlans();

foreach ($response->plans as $plan) {
    echo $plan->name;
    echo $plan->description;
    echo $plan->pricing->currency . ' ' . $plan->pricing->amount;
    echo $plan->pricing->interval;       // "monthly" | "yearly"
    echo $plan->pricing->isSeatBased ? 'Seat-based' : 'Flat';
    echo $plan->isActive ? 'Active' : 'Inactive';

    // Feature flags
    foreach ($plan->features as $key => $value) {
        echo "$key: " . ($value ? 'yes' : 'no');
    }

    // Usage limits
    foreach ($plan->limits as $metric => $limit) {
        echo "$metric: $limit";
    }
}

Subscriptions

// B2B: pass externalTenantId  |  D2C: pass externalUserId
$response = $client->getSubscriptions('org_123');

foreach ($response->subscriptions as $sub) {
    echo $sub->id;
    echo $sub->status;                           // "active" | "trialing" | "canceled" | etc.
    echo $sub->billing['current_period_end'];
    echo $sub->trial['is_active'] ? 'On trial' : 'Paid';

    // Seat-based capacity
    if ($sub->capacity) {
        echo 'Seats used: ' . $sub->capacity['used'] . ' / ' . $sub->capacity['total'];
    }
}

Checkout

Checkout calls are never retried automatically to prevent duplicate payments.

use Crovver\Types\CreateCheckoutSessionRequest;

// B2B checkout (tenant + user)
$response = $client->createCheckoutSession(new CreateCheckoutSessionRequest(
    externalUserId: 'user_456',
    planId: 'plan_789',
    provider: 'stripe',
    externalTenantId: 'org_123',
    successUrl: 'https://app.example.com/success',
    cancelUrl: 'https://app.example.com/cancel',
));

header('Location: ' . $response->checkoutUrl);
exit;

// D2C checkout (user only)
$response = $client->createCheckoutSession(new CreateCheckoutSessionRequest(
    externalUserId: 'user_456',
    planId: 'plan_789',
    provider: 'stripe',
    userEmail: 'user@example.com',
    userName: 'John Doe',
    successUrl: 'https://app.example.com/success',
    cancelUrl: 'https://app.example.com/cancel',
));

CreateCheckoutSessionRequest parameters:

Parameter Type Required Description
externalUserId string Yes User initiating the checkout
planId string Yes Target plan ID
provider string Yes Payment provider code (e.g. "stripe")
externalTenantId string|null No Tenant ID for B2B flows
userEmail string|null No Pre-fill checkout email
userName string|null No Pre-fill customer name
successUrl string|null No Redirect after successful payment
cancelUrl string|null No Redirect after cancelled payment
quantity int|null No Seat quantity for seat-based plans
metadata array|null No Arbitrary metadata

Entitlements

Feature access

if ($client->canAccess('org_123', 'export_csv')) {
    // feature is available for this tenant
}

Metered usage

// Record usage (defaults to incrementing by 1)
$client->recordUsage('org_123', 'api_calls');
$client->recordUsage('org_123', 'api_calls', 10);
$client->recordUsage('org_123', 'storage_gb', 5, ['source' => 'file_upload']);

// Check against plan limits
$usage = $client->checkUsageLimit('org_123', 'api_calls');

echo $usage->current;     // current usage count
echo $usage->limit;       // plan limit (null = unlimited)
echo $usage->remaining;   // remaining before limit
echo $usage->percentage;  // 0–100 (null if unlimited)
echo $usage->allowed;     // false if limit exceeded

if (!$usage->allowed) {
    // block the action or prompt upgrade
}

Seat-based Proration

For seat-based plans, use proration checkout to upgrade capacity mid-cycle with prorated billing.

// Preview and initiate a capacity upgrade
$response = $client->createProrationCheckout(
    externalTenantId: 'org_123',
    newCapacity: 20,              // total seats after upgrade
    planId: 'plan_789',           // optional if only one active plan
    successUrl: 'https://app.example.com/success',
    cancelUrl: 'https://app.example.com/cancel',
);

echo $response->prorationAmount;   // charge for remainder of billing period
echo $response->message;

if ($response->requiresPayment) {
    header('Location: ' . $response->checkoutUrl);
    exit;
}
// If no payment required, capacity is upgraded immediately

Payment Providers

$response = $client->getSupportedProviders();

foreach ($response->providers as $provider) {
    echo $provider->name . ' (' . $provider->code . ')';
    echo $provider->isEnabled ? 'Enabled' : 'Disabled';
    echo $provider->supportsTestMode ? ' — test mode available' : '';
    echo $provider->supportsRecurringBilling ? ' — recurring billing' : '';
}

Error Handling

use Crovver\CrovverError;

try {
    $response = $client->getSubscriptions('org_123');
} catch (CrovverError $e) {
    echo $e->getMessage();      // human-readable error message
    echo $e->getStatusCode();   // HTTP status code (null for network errors)
    echo $e->getErrorCode();    // API error code string (e.g. "TENANT_NOT_FOUND")
    echo $e->isRetryable();     // bool — whether the SDK already retried

    print_r($e->toArray());     // structured array of all fields
} catch (\Throwable $e) {
    // Unexpected errors
}

Retry behaviour

The SDK retries automatically with exponential backoff + ±25% jitter (base 1 s, cap 30 s).

Condition Retried
5xx server errors Yes
429 Too Many Requests Yes
408 Request Timeout Yes
Network / connection errors Yes
4xx client errors (400, 401, 403, 404) No
Checkout / payment endpoints Never

The server exposes these endpoints:

Method Path Description
GET /plans List all plans
GET /tenant?externalTenantId= Get a tenant
POST /tenant Create / provision a tenant
GET /subscriptions?externalTenantId= List subscriptions
POST /can-access Check feature access
POST /checkout Create a checkout session
GET /providers List supported payment providers

License

MIT