Skip to content

Commit

Permalink
Merge branch 'main' into ECP-9484
Browse files Browse the repository at this point in the history
  • Loading branch information
khushboo-singhvi authored Nov 12, 2024
2 parents 02ee38c + 331afcd commit 14bcbb1
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v1
tools: composer:v2

- name: Test plugin installation
run: |
Expand Down
88 changes: 87 additions & 1 deletion Helper/PaymentResponseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

namespace Adyen\Payment\Helper;

use Adyen\Model\Checkout\CancelOrderRequest;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\ResourceModel\PaymentResponse\CollectionFactory as PaymentResponseCollectionFactory;
use Exception;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\InputException;
Expand All @@ -21,6 +24,9 @@
use Magento\Sales\Model\OrderRepository;
use Magento\Sales\Model\ResourceModel\Order;
use Magento\Sales\Model\Order as OrderModel;
use Adyen\Payment\Helper\Data;
use Magento\Framework\Mail\Exception\InvalidArgumentException;
use Adyen\Client;

class PaymentResponseHandler
{
Expand Down Expand Up @@ -57,6 +63,8 @@ class PaymentResponseHandler
private OrderRepository $orderRepository;
private HistoryFactory $orderHistoryFactory;
private StateData $stateDataHelper;
private PaymentResponseCollectionFactory $paymentResponseCollectionFactory;
private Config $configHelper;

public function __construct(
AdyenLogger $adyenLogger,
Expand All @@ -67,7 +75,9 @@ public function __construct(
\Adyen\Payment\Helper\Order $orderHelper,
OrderRepository $orderRepository,
HistoryFactory $orderHistoryFactory,
StateData $stateDataHelper
StateData $stateDataHelper,
PaymentResponseCollectionFactory $paymentResponseCollectionFactory,
Config $configHelper
) {
$this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper;
Expand All @@ -78,6 +88,8 @@ public function __construct(
$this->orderRepository = $orderRepository;
$this->orderHistoryFactory = $orderHistoryFactory;
$this->stateDataHelper = $stateDataHelper;
$this->paymentResponseCollectionFactory = $paymentResponseCollectionFactory;
$this->configHelper = $configHelper;
}

public function formatPaymentResponse(
Expand Down Expand Up @@ -279,6 +291,15 @@ public function handlePaymentsDetailsResponse(
break;
case self::REFUSED:
case self::CANCELLED:
$activeGiftCards = $this->hasActiveGiftCardPayments(
$paymentsDetailsResponse['merchantReference']
);

if(null !== $activeGiftCards)
{
$this->cancelGiftCardOrders($activeGiftCards,$order);
}

// Cancel order in case result is refused
if (null !== $order) {
// Check if the current state allows for changing to new for cancellation
Expand Down Expand Up @@ -341,4 +362,69 @@ private function isValidMerchantReference(array $paymentsDetailsResponse, OrderI

return true;
}

// Method to check for existing Gift Card payments
private function hasActiveGiftCardPayments($merchantReference)
{
$paymentResponseCollection = $this->paymentResponseCollectionFactory->create()
->addFieldToFilter('merchant_reference', $merchantReference)
->addFieldToFilter('result_code', 'Authorised');

if ($paymentResponseCollection->getSize() > 0) {
return $paymentResponseCollection->getData();
}
return null;
}

private function cancelGiftCardOrders($activeGiftCards, $order)
{
//Cancel the Authorised GC Payments
$storeId = $order->getStoreId();
$client = $this->dataHelper->initializeAdyenClient($storeId);
$service = $this->dataHelper->initializeOrdersApi($client);
foreach ($activeGiftCards as $giftcardData) {
try {
// Decode JSON response and validate it
$response = json_decode($giftcardData['response'], true);
if (json_last_error() !== JSON_ERROR_NONE || !isset($response['order'])) {
throw new InvalidArgumentException('Invalid giftcard response data');
}

// Extract order data and PSPRef
$orderData = $response['order']['orderData'] ?? null;
$pspReference = $response['order']['pspReference'] ?? null;

if (!$orderData || !$pspReference) {
throw new InvalidArgumentException('Missing orderData or pspReference in the response');
}

// Prepare cancel request
$merchantAccount = $this->configHelper->getAdyenAbstractConfigData("merchant_account", $storeId);
$cancelRequest = [
'order' => [
'pspReference' => $pspReference,
'orderData' => $orderData,
],
'merchantAccount' => $merchantAccount,
];
$this->dataHelper->logRequest($cancelRequest, Client::API_CHECKOUT_VERSION, '/orders/cancel');
// Call the cancel service
$cancelResponse = $service->cancelOrder(new CancelOrderRequest($cancelRequest));
$response = $cancelResponse->toArray();
$this->dataHelper->logResponse($response);
if (is_null($response['resultCode'])) {
// In case the result is unknown we log the request and don't update the history
$this->adyenLogger->error(
"Unexpected result query parameter for cancel order request. Response: " . json_encode($response)
);
}
} catch (\Exception $e) {
// Log the error with relevant information for debugging
$this->adyenLogger->error('Error canceling partial payments', [
'exception' => $e->getMessage(),
'giftcardData' => $giftcardData,
]);
}
}
}
}
152 changes: 147 additions & 5 deletions Test/Unit/Helper/PaymentResponseHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
*/
namespace Adyen\Payment\Test\Unit\Helper;

namespace Adyen\Payment\Test\Unit\Helper;

use Adyen\Client;
use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Helper\Vault;
Expand All @@ -29,6 +28,10 @@
use Magento\Sales\Model\OrderRepository;
use Magento\Sales\Model\Order\Status\HistoryFactory;
use Adyen\Payment\Helper\StateData;
use Adyen\Payment\Model\ResourceModel\PaymentResponse\Collection;
use Adyen\Payment\Model\ResourceModel\PaymentResponse\CollectionFactory;
use Adyen\Payment\Helper\Config;
use ReflectionClass;

class PaymentResponseHandlerTest extends AbstractAdyenTestCase
{
Expand All @@ -43,7 +46,6 @@ class PaymentResponseHandlerTest extends AbstractAdyenTestCase
private $orderRepositoryMock;
private $orderHistoryFactoryMock;
private $stateDataHelperMock;

private $paymentResponseHandler;

protected function setUp(): void
Expand All @@ -61,6 +63,11 @@ protected function setUp(): void
'create'
]);
$this->stateDataHelperMock = $this->createMock(StateData::class);
$this->configHelperMock = $this->createMock(Config::class);

$this->paymentResponseMockForFactory = $this->createMock(Collection::class);

$this->paymentResponseCollectionFactoryMock = $this->createGeneratedMock(CollectionFactory::class, ['create']);

$orderHistory = $this->createMock(History::class);
$orderHistory->method('setStatus')->willReturnSelf();
Expand All @@ -74,7 +81,7 @@ protected function setUp(): void
$this->orderMock->method('getStatus')->willReturn('pending');
$this->orderMock->method('getIncrementId')->willReturn('00123456');

$this->orderHelperMock->method('setStatusOrderCreation')->willReturn( $this->orderMock);
$this->orderHelperMock->method('setStatusOrderCreation')->willReturn($this->orderMock);

$this->paymentResponseHandler = new PaymentResponseHandler(
$this->adyenLoggerMock,
Expand All @@ -85,7 +92,9 @@ protected function setUp(): void
$this->orderHelperMock,
$this->orderRepositoryMock,
$this->orderHistoryFactoryMock,
$this->stateDataHelperMock
$this->stateDataHelperMock,
$this->paymentResponseCollectionFactoryMock,
$this->configHelperMock
);
}

Expand Down Expand Up @@ -399,6 +408,67 @@ public function testHandlePaymentsDetailsResponseCancelOrRefused($resultCode)
]
];

$this->paymentResponseMockForFactory->expects($this->any())
->method('addFieldToFilter')
->willReturn($this->paymentResponseMockForFactory);

$this->paymentResponseMockForFactory->expects($this->any())
->method('getSize')
->willReturn(1); // Simulate there is at least one record

// Mock getData to return the desired array of data from the database
$this->paymentResponseMockForFactory->expects($this->any())
->method('getData')
->willReturn([
[
'merchant_reference' => '12345',
'result_code' => 'Authorised',
'response' => '{
"additionalData":{"paymentMethod":"svs","merchantReference":"123","acquirerCode":"Test"},
"amount":{"currency":"EUR","value":5000},
"merchantReference":"123",
"order":{"amount":{"currency":"EUR","value":17800},"expiresAt":"2024-10-10T13:11:37Z",
"orderData":"orderData....",
"pspReference":"XYZ654",
"reference":"123",
"remainingAmount":{"currency":"EUR","value":12800}},
"paymentMethod":{"brand":"svs","type":"giftcard"},
"pspReference":"ABC123",
"resultCode":"Authorised"
}'
]
]);

