Skip to content

Commit 67aa540

Browse files
Merge branch 'AC-461' into develop-bugfixes-121621
2 parents 0c8f826 + 09739ba commit 67aa540

File tree

22 files changed

+916
-2
lines changed

22 files changed

+916
-2
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ReCaptchaCheckoutSalesRule\Block\LayoutProcessor\Checkout;
9+
10+
use Magento\Checkout\Block\Checkout\LayoutProcessorInterface;
11+
use Magento\Framework\Exception\InputException;
12+
use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface;
13+
use Magento\ReCaptchaUi\Model\UiConfigResolverInterface;
14+
15+
/**
16+
* Provides reCaptcha component configuration.
17+
*/
18+
class Onepage implements LayoutProcessorInterface
19+
{
20+
/**
21+
* @var UiConfigResolverInterface
22+
*/
23+
private $captchaUiConfigResolver;
24+
25+
/**
26+
* @var IsCaptchaEnabledInterface
27+
*/
28+
private $isCaptchaEnabled;
29+
30+
/**
31+
* @param UiConfigResolverInterface $captchaUiConfigResolver
32+
* @param IsCaptchaEnabledInterface $isCaptchaEnabled
33+
*/
34+
public function __construct(
35+
UiConfigResolverInterface $captchaUiConfigResolver,
36+
IsCaptchaEnabledInterface $isCaptchaEnabled
37+
) {
38+
$this->captchaUiConfigResolver = $captchaUiConfigResolver;
39+
$this->isCaptchaEnabled = $isCaptchaEnabled;
40+
}
41+
42+
/**
43+
* @inheritDoc
44+
*/
45+
public function process($jsLayout)
46+
{
47+
$key = 'coupon_code';
48+
if ($this->isCaptchaEnabled->isCaptchaEnabledFor($key)) {
49+
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
50+
['payment']['children']['afterMethods']['children']['discount']['children']
51+
['checkout_sales_rule']['settings'] = $this->captchaUiConfigResolver->get($key);
52+
} else {
53+
if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
54+
['payment']['children']['afterMethods']['children']['discount']['children']['checkout_sales_rule'])) {
55+
unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
56+
['payment']['children']['afterMethods']['children']['discount']['children']['checkout_sales_rule']);
57+
}
58+
}
59+
60+
return $jsLayout;
61+
}
62+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ReCaptchaCheckoutSalesRule\Model;
9+
10+
use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface;
11+
use Magento\ReCaptchaUi\Model\ValidationConfigResolverInterface;
12+
use Magento\ReCaptchaValidationApi\Api\Data\ValidationConfigInterface;
13+
use Magento\ReCaptchaWebapiApi\Api\Data\EndpointInterface;
14+
use Magento\ReCaptchaWebapiApi\Api\WebapiValidationConfigProviderInterface;
15+
16+
/**
17+
* Provide checkout related endpoint configuration.
18+
*/
19+
class WebapiConfigProvider implements WebapiValidationConfigProviderInterface
20+
{
21+
private const CAPTCHA_ID = 'coupon_code';
22+
23+
/**
24+
* @var IsCaptchaEnabledInterface
25+
*/
26+
private $isEnabled;
27+
28+
/**
29+
* @var ValidationConfigResolverInterface
30+
*/
31+
private $configResolver;
32+
33+
/**
34+
* @param IsCaptchaEnabledInterface $isEnabled
35+
* @param ValidationConfigResolverInterface $configResolver
36+
*/
37+
public function __construct(IsCaptchaEnabledInterface $isEnabled, ValidationConfigResolverInterface $configResolver)
38+
{
39+
$this->isEnabled = $isEnabled;
40+
$this->configResolver = $configResolver;
41+
}
42+
43+
/**
44+
* @inheritDoc
45+
*/
46+
public function getConfigFor(EndpointInterface $endpoint): ?ValidationConfigInterface
47+
{
48+
//phpcs:disable Magento2.PHP.LiteralNamespaces
49+
if ((($endpoint->getServiceClass() === 'Magento\Quote\Api\CouponManagementInterface' ||
50+
$endpoint->getServiceClass() === 'Magento\Quote\Api\GuestCouponManagementInterface') &&
51+
$endpoint->getServiceMethod() === 'set')
52+
|| ($endpoint->getServiceClass() === 'Magento\QuoteGraphQl\Model\Resolver\ApplyCouponToCart'
53+
|| $endpoint->getServiceMethod() === 'ApplyCouponToCart')
54+
) {
55+
if ($this->isEnabled->isCaptchaEnabledFor(self::CAPTCHA_ID)) {
56+
return $this->configResolver->get(self::CAPTCHA_ID);
57+
}
58+
}
59+
//phpcs:enable Magento2.PHP.LiteralNamespaces
60+
61+
return null;
62+
}
63+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ReCaptchaCheckoutSalesRule\Observer;
9+
10+
use Magento\Framework\App\Action\Action;
11+
use Magento\Framework\App\Response\RedirectInterface;
12+
use Magento\Framework\Event\Observer;
13+
use Magento\Framework\Event\ObserverInterface;
14+
use Magento\Framework\Exception\InputException;
15+
use Magento\ReCaptchaUi\Model\IsCaptchaEnabledInterface;
16+
use Magento\ReCaptchaUi\Model\RequestHandlerInterface;
17+
18+
/**
19+
* Add ReCaptcha support for Coupon Code
20+
*/
21+
class CouponCodeObserver implements ObserverInterface
22+
{
23+
private const CAPTCHA_KEY = 'coupon_code';
24+
25+
/**
26+
* @var RedirectInterface
27+
*/
28+
private $redirect;
29+
30+
/**
31+
* @var IsCaptchaEnabledInterface
32+
*/
33+
private $isCaptchaEnabled;
34+
35+
/**
36+
* @var RequestHandlerInterface
37+
*/
38+
private $requestHandler;
39+
40+
/**
41+
* @param RedirectInterface $redirect
42+
* @param IsCaptchaEnabledInterface $isCaptchaEnabled
43+
* @param RequestHandlerInterface $requestHandler
44+
*/
45+
public function __construct(
46+
RedirectInterface $redirect,
47+
IsCaptchaEnabledInterface $isCaptchaEnabled,
48+
RequestHandlerInterface $requestHandler
49+
) {
50+
$this->redirect = $redirect;
51+
$this->isCaptchaEnabled = $isCaptchaEnabled;
52+
$this->requestHandler = $requestHandler;
53+
}
54+
55+
/**
56+
* @inheritdoc
57+
* @param Observer $observer
58+
* @return void
59+
* @throws InputException
60+
*/
61+
public function execute(Observer $observer): void
62+
{
63+
/** @var Action $controller */
64+
$controller = $observer->getControllerAction();
65+
$request_param = $controller->getRequest()->getParams();
66+
if (!isset($request_param['remove']) && $this->isCaptchaEnabled->isCaptchaEnabledFor(self::CAPTCHA_KEY)) {
67+
$request = $controller->getRequest();
68+
$response = $controller->getResponse();
69+
$redirectOnFailureUrl = $this->redirect->getRefererUrl();
70+
$this->requestHandler->execute(self::CAPTCHA_KEY, $request, $response, $redirectOnFailureUrl);
71+
}
72+
}
73+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\ReCaptchaCheckoutSalesRule\Plugin;
7+
8+
use Magento\Checkout\Block\Cart\Coupon;
9+
use Magento\ReCaptchaUi\Block\ReCaptcha;
10+
11+
/**
12+
* Plugin for adding recaptcha in coupon form
13+
*/
14+
class CouponSetLayoutPlugin
15+
{
16+
/**
17+
* Add Child ReCaptcha in Coupon form
18+
*
19+
* @param Coupon $subject
20+
* @return Coupon
21+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
22+
*/
23+
public function afterSetLayout(Coupon $subject): Coupon
24+
{
25+
if (!$subject->getChildBlock('captcha')) {
26+
$subject->addChild(
27+
'captcha',
28+
ReCaptcha::class,
29+
[
30+
'jsLayout' => [
31+
'components' => [
32+
'captcha' => ['component' => 'Magento_ReCaptchaFrontendUi/js/reCaptcha']
33+
]
34+
]
35+
]
36+
);
37+
}
38+
return $subject;
39+
}
40+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Magento reCAPTCHA
2+
Google reCAPTCHA ensures that a human being, rather than a computer (or “bot”), is interacting with your website. Unlike the standard Magento CAPTCHA, Google reCAPTCHA provides enhanced security with a selection of different display options and methods. Additional website traffic information is available in the dashboard of your Google reCAPTCHA account.
3+
4+
This module provides the reCAPTCHA implementations related to coupon code apply action on checkout cart & payment.
5+
6+
For more information please visit the Magento document for reCAPTCHA.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\ReCaptchaCheckoutSalesRule\Test\Api;
9+
10+
use Magento\Framework\Webapi\Rest\Request;
11+
use Magento\Quote\Model\Quote;
12+
use Magento\Quote\Model\QuoteFactory;
13+
use Magento\TestFramework\TestCase\WebapiAbstract;
14+
15+
/**
16+
* Test that Coupon APIs are covered with ReCaptcha
17+
*/
18+
class CouponApplyFormRecaptchaTest extends WebapiAbstract
19+
{
20+
private const API_ROUTE = '/V1/carts/mine/coupons/%s';
21+
private const COUPON_CODE = 'testCoupon';
22+
23+
/**
24+
* @var \Magento\TestFramework\ObjectManager
25+
*/
26+
protected $objectManager;
27+
28+
/**
29+
* @var QuoteFactory
30+
*/
31+
private $quoteFactory;
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
protected function setUp(): void
37+
{
38+
parent::setUp();
39+
40+
$this->_markTestAsRestOnly();
41+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
42+
$this->quoteFactory = $this->objectManager->get(QuoteFactory::class);
43+
}
44+
45+
/**
46+
* @magentoApiDataFixture Magento/Checkout/_files/quote.php
47+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
48+
* @magentoConfigFixture default_store customer/captcha/enable 0
49+
* @magentoConfigFixture base_website recaptcha_frontend/type_invisible/public_key test_public_key
50+
* @magentoConfigFixture base_website recaptcha_frontend/type_invisible/private_key test_private_key
51+
* @magentoConfigFixture base_website recaptcha_frontend/type_for/coupon_code invisible
52+
*/
53+
public function testRequired(): void
54+
{
55+
$this->expectException(\Throwable::class);
56+
$this->expectExceptionCode(400);
57+
$this->expectExceptionMessage('{"message":"ReCaptcha validation failed, please try again"}');
58+
59+
// get customer ID token
60+
/** @var \Magento\Integration\Api\CustomerTokenServiceInterface $customerTokenService */
61+
$customerTokenService = $this->objectManager->create(
62+
\Magento\Integration\Api\CustomerTokenServiceInterface::class
63+
);
64+
$token = $customerTokenService->createCustomerAccessToken('[email protected]', 'password');
65+
66+
/** @var Quote $quote */
67+
$quote = $this->quoteFactory->create();
68+
$quote->load('test_order_1', 'reserved_order_id');
69+
$cartId = $quote->getId();
70+
71+
$api_url = sprintf(self::API_ROUTE, self::COUPON_CODE);
72+
$serviceInfo = [
73+
'rest' => [
74+
'resourcePath' => $api_url,
75+
'httpMethod' => Request::HTTP_METHOD_PUT,
76+
'token' => $token,
77+
],
78+
];
79+
$requestData = [
80+
'cart_id' => $cartId
81+
];
82+
83+
$this->_webApiCall($serviceInfo, $requestData);
84+
}
85+
86+
/**
87+
* @magentoApiDataFixture Magento/Checkout/_files/quote.php
88+
* @magentoConfigFixture default_store customer/captcha/enable 0
89+
* @magentoConfigFixture base_website recaptcha_frontend/type_invisible/public_key test_public_key
90+
* @magentoConfigFixture base_website recaptcha_frontend/type_invisible/private_key test_private_key
91+
* @magentoConfigFixture base_website recaptcha_frontend/type_for/coupon_code invisible
92+
*/
93+
public function testGuestCartTest(): void
94+
{
95+
$this->expectException(\Throwable::class);
96+
$this->expectExceptionCode(400);
97+
$this->expectExceptionMessage('{"message":"ReCaptcha validation failed, please try again"}');
98+
99+
/** @var Quote $quote */
100+
$quote = $this->quoteFactory->create();
101+
$quote->load('test_order_1', 'reserved_order_id');
102+
$cartId = $quote->getId();
103+
$api_url = "/V1/guest-carts/$cartId/coupons/".self::COUPON_CODE;
104+
105+
$serviceInfo = [
106+
'rest' => [
107+
'resourcePath' => $api_url,
108+
'httpMethod' => Request::HTTP_METHOD_PUT,
109+
'token' => null
110+
],
111+
];
112+
$requestData = [];
113+
$this->_webApiCall($serviceInfo, $requestData);
114+
}
115+
}

0 commit comments

Comments
 (0)