PayPal and Adyen Integration in Magento 2: Architecture and Webhooks Compared
AI generated
M2
webhook
Magento 2.4.8-p4 · PHP 8.4 · Payment Integration
PayPal and Adyen Integration in Magento 2
Differences in Architecture and Webhooks in Detail

A PayPal and Adyen integration in Magento 2 is not a swap of two equivalent payment methods: client-side order creation meets server-side sessions, certificate-based signatures meet HMAC notifications. This article compares both architectures down to command class level.

15 min read PayPal · Adyen · Webhooks · Gateway Commands Magento 2.4.8-p4 · PHP 8.4

1. Why the architecture differences between PayPal and Adyen matter for Magento integrations

Anyone facing the decision in a Magento 2 project of whether to connect PayPal or Adyen often underestimates how differently the two providers are built technically. A PayPal and Adyen integration is not a question of two interchangeable payment methods sharing the same interface, it is two fundamentally different architectures: PayPal relies on client-side order creation through the Smart Payment Buttons, Adyen relies on a server-side Sessions API paired with the Drop-in Component. These differences directly affect order creation, webhook processing and the payment method facade in Magento, long before the first test transaction ever runs.

The consequence for development teams: a generic payment gateway adapter that serves both providers with the same internal logic almost always breaks on edge cases, such as partial refunds, duplicate webhook deliveries or diverging order states. A clean PayPal integration needs different command classes, different webhook controllers and a different state machine mapping than an Adyen integration, even though both are ultimately addressed through the same Magento payment method facade.

This article compares both architectures step by step: from order creation through webhook signature verification to the state machine and the PCI relevant aspects of tokenization. The goal is a technical understanding that allows an informed decision for the right PayPal and Adyen integration in a given project, instead of treating both providers as if they were the same thing.

2. PayPal architecture: REST API, Smart Payment Buttons and client-side order creation

The PayPal integration is based on the PayPal REST API v2, specifically the endpoints of the Orders API. The central building block on the frontend is the JavaScript SDK, which renders the Smart Payment Buttons. When the button is clicked, the browser calls createOrder directly, which internally issues a POST to /v2/checkout/orders and returns a PayPal order id, before Magento has even created its own order on the server side. This client-side order creation sets PayPal fundamentally apart from server-centric payment architectures.

Only after the customer approves in the PayPal popup or redirect does the frontend call approve and then, server-side, capture or authorize. For Magento this means: the backend integration has to accept the PayPal order id generated in the browser, store it in additional_information on the payment, and only then trigger the actual order creation and authorization on the server. This timing gap between order creation in the PayPal system and order creation in Magento is one of the most common sources of errors in a PayPal integration, especially when the customer closes the checkout tab before the server-side confirmation completes.

For the REST API communication itself, PayPal uses OAuth2 client credentials with a client id and secret, typically managed for Magento through a dedicated ConfigProvider module in system.xml. The architecture therefore stays comparatively lean: no server-side session object before checkout, but a tighter coupling between frontend JavaScript and backend API calls that must be kept cleanly synchronized in every PayPal integration.

3. Adyen architecture: Drop-in Component, Sessions API and server-side payment creation

The Adyen integration takes the opposite approach. Before any payment surface even appears on the frontend, Magento creates a payment session server-side through the Adyen Checkout Sessions API, including amount, currency and reference. Only the result of this server-side call, in particular the sessionData field, is passed to the Drop-in Component in the browser, which then renders the applicable payment methods.

This server-side payment creation has a decisive architectural advantage over PayPal's client-side model: amount and reference are never generated in a way the browser could manipulate, they are fixed exclusively on the server before the session even exists. The following example shows a lean client for the Sessions API, as it is typically encapsulated as its own service class in an Adyen integration.

Model/Adyen/SessionsClient.php

<?php

declare(strict_types=1);

namespace Mironsoft\Payment\Model\Adyen;

use Adyen\Client;
use Adyen\Service\Checkout;
use Magento\Framework\Exception\LocalizedException;

/**
 * Thin wrapper around the Adyen Checkout Sessions API.
 */
final class SessionsClient
{
    public function __construct(
        private readonly Client $adyenClient,
        private readonly string $merchantAccount,
    ) {
    }

