dalpras/payment-paypal

PayPal Orders v2 connector for dalpras/payment-core.

Maintainers

Package info

github.com/dalpras/payment-paypal

pkg:composer/dalpras/payment-paypal

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.5.0 2026-05-21 09:08 UTC

This package is auto-updated.

Last update: 2026-05-21 09:16:37 UTC


README

PayPal Orders v2 / Payments v2 connector for dalpras/payment-core.

Supported flow:

  • create PayPal order
  • redirect buyer to the approval link
  • complete checkout by capturing or authorizing the order
  • persist PayPal capture and authorization ids as normalized metadata
  • capture authorized payments
  • refund captured payments
  • void authorizations through cancel
  • sync order state
  • parse and verify webhooks

Installation

composer require dalpras/payment-paypal

Dependencies

  • dalpras/payment-core
  • psr/http-client
  • psr/http-factory
  • psr/http-message

Bring your own PSR-18 client and PSR-17 factories.

Basic usage

use DalPraS\Payment\PayPal\Config\PayPalConfig;
use DalPraS\Payment\PayPal\Http\PayPalHttpClient;
use DalPraS\Payment\PayPal\Mapper\PayPalOrderMapper;
use DalPraS\Payment\PayPal\Provider\PayPalProvider;

$config = new PayPalConfig(
    clientId: 'sandbox-client-id',
    clientSecret: 'sandbox-client-secret',
    sandbox: true,
    webhookId: null,
    brandName: 'My Store',
);

$httpClient = new PayPalHttpClient(
    config: $config,
    httpClient: $psr18Client,
    requestFactory: $requestFactory,
    streamFactory: $streamFactory,
);

$provider = new PayPalProvider(
    config: $config,
    httpClient: $httpClient,
    mapper: new PayPalOrderMapper(),
);

Register the provider in PaymentManager through the core ProviderRegistry.

Checkout mapping

CheckoutRequest maps to POST /v2/checkout/orders.

Core field PayPal field
intent = sale intent = CAPTURE
intent = authorize / capture_later intent = AUTHORIZE
line items purchase_units[0].items
amount breakdown purchase_units[0].amount.breakdown
paymentReference purchase_units[0].custom_id
return/cancel URLs payment_source.paypal.experience_context
locale payment_source.paypal.experience_context.locale
idempotency key PayPal-Request-Id

Metadata returned by this provider

Checkout creation

[
    'provider' => 'paypal',
    'provider_payment_id' => $paypalOrderId,
    'order_id' => $paypalOrderId,
    'paypal_order_id' => $paypalOrderId,
]

Completion / authorization / sync

The provider extracts capture and authorization ids from PayPal order payloads:

[
    'provider' => 'paypal',
    'provider_payment_id' => $paypalOrderId,
    'order_id' => $paypalOrderId,
    'paypal_order_id' => $paypalOrderId,
    'capture_id' => $captureId,
    'paypal_capture_id' => $captureId,
    'authorization_id' => $authorizationId,
    'paypal_authorization_id' => $authorizationId,
]

Core stores these values and reuses them for future refund/capture/cancel operations.

Completion

completeCheckout() resolves the PayPal order id from:

  1. expectedProviderPaymentId
  2. queryParams['token']
  3. bodyParams['token']
  4. metadata['paypal_order_id']
  5. metadata['order_id']

Then it chooses the final action from expectedIntent:

  • sale -> capture the order
  • authorize / capture_later -> authorize the order

When used through PaymentManager, expectedProviderPaymentId, expectedIntent and metadata are normally filled automatically from the stored Payment.

Refunds

Refunds use POST /v2/payments/captures/{capture_id}/refund.

The provider resolves the capture id from:

  1. metadata['capture_id']
  2. metadata['paypal_capture_id']
  3. providerPaymentId

Full refund:

$result = $paymentManager->refund(new RefundRequest(
    providerCode: 'paypal',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: $refundId,
    metadata: [
        'description' => 'Customer refund',
    ],
));

Partial refund:

$result = $paymentManager->refund(new RefundRequest(
    providerCode: 'paypal',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: $refundId,
    metadata: [
        'amount_decimal' => '50.00',
        'currency' => 'EUR',
        'description' => 'Partial refund',
    ],
));

Capturing authorized payments

Captures use POST /v2/payments/authorizations/{authorization_id}/capture when an authorization id is available.

$result = $paymentManager->capture(new CaptureRequest(
    providerCode: 'paypal',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: $captureId,
    metadata: [
        'amount_decimal' => '50.00',
        'currency' => 'EUR',
        'description' => 'Capture authorized amount',
        'final_capture' => true,
    ],
));

Cancelling / voiding authorizations

cancel() voids an authorization when authorization_id or paypal_authorization_id is available. Through PaymentManager, that metadata is normally reused from completion/authorization.

$result = $paymentManager->cancel(new CancelRequest(
    providerCode: 'paypal',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: $voidId,
    metadata: [
        'description' => 'Customer cancelled before capture',
    ],
));

Webhook verification

The provider builds PayPal's webhook verification payload from incoming headers and the configured webhook id. Configure webhookId in PayPalConfig before using verifyWebhook() in production.

Testing

composer install
vendor/bin/phpunit

Syntax check:

find src tests -name '*.php' -print0 | xargs -0 -n1 php -l

License

MIT