Saltar al contenido
← Volver a proyectos

Laravel Stripe

PHP/Laravel client for the Stripe API: customers, subscriptions, products, prices, checkout sessions, billing portal, invoices, payment intents, events and webhook signature verification.

Laravel Stripe

#Laravel Stripe

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads License

A PHP/Laravel client for the Stripe API. Covers customers, subscriptions, products, prices, checkout sessions, the billing portal, invoices, payment intents and events through a simple API built on Laravel's Http client — plus verification of Stripe's webhook signatures.

No stripe/stripe-php dependency: just illuminate/http, so responses come back as plain arrays and are trivial to fake in tests.

#Features

  • Customers: list, get, create, update, delete, find by email
  • Subscriptions: list, get, create, update, cancel (now or at period end), resume, list for a customer
  • Products: list, get, create, update, archive, delete
  • Prices: list, get, create, update, archive
  • Checkout: create a session, get, list, expire, line items
  • Billing portal: create a customer portal session
  • Invoices: list, get, list for a customer, pay, send, void
  • Payment intents: list, get, create, update, confirm, capture, cancel
  • Events: list, get, filter by type (the 30-day log behind webhooks)
  • Webhooks: Stripe-Signature verification (HMAC-SHA256, replay-window check, rotation-safe)
  • Pin an API version with a single env var
  • Throws StripeException (with the original error body, code, type and param) on any non-2xx response
  • Throws InvalidArgumentException before hitting the API when a required field is missing

#Installation

You can install the package via composer:

composer require jeffersongoncalves/laravel-stripe

Publish the config file:

php artisan vendor:publish --tag=stripe-config

Set your credentials in .env:

STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Find the keys under Developers > API keys and the webhook signing secret under Developers > Webhooks in the Stripe dashboard. Test and live keys are separate — only STRIPE_SECRET is used by this package, STRIPE_KEY is kept in the config for your client-side code.

#Configuration

// config/stripe.php
return [
    'secret' => env('STRIPE_SECRET'),
    'key' => env('STRIPE_KEY'),
    'base_url' => env('STRIPE_BASE_URL', 'https://api.stripe.com'),
    'api_version' => env('STRIPE_API_VERSION'),
    'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
    'default_limit' => env('STRIPE_DEFAULT_LIMIT', 10),
];

#Usage

Use the Stripe facade or inject JeffersonGoncalves\Stripe\Stripe. Each API group is exposed as a method returning a dedicated resource class. Every list() accepts Stripe's own filters (limit, starting_after, ending_before, created, ...) and defaults limit to default_limit.

Amounts are always in the currency's smallest unit — 1990 is US$ 19.90.

#Customers

use JeffersonGoncalves\Stripe\Facades\Stripe;

$customer = Stripe::customers()->create('jane@example.com', ['name' => 'Jane Doe']);

Stripe::customers()->list(['limit' => 10]);
Stripe::customers()->get($customer['id']);
Stripe::customers()->update($customer['id'], ['name' => 'Janet Doe']);
Stripe::customers()->delete($customer['id']);

// First customer with that email, or null — Stripe does not enforce uniqueness
$existing = Stripe::customers()->findByEmail('jane@example.com');

#Products and prices

$product = Stripe::products()->create('Pro Plan', ['description' => 'Everything in one plan']);

$price = Stripe::prices()->create(
    productId: $product['id'],
    unitAmount: 1990,        // US$ 19.90
    currency: 'usd',
    attributes: ['recurring' => ['interval' => 'month']],
);

Stripe::products()->archive($product['id']);   // active = false
Stripe::prices()->archive($price['id']);

#Checkout and the billing portal

$session = Stripe::checkout()->create(
    lineItems: [['price' => $price['id'], 'quantity' => 1]],
    mode: 'subscription',
    successUrl: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
    attributes: [
        'customer' => $customer['id'],
        'cancel_url' => 'https://example.com/cancel',
    ],
);

return redirect()->away($session['url']);
$portal = Stripe::billingPortal()->createSession($customer['id'], 'https://example.com/account');

return redirect()->away($portal['url']);

#Subscriptions

$subscription = Stripe::subscriptions()->create($customer['id'], [
    ['price' => $price['id'], 'quantity' => 1],
]);

Stripe::subscriptions()->forCustomer($customer['id'], ['status' => 'active']);

Stripe::subscriptions()->update($subscription['id'], [
    'items' => [['id' => $subscription['items']['data'][0]['id'], 'quantity' => 3]],
    'proration_behavior' => 'always_invoice',
]);

Stripe::subscriptions()->cancel($subscription['id']);                      // immediately
Stripe::subscriptions()->cancel($subscription['id'], atPeriodEnd: true);   // at period end
Stripe::subscriptions()->resume($subscription['id']);                      // undo the above

#Invoices, payment intents and events

Stripe::invoices()->forCustomer($customer['id'], ['status' => 'paid']);
Stripe::invoices()->pay('in_123');
Stripe::invoices()->void('in_123');

$intent = Stripe::paymentIntents()->create(2500, 'brl', [
    'customer' => $customer['id'],
    'automatic_payment_methods' => ['enabled' => 'true'],
]);

Stripe::paymentIntents()->capture($intent['id']);   // capture_method = manual
Stripe::paymentIntents()->cancel($intent['id']);

Stripe::events()->ofType('checkout.session.completed');

#Webhooks

Verify the Stripe-Signature header against the raw request body before trusting a webhook:

use Illuminate\Http\Request;
use JeffersonGoncalves\Stripe\Facades\Stripe;

Route::post('/stripe/webhook', function (Request $request) {
    abort_unless(Stripe::webhooks()->verifyRequest($request), 403);

    $event = $request->json()->all();

    match ($event['type']) {
        'checkout.session.completed' => /* provision access */ null,
        'customer.subscription.deleted' => /* revoke access */ null,
        'invoice.payment_failed' => /* notify the customer */ null,
        default => null,
    };

    return response()->noContent();
})->withoutMiddleware([VerifyCsrfToken::class]);

verify(string $payload, string $signature, int $tolerance = 300) is available when you already hold the raw body. Signatures older than $tolerance seconds are rejected (pass 0 to disable that check). More than one v1 signature in the header is handled, so rotating the signing secret does not drop events. The payload must be the raw body — a re-encoded array will not match.

#Error handling

Any non-2xx API response throws JeffersonGoncalves\Stripe\Exceptions\StripeException, which exposes the decoded error body along with Stripe's code, type and offending parameter:

use JeffersonGoncalves\Stripe\Exceptions\StripeException;

try {
    Stripe::subscriptions()->get('sub_missing');
} catch (StripeException $e) {
    if ($e->errorCode() === 'resource_missing') {
        // ...
    }

    logger()->error($e->getMessage(), $e->errorBody());
}

Missing required fields (e.g. email on customers()->create()) throw InvalidArgumentException before any HTTP call is made.

#Testing

composer test

#Changelog

Please see CHANGELOG for more information on what has changed recently.

#Contributing

Please see CONTRIBUTING for details.

#Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

#Credits

#License

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

Nueva versión disponible.