    /**
     * Creates a payment session server-side before the Drop-in Component renders.
     *
     * @param string $orderIncrementId Magento order increment id used as Adyen reference.
     * @param int $amountMinorUnits Order total in minor currency units (e.g. cents).
     * @param string $currencyCode ISO 4217 currency code.
     * @param string $returnUrl Redirect target after payment method flows.
     * @return array<string, mixed> Session data including sessionId and sessionData.
     * @throws LocalizedException
     */
    public function createSession(
        string $orderIncrementId,
        int $amountMinorUnits,
        string $currencyCode,
        string $returnUrl,
    ): array {
        $checkout = new Checkout($this->adyenClient);

        try {
            return $checkout->sessions([
                'merchantAccount' => $this->merchantAccount,
                'reference' => $orderIncrementId,
                'amount' => [
                    'value' => $amountMinorUnits,
                    'currency' => $currencyCode,
                ],
                'returnUrl' => $returnUrl,
                'channel' => 'Web',
            ]);
        } catch (\Adyen\AdyenException $exception) {
            throw new LocalizedException(__('Adyen session could not be created.'), $exception);
        }
    }
}

After the payment method completes in the Drop-in Component, Adyen sends the result back to Magento through a redirect or an asynchronous notification. The actual confirmation, however, never relies solely on the redirect, it always additionally comes through the server-side notification, which makes the Adyen architecture more resilient against interrupted browser sessions than the more frontend-driven PayPal integration.

4. Webhook processing at PayPal: IPN and webhook events, signature verification, relevant event types

PayPal historically distinguishes between the older IPN mechanism (Instant Payment Notification) and the more modern PayPal Webhooks, which build on the REST API. For new PayPal integrations, only webhooks are relevant, IPN is considered legacy and should no longer be implemented in new Magento projects. A PayPal webhook delivers exactly one event per HTTP request, identified by the event_type field, for example PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED or PAYMENT.CAPTURE.REFUNDED.

Verifying an incoming webhook's signature relies on several HTTP headers: Paypal-Transmission-Id, Paypal-Transmission-Time, Paypal-Cert-Url, Paypal-Auth-Algo and Paypal-Transmission-Sig. PayPal signs the payload with a private key, whose public certificate must be fetched from the URL given in Paypal-Cert-Url and verified, either locally or through the Verify Webhook Signature endpoint of the REST API. Without this check, a Magento controller would theoretically accept any arbitrary payload as a genuine PayPal event, a significant security risk for any production PayPal integration.

The following controller shows a lean implementation that extracts the signature headers, delegates verification to a dedicated verifier, and only then processes the event based on its event_type.

Controller/Webhook/Paypal.php

<?php

declare(strict_types=1);

namespace Mironsoft\Payment\Controller\Webhook;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Mironsoft\Payment\Model\Paypal\WebhookSignatureVerifier;
use Psr\Log\LoggerInterface;

/**
 * Receives PayPal webhook events and verifies their transmission signature before dispatching.
 */
final class Paypal implements HttpPostActionInterface, CsrfAwareActionInterface
{
    public function __construct(
        private readonly RequestInterface $request,
        private readonly JsonFactory $resultJsonFactory,
        private readonly WebhookSignatureVerifier $signatureVerifier,
        private readonly LoggerInterface $logger,
    ) {
    }

    /**
     * Verifies the transmission signature and processes the webhook event payload.
     *
     * @return ResultInterface
     */
    public function execute(): ResultInterface
    {
        $result = $this->resultJsonFactory->create();
        $body = (string) $this->request->getContent();
        $headers = [
            'transmissionId' => (string) $this->request->getHeader('Paypal-Transmission-Id'),
            'transmissionTime' => (string) $this->request->getHeader('Paypal-Transmission-Time'),
            'certUrl' => (string) $this->request->getHeader('Paypal-Cert-Url'),
            'authAlgo' => (string) $this->request->getHeader('Paypal-Auth-Algo'),
            'transmissionSig' => (string) $this->request->getHeader('Paypal-Transmission-Sig'),
        ];

        if (!$this->signatureVerifier->isValid($headers, $body)) {
            $this->logger->warning('PayPal webhook signature verification failed.');
            return $result->setHttpResponseCode(400)->setData(['status' => 'invalid_signature']);
        }

        /** @var array<string, mixed> $event */
        $event = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        $eventType = (string) ($event['event_type'] ?? '');

        // Relevant event types for order and capture reconciliation
        match ($eventType) {
            'PAYMENT.CAPTURE.COMPLETED', 'PAYMENT.CAPTURE.DENIED', 'PAYMENT.CAPTURE.REFUNDED' => $this->logger->info(
                sprintf('Processing PayPal event %s', $eventType)
            ),
            default => $this->logger->info(sprintf('Ignoring unhandled PayPal event %s', $eventType)),
        };

        return $result->setData(['status' => 'ok']);
    }

