A clean, type-safe PHP interface for the Stripe API designed specifically for Laravel applications. This library wraps the Stripe PHP SDK with strongly-typed objects, service classes, and enums while maintaining full compatibility with Laravel 11-12.
The official Stripe PHP SDK, while powerful, returns dynamic objects and arrays that lack type safety and IDE support. This library bridges that gap by providing:
- Type Safety: All Stripe objects are represented as strongly-typed PHP classes with full PHPStan Level 8 compliance
- Laravel Integration: Seamless integration with Laravel's service container and testing infrastructure
- Developer Experience: Rich IDE autocompletion, type hints, and inline documentation
- Consistent API: Clean, predictable methods that follow Laravel conventions
- Comprehensive Testing: Built-in testing utilities that fake Stripe API calls without network requests
- Stripe SDK Escape Hatch: Direct access to the Stripe SDK for when you need to do something super custom, that this SDK does not directly support.
- PHP 8.3+
- Laravel 11 or 12
- Stripe PHP SDK ^18.0
Install via Composer:
composer require encoredigitalgroup/stripeThe package will automatically register its service provider with Laravel's auto-discovery feature.
Configure your Stripe secret key in .env:
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...Then add the following to your services.php config file:
[
"stripe" => [
"public_key" => env("STRIPE_PUBLIC_KEY"),
"secret_key" => env("STRIPE_SECRET_KEY"),
]
]You may also configure the public and secret keys directly if you wish:
use EncoreDigitalGroup\Stripe\Stripe;
use Illuminate\Support\Facades\Config;
Stripe::config()->authentication->publicKey = Config::get("your.custom.config.key.public")
Stripe::config()->authentication->private = Config::get("your.custom.config.key.secret")Start using the library:
use EncoreDigitalGroup\Stripe\Stripe;
// Create a customer
$customer = Stripe::customer()
->withEmail('[email protected]')
->withName('John Doe')
->save();
echo $customer->id(); // cus_...All objects use a fluent API pattern with private properties and chainable withXXX() methods. Access DTOs through the Stripe facade:
use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Objects\Product\{StripeProduct, StripePrice, StripeRecurring};
use EncoreDigitalGroup\Stripe\Objects\Support\StripeAddress;
use EncoreDigitalGroup\Stripe\Objects\Customer\StripeShipping;
// Customer with full details
$customer = Stripe::customer()
->withEmail('[email protected]')
->withName('John Doe')
->withPhone('+1-555-123-4567')
->withAddress(
StripeAddress::make()
->withLine1('123 Main St')
->withCity('San Francisco')
->withState('CA')
->withPostalCode('94105')
->withCountry('US')
);
// Subscription with trial
$subscription = Stripe::subscription()
->withCustomer('cus_123')
->withItems(collect([
StripeSubscriptionItem::make()
->withPrice('price_monthly')
->withQuantity(1)
]))
->withTrialEnd(now()->addDays(14));
// Webhook endpoint
$endpoint = Stripe::webhook()
->withUrl('https://myapp.com/webhooks/stripe')
->withEnabledEvents(['customer.created', 'invoice.paid'])
->withDescription('Production webhook');String-backed enums prevent typos and provide IDE autocompletion:
use EncoreDigitalGroup\Stripe\Enums\{
RecurringInterval,
PriceType,
SubscriptionStatus,
CollectionMethod,
ProrationBehavior
};
// All enum cases use PascalCase
RecurringInterval::Month
RecurringInterval::Year
PriceType::Recurring
SubscriptionStatus::ActiveTest your Stripe integration without making real API calls:
use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Support\Testing\{StripeFixtures, StripeMethod};
test('can create a customer', function () {
// Set up fake responses
$fake = Stripe::fake([
StripeMethod::CustomersCreate->value => StripeFixtures::customer([
'id' => 'cus_test123',
'email' => '[email protected]'
])
]);
// Execute code under test
$customer = Stripe::customer()
->withEmail('[email protected]')
->withName('Test Customer')
->save();
// Assert results
expect($customer->id())->toBe('cus_test123')
->and($fake)->toHaveCalledStripeMethod(StripeMethod::CustomersCreate);
});use EncoreDigitalGroup\Stripe\Stripe;
// Create customer
$customer = Stripe::customer()
->withEmail('[email protected]')
->withName('Jane Smith')
->withMetadata(['user_id' => '12345'])
->save();
// Retrieve customer
$customer = Stripe::customer()->get('cus_123');
// Update customer
$updated = Stripe::customer()
->get('cus_123')
->withName('Jane Doe')
->save();use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Enums\PaymentMethodType;
use EncoreDigitalGroup\Stripe\Objects\Payment\StripePaymentMethod;
// Create and attach payment method to customer
$customer = Stripe::customer()->get('cus_123');
$paymentMethod = StripePaymentMethod::make()
->withType(PaymentMethodType::Card);
$customer->addPaymentMethod($paymentMethod);
// List customer's payment methods
$paymentMethods = $customer->paymentMethods();
// Access individual payment method
$paymentMethod = $paymentMethods->first();
echo $paymentMethod->type()?->value; // "card"use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Enums\{PriceType, RecurringInterval};
// Create product
$product = Stripe::product()
->withName('Premium Subscription')
->withDescription('Access to all premium features')
->save();
// Create recurring price with strongly-typed recurring object
$price = Stripe::price()
->withProduct($product->id())
->withCurrency('usd')
->withUnitAmount(2999) // $29.99
->withType(PriceType::Recurring)
->withRecurring(
StripeRecurring::make()
->withInterval(RecurringInterval::Month)
->withIntervalCount(1)
)
->save();use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Enums\ProrationBehavior;
use EncoreDigitalGroup\Stripe\Objects\Subscription\StripeSubscriptionItem;
// Create subscription
$subscription = Stripe::subscription()
->withCustomer('cus_123')
->withItems(collect([
StripeSubscriptionItem::make()
->withPrice('price_monthly')
->withQuantity(1)
]))
->withMetadata(['plan' => 'professional'])
->save();
// Update subscription (upgrade/downgrade)
$updated = Stripe::subscription()
->get('sub_123')
->withItems(collect([
StripeSubscriptionItem::make()->withPrice('price_premium')
]))
->withProrationBehavior(ProrationBehavior::CreateProrations)
->save();
// Cancel subscription at period end
$canceled = Stripe::subscription()
->get('sub_123')
->cancelAtPeriodEnd()
->save();
// Cancel immediately
$canceled = Stripe::subscription()
->get('sub_123')
->cancelImmediately()
->save();
// Resume canceled subscription
$resumed = Stripe::subscription()
->get('sub_123')
->resume()
->save();Plan complex subscription changes over time:
use EncoreDigitalGroup\Stripe\Stripe;
use EncoreDigitalGroup\Stripe\Objects\Subscription\Schedules\{StripeSubscriptionSchedule, StripePhaseItem};
// Access schedule from subscription and add phases
$subscription = Stripe::subscription()->get('sub_123');
$subscription->schedule()
->get()
->addPhase(
StripePhaseItem::make()
->withPrice('price_intro')
->withQuantity(1)
)
->addPhase(
StripePhaseItem::make()
->withPrice('price_regular')
->withQuantity(1)
)
->save();
// Or create a standalone schedule
$schedule = StripeSubscriptionSchedule::make()
->withCustomer('cus_123')
->withStartDate(now()->addDay())
->save();use EncoreDigitalGroup\Stripe\Stripe;
// Create webhook endpoint
$endpoint = Stripe::webhook()
->withUrl(route('stripe.webhook'))
->withEnabledEvents([
'customer.created',
'customer.updated',
'invoice.paid',
'invoice.payment_failed',
'customer.subscription.created',
'customer.subscription.updated',
'customer.subscription.deleted'
])
->withDescription('Production webhook')
->save();
// Store the webhook secret (IMPORTANT!)
$webhookSecret = $endpoint->secret();use EncoreDigitalGroup\Stripe\Support\StripeWebhookHelper;
use EncoreDigitalGroup\Stripe\Objects\Customer\StripeCustomer;
use Illuminate\Http\Request;
class StripeWebhookController extends Controller
{
public function handleWebhook(Request $request)
{
try {
// Verify and construct event
$event = StripeWebhookHelper::constructEvent(
$request->getContent(),
StripeWebhookHelper::getSignatureHeader(),
config('services.stripe.webhook_secret')
);
// Process event
match ($event->type) {
'customer.created' => $this->handleCustomerCreated($event),
'invoice.paid' => $this->handleInvoicePaid($event),
default => logger()->info('Unhandled event', ['type' => $event->type])
};
return response()->json(['status' => 'success']);
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid signature'], 400);
}
}
protected function handleCustomerCreated($event): void
{
$customer = StripeCustomer::fromStripeObject($event->data->object);
User::updateOrCreate(
['stripe_customer_id' => $customer->id()],
['email' => $customer->email(), 'name' => $customer->name()]
);
}
}Run the test suite:
# All tests
./vendor/bin/pest
# Specific suite
./vendor/bin/pest tests/Feature/
./vendor/bin/pest tests/Unit/
# With coverage
./vendor/bin/pest --coverage --min=80
# Stop on first failure
./vendor/bin/pest --stop-on-failure# Static analysis (Level 8)
./vendor/bin/phpstan analyse
# Code style fixing
./vendor/bin/duster fix
# Refactoring
./vendor/bin/rector processContributions to this repository are governed by the Encore Digital Group Contribution Terms.
Additional details on how to contribute are available here.
This repository is licensed using a modified version of the BSD 3-Clause License.
The license is available for review here.