Skip to content

Commit fa939e9

Browse files
authored
[ECP-8667] Enable Adyen Giving donations on headless integration (#3254)
* [ECP-8667] Indicate the donations' availability in the payment status * [ECP-8667] Implement GraphQl resolver for donation endpoints * [ECP-8667] Write unit tests * [ECP-8667] Move duplicate code to the abstract class
1 parent 754092d commit fa939e9

14 files changed

+1116
-4
lines changed

Helper/PaymentResponseHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,16 @@ public function __construct(
7474

7575
public function formatPaymentResponse(
7676
string $resultCode,
77-
?array $action = null
77+
?array $action = null,
78+
?bool $donationTokenExists = false
7879
): array {
7980
switch ($resultCode) {
8081
case self::AUTHORISED:
82+
return [
83+
"isFinal" => true,
84+
"resultCode" => $resultCode,
85+
"canDonate" => $donationTokenExists
86+
];
8187
case self::REFUSED:
8288
case self::ERROR:
8389
case self::POS_SUCCESS:

Model/Api/AdyenOrderPaymentStatus.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public function getOrderPaymentStatus(string $orderId): string
6161

6262
return json_encode($this->paymentResponseHandler->formatPaymentResponse(
6363
$additionalInformation['resultCode'],
64-
!empty($additionalInformation['action']) ? $additionalInformation['action'] : null
64+
!empty($additionalInformation['action']) ? $additionalInformation['action'] : null,
65+
!empty($additionalInformation['donationToken'])
6566
));
6667
}
6768
}

Model/Api/AdyenPaymentsDetails.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function initiate(string $payload, string $orderId): string
7575
$this->paymentResponseHandler->formatPaymentResponse(
7676
$response['resultCode'],
7777
$response['action'] ?? null,
78-
$response['additionalData'] ?? null
78+
!empty($response['donationToken'])
7979
)
8080
);
8181
}

