Installation Add the bundle via Composer:
composer require beelab/paypal-bundle
Enable it in config/bundles.php:
BeeLab\PaypalBundle\BeeLabPaypalBundle::class => ['all' => true],
Configuration Publish the default config:
php bin/console beelab:paypal:install
Update config/packages/beelab_paypal.yaml with your PayPal credentials (e.g., client_id, secret, mode: sandbox).
First Use Case: Create a Payment
Use the PaypalClient service in a controller:
use BeeLab\PaypalBundle\Service\PaypalClient;
class PaymentController extends AbstractController
{
public function createPayment(PaypalClient $paypalClient): Response
{
$payment = $paypalClient->createPayment([
'intent' => 'sale',
'payer' => ['payment_method' => 'paypal'],
'transactions' => [[
'amount' => ['total' => '10.00', 'currency' => 'USD'],
'description' => 'Test payment',
]],
'redirect_urls' => [
'return_url' => $this->generateUrl('payment_success'),
'cancel_url' => $this->generateUrl('payment_cancel'),
],
]);
return $this->redirect($payment->getApprovalLink());
}
}
Key Files to Review
config/packages/beelab_paypal.yaml: Configuration reference.Resources/doc/index.md: Official documentation (e.g., webhooks, refunds).Service/PaypalClient.php: Core API methods (e.g., createPayment(), executePayment()).Standard Checkout Flow
intent) and redirect to PayPal.
$payment = $paypalClient->createPayment($data);
return $this->redirect($payment->getApprovalLink());
return_url (e.g., payment_success route).
public function handleReturn(PaypalClient $paypalClient, Request $request): Response
{
$paymentId = $request->query->get('paymentId');
$payerId = $request->query->get('PayerID');
$payment = $paypalClient->executePayment($paymentId, $payerId);
// Save transaction or update order status.
}
Subscription Management
Use createPlan() and createSubscription() for recurring payments:
$plan = $paypalClient->createPlan([
'name' => 'Premium',
'billing_cycles' => [[
'frequency' => 'MONTH',
'frequency_interval' => 1,
'tenure_type' => 'REGULAR',
'sequence' => 1,
'amount' => ['currency' => 'USD', 'value' => '9.99'],
]],
]);
$subscription = $paypalClient->createSubscription([
'plan_id' => $plan->getId(),
'start_time' => (new \DateTime())->format(\DateTime::ATOM),
]);
Webhook Handling
config/routes.yaml to handle PayPal events (e.g., payment.capture.completed).PaypalWebhook service to verify and process events:
use BeeLab\PaypalBundle\Service\PaypalWebhook;
public function handleWebhook(PaypalWebhook $webhook, Request $request): Response
{
$event = $webhook->verifyAndParse($request->getContent());
// Process $event->getResource() (e.g., capture, refund).
}
PaypalClient over instantiating it directly.config/packages/beelab_paypal.yaml:
beelab_paypal:
client_id: '%env(PAYPAL_CLIENT_ID)%'
secret: '%env(PAYPAL_SECRET)%'
mode: '%env(PAYPAL_MODE)%' # 'sandbox' or 'live'
webhook_id: '%env(PAYPAL_WEBHOOK_ID)%' # For webhook verification.
.env (e.g., PAYPAL_CLIENT_ID).sandbox mode and PayPal’s developer accounts for testing.Webhook Verification
webhook_id and auth_algo/cert_url.webhook_id is set in config and use PaypalWebhook::verifyAndParse():
try {
$event = $webhook->verifyAndParse($rawBody, $headers);
} catch (\RuntimeException $e) {
// Log and ignore unverified events.
}
Idempotency Keys
idempotency_key for createPayment() to avoid duplicate transactions.$paymentData = [
'intent' => 'sale',
'idempotency_key' => Str::uuid()->toString(),
// ... other fields
];
Currency and Amount Formatting
"10.00", not 10 or 10.0).number_format():
$amount = number_format($order->getTotal(), 2, '.', '');
Redirect URLs
return_url and cancel_url must be publicly accessible and HTTPS.https://yourdomain.com/payment/success).Subscription Billing Cycles
tenure_type (e.g., REGULAR vs. TRIAL) can break subscriptions.debug: true in config to log PayPal API responses:
beelab_paypal:
debug: true
PaypalClient exceptions for PayPal error details (e.g., INVALID_RECEIVER_EMAIL).Custom Event Handlers
PaypalWebhook service to add custom logic for specific events:
$event = $webhook->verifyAndParse($rawBody);
if ($event->getEventType() === 'PAYMENT.CAPTURE.COMPLETED') {
$this->handleCaptureCompleted($event->getResource());
}
Custom Payment Data
custom field:
$paymentData = [
'transactions' => [[
'amount' => ['currency' => 'USD', 'total' => '10.00'],
'custom' => json_encode(['order_id' => $order->getId()]),
]],
];
Override Services
PaypalClient with a decorator for additional logic:
// src/Service/PaypalClientDecorator.php
class PaypalClientDecorator implements PaypalClientInterface
{
private $decorated;
public function __construct(PaypalClient $decorated)
{
$this->decorated = $decorated;
}
public function createPayment(array $data)
{
$data['custom'] = json_encode(['extra' => 'metadata']);
return $this->decorated->createPayment($data);
}
}
services.yaml:
services:
BeeLab\PaypalBundle\Service\PaypalClientInterface: '@App\Service\PaypalClientDecorator'
Add New API Methods
Paypal\Api\ classes:
use Paypal\Api\Refund
How can I help you explore Laravel packages today?