$this->paymentResponseCollectionFactoryMock->expects($this->any())
->method('create')
->willReturn($this->paymentResponseMockForFactory);

$merchantAccount = 'mock_merchant_account';
$storeId = 1;
$this->orderMock->expects($this->once())->method('getStoreId')->willReturn($storeId);
$this->configHelperMock->expects($this->any())
->method('getAdyenAbstractConfigData')
->with('merchant_account', $storeId)
->willReturn($merchantAccount);

// Create an instance of the class that has the private method
$class = new \ReflectionClass(PaymentResponseHandler::class);
$instance = $class->newInstanceWithoutConstructor();

// Inject the mocked factory into the instance if necessary
$property = $class->getProperty('paymentResponseCollectionFactory');
$property->setAccessible(true);
$property->setValue($instance, $this->paymentResponseCollectionFactoryMock);

// Use Reflection to access the private method
$method = $class->getMethod('hasActiveGiftCardPayments');
$method->setAccessible(true);

// Mock order cancellation
$this->orderMock->expects($this->any())
->method('canCancel')
->willReturn(true);

$this->adyenLoggerMock->expects($this->atLeastOnce())->method('addAdyenResult');

$result = $this->paymentResponseHandler->handlePaymentsDetailsResponse(
Expand Down Expand Up @@ -455,4 +525,76 @@ public function testHandlePaymentsDetailsResponseInvalidMerchantReference(){

$this->assertFalse($result);
}

public function testHandlePaymentsDetailsResponseValidMerchantReference()
{
$paymentsDetailsResponse = [
'resultCode' => PaymentResponseHandler::AUTHORISED,
'pspReference' => 'ABC123456789',
'paymentMethod' => [
'brand' => 'ideal'
],
'merchantReference' => '00123456' // assuming this is a valid reference
];
// Mock the isValidMerchantReference to return true
$reflectionClass = new ReflectionClass(PaymentResponseHandler::class);
$method = $reflectionClass->getMethod('isValidMerchantReference');
$method->setAccessible(true);
$isValidMerchantReference = $method->invokeArgs($this->paymentResponseHandler, [$paymentsDetailsResponse,$this->orderMock]);
$this->assertTrue($isValidMerchantReference);
}

public function testPaymentDetailsCallFailureLogsError()
{
$resultCode = 'some_result_code';
$paymentsDetailsResponse = ['error' => 'some error message'];

// Expect the logger to be called with the specific message
$this->adyenLoggerMock->expects($this->once())
->method('error');

// Call the method that triggers the logging, e.g., handlePaymentDetailsFailure()
$this->paymentResponseHandler->handlePaymentsDetailsResponse(
$paymentsDetailsResponse,
$this->orderMock
);
}

public function testLogsErrorAndReturnsFalseForUnknownResult()
{
// Arrange
$paymentsDetailsResponse = [
'merchantReference' => '00123456'
];

// Mock the logger to expect an error to be logged
$this->adyenLoggerMock->expects($this->once())
->method('error')
->with($this->stringContains('Unexpected result query parameter. Response: ' . json_encode($paymentsDetailsResponse)));

// Act: Call the method that will trigger the unexpected result handling
$result = $this->paymentResponseHandler->handlePaymentsDetailsResponse($paymentsDetailsResponse, $this->orderMock);

// Assert: Ensure the method returned false
$this->assertFalse($result);
}

public function testOrderStatusUpdateWhenResponseIsValid()
{
$paymentsDetailsResponse = [
'merchantReference' => '00123456',
'resultCode' => 'AUTHORISED'
];

$this->orderMock->expects($this->once())
->method('getState')
->willReturn('pending_payment');

// Mock the order repository to save the order
$this->orderRepositoryMock->expects($this->once())
->method('save')
->with($this->orderMock);

$this->paymentResponseHandler->handlePaymentsDetailsResponse($paymentsDetailsResponse, $this->orderMock);
}
}
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,11 @@
"Composer\\Config::disableProcessTimeout",
"vendor/bin/phpunit -c Test/phpunit.xml"
]
},
"config": {
"allow-plugins": {
"magento/composer-dependency-version-audit-plugin": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

0 comments on commit 14bcbb1

Please sign in to comment.