Model/Api/GuestAdyenOrderPaymentStatus.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function getOrderPaymentStatus(string $orderId, string $cartId): string
7878
return json_encode($this->paymentResponseHandler->formatPaymentResponse(
7979
$additionalInformation['resultCode'],
8080
!empty($additionalInformation['action']) ? $additionalInformation['action'] : null,
81-
!empty($additionalInformation['additionalData']) ? $additionalInformation['additionalData'] : null
81+
!empty($additionalInformation['donationToken'])
8282
));
8383
}
8484
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
/**
3+
*
4+
* Adyen Payment Module
5+
*
6+
* Copyright (c) 2026 Adyen N.V.
7+
* This file is open source and available under the MIT license.
8+
* See the LICENSE file for more info.
9+
*
10+
* Author: Adyen <magento@adyen.com>
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace Adyen\Payment\Model;
15+
16+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
17+
18+
class GraphqlInputArgumentValidator
19+
{
20+
/**
21+
* Validates GraphQl input arguments
22+
*
23+
* Multidimensional arrays can be validated with fields separated with a dot.
24+
*
25+
* @param array|null $args
26+
* @param array $requiredFields
27+
* @return void
28+
* @throws GraphQlInputException
29+
*/
30+
public function execute(?array $args, array $requiredFields): void
31+
{
32+
$missingFields = [];
33+
34+
foreach ($requiredFields as $field) {
35+
$keys = explode('.', $field);
36+
$value = $args;
37+
38+
foreach ($keys as $key) {
39+
$value = $value[$key] ?? null;
40+
}
41+
42+
if (empty($value)) {
43+
$missingFields[] = $field;
44+
}
45+
}
46+
47+
if (!empty($missingFields)) {
48+
throw new GraphQlInputException(
49+
__('Required parameters "%1" are missing', implode(', ', $missingFields))
50+
);
51+
}
52+
}
53+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
/**
3+
*
4+
* Adyen Payment Module
5+
*
6+
* Copyright (c) 2026 Adyen N.V.
7+
* This file is open source and available under the MIT license.
8+
* See the LICENSE file for more info.
9+
*
10+
* Author: Adyen <magento@adyen.com>
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace Adyen\Payment\Model\Resolver;
15+
16+
use Adyen\Payment\Exception\GraphQlAdyenException;
17+
use Adyen\Payment\Logger\AdyenLogger;
18+
use Adyen\Payment\Model\GraphqlInputArgumentValidator;
19+
use Adyen\Payment\Model\Sales\OrderRepository;
20+
use Exception;
21+
use Magento\Framework\Exception\LocalizedException;
22+
use Magento\Framework\Exception\NoSuchEntityException;
23+
use Magento\Framework\GraphQl\Config\Element\Field;
24+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
25+
use Magento\Framework\GraphQl\Query\ResolverInterface;
26+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
27+
use Magento\GraphQl\Helper\Error\AggregateExceptionMessageFormatter;
28+
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
29+
use Magento\Sales\Api\Data\OrderInterface;
30+
31+
abstract class AbstractDonationResolver implements ResolverInterface
32+
{
33+
/**
34+
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
35+
* @param OrderRepository $orderRepository
36+
* @param GraphqlInputArgumentValidator $graphqlInputArgumentValidator
37+
* @param AdyenLogger $adyenLogger
38+
* @param AggregateExceptionMessageFormatter $adyenGraphQlExceptionMessageFormatter
39+
*/
40+
public function __construct(
41+
protected readonly MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
42+
protected readonly OrderRepository $orderRepository,
43+
protected readonly GraphqlInputArgumentValidator $graphqlInputArgumentValidator,
44+
protected readonly AdyenLogger $adyenLogger,
45+
protected readonly AggregateExceptionMessageFormatter $adyenGraphQlExceptionMessageFormatter
46+
) { }
47+
48+
/**
49+
* @return array
50+
*/
51+
abstract protected function getRequiredFields(): array;
52+
53+
/**
54+
* @param OrderInterface $order
55+
* @param array $args
56+
* @param Field $field
57+
* @param $context
58+
* @param ResolveInfo $info
59+
* @return array
60+
* @throws GraphQlAdyenException
61+
*/
62+
abstract protected function performOperation(
63+
OrderInterface $order,
64+
array $args,
65+
Field $field,
66+
$context,
67+
ResolveInfo $info
68+
): array;
69+
70+
/**
71+
* @return string
72+
*/
73+
protected function getGenericErrorMessage(): string
74+
{
75+
return 'An error occurred while processing the donation.';
76+
}
77+
78+
/**
79+
* @param Field $field
80+
* @param $context
81+
* @param ResolveInfo $info
82+
* @param array|null $value
83+
* @param array|null $args
84+
* @return array
85+
* @throws GraphQlAdyenException
86+
* @throws GraphQlInputException
87+
*/
88+
public function resolve(
89+
Field $field,
90+
$context,
91+
ResolveInfo $info,
92+
?array $value = null,
93+
?array $args = null
94+
): array {
95+
$this->graphqlInputArgumentValidator->execute($args, $this->getRequiredFields());
96+
97+
try {
98+
$quoteId = $this->maskedQuoteIdToQuoteId->execute($args['cartId']);
99+
} catch (NoSuchEntityException $e) {
100+
$this->adyenLogger->error(sprintf("Quote with masked ID %s not found!", $args['cartId']));
101+
throw new GraphQlAdyenException(__($this->getGenericErrorMessage()));
102+
}
103+
104+
$order = $this->orderRepository->getOrderByQuoteId($quoteId);
105+
106+
if (!$order) {
107+
$this->adyenLogger->error(sprintf("Order for quote ID %s not found!", $quoteId));
108+
throw new GraphQlAdyenException(__($this->getGenericErrorMessage()));
109+
}
110+
111+
try {
112+
return $this->performOperation($order, $args, $field, $context, $info);
113+
} catch (LocalizedException $e) {
114+
throw $this->adyenGraphQlExceptionMessageFormatter->getFormatted(
115+
$e,
116+
__($this->getGenericErrorMessage()),
117+
$this->getGenericErrorMessage(),
118+
$field,
119+
$context,
120+
$info
121+
);
122+
} catch (Exception $e) {
123+
$this->adyenLogger->error(sprintf(
124+
'%s: %s',
125+
$this->getGenericErrorMessage(),
126+
$e->getMessage()
127+
));
128+
throw new GraphQlAdyenException(__($this->getGenericErrorMessage()));
129+
}
130+
}
131+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/**
3+
*
4+
* Adyen Payment Module
5+
*
6+
* Copyright (c) 2026 Adyen N.V.
7+
* This file is open source and available under the MIT license.
8+
* See the LICENSE file for more info.
9+
*
10+
* Author: Adyen <magento@adyen.com>
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace Adyen\Payment\Model\Resolver;
15+
16+
use Adyen\Payment\Exception\GraphQlAdyenException;
17+
use Adyen\Payment\Model\Api\AdyenDonationCampaigns;
18+
use Magento\Framework\App\ObjectManager;
19+
use Magento\Framework\Exception\LocalizedException;
20+
use Magento\Framework\GraphQl\Config\Element\Field;
21+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
22+
use Magento\Sales\Api\Data\OrderInterface;
23+
24+
class DonationCampaigns extends AbstractDonationResolver
25+
{
26+
/**
27+
* @return array
28+
*/
29+
protected function getRequiredFields(): array
30+
{
31+
return [
32+
'cartId'
33+
];
34+
}
35+
36+
/**
37+
* @param OrderInterface $order
38+
* @param array $args
39+
* @param Field $field
40+
* @param $context
41+
* @param ResolveInfo $info
42+
* @return array
43+
* @throws GraphQlAdyenException|LocalizedException
44+
*/
45+
protected function performOperation(
46+
OrderInterface $order,
47+
array $args,
48+
Field $field,
49+
$context,
50+
ResolveInfo $info
51+
): array {
52+
$adyenDonationCampaigns = ObjectManager::getInstance()->get(AdyenDonationCampaigns::class);
53+
$campaignsResponse = $adyenDonationCampaigns->getCampaigns((int) $order->getEntityId());
54+
55+
return ['campaignsData' => $campaignsResponse];
56+
}
57+
}

