PHP code example of leonardganyire / paypal

1. Go to this page and download the library: Download leonardganyire/paypal library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

leonardganyire / paypal example snippets


use LeonardGanyire\Paypal\Facades\PayPal;

$paypalOrder = PayPal::createOrder($payload, idempotencyKey: (string) $payment->id);
$approvalUrl = PayPal::approvalUrl($paypalOrder);
$capture = PayPal::captureOrder($paypalOrder['id']);

use LeonardGanyire\Paypal\PayPalClient;

final class PayPalCheckoutController
{
    public function __construct(
        private readonly PayPalClient $paypal,
    ) {}
}

Schema::create('payments', function (Blueprint $table): void {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->foreignId('order_id')->constrained();
    $table->string('provider')->default('paypal');
    $table->string('provider_reference')->nullable()->index();
    $table->string('capture_reference')->nullable()->index();
    $table->string('status')->default('pending')->index();
    $table->string('currency', 3);
    $table->decimal('amount', 10, 2);
    $table->json('provider_payload')->nullable();
    $table->timestamps();
});

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

final class Payment extends Model
{
    protected $fillable = [
        'user_id',
        'order_id',
        'provider',
        'provider_reference',
        'capture_reference',
        'status',
        'currency',
        'amount',
        'provider_payload',
    ];

    protected function casts(): array
    {
        return [
            'amount' => 'decimal:2',
            'provider_payload' => 'array',
        ];
    }
}

use App\Http\Controllers\Payments\PayPalCancelController;
use App\Http\Controllers\Payments\PayPalCaptureController;
use App\Http\Controllers\Payments\PayPalOrderController;
use App\Http\Controllers\Payments\PayPalReturnController;
use App\Http\Controllers\Payments\PayPalWebhookController;
use Illuminate\Support\Facades\Route;

Route::middleware('auth')->prefix('payments/paypal')->name('payments.paypal.')->group(function (): void {
    Route::post('/orders', [PayPalOrderController::class, 'store'])->name('orders.store');
    Route::post('/orders/{payment}/capture', [PayPalCaptureController::class, 'store'])->name('orders.capture');
    Route::get('/return/{payment}', PayPalReturnController::class)->name('return');
    Route::get('/cancel/{payment}', PayPalCancelController::class)->name('cancel');
});

Route::post('/payments/paypal/webhook', PayPalWebhookController::class)
    ->name('payments.paypal.webhook');

Route::middleware('auth:sanctum')->prefix('payments/paypal')->name('api.payments.paypal.')->group(function (): void {
    Route::post('/orders', [PayPalOrderController::class, 'store'])->name('orders.store');
    Route::post('/orders/{payment}/capture', [PayPalCaptureController::class, 'store'])->name('orders.capture');
});

namespace App\Http\Controllers\Payments;

use App\Models\Order;
use App\Models\Payment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use LeonardGanyire\Paypal\PayPalClient;

final class PayPalOrderController
{
    public function __construct(
        private readonly PayPalClient $paypal,
    ) {}

    public function store(Request $request): JsonResponse|RedirectResponse
    {
        $validated = $request->validate([
            'order_id' => ['=> $order->total,
        ]);

        $paypalOrder = $this->paypal->createOrder([
            'intent' => 'CAPTURE',
            'purchase_units' => [[
                'reference_id' => (string) $payment->id,
                'description' => "Order {$order->id}",
                'amount' => [
                    'currency_code' => strtoupper($payment->currency),
                    'value' => number_format((float) $payment->amount, 2, '.', ''),
                ],
            ]],
            'application_context' => [
                'brand_name' => config('app.name'),
                'return_url' => route('payments.paypal.return', $payment),
                'cancel_url' => route('payments.paypal.cancel', $payment),
                'user_action' => 'PAY_NOW',
            ],
        ], idempotencyKey: (string) $payment->id);

        $payment->update([
            'provider_reference' => $paypalOrder['id'] ?? null,
            'provider_payload' => $paypalOrder,
        ]);

        $approvalUrl = $this->paypal->approvalUrl($paypalOrder);

        abort_if($approvalUrl === null, 502, 'PayPal did not return an approval URL.');

        if (($validated['mode'] ?? null) === 'redirect') {
            return redirect()->away($approvalUrl);
        }

        return response()->json([
            'payment_id' => $payment->id,
            'paypal_order_id' => $paypalOrder['id'],
            'approval_url' => $approvalUrl,
        ]);
    }
}

namespace App\Http\Controllers\Payments;

use App\Models\Payment;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use LeonardGanyire\Paypal\Enums\PayPalOrderStatus;
use LeonardGanyire\Paypal\PayPalClient;

final class PayPalReturnController
{
    public function __construct(
        private readonly PayPalClient $paypal,
    ) {}

