Skip to content

[ECP-8668] Refactor the logic of getting capture mode of payment methods#3286

Merged
shubhamk67 merged 19 commits into
develop-11from
feature/TxVariant-ManualCaptureHandling
Apr 17, 2026
Merged

[ECP-8668] Refactor the logic of getting capture mode of payment methods#3286
shubhamk67 merged 19 commits into
develop-11from
feature/TxVariant-ManualCaptureHandling

Conversation

@candemiralp
Copy link
Copy Markdown
Contributor

@candemiralp candemiralp commented Mar 27, 2026

Description
This PR implements the functionality to leverage payment method configuration objects under config.xml to identify whether manual capture is supported or not. Also, etc/adyen_payment.xml document is being used to identify the mode of scheme payment methods.

Besides these changes, Helper/Util/PaymentMethodUtil class has been deleted in favor of using structured configuration fields of the payment methods.

khushboo-singhvi and others added 10 commits January 21, 2026 12:29
[ECP-9676] Increasing the accuracy of the TxVariant class by adding
* [ECP-9900] Refactor response handlers to get ccType consistently

* [ECP-9900] Use ccType while generating wallet card tokens

* [ECP-9900] Fix unit tests

* [ECP-9900] Run main workflows for feature branches always
…tion for capture_mode (#3234)

* [ECP-8668] Deprecate PaymentMethodUtil class

* [ECP-8668] Refactor isAutoCapture method

* [ECP-8668] Move the config values to an array

* [ECP-8668] Update Adyen card variants XML file

* [ECP-8668] Rename TxVariantInterpreter to TxVariant

* [ECP-8668] Consume newly built TxVariant class

* [ECP-8668] Remove deprecated PaymentMethodUtil

* [ECP-8668] Update the import statements

* [ECP-8668] Wallets check the method instance together with card portion
@candemiralp candemiralp requested a review from a team as a code owner March 27, 2026 14:42
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@candemiralp candemiralp changed the title Feature/tx variant manual capture handling [ECP-8668] Refactor the logic of getting capture mode of payment methods Mar 27, 2026
@candemiralp candemiralp added the Breaking change Indicates a change that has caused a major version update label Mar 27, 2026
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
49.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@amihajlovski
Copy link
Copy Markdown

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the payment method variant handling and capture logic by introducing a TxVariant model and factory, replacing the static PaymentMethodUtil helper. The configuration schema and XML have been updated to include card_type and manual_capture flags for payment types. Review feedback highlights a logic error in manual capture detection for wallets, potential TypeError risks in the factory and response handlers due to missing validation of input data, and redundant object instantiation. Additionally, a typo in the Maestro UK label was identified, and the loss of diagnostic logging during the refactor was noted as a concern for production troubleshooting.

Comment thread Helper/PaymentMethods.php
Comment on lines +823 to +826
$supportsManualCapture = filter_var(
$cardVariants[$txVariant]['manual_capture'] ?? false,
FILTER_VALIDATE_BOOLEAN
) && $this->supportsManualCapture($adyenTxVariant->getMethodInstance());
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());

if ($isWalletPaymentMethod) {
// Extract the scheme card brand from the wallet payment response
$txVariant = $this->txVariantFactory->create([
'txVariant' => $response['paymentMethod']['brand']
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

Accessing $response['paymentMethod']['brand'] without checking if the key exists can lead to PHP notices and a subsequent TypeError when passing null to the TxVariantFactory::create method (which expects a string). Ensure the brand is present and not empty before attempting to create the variant.

                $brand = $response['paymentMethod']['brand'] ?? null;
                if ($brand) {
                    $txVariant = $this->txVariantFactory->create([
                        'txVariant' => $brand
                    ]);

                    $ccType = $txVariant->getCard();
                }

Comment thread Helper/Vault.php

// Based on the CA config, card type might contain wallet type or not (ie: `mc` or `mc_googlepay`)
if (empty($cardVariants[$ccType])) {
$validatedTxVariant = $this->txVariantFactory->create(['txVariant' => $ccType]);
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

If $ccType is null or empty, passing it to TxVariantFactory::create will cause a TypeError because the TxVariant constructor expects a string. Add a check to ensure $ccType is a valid string before calling the factory.

            if (!empty($ccType) && empty($cardVariants[$ccType])) {
                $validatedTxVariant = $this->txVariantFactory->create(['txVariant' => $ccType]);
                $ccType = $validatedTxVariant->getCard();
            }

Comment thread Helper/PaymentMethods.php
* @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.

Comment thread Helper/PaymentMethods.php
Comment on lines +752 to +775
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]);
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.

* @param array $data
* @return TxVariant|null
*/
public function create(array $data = []): ?TxVariant
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 create method should validate that the txVariant key exists in the $data array and is a string before attempting to instantiate the object. This prevents potential TypeError exceptions during object creation.

    public function create(array $data = []): ?TxVariant
    {
        if (!isset($data['txVariant']) || !is_string($data['txVariant'])) {
            return null;
        }

        try {

Comment thread etc/adyen_payment.xml Outdated
@shubhamk67
Copy link
Copy Markdown
Contributor

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the manual capture detection logic by migrating the supported payment methods list from a hardcoded utility to the adyen_payment.xml configuration. It introduces a TxVariant model and factory to resolve payment method instances and card types dynamically. Feedback highlights the need to restore diagnostic logging for observability, suggests optimizing redundant TxVariant object creation, and identifies a potential configuration regression regarding Bancontact's manual capture support.

Comment thread Helper/PaymentMethods.php
Comment on lines 744 to +797
public function isAutoCapture(Order $order, string $notificationPaymentMethod): bool
{
// 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]);

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;
}
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.

Comment thread Helper/PaymentMethods.php
Comment on lines +752 to +775
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]);
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.

Comment thread etc/adyen_payment.xml
<label>Bancontact</label>
<code_alt>bcmc</code_alt>
<card_type>scheme</card_type>
<manual_capture>0</manual_capture>
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 manual_capture support for Bancontact (BCMC) has been set to 0 (false). In the previous implementation (Helper/Util/PaymentMethodUtil.php), bcmc was included in the MANUAL_CAPTURE_SUPPORTED_PAYMENT_METHODS list. This change in configuration will result in Bancontact payments always being treated as auto-capture, which might be a regression if manual capture was intended to be supported for this method.

@shubhamk67 shubhamk67 force-pushed the feature/TxVariant-ManualCaptureHandling branch from fea941a to 2fd2269 Compare April 16, 2026 13:41
@shubhamk67 shubhamk67 merged commit 3f58561 into develop-11 Apr 17, 2026
3 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking change Indicates a change that has caused a major version update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants