payment-laravel maintained by sayed
User Guide - How to Use This Package in Your Laravel Application
This guide provides step-by-step instructions for integrating the Sayed Payment Laravel package into your application.
Table of Contents
Installation
Step 1: Install the Package
Install via Composer:
composer require sayed/payment-laravel
Step 2: Publish Configuration
Publish the configuration file:
php artisan vendor:publish --tag=payment-config
This creates config/payment.php in your application.
Configuration
Step 3: Add Environment Variables
Add your payment provider credentials to .env:
# Choose your default provider
PAYMENT_PROVIDER=stripe
# Stripe Configuration
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# PayPal Configuration
PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=
PAYPAL_MODE=sandbox
# Paddle Configuration
PADDLE_VENDOR_ID=
PADDLE_VENDOR_AUTH_CODE=
PADDLE_PUBLIC_KEY=
PADDLE_ENVIRONMENT=
Step 4: Verify Configuration
Check config/payment.php to ensure providers are configured:
Basic Setup
Step 5: Create Payment Controller
Create a controller to handle payments:
php artisan make:controller PaymentController
app/Http/Controllers/PaymentController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Sayed\Payment\Facades\Payment;
use Exception;
class PaymentController extends Controller
{
/**
* Create payment checkout session
*/
public function createCheckout(Request $request)
{
try {
$result = Payment::driver('stripe')->checkout([
'currency' => 'usd',
'amount' => 2000, // $20.00 in cents
'products' => [
[
'title' => 'Premium Plan',
'amount' => 2000,
'quantity' => 1,
]
],
'is_subscription' => false,
'success_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'metadata' => [
'user_id' => auth()->id(),
'order_id' => 'ORD-' . time(),
],
]);
return redirect($result['paymentLinkUrl']);
} catch (Exception $e) {
return back()->with('error', 'Payment failed: ' . $e->getMessage());
}
}
}
One-Time Payment
use Sayed\Payment\Facades\Payment;
$result = Payment::driver('stripe')->checkout([
'products' => [
[
'title' => 'Product Name',
'amount' => 5000,
'quantity' => 1,
]
],
'is_subscription' => false,
'success_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
]);
// Redirect user to payment page
return redirect($result['paymentLinkUrl']);
Subscription Payment
$result = Payment::driver('stripe')->checkout([
'currency' => 'usd',
'amount' => 2999, // $29.99
'products' => [
[
'title' => 'Monthly Subscription',
'amount' => 2999,
'quantity' => 1,
]
],
'is_subscription' => true,
'interval' => 'month', // day, week, month, year
'success_url' => route('subscription.success'),
'cancel_url' => route('subscription.cancel'),
]);
Multiple Products
$result = Payment::driver('stripe')->checkout([
'currency' => 'usd',
'products' => [
[
'title' => 'Product 1',
'amount' => 1000,
'quantity' => 2,
],
[
'title' => 'Product 2',
'amount' => 1500,
'quantity' => 1,
],
],
'is_subscription' => false,
'success_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
]);
Invoice Payment (Stripe Only)
// Step 3: Create and pay invoice
$invoice = Payment::driver('stripe')->payWithInvoice([
'customer_id' => $customer['customer_id'],
'payment_method_id' => $request->payment_method_id,
'currency' => 'usd',
'items' => [
[
'description' => 'Service Fee',
'amount' => 5000,
'quantity' => 1,
],
],
]);
return response()->json([
'success' => true,
'invoice_pdf' => $invoice['invoice_pdf'],
]);
Refund Payment
$refund = Payment::driver('stripe')->refundPayment(
transactionId: 'ch_xxxxx',
amount: 1000 // $10.00
);
Product Creation
Create products and subscription plans programmatically across all payment providers. All methods return DTOs for type-safe access.
Create One-Time Product
use Sayed\Payment\Facades\Payment;
// Create a one-time payment product
$product = Payment::driver('stripe')->createProduct([
'name' => 'Premium E-book',
'description' => 'Complete guide to Laravel',
'amount' => 2999, // $29.99 in cents
'currency' => 'usd',
]);
// Access product details
echo $product->productId; // prod_xxxxx
echo $product->priceId; // price_xxxxx
echo $product->amount; // 2999
Create Recurring Subscription
// Create a monthly subscription
$subscription = Payment::driver('stripe')->createRecurringProduct([
'name' => 'Pro Membership',
'description' => 'Monthly access to premium features',
'amount' => 1999, // $19.99 in cents
'currency' => 'usd',
'interval' => 'month', // day, week, month, year
'interval_count' => 1,
]);
// Use in checkout
$checkout = Payment::driver('stripe')->checkout([
'success_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'products' => [
['id' => $subscription->priceId, 'quantity' => 1]
],
'is_subscription' => true,
]);
PayPal Product Creation
// Create PayPal product
$product = Payment::driver('paypal')->createProduct([
'name' => 'Online Course',
'description' => 'Complete PHP Course',
'amount' => 4999,
'currency' => 'USD',
]);
// Create PayPal subscription plan
$plan = Payment::driver('paypal')->createRecurringProduct([
'name' => 'Monthly SaaS Plan',
'amount' => 2999,
'currency' => 'USD',
'interval' => 'MONTH',
]);
echo $plan->planId; // Plan ID for subscriptions
Paddle Product Creation
// Create Paddle product
$product = Payment::driver('paddle')->createProduct([
'name' => 'Software License',
'amount' => 9999,
'currency' => 'USD',
]);
// Create Paddle subscription with trial
$subscription = Payment::driver('paddle')->createRecurringProduct([
'name' => 'Basic Plan',
'amount' => 999,
'currency' => 'USD',
'interval' => 'month',
'trial_days' => 14,
]);
Advanced Features
Dynamic Provider Selection
use Sayed\Payment\Facades\Payment;
use Sayed\Payment\Enums\PaymentMethod;
// Use default provider from config
$payment = Payment::driver();
// Use specific provider with string
$stripePayment = Payment::driver('stripe');
$paypalPayment = Payment::driver('paypal');
$paddlePayment = Payment::driver('paddle');
// Use specific provider with enum (type-safe)
$stripePayment = Payment::driver(PaymentMethod::STRIPE);
$paypalPayment = Payment::driver(PaymentMethod::PAYPAL);
$paddlePayment = Payment::driver(PaymentMethod::PADDLE);
// Dynamic selection with enum
$provider = PaymentMethod::from($request->payment_method);
$payment = Payment::driver($provider);
Using PaymentMethod Enum
use Sayed\Payment\Enums\PaymentMethod;
// Get all available payment methods
$methods = PaymentMethod::values(); // ['stripe', 'paypal', 'paddle']
// Display names
foreach (PaymentMethod::cases() as $method) {
echo $method->displayName(); // Stripe, PayPal, Paddle
}
// Type-safe payment creation
$result = Payment::driver(PaymentMethod::STRIPE)->checkout([
'currency' => 'usd',
'products' => [
['title' => 'Product', 'amount' => 2999, 'quantity' => 1]
],
'is_subscription' => false,
'success_url' => route('payment.success'),
]);
Custom Metadata
Payment::driver('stripe')->checkout([
// ... other fields
'metadata' => [
'user_id' => auth()->id(),
'order_id' => $order->id,
'plan' => 'premium',
'source' => 'web',
],
]);
Get Invoice Details (Stripe)
$invoice = Payment::driver('stripe')->getInvoice('in_xxxxx');
return view('invoice', [
'invoice_pdf' => $invoice['invoice_pdf'],
'amount' => $invoice['amount_paid'],
'status' => $invoice['status'],
]);
Event-Driven Architecture
The package provides an event-driven architecture that allows you to easily handle payment events by creating custom event classes.
How It Works
When a webhook is received:
- The package validates and transforms the webhook data
- It dispatches events based on the event type (invoice, checkout, subscription)
- Your custom event classes are automatically instantiated and their
handle()method is called - You can also listen to these events using Laravel's event listener system
Step 1: Create Your Event Classes
Create event classes in app/Events by extending the base event classes provided by the package:
Invoice Events
<?php
namespace App\Events;
use Sayed\Payment\Events\InvoiceEvent;
use App\Models\Invoice;
use Illuminate\Support\Facades\Log;
class InvoicePaymentSucceeded extends InvoiceEvent
{
public function getEventName(): string
{
return 'invoice.payment_succeeded';
}
public function handle(): void
{
Log::info('Invoice paid', ['invoice_id' => $this->invoiceId]);
// Update your database
Invoice::where('payment_invoice_id', $this->invoiceId)->update([
'status' => 'paid',
'paid_at' => now(),
]);
// Your business logic here
// - Grant access to product
// - Send confirmation email
// - Trigger fulfillment process
}
}
Checkout Events
<?php
namespace App\Events;
use Sayed\Payment\Events\CheckoutEvent;
use App\Models\Order;
class CheckoutCompleted extends CheckoutEvent
{
public function getEventName(): string
{
return 'checkout.completed';
}
public function handle(): void
{
// Get order from metadata
$orderId = $this->metadata['order_id'] ?? null;
if ($orderId) {
Order::find($orderId)->update([
'status' => 'paid',
'transaction_id' => $this->transactionId,
'paid_at' => now(),
]);
}
}
}
Subscription Events
<?php
namespace App\Events;
use Sayed\Payment\Events\SubscriptionEvent;
use App\Models\Subscription;
class SubscriptionCreated extends SubscriptionEvent
{
public function getEventName(): string
{
return 'subscription.created';
}
public function handle(): void
{
Subscription::create([
'payment_subscription_id' => $this->subscriptionId,
'customer_id' => $this->customerId,
'plan_id' => $this->planId,
'status' => $this->status,
'amount' => $this->amount,
'currency' => $this->currency,
]);
}
}
Step 2: Register Events in Config
Update config/payment.php to map webhook events to your custom classes:
return [
// ... other config
'events' => [
'invoice' => [
'payment_succeeded' => \App\Events\InvoicePaymentSucceeded::class,
'payment_failed' => \App\Events\InvoicePaymentFailed::class,
'finalized' => \App\Events\InvoiceFinalized::class,
'updated' => \App\Events\InvoiceUpdated::class,
],
'checkout' => [
'completed' => \App\Events\CheckoutCompleted::class,
'expired' => \App\Events\CheckoutExpired::class,
],
'subscription' => [
'created' => \App\Events\SubscriptionCreated::class,
'updated' => \App\Events\SubscriptionUpdated::class,
'deleted' => \App\Events\SubscriptionDeleted::class,
'trial_ending' => \App\Events\SubscriptionTrialEnding::class,
],
],
];
Step 3: Access Event Data
Your event classes have access to the following properties:
InvoiceEvent Properties
$this->provider; // 'stripe', 'paypal', or 'paddle'
$this->invoiceId; // Provider's invoice ID
$this->status; // Invoice status
$this->amount; // Amount in cents (integer)
$this->currency; // Currency code (e.g., 'usd')
$this->customerId; // Customer ID (if available)
$this->subscriptionId; // Subscription ID (if applicable)
$this->metadata; // Custom metadata array
$this->rawPayload; // Original webhook payload (JSON string)
CheckoutEvent Properties
$this->provider; // Payment provider
$this->transactionId; // Transaction/session ID
$this->status; // Payment status
$this->amount; // Amount in cents (integer)
$this->currency; // Currency code
$this->customerId; // Customer ID (if available)
$this->customerEmail; // Customer email (if available)
$this->metadata; // Custom metadata array
$this->rawPayload; // Original webhook payload (JSON string)
SubscriptionEvent Properties
$this->provider; // Payment provider
$this->subscriptionId; // Subscription ID
$this->status; // Subscription status
$this->amount; // Amount in cents (integer, nullable)
$this->currency; // Currency code (nullable)
$this->customerId; // Customer ID (if available)
$this->customerEmail; // Customer email (if available)
$this->planId; // Plan/price ID (if available)
$this->currentPeriodStart; // Period start date (nullable)
$this->currentPeriodEnd; // Period end date (nullable)
$this->metadata; // Custom metadata array
$this->rawPayload; // Original webhook payload (JSON string)
Step 4: Using Laravel's Event System (Optional)
You can also use Laravel's native event listeners:
Create a Listener:
php artisan make:listener SendInvoicePaidNotification
Register in EventServiceProvider:
use App\Events\InvoicePaymentSucceeded;
use App\Listeners\SendInvoicePaidNotification;
protected $listen = [
InvoicePaymentSucceeded::class => [
SendInvoicePaidNotification::class,
],
];
Listener Example:
<?php
namespace App\Listeners;
use App\Events\InvoicePaymentSucceeded;
use Illuminate\Support\Facades\Mail;
class SendInvoicePaidNotification
{
public function handle(InvoicePaymentSucceeded $event)
{
// Send email notification
Mail::to($event->customerEmail)->send(
new InvoicePaidMail($event->invoiceId, $event->amount)
);
}
}
Available Event Names
Stripe Event Mapping
| Stripe Event | Simplified Name | Event Type |
|---|---|---|
checkout.session.completed |
completed |
checkout |
checkout.session.expired |
expired |
checkout |
customer.subscription.created |
created |
subscription |
customer.subscription.updated |
updated |
subscription |
customer.subscription.deleted |
deleted |
subscription |
customer.subscription.trial_will_end |
trial_ending |
subscription |
invoice.created |
created |
invoice |
invoice.finalized |
finalized |
invoice |
invoice.paid |
payment_succeeded |
invoice |
invoice.payment_failed |
payment_failed |
invoice |
invoice.updated |
updated |
invoice |
Real-World Example
Here's a complete example of handling invoice payments:
<?php
namespace App\Events;
use Sayed\Payment\Events\InvoiceEvent;
use App\Models\Invoice;
use App\Models\User;
use App\Jobs\SendInvoiceReceiptJob;
use App\Jobs\GrantProductAccessJob;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class InvoicePaymentSucceeded extends InvoiceEvent
{
public function getEventName(): string
{
return 'invoice.payment_succeeded';
}
public function handle(): void
{
DB::transaction(function () {
// 1. Update invoice in database
$invoice = Invoice::where('payment_invoice_id', $this->invoiceId)
->lockForUpdate()
->first();
if (!$invoice) {
Log::error('Invoice not found', ['invoice_id' => $this->invoiceId]);
return;
}
$invoice->update([
'status' => 'paid',
'paid_at' => now(),
'payment_provider' => $this->provider,
'payment_data' => $this->metadata,
]);
// 2. Get user
$user = $invoice->user;
// 3. Grant access to product/service
if ($invoice->product_id) {
GrantProductAccessJob::dispatch($user, $invoice->product_id);
}
// 4. Send receipt email
SendInvoiceReceiptJob::dispatch($user, $invoice);
// 5. Log for analytics
Log::info('Invoice payment processed successfully', [
'invoice_id' => $this->invoiceId,
'user_id' => $user->id,
'amount' => $this->amount,
'provider' => $this->provider,
]);
});
}
}