recurly/recurly-client
Official PHP client for Recurly API v3. Install via Composer, create a Client with your API key (supports EU region), and optionally plug in a PSR-3 logger. Provides a single entry point for all Recurly operations with semver releases.
Installation:
composer require recurly/recurly-client
Add to composer.json under require:
"recurly/recurly-client": "^4"
Initialize Client:
Store your API key securely (e.g., Laravel .env):
RECURLY_API_KEY=your_api_key_here
Create a service provider or helper:
// app/Providers/RecurlyServiceProvider.php
use Recurly\Client;
public function register()
{
$this->app->singleton(Client::class, function ($app) {
return new Client(config('services.recurly.key'));
});
}
First Use Case: Fetch a subscription:
$subscription = app(Client::class)->getSubscription('code-subscription123');
return $subscription->getAccount()->getEmail(); // Example: "user@example.com"
Create Subscription:
$subscription = app(Client::class)->createSubscription([
'account' => ['code' => 'acc123'],
'plan_code' => 'premium',
'billing_info' => ['first_name' => 'John', 'last_name' => 'Doe'],
]);
Update Subscription:
$subscription = app(Client::class)->updateSubscription('code-sub123', [
'plan_code' => 'enterprise',
]);
Cancel Subscription:
$subscription = app(Client::class)->cancelSubscription('code-sub123', [
'reason' => 'customer_request',
]);
List Accounts with Pagination:
$accounts = app(Client::class)->listAccounts(['limit' => 100]);
foreach ($accounts as $account) {
logger()->info("Account: {$account->getEmail()}");
}
Bulk Actions:
$pastDueAccounts = app(Client::class)->listAccounts(['past_due' => true]);
foreach ($pastDueAccounts as $account) {
app(Client::class)->sendAccountEmail($account->getCode(), 'past_due_reminder');
}
Create Plan:
$plan = app(Client::class)->createPlan([
'name' => 'Annual Pro',
'code' => 'pro-annual',
'currencies' => [
['currency' => 'USD', 'unit_amount' => 999.00],
],
]);
Fetch Plan Details:
$plan = app(Client::class)->getPlan('code-pro-annual');
return $plan->getCurrencies()[0]->getUnitAmount(); // 999.00
use Recurly\Webhook;
$webhook = new Webhook($request->getContent(), config('services.recurly.webhook_secret'));
if ($webhook->isValid()) {
$event = $webhook->getEvent();
// Handle event (e.g., subscription_canceled)
}
Service Container Binding:
Bind the client in AppServiceProvider:
public function boot()
{
$this->app->bind(Client::class, function ($app) {
return new Client(config('services.recurly.key'), [
'region' => config('services.recurly.region', 'us'),
]);
});
}
API Rate Limiting:
Use Laravel's rate limiter with Recurly's RateLimitRemaining header:
$response = app(Client::class)->getSubscription('code-sub123')->getResponse();
$remaining = $response->getRateLimitRemaining();
if ($remaining < 5) {
throw new \Exception("Recurly API rate limit exceeded");
}
Error Handling Middleware: Create middleware to catch Recurly errors:
// app/Http/Middleware/HandleRecurlyErrors.php
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (\Recurly\Errors\Validation $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (\Recurly\RecurlyError $e) {
return response()->json(['error' => 'Recurly API Error'], 500);
}
}
Jobs for Async Operations: Queue long-running operations (e.g., bulk account updates):
// app/Jobs/ProcessRecurlyAccounts.php
public function handle()
{
$accounts = app(Client::class)->listAccounts(['limit' => 1000]);
foreach ($accounts as $account) {
// Process account asynchronously
}
}
API Key Exposure:
.env and config/services.php:
'recurly' => [
'key' => env('RECURLY_API_KEY'),
'region' => env('RECURLY_REGION', 'us'),
'webhook_secret' => env('RECURLY_WEBHOOK_SECRET'),
],
Pagination Lazy Loading:
list* methods return a Pager object that does not immediately query the API. Iterate or call getCount() to trigger the request:
// ❌ No API call yet
$accounts = app(Client::class)->listAccounts();
// ✅ Triggers API call
$count = $accounts->getCount();
Time Zone Handling:
$subscription = app(Client::class)->getSubscription('code-sub123');
$createdAt = Carbon::parse($subscription->getCreatedAt())->timezone('America/New_York');
Webhook Validation:
Webhook class to prevent spoofing:
$webhook = new Webhook($request->getContent(), config('services.recurly.webhook_secret'));
if (!$webhook->isValid()) {
abort(403, 'Invalid webhook signature');
}
Rate Limiting:
$plan = Cache::remember("recurly_plan_{$planCode}", now()->addMinutes(5), function () use ($planCode) {
return app(Client::class)->getPlan("code-$planCode");
});
Enable Logging:
$logger = new \Monolog\Logger('recurly');
$logger->pushHandler(new \Monolog\Handler\StreamHandler(storage_path('logs/recurly.log'), \Monolog\Logger::DEBUG));
$client = new Client(config('services.recurly.key'), $logger);
DEBUG level in production (exposes sensitive data).Inspect HTTP Metadata:
getResponse():
$response = app(Client::class)->getSubscription('code-sub123')->getResponse();
logger()->debug('Request ID:', [$response->getRequestId()]);
logger()->debug('Headers:', [$response->getHeaders()]);
Common Errors:
NotFound: Verify IDs (e.g., account_code, subscription_code).Validation: Check required fields (e.g., billing_info for subscriptions).Forbidden: Ensure API key has correct permissions.Custom Resources:
Resource class to add domain-specific methods:
class CustomSubscription extends \Recurly\Resource\Subscription
{
public function isActive()
{
return $this->getState() === 'active';
}
}
API Client Decorator:
class RecurlyClientDecorator extends Client
{
public function __construct($apiKey, $options = [])
{
parent::__construct($apiKey, $options);
$this->middleware(function
How can I help you explore Laravel packages today?