diff --git a/.github/Makefile b/.github/Makefile
index 179e2ec80e..04fe1c8b1e 100644
--- a/.github/Makefile
+++ b/.github/Makefile
@@ -60,8 +60,8 @@ flush:
enable-express:
bin/magento module:enable Adyen_ExpressCheckout
bin/magento setup:upgrade
- bin/magento config:set payment/adyen_express/show_google_pay_on "1,2,3"
- bin/magento config:set payment/adyen_express/show_apple_pay_on "1,2,3"
+ bin/magento config:set payment/adyen_googlepay/express_show_on "1,2,3"
+ bin/magento config:set payment/adyen_applepay/express_show_on "1,2,3"
bin/magento cache:clean
# Full plugin setup
diff --git a/.github/docker-compose.e2e.yml b/.github/docker-compose.e2e.yml
index e0842a2bdd..f5f51a8663 100644
--- a/.github/docker-compose.e2e.yml
+++ b/.github/docker-compose.e2e.yml
@@ -1,7 +1,7 @@
version: '3'
services:
playwright:
- image: mcr.microsoft.com/playwright:v1.40.1
+ image: mcr.microsoft.com/playwright:v1.47.2
shm_size: 1gb
ipc: host
cap_add:
diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml
index 9552773508..21c4b69c98 100644
--- a/.github/docker-compose.yml
+++ b/.github/docker-compose.yml
@@ -12,7 +12,7 @@ services:
MARIADB_USER: magento
MARIADB_PASSWORD: magento
elastic:
- image: elasticsearch:7.17.13
+ image: elasticsearch:7.17.23
container_name: elasticsearch
networks:
- backend
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d5144210b2..f064369e94 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,14 +1,9 @@
name: "CodeQL"
on:
- push:
- branches: [ "develop", "develop-6", "develop-7", "develop-8", "main", "main-6", "main-7", "main-8" ]
pull_request:
- branches: [ "develop", "main" ]
paths-ignore:
- - 'view/base/web/js/*'
- schedule:
- - cron: "6 1 * * 0"
+ - 'view/base/web/js/**'
jobs:
analyze:
diff --git a/.github/workflows/e2e-test-dispatch.yml b/.github/workflows/e2e-test-dispatch.yml
index 2ff1d8d028..81a78c862d 100644
--- a/.github/workflows/e2e-test-dispatch.yml
+++ b/.github/workflows/e2e-test-dispatch.yml
@@ -11,7 +11,7 @@ on:
expressBranch:
description: "Express Checkout Repository Pipeline"
required: true
- default: "develop"
+ default: "main"
testGroup:
description: "Test group"
required: true
@@ -42,11 +42,11 @@ jobs:
- uses: actions/checkout@v3
- name: Install Magento
- run: docker-compose -f .github/docker-compose.yml run --rm web make magento
+ run: docker compose -f .github/docker-compose.yml run --rm web make magento
- name: Start web server in background
- run: docker-compose -f .github/docker-compose.yml up -d web
-
+ run: docker compose -f .github/docker-compose.yml up -d web
+
- name: Setup permissions
run: docker exec magento2-container make fs
@@ -94,7 +94,7 @@ jobs:
run: docker exec magento2-container make fs
- name: Run E2E tests
- run: docker-compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh ${{inputs.testGroup}}
+ run: docker compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh ${{inputs.testGroup}}
env:
INTEGRATION_TESTS_BRANCH: ${{inputs.testBranch}}
MAGENTO_ADMIN_USERNAME: ${{secrets.MAGENTO_ADMIN_USERNAME}}
diff --git a/.github/workflows/e2e-test-express-checkout.yml b/.github/workflows/e2e-test-express-checkout.yml
index 91e505b3f3..48e9ff7220 100644
--- a/.github/workflows/e2e-test-express-checkout.yml
+++ b/.github/workflows/e2e-test-express-checkout.yml
@@ -4,16 +4,14 @@ run-name: Adyen Magento 2 Express Checkout Plugin E2E tests
on:
workflow_dispatch:
pull_request:
- types: [opened, synchronize]
+ branches: [main]
+ pull_request_target:
branches: [main]
jobs:
build:
- if: |
- ${{
- github.event.pull_request.draft == false &&
- (github.actor != 'renovate[bot]' || github.actor != 'lgtm-com[bot]')
- }}
+ if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'workflow_dispatch')
+ environment: ${{ (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) && 'external' || 'internal' }}
runs-on:
group: larger-runners
labels: ubuntu-latest-8-cores
@@ -32,10 +30,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Magento
- run: docker-compose -f .github/docker-compose.yml run --rm web make magento
+ run: docker compose -f .github/docker-compose.yml run --rm web make magento
- name: Start web server in background
- run: docker-compose -f .github/docker-compose.yml up -d web
+ run: docker compose -f .github/docker-compose.yml up -d web
- name: Setup permissions
run: docker exec magento2-container make fs
@@ -59,7 +57,7 @@ jobs:
run: docker exec magento2-container make fs
- name: Run E2E tests
- run: docker-compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh express-checkout
+ run: docker compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh express-checkout
env:
INTEGRATION_TESTS_BRANCH: develop
MAGENTO_ADMIN_USERNAME: ${{secrets.MAGENTO_ADMIN_USERNAME}}
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index b3b2cee25f..09824636c3 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -1,11 +1,6 @@
name: Magento 2 E2E Pipeline
run-name: Adyen Magento 2 Payment Plugin E2E tests
-
-on:
- pull_request:
- types: [opened, synchronize, ready_for_review]
- pull_request_target:
- types: [opened, synchronize, ready_for_review]
+on: [pull_request, pull_request_target]
jobs:
build:
@@ -29,10 +24,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Magento
- run: docker-compose -f .github/docker-compose.yml run --rm web make magento
+ run: docker compose -f .github/docker-compose.yml run --rm web make magento
- name: Start web server in background
- run: docker-compose -f .github/docker-compose.yml up -d web
+ run: docker compose -f .github/docker-compose.yml up -d web
- name: Setup permissions
run: docker exec magento2-container make fs
@@ -72,7 +67,7 @@ jobs:
run: docker exec magento2-container make fs
- name: Run E2E tests
- run: docker-compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh standard
+ run: docker compose -f .github/docker-compose.e2e.yml run --rm playwright /e2e.sh standard
env:
INTEGRATION_TESTS_BRANCH: develop
MAGENTO_ADMIN_USERNAME: ${{secrets.MAGENTO_ADMIN_USERNAME}}
diff --git a/.github/workflows/graphql-test.yml b/.github/workflows/graphql-test.yml
index 2193cea742..0b7a25ab7d 100644
--- a/.github/workflows/graphql-test.yml
+++ b/.github/workflows/graphql-test.yml
@@ -25,10 +25,10 @@ jobs:
- uses: actions/checkout@v3
- name: Install Magento
- run: docker-compose -f .github/docker-compose.yml run --rm web make magento
+ run: docker compose -f .github/docker-compose.yml run --rm web make magento
- name: Start web server in background
- run: docker-compose -f .github/docker-compose.yml up -d web
+ run: docker compose -f .github/docker-compose.yml up -d web
- name: Setup permissions
run: docker exec magento2-container make fs
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 68772eef69..462312b479 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -4,12 +4,10 @@ on:
pull_request:
pull_request_target:
workflow_dispatch:
- push:
- branches: [develop]
jobs:
build:
- if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'push') || (github.event_name == 'workflow_dispatch')
+ if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) || (github.event_name == 'workflow_dispatch')
environment: ${{ (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) && 'external' || 'internal' }}
runs-on: ubuntu-latest
diff --git a/.github/workflows/marketplace-release.yml b/.github/workflows/marketplace-release.yml
new file mode 100644
index 0000000000..47a503b466
--- /dev/null
+++ b/.github/workflows/marketplace-release.yml
@@ -0,0 +1,190 @@
+name: Marketplace Automation
+
+on:
+ workflow_dispatch:
+ release:
+ types: [published]
+
+jobs:
+ marketplace-automation:
+ runs-on: ubuntu-latest
+ # environment: internal
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Fetch the latest release
+ id: fetch_release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # for test only
+ # response=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/releases/latest")
+
+ # for live only
+ response=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }}")
+
+ release_url=$(echo $response | jq -r '.zipball_url')
+ release_notes=$(echo $response | jq -r '.body')
+ release_tagname=$(echo $response | jq -r '.tag_name')
+
+ # Remove 'v' prefix if it exists
+ if [[ $release_tagname == v* ]]; then
+ release_tagname=${release_tagname#v}
+ fi
+
+ echo "LATEST_RELEASE_URL=$release_url" >> $GITHUB_OUTPUT
+ echo "RELEASE_NOTES<
' . __('authResult:') . ' ' . $responseCode;
- $payment->getOrder()->setAdyenResulturlEventCode($responseCode);
- }
+ if (!empty($responseCode)) {
+ $comment .= '
' . __('authResult:') . ' ' . $responseCode;
+ $payment->getOrder()->setAdyenResulturlEventCode($responseCode);
+ }
- if ($pspReference) {
- $comment .= '
' . __('pspReference:') . ' ' . $pspReference;
+ if (isset($response['pspReference'])) {
+ $comment .= '
' . __('pspReference:') . ' ' . $response['pspReference'];
+ }
+ $comment .= '
';
}
$payment->getOrder()->addStatusHistoryComment($comment, $payment->getOrder()->getStatus());
-
return $this;
}
}
diff --git a/Gateway/Response/CheckoutPaymentsDetailsHandler.php b/Gateway/Response/CheckoutPaymentsDetailsHandler.php
index 370b4fca98..d9183c6115 100644
--- a/Gateway/Response/CheckoutPaymentsDetailsHandler.php
+++ b/Gateway/Response/CheckoutPaymentsDetailsHandler.php
@@ -32,7 +32,7 @@ public function __construct(
* This is being used for all checkout methods (adyen hpp payment method)
*
*/
- public function handle(array $handlingSubject, array $response)
+ public function handle(array $handlingSubject, array $responseCollection)
{
$paymentDataObject = SubjectReader::readPayment($handlingSubject);
@@ -48,6 +48,9 @@ public function handle(array $handlingSubject, array $response)
$payment->getOrder()->setCanSendNewEmailFlag(false);
}
+ // for partial payments, non-giftcard payments will always be the last element in the collection
+ // for non-partial, there is only one response in the collection
+ $response = end($responseCollection);
if (!empty($response['pspReference'])) {
// set pspReference as transactionId
$payment->setCcTransId($response['pspReference']);
@@ -57,7 +60,7 @@ public function handle(array $handlingSubject, array $response)
$payment->setTransactionId($response['pspReference']);
}
- // do not close transaction so you can do a cancel() and void
+ // do not close transaction, so you can do a cancel() and void
$payment->setIsTransactionClosed(false);
$payment->setShouldCloseParentTransaction(false);
}
diff --git a/Gateway/Response/VaultDetailsHandler.php b/Gateway/Response/VaultDetailsHandler.php
index 3ae2fa368f..7b34b5ae5b 100644
--- a/Gateway/Response/VaultDetailsHandler.php
+++ b/Gateway/Response/VaultDetailsHandler.php
@@ -35,13 +35,16 @@ public function __construct(Vault $vaultHelper)
/**
* @param array $handlingSubject
- * @param array $response
+ * @param array $responseCollection
* @return void
- * @throws LocalizedException
*/
- public function handle(array $handlingSubject, array $response): void
+ public function handle(array $handlingSubject, array $responseCollection): void
{
- if (empty($response['additionalData'])) {
+ // for (non-) partial payments, the non-giftcard payment is always last.
+ $response = end($responseCollection);
+
+ // payments without additional data or only giftcards should be ignored.
+ if (empty($response['additionalData']) || $responseCollection['hasOnlyGiftCards']) {
return;
}
diff --git a/Gateway/Validator/CheckoutResponseValidator.php b/Gateway/Validator/CheckoutResponseValidator.php
index e639089138..102177dfd6 100644
--- a/Gateway/Validator/CheckoutResponseValidator.php
+++ b/Gateway/Validator/CheckoutResponseValidator.php
@@ -57,82 +57,119 @@ public function __construct(
* @param array $validationSubject
* @return ResultInterface
*/
- public function validate(array $validationSubject)
+ public function validate(array $validationSubject): ResultInterface
{
- $response = SubjectReader::readResponse($validationSubject);
- $paymentDataObjectInterface = SubjectReader::readPayment($validationSubject);
+ // Extract all the payment responses
+ $responseCollection = $validationSubject['response'];
+ unset($validationSubject['response']);
+ // Assign the remaining items to $commandSubject
+ $commandSubject = $validationSubject;
+
+ if (empty($responseCollection)) {
+ throw new ValidatorException(__("No responses were provided"));
+ }
+
+ // hasOnlyGiftCards is needed later but cannot be processed as a response
+ unset($responseCollection['hasOnlyGiftCards']);
+ foreach ($responseCollection as $thisResponse) {
+ $responseSubject = array_merge($commandSubject, ['response' => $thisResponse]);
+ $this->validateResponse($responseSubject);
+ }
+
+ return $this->createResult(true);
+ }
+
+ /**
+ * @throws ValidatorException
+ */
+ private function validateResponse($responseSubject): void
+ {
+ $response = SubjectReader::readResponse($responseSubject);
+ $paymentDataObjectInterface = SubjectReader::readPayment($responseSubject);
$payment = $paymentDataObjectInterface->getPayment();
$payment->setAdditionalInformation('3dActive', false);
- $isValid = true;
- $errorMessages = [];
// validate result
- if (!empty($response['resultCode'])) {
- $resultCode = $response['resultCode'];
- $payment->setAdditionalInformation('resultCode', $resultCode);
-
- if (!empty($response['action'])) {
- $payment->setAdditionalInformation('action', $response['action']);
- }
-
- if (!empty($response['additionalData'])) {
- $payment->setAdditionalInformation('additionalData', $response['additionalData']);
- }
-
- if (!empty($response['pspReference'])) {
- $payment->setAdditionalInformation('pspReference', $response['pspReference']);
- }
-
- if (!empty($response['details'])) {
- $payment->setAdditionalInformation('details', $response['details']);
- }
-
- if (!empty($response['donationToken'])) {
- $payment->setAdditionalInformation('donationToken', $response['donationToken']);
- }
-
- switch ($resultCode) {
- case "Authorised":
- case "Received":
- // Save cc_type if available in the response
- if (!empty($response['additionalData']['paymentMethod'])) {
- $ccType = $this->adyenHelper->getMagentoCreditCartType(
- $response['additionalData']['paymentMethod']
- );
- $payment->setAdditionalInformation('cc_type', $ccType);
- $payment->setCcType($ccType);
- }
- break;
- case "IdentifyShopper":
- case "ChallengeShopper":
- case "PresentToShopper":
- case 'Pending':
- case "RedirectShopper":
- // nothing extra
- break;
- case "Refused":
- $errorMsg = __('The payment is REFUSED.');
- // this will result the specific error
- throw new ValidatorException($errorMsg);
- default:
- $errorMsg = __('Error with payment method please select different payment method.');
- throw new ValidatorException($errorMsg);
- }
- } else {
- if (!empty($response['error'])) {
- $this->adyenLogger->error($response['error']);
- }
+ if (empty($response['resultCode'])) {
+ $this->handleEmptyResultCode($response);
+ }
- if (!empty($response['errorCode']) && !empty($response['error']) && in_array($response['errorCode'], self::ALLOWED_ERROR_CODES, true)) {
- $errorMsg = __($response['error']);
- } else {
- $errorMsg = __('Error with payment method, please select a different payment method.');
- }
+ $this->validateResult($response, $payment);
+ }
+
+ /**
+ * @throws ValidatorException
+ */
+ private function validateResult($response, $payment)
+ {
+ $resultCode = $response['resultCode'];
+ $payment->setAdditionalInformation('resultCode', $resultCode);
+
+ if (!empty($response['action'])) {
+ $payment->setAdditionalInformation('action', $response['action']);
+ }
- throw new ValidatorException($errorMsg);
+ if (!empty($response['additionalData'])) {
+ $payment->setAdditionalInformation('additionalData', $response['additionalData']);
+ }
+
+ if (!empty($response['pspReference'])) {
+ $payment->setAdditionalInformation('pspReference', $response['pspReference']);
+ }
+
+ if (!empty($response['details'])) {
+ $payment->setAdditionalInformation('details', $response['details']);
+ }
+
+ if (!empty($response['donationToken'])) {
+ $payment->setAdditionalInformation('donationToken', $response['donationToken']);
+ }
+
+ switch ($resultCode) {
+ case "Authorised":
+ case "Received":
+ // Save cc_type if available in the response
+ if (!empty($response['additionalData']['paymentMethod'])) {
+ $ccType = $this->adyenHelper->getMagentoCreditCartType(
+ $response['additionalData']['paymentMethod']
+ );
+ $payment->setAdditionalInformation('cc_type', $ccType);
+ $payment->setCcType($ccType);
+ }
+ break;
+ case "IdentifyShopper":
+ case "ChallengeShopper":
+ case "PresentToShopper":
+ case 'Pending':
+ case "RedirectShopper":
+ // nothing extra
+ break;
+ case "Refused":
+ $errorMsg = __('The payment is REFUSED.');
+ // this will result the specific error
+ throw new ValidatorException($errorMsg);
+ default:
+ $errorMsg = __('Error with payment method please select different payment method.');
+ throw new ValidatorException($errorMsg);
+ }
+ }
+
+ /**
+ * @throws ValidatorException
+ */
+ private function handleEmptyResultCode($response): never
+ {
+ if (!empty($response['error'])) {
+ $this->adyenLogger->error($response['error']);
+ }
+
+ if (!empty($response['errorCode']) && !empty($response['error']) && in_array($response['errorCode'], self::ALLOWED_ERROR_CODES, true)) {
+ $errorMsg = __($response['error']);
+ } else {
+ $errorMsg = __('Error with payment method, please select a different payment method.');
}
- return $this->createResult($isValid, $errorMessages);
+ throw new ValidatorException($errorMsg);
}
}
diff --git a/Helper/Data.php b/Helper/Data.php
index 81a41e5772..3b82731973 100755
--- a/Helper/Data.php
+++ b/Helper/Data.php
@@ -14,25 +14,32 @@
use Adyen\AdyenException;
use Adyen\Client;
use Adyen\Environment;
+use Adyen\Model\Checkout\ApplicationInfo;
+use Adyen\Model\Checkout\CommonField;
+use Adyen\Model\Checkout\UtilityRequest;
+use Adyen\Payment\Helper\Config as ConfigHelper;
use Adyen\Payment\Gateway\Request\HeaderDataBuilder;
use Adyen\Service\Checkout;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Config\Source\RenderMode;
use Adyen\Payment\Model\RecurringType;
use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory as NotificationCollectionFactory;
-use Adyen\Payment\Helper\Config as ConfigHelper;
use Adyen\Payment\Observer\AdyenPaymentMethodDataAssignObserver;
-use Adyen\Service\CheckoutUtility;
+use Adyen\Service\Checkout\ModificationsApi;
+use Adyen\Service\Checkout\OrdersApi;
+use Adyen\Service\Checkout\PaymentsApi;
+use Adyen\Service\Checkout\UtilityApi;
use Adyen\Service\PosPayment;
use Adyen\Service\Recurring;
+use Adyen\Service\RecurringApi;
use DateTime;
use Exception;
use Magento\Backend\Helper\Data as BackendHelper;
use Magento\Directory\Model\Config\Source\Country;
use Magento\Framework\App\CacheInterface;
+use Magento\Framework\App\Cache\Type\Config as ConfigCache;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Helper\AbstractHelper;
-use Magento\Framework\App\Cache\Type\Config as ConfigCache;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ProductMetadataInterface;
@@ -1181,6 +1188,30 @@ public function buildRequestHeaders($payment = null)
return $headers;
}
+ public function buildApplicationInfo(Client $client) :ApplicationInfo
+ {
+ $applicationInfo = new ApplicationInfo();
+
+ $adyenLibrary['name'] = $client->getLibraryName(); // deprecated but no alternative was given.
+ $adyenLibrary['version'] = $client->getLibraryVersion(); // deprecated but no alternative was given.
+
+ $applicationInfo->setAdyenLibrary(new CommonField($adyenLibrary));
+
+ if ($adyenPaymentSource = $client->getConfig()->getAdyenPaymentSource()) {
+ $applicationInfo->setAdyenPaymentSource(new CommonField($adyenPaymentSource));
+ }
+
+ if ($externalPlatform = $client->getConfig()->getExternalPlatform()) {
+ $applicationInfo->setExternalPlatform($externalPlatform);
+ }
+
+ if ($merchantApplication = $client->getConfig()->getMerchantApplication()) {
+ $applicationInfo->setMerchantApplication(new CommonField($merchantApplication));
+ }
+
+ return $applicationInfo;
+ }
+
/**
* @throws AdyenException
* @throws NoSuchEntityException
@@ -1197,6 +1228,26 @@ public function initializeAdyenClientWithClientConfig(array $clientConfig): Clie
return $this->initializeAdyenClient($storeId, null, $motoMerchantAccount);
}
+ public function initializePaymentsApi(Client $client):PaymentsApi
+ {
+ return new PaymentsApi($client);
+ }
+
+ public function initializeModificationsApi(Client $client):ModificationsApi
+ {
+ return new ModificationsApi($client);
+ }
+
+ public function initializeRecurringApi(Client $client):RecurringApi
+ {
+ return new RecurringApi($client);
+ }
+
+ public function initializeOrdersApi(Client $client): OrdersApi
+ {
+ return new OrdersApi($client);
+ }
+
/**
* @param Client $client
* @return PosPayment
@@ -1281,8 +1332,9 @@ private function getOriginKeyForOrigin($origin, $storeId = null)
$client = $this->initializeAdyenClient($storeId);
try {
- $service = $this->createAdyenCheckoutUtilityService($client);
- $response = $service->originKeys($params);
+ $service = new UtilityApi($client);
+ $responseObj = $service->originKeys(new UtilityRequest($params));
+ $response = json_decode(json_encode($responseObj->jsonSerialize()), true);
} catch (Exception $e) {
$this->adyenLogger->error($e->getMessage());
}
@@ -1318,16 +1370,6 @@ public function getCheckoutEnvironment($storeId = null)
}
}
- /**
- * @param Client $client
- * @return CheckoutUtility
- * @throws AdyenException
- */
- private function createAdyenCheckoutUtilityService($client)
- {
- return new CheckoutUtility($client);
- }
-
/**
* Method can be used by interceptors to provide the customer ID in a different way.
*
@@ -1376,6 +1418,7 @@ public function isHppVaultEnabled($storeId = null)
* @return Checkout
* @throws AdyenException
* @throws NoSuchEntityException
+ * @deprecared use `initializePaymentsApi`, or `initializeModificationsApi` based on your case
*/
public function createAdyenCheckoutService(Client $client = null): Checkout
{
@@ -1390,6 +1433,7 @@ public function createAdyenCheckoutService(Client $client = null): Checkout
* @param $client
* @return Recurring
* @throws AdyenException
+ * @deprecared use `initializeRecurringApi()`
*/
public function createAdyenRecurringService($client)
{
@@ -1516,6 +1560,14 @@ public function logResponse(array $response)
$this->adyenLogger->info('Response from Adyen API', $context);
}
+ public function logAdyenException(AdyenException $e)
+ {
+ $responseArray = [];
+ $responseArray['error'] = $e->getMessage();
+ $responseArray['errorCode'] = $e->getAdyenErrorCode();
+ $this->logResponse($responseArray);
+ }
+
private function filterReferences(array $data): array
{
return array_filter($data, function($value, $key) {
diff --git a/Helper/ManagementHelper.php b/Helper/ManagementHelper.php
index b745f1a486..2baa4059c3 100644
--- a/Helper/ManagementHelper.php
+++ b/Helper/ManagementHelper.php
@@ -16,8 +16,15 @@
*/
use Adyen\AdyenException;
-use Adyen\ConnectionException;
-use Adyen\Service\Management;
+use Adyen\Client;
+use Adyen\Model\Management\CreateAllowedOriginRequest;
+use Adyen\Model\Management\CreateMerchantWebhookRequest;
+use Adyen\Model\Management\TestWebhookRequest;
+use Adyen\Model\Management\TestWebhookResponse;
+use Adyen\Model\Management\UpdateMerchantWebhookRequest;
+use Adyen\Service\Management\AccountMerchantLevelApi;
+use Adyen\Service\Management\MyAPICredentialApi;
+use Adyen\Service\Management\WebhooksMerchantLevelApi;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Store\Model\StoreManager;
@@ -29,32 +36,30 @@ class ManagementHelper
/**
* @var Data
*/
- private $dataHelper;
+ private Data $dataHelper;
/**
* @var StoreManager
*/
- private $storeManager;
+ private StoreManager $storeManager;
/**
* @var Config
*/
- private $configHelper;
+ private Config $configHelper;
/**
* @var EncryptorInterface
*/
- private $encryptor;
+ private EncryptorInterface $encryptor;
/**
- * Logging instance
- *
* @var AdyenLogger
*/
- private $adyenLogger;
+ private AdyenLogger $adyenLogger;
/**
* @var ManagerInterface
*/
- protected $messageManager;
+ protected ManagerInterface $messageManager;
/**
* ManagementHelper constructor.
@@ -66,12 +71,12 @@ class ManagementHelper
* @param ManagerInterface $messageManager
*/
public function __construct(
- StoreManager $storeManager,
+ StoreManager $storeManager,
EncryptorInterface $encryptor,
- Data $dataHelper,
- Config $configHelper,
- AdyenLogger $adyenLogger,
- ManagerInterface $messageManager
+ Data $dataHelper,
+ Config $configHelper,
+ AdyenLogger $adyenLogger,
+ ManagerInterface $messageManager
) {
$this->dataHelper = $dataHelper;
$this->storeManager = $storeManager;
@@ -82,18 +87,23 @@ public function __construct(
}
/**
- * @param Management $managementApiService
+ * @param AccountMerchantLevelApi $accountMerchantLevelApi
+ * @param MyAPICredentialApi $myAPICredentialApi
* @return array
- * @throws AdyenException | ConnectionException
+ * @throws AdyenException
* @throws NoSuchEntityException
*/
- public function getMerchantAccountsAndClientKey(Management $managementApiService): array
- {
+ public function getMerchantAccountsAndClientKey(
+ AccountMerchantLevelApi $accountMerchantLevelApi,
+ MyAPICredentialApi $myAPICredentialApi
+ ): array {
$merchantAccounts = [];
$page = 1;
$pageSize = 100;
- //get the merchant accounts using get /merchants.
- $responseMerchants = $managementApiService->merchantAccount->list(["pageSize" => $pageSize]);
+ $responseMerchantsObj = $accountMerchantLevelApi->listMerchantAccounts(
+ ['queryParams' => ['pageSize' => $pageSize]]
+ );
+ $responseMerchants = $responseMerchantsObj->toArray();
while (count($merchantAccounts) < $responseMerchants['itemsTotal']) {
foreach ($responseMerchants['data'] as $merchantAccount) {
$defaultDC = array_filter($merchantAccount['dataCenters'], function ($dc) {
@@ -107,12 +117,15 @@ public function getMerchantAccountsAndClientKey(Management $managementApiService
}
++$page;
if (isset($responseMerchants['_links']['next'])) {
- $responseMerchants = $managementApiService->merchantAccount->list(
- ["pageSize" => $pageSize, "pageNumber" => $page]
+ $responseMerchantsObj = $accountMerchantLevelApi->listMerchantAccounts(
+ ['queryParams' => ["pageSize" => $pageSize, "pageNumber" => $page]]
);
+ $responseMerchants = $responseMerchantsObj->toArray();
}
}
- $responseMe = $managementApiService->me->retrieve();
+
+ $responseMeObj = $myAPICredentialApi->getApiCredentialDetails();
+ $responseMe = $responseMeObj->toArray();
$currentMerchantAccount = $this->configHelper->getMerchantAccount($this->storeManager->getStore()->getId());
@@ -129,18 +142,18 @@ public function getMerchantAccountsAndClientKey(Management $managementApiService
* @param string $password
* @param string $url
* @param bool $demoMode
- * @param Management $managementApiService
+ * @param WebhooksMerchantLevelApi $service
* @return string|null
* @throws AdyenException
* @throws NoSuchEntityException
*/
public function setupWebhookCredentials(
- string $merchantId,
- string $username,
- string $password,
- string $url,
- bool $demoMode,
- Management $managementApiService
+ string $merchantId,
+ string $username,
+ string $password,
+ string $url,
+ bool $demoMode,
+ WebhooksMerchantLevelApi $service
): ?string {
$params = [
'url' => $url,
@@ -176,8 +189,9 @@ public function setupWebhookCredentials(
// Try to reuse saved webhookId if merchant account is the same.
if (!empty($webhookId) && $merchantId === $savedMerchantAccount) {
try {
- $response = $managementApiService->merchantWebhooks->update($merchantId, $webhookId, $params);
- } catch (AdyenException $exception){
+ $updateRequest = new UpdateMerchantWebhookRequest($params);
+ $response = $service->updateWebhook($merchantId, $webhookId, $updateRequest);
+ } catch (AdyenException $exception) {
$this->adyenLogger->error($exception->getMessage());
}
}
@@ -186,9 +200,9 @@ public function setupWebhookCredentials(
if (!isset($response) || empty($webhookId)) {
try {
$params['type'] = 'standard';
- $response = $managementApiService->merchantWebhooks->create($merchantId, $params);
+ $response = $service->setUpWebhook($merchantId, new CreateMerchantWebhookRequest($params));
// save webhook_id to configuration
- $webhookId = $response['id'];
+ $webhookId = $response->getId();
$this->configHelper->setConfigData($webhookId, 'webhook_id', Config::XML_ADYEN_ABSTRACT_PREFIX);
} catch (\Exception $exception) {
$this->adyenLogger->error($exception->getMessage());
@@ -198,8 +212,8 @@ public function setupWebhookCredentials(
if (!empty($webhookId)) {
try {
// generate hmac key and save
- $response = $managementApiService->merchantWebhooks->generateHmac($merchantId, $webhookId);
- $hmacKey = $response['hmacKey'];
+ $response = $service->generateHmacKey($merchantId, $webhookId);
+ $hmacKey = $response->getHmacKey();
$hmac = $this->encryptor->encrypt($hmacKey);
$mode = $demoMode ? 'test' : 'live';
$this->configHelper->setConfigData($hmac, 'notification_hmac_key_' . $mode, Config::XML_ADYEN_ABSTRACT_PREFIX);
@@ -217,66 +231,60 @@ public function setupWebhookCredentials(
}
/**
- * @param Management $managementApiService
+ * @param MyAPICredentialApi $service
* @return array
* @throws AdyenException
*/
- public function getAllowedOrigins(Management $managementApiService): array
+ public function getAllowedOrigins(MyAPICredentialApi $service): array
{
- $response = $managementApiService->allowedOrigins->list();
+ $responseObj = $service->getAllowedOrigins();
+ $response = $responseObj->toArray();
return !empty($response) ? array_column($response['data'], 'domain') : [];
}
/**
- * @param Management $managementApiService
+ * @param MyAPICredentialApi $service
* @param string $domain
* @return void
* @throws AdyenException
*/
- public function saveAllowedOrigin(Management $managementApiService, string $domain): void
+ public function saveAllowedOrigin(MyAPICredentialApi $service, string $domain): void
{
- $managementApiService->allowedOrigins->create(['domain' => $domain]);
+ $service->addAllowedOrigin(new CreateAllowedOriginRequest(['domain' => $domain]));
}
/**
* @param string $merchantId
* @param string $webhookId
- * @param Management $managementApiService
- * @return mixed|string
+ * @param WebhooksMerchantLevelApi $service
+ * @return TestWebhookResponse|null
*/
- public function webhookTest(string $merchantId, string $webhookId, Management $managementApiService)
- {
- $params = [
- 'types' => [
- 'AUTHORISATION'
- ]
- ];
-
+ public function webhookTest(
+ string $merchantId,
+ string $webhookId,
+ WebhooksMerchantLevelApi $service
+ ): ?TestWebhookResponse {
+ $testWebhookRequest = new TestWebhookRequest(['types' => ['AUTHORISATION']]);
+ $response = null;
try {
- $response = $managementApiService->merchantWebhooks->test($merchantId, $webhookId, $params);
-
- $this->adyenLogger->info(
- sprintf( 'response from webhook test %s',
- json_encode($response))
- );
-
- return $response;
+ $response = $service->testWebhook($merchantId, $webhookId, $testWebhookRequest);
+ $this->adyenLogger->info(sprintf('response from webhook test %s', $response));
} catch (AdyenException $exception) {
$this->adyenLogger->error($exception->getMessage());
-
- return $exception->getMessage();
}
+
+ return $response;
}
/**
* @param string $apiKey
* @param bool $demoMode
- * @return Management
+ * @return Client
* @throws AdyenException
* @throws NoSuchEntityException
*/
- public function getManagementApiService(string $apiKey, bool $demoMode): Management
+ public function getAdyenApiClient(string $apiKey, bool $demoMode): Client
{
$environment = $demoMode ? 'test' : 'live';
$storeId = $this->storeManager->getStore()->getId();
@@ -286,8 +294,40 @@ public function getManagementApiService(string $apiKey, bool $demoMode): Managem
$apiKey = $this->configHelper->getApiKey($environment);
}
- $client = $this->dataHelper->initializeAdyenClient($storeId, $apiKey, null, $environment === 'test');
+ return $this->dataHelper->initializeAdyenClient(
+ $storeId, $apiKey,
+ null,
+ $environment === 'test'
+ );
+ }
+
+ /**
+ * @param Client $client
+ * @return AccountMerchantLevelApi
+ * @throws AdyenException
+ */
+ public function getAccountMerchantLevelApi(Client $client): AccountMerchantLevelApi
+ {
+ return new AccountMerchantLevelApi($client);
+ }
+
+ /**
+ * @param Client $client
+ * @return MyAPICredentialApi
+ * @throws AdyenException
+ */
+ public function getMyAPICredentialApi(Client $client): MyAPICredentialApi
+ {
+ return new MyAPICredentialApi($client);
+ }
- return new Management($client);
+ /**
+ * @param Client $client
+ * @return WebhooksMerchantLevelApi
+ * @throws AdyenException
+ */
+ public function getWebhooksMerchantLevelApi(Client $client): WebhooksMerchantLevelApi
+ {
+ return new WebhooksMerchantLevelApi($client);
}
}
diff --git a/Helper/OpenInvoice.php b/Helper/OpenInvoice.php
index fad9152c18..85558ad24f 100644
--- a/Helper/OpenInvoice.php
+++ b/Helper/OpenInvoice.php
@@ -127,7 +127,7 @@ protected function getImageUrl($item): string
$product = $item->getProduct();
$imageUrl = "";
- if ($image = $product->getSmallImage()) {
+ if ($product && $image = $product->getSmallImage()) {
$imageUrl = $this->imageHelper->init($product, 'product_page_image_small')
->setImageFile($image)
->getUrl();
@@ -187,14 +187,14 @@ protected function formatLineItem(AdyenAmountCurrency $itemAmountCurrency, $item
$product = $item->getProduct();
return [
- 'id' => $product->getId(),
+ 'id' => $product ? $product->getId() : $item->getProductId(),
'amountExcludingTax' => $formattedPriceExcludingTax,
'amountIncludingTax' => $formattedPriceIncludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $item->getName(),
'quantity' => (int) ($qty ?? $item->getQty()),
'taxPercentage' => $formattedTaxPercentage,
- 'productUrl' => $product->getUrlModel()->getUrl($product),
+ 'productUrl' => $product ? $product->getUrlModel()->getUrl($product) : '',
'imageUrl' => $this->getImageUrl($item)
];
}
diff --git a/Helper/OrdersApi.php b/Helper/OrdersApi.php
index ee438a47cb..f173468263 100644
--- a/Helper/OrdersApi.php
+++ b/Helper/OrdersApi.php
@@ -12,17 +12,34 @@
namespace Adyen\Payment\Helper;
use Adyen\AdyenException;
+use Adyen\Model\Checkout\CreateOrderRequest;
use Adyen\Client;
-use Adyen\ConnectionException;
use Adyen\Payment\Logger\AdyenLogger;
+use Adyen\Service\Checkout\OrdersApi as CheckoutOrdersApi;
use Magento\Framework\Exception\NoSuchEntityException;
class OrdersApi
{
+ /**
+ * @var Config
+ */
private Config $configHelper;
+
+ /**
+ * @var Data
+ */
private Data $adyenHelper;
+
+ /**
+ * @var AdyenLogger
+ */
private AdyenLogger $adyenLogger;
+ /**
+ * @param Config $configHelper
+ * @param Data $adyenHelper
+ * @param AdyenLogger $adyenLogger
+ */
public function __construct(
Config $configHelper,
Data $adyenHelper,
@@ -40,7 +57,6 @@ public function __construct(
* @param string $merchantReference
* @return array
* @throws AdyenException
- * @throws ConnectionException
* @throws NoSuchEntityException
*/
public function createOrder(string $merchantReference, int $amount, string $currency, string $storeId): array
@@ -48,12 +64,13 @@ public function createOrder(string $merchantReference, int $amount, string $curr
$request = $this->buildOrdersRequest($amount, $currency, $merchantReference, $storeId);
$client = $this->adyenHelper->initializeAdyenClient($storeId);
- $checkoutService = $this->adyenHelper->createAdyenCheckoutService($client);
+ $checkoutService = new CheckoutOrdersApi($client);
try {
$this->adyenHelper->logRequest($request, Client::API_CHECKOUT_VERSION, '/orders');
- $response = $checkoutService->orders($request);
- } catch (ConnectionException $e) {
+ $responseObj = $checkoutService->orders(new CreateOrderRequest($request));
+ $response = $responseObj->toArray();
+ } catch (AdyenException $e) {
$this->adyenLogger->error(
"Connection to the endpoint failed. Check the Adyen Live endpoint prefix configuration."
);
diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php
index ed0638a728..8e5d42387d 100644
--- a/Helper/PaymentMethods.php
+++ b/Helper/PaymentMethods.php
@@ -15,6 +15,7 @@
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\Notification;
use Adyen\Payment\Model\Ui\Adminhtml\AdyenMotoConfigProvider;
@@ -26,6 +27,7 @@
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\View\Asset\Repository;
@@ -35,8 +37,11 @@
use Magento\Payment\Helper\Data as MagentoDataHelper;
use Magento\Payment\Model\MethodInterface;
use Magento\Quote\Api\CartRepositoryInterface;
+use Magento\Quote\Api\Data\CartInterface;
+use Magento\Quote\Model\Quote;
use Magento\Sales\Model\Order;
use Adyen\Payment\Helper\Data as AdyenDataHelper;
+use Magento\Sales\Model\Order\Payment;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;
use Magento\Vault\Api\PaymentTokenRepositoryInterface;
@@ -70,25 +75,116 @@ class PaymentMethods extends AbstractHelper
AdyenMotoConfigProvider::CODE
];
+ /**
+ * @var CartRepositoryInterface
+ */
protected CartRepositoryInterface $quoteRepository;
+
+ /**
+ * @var ScopeConfigInterface
+ */
protected ScopeConfigInterface $config;
+
+ /**
+ * @var Data
+ */
protected Data $adyenHelper;
+
+ /**
+ * @var MagentoDataHelper
+ */
private MagentoDataHelper $dataHelper;
+
+ /**
+ * @var ResolverInterface
+ */
protected ResolverInterface $localeResolver;
+
+ /**
+ * @var AdyenLogger
+ */
protected AdyenLogger $adyenLogger;
+
+ /**
+ * @var Data
+ */
protected Data $adyenDataHelper;
+
+ /**
+ * @var Repository
+ */
protected Repository $assetRepo;
+
+ /**
+ * @var RequestInterface
+ */
protected RequestInterface $request;
+
+ /**
+ * @var Source
+ */
protected Source $assetSource;
+
+ /**
+ * @var DesignInterface
+ */
protected DesignInterface $design;
+
+ /**
+ * @var ThemeProviderInterface
+ */
protected ThemeProviderInterface $themeProvider;
- protected \Magento\Quote\Model\Quote $quote;
+
+ /**
+ * @var CartInterface
+ */
+ protected CartInterface $quote;
+
+ /**
+ * @var ChargedCurrency
+ */
private ChargedCurrency $chargedCurrency;
+
+ /**
+ * @var Config
+ */
private Config $configHelper;
+
+ /**
+ * @var SerializerInterface
+ */
private SerializerInterface $serializer;
+
+ /**
+ * @var PaymentTokenRepositoryInterface
+ */
private PaymentTokenRepositoryInterface $paymentTokenRepository;
+
+ /**
+ * @var SearchCriteriaBuilder
+ */
private SearchCriteriaBuilder $searchCriteriaBuilder;
+ /**
+ * @param Context $context
+ * @param CartRepositoryInterface $quoteRepository
+ * @param ScopeConfigInterface $config
+ * @param Data $adyenHelper
+ * @param ResolverInterface $localeResolver
+ * @param AdyenLogger $adyenLogger
+ * @param Repository $assetRepo
+ * @param RequestInterface $request
+ * @param Source $assetSource
+ * @param DesignInterface $design
+ * @param ThemeProviderInterface $themeProvider
+ * @param ChargedCurrency $chargedCurrency
+ * @param Config $configHelper
+ * @param MagentoDataHelper $dataHelper
+ * @param SerializerInterface $serializer
+ * @param Data $adyenDataHelper
+ * @param PaymentTokenRepositoryInterface $paymentTokenRepository
+ * @param SearchCriteriaBuilder $searchCriteriaBuilder
+ */
public function __construct(
Context $context,
CartRepositoryInterface $quoteRepository,
@@ -129,6 +225,15 @@ public function __construct(
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
}
+ /**
+ * @param int $quoteId
+ * @param string|null $country
+ * @param string|null $shopperLocale
+ * @return string
+ * @throws AdyenException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
+ */
public function getPaymentMethods(int $quoteId, ?string $country = null, ?string $shopperLocale = null): string
{
// get quote from quoteId
@@ -143,11 +248,18 @@ public function getPaymentMethods(int $quoteId, ?string $country = null, ?string
return $this->fetchPaymentMethods($country, $shopperLocale);
}
+ /**
+ * @param string $methodCode
+ * @return bool
+ */
public function isAdyenPayment(string $methodCode): bool
{
return in_array($methodCode, $this->getAdyenPaymentMethods(), true);
}
+ /**
+ * @return array
+ */
public function getAdyenPaymentMethods() : array
{
$paymentMethods = $this->dataHelper->getPaymentMethodList();
@@ -163,8 +275,17 @@ function ($key) {
return array_keys($filtered);
}
- public function togglePaymentMethodsActivation(?bool $isActive =null, string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, int $scopeId = 0): array
- {
+ /**
+ * @param bool|null $isActive
+ * @param string $scope
+ * @param int $scopeId
+ * @return array
+ */
+ public function togglePaymentMethodsActivation(
+ ?bool $isActive = null,
+ string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
+ int $scopeId = 0
+ ): array {
$enabledPaymentMethods = [];
if (is_null($isActive)) {
@@ -191,7 +312,7 @@ public function togglePaymentMethodsActivation(?bool $isActive =null, string $sc
* @param int $scopeId
* @return void
*/
- public function removePaymentMethodsActivation(string $scope, int $scopeId) : void
+ public function removePaymentMethodsActivation(string $scope, int $scopeId): void
{
foreach ($this->getAdyenPaymentMethods() as $paymentMethod)
{
@@ -203,6 +324,14 @@ public function removePaymentMethodsActivation(string $scope, int $scopeId) : vo
}
}
+ /**
+ * @param string|null $country
+ * @param string|null $shopperLocale
+ * @return string
+ * @throws AdyenException
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
+ */
protected function fetchPaymentMethods(?string $country = null, ?string $shopperLocale = null): string
{
$quote = $this->getQuote();
@@ -240,7 +369,13 @@ protected function fetchPaymentMethods(?string $country = null, ?string $shopper
return json_encode($response);
}
- protected function filterStoredPaymentMethods($allowMultistoreTokens, $responseData, $customerId)
+ /**
+ * @param $allowMultistoreTokens
+ * @param $responseData
+ * @param $customerId
+ * @return mixed
+ */
+ protected function filterStoredPaymentMethods($allowMultistoreTokens, $responseData, $customerId): mixed
{
if (!$allowMultistoreTokens && isset($responseData['storedPaymentMethods'])) {
$searchCriteria = $this->searchCriteriaBuilder
@@ -264,6 +399,10 @@ function ($method) use ($gatewayTokens) {
return $responseData;
}
+ /**
+ * @return float
+ * @throws AdyenException
+ */
protected function getCurrentPaymentAmount(): float
{
$total = $this->chargedCurrency->getQuoteAmountCurrency($this->getQuote())->getAmount();
@@ -291,6 +430,10 @@ protected function getCurrentPaymentAmount(): float
throw new AdyenException($exceptionMessage);
}
+ /**
+ * @param Store $store
+ * @return string
+ */
protected function getCurrentCountryCode(Store $store): string
{
$quote = $this->getQuote();
@@ -314,17 +457,25 @@ protected function getCurrentCountryCode(Store $store): string
return "";
}
+ /**
+ * @param array $requestParams
+ * @param Store $store
+ * @return array
+ * @throws AdyenException
+ * @throws NoSuchEntityException
+ */
protected function getPaymentMethodsResponse(array $requestParams, Store $store): array
{
// initialize the adyen client
$client = $this->adyenHelper->initializeAdyenClient($store->getId());
// initialize service
- $service = $this->adyenHelper->createAdyenCheckoutService($client);
+ $service =$this->adyenHelper->initializePaymentsApi($client);
try {
$this->adyenHelper->logRequest($requestParams, Client::API_CHECKOUT_VERSION, '/paymentMethods');
- $responseData = $service->paymentMethods($requestParams);
+ $response = $service->paymentMethods(new PaymentMethodsRequest($requestParams));
+ $responseData = $response->toArray();
} catch (AdyenException $e) {
$this->adyenLogger->error(
"The Payment methods response is empty check your Adyen configuration in Magento."
@@ -343,26 +494,45 @@ protected function getPaymentMethodsResponse(array $requestParams, Store $store)
return $responseData;
}
- protected function getQuote(): \Magento\Quote\Model\Quote
+ /**
+ * @return CartInterface
+ */
+ protected function getQuote(): CartInterface
{
return $this->quote;
}
- protected function setQuote(\Magento\Quote\Model\Quote $quote): void
+ /**
+ * @param CartInterface $quote
+ * @return void
+ */
+ protected function setQuote(CartInterface $quote): void
{
$this->quote = $quote;
}
+ /**
+ * @return string|null
+ */
protected function getCurrentShopperReference(): ?string
{
$customerId = $this->getQuote()->getCustomerId();
return $customerId ? (string)$customerId : null;
}
+ /**
+ * @param $merchantAccount
+ * @param Store $store
+ * @param Quote $quote
+ * @param string|null $shopperLocale
+ * @param string|null $country
+ * @return array
+ * @throws AdyenException
+ */
protected function getPaymentMethodsRequest(
$merchantAccount,
Store $store,
- \Magento\Quote\Model\Quote $quote,
+ Quote $quote,
?string $shopperLocale = null,
?string $country = null
): array {
@@ -398,6 +568,12 @@ protected function getPaymentMethodsRequest(
return $paymentMethodRequest;
}
+ /**
+ * @param array $paymentMethods
+ * @param array $paymentMethodsExtraDetails
+ * @return array
+ * @throws LocalizedException
+ */
protected function showLogosPaymentMethods(array $paymentMethods, array $paymentMethodsExtraDetails): array
{
if (!$this->adyenHelper->showLogos()) {
@@ -445,6 +621,12 @@ protected function showLogosPaymentMethods(array $paymentMethods, array $payment
return $paymentMethodsExtraDetails;
}
+ /**
+ * @param array $paymentMethods
+ * @param array $paymentMethodsExtraDetails
+ * @return array
+ * @throws AdyenException
+ */
protected function addExtraConfigurationToPaymentMethods(
array $paymentMethods,
array $paymentMethodsExtraDetails
@@ -468,16 +650,29 @@ protected function addExtraConfigurationToPaymentMethods(
return $paymentMethodsExtraDetails;
}
+ /**
+ * @param MethodInterface $paymentMethodInstance
+ * @return bool
+ */
public function isWalletPaymentMethod(MethodInterface $paymentMethodInstance): bool
{
return boolval($paymentMethodInstance->getConfigData('is_wallet'));
}
+ /**
+ * @param MethodInterface $paymentMethodInstance
+ * @return bool
+ */
public function isAlternativePaymentMethod(MethodInterface $paymentMethodInstance): bool
{
return $paymentMethodInstance->getConfigData('group') === self::ADYEN_GROUP_ALTERNATIVE_PAYMENT_METHODS;
}
+ /**
+ * @param MethodInterface $paymentMethodInstance
+ * @return string
+ * @throws AdyenException
+ */
public function getAlternativePaymentMethodTxVariant(MethodInterface $paymentMethodInstance): string
{
if (!$this->isAlternativePaymentMethod($paymentMethodInstance)) {
@@ -487,16 +682,28 @@ public function getAlternativePaymentMethodTxVariant(MethodInterface $paymentMet
return str_replace('adyen_', '', $paymentMethodInstance->getCode());
}
+ /**
+ * @param MethodInterface $paymentMethodInstance
+ * @return bool
+ */
public function paymentMethodSupportsRecurring(MethodInterface $paymentMethodInstance): bool
{
return boolval($paymentMethodInstance->getConfigData('supports_recurring'));
}
+ /**
+ * @param Payment $payment
+ * @param string $method
+ * @return bool
+ */
public function checkPaymentMethod(Order\Payment $payment, string $method): bool
{
return $payment->getMethod() === $method;
}
+ /**
+ * @return array
+ */
public function getCcAvailableTypes(): array
{
$types = [];
@@ -514,6 +721,9 @@ public function getCcAvailableTypes(): array
return $types;
}
+ /**
+ * @return array
+ */
public function getCcAvailableTypesByAlt(): array
{
$types = [];
@@ -531,6 +741,11 @@ public function getCcAvailableTypesByAlt(): array
return $types;
}
+ /**
+ * @param Order $order
+ * @param string $notificationPaymentMethod
+ * @return bool
+ */
public function isAutoCapture(Order $order, string $notificationPaymentMethod): bool
{
// validate if payment methods allows manual capture
@@ -698,6 +913,13 @@ public function isAutoCapture(Order $order, string $notificationPaymentMethod):
}
}
+ /**
+ * @param Order $order
+ * @param Notification $notification
+ * @return bool
+ * @throws AdyenException
+ * @throws LocalizedException
+ */
public function compareOrderAndWebhookPaymentMethods(Order $order, Notification $notification): bool
{
$paymentMethodInstance = $order->getPayment()->getMethodInstance();
@@ -722,6 +944,10 @@ public function compareOrderAndWebhookPaymentMethods(Order $order, Notification
return false;
}
+ /**
+ * @param string $paymentMethod
+ * @return bool
+ */
public function isBankTransfer(string $paymentMethod): bool
{
if (strlen($paymentMethod) >= 12 && substr($paymentMethod, 0, 12) == "bankTransfer") {
@@ -732,6 +958,12 @@ public function isBankTransfer(string $paymentMethod): bool
return $isBankTransfer;
}
+ /**
+ * @param Order $order
+ * @param Notification $notification
+ * @param string $status
+ * @return string|null
+ */
public function getBoletoStatus(Order $order, Notification $notification, string $status): ?string
{
$additionalData = !empty($notification->getAdditionalData()) ? $this->serializer->unserialize(
diff --git a/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php
index 6c11537a98..dd5950ff6c 100644
--- a/Helper/PaymentResponseHandler.php
+++ b/Helper/PaymentResponseHandler.php
@@ -20,6 +20,7 @@
use Magento\Sales\Model\Order\Status\HistoryFactory;
use Magento\Sales\Model\OrderRepository;
use Magento\Sales\Model\ResourceModel\Order;
+use Magento\Sales\Model\Order as OrderModel;
class PaymentResponseHandler
{
@@ -139,6 +140,10 @@ public function handlePaymentsDetailsResponse(
return false;
}
+ if(!$this->isValidMerchantReference($paymentsDetailsResponse, $order)){
+ return false;
+ }
+
$this->adyenLogger->addAdyenResult('Updating the order');
$payment = $order->getPayment();
@@ -169,8 +174,9 @@ public function handlePaymentsDetailsResponse(
$paymentMethod
);
- if (!empty($paymentsDetailsResponse['resultCode'])) {
- $payment->setAdditionalInformation('resultCode', $paymentsDetailsResponse['resultCode']);
+ $resultCode = $paymentsDetailsResponse['resultCode'];
+ if (!empty($resultCode)) {
+ $payment->setAdditionalInformation('resultCode', $resultCode);
}
if (!empty($paymentsDetailsResponse['action'])) {
@@ -197,7 +203,7 @@ public function handlePaymentsDetailsResponse(
$this->vaultHelper->handlePaymentResponseRecurringDetails($payment, $paymentsDetailsResponse);
// If the response is valid, update the order status.
- if (!in_array($paymentsDetailsResponse['resultCode'], PaymentResponseHandler::ACTION_REQUIRED_STATUSES)) {
+ if (!in_array($resultCode, PaymentResponseHandler::ACTION_REQUIRED_STATUSES) && $order->getState() === OrderModel::STATE_PENDING_PAYMENT) {
/*
* Change order state from pending_payment to new and expect authorisation webhook
* if no additional action is required according to /paymentsDetails response.
@@ -214,7 +220,7 @@ public function handlePaymentsDetailsResponse(
$this->adyenLogger->error(__('Error cleaning the payment state data: %s', $exception->getMessage()));
}
- switch ($paymentsDetailsResponse['resultCode']) {
+ switch ($resultCode) {
case self::AUTHORISED:
if (!empty($paymentsDetailsResponse['pspReference'])) {
// set pspReference as transactionId
@@ -290,7 +296,7 @@ public function handlePaymentsDetailsResponse(
$this->adyenLogger->error(
sprintf("Payment details call failed for action, resultCode is %s Raw API responds: %s.
Cancel or Hold the order on OFFER_CLOSED notification.",
- $paymentsDetailsResponse['resultCode'],
+ $resultCode,
json_encode($paymentsDetailsResponse)
));
@@ -312,4 +318,27 @@ public function handlePaymentsDetailsResponse(
return $result;
}
+
+ /**
+ * Validate whether the merchant reference is present in the response and belongs to the current order.
+ *
+ * @param array $paymentsDetailsResponse
+ * @param OrderInterface $order
+ * @return bool
+ */
+ private function isValidMerchantReference(array $paymentsDetailsResponse, OrderInterface $order): bool
+ {
+ $merchantReference = $paymentsDetailsResponse['merchantReference'] ?? null;
+ if (!$merchantReference) {
+ $this->adyenLogger->error("No merchantReference in the response");
+ return false;
+ }
+
+ if ($order->getIncrementId() !== $merchantReference) {
+ $this->adyenLogger->error("Wrong merchantReference was set in the query or in the session");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/Helper/PaymentsDetails.php b/Helper/PaymentsDetails.php
index 279708403d..db54ba2ef4 100644
--- a/Helper/PaymentsDetails.php
+++ b/Helper/PaymentsDetails.php
@@ -12,6 +12,7 @@
namespace Adyen\Payment\Helper;
use Adyen\AdyenException;
+use Adyen\Model\Checkout\PaymentDetailsRequest;
use Adyen\Payment\Helper\Util\DataArrayValidator;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Checkout\Model\Session;
@@ -33,11 +34,32 @@ class PaymentsDetails
'merchantReference'
];
+ /**
+ * @var Session
+ */
private Session $checkoutSession;
+
+ /**
+ * @var Data
+ */
private Data $adyenHelper;
+
+ /**
+ * @var AdyenLogger
+ */
private AdyenLogger $adyenLogger;
+
+ /**
+ * @var Idempotency
+ */
private Idempotency $idempotencyHelper;
+ /**
+ * @param Session $checkoutSession
+ * @param Data $adyenHelper
+ * @param AdyenLogger $adyenLogger
+ * @param Idempotency $idempotencyHelper
+ */
public function __construct(
Session $checkoutSession,
Data $adyenHelper,
@@ -58,15 +80,15 @@ public function __construct(
public function initiatePaymentDetails(OrderInterface $order, array $payload): array
{
$request = $this->cleanUpPaymentDetailsPayload($payload);
-
try {
$client = $this->adyenHelper->initializeAdyenClient($order->getStoreId());
- $service = $this->adyenHelper->createAdyenCheckoutService($client);
+ $service = $this->adyenHelper->initializePaymentsApi($client);
$requestOptions['idempotencyKey'] = $this->idempotencyHelper->generateIdempotencyKey($request);
$requestOptions['headers'] = $this->adyenHelper->buildRequestHeaders();
- $response = $service->paymentsDetails($request, $requestOptions);
+ $paymentDetailsObj = $service->paymentsDetails(new PaymentDetailsRequest($request), $requestOptions);
+ $response = $paymentDetailsObj->toArray();
} catch (AdyenException $e) {
$this->adyenLogger->error("Payment details call failed: " . $e->getMessage());
$this->checkoutSession->restoreQuote();
@@ -77,6 +99,10 @@ public function initiatePaymentDetails(OrderInterface $order, array $payload): a
return $response;
}
+ /**
+ * @param array $payload
+ * @return array
+ */
private function cleanUpPaymentDetailsPayload(array $payload): array
{
$payload = DataArrayValidator::getArrayOnlyWithApprovedKeys(
diff --git a/Helper/Vault.php b/Helper/Vault.php
index 6058e0b40d..8360da3999 100644
--- a/Helper/Vault.php
+++ b/Helper/Vault.php
@@ -173,7 +173,7 @@ public function getAdyenTokenType(PaymentTokenInterface $paymentToken): ?string
return null;
}
- public function createVaultToken(OrderPaymentInterface $payment, string $detailReference): PaymentTokenInterface
+ public function createVaultToken(OrderPaymentInterface $payment, string $detailReference, ?string $cardHolderName = null): PaymentTokenInterface
{
$paymentMethodInstance = $payment->getMethodInstance();
$paymentMethodCode = $paymentMethodInstance->getCode();
@@ -211,6 +211,11 @@ public function createVaultToken(OrderPaymentInterface $payment, string $detailR
'maskedCC' => $additionalData['cardSummary'],
'expirationDate' => $additionalData['expiryDate']
];
+
+ if ($cardHolderName !== null) {
+ $details['cardHolderName'] = $cardHolderName;
+ }
+
$paymentToken->setExpiresAt($this->getExpirationDate($additionalData['expiryDate']));
} elseif ($paymentMethodCode === PaymentMethods::ADYEN_CC ||
$paymentMethodCode === AdyenPosCloudConfigProvider::CODE) {
@@ -220,6 +225,9 @@ public function createVaultToken(OrderPaymentInterface $payment, string $detailR
'maskedCC' => $additionalData['cardSummary'],
'expirationDate' => $additionalData['expiryDate']
];
+ if ($cardHolderName !== null) {
+ $details['cardHolderName'] = $cardHolderName;
+ }
$paymentToken->setExpiresAt($this->getExpirationDate($additionalData['expiryDate']));
} elseif ($this->paymentMethodsHelper->isAlternativePaymentMethod($paymentMethodInstance)) {
$paymentToken->setType(PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT);
@@ -293,9 +301,11 @@ public function handlePaymentResponseRecurringDetails(Payment $payment, array $r
if ($this->hasRecurringDetailReference($response) && $isRecurringEnabled) {
try {
+ $cardHolderName = $response['additionalData']['cardHolderName'] ?? null;
$paymentToken = $this->createVaultToken(
$payment,
- $response['additionalData'][self::RECURRING_DETAIL_REFERENCE]
+ $response['additionalData'][self::RECURRING_DETAIL_REFERENCE],
+ $cardHolderName
);
$extensionAttributes = $this->getExtensionAttributes($payment);
$extensionAttributes->setVaultPaymentToken($paymentToken);
diff --git a/Helper/Webhook/OfferClosedWebhookHandler.php b/Helper/Webhook/OfferClosedWebhookHandler.php
index ca9f8a796e..9695736d72 100644
--- a/Helper/Webhook/OfferClosedWebhookHandler.php
+++ b/Helper/Webhook/OfferClosedWebhookHandler.php
@@ -105,7 +105,10 @@ public function handleWebhook(MagentoOrder $order, Notification $notification, s
}
// Move the order from PAYMENT_REVIEW to NEW, so that it can be cancelled
- if (!$order->canCancel() && $this->configHelper->getNotificationsCanCancel($order->getStoreId())) {
+ if (!$order->isCanceled()
+ && !$order->canCancel()
+ && $this->configHelper->getNotificationsCanCancel($order->getStoreId())
+ ) {
$order->setState(MagentoOrder::STATE_NEW);
}
diff --git a/Model/Api/AdyenPaymentMethodsBalance.php b/Model/Api/AdyenPaymentMethodsBalance.php
index 71ec7af5cc..fc60a820e7 100644
--- a/Model/Api/AdyenPaymentMethodsBalance.php
+++ b/Model/Api/AdyenPaymentMethodsBalance.php
@@ -13,10 +13,12 @@
namespace Adyen\Payment\Model\Api;
use Adyen\AdyenException;
+use Adyen\Model\Checkout\BalanceCheckRequest;
use Adyen\Payment\Api\AdyenPaymentMethodsBalanceInterface;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Store\Model\StoreManager;
@@ -24,12 +26,38 @@ class AdyenPaymentMethodsBalance implements AdyenPaymentMethodsBalanceInterface
{
const FAILED_RESULT_CODE = 'Failed';
+ /**
+ * @var Json
+ */
private Json $jsonSerializer;
+
+ /**
+ * @var StoreManager
+ */
private StoreManager $storeManager;
+
+ /**
+ * @var Config
+ */
private Config $config;
+
+ /**
+ * @var Data
+ */
private Data $adyenHelper;
+
+ /**
+ * @var AdyenLogger
+ */
private AdyenLogger $adyenLogger;
+ /**
+ * @param Json $jsonSerializer
+ * @param StoreManager $storeManager
+ * @param Config $config
+ * @param Data $adyenHelper
+ * @param AdyenLogger $adyenLogger
+ */
public function __construct(
Json $jsonSerializer,
StoreManager $storeManager,
@@ -44,6 +72,12 @@ public function __construct(
$this->adyenLogger = $adyenLogger;
}
+ /**
+ * @param string $payload
+ * @return string
+ * @throws AdyenException
+ * @throws NoSuchEntityException
+ */
public function getBalance(string $payload): string
{
$payload = $this->jsonSerializer->unserialize($payload);
@@ -53,17 +87,16 @@ public function getBalance(string $payload): string
try {
$client = $this->adyenHelper->initializeAdyenClient($storeId);
- $service = $this->adyenHelper->createAdyenCheckoutService($client);
-
- $response = $service->paymentMethodsBalance($payload);
+ $service = $this->adyenHelper->initializeOrdersApi($client);
+ $response = $service->getBalanceOfGiftCard(new BalanceCheckRequest($payload));
- if ($response['resultCode'] === self::FAILED_RESULT_CODE) {
+ if ($response->getResultCode() === self::FAILED_RESULT_CODE) {
// Balance endpoint doesn't send HTTP status 422 for invalid PIN, manual handling required.
$errorMessage = $response['additionalData']['acquirerResponseCode'] ?? 'Unknown error!';
throw new AdyenException($errorMessage);
}
- return json_encode($response);
+ return json_encode($response->jsonSerialize());
} catch (AdyenException $e) {
$this->adyenLogger->error(
sprintf("An error occurred during balance check! %s", $e->getMessage())
diff --git a/Model/Api/PaymentRequest.php b/Model/Api/PaymentRequest.php
index 63c27f2df3..4978f894ab 100755
--- a/Model/Api/PaymentRequest.php
+++ b/Model/Api/PaymentRequest.php
@@ -12,6 +12,9 @@
namespace Adyen\Payment\Model\Api;
use Adyen\AdyenException;
+use Adyen\Model\Checkout\PaymentDetailsRequest;
+use Adyen\Model\Recurring\DisableRequest;
+use Adyen\Model\Recurring\RecurringDetailsRequest;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
@@ -27,16 +30,34 @@
class PaymentRequest extends DataObject
{
+ /**
+ * @var EncryptorInterface
+ */
protected EncryptorInterface $encryptor;
+ /**
+ * @var Data
+ */
protected Data $adyenHelper;
+ /**
+ * @var AdyenLogger
+ */
protected AdyenLogger $adyenLogger;
+ /**
+ * @var Config
+ */
protected Config $configHelper;
+ /**
+ * @var RecurringType
+ */
protected RecurringType $recurringType;
+ /**
+ * @var State
+ */
protected State $appState;
/**
@@ -44,6 +65,7 @@ class PaymentRequest extends DataObject
* @param EncryptorInterface $encryptor
* @param Data $adyenHelper
* @param AdyenLogger $adyenLogger
+ * @param Config $configHelper
* @param RecurringType $recurringType
* @param array $data
*/
@@ -70,7 +92,7 @@ public function __construct(
* @throws LocalizedException
* @throws NoSuchEntityException
*/
- public function authorise3d(Payment $payment): mixed
+ public function authorise3d(Payment $payment): array
{
$order = $payment->getOrder();
$storeId = $order->getStoreId();
@@ -102,13 +124,13 @@ public function authorise3d(Payment $payment): mixed
try {
$client = $this->adyenHelper->initializeAdyenClient($storeId);
- $service = $this->adyenHelper->createAdyenCheckoutService($client);
- $result = $service->paymentsDetails($request);
+ $service = $this->adyenHelper->initializePaymentsApi($client);
+ $response = $service->paymentsDetails(new PaymentDetailsRequest($request));
} catch (AdyenException $e) {
throw new LocalizedException(__('3D secure failed'));
}
- return $result;
+ return $response->toArray();
}
/**
@@ -179,10 +201,10 @@ public function listRecurringContractByType(string $shopperReference, int $store
// call lib
$client = $this->adyenHelper->initializeAdyenClient($storeId);
- $service = $this->adyenHelper->createAdyenRecurringService($client);
- $result = $service->listRecurringDetails($request);
+ $service = $this->adyenHelper->initializeRecurringApi($client);
+ $response = $service->listRecurringDetails(new RecurringDetailsRequest($request));
- return $result;
+ return (array)$response->jsonSerialize();
}
/**
@@ -209,10 +231,11 @@ public function disableRecurringContract(
// call lib
$client = $this->adyenHelper->initializeAdyenClient($storeId);
- $service = $this->adyenHelper->createAdyenRecurringService($client);
+ $service = $this->adyenHelper->initializeRecurringApi($client);
try {
- $result = $service->disable($request);
+ $response = $service->disable(new DisableRequest($request));
+ $result = (array) $response->jsonSerialize();
} catch (Exception $e) {
$this->adyenLogger->info($e->getMessage());
}
diff --git a/Model/Config/Adminhtml/WebhookTest.php b/Model/Config/Adminhtml/WebhookTest.php
index e21a3e750a..1a26b30ea2 100644
--- a/Model/Config/Adminhtml/WebhookTest.php
+++ b/Model/Config/Adminhtml/WebhookTest.php
@@ -55,7 +55,7 @@ protected function _getElementHtml(AbstractElement $element)
public function getAjaxUrl()
{
- return $this->getUrl('adyen/configuration/webhooktest');
+ return $this->getUrl('adyen/configuration/webhookTest');
}
public function getButtonHtml()
diff --git a/Model/Config/Backend/AutoConfiguration.php b/Model/Config/Backend/AutoConfiguration.php
index 9aeff89194..19794788f9 100644
--- a/Model/Config/Backend/AutoConfiguration.php
+++ b/Model/Config/Backend/AutoConfiguration.php
@@ -11,12 +11,14 @@
namespace Adyen\Payment\Model\Config\Backend;
+use Adyen\AdyenException;
use Adyen\Payment\Helper\BaseUrlHelper;
use Adyen\Payment\Helper\ManagementHelper;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Value;
use Magento\Framework\Data\Collection\AbstractDb;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
@@ -27,16 +29,30 @@ class AutoConfiguration extends Value
/**
* @var ManagementHelper
*/
- private $managementApiHelper;
+ private ManagementHelper $managementApiHelper;
+
/**
* @var UrlInterface
*/
- private $url;
+ private UrlInterface $url;
+
/**
* @var BaseUrlHelper
*/
- private $baseUrlHelper;
+ private BaseUrlHelper $baseUrlHelper;
+ /**
+ * @param Context $context
+ * @param Registry $registry
+ * @param ScopeConfigInterface $config
+ * @param TypeListInterface $cacheTypeList
+ * @param ManagementHelper $managementApiHelper
+ * @param UrlInterface $url
+ * @param BaseUrlHelper $baseUrlHelper
+ * @param AbstractResource|null $resource
+ * @param AbstractDb|null $resourceCollection
+ * @param array $data
+ */
public function __construct(
Context $context,
Registry $registry,
@@ -55,23 +71,37 @@ public function __construct(
$this->baseUrlHelper = $baseUrlHelper;
}
- public function beforeSave()
+ /**
+ * @return AutoConfiguration
+ * @throws AdyenException
+ * @throws NoSuchEntityException
+ */
+ public function beforeSave(): AutoConfiguration
{
if ('auto' === $this->getValue()) {
- $demoMode = (int)$this->getFieldsetDataValue('demo_mode');
- $environment = $demoMode ? 'test' : 'live';
+ $this->saveAllowedOrigins();
+ }
+ return parent::beforeSave();
+ }
- $apiKey = $this->getFieldsetDataValue('api_key_' . $environment);
+ /**
+ * @return void
+ * @throws AdyenException
+ * @throws NoSuchEntityException
+ */
+ private function saveAllowedOrigins(): void
+ {
+ $demoMode = (int)$this->getFieldsetDataValue('demo_mode');
+ $environment = $demoMode ? 'test' : 'live';
- $managementApiService = $this->managementApiHelper->getManagementApiService($apiKey, $demoMode);
- $configuredOrigins = $this->managementApiHelper->getAllowedOrigins($managementApiService);
+ $apiKey = $this->getFieldsetDataValue('api_key_' . $environment);
+ $client = $this->managementApiHelper->getAdyenApiClient($apiKey, $demoMode);
+ $service = $this->managementApiHelper->getMyAPICredentialApi($client);
+ $configuredOrigins = $this->managementApiHelper->getAllowedOrigins($service);
- $domain = $this->baseUrlHelper->getDomainFromUrl($this->url->getBaseUrl());
- if (!in_array($domain, $configuredOrigins)) {
- $managementApiService = $this->managementApiHelper->getManagementApiService($apiKey, $demoMode);
- $this->managementApiHelper->saveAllowedOrigin($managementApiService, $domain);
- }
+ $domain = $this->baseUrlHelper->getDomainFromUrl($this->url->getBaseUrl());
+ if (!in_array($domain, $configuredOrigins)) {
+ $this->managementApiHelper->saveAllowedOrigin($service, $domain);
}
- return parent::beforeSave();
}
}
diff --git a/Model/Config/Backend/WebhookCredentials.php b/Model/Config/Backend/WebhookCredentials.php
index 77f0d9e951..8a3ca5b515 100644
--- a/Model/Config/Backend/WebhookCredentials.php
+++ b/Model/Config/Backend/WebhookCredentials.php
@@ -10,12 +10,15 @@
namespace Adyen\Payment\Model\Config\Backend;
+use Adyen\AdyenException;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Helper\ManagementHelper;
+use Adyen\Service\Management\WebhooksMerchantLevelApi;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Value;
use Magento\Framework\Data\Collection\AbstractDb;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
@@ -26,17 +29,30 @@ class WebhookCredentials extends Value
/**
* @var ManagementHelper
*/
- private $managementApiHelper;
+ private ManagementHelper $managementApiHelper;
+
/**
* @var Config
*/
- private $configHelper;
+ private Config $configHelper;
/**
* @var UrlInterface
*/
- private $url;
+ private UrlInterface $url;
+ /**
+ * @param Context $context
+ * @param Registry $registry
+ * @param ScopeConfigInterface $config
+ * @param TypeListInterface $cacheTypeList
+ * @param ManagementHelper $managementApiHelper
+ * @param Config $configHelper
+ * @param UrlInterface $url
+ * @param AbstractResource|null $resource
+ * @param AbstractDb|null $resourceCollection
+ * @param array $data
+ */
public function __construct(
Context $context,
Registry $registry,
@@ -55,7 +71,12 @@ public function __construct(
parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
}
- public function beforeSave()
+ /**
+ * @return WebhookCredentials
+ * @throws AdyenException
+ * @throws NoSuchEntityException
+ */
+ public function beforeSave(): WebhookCredentials
{
if ($this->getFieldsetDataValue('configuration_mode') === 'auto' &&
$this->getFieldsetDataValue('create_new_webhook') === '1') {
@@ -72,15 +93,16 @@ public function beforeSave()
$apiKey = $this->configHelper->getApiKey($environment);
}
$merchantAccount = $this->getFieldsetDataValue('merchant_account_auto');
+ $client = $this->managementApiHelper->getAdyenApiClient($apiKey, $isDemoMode);
+ $service = new WebhooksMerchantLevelApi($client);
- $managementApiService = $this->managementApiHelper->getManagementApiService($apiKey, $isDemoMode);
$this->managementApiHelper->setupWebhookCredentials(
$merchantAccount,
$username,
$password,
$webhookUrl,
$isDemoMode,
- $managementApiService
+ $service
);
}
diff --git a/Model/Method/TxVariant.php b/Model/Method/TxVariant.php
index ead1a234a0..8ef4370c96 100644
--- a/Model/Method/TxVariant.php
+++ b/Model/Method/TxVariant.php
@@ -14,7 +14,7 @@
class TxVariant
{
- private ?string $card;
+ private ?string $card = null;
private string $paymentMethod;
public function __construct(string $txVariant)
diff --git a/Model/Queue/Notification/Publisher.php b/Model/Queue/Notification/Publisher.php
index b8eef3588c..263b72f932 100644
--- a/Model/Queue/Notification/Publisher.php
+++ b/Model/Queue/Notification/Publisher.php
@@ -3,29 +3,43 @@
namespace Adyen\Payment\Model\Queue\Notification;
use Adyen\Payment\Api\Data\NotificationInterface;
+use Adyen\Payment\Model\ResourceModel\Notification;
+use Exception;
use Magento\Framework\MessageQueue\PublisherInterface;
class Publisher
{
- private const TOPIC_NAME = "adyen.notification";
+ public const TOPIC_NAME = "adyen.notification";
/** @var PublisherInterface $publisher */
private $publisher;
+ /** @var Notification $notificationResource */
+ private $notificationResource;
+
/**
* @param PublisherInterface $publisher
+ * @param Notification $notificationResource
*/
- public function __construct(PublisherInterface $publisher)
- {
+ public function __construct(
+ PublisherInterface $publisher,
+ Notification $notificationResource
+ ) {
$this->publisher = $publisher;
+ $this->notificationResource = $notificationResource;
}
/**
* @param NotificationInterface $notification
* @return void
+ * @throws Exception
*/
public function execute(NotificationInterface $notification): void
{
+ // Set processing=true to skip adding duplicate queue entries
+ $notification->setProcessing(true);
+ $this->notificationResource->save($notification);
+
$this->publisher->publish(self::TOPIC_NAME, $notification);
}
}
diff --git a/Model/Resolver/StoreConfig/StoreLocale.php b/Model/Resolver/StoreConfig/StoreLocale.php
new file mode 100644
index 0000000000..f7e0ecf9f6
--- /dev/null
+++ b/Model/Resolver/StoreConfig/StoreLocale.php
@@ -0,0 +1,51 @@
+
+ */
+declare(strict_types=1);
+
+namespace Adyen\Payment\Model\Resolver\StoreConfig;
+
+use Adyen\Payment\Helper\Data;
+use Magento\Framework\GraphQl\Config\Element\Field;
+use Magento\Framework\GraphQl\Query\ResolverInterface;
+use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
+use Magento\GraphQl\Model\Query\Context;
+use Magento\Store\Api\Data\StoreInterface;
+
+class StoreLocale implements ResolverInterface
+{
+ protected Data $adyenHelper;
+
+ /**
+ * @param \Adyen\Payment\Helper\Data $adyenHelper
+ */
+ public function __construct(
+ Data $adyenHelper
+ ) {
+ $this->adyenHelper = $adyenHelper;
+ }
+
+ /**
+ * @param Context $context
+ * @inheritDoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ): ?string {
+ /** @var StoreInterface $store */
+ $store = $context->getExtensionAttributes()->getStore();
+ return $this->adyenHelper->getStoreLocale((int)$store->getId());
+ }
+}
diff --git a/Model/Ui/AdyenGenericConfigProvider.php b/Model/Ui/AdyenGenericConfigProvider.php
index b5e6dc627d..bc6690ff6b 100644
--- a/Model/Ui/AdyenGenericConfigProvider.php
+++ b/Model/Ui/AdyenGenericConfigProvider.php
@@ -32,12 +32,12 @@ class AdyenGenericConfigProvider implements ConfigProviderInterface
* This data member will be passed to the js frontend. It will be used to map the method code (adyen_ideal) to the
* corresponding txVariant (ideal). The txVariant will then be used to instantiate the component
*/
- private array $txVariants;
+ protected array $txVariants;
/**
* These payment methods have a custom method render file. This array has been used in the adyen-method.js
* file to push correct payment method renderer.
*/
- private array $customMethodRenderers;
+ protected array $customMethodRenderers;
public function __construct(
Data $adyenHelper,
diff --git a/Plugin/PaymentVaultDeleteToken.php b/Plugin/PaymentVaultDeleteToken.php
index 1f08e433a8..6657f815d6 100644
--- a/Plugin/PaymentVaultDeleteToken.php
+++ b/Plugin/PaymentVaultDeleteToken.php
@@ -3,7 +3,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
- * Copyright (c) 2023 Adyen N.V. (https://www.adyen.com/)
+ * Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen
pspReference: MDH54321
" .
+ "
authResult: Authorised
pspReference: ABC12345
"
+ ),
+ $this->anything()
+ );
+
+ $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfEmptyResponseCodeIsHandledCorrectly()
+ {
+ // Prepare a sample response collection without a resultCode
+ $responseCollection = [
+ [
+ 'pspReference' => '123456789'
+ ]
+ ];
+
+ // Set expectations for the mocked order object
+ $this->orderMock
+ ->expects($this->once())
+ ->method('addStatusHistoryComment')
+ ->with(
+ $this->stringContains('pspReference: 123456789'),
+ $this->anything()
+ );
+
+ // Execute the handler
+ $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfNoPspReferenceIsHandledCorrectly()
+ {
+ // Prepare a sample response collection without a pspReference
+ $responseCollection = [
+ [
+ 'resultCode' => 'Authorised'
+ ]
+ ];
+
+ // Set expectations for the mocked order object
+ $this->orderMock
+ ->expects($this->once())
+ ->method('addStatusHistoryComment')
+ ->with(
+ $this->stringContains('authResult: Authorised'),
+ $this->anything()
+ );
+
+ // Execute the handler
+ $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection);
+ }
+}
diff --git a/Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php b/Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php
new file mode 100644
index 0000000000..fa7143dea3
--- /dev/null
+++ b/Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php
@@ -0,0 +1,171 @@
+adyenHelperMock = $this->createMock(Data::class);
+ $this->checkoutPaymentsDetailsHandler = new CheckoutPaymentsDetailsHandler($this->adyenHelperMock);
+
+ $orderAdapterMock = $this->createMock(OrderAdapterInterface::class);
+ $this->orderMock = $this->createMock(Order::class);
+
+ $this->paymentMock = $this->createMock(Payment::class);
+ $this->paymentMock->method('getOrder')->willReturn($this->orderMock);
+ $this->paymentDataObject = new PaymentDataObject($orderAdapterMock, $this->paymentMock);
+
+ $this->handlingSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'paymentAction' => "authorize",
+ 'stateObject' => null
+ ];
+ }
+
+ public function testIfGeneralFlowIsHandledCorrectly()
+ {
+ // prepare Handler input.
+ $responseCollection = [
+ 'hasOnlyGiftCards' => false,
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ ]
+ ];
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('getMethod')
+ ->willReturn('any_method');
+
+ $this->orderMock
+ ->expects($this->once())
+ ->method('setCanSendNewEmailFlag')
+ ->with(false);
+
+ $this->applyGenericMockExpectations();
+
+ $this->checkoutPaymentsDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfBoletoSendsAnEmail()
+ {
+ // prepare Handler input.
+ $responseCollection = [
+ 'hasOnlyGiftCards' => false,
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345'
+ ]
+ ];
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('getMethod')
+ ->willReturn(CheckoutPaymentsDetailsHandler::ADYEN_BOLETO);
+
+ // for boleto it should not call this function.
+ $this->orderMock
+ ->expects($this->never())
+ ->method('setCanSendNewEmailFlag')
+ ->with(false);
+
+ $this->applyGenericMockExpectations();
+
+ $this->checkoutPaymentsDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfPartialPaymentHandlesLastPaymentResponse()
+ {
+ // prepare Handler input.
+ $responseCollection = [
+ 'hasOnlyGiftCards' => false,
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC54321',
+ 'paymentMethod' => [
+ 'name' => 'giftcard',
+ 'type' => 'Givex',
+ ]
+ ],
+ 1 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345',
+ 'paymentMethod' => [
+ 'name' => 'card',
+ 'type' => 'CreditCard',
+ ]
+ ]
+ ];
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('getMethod')
+ ->willReturn('any_method');
+
+ $this->orderMock
+ ->expects($this->once())
+ ->method('setCanSendNewEmailFlag')
+ ->with(false);
+
+ // validate whether the psp reference of the last payment method is used when setting these values.
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setCcTransId')
+ ->with('ABC12345');
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setLastTransId')
+ ->with('ABC12345');
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setTransactionId')
+ ->with('ABC12345');
+
+ $this->applyGenericMockExpectations();
+
+ $this->checkoutPaymentsDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ private function applyGenericMockExpectations() : void
+ {
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setIsTransactionPending')
+ ->with(true);
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setIsTransactionClosed')
+ ->with(false);
+
+ $this->paymentMock
+ ->expects($this->once())
+ ->method('setShouldCloseParentTransaction')
+ ->with(false);
+ }
+}
diff --git a/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php b/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php
new file mode 100644
index 0000000000..206fdd275c
--- /dev/null
+++ b/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php
@@ -0,0 +1,96 @@
+vaultHelperMock = $this->createMock(Vault::class);
+ $this->vaultDetailsHandler = new VaultDetailsHandler($this->vaultHelperMock);
+
+ $orderAdapterMock = $this->createMock(OrderAdapterInterface::class);
+ $orderMock = $this->createMock(Order::class);
+
+ $this->paymentMock = $this->createMock(Payment::class);
+ $this->paymentMock->method('getOrder')->willReturn($orderMock);
+ $this->paymentDataObject = new PaymentDataObject($orderAdapterMock, $this->paymentMock);
+
+ $this->handlingSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'paymentAction' => "authorize",
+ 'stateObject' => null
+ ];
+ }
+
+ public function testIfGeneralFlowIsHandledCorrectly()
+ {
+ // prepare Handler input.
+ $responseCollection = [
+ 'hasOnlyGiftCards' => false,
+ 0 => [
+ 'additionalData' => ['someData' => 'value'],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ ]
+ ];
+
+ // Ensure the vaultHelper's method is called once with the correct arguments.
+ $this->vaultHelperMock
+ ->expects($this->once())
+ ->method('handlePaymentResponseRecurringDetails')
+ ->with($this->paymentMock, $responseCollection[0]);
+
+ $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfPaymentsWithoutAdditionalDataAreIgnored()
+ {
+ // Prepare a responseCollection without additionalData
+ $responseCollection = [
+ 'hasOnlyGiftCards' => false,
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ ]
+ ];
+
+ // Ensure the vaultHelper's method is NOT called since additionalData is empty
+ $this->vaultHelperMock
+ ->expects($this->never())
+ ->method('handlePaymentResponseRecurringDetails');
+
+ $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+
+ public function testIfGiftCardOnlyPaymentsAreIgnored()
+ {
+ $responseCollection = [
+ 'hasOnlyGiftCards' => true,
+ 'additionalData' => ['someData' => 'value'],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ ];
+
+ // Ensure the vaultHelper's method is NOT called since additionalData is empty
+ $this->vaultHelperMock
+ ->expects($this->never())
+ ->method('handlePaymentResponseRecurringDetails');
+
+ $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection);
+ }
+}
diff --git a/Test/Unit/Gateway/Validator/CheckoutResponseValidatorTest.php b/Test/Unit/Gateway/Validator/CheckoutResponseValidatorTest.php
new file mode 100644
index 0000000000..5730c92cfe
--- /dev/null
+++ b/Test/Unit/Gateway/Validator/CheckoutResponseValidatorTest.php
@@ -0,0 +1,259 @@
+adyenLoggerMock = $this->createMock(AdyenLogger::class);
+ $this->adyenHelperMock = $this->createMock(Data::class);
+ $this->resultFactoryMock = $this->getMockBuilder(ResultInterfaceFactory::class)
+ ->setMethods(['create'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $orderAdapterMock = $this->createMock(OrderAdapterInterface::class);
+ $paymentMock = $this->createMock(Payment::class);
+ $this->paymentDataObject = new PaymentDataObject($orderAdapterMock, $paymentMock);
+
+ $this->checkoutResponseValidator = new CheckoutResponseValidator(
+ $this->resultFactoryMock,
+ $this->adyenLoggerMock,
+ $this->adyenHelperMock
+ );
+ }
+
+ public function testIfValidationFailsWhenResponseIsEmpty()
+ {
+ $validationSubject = [
+ 'payment' => [],
+ 'stateObject' => [],
+ 'response' => []
+ ];
+
+ $this->expectException(ValidatorException::class);
+ $this->expectExceptionMessage("No responses were provided");
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+ public function testValidateSuccessWithAuthorisedResultCode()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345'
+ ]
+ ]
+ ];
+
+ $this->resultFactoryMock->expects($this->once())
+ ->method('create')
+ ->with([
+ 'isValid' => true,
+ 'failsDescription' => [],
+ 'errorCodes' => []
+ ])
+ ->willReturn(new Result(true));
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+ public function testValidateThrowsExceptionForRefusedResultCode()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Refused',
+ 'pspReference' => 'ABC12345'
+ ]
+ ]
+ ];
+
+ $this->expectException(ValidatorException::class);
+ $this->expectExceptionMessage("The payment is REFUSED.");
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+ public function testValidateThrowsExceptionForUnknownResultCode()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Some other result code',
+ 'pspReference' => 'ABC12345'
+ ]
+ ]
+ ];
+
+ $this->expectException(ValidatorException::class);
+ $this->expectExceptionMessage("Error with payment method please select different payment method.");
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+ public function testValidateHandlesAllowedErrorCode()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => '',
+ 'pspReference' => 'ABC12345',
+ 'errorCode' => '124',
+ 'error' => 'No result code present in response.'
+ ]
+ ]
+ ];
+
+ $this->expectException(ValidatorException::class);
+ $this->expectExceptionMessage("No result code present in response.");
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+ public function testValidateForSuccessfulPartialPayments()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345',
+ 'paymentMethod' => [
+ 'name' => 'giftcard',
+ 'type' => 'Givex',
+ ]
+ ],
+ 1 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345',
+ 'paymentMethod' => [
+ 'name' => 'card',
+ 'type' => 'CreditCard',
+ ]
+ ]
+ ]
+ ];
+
+ $this->resultFactoryMock->expects($this->once())
+ ->method('create')
+ ->with([
+ 'isValid' => true,
+ 'failsDescription' => [],
+ 'errorCodes' => []
+ ])
+ ->willReturn(new Result(true));
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+
+ public function testValidateForFailedPartialPayments()
+ {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Authorised',
+ 'pspReference' => 'ABC12345',
+ 'paymentMethod' => [
+ 'name' => 'Givex',
+ 'type' => 'giftcard',
+ ]
+ ],
+ 1 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => 'Refused',
+ 'pspReference' => 'ABC12345',
+ 'paymentMethod' => [
+ 'name' => 'Cards',
+ 'type' => 'scheme',
+ ]
+ ]
+ ]
+ ];
+
+ $this->expectException(ValidatorException::class);
+ $this->expectExceptionMessage("The payment is REFUSED.");
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+
+ public function testIfValidationSucceedsOnMiscellaneousResultCodes()
+ {
+ $resultCodes = [
+ 'IdentifyShopper',
+ 'ChallengeShopper',
+ 'PresentToShopper',
+ 'Pending',
+ 'RedirectShopper'
+ ];
+
+ $this->resultFactoryMock->expects($this->exactly(count($resultCodes)))
+ ->method('create')
+ ->with([
+ 'isValid' => true,
+ 'failsDescription' => [],
+ 'errorCodes' => []
+ ])
+ ->willReturn(new Result(true));
+
+ foreach ($resultCodes as $resultCode) {
+ $validationSubject = [
+ 'payment' => $this->paymentDataObject,
+ 'stateObject' => [],
+ 'response' => [
+ 0 => [
+ 'additionalData' => [],
+ 'amount' => [],
+ 'resultCode' => $resultCode,
+ 'pspReference' => 'ABC12345'
+ ],
+ ]
+ ];
+
+ $this->checkoutResponseValidator->validate($validationSubject);
+ }
+ }
+}
diff --git a/Test/Unit/Helper/DataTest.php b/Test/Unit/Helper/DataTest.php
index cb7a211375..ffb82e21cc 100755
--- a/Test/Unit/Helper/DataTest.php
+++ b/Test/Unit/Helper/DataTest.php
@@ -10,7 +10,11 @@
namespace Adyen\Payment\Test\Unit\Helper;
+use Adyen\AdyenException;
use Adyen\Client;
+use Adyen\Config as AdyenConfig;
+use Adyen\Model\Checkout\ApplicationInfo;
+use Adyen\Model\Checkout\CommonField;
use Adyen\Payment\Gateway\Request\HeaderDataBuilder;
use Adyen\Payment\Helper\Config as ConfigHelper;
use Adyen\Payment\Helper\Data;
@@ -21,6 +25,10 @@
use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory as NotificationCollectionFactory;
use Adyen\Payment\Observer\AdyenPaymentMethodDataAssignObserver;
use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
+use Adyen\Service\Checkout\ModificationsApi;
+use Adyen\Service\Checkout\OrdersApi;
+use Adyen\Service\Checkout\PaymentsApi;
+use Adyen\Service\RecurringApi;
use Magento\Backend\Helper\Data as BackendHelper;
use Magento\Customer\Helper\Address;
use Magento\Directory\Model\Config\Source\Country;
@@ -41,16 +49,14 @@
use Magento\Sales\Api\OrderManagementInterface;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Order\Status\HistoryFactory;
+use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManager;
use Magento\Tax\Model\Calculation;
use Magento\Tax\Model\Config;
use Magento\Sales\Model\Order;
-use Magento\Framework\View\Asset\File;
use ReflectionClass;
-use Adyen\Service\Recurring;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-
class DataTest extends AbstractAdyenTestCase
{
/**
@@ -58,8 +64,42 @@ class DataTest extends AbstractAdyenTestCase
*/
private $dataHelper;
+ private $clientMock;
+ private $adyenLogger;
+ private $ccTypesAltData;
+ private $configHelper;
+ private $objectManager;
+ private $store;
+ private $encryptor;
+ private $dataStorage;
+ private $assetRepo;
+ private $assetSource;
+ private $taxConfig;
+ private $taxCalculation;
+ private $backendHelper;
+ private $storeManager;
+ private $cache;
+ private $localeResolver;
+ private $config;
+ private $componentRegistrar;
+ private $localeHelper;
+ private $orderManagement;
+ private $orderStatusHistoryFactory;
+
public function setUp(): void
{
+ $this->clientMock = $this->createConfiguredMock(Client::class, [
+ 'getConfig' => new AdyenConfig([
+ 'environment' => 'test',
+ 'externalPlatform' => ['name' => 'test platform', 'version' => '1.2.3', 'integrator' => 'test integrator'],
+ 'merchantApplication' => ['name' => 'test merchant', 'version' => '1.2.3'],
+ 'adyenPaymentSource' => ['name' => 'test source', 'version' => '1.2.3']
+ ]),
+ 'getLibraryName' => 'test library',
+ 'getLibraryVersion' => '1.2.3',
+ ]
+ );
+
// Prepare mock data for ccTypesAltData
$this->ccTypesAltData = [
'VI' => ['code_alt' => 'VI', 'code' => 'VI'],
@@ -71,9 +111,10 @@ public function setUp(): void
'demo_mode' => '1'
]
]);
+
$this->objectManager = new ObjectManager($this);
$context = $this->createMock(Context::class);
- $this->store = $this->createMock(\Magento\Store\Model\Store::class);
+ $this->store = $this->createMock(Store::class);
$this->encryptor = $this->createMock(EncryptorInterface::class);
$this->dataStorage = $this->createMock(DataInterface::class);
$country = $this->createMock(Country::class);
@@ -91,6 +132,7 @@ public function setUp(): void
]);
$this->adyenLogger = $this->createMock(AdyenLogger::class);
$this->storeManager = $this->createMock(StoreManager::class);
+ $this->storeManager->method('getStore')->willReturn($this->store);
$this->cache = $this->createMock(CacheInterface::class);
$this->localeResolver = $this->createMock(ResolverInterface::class);
$this->config = $this->createMock(ScopeConfigInterface::class);
@@ -1011,6 +1053,36 @@ public function testBuildRequestHeaders()
$this->assertEquals($expectedHeaders, $headers);
}
+ public function testBuildApplicationInfo()
+ {
+ $expectedApplicationInfo = new ApplicationInfo();
+
+ // These getters are deprecated but needed to mock the client
+ $expectedApplicationInfo->setAdyenLibrary(new CommonField([
+ 'name' => $this->clientMock->getLibraryName(),
+ 'version' => $this->clientMock->getLibraryVersion()
+ ]));
+
+ $expectedApplicationInfo->setAdyenPaymentSource(new CommonField(
+ $this->clientMock->getConfig()->getAdyenPaymentSource())
+ );
+
+ $expectedApplicationInfo->setExternalPlatform(
+ $this->clientMock->getConfig()->getExternalPlatform()
+ );
+
+ $expectedApplicationInfo->setMerchantApplication(new CommonField(
+ $this->clientMock->getConfig()->getMerchantApplication())
+ );
+
+ $applicationInfo = $this->dataHelper->buildApplicationInfo($this->clientMock);
+
+ $this->assertEquals(
+ $expectedApplicationInfo,
+ $applicationInfo
+ );
+ }
+
public function testBuildRequestHeadersWithNonNullFrontendType()
{
// Mock dependencies as needed
@@ -1365,8 +1437,9 @@ public function testGetOrigin()
{
$storeId = 1;
$expectedBaseUrl = 'https://example.com/';
+
$stateMock = $this->createMock(State::class);
- $storeMock = $this->createMock(\Magento\Store\Model\Store::class);
+
$objectManagerStub = $this->createMock(\Magento\Framework\App\ObjectManager::class);
$objectManagerStub->method('get')->willReturnMap([
[State::class, $stateMock]
@@ -1379,13 +1452,8 @@ public function testGetOrigin()
->with('payment_origin_url', $storeId)
->willReturn('');
- // Mock the store manager to return the store mock
- $this->storeManager->expects($this->once())
- ->method('getStore')
- ->willReturn($storeMock);
-
// Mock the store to return the expected base URL
- $storeMock->expects($this->once())
+ $this->store->expects($this->once())
->method('getBaseUrl')
->with(UrlInterface::URL_TYPE_WEB)
->willReturn($expectedBaseUrl);
@@ -1446,7 +1514,7 @@ public function testGetAdyenMerchantAccountForAdyenPaymentMethod()
$merchantAccount = 'mock_merchant_account';
// Mock the store manager and config helper
- $storeMock = $this->createMock(\Magento\Store\Model\Store::class);
+ $storeMock = $this->createMock(Store::class);
$storeMock->expects($this->any())
->method('getId')
->willReturn($storeId);
@@ -1476,7 +1544,7 @@ public function testGetAdyenMerchantAccountForAdyenPosCloudPaymentMethod()
$merchantAccountPos = 'mock_pos_merchant_account';
// Mock the store manager and config helper
- $storeMock = $this->createMock(\Magento\Store\Model\Store::class);
+ $storeMock = $this->createMock(Store::class);
$storeMock->expects($this->any())
->method('getId')
->willReturn($storeId);
@@ -1715,4 +1783,119 @@ public function testCreateOpenInvoiceLineShipping()
$this->assertArrayHasKey('openinvoicedata.line1.itemId', $result);
$this->assertEquals("shippingCost", $result['openinvoicedata.line1.itemId']);
}
+
+ /**
+ * @test
+ */
+ public function getRecurringTypesShouldReturnAnArrayOfRecurringTypes()
+ {
+ $this->assertEquals([
+ RecurringType::ONECLICK => 'ONECLICK',
+ RecurringType::ONECLICK_RECURRING => 'ONECLICK,RECURRING',
+ RecurringType::RECURRING => 'RECURRING'
+ ], $this->dataHelper->getRecurringTypes());
+ }
+
+ public function getCheckoutFrontendRegionsShouldReturnAnArray()
+ {
+ $this->assertEquals([
+ 'eu' => 'Default (EU - Europe)',
+ 'au' => 'AU - Australasia',
+ 'us' => 'US - United States',
+ 'in' => 'IN - India'
+ ], $this->dataHelper->getRecurringTypes());
+ }
+
+ public function testGetClientKey()
+ {
+ $expectedValue = 'client_key_test_value';
+ $storeId = 1;
+
+ $this->configHelper->method('isDemoMode')
+ ->with($storeId)
+ ->willReturn(true);
+
+ $this->configHelper->method('getAdyenAbstractConfigData')
+ ->with('client_key_test', $storeId)
+ ->willReturn($expectedValue);
+
+ $key = $this->dataHelper->getClientKey(1);
+ $this->assertEquals($expectedValue, $key);
+ }
+
+ public function testGetApiKey()
+ {
+ $apiKey = 'api_key_test_value';
+ $expectedValue = 'api_key_test_decryted_value';
+ $storeId = 1;
+
+ $this->configHelper->method('isDemoMode')
+ ->with($storeId)
+ ->willReturn(true);
+
+ $this->configHelper->method('getAdyenAbstractConfigData')
+ ->with('api_key_test', $storeId)
+ ->willReturn($apiKey);
+
+ $this->encryptor->method('decrypt')
+ ->with($apiKey)
+ ->willReturn($expectedValue);
+
+ $key = $this->dataHelper->getAPIKey(1);
+ $this->assertEquals($expectedValue, $key);
+ }
+
+ public function testIsDemoMode()
+ {
+ $storeId = 1;
+ $this->configHelper->method('getAdyenAbstractConfigDataFlag')
+ ->with('demo_mode', $storeId)
+ ->willReturn(true);
+
+ $value = $this->dataHelper->isDemoMode($storeId);
+
+ $this->assertEquals(true, $value);
+ }
+
+ public function testCaptureModes()
+ {
+ $this->assertSame(
+ [
+ 'auto' => 'Immediate',
+ 'manual' => 'Manual'
+ ],
+ $this->dataHelper->getCaptureModes()
+ );
+ }
+
+ public function testInitializePaymentsApi()
+ {
+ $service = $this->dataHelper->initializePaymentsApi($this->clientMock);
+ $this->assertInstanceOf(PaymentsApi::class, $service);
+ }
+
+ public function testInitializeModificationsApi()
+ {
+ $service = $this->dataHelper->initializeModificationsApi($this->clientMock);
+ $this->assertInstanceOf(ModificationsApi::class, $service);
+ }
+
+ public function testInitializeRecurringApi()
+ {
+ $service = $this->dataHelper->initializeRecurringApi($this->clientMock);
+ $this->assertInstanceOf(RecurringApi::class, $service);
+ }
+
+ public function testInitializeOrdersApi()
+ {
+ $service = $this->dataHelper->initializeOrdersApi($this->clientMock);
+ $this->assertInstanceOf(OrdersApi::class, $service);
+ }
+
+ public function testLogAdyenException()
+ {
+ $this->store->method('getId')->willReturn(1);
+ $this->adyenLogger->expects($this->once())->method('info');
+ $this->dataHelper->logAdyenException(new AdyenException('error message', 123));
+ }
}
diff --git a/Test/Unit/Helper/ManagementHelperTest.php b/Test/Unit/Helper/ManagementHelperTest.php
index 028d3a5772..9095f5f9d9 100644
--- a/Test/Unit/Helper/ManagementHelperTest.php
+++ b/Test/Unit/Helper/ManagementHelperTest.php
@@ -11,18 +11,25 @@
namespace Adyen\Payment\Test\Unit\Helper;
+use Adyen\AdyenException;
use Adyen\Client;
use Adyen\Config as HttpClientConfig;
use Adyen\Environment;
+use Adyen\Model\Management\AllowedOrigin;
+use Adyen\Model\Management\AllowedOriginsResponse;
+use Adyen\Model\Management\GenerateHmacKeyResponse;
+use Adyen\Model\Management\ListMerchantResponse;
+use Adyen\Model\Management\MeApiCredential;
+use Adyen\Model\Management\TestWebhookResponse;
+use Adyen\Model\Management\Webhook;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\ManagementHelper;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
-use Adyen\Service\ResourceModel\Management\AllowedOrigins;
-use Adyen\Service\ResourceModel\Management\Me;
-use Adyen\Service\ResourceModel\Management\MerchantAccount;
-use Adyen\Service\ResourceModel\Management\MerchantWebhooks;
+use Adyen\Service\Management\AccountMerchantLevelApi;
+use Adyen\Service\Management\MyAPICredentialApi;
+use Adyen\Service\Management\WebhooksMerchantLevelApi;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Message\ManagerInterface;
use Magento\Store\Api\Data\StoreInterface;
@@ -30,6 +37,16 @@
class ManagementHelperTest extends AbstractAdyenTestCase
{
+ private Client $clientMock;
+
+ public function setUp(): void
+ {
+ $this->clientMock = $this->createMock(Client::class);
+ $this->clientMock->expects($this->any())
+ ->method('getConfig')
+ ->willReturn(new \Adyen\Config(['environment' => 'test']));
+ }
+
public function testGetMerchantAccountsAndClientKey()
{
$merchantAccountListResponseJson = <<
Learn more about headless integration
{\n public readonly _id = `${this.constructor['type']}-${uuid()}`;\n public props: P;\n public state;\n protected static defaultProps = {};\n public _node;\n public _component;\n public eventEmitter = new EventEmitter();\n protected readonly _parentInstance: Core;\n\n protected resources: Resources;\n\n protected constructor(props: P) {\n this.props = this.formatProps({ ...this.constructor['defaultProps'], setStatusAutomatically: true, ...props });\n this._parentInstance = this.props._parentInstance;\n this._node = null;\n this.state = {};\n this.resources = this.props.modules ? this.props.modules.resources : undefined;\n }\n\n /**\n * Executed during creation of any payment element.\n * Gives a chance to any paymentMethod to format the props we're receiving.\n */\n protected formatProps(props: P) {\n return props;\n }\n\n /**\n * Executed on the `data` getter.\n * Returns the component data necessary for the /payments request\n *\n * TODO: Replace 'any' by type PaymentMethodData extends BaseElement implements IUIElement {\n protected componentRef: any;\n public elementRef: UIElement;\n\n constructor(props: P) {\n super(props);\n this.submit = this.submit.bind(this);\n this.setState = this.setState.bind(this);\n this.onValid = this.onValid.bind(this);\n this.onComplete = this.onComplete.bind(this);\n this.onSubmit = this.onSubmit.bind(this);\n this.handleAction = this.handleAction.bind(this);\n this.handleOrder = this.handleOrder.bind(this);\n this.handleResponse = this.handleResponse.bind(this);\n this.setElementStatus = this.setElementStatus.bind(this);\n this.submitAnalytics = this.submitAnalytics.bind(this);\n\n this.elementRef = (props && props.elementRef) || this;\n }\n\n public setState(newState: object): void {\n this.state = { ...this.state, ...newState };\n this.onChange();\n }\n\n protected onChange(): object {\n const isValid = this.isValid;\n const state = { data: this.data, errors: this.state.errors, valid: this.state.valid, isValid };\n if (this.props.onChange) this.props.onChange(state, this.elementRef);\n if (isValid) this.onValid();\n\n return state;\n }\n\n // Only called once, for UIElements (including Dropin), as they are being mounted\n protected setUpAnalytics(setUpAnalyticsObj: AnalyticsInitialEvent) {\n const sessionId = this.props.session?.id;\n\n return this.props.modules.analytics.setUp({\n ...setUpAnalyticsObj,\n ...(sessionId && { sessionId })\n });\n }\n\n /**\n * A function for all UIElements, or BaseElement, to use to create an analytics action for when it's been:\n * - mounted,\n * - a PM has been selected\n * - onSubmit has been called (as a result of the pay button being pressed)\n *\n * In some other cases e.g. 3DS2 components, this function is overridden to allow more specific analytics actions to be created\n */\n /* eslint-disable-next-line */\n protected submitAnalytics(analyticsObj: SendAnalyticsObject, uiElementProps?) {\n /** Work out what the component's \"type\" is:\n * - first check for a dedicated \"analyticsType\" (currently only applies to custom-cards)\n * - otherwise, distinguish cards from non-cards: cards will use their static type property, everything else will use props.type\n */\n let component = this.constructor['analyticsType'];\n if (!component) {\n component = this.constructor['type'] === 'scheme' || this.constructor['type'] === 'bcmc' ? this.constructor['type'] : this.props.type;\n }\n\n this.props.modules?.analytics.sendAnalytics(component, analyticsObj, uiElementProps);\n }\n\n private onSubmit(): void {\n //TODO: refactor this, instant payment methods are part of Dropin logic not UIElement\n if (this.props.isInstantPayment) {\n const dropinElementRef = this.elementRef as DropinElement;\n dropinElementRef.closeActivePaymentMethod();\n }\n\n if (this.props.setStatusAutomatically) {\n this.setElementStatus('loading');\n }\n\n if (this.props.onSubmit) {\n /** Classic flow */\n // Call analytics endpoint\n this.submitAnalytics({ type: ANALYTICS_SUBMIT_STR });\n\n // Call onSubmit handler\n this.props.onSubmit({ data: this.data, isValid: this.isValid }, this.elementRef);\n } else if (this._parentInstance.session) {\n /** Session flow */\n // wrap beforeSubmit callback in a promise\n const beforeSubmitEvent = this.props.beforeSubmit\n ? new Promise((resolve, reject) =>\n this.props.beforeSubmit(this.data, this.elementRef, {\n resolve,\n reject\n })\n )\n : Promise.resolve(this.data);\n\n beforeSubmitEvent\n .then(data => {\n // Call analytics endpoint\n this.submitAnalytics({ type: ANALYTICS_SUBMIT_STR });\n // Submit payment\n return this.submitPayment(data);\n })\n .catch(() => {\n // set state as ready to submit if the merchant cancels the action\n this.elementRef.setStatus('ready');\n });\n } else {\n this.handleError(new AdyenCheckoutError('IMPLEMENTATION_ERROR', 'Could not submit the payment'));\n }\n }\n\n private onValid() {\n const state = { data: this.data };\n if (this.props.onValid) this.props.onValid(state, this.elementRef);\n return state;\n }\n\n onComplete(state): void {\n if (this.props.onComplete) this.props.onComplete(state, this.elementRef);\n }\n\n /**\n * Submit payment method data. If the form is not valid, it will trigger validation.\n */\n public submit(): void {\n if (!this.isValid) {\n this.showValidation();\n return;\n }\n\n this.onSubmit();\n }\n\n public showValidation(): this {\n if (this.componentRef && this.componentRef.showValidation) this.componentRef.showValidation();\n return this;\n }\n\n public setElementStatus(status: UIElementStatus, props?: any): this {\n this.elementRef?.setStatus(status, props);\n return this;\n }\n\n public setStatus(status: UIElementStatus, props?): this {\n if (this.componentRef?.setStatus) {\n this.componentRef.setStatus(status, props);\n }\n return this;\n }\n\n private submitPayment(data): Promise