payum/paypal-express-checkout-nvp
Payum extension for integrating PayPal Express Checkout (NVP) payments in PHP apps. Includes gateway implementation, resources and docs via Payum, with support links and MIT license.
Installation
composer require payum/paypal-express-checkout-nvp
Ensure payum/payum is also installed (core package).
Configuration
Add the extension to your Payum gateway configuration in config/services.php or via config/payum.php:
'gateways' => [
'paypal_express' => [
'factory' => 'paypal_express',
'username' => env('PAYPAL_USERNAME'),
'password' => env('PAYPAL_PASSWORD'),
'signature' => env('PAYPAL_SIGNATURE'),
'test' => env('PAYPAL_TEST_MODE', false),
'solution_type' => 'Sole', // or 'Mark', 'Sole' (default)
'landing_page' => 'Billing', // or 'Login'
'currency' => 'USD',
'locale' => 'en_US',
],
],
First Use Case: Capture a Payment
use Payum\Core\Payum;
use Payum\Core\Request\Capture;
$payum = Payum::create();
$gateway = $payum->getGateway('paypal_express');
$captureToken = $gateway->getToken();
$captureToken->setAmount(1000); // 10.00 USD
$captureToken->setCurrencyCode('USD');
$captureToken->setDescription('Test payment');
$gateway->execute($captureToken);
Initialize Payment
$token = $gateway->getToken();
$token->setNumber('ORDER-123'); // Order ID
$token->setDescription('Product purchase');
$token->setAmount(2000); // 20.00 USD
$token->setCurrencyCode('USD');
$token->setDetails([
'items' => [
['name' => 'Product 1', 'amount' => 1000, 'quantity' => 2],
],
]);
Redirect to PayPal
$capture = new Capture();
$capture->setToken($token);
$gateway->execute($capture);
// Redirect to PayPal's authorization URL
$token->getTargetUrl(); // Returns PayPal redirect URL
Handle PayPal Callback
// After user returns from PayPal, capture the payment
$notify = new Notify();
$notify->setToken($token);
$gateway->execute($notify);
// Verify payment status
if ($notify->isCaptured()) {
// Payment successful
}
use Payum\Core\Payum;
use Payum\Core\Request\Capture;
class PaymentController extends Controller
{
protected $payum;
public function __construct(Payum $payum)
{
$this->payum = $payum;
}
public function checkout()
{
$gateway = $this->payum->getGateway('paypal_express');
$token = $gateway->getToken();
// Set payment details
$token->setNumber('ORDER-' . time());
$token->setAmount(1500);
$token->setCurrencyCode('USD');
// Redirect to PayPal
return redirect()->away($token->getTargetUrl());
}
public function callback(Request $request)
{
$gateway = $this->payum->getGateway('paypal_express');
$token = $gateway->getToken();
// Reconstruct token from PayPal callback
$token->setToken($request->input('token'));
$notify = new Notify();
$notify->setToken($token);
$gateway->execute($notify);
if ($notify->isCaptured()) {
// Update order status, send confirmation email, etc.
return redirect()->route('order.success');
}
return redirect()->route('order.failed');
}
}
use Payum\Core\Request\Refund;
$refund = new Refund();
$refund->setToken($token);
$refund->setAmount(500); // Refund 5.00 USD
$gateway->execute($refund);
Token Management
$token->getToken(); // Store this value in session/database
Test Mode Quirks
test: true is set in the gateway config.Currency and Locale
USD, not US). Validate this before submission.en_US, fr_FR). Check PayPal's docs for the latest list.Amount Precision
1000 for $10.00). Use integers, not floats.IPN (Instant Payment Notification)
payum/paypal-ipn).Enable Payum Logging
Add to config/logging.php:
'channels' => [
'payum' => [
'driver' => 'single',
'path' => storage_path('logs/payum.log'),
'level' => 'debug',
],
],
Then enable logging in your gateway:
$gateway->setLogger($this->app->make('monolog.logger.payum'));
Inspect Raw NVP Requests Payum logs raw NVP (Name-Value Pair) requests/responses. Check logs for malformed data:
[DEBUG] PaypalExpressCheckoutNvp Gateway: Sending NVP request: METHOD=SetExpressCheckout&...
PayPal API Limits
METHOD=SetExpressCheckout failures gracefully.Customize Request/Response
Extend the Payum\Core\Model\Array model to add custom fields:
use Payum\Core\Model\ArrayObject;
class CustomDetails extends ArrayObject
{
public function setCustomField($name, $value)
{
$this[$name] = $value;
}
}
Then attach it to your token:
$token->setDetails(new CustomDetails());
$token->getDetails()->setCustomField('shipping_address', $address);
Override Gateway Logic
Create a custom gateway class by extending Payum\Paypal\ExpressCheckout\Nvp\Gateway:
namespace App\Payum;
use Payum\Paypal\ExpressCheckout\Nvp\Gateway as BaseGateway;
class CustomPaypalGateway extends BaseGateway
{
protected function executeSetExpressCheckout(array $details)
{
// Custom logic before calling parent
$details['CUSTOM'] = 'YourCustomValue';
return parent::executeSetExpressCheckout($details);
}
}
Register it in your config:
'gateways' => [
'paypal_express' => [
'factory' => 'custom_paypal_express',
// ...
],
],
Handle Webhooks Manually For advanced use cases, bypass Payum's token system and use raw NVP requests:
$api = $gateway->getApi();
$response = $api->doNvp('SetExpressCheckout', [
'METHOD' => 'SetExpressCheckout',
'USER' => $gateway->getConfig()->getUsername(),
// ... other params
]);
Solution Type
Sole: Customer pays for the entire order (default).Mark: Customer pays for a portion (e.g., shipping).Order: Customer pays for multiple items (rarely used).Landing Page
Billing: Redirects to PayPalHow can I help you explore Laravel packages today?