    /**
     * Disables CSRF validation for this webhook endpoint since PayPal cannot supply a form key.
     *
     * @param RequestInterface $request
     * @return InvalidRequestException|null
     */
    public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
    {
        return null;
    }

    /**
     * @param RequestInterface $request
     * @return bool
     */
    public function validateForCsrf(RequestInterface $request): bool
    {
        return true;
    }
}

5. Webhook processing at Adyen: HMAC signature, notification items and idempotent processing

Adyen structures webhooks in a fundamentally different way than PayPal. A single HTTP request can contain an array called notificationItems that carries several NotificationRequestItem objects at once, for example when an authorisation and an immediate capture are reported in the same batch. An Adyen integration therefore has to loop over all items per request, instead of assuming exactly one event per call as with PayPal.

Signature verification happens per item using HMAC-SHA256, not through a certificate as with PayPal. The value sits in the additionalData.hmacSignature field of every single NotificationRequestItem and is checked against a shared HMAC key previously configured in the Adyen Customer Area. Since the key is symmetric, a local computation without an external certificate fetch is sufficient, which tends to make the HMAC check simpler to implement than PayPal's certificate-based signature verification, but no less mandatory for any secure Adyen integration.

Because Adyen can redeliver the same notification if no confirmation is received, processing must be strictly idempotent: a combination of pspReference and eventCode that has already been processed must not result in a duplicate booking a second time. The following controller checks the HMAC signature per item and additionally maintains an idempotency registry before an event is actually processed.

Controller/Webhook/Adyen.php

<?php

declare(strict_types=1);

namespace Mironsoft\Payment\Controller\Webhook;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
use Mironsoft\Payment\Model\Adyen\HmacValidator;
use Mironsoft\Payment\Model\Adyen\ProcessedNotificationRegistry;
use Psr\Log\LoggerInterface;

/**
 * Receives Adyen notification webhooks and verifies the HMAC signature per item.
 */
final class Adyen implements HttpPostActionInterface, CsrfAwareActionInterface
{
    public function __construct(
        private readonly RequestInterface $request,
        private readonly JsonFactory $resultJsonFactory,
        private readonly HmacValidator $hmacValidator,
        private readonly ProcessedNotificationRegistry $processedRegistry,
        private readonly LoggerInterface $logger,
    ) {
    }

    /**
     * Iterates all notification items in the payload and processes each idempotently.
     *
     * @return ResultInterface
     */
    public function execute(): ResultInterface
    {
        $result = $this->resultJsonFactory->create();
        $body = (string) $this->request->getContent();

        /** @var array{notificationItems: array<int, array{NotificationRequestItem: array<string, mixed>}>} $payload */
        $payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

        foreach ($payload['notificationItems'] as $wrapper) {
            $item = $wrapper['NotificationRequestItem'];
            $hmacSignature = (string) ($item['additionalData']['hmacSignature'] ?? '');

            if (!$this->hmacValidator->isValid($item, $hmacSignature)) {
                $this->logger->warning('Adyen notification item failed HMAC validation.');
                return $result->setHttpResponseCode(401)->setData(['notificationResponse' => '[failed]']);
            }

            $pspReference = (string) $item['pspReference'];

            // Idempotent processing: skip items already handled in a previous delivery attempt
            if ($this->processedRegistry->wasProcessed($pspReference, (string) $item['eventCode'])) {
                continue;
            }

            $this->processedRegistry->markProcessed($pspReference, (string) $item['eventCode']);
        }

        return $result->setData(['notificationResponse' => '[accepted]']);
    }

    public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
    {
        return null;
    }

    public function validateForCsrf(RequestInterface $request): bool
    {
        return true;
    }
}

6. Magento payment method facade: dedicated gateway command classes for both providers

Magento encapsulates every payment method through the payment method facade, implemented as Magento\Payment\Model\Method\Adapter, combined with a CommandPool from the gateway command pattern. For a clean PayPal and Adyen integration this means: two fully separate command pools, each with its own capture and refund command classes, wired through di.xml as a virtualType, instead of building a single generic command class with internal branching per provider.

This separation is not an end in itself: the PayPal capture command has to know the PayPal order id generated on the frontend and call it against the Orders API, while the Adyen capture command works against the Payments API using an originalReference pointing to the previous authorisation. Both command classes implement the same CommandInterface, but their internal logic is fundamentally different, which is not a problem for the Magento payment method facade as long as the wiring in di.xml stays cleanly separated per provider.

The following di.xml snippet shows how two independent command pools and two independent facade virtualTypes for PayPal and Adyen coexist side by side, without command classes or configuration overlapping.

etc/di.xml

<?xml version="1.0"?>
<!-- File: app/code/Mironsoft/Payment/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <!-- PayPal gateway command pool: one command class per operation -->
    <virtualType name="MironsoftPaypalCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="capture" xsi:type="string">Mironsoft\Payment\Gateway\Paypal\CaptureCommand</item>
                <item name="refund" xsi:type="string">Mironsoft\Payment\Gateway\Paypal\RefundCommand</item>
            </argument>
        </arguments>
    </virtualType>

    <!-- Adyen gateway command pool: separate implementation, same Command Pattern -->
    <virtualType name="MironsoftAdyenCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="capture" xsi:type="string">Mironsoft\Payment\Gateway\Adyen\CaptureCommand</item>
                <item name="refund" xsi:type="string">Mironsoft\Payment\Gateway\Adyen\RefundCommand</item>
            </argument>
        </arguments>
    </virtualType>

    <virtualType name="MironsoftPaypalFacade" type="Magento\Payment\Model\Method\Adapter">
        <arguments>
            <argument name="code" xsi:type="const">Mironsoft\Payment\Model\Paypal\ConfigProvider::CODE</argument>
            <argument name="commandPool" xsi:type="object">MironsoftPaypalCommandPool</argument>
        </arguments>
    </virtualType>

    <virtualType name="MironsoftAdyenFacade" type="Magento\Payment\Model\Method\Adapter">
        <arguments>
            <argument name="code" xsi:type="const">Mironsoft\Payment\Model\Adyen\ConfigProvider::CODE</argument>
            <argument name="commandPool" xsi:type="object">MironsoftAdyenCommandPool</argument>
        </arguments>
    </virtualType>

</config>

7. Differences in the order state machine: Pending, Authorized and Captured at PayPal vs. Adyen

Both providers conceptually pass through similar states, but name and report them differently. At PayPal, the Orders API returns an order status such as CREATED, APPROVED or COMPLETED, directly as the response to a synchronous API call. At Adyen, on the other hand, the state transition arrives almost exclusively asynchronously through notification event codes such as AUTHORISATION, CAPTURE or REFUND, each with a success flag that distinguishes genuine success from a merely accepted request.

For the Magento order state machine this means: a PayPal integration can often set the order status right after the synchronous API call, while an Adyen integration must wait for the asynchronous notification before an order can reliably be marked as authorized or captured. Anyone ignoring this difference and setting the order status right after the redirect for Adyen risks orders that are considered paid even though the actual authorisation is still pending on the server side, or has even failed.

The following table directly contrasts the most important architecture aspects of a PayPal and Adyen integration.

Architecture Aspect PayPal Adyen
Order creation Client-side through Smart Payment Buttons, JS SDK calls createOrder, order id is created in the browser context Server-side through the Sessions API, Magento creates the payment session before rendering the Drop-in
Checkout component Smart Payment Buttons, JS SDK with popup or redirect flow Drop-in Component, embedded web component without a required redirect
Webhook mechanism PayPal Webhooks, event based, one event per request Adyen Notifications, notificationItems array, several items per request possible
Signature verification Transmission signature through a PayPal certificate, Paypal-Transmission-Sig header, cert URL fetch HMAC-SHA256 through hmacSignature in additionalData, shared symmetric key
State mapping Order status CREATED, APPROVED, COMPLETED from a synchronous API response Notification event codes AUTHORISATION, CAPTURE, REFUND with a success flag

The state mapping in the table shows why a unified internal status enum in your own Magento extension makes sense: it translates both PayPal's synchronous order status and Adyen's asynchronous event codes onto the same internal set of states, so that downstream processes like invoicing or shipping do not depend directly on the respective provider's terminology.

8. Refunds and captures: API differences for partial refund and partial capture

A partial capture, meaning capturing a lower amount than originally authorized, works at PayPal through the capture endpoint of the Orders API using the final_capture field. If final_capture is set to false, the authorisation remains open for further partial captures until either the full amount is captured or the authorisation is explicitly closed. The following command class shows how a PayPal integration maps this distinction between full and partial capture on the server side.

Gateway/Paypal/CaptureCommand.php

<?php

declare(strict_types=1);

namespace Mironsoft\Payment\Gateway\Paypal;

use Magento\Payment\Gateway\CommandInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Mironsoft\Payment\Model\Paypal\OrdersApiClient;

/**
 * Captures a previously authorized PayPal order, either fully or partially.
 */
final class CaptureCommand implements CommandInterface
{
    public function __construct(
        private readonly OrdersApiClient $ordersApiClient,
    ) {
    }

    /**
     * Executes the capture call against the PayPal Orders API v2 endpoint.
     *
     * @param array<string, mixed> $commandSubject
     * @return void
     */
    public function execute(array $commandSubject): void
    {
        /** @var PaymentDataObjectInterface $paymentDataObject */
        $paymentDataObject = $commandSubject['payment'];
        $payment = $paymentDataObject->getPayment();
        $amount = (float) ($commandSubject['amount'] ?? 0.0);

        $paypalOrderId = (string) $payment->getAdditionalInformation('paypal_order_id');
        $isPartialCapture = $amount < (float) $payment->getOrder()->getGrandTotal();

        // Partial captures require final_capture=false so the authorization stays open
        $captureResponse = $this->ordersApiClient->captureOrder(
            orderId: $paypalOrderId,
            amount: $amount,
            currencyCode: (string) $payment->getOrder()->getOrderCurrencyCode(),
            isFinalCapture: !$isPartialCapture,
        );

        $payment->setTransactionId((string) $captureResponse['id']);
        $payment->setIsTransactionClosed($captureResponse['status'] === 'COMPLETED' && !$isPartialCapture);
    }
}

Adyen handles partial captures and partial refunds in a structurally similar way, but through different endpoints technically: a capture call against the Payments API references the original pspReference, a refund call does the same. Several partial refunds against the same pspReference are explicitly supported at Adyen, as long as the sum of all refunds does not exceed the originally captured amount, which Adyen validates server-side and rejects with an error if exceeded.

The practical difference for a PayPal and Adyen integration lies mainly in error handling: PayPal reports a rejected capture attempt synchronously as an HTTP error with a structured error code, while Adyen initially only accepts a refund or capture request synchronously and reports the actual result, for example REFUND_FAILED, only through the asynchronous notification. Anyone handling both flows in the same refund logic has to explicitly account for this difference in error feedback.

9. Security and PCI aspects: tokenization and differences in 3D Secure 2

Both providers drastically reduce Magento's PCI-DSS scope by never letting card data pass through your own server. At PayPal, either the hosted checkout flow or, when Advanced Card Fields are enabled, an iframe-based field set handles card data capture. At Adyen, the Drop-in Component performs the same task through encapsulated, isolated input fields, so that in both cases no plaintext card number ever reaches your own Magento server.

For recurring payments, PayPal relies on vault tokens that are linked to the customer through the REST API and referenced on subsequent orders, while Adyen uses its own tokenization scheme with recurringProcessingModel, which distinguishes between one-off and recurring charges. A careful PayPal and Adyen integration must store these different token formats strictly separately, since a PayPal vault token is worthless at Adyen and vice versa. This is exactly where a cleanly separated payment integration with dedicated gateway command classes pays off.

