Installation
composer require mdiqbal/laravel-payments
php artisan vendor:publish --provider="MDIqbal\LaravelPayments\LaravelPaymentsServiceProvider" --tag="config"
Publish migrations and run:
php artisan migrate
Configure
Edit .env with your preferred gateway credentials (e.g., STRIPE_SECRET_KEY or PAYPAL_CLIENT_ID). Update config/payments.php to set default gateway and preferences.
First Use Case Process a payment in a controller:
use MDIqbal\LaravelPayments\Facades\Payment;
$payment = Payment::gateway('stripe')
->amount(1000) // $10.00
->currency('USD')
->method('card')
->pay();
config/payments.php (default gateway, supported gateways, settings).capture vs. authorize).routes/web.php for pre-configured webhook routes (e.g., payment/webhook/stripe).Gateway Selection Dynamically switch gateways based on user location, preferences, or business logic:
$gateway = request()->input('gateway') ?? config('payments.default');
$payment = Payment::gateway($gateway)->process();
Payment Processing Create Intent (for Stripe, PayPal, etc.):
$intent = Payment::gateway('stripe')
->amount(5000)
->currency('USD')
->method('card')
->createIntent();
Return the client_secret to frontend for payment confirmation.
Handle Callback:
public function handleCallback(Request $request) {
$payment = Payment::gateway('paypal')
->verify($request->all());
if ($payment->success()) {
// Fulfill order
}
}
Subscription Management
// Stripe example
$subscription = Payment::gateway('stripe')
->customer('cus_123')
->plan('monthly_plan')
->createSubscription();
Refunds/Voids
$refund = Payment::gateway('paystack')
->transaction('txn_123')
->refund(500); // Partial refund
Frontend Integration:
Use the createIntent() method to generate tokens/secrets (e.g., Stripe’s PaymentIntent or PayPal’s Order ID). Pass these to frontend libraries (e.g., Stripe.js, PayPal.js) for seamless checkout.
// Example for Stripe
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: elements.getElement(CardElement) }
});
Order Workflow:
Tie payments to Eloquent models using payment_data or metadata:
$payment = Payment::gateway('razorpay')
->amount(2000)
->currency('INR')
->metadata(['order_id' => $order->id])
->pay();
Webhooks:
Validate webhooks using the verify() method. Log unhandled events for debugging:
public function stripeWebhook(Request $request) {
$event = Payment::gateway('stripe')->verifyWebhook($request->getContent());
// Handle event (e.g., payment_succeeded, charge.refunded)
}
Fallback Logic: Implement a fallback to another gateway if the primary fails:
try {
$payment = Payment::gateway('paypal')->pay();
} catch (\Exception $e) {
$payment = Payment::gateway('stripe')->pay();
}
Gateway-Specific Quirks:
capture() for immediate funds and authorize() for holds. Ensure your flow matches the provider’s requirements.email and contact for customers. Missing these may cause failures.store_id, store_passwd, and total_amount in a specific format. Validate against their API docs.merchant_invoice_number (use metadata or a UUID).Webhook Validation:
stripe-signature header). Use the verifyWebhook() method to avoid replay attacks.\Log::debug('Unhandled webhook event:', $event->toArray());
Currency/Amount Handling:
amount() in cents, not dollars.Testing:
.env). Example:
STRIPE_SECRET_KEY=sk_test_...
PAYPAL_MODE=sandbox
$this->postJson('/payment/webhook/stripe', $payload)
->assertSuccessful();
Rate Limiting:
use MDIqbal\LaravelPayments\Exceptions\PaymentException;
try {
$payment = Payment::gateway('paypal')->pay();
} catch (PaymentException $e) {
if ($e->isRetryable()) {
retry()->times(3)->try(fn() => Payment::gateway('paypal')->pay());
}
}
Enable Logging:
Add to config/payments.php:
'log' => [
'enabled' => true,
'channel' => 'single',
],
Check logs for gateway responses/errors.
Inspect Raw Responses:
Use ->getRawResponse() to debug API responses:
$response = Payment::gateway('stripe')->createIntent()->getRawResponse();
\Log::info('Stripe response:', $response);
Common Errors:
Invalid amount: Check currency unit (e.g., USD = cents, INR = paise).Missing parameter: Validate required fields (e.g., email for Razorpay).Signature verification failed: Ensure webhook secrets match .env.Gateway not found: Verify the gateway name in config/payments.php under supported_gateways.Add a New Gateway:
MDIqbal\LaravelPayments\Contracts\Gateway.config/payments.php:
'supported_gateways' => [
'custom_gateway' => \App\Services\CustomGateway::class,
],
createIntent(), verify(), and refund() methods.Customize Payment Data:
Extend the Payment facade to add methods:
// app/Providers/AppServiceProvider.php
Payment::macro('customMethod', function () {
return $this->getGateway()->customLogic();
});
Override Default Config: Publish the config and modify:
php artisan vendor:publish --tag="payments-config"
Update config/payments.php to change default behavior (e.g., timeout, retry logic).
Webhook Handlers:
Extend the WebhookHandler class to add custom logic for specific events:
namespace App\Services;
use MDIqbal\LaravelPayments\Handlers\WebhookHandler as BaseHandler;
class CustomWebhookHandler extends BaseHandler {
public function handlePaymentSucceeded($payload) {
// Custom logic
}
}
Bind it in AppServiceProvider:
Payment::setWebhookHandler(CustomWebhookHandler::class);
How can I help you explore Laravel packages today?