blendbyte / paypal
PayPal REST API client for Laravel and standalone PHP.
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.9
- guzzlehttp/psr7: ^2.0
- illuminate/support: ^12.0|^13.0
- nesbot/carbon: ^3.0
- psr/http-client: ^1.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/framework: ^12.0|^13.0
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- phpunit/phpunit: ^11.0|^12.0
- symfony/var-dumper: ^7.0
- dev-main
- v3.1.x-dev
- 3.1.0
- v3.0.x-dev
- 3.0.40
- 3.0.32
- 3.0.31
- 3.0.30
- 3.0.28
- 3.0.27
- 3.0.26
- 3.0.25
- 3.0.24
- 3.0.23
- 3.0.22
- 3.0.21
- 3.0.20
- 3.0.19
- 3.0.18
- 3.0.17
- 3.0.16
- 3.0.15
- 3.0.14
- 3.0.13
- 3.0.12
- 3.0.11
- 3.0.10
- 3.0.9
- 3.0.8
- 3.0.7
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- v2.0.x-dev
- 2.0.30
- 2.0.20
- 2.0.10
- 2.0.2
- 2.0.1
- 2.0.0
- 2.0.0-beta-3
- v2.0.0-beta-2
- v2.0.0-beta-1
- 1.11.12
- 1.11.11
- 1.11.10
- 1.11.0
- 1.10.0
- 1.9.0
- 1.8.0
- 1.7.0
- 1.6.11
- 1.6.10
- 1.6.9
- 1.6.8
- 1.6.7
- 1.6.6
- 1.6.5
- 1.6.4
- 1.6.3
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.9
- 1.5.8
- 1.5.7
- 1.5.6
- 1.5.5
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.9
- 1.4.8
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.9
- 1.3.8
- 1.3.7
- 1.3.6
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.0
- v1.0.x-dev
- 1.0.3
- 1.0.2
- 1.0.1
- v1.0.0
- 0.2.2
- 0.2.1
- 0.2
- dev-audit-v3.1-review
- dev-test/pr7-coverage-100
- dev-master
This package is auto-updated.
Last update: 2026-04-16 04:47:40 UTC
README
Laravel PayPal
A PayPal REST API package for Laravel, also usable as a standalone PHP client without any framework.
Supports: PHP 8.2–8.5 · Laravel 12 / 13
Disclaimer: This package is an independent community project and is not affiliated with, endorsed by, or supported by PayPal, Inc. "PayPal" is a registered trademark of PayPal, Inc. Use this package at your own risk; no warranty is provided beyond what the MIT license covers.
- Version Compatibility
- Moving to Orders v2 (v1 Payments API sunset Jan 2027)
- Installation
- Standalone Usage (without Laravel)
- Configuration
- Usage
- PayPal Fastlane
- Subscription Helpers
- Billing Plans
- Catalog Products
- Orders
- Payments
- Payouts
- Referenced Payouts
- Reference Transactions (Billing Agreements)
- Invoices
- Invoice Search
- Invoice Templates
- Subscriptions
- Disputes
- Dispute Actions
- Trackers
- Webhooks
- Payment Method Tokens
- Reporting
- Identity
- Partner Referrals
- Payment Experience
Version Compatibility
| Version | PayPal API | PayPal Deprecation | PHP | Laravel | Maintained |
|---|---|---|---|---|---|
| v1.0 | Classic NVP/SOAP (Express Checkout, Adaptive Payments) | Deprecated since 2017, no firm sunset date | 5.6+ | 5.1+ | ❌ |
| v2.0 | REST v1 Payments + v2 Orders | /v1/payments sunset Jan 2027 |
7.2+ | 6+ | ❌ |
| v3.0 | REST v2 Orders + v2 Subscriptions | Current | 7.4+ | 6–12 | ❌ |
| v3.1 | REST v2 Orders + v2 Subscriptions | Current | 8.2+ | 12–13 | ✅ |
What's new in v3.1
- PHP 8.2+ and Laravel 12 / 13 required
- Standalone usage — no Laravel dependency required, pass credentials directly
- PSR-18 HTTP client — swap Guzzle for any compliant client via
setClient() - Configurable timeouts and retries —
timeout,connect_timeout,max_retriesin config - Exception-based error handling — opt in with
withExceptions()forPayPalApiException - Local webhook verification —
verifyWebHookLocally()with in-memory cert caching, no API roundtrip - PayPal Fastlane —
generateClientToken()for one-click guest checkout - Payment Method Tokens — full Vault v3 API (setup tokens, permanent tokens, Apple Pay, Google Pay)
getCaptureIdFromOrder()— extract capture/transaction ID from order responses- Bug fixes — float precision, URL encoding, null guards, invoice date normalization
- 100% test coverage with Pest v3/v4 and PHPStan level 7
Moving to Orders v2 (v1 Payments API sunset Jan 2027)
PayPal is sunsetting the v1 Payments REST API (/v1/payments/payment) in January 2027. If your integration uses the old create-payment → redirect → execute-payment flow, or the classic Billing Agreements API (/v1/billing-agreements/), you need to migrate before then.
This package already uses Orders v2 and Subscriptions v2 throughout. The migration notes below are for callers who built custom flows against the legacy endpoints or who were previously using the Express Checkout helpers from older versions.
Checkout: redirect-based payment flow
| Old (v1 Payments — being sunset) | New (Orders v2) |
|---|---|
POST /v1/payments/payment → redirect → POST /v1/payments/payment/{id}/execute |
createOrder() → redirect → capturePaymentOrder() |
// 1. Create the order and redirect the buyer $order = $provider->createOrder([ 'intent' => 'CAPTURE', 'purchase_units' => [ ['amount' => ['currency_code' => 'USD', 'value' => '49.99']], ], 'payment_source' => [ 'paypal' => [ 'experience_context' => [ 'return_url' => 'https://example.com/paypal/return', 'cancel_url' => 'https://example.com/paypal/cancel', ], ], ], ]); // Redirect the buyer to: $order['links'][href where rel === 'payer-action'] // 2. After the buyer approves, capture the payment $capture = $provider->capturePaymentOrder($order['id']); $captureId = $provider->getCaptureIdFromOrder($capture); // store this
Recurring billing: Billing Agreements → Subscriptions
| Old (v1 Billing Agreements — being sunset) | New (Subscriptions v2) |
|---|---|
createBillingAgreementToken() → createBillingAgreement() |
addProductById() → addBillingPlanById() → setupSubscription() |
// New subscriptions flow $response = $provider->addProductById('PROD-XYAB12ABSB7868434') ->addBillingPlanById('P-5ML4271244454362WXNWU5NQ') ->setReturnAndCancelUrl('https://example.com/success', 'https://example.com/cancel') ->setupSubscription('John Doe', 'john@example.com'); // Redirect the buyer to: $response['links'][href where rel === 'approve']
See the Subscription Helpers section for creating plans programmatically.
Installation
composer require srmklive/paypal
Publish the config file:
php artisan vendor:publish --provider "Srmklive\PayPal\Providers\PayPalServiceProvider"
Standalone Usage (without Laravel)
The package has no hard dependency on Laravel — you can use it in any PHP project:
composer require srmklive/paypal
Instantiate the client and pass your credentials directly via setApiCredentials(). No service provider or .env file needed:
use Srmklive\PayPal\Services\PayPal as PayPalClient; $provider = new PayPalClient; $provider->setApiCredentials([ 'mode' => 'sandbox', // or 'live' 'sandbox' => [ 'client_id' => 'YOUR_SANDBOX_CLIENT_ID', 'client_secret' => 'YOUR_SANDBOX_CLIENT_SECRET', 'app_id' => 'APP-80W284485P519543T', ], 'live' => [ 'client_id' => 'YOUR_LIVE_CLIENT_ID', 'client_secret' => 'YOUR_LIVE_CLIENT_SECRET', 'app_id' => 'YOUR_LIVE_APP_ID', ], 'payment_action' => 'Sale', 'currency' => 'USD', 'notify_url' => '', 'locale' => 'en_US', 'validate_ssl' => true, ]); $provider->getAccessToken(); // All API methods are now available $order = $provider->createOrder([...]);
The facade and php artisan vendor:publish are Laravel-only conveniences; everything else works identically.
Configuration
Add to your .env:
PAYPAL_MODE=sandbox PAYPAL_SANDBOX_CLIENT_ID= PAYPAL_SANDBOX_CLIENT_SECRET= PAYPAL_LIVE_CLIENT_ID= PAYPAL_LIVE_CLIENT_SECRET= PAYPAL_LIVE_APP_ID= # Optional — shown with defaults PAYPAL_TIMEOUT=30 PAYPAL_CONNECT_TIMEOUT=10 PAYPAL_MAX_RETRIES=2
The published config/paypal.php:
return [ 'mode' => env('PAYPAL_MODE', 'sandbox'), // 'sandbox' or 'live' 'sandbox' => [ 'client_id' => env('PAYPAL_SANDBOX_CLIENT_ID', ''), 'client_secret' => env('PAYPAL_SANDBOX_CLIENT_SECRET', ''), 'app_id' => 'APP-80W284485P519543T', ], 'live' => [ 'client_id' => env('PAYPAL_LIVE_CLIENT_ID', ''), 'client_secret' => env('PAYPAL_LIVE_CLIENT_SECRET', ''), 'app_id' => env('PAYPAL_LIVE_APP_ID', ''), ], 'payment_action' => env('PAYPAL_PAYMENT_ACTION', 'Sale'), // 'Sale', 'Authorization', or 'Order' 'currency' => env('PAYPAL_CURRENCY', 'USD'), 'notify_url' => env('PAYPAL_NOTIFY_URL', ''), 'locale' => env('PAYPAL_LOCALE', 'en_US'), 'validate_ssl' => env('PAYPAL_VALIDATE_SSL', true), 'timeout' => env('PAYPAL_TIMEOUT', 30), // total request timeout (seconds) 'connect_timeout' => env('PAYPAL_CONNECT_TIMEOUT', 10), // connection timeout (seconds) 'max_retries' => env('PAYPAL_MAX_RETRIES', 2), // retries on 5xx / network errors (0 to disable) ];
Usage
Initialization
use Srmklive\PayPal\Services\PayPal as PayPalClient; $provider = new PayPalClient; // Or via facade $provider = \PayPal::setProvider();
Custom HTTP Client (PSR-18)
setClient() accepts any PSR-18 ClientInterface, so you can swap out Guzzle for Symfony HttpClient, Buzz, or any other compliant implementation:
use Symfony\Component\HttpClient\Psr18Client; $provider->setClient(new Psr18Client());
Pass null (or call with no argument) to restore the default Guzzle client with the configured timeout and retry middleware.
Note: The built-in retry middleware runs only on the default Guzzle client. When you inject a custom client, handle retries in that client's own middleware stack.
Override Configuration at Runtime
$provider->setApiCredentials([ 'mode' => 'live', 'live' => [ 'client_id' => 'PAYPAL_LIVE_CLIENT_ID', 'client_secret' => 'PAYPAL_LIVE_CLIENT_SECRET', 'app_id' => 'PAYPAL_LIVE_APP_ID', ], 'payment_action' => 'Sale', 'currency' => 'USD', 'notify_url' => 'https://your-site.com/paypal/notify', 'locale' => 'en_US', 'validate_ssl' => true, ]);
Get Access Token
Call this before any API method:
$provider->getAccessToken();
Set Currency
$provider->setCurrency('EUR');
Error Handling
By default, API errors are returned as an array with an error key:
$response = $provider->showOrderDetails('bad-id'); if (isset($response['error'])) { // $response['error'] is the decoded PayPal error object or a plain string }
Opt in to exceptions with withExceptions(). All API errors will then throw PayPalApiException instead:
use Srmklive\PayPal\Exceptions\PayPalApiException; $provider->withExceptions(); try { $order = $provider->showOrderDetails('bad-id'); } catch (PayPalApiException $e) { $e->getHttpStatus(); // HTTP status code: 400, 401, 404, 422, 500, etc. (0 for network errors) $e->getMessage(); // JSON-encoded error string $e->getPayPalError(); // decoded array (e.g. ['name' => 'RESOURCE_NOT_FOUND', ...]) // or a plain string for non-JSON errors }
Call withoutExceptions() to revert to silent mode. Both methods are fluent.
PayPal Fastlane
PayPal Fastlane is a one-click guest checkout experience that pre-fills shipping and payment details for returning PayPal customers, typically delivering ~50% higher conversion on guest checkout flows.
Server role: generate a client token and handle Orders v2 create/capture. The Fastlane UI is rendered entirely by the PayPal JS SDK on the client.
1. Generate a client token (server-side)
$provider->getAccessToken(); $result = $provider->generateClientToken(); // $result['client_token'] — pass this to your frontend
2. Initialise Fastlane (client-side)
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&components=fastlane"></script> <script> const { Fastlane } = await paypal.Fastlane({ clientToken: '<?= $result["client_token"] ?>' }); const { selectionChanged, selectedCard } = await Fastlane.identity.lookupCustomerByEmail(email); // render Fastlane.FastlaneWatermarkComponent(), Fastlane.FastlaneCardComponent(), etc. </script>
3. Create & capture the order (server-side)
// Create $order = $provider->createOrder([ 'intent' => 'CAPTURE', 'purchase_units' => [ ['amount' => ['currency_code' => 'USD', 'value' => '49.99']], ], 'payment_source' => [ 'card' => [ 'single_use_token' => $singleUseToken, // from Fastlane.FastlaneCardComponent ], ], ]); // Capture $capture = $provider->capturePaymentOrder($order['id']); // Extract the transaction/capture ID $captureId = $provider->getCaptureIdFromOrder($capture);
Subscription Helpers
A fluent helper API for creating subscriptions without manually building plan/product payloads.
addPlanTrialPricing()andsetReturnAndCancelUrl()are optional. Return/cancel URLs require real domains (notlocalhost).
Daily Subscription
$response = $provider->addProduct('Demo Product', 'Demo Product', 'SERVICE', 'SOFTWARE') ->addPlanTrialPricing('DAY', 7) ->addDailyPlan('Demo Plan', 'Demo Plan', 1.50) ->setReturnAndCancelUrl('https://example.com/paypal-success', 'https://example.com/paypal-cancel') ->setupSubscription('John Doe', 'john@example.com', '2025-01-01');
Weekly / Monthly / Annual Subscription
// Weekly $response = $provider->addProduct('Demo Product', 'Demo Product', 'SERVICE', 'SOFTWARE') ->addPlanTrialPricing('DAY', 7) ->addWeeklyPlan('Demo Plan', 'Demo Plan', 30) ->setReturnAndCancelUrl('https://example.com/paypal-success', 'https://example.com/paypal-cancel') ->setupSubscription('John Doe', 'john@example.com', '2025-01-01'); // Monthly $response = $provider->addProduct(...)->addMonthlyPlan('Demo Plan', 'Demo Plan', 100)->...->setupSubscription(...); // Annual $response = $provider->addProduct(...)->addAnnualPlan('Demo Plan', 'Demo Plan', 600)->...->setupSubscription(...);
Custom Interval
$response = $provider->addProduct('Demo Product', 'Demo Product', 'SERVICE', 'SOFTWARE') ->addCustomPlan('Demo Plan', 'Demo Plan', 150, 'MONTH', 3) ->setReturnAndCancelUrl('https://example.com/paypal-success', 'https://example.com/paypal-cancel') ->setupSubscription('John Doe', 'john@example.com', '2025-01-01');
Use Existing Product & Billing Plan
$response = $provider->addProductById('PROD-XYAB12ABSB7868434') ->addBillingPlanById('P-5ML4271244454362WXNWU5NQ') ->setReturnAndCancelUrl('https://example.com/paypal-success', 'https://example.com/paypal-cancel') ->setupSubscription('John Doe', 'john@example.com', '2025-01-01');
Additional Options
// Setup fee $provider->addSetupFee(9.99)->addProductById(...)->...->setupSubscription(...); // Shipping address $provider->addShippingAddress('John Doe', '123 Main St', 'Suite 1', 'Austin', 'TX', 78701, 'US') ->addProductById(...)->...->setupSubscription(...); // Payment failure threshold $provider->addPaymentFailureThreshold(5)->addProductById(...)->...->setupSubscription(...);
Update Pricing Schemes for a Billing Plan
$response = $provider->addBillingPlanById('P-5ML4271244454362WXNWU5NQ') ->addPricingScheme('DAY', 7, 0, true) ->addPricingScheme('MONTH', 1, 100) ->processBillingPlanPricingUpdates();
Billing Plans
// List (page, count, show_total, fields) $plans = $provider->listPlans(); $plans = $provider->listPlans(1, 30, true, ['id', 'name', 'description']); // Create $plan = $provider->createPlan($data); // Update $provider->updatePlan('P-7GL4271244454362WXNWU5NQ', [ ['op' => 'replace', 'path' => '/payment_preferences/payment_failure_threshold', 'value' => 7], ]); // Show / Activate / Deactivate $plan = $provider->showPlanDetails('P-7GL4271244454362WXNWU5NQ'); $provider->activatePlan('P-7GL4271244454362WXNWU5NQ'); $provider->deactivatePlan('P-7GL4271244454362WXNWU5NQ'); // Update pricing $provider->updatePlanPricing('P-7GL4271244454362WXNWU5NQ', $pricingData);
Catalog Products
$products = $provider->listProducts(); $products = $provider->listProducts(1, 30, true); $product = $provider->createProduct($data, 'create-product-'.time()); $provider->updateProduct('72255d4849af8ed6e0df1173', [ ['op' => 'replace', 'path' => '/description', 'value' => 'Updated description'], ]); $product = $provider->showProductDetails('72255d4849af8ed6e0df1173');
Orders
// Create $order = $provider->createOrder([ 'intent' => 'CAPTURE', 'purchase_units' => [ ['amount' => ['currency_code' => 'USD', 'value' => '100.00']], ], ]); // Update, show, authorize $provider->updateOrder('5O190127TN364715T', $patchData); $order = $provider->showOrderDetails('5O190127TN364715T'); $provider->authorizePaymentOrder('5O190127TN364715T'); // Capture — and extract the capture/transaction ID from the response $capture = $provider->capturePaymentOrder($order['id']); $captureId = $provider->getCaptureIdFromOrder($capture); // $captureId is the value you store in your database and use for refunds, // dispute lookups, and shipment tracking (see Trackers section).
Payments
Authorizations
$provider->showAuthorizedPaymentDetails('0VF52814937998046'); $provider->captureAuthorizedPayment('0VF52814937998046', 'INVOICE-123', 10.99, 'Payment note'); $provider->reAuthorizeAuthorizedPayment('0VF52814937998046', 10.99); $provider->voidAuthorizedPayment('0VF52814937998046');
Captures & Refunds
$provider->showCapturedPaymentDetails('2GG279541U471931P'); $provider->refundCapturedPayment('2GG279541U471931P', 'INVOICE-123', 10.99, 'Defective product'); $provider->showRefundDetails('1JU08902781691411');
Payouts
$provider->createBatchPayout($data); $provider->showBatchPayoutDetails('FYXMPQTX4JC9N'); $provider->showPayoutItemDetails('8AELMXH8UB2P8'); $provider->cancelUnclaimedPayoutItem('8AELMXH8UB2P8');
Referenced Payouts
// Create batch $provider->createReferencedBatchPayout([ 'referenced_payouts' => [ ['reference_id' => '2KP03934U4415543C', 'reference_type' => 'TRANSACTION_ID'], ], ], 'some-request-id', 'some-attribution-id'); $provider->listItemsReferencedInBatchPayout('KHbwO28lWlXwi2IlToJ2IYNG4juFv6kpbFx4J9oQ5Hb24RSp96Dk5FudVHd6v4E='); $provider->createReferencedBatchPayoutItem( ['reference_id' => 'CAPTURETXNID', 'reference_type' => 'TRANSACTION_ID'], 'some-request-id', 'some-attribution-id' ); $provider->showReferencedPayoutItemDetails('CDZEC5MJ8R5HY', 'some-attribution-id');
Reference Transactions (Billing Agreements)
Note: This is a limited-release PayPal API. You must request access from PayPal before using it.
// Create an agreement token (first step) $provider->createBillingAgreementToken($data); // Get details of an existing agreement token $provider->getBillingAgreementTokenDetails('token-id'); // Create a billing agreement from a token $provider->createBillingAgreement('token-id'); // Show / Update / Cancel a billing agreement $provider->showBillingAgreementDetails('agreement-id'); $provider->updateBillingAgreement('agreement-id', $patchData); $provider->cancelBillingAgreement('agreement-id');
Invoices
$invoiceNo = $provider->generateInvoiceNumber(); $invoices = $provider->listInvoices(); $invoices = $provider->listInvoices(2, 50); $invoice = $provider->createInvoice($data); $provider->deleteInvoice('INV2-Z56S-5LLA-Q52L-CPZ5'); $provider->updateInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', $data); $invoice = $provider->showInvoiceDetails('INV2-Z56S-5LLA-Q52L-CPZ5'); $provider->cancelInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', $data); $provider->generateQRCodeInvoice('INV2-Z56S-5LLA-Q52L-CPZ5'); $provider->generateQRCodeInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', 50, 50); // custom dimensions $provider->sendInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', 'Subject', 'Note'); $provider->sendInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', 'Subject', 'Note', true, true, ['cc@example.com']); $provider->sendInvoiceReminder('INV2-Z56S-5LLA-Q52L-CPZ5', 'Subject', 'Note'); $provider->sendInvoiceReminder('INV2-Z56S-5LLA-Q52L-CPZ5', 'Subject', 'Note', true, true, ['cc@example.com']); $provider->registerPaymentInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', '2024-05-21', 'BANK_TRANSFER', 10.00); $provider->deleteExternalPaymentInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', 'EXTR-86F38350LX4353815'); $provider->refundInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', '2024-05-26', 'BANK_TRANSFER', 5.00); $provider->deleteRefundInvoice('INV2-Z56S-5LLA-Q52L-CPZ5', 'EXTR-2LG703375E477444T');
Invoice Search
$invoices = $provider->searchInvoices(); $invoices = $provider->searchInvoices(1, 50, false);
Available filters (chainable, call searchInvoices() at the end):
$invoices = $provider ->addInvoiceFilterByRecipientEmail('bill-me@example.com') ->addInvoiceFilterByRecipientFirstName('John') ->addInvoiceFilterByRecipientLastName('Doe') ->addInvoiceFilterByRecipientBusinessName('Acme Inc.') ->addInvoiceFilterByInvoiceNumber('#123') ->addInvoiceFilterByInvoiceStatus(['PAID', 'MARKED_AS_PAID']) ->addInvoiceFilterByReferenceorMemo('deal-ref') ->addInvoiceFilterByCurrencyCode('USD') ->addInvoiceFilterByAmountRange(30, 50) // uses configured currency ->addInvoiceFilterByAmountRange(30, 50, 'EUR') // explicit currency ->addInvoiceFilterByDateRange('2024-01-01', '2024-06-30', 'invoice_date') // invoice_date|due_date|payment_date|creation_date ->addInvoiceFilterByArchivedStatus(false) ->addInvoiceFilterByFields(['items', 'payments', 'refunds']) ->searchInvoices();
Invoice Templates
$provider->listInvoiceTemplates(); $provider->listInvoiceTemplates(1, 50); $provider->createInvoiceTemplate($data); $provider->deleteInvoiceTemplate('TEMP-19V05281TU309413B'); $provider->updateInvoiceTemplate('TEMP-19V05281TU309413B', $data); $provider->showInvoiceTemplateDetails('TEMP-19V05281TU309413B');
Subscriptions
Full CRUD for PayPal Subscriptions (distinct from the subscription helper methods above):
$provider->createSubscription($data); $provider->updateSubscription('I-BW452GLLEP1G', [ ['op' => 'replace', 'path' => '/billing_info/outstanding_balance', 'value' => ['currency_code' => 'USD', 'value' => '50.00']], ]); $provider->showSubscriptionDetails('I-BW452GLLEP1G'); $provider->activateSubscription('I-BW452GLLEP1G', 'Reactivating the subscription'); $provider->cancelSubscription('I-BW452GLLEP1G', 'Not satisfied with the service'); $provider->suspendSubscription('I-BW452GLLEP1G', 'Item out of stock'); $provider->captureSubscriptionPayment('I-BW452GLLEP1G', 'Balance reached limit', 100); $provider->reviseSubscription('I-BW452GLLEP1G', $data); $provider->listSubscriptionTransactions('I-BW452GLLEP1G', '2024-01-01T00:00:00Z', '2024-12-31T23:59:59Z');
Disputes
$provider->listDisputes(); $provider->updateDispute('PP-D-27803', $patchData); $provider->showDisputeDetails('PP-D-27803');
Dispute Actions
$provider->acceptDisputeClaim('PP-D-27803', 'Wrong item shipped'); $provider->acceptDisputeOfferResolution('PP-D-27803', 'Accepting discount offer'); $provider->acknowledgeItemReturned('PP-D-27803', 'Items received', 'ITEM_RECEIVED'); $provider->makeOfferToResolveDispute('PP-D-27803', 'Offering refund', 10.00, 'REFUND'); $provider->escalateDisputeToClaim('PP-D-27803', 'Escalating unresolved dispute'); $provider->updateDisputeStatus('PP-D-27803', $data); // Provide evidence (jpg, png, pdf only) $provider->provideDisputeEvidence('PP-D-27803', [ '/path/to/invoice.pdf', '/path/to/screenshot.jpg', ]);
Trackers
The transaction-id used here is the capture ID — get it via getCaptureIdFromOrder() after calling capturePaymentOrder() (see Orders).
$provider->addBatchTracking($data); $provider->addTracking($data); $provider->listTrackingDetails($captureId); $provider->listTrackingDetails($captureId, 'tracking-number'); $provider->updateTrackingDetails('tracking-id', $data); $provider->showTrackingDetails('tracking-id');
Webhooks
// Create $provider->createWebHook('https://example.com/paypal/webhook', ['PAYMENT.CAPTURE.COMPLETED']); // List / Show / Update / Delete $provider->listWebHooks(); $provider->showWebHookDetails('webhook-id'); $provider->updateWebHook('webhook-id', $patchData); $provider->deleteWebHook('webhook-id'); // Events $provider->listWebHookEvents('webhook-id'); $provider->listEventTypes(); $provider->listEvents(); $provider->showEventDetails('event-id'); $provider->resendEventNotification('event-id', ['webhook-id']); // Verify incoming webhook signature (API roundtrip) $provider->verifyWebHook([ 'auth_algo' => $request->header('PAYPAL-AUTH-ALGO'), 'cert_url' => $request->header('PAYPAL-CERT-URL'), 'transmission_id' => $request->header('PAYPAL-TRANSMISSION-ID'), 'transmission_sig' => $request->header('PAYPAL-TRANSMISSION-SIG'), 'transmission_time' => $request->header('PAYPAL-TRANSMISSION-TIME'), 'webhook_id' => 'your-webhook-id', 'webhook_event' => $request->all(), ]); // Verify locally (offline — no API roundtrip, faster for high-volume webhooks) // Pass all request headers, your webhook ID, and the RAW (unmodified) request body. $valid = $provider->verifyWebHookLocally( $request->headers->all(), 'your-webhook-id', $request->getContent(), // must be the raw body bytes, not re-encoded JSON );
Local verification skips the PayPal verify API entirely. The signing certificate is fetched over HTTPS from the
PAYPAL-CERT-URLon the first call, then cached in memory for the lifetime of the process — subsequent calls are pure in-memory RSA-SHA256 with no network I/O. Short-lived processes (serverless, etc.) will still fetch the cert on each cold start. The cert URL is validated against PayPal's known API domains before any request is made (SSRF guard).
Payment Method Tokens
// Payment tokens (permanent) $provider->createPaymentSourceToken($data); $provider->setCustomerId('customer_4029352050'); // required before listPaymentSourceTokens() $provider->listPaymentSourceTokens(1, 10, true); $provider->showPaymentSourceTokenDetails('token-id'); $provider->deletePaymentSourceToken('token-id'); // Setup tokens (single-use, used to create a payment token) $provider->createPaymentSetupToken($data); $provider->showPaymentSetupTokenDetails('token-id'); $provider->deletePaymentSetupToken('token-id');
Using the fluent helpers to create a token:
$response = $provider->setTokenSource('5C991763VB2781612', 'SETUP_TOKEN') ->setCustomerId('customer_4029352050') ->sendPaymentMethodRequest(); // or ->sendPaymentMethodRequest(true) to create a setup token instead
Reporting
use Carbon\Carbon; $provider->listTransactions([ 'start_date' => Carbon::now()->toIso8601String(), 'end_date' => Carbon::now()->addDays(30)->toIso8601String(), ]); $provider->listBalances('2024-01-01'); $provider->listBalances('2024-01-01', 'EUR');
Identity
$provider->showProfileInfo(); $provider->createMerchantApplication( 'AGGREGATOR', ['https://example.com/callback'], ['merchant@example.com'], 'WDJJHEBZ4X2LY', 'some-open-id' ); $provider->setAccountProperties($data); $provider->disableAccountProperties(); $provider->listUsers(1, 10); $provider->showUserDetails('user-id'); $provider->deleteUser('user-id'); // Client token — used with PayPal Fastlane and Advanced Card Payments $provider->generateClientToken(); // preferred alias $provider->getClientToken(); // equivalent
Partner Referrals
$provider->createPartnerReferral($data); $provider->showReferralData('ZjcyODU4ZWYtYTA1OC00ODIwLTk2M2EtOTZkZWQ4NmQwYzI3RU12cE5xa0xMRmk1NWxFSVJIT1JlTFdSbElCbFU1Q3lhdGhESzVQcU9iRT0='); $provider->listSellerTrackingInfo('tracking-id'); $provider->listSellerStatus('partner-id', 'merchant-id'); $provider->listMerchantCredentials();
Payment Experience
$provider->listWebExperienceProfiles(); $provider->createWebExperienceProfile($data); $provider->showWebExperienceProfileDetails('XP-A88A-LYLW-8Y3X-E5ER'); $provider->updateWebExperienceProfile('XP-A88A-LYLW-8Y3X-E5ER', $data); $provider->patchWebExperienceProfile('XP-A88A-LYLW-8Y3X-E5ER', $patchData); $provider->deleteWebExperienceProfile('XP-A88A-LYLW-8Y3X-E5ER');
Maintained by Blendbyte
This project is maintained by Blendbyte — a team of engineers with 20+ years of experience building cloud infrastructure, web applications, and developer tools. We use these packages in production ourselves and actively contribute to the open source ecosystem we rely on every day. Issues and PRs are always welcome.
🌐 blendbyte.com · 📧 hello@blendbyte.com