Model/Resolver/Donations.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
*
4+
* Adyen Payment Module
5+
*
6+
* Copyright (c) 2026 Adyen N.V.
7+
* This file is open source and available under the MIT license.
8+
* See the LICENSE file for more info.
9+
*
10+
* Author: Adyen <magento@adyen.com>
11+
*/
12+
declare(strict_types=1);
13+
14+
namespace Adyen\Payment\Model\Resolver;
15+
16+
use Adyen\Payment\Exception\GraphQlAdyenException;
17+
use Adyen\Payment\Model\Api\AdyenDonations;
18+
use Magento\Framework\App\ObjectManager;
19+
use Magento\Framework\Exception\LocalizedException;
20+
use Magento\Framework\GraphQl\Config\Element\Field;
21+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
22+
use Magento\Framework\Serialize\Serializer\Json;
23+
use Magento\Sales\Api\Data\OrderInterface;
24+
25+
class Donations extends AbstractDonationResolver
26+
{
27+
/**
28+
* @return array
29+
*/
30+
protected function getRequiredFields(): array
31+
{
32+
return [
33+
'cartId',
34+
'amount',
35+
'amount.currency',
36+
'returnUrl'
37+
];
38+
}
39+
40+
/**
41+
* @param OrderInterface $order
42+
* @param array $args
43+
* @param Field $field
44+
* @param $context
45+
* @param ResolveInfo $info
46+
* @return array
47+
* @throws GraphQlAdyenException|LocalizedException
48+
*/
49+
protected function performOperation(
50+
OrderInterface $order,
51+
array $args,
52+
Field $field,
53+
$context,
54+
ResolveInfo $info
55+
): array {
56+
$payloadData = [
57+
'amount' => [
58+
'currency' => $args['amount']['currency'],
59+
'value' => $args['amount']['value']
60+
],
61+
'returnUrl' => $args['returnUrl']
62+
];
63+
64+
$jsonSerializer = ObjectManager::getInstance()->get(Json::class);
65+
$payload = $jsonSerializer->serialize($payloadData);
66+
67+
$adyenDonations = ObjectManager::getInstance()->get(AdyenDonations::class);
68+
$adyenDonations->makeDonation($payload, $order);
69+
70+
return ['status' => true];
71+
}
72+
}

0 commit comments

Comments
 (0)