    public function __invoke(Request $request, Payment $payment): RedirectResponse
    {
        abort_unless($request->user()->is($payment->user), 403);

        $paypalOrderId = (string) $request->query('token');

        abort_unless(
            is_string($payment->provider_reference) && $paypalOrderId === $payment->provider_reference,
            403,
        );

        $capture = $this->paypal->captureOrder($paypalOrderId);
        $status = PayPalOrderStatus::fromResponse($capture);

        if ($status->isCompleted()) {
            $payment->update([
                'status' => 'paid',
                'capture_reference' => PayPalOrderStatus::captureReference($capture),
                'provider_payload' => $capture,
            ]);

            // Fulfill the order here: grant access, dispatch shipment, send receipt, etc.
        }

        return redirect()->route('orders.show', $payment->order);
    }
}

namespace App\Http\Controllers\Payments;

use App\Models\Payment;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

final class PayPalCancelController
{
    public function __invoke(Request $request, Payment $payment): RedirectResponse
    {
        abort_unless($request->user()->is($payment->user), 403);

        $payment->update(['status' => 'cancelled']);

        return redirect()
            ->route('orders.show', $payment->order)
            ->with('status', 'PayPal checkout was cancelled.');
    }
}

namespace App\Http\Controllers\Payments;

use App\Models\Payment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use LeonardGanyire\Paypal\Enums\PayPalOrderStatus;
use LeonardGanyire\Paypal\PayPalClient;

final class PayPalCaptureController
{
    public function __construct(
        private readonly PayPalClient $paypal,
    ) {}

    public function store(Request $request, Payment $payment): JsonResponse
    {
        abort_unless($request->user()->is($payment->user), 403);

        $validated = $request->validate([
            'paypal_order_id' => ['s->isCompleted()) {
            $payment->update([
                'status' => 'paid',
                'capture_reference' => PayPalOrderStatus::captureReference($capture),
                'provider_payload' => $capture,
            ]);

            // Fulfill the order here.
        }

        $freshPayment = $payment->fresh();

        return response()->json([
            'status' => $freshPayment->status,
            'payment_id' => $freshPayment->id,
            'capture_id' => $freshPayment->capture_reference,
        ]);
    }
}

use Inertia\Inertia;

return Inertia::render('checkout/show', [
    'order' => [
        'id' => $order->id,
        'total' => $order->total,
        'currency' => $order->currency,
    ],
    'paypal' => [
        'clientId' => config('paypal.client_id'),
        'currency' => strtoupper($order->currency),
    ],
]);

use LeonardGanyire\Paypal\Facades\PayPal;

// Full refund
$refund = PayPal::refundCapture($payment->capture_reference);

// Partial refund
$refund = PayPal::refundCapture($payment->capture_reference, [
    'amount' => [
        'value' => '5.00',
        'currency_code' => 'USD',
    ],
]);

$paypalOrder = PayPal::createOrder([
    'intent' => 'AUTHORIZE',
    'purchase_units' => [[
        'amount' => [
            'currency_code' => 'USD',
            'value' => '100.00',
        ],
    ]],
]);

$authorization = PayPal::authorizeOrder($paypalOrder['id']);

$paypalOrder = PayPal::getOrder($payment->provider_reference);

namespace App\Http\Controllers\Payments;

use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use LeonardGanyire\Paypal\Facades\PayPal;

final class PayPalWebhookController
{
    public function __invoke(Request $request): Response
    {
        $headers = [
            'paypal-auth-algo' => $request->header('PAYPAL-AUTH-ALGO'),
            'paypal-cert-url' => $request->header('PAYPAL-CERT-URL'),
            'paypal-transmission-id' => $request->header('PAYPAL-TRANSMISSION-ID'),
            'paypal-transmission-sig' => $request->header('PAYPAL-TRANSMISSION-SIG'),
            'paypal-transmission-time' => $request->header('PAYPAL-TRANSMISSION-TIME'),
        ];

        $payload = $request->all();

        if (! PayPal::verifyWebhookSignature($headers, $payload)) {
            abort(400, 'Invalid PayPal webhook signature.');
        }

        $eventType = $payload['event_type'] ?? null;
        $resource = $payload['resource'] ?? [];

        match ($eventType) {
            'PAYMENT.CAPTURE.COMPLETED' => $this->markCaptureCompleted($resource),
            'PAYMENT.CAPTURE.REFUNDED' => $this->markCaptureRefunded($resource),
            default => null,
        };

        return response('OK');
    }

    /**
     * @param  array<string, mixed>  $resource
     */
    private function markCaptureCompleted(array $resource): void
    {
        $captureId = $resource['id'] ?? null;

        if (! is_string($captureId)) {
            return;
        }

        Payment::query()
            ->where('capture_reference', $captureId)
            ->where('status', '!=', 'paid')
            ->update(['status' => 'paid']);
    }

    /**
     * @param  array<string, mixed>  $resource
     */
    private function markCaptureRefunded(array $resource): void
    {
        $captureId = $resource['supplementary_data']['related_ids']['capture_id'] ?? null;

        if (! is_string($captureId)) {
            return;
        }

        Payment::query()
            ->where('capture_reference', $captureId)
            ->update(['status' => 'refunded']);
    }
}

use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->validateCsrfTokens(except: [
        'payments/paypal/webhook',
    ]);
})

use LeonardGanyire\Paypal\Enums\PayPalOrderStatus;

$status = PayPalOrderStatus::fromResponse($capture);

$status->isCompleted();
$status->isCancelled();

$captureId = PayPalOrderStatus::captureReference($capture);

use LeonardGanyire\Paypal\Exceptions\PayPalException;
use LeonardGanyire\Paypal\Facades\PayPal;

try {
    $paypalOrder = PayPal::createOrder($payload);
} catch (PayPalException $exception) {
    report($exception);

    return response()->json([
        'message' => $exception->getMessage(),
        'code' => $exception->errorCode(),
        'details' => $exception->details(),
    ], $exception->status());
}

use Illuminate\Support\Facades\Http;

Http::fake([
    'api-m.sandbox.paypal.com/v1/oauth2/token' => Http::response([
        'access_token' => 'test-token',
        'expires_in' => 3600,
    ]),
    'api-m.sandbox.paypal.com/v2/checkout/orders' => Http::response([
        'id' => 'PAYPAL-ORDER-1',
        'links' => [
            [
                'rel' => 'approve',
                'href' => 'https://paypal.test/approve/PAYPAL-ORDER-1',
            ],
        ],
    ], 201),
    'api-m.sandbox.paypal.com/v2/checkout/orders/PAYPAL-ORDER-1/capture' => Http::response([
        'status' => 'COMPLETED',
        'purchase_units' => [[
            'payments' => [
                'captures' => [
                    ['id' => 'CAPTURE-1'],
                ],
            ],
        ]],
    ], 201),
]);

use App\Models\Order;
use Illuminate\Support\Facades\Http;

it('captures a paypal payment', function (): void {
    Http::preventStrayRequests();

    Http::fake([
        'api-m.sandbox.paypal.com/v1/oauth2/token' => Http::response([
            'access_token' => 'test-token',
            'expires_in' => 3600,
        ]),
        'api-m.sandbox.paypal.com/v2/checkout/orders/PAYPAL-ORDER-1/capture' => Http::response([
            'status' => 'COMPLETED',
            'purchase_units' => [[
                'payments' => [
                    'captures' => [
                        ['id' => 'CAPTURE-1'],
                    ],
                ],
            ]],
        ], 201),
    ]);

    $order = Order::factory()->create();
    $payment = $order->payment()->create([
        'user_id' => $order->user_id,
        'provider' => 'paypal',
        'provider_reference' => 'PAYPAL-ORDER-1',
        'status' => 'pending',
        'currency' => 'USD',
        'amount' => '25.00',
    ]);

    $this->actingAs($order->user)
        ->postJson(route('payments.paypal.orders.capture', $payment), [
            'paypal_order_id' => 'PAYPAL-ORDER-1',
        ])
        ->assertOk()
        ->assertJsonPath('status', 'paid');

    expect($payment->fresh()->capture_reference)->toBe('CAPTURE-1');
});
bash
php artisan vendor:publish --tag=paypal-config