mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-31 06:52:50 +08:00
Merge pull request #1170 from woocommerce/PCP-1430-validate-free-trial
Validate before free trial
This commit is contained in:
commit
ec04c02b3d
11 changed files with 364 additions and 3 deletions
|
@ -18,6 +18,7 @@ import {hide, setVisible, setVisibleByClass} from "./modules/Helper/Hiding";
|
|||
import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
|
||||
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
|
||||
import FormSaver from './modules/Helper/FormSaver';
|
||||
import FormValidator from "./modules/Helper/FormValidator";
|
||||
|
||||
// TODO: could be a good idea to have a separate spinner for each gateway,
|
||||
// but I think we care mainly about the script loading, so one spinner should be enough.
|
||||
|
@ -36,7 +37,13 @@ const bootstrap = () => {
|
|||
PayPalCommerceGateway.ajax.save_checkout_form.nonce,
|
||||
);
|
||||
|
||||
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, spinner, errorHandler);
|
||||
const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
|
||||
new FormValidator(
|
||||
PayPalCommerceGateway.ajax.validate_checkout.endpoint,
|
||||
PayPalCommerceGateway.ajax.validate_checkout.nonce,
|
||||
) : null;
|
||||
|
||||
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler);
|
||||
|
||||
jQuery('form.woocommerce-checkout input').on('keydown', e => {
|
||||
if (e.key === 'Enter' && [
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
class FreeTrialHandler {
|
||||
/**
|
||||
* @param config
|
||||
* @param formSelector
|
||||
* @param {FormSaver} formSaver
|
||||
* @param {FormValidator|null} formValidator
|
||||
* @param {Spinner} spinner
|
||||
* @param {ErrorHandler} errorHandler
|
||||
*/
|
||||
constructor(
|
||||
config,
|
||||
formSelector,
|
||||
formSaver,
|
||||
formValidator,
|
||||
spinner,
|
||||
errorHandler
|
||||
) {
|
||||
this.config = config;
|
||||
this.formSelector = formSelector;
|
||||
this.formSaver = formSaver;
|
||||
this.formValidator = formValidator;
|
||||
this.spinner = spinner;
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
@ -24,6 +34,19 @@ class FreeTrialHandler {
|
|||
}
|
||||
|
||||
try {
|
||||
if (this.formValidator) {
|
||||
try {
|
||||
const errors = await this.formValidator.validate(document.querySelector(this.formSelector));
|
||||
if (errors.length > 0) {
|
||||
this.spinner.unblock();
|
||||
this.errorHandler.messages(errors);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(this.config.ajax.vault_paypal.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
export default class FormValidator {
|
||||
constructor(url, nonce) {
|
||||
this.url = url;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
async validate(form) {
|
||||
const formData = new FormData(form);
|
||||
const formJsonObj = Object.fromEntries(formData.entries());
|
||||
|
||||
const res = await fetch(this.url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: this.nonce,
|
||||
form: formJsonObj,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!data.success) {
|
||||
if (data.data.errors) {
|
||||
return data.data.errors;
|
||||
}
|
||||
throw Error(data.data.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Button;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
|
@ -106,6 +108,7 @@ return array(
|
|||
$currency,
|
||||
$container->get( 'wcgateway.all-funding-sources' ),
|
||||
$container->get( 'button.basic-checkout-validation-enabled' ),
|
||||
$container->get( 'button.early-wc-checkout-validation-enabled' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
@ -210,6 +213,13 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'button.endpoint.validate-checkout' => static function ( ContainerInterface $container ): ValidateCheckoutEndpoint {
|
||||
return new ValidateCheckoutEndpoint(
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'button.validation.wc-checkout-validator' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure {
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new ThreeDSecure( $logger );
|
||||
|
@ -246,4 +256,7 @@ return array(
|
|||
*/
|
||||
return (bool) apply_filters( 'woocommerce_paypal_payments_early_wc_checkout_validation_enabled', true );
|
||||
},
|
||||
'button.validation.wc-checkout-validator' => static function ( ContainerInterface $container ): CheckoutFormValidator {
|
||||
return new CheckoutFormValidator();
|
||||
},
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
|
@ -156,6 +157,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
*/
|
||||
private $basic_checkout_validation_enabled;
|
||||
|
||||
/**
|
||||
* Whether to execute WC validation of the checkout form.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $early_validation_enabled;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -189,6 +197,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @param string $currency 3-letter currency code of the shop.
|
||||
* @param array $all_funding_sources All existing funding sources.
|
||||
* @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled.
|
||||
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -208,6 +217,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
string $currency,
|
||||
array $all_funding_sources,
|
||||
bool $basic_checkout_validation_enabled,
|
||||
bool $early_validation_enabled,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
|
@ -227,6 +237,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
$this->currency = $currency;
|
||||
$this->all_funding_sources = $all_funding_sources;
|
||||
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -783,6 +794,10 @@ class SmartButton implements SmartButtonInterface {
|
|||
'endpoint' => \WC_AJAX::get_endpoint( SaveCheckoutFormEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( SaveCheckoutFormEndpoint::nonce() ),
|
||||
),
|
||||
'validate_checkout' => array(
|
||||
'endpoint' => \WC_AJAX::get_endpoint( ValidateCheckoutEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( ValidateCheckoutEndpoint::nonce() ),
|
||||
),
|
||||
),
|
||||
'enforce_vault' => $this->has_subscriptions(),
|
||||
'can_save_vault_token' => $this->can_save_vault_token(),
|
||||
|
@ -872,6 +887,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
|
||||
'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
|
||||
'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
|
||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||
);
|
||||
|
||||
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
|
||||
|
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Button;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
|
@ -96,7 +97,7 @@ class ButtonModule implements ModuleInterface {
|
|||
*
|
||||
* @param ContainerInterface $container The Container.
|
||||
*/
|
||||
private function register_ajax_endpoints( ContainerInterface $container ) {
|
||||
private function register_ajax_endpoints( ContainerInterface $container ): void {
|
||||
add_action(
|
||||
'wc_ajax_' . DataClientIdEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
|
@ -167,6 +168,15 @@ class ButtonModule implements ModuleInterface {
|
|||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . ValidateCheckoutEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.validate-checkout' );
|
||||
assert( $endpoint instanceof ValidateCheckoutEndpoint );
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
107
modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
Normal file
107
modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for validating the checkout form.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
|
||||
/**
|
||||
* Class ValidateCheckoutEndpoint.
|
||||
*/
|
||||
class ValidateCheckoutEndpoint implements EndpointInterface {
|
||||
|
||||
|
||||
const ENDPOINT = 'ppc-validate-checkout';
|
||||
|
||||
/**
|
||||
* The Request Data Helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The CheckoutFormValidator.
|
||||
*
|
||||
* @var CheckoutFormValidator
|
||||
*/
|
||||
private $checkout_form_validator;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* ValidateCheckoutEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The Request Data Helper.
|
||||
* @param CheckoutFormValidator $checkout_form_validator The CheckoutFormValidator.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestData $request_data,
|
||||
CheckoutFormValidator $checkout_form_validator,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->request_data = $request_data;
|
||||
$this->checkout_form_validator = $checkout_form_validator;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
|
||||
$form_fields = $data['form'];
|
||||
|
||||
$this->checkout_form_validator->validate( $form_fields );
|
||||
|
||||
wp_send_json_success();
|
||||
|
||||
return true;
|
||||
} catch ( ValidationException $exception ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $exception->getMessage(),
|
||||
'errors' => $exception->errors(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
} catch ( Throwable $error ) {
|
||||
$this->logger->error( "Form validation execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $error->getMessage(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Mockery;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
use WooCommerce\PayPalCommerce\TestCase;
|
||||
use function Brain\Monkey\Functions\expect;
|
||||
|
||||
class ValidateCheckoutEndpointTest extends TestCase
|
||||
{
|
||||
private $requestData;
|
||||
private $formValidator;
|
||||
private $logger;
|
||||
private $sut;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->requestData = Mockery::mock(RequestData::class);
|
||||
$this->formValidator = Mockery::mock(CheckoutFormValidator::class);
|
||||
$this->logger = Mockery::mock(LoggerInterface::class);
|
||||
|
||||
$this->sut = new ValidateCheckoutEndpoint(
|
||||
$this->requestData,
|
||||
$this->formValidator,
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$this->requestData->expects('read_request')->andReturn(['form' => ['field1' => 'value']]);
|
||||
}
|
||||
|
||||
public function testValid()
|
||||
{
|
||||
$this->formValidator->expects('validate')->once();
|
||||
|
||||
expect('wp_send_json_success')->once();
|
||||
|
||||
$this->sut->handle_request();
|
||||
}
|
||||
|
||||
public function testInvalid()
|
||||
{
|
||||
$exception = new ValidationException(['Invalid value']);
|
||||
$this->formValidator->expects('validate')->once()
|
||||
->andThrow($exception);
|
||||
|
||||
expect('wp_send_json_error')->once()
|
||||
->with(['message' => $exception->getMessage(), 'errors' => ['Invalid value']]);
|
||||
|
||||
$this->sut->handle_request();
|
||||
}
|
||||
|
||||
public function testFailure()
|
||||
{
|
||||
$exception = new Exception('BOOM');
|
||||
$this->formValidator->expects('validate')->once()
|
||||
->andThrow($exception);
|
||||
|
||||
expect('wp_send_json_error')->once()
|
||||
->with(['message' => $exception->getMessage()]);
|
||||
|
||||
$this->logger->expects('error')->once();
|
||||
|
||||
$this->sut->handle_request();
|
||||
}
|
||||
}
|
|
@ -8,5 +8,6 @@ require_once ROOT_DIR . '/vendor/autoload.php';
|
|||
require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway_CC.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/WC_Ajax.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/WC_Checkout.php';
|
||||
|
||||
Hamcrest\Util::registerGlobalFunctions();
|
||||
|
|
70
tests/e2e/PHPUnit/Validation/ValidationTest.php
Normal file
70
tests/e2e/PHPUnit/Validation/ValidationTest.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\E2e\Validation;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
use WooCommerce\PayPalCommerce\Tests\E2e\TestCase;
|
||||
|
||||
class ValidationTest extends TestCase
|
||||
{
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var CheckoutFormValidator
|
||||
*/
|
||||
protected $sut;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->container = $this->getContainer();
|
||||
|
||||
$this->sut = $this->container->get( 'button.validation.wc-checkout-validator' );
|
||||
assert($this->sut instanceof CheckoutFormValidator);
|
||||
}
|
||||
|
||||
public function testValid()
|
||||
{
|
||||
$this->sut->validate([
|
||||
'billing_first_name'=>'John',
|
||||
'billing_last_name'=>'Doe',
|
||||
'billing_company'=>'',
|
||||
'billing_country'=>'DE',
|
||||
'billing_address_1'=>'1 Main St',
|
||||
'billing_address_2'=>'city1',
|
||||
'billing_postcode'=>'11111',
|
||||
'billing_city'=>'city1',
|
||||
'billing_state'=>'DE-BW',
|
||||
'billing_phone'=>'12345678',
|
||||
'billing_email'=>'a@gmail.com',
|
||||
'terms-field'=>'1',
|
||||
'terms'=>'on',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testInvalid()
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageMatches('/.+First name.+Postcode/i');
|
||||
|
||||
$this->sut->validate([
|
||||
'billing_first_name'=>'',
|
||||
'billing_postcode'=>'ABCDE',
|
||||
|
||||
'billing_last_name'=>'Doe',
|
||||
'billing_company'=>'',
|
||||
'billing_country'=>'DE',
|
||||
'billing_address_1'=>'1 Main St',
|
||||
'billing_address_2'=>'city1',
|
||||
'billing_city'=>'city1',
|
||||
'billing_state'=>'DE-BW',
|
||||
'billing_phone'=>'12345678',
|
||||
'billing_email'=>'a@gmail.com',
|
||||
'terms-field'=>'1',
|
||||
'terms'=>'on',
|
||||
]);
|
||||
}
|
||||
}
|
11
tests/stubs/WC_Checkout.php
Normal file
11
tests/stubs/WC_Checkout.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
declare( strict_types=1 );
|
||||
|
||||
class WC_Checkout {
|
||||
public function get_posted_data() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function validate_checkout( &$data, &$errors ) {
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue