Integrating PayPal Subscription Plans with Webhooks in Laravel

In this article, we will walk through integrating PayPal subscription plans in a Laravel application. We will focus on setting up the PayPal SDK, creating payments using PayPal subscription plans, and handling payment notifications via PayPal Webhooks.

Install PayPal SDK

To start using PayPal services in your Laravel application, we need to install the srmklive/paypal package.

composer require srmklive/paypal

After installation, publish the configuration file for PayPal.

php artisan vendor:publish --provider="Srmklive\PayPal\PayPalServiceProvider" --tag="config"

This will create a config/paypal.php file, where you can configure your PayPal settings. However, we’ll be fetching these configurations directly from the .env file.

Set Up PayPal Credentials in .env

To keep your credentials secure, we will use Laravel’s .env file for storing the PayPal API credentials.

Add the following lines to your .env file:

PAYPAL_CLIENT_ID=your-client-id
PAYPAL_CLIENT_SECRET=your-client-secret
PAYPAL_MODE=sandbox  # or 'live' for production
PAYPAL_APP_ID=your-app-id  # Optional, if required by your PayPal app
PAYPAL_NOTIFY_URL=https://your-domain.com/paypal/notify  # Webhook notification URL




Create the PayPal Payment Controller

Now, let’s create the controller to handle PayPal payments and subscription creation. We will use the PayPal service to interact with the PayPal API.

Here is a sample implementation:

namespace App\Http\Controllers\Payments;

use App\Http\Controllers\Controller;
use App\Models\Payment;
use Srmklive\PayPal\Services\PayPal;
use Illuminate\Http\Request;

class PaypalPaymentController extends Controller
{
    private $provider;

    public function __construct()
    {
        $this->provider = new PayPal;

        $conf = [
            'mode'    => env('PAYPAL_MODE', 'sandbox'),
            'live' => [
                'client_id'         => env('PAYPAL_CLIENT_ID'),
                'client_secret'     => env('PAYPAL_CLIENT_SECRET'),
                'app_id'            => env('PAYPAL_APP_ID', ''),
            ],
            'payment_action' => 'Sale',
            'currency'       => 'USD',
            'notify_url'     => env('PAYPAL_NOTIFY_URL', ''),
            'locale'         => 'en_US',
            'validate_ssl'   => false,
        ];

        // Set PayPal credentials
        $this->provider->setApiCredentials($conf);

        // Get access token to use the PayPal API
        $this->provider->getAccessToken();
    }

    public function createPayment(Request $request)
    {
        $order = $request->order;
        $price = $order->total;
        $customerId = $order->customer_id;

        $payment = Payment::create([
            'price' => $price,
            'order_id' => $order->id,
            'status' => 'incompleted',
            'payment_method' => 'paypal',
        ]);

        $successUrl = route('paypal.success', ['orderId' => $order->id, 'paymentId' => $payment->id]);
        $cancelUrl = route('paypal.cancel', ['orderId' => $order->id]);

        $data = [
            'intent' => 'CAPTURE',
            'purchase_units' => [
                [
                    'amount' => [
                        'currency_code' => 'USD',
                        'value' => $price,
                    ],
                ],
            ],
            'application_context' => [
                'locale' => 'en-US',
                'shipping_preference' => 'NO_SHIPPING',
                'user_action' => 'PAY_NOW',
                'payment_method' => [
                    'payer_selected' => 'PAYPAL',
                    'payee_preferred' => 'IMMEDIATE_PAYMENT_REQUIRED',
                ],
                'return_url' => $successUrl,
                'cancel_url' => $cancelUrl,
            ],
        ];

        try {
            $response = $this->provider->createOrder($data);
            return response()->json(['payment_url' => $response['links'][1]['href'], 'payment_id' => $payment->id]);
        } catch (\Throwable $th) {
            return response()->json(['error' => 'Error creating payment'], 500);
        }
    }

    public function success(Request $request)
    {
        // Handle successful payment
        $paymentId = $request->input('paymentId');
        $orderId = $request->input('orderId');

        $payment = Payment::find($paymentId);
        $payment->update(['status' => 'completed']);

        return view('payment.success', compact('orderId', 'paymentId'));
    }

    public function cancel(Request $request)
    {
        // Handle canceled payment
        $paymentId = $request->input('paymentId');
        $payment = Payment::find($paymentId);
        $payment->update(['status' => 'canceled']);

        return view('payment.cancel');
    }
}




Add PayPal Webhook for Payment Notifications

PayPal Webhooks allow you to receive notifications about payment events, such as successful transactions or failed payments. Here, we’ll add a route and a handler to process the webhook.

  1. Configure Webhook Route:

Add the following route in your routes/web.php file.

Route::post('/paypal/notify', [PaypalPaymentController::class, 'handleWebhook']);
  1. Handle the Webhook in Controller:

In the PaypalPaymentController, add a method to process the webhook.

public function handleWebhook(Request $request)
{
    $data = $request->all();
    
    // Check the PayPal event type
    $eventType = $data['event_type'] ?? null;

    if ($eventType === 'PAYMENT.SALE.COMPLETED') {
        $paymentId = $data['resource']['id'];
        $payment = Payment::where('payment_id', $paymentId)->first();

        if ($payment) {
            $payment->status = 'completed';
            $payment->save();
        }
    }

    return response()->json(['status' => 'success']);
}

PayPal will send notifications to this route whenever an event (like a completed payment) occurs. We will check the event type (PAYMENT.SALE.COMPLETED) and update the payment status accordingly.

This article has covered the basic steps of integrating PayPal subscription plans into your Laravel application. We’ve shown how to configure PayPal credentials, create payment orders, and handle successful and canceled payments. Additionally, we added a webhook handler to process payment notifications.

With this setup, you can easily integrate PayPal as a payment gateway for your application and ensure that you receive real-time updates about your transactions through Webhooks.