With 3D Secure 2, the two providers differ in how the challenge flows: PayPal largely delegates 3DS authentication to the underlying card payment flow and reports the result through liability_shift in the API response. Adyen controls 3D Secure 2 directly through the Drop-in Component, including a native challenge presentation without a full page redirect, which in practice leads to noticeably fewer purchase abandonments than classic redirect based 3DS flows. For any modern PayPal and Adyen integration, correctly handling 3D Secure 2 is not an optional extra, it is practically mandatory under PSD2 and SCA requirements in Europe.

10. Summary

A PayPal and Adyen integration in Magento 2 is technically more demanding than swapping two equivalent payment methods. PayPal relies on client-side order creation through Smart Payment Buttons and certificate-based webhook signatures, Adyen relies on server-side session creation through the Drop-in Component and HMAC-signed notification batches. Both architectures require dedicated gateway command classes, dedicated webhook controllers and a clean mapping onto a shared internal order state machine.

Anyone who respects these differences from the start, instead of building a generic adapter for both providers, avoids the typical sources of error in partial refunds, duplicate webhook deliveries and inconsistent order states. A cleanly separated PayPal integration and Adyen integration, each with its own command class, its own signature verification and its own state mapping, is the foundation for a stable, PCI-compliant payment integration that works reliably even at high transaction volumes.

PayPal and Adyen Integration in Magento 2: The Essentials at a Glance

Architecture

PayPal creates orders client-side through Smart Payment Buttons, Adyen creates payment sessions server-side through the Sessions API before the Drop-in.

Webhooks

PayPal delivers one certificate-signed event per request, Adyen delivers HMAC-signed notificationItems, sometimes several per request, to be processed idempotently.

Payment Facade

Two separate command pools and virtualTypes in di.xml, dedicated capture and refund commands per provider instead of a generic adapter.

Security

Separate tokenization schemes, different 3D Secure 2 flows and strict signature verification are mandatory for a PCI-compliant payment integration.

11. FAQ: PayPal and Adyen Integration in Magento 2

1What is the most important architecture difference between PayPal and Adyen integration?
PayPal creates orders client-side through the Smart Payment Buttons, Adyen creates payment sessions server-side through the Sessions API before the Drop-in Component renders.
2How does PayPal create an order compared to Adyen?
PayPal calls createOrder directly from the browser, so the order id is created client-side. Adyen instead creates a session server-side before a payment method becomes visible.
3What separates PayPal Webhooks from PayPal IPN?
IPN is legacy and REST-independent. Webhooks build on the REST API and are signed in a certificate based way through transmission headers.
4How does HMAC signature verification work at Adyen?
Every NotificationRequestItem carries an HMAC-SHA256 value in additionalData.hmacSignature, checked against a shared key without an external certificate fetch.
5Why does an Adyen request contain several events?
Adyen bundles several NotificationRequestItem objects in the notificationItems array, for example for an authorisation and capture reported in the same batch.
6What does the payment method facade look like for both providers?
Each provider gets its own command pool and its own adapter virtualType in di.xml, with separate capture and refund commands.
7Which order states does a PayPal payment go through?
Typical states are CREATED, APPROVED and COMPLETED, each returned directly as a synchronous response to the corresponding API call.
8How do partial refunds differ between PayPal and Adyen?
PayPal reports errors synchronously through an HTTP status code, Adyen only accepts the request synchronously and reports the result later through a notification.
9What does idempotent processing mean for webhooks?
Already processed combinations of reference and event type are recognized and skipped on redelivery to avoid duplicate bookings.
10What role does 3D Secure 2 play?
PayPal delegates 3DS to the card payment flow, Adyen controls it directly in the Drop-in Component with a native challenge presentation, which reduces purchase abandonment.

Mironsoft

Payment gateway integration, webhook security and custom payment gateways for Magento 2

How robust is your PayPal and Adyen integration really?

We review your existing payment integration for webhook security, state machine consistency and PCI relevant aspects, and build dedicated gateway command classes for PayPal, Adyen or another provider where needed.

Payment gateway integration

Clean PayPal and Adyen integration with dedicated gateway command classes instead of generic adapter logic

Webhook security audit

Review of signature schemes, idempotency and error handling in your existing webhook controllers

Custom payment gateway development

Dedicated payment method facade and command pattern implementation for providers outside the standard extensions