Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9d85d2c
[ECP-9676] Increasing the accuracy of the TxVariant class by adding v…
khushboo-singhvi Jan 21, 2026
ecc908f
[ECP-9676] Increasing the accuracy of the TxVariant class by adding v…
khushboo-singhvi Jan 21, 2026
929cbfa
[ECP-9676] Increasing the accuracy of the TxVariant class by adding v…
khushboo-singhvi Jan 21, 2026
ac0f55f
[ECP-9676] Increasing the accuracy of the TxVariant class by adding v…
khushboo-singhvi Jan 22, 2026
aca68cc
[ECP-9676] Updating the class name
khushboo-singhvi Jan 23, 2026
809e0ef
[ECP-9676] Updating the class name
khushboo-singhvi Jan 23, 2026
dc407b2
[ECP-9676] Updating the class name
khushboo-singhvi Jan 23, 2026
5420dd1
Merge pull request #3230 from Adyen/ECP-9676
khushboo-singhvi Jan 23, 2026
a5a8772
[ECP-9900] Store card brand as ccType for wallet payments (#3236)
candemiralp Jan 30, 2026
bf84594
[ECP-8668] Remove PaymentMethodUtils and start using method configura…
candemiralp Feb 16, 2026
802601d
Remote tracking develop-11
candemiralp Mar 27, 2026
edb15d6
Delete empty class
candemiralp Mar 27, 2026
832909a
Remove unnecessary imports
candemiralp Mar 27, 2026
1aa1a74
Formatting
candemiralp Mar 27, 2026
bbed102
Fix incorrect variable name
candemiralp Mar 27, 2026
54b2838
Fix unit tests
candemiralp Mar 27, 2026
b063542
Merge branch 'develop-11' into feature/TxVariant-ManualCaptureHandling
candemiralp Mar 27, 2026
36f9346
Merge branch 'develop-11' into feature/TxVariant-ManualCaptureHandling
shubhamk67 Mar 31, 2026
2fd2269
Fix wallet manual capture lookup and null-safe TxVariantFactory calls
shubhamk67 Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Helper/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Config
const XML_WEBHOOK_NOTIFICATION_PROCESSOR = 'webhook_notification_processor';
const AUTO_CAPTURE_OPENINVOICE = 'auto';
const XML_CAPTURE_MODE = 'capture_mode';
const XML_SEPA_FLOW = 'sepa_flow';
const XML_PAYPAL_CAPTURE_MODE = 'paypal_capture_mode';
const XML_RECURRING_CONFIGURATION = 'recurring_configuration';
const XML_ALLOW_MULTISTORE_TOKENS = 'allow_multistore_tokens';
const XML_THREEDS_FLOW = 'threeds_flow';
Expand Down Expand Up @@ -704,6 +706,29 @@ public function getCaptureMode(?int $storeId = null): ?string
return $this->getConfigData(self::XML_CAPTURE_MODE, Config::XML_ADYEN_ABSTRACT_PREFIX, $storeId);
}

/**
* @param int|null $storeId
* @return string|null
*/
public function getSepaFlow(?int $storeId = null): ?string
{
return $this->getConfigData(self::XML_SEPA_FLOW, Config::XML_ADYEN_ABSTRACT_PREFIX, $storeId);
}

/**
* @param int|null $storeId
* @return bool
*/
public function isPaypalManualCapture(?int $storeId = null): bool
{
return $this->getConfigData(
self::XML_PAYPAL_CAPTURE_MODE,
Config::XML_ADYEN_ABSTRACT_PREFIX,
$storeId,
true
);
}

/**
* @return string|null
*/
Expand Down
252 changes: 94 additions & 158 deletions Helper/PaymentMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
use Adyen\AdyenException;
use Adyen\Client;
use Adyen\ConnectionException;
use Adyen\Payment\Helper\Util\PaymentMethodUtil;
use Adyen\Model\Checkout\PaymentMethodsRequest;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Config\Source\CaptureMode;
use Adyen\Payment\Model\Config\Source\RenderMode;
use Adyen\Payment\Model\Config\Source\SepaFlow;
use Adyen\Payment\Model\Method\TxVariantFactory;
use Adyen\Payment\Model\Notification;
use Adyen\Payment\Model\Ui\Adminhtml\AdyenMotoConfigProvider;
use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider;
Expand Down Expand Up @@ -54,6 +56,7 @@ class PaymentMethods extends AbstractHelper
const ADYEN_ONE_CLICK = 'adyen_oneclick';
const ADYEN_PAY_BY_LINK = 'adyen_pay_by_link';
const ADYEN_PAYPAL = 'adyen_paypal';
const ADYEN_SEPADIRECTDEBIT = 'adyen_sepadirectdebit';
const ADYEN_BOLETO = 'adyen_boleto';
const ADYEN_PREFIX = 'adyen_';
const ADYEN_CC_VAULT = 'adyen_cc_vault';
Expand Down Expand Up @@ -114,6 +117,7 @@ class PaymentMethods extends AbstractHelper
* @param ShopperConversionId $generateShopperConversionId
* @param CheckoutSession $checkoutSession
* @param RequestInterface $request
* @param TxVariantFactory $txVariantFactory
*/
public function __construct(
Context $context,
Expand All @@ -134,7 +138,8 @@ public function __construct(
protected readonly Locale $localeHelper,
protected readonly ShopperConversionId $generateShopperConversionId,
protected readonly CheckoutSession $checkoutSession,
protected readonly RequestInterface $request
protected readonly RequestInterface $request,
protected readonly TxVariantFactory $txVariantFactory
) {
parent::__construct($context);
}
Expand Down Expand Up @@ -636,6 +641,17 @@ public function isWalletPaymentMethod(MethodInterface $paymentMethodInstance): b
return boolval($paymentMethodInstance->getConfigData('is_wallet'));
}

/**
* This method returns the `supports_manual_capture` configuration value of a given payment method instance.
*
* @param MethodInterface $paymentMethodInstance
* @return bool
*/
public function supportsManualCapture(MethodInterface $paymentMethodInstance): bool
{
return boolval($paymentMethodInstance->getConfigData('supports_manual_capture'));
}

/**
* @param MethodInterface $paymentMethodInstance
* @return bool
Expand Down Expand Up @@ -720,182 +736,102 @@ public function getCcAvailableTypesByAlt(): array

/**
* Checks whether if the capture mode is auto on an order with the given notification `paymentMethod`.
* Note that, only a `notificationPaymentMethod` related to the order should be provided.
*
* @param Order $order Order object
* @param string $notificationPaymentMethod `paymentMethod` provided on the webhook of the given order
* @return bool
*/
public function isAutoCapture(Order $order, string $notificationPaymentMethod): bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The refactored isAutoCapture method has removed all the diagnostic logging that was present in the previous implementation. While the new logic is cleaner, losing these logs can make troubleshooting webhook processing significantly harder in production environments. Consider re-introducing relevant log messages to indicate why a specific capture mode was chosen.

{
// TODO::Add a validation checking `$notificationPaymentMethod` belongs to the correct order (webhook) or not.

$payment = $order->getPayment();
$paymentMethodInstance = $payment->getMethodInstance();

// validate if payment methods allows manual capture
if (PaymentMethodUtil::isManualCaptureSupported($notificationPaymentMethod)) {
$captureMode = trim(
(string) $this->configHelper->getConfigData(
'capture_mode',
'adyen_abstract',
$order->getStoreId()
)
);
$sepaFlow = trim(
(string) $this->configHelper->getConfigData(
'sepa_flow',
'adyen_abstract',
$order->getStoreId()
)
);
$paymentCode = $order->getPayment()->getMethod();
$autoCaptureOpenInvoice = $this->configHelper->getAutoCaptureOpenInvoice($order->getStoreId());
$manualCapturePayPal = trim(
(string) $this->configHelper->getConfigData(
'paypal_capture_mode',
'adyen_abstract',
$order->getStoreId()
)
$isAutoCapture = true;

/*
* First, validate the incoming tx_variant supports manual capture on the tx_variant level.
* Then check configuration and payment method specific settings.
*/
if ($this->txVariantSupportsManualCapture($notificationPaymentMethod)) {
$captureModePos = $this->configHelper->getAdyenPosCloudConfigData(
'capture_mode_pos',
$order->getStoreId()
);

/*
* if you are using authcap the payment method is manual.
* There will be a capture send to indicate if payment is successful
*/
if ($notificationPaymentMethod == "sepadirectdebit") {
if ($sepaFlow == "authcap") {
$this->adyenLogger->addAdyenNotification(
'Manual Capture is applied for sepa because it is in authcap flow',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return false;
// Use order payment method to evaluate the capture mode instead of notification payment method for POS
if ($order->getPayment()->getMethod() === AdyenPosCloudConfigProvider::CODE &&
$captureModePos === CaptureMode::CAPTURE_MODE_MANUAL) {
// Evaluate capture mode of POS payments based on the order's `paymentMethod` field and configuration
$isAutoCapture = false;
} else {
// Evaluate capture mode of ECOM payments based on the webhook's `paymentMethod` field

$sepaFlow = $this->configHelper->getSepaFlow($order->getStoreId());
$isPaypalManualCapture = $this->configHelper->isPaypalManualCapture($order->getStoreId());
$autoCaptureOpenInvoice = $this->configHelper->getAutoCaptureOpenInvoice($order->getStoreId());
$captureMode = $this->configHelper->getCaptureMode($order->getStoreId());

/*
* `adyenTxVariant` can be `null` if a card variant is provided. In this case, skip payment
* method specific checks and use the global capture mode setting.
*/
$adyenTxVariant = $this->txVariantFactory->create(['txVariant' => $notificationPaymentMethod]);
Comment on lines +752 to +775

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a redundant instantiation of the TxVariant object. It is first created inside txVariantSupportsManualCapture and then created again on line 775. For better efficiency, consider creating the TxVariant object once at the beginning of isAutoCapture and passing it to txVariantSupportsManualCapture.

Comment on lines +752 to +775

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The TxVariant object for the notificationPaymentMethod is being created twice: once inside txVariantSupportsManualCapture (line 814) and again in isAutoCapture (line 775). This is redundant and inefficient. Consider refactoring txVariantSupportsManualCapture to return the TxVariant object (or null if it cannot be resolved) so it can be reused throughout the isAutoCapture logic.


if (isset($adyenTxVariant)) {
$webhookMethodInstance = $adyenTxVariant->getMethodInstance();
$webhookMethodCode = $webhookMethodInstance->getCode();

if (($webhookMethodCode === self::ADYEN_SEPADIRECTDEBIT && $sepaFlow === SepaFlow::SEPA_FLOW_AUTHCAP) ||
($webhookMethodCode === self::ADYEN_PAYPAL && $isPaypalManualCapture) ||
($this->isOpenInvoice($webhookMethodInstance) && !$autoCaptureOpenInvoice) ||
($captureMode === CaptureMode::CAPTURE_MODE_MANUAL)
) {
$isAutoCapture = false;
}
} else {
// payment method ideal, cash adyen_boleto has direct capture
$this->adyenLogger->addAdyenNotification(
'This payment method does not allow manual capture.(2) paymentCode:' .
$paymentCode . ' paymentMethod:' . $notificationPaymentMethod . ' sepaFLow:' . $sepaFlow,
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return true;
if ($captureMode === CaptureMode::CAPTURE_MODE_MANUAL) {
$isAutoCapture = false;
}
}
}
}

if ($paymentCode == "adyen_pos_cloud") {
$captureModePos = $this->configHelper->getAdyenPosCloudConfigData(
'capture_mode_pos',
$order->getStoreId()
);
if (strcmp((string) $captureModePos, 'auto') === 0) {
$this->adyenLogger->addAdyenNotification(
'This payment method is POS Cloud and configured to be working as auto capture ',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return true;
} elseif (strcmp((string) $captureModePos, 'manual') === 0) {
$this->adyenLogger->addAdyenNotification(
'This payment method is POS Cloud and configured to be working as manual capture ',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return false;
}
}
return $isAutoCapture;
}
Comment on lines 744 to +797

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The refactored isAutoCapture method has removed all the diagnostic logging that was present in the previous implementation. These logs are crucial for troubleshooting payment issues, as they explain exactly why a specific capture mode (auto vs manual) was selected for a given order and webhook notification. Please restore the logging statements to maintain observability.


// if auto capture mode for openinvoice is turned on then use auto capture
if ($autoCaptureOpenInvoice && $this->isOpenInvoice($paymentMethodInstance)) {
$this->adyenLogger->addAdyenNotification(
'This payment method is configured to be working as auto capture ',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return true;
}
/**
* This method checks if the tx_variant supports manual capture.
*
* @param string $txVariant
* @return bool
*/
private function txVariantSupportsManualCapture(string $txVariant): bool
{
$supportsManualCapture = false;
$cardVariants = $this->adyenHelper->getCcTypesAltData();

// if PayPal capture modues is different from the default use this one
if (strcmp($notificationPaymentMethod, 'paypal') === 0) {
if ($manualCapturePayPal) {
$this->adyenLogger->addAdyenNotification(
'This payment method is paypal and configured to work as manual capture',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return false;
} else {
$this->adyenLogger->addAdyenNotification(
'This payment method is paypal and configured to work as auto capture',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return true;
}
}
if (strcmp($captureMode, 'manual') === 0) {
$this->adyenLogger->addAdyenNotification(
'Capture mode for this payment is set to manual',
array_merge(
$this->adyenLogger->getOrderContext($order),
[
'paymentMethod' => $notificationPaymentMethod,
'pspReference' => $order->getPayment()->getData('adyen_psp_reference')
]
)
);
return false;
}
// Check the card (scheme or giftcard) method manual capture support
if (filter_var($cardVariants[$txVariant]['manual_capture'] ?? false, FILTER_VALIDATE_BOOLEAN)) {
$supportsManualCapture = true;
} else {
$adyenTxVariant = $this->txVariantFactory->create(['txVariant' => $txVariant]);

/*
* online capture after delivery, use Magento backend to online invoice
* (if the option auto capture mode for openinvoice is not set)
* If AdyenTxVariant object is evaluated, rely on the method instance.
* Otherwise, fallback back to auto capture by-default.
*/
if ($this->isOpenInvoice($paymentMethodInstance)) {
$this->adyenLogger->addAdyenNotification(
'Capture mode for klarna is by default set to manual',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return false;
if (isset($adyenTxVariant)) {
if (!empty($adyenTxVariant->getCard())) {
// Check the wallet method capture mode using card portion and method instance together
$supportsManualCapture = filter_var(
$cardVariants[$adyenTxVariant->getCard()]['manual_capture'] ?? false,
FILTER_VALIDATE_BOOLEAN
) && $this->supportsManualCapture($adyenTxVariant->getMethodInstance());
Comment on lines +823 to +826

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for checking manual capture support on wallet variants is incorrect. It currently uses the full $txVariant (e.g., mc_googlepay) to look up configuration in $cardVariants, but $cardVariants only contains base scheme keys (e.g., mc). It should use the extracted card part from the TxVariant object instead.

                    $supportsManualCapture = filter_var(
                        $cardVariants[$adyenTxVariant->getCard()]['manual_capture'] ?? false,
                        FILTER_VALIDATE_BOOLEAN
                    ) && $this->supportsManualCapture($adyenTxVariant->getMethodInstance());

} elseif ($this->supportsManualCapture($adyenTxVariant->getMethodInstance())) {
// Check the alternative payment method manual capture mode based on the method instance
$supportsManualCapture = true;
}
}

$this->adyenLogger->addAdyenNotification(
'Capture mode is set to auto capture',
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);
return true;
} else {
// does not allow manual capture so is always immediate capture
$this->adyenLogger->addAdyenNotification(
sprintf('Payment method %s, does not allow manual capture', $notificationPaymentMethod),
array_merge(
$this->adyenLogger->getOrderContext($order),
['pspReference' => $order->getPayment()->getData('adyen_psp_reference')]
)
);

return true;
}

return $supportsManualCapture;
}

/**
Expand Down
Loading
Loading