Merge pull request #445 from woocommerce/pcp-370-onboarding

Improve onboarding, allow no card processing
This commit is contained in:
Emili Castells 2022-02-10 09:38:57 +01:00 committed by GitHub
commit 415ffb4268
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 864 additions and 699 deletions

3
.gitignore vendored
View file

@ -5,7 +5,8 @@ node_modules
.phpunit.result.cache .phpunit.result.cache
yarn-error.log yarn-error.log
modules/ppcp-button/assets/* modules/ppcp-button/assets/*
modules/ppcp-wc-gateway/assets/* modules/ppcp-wc-gateway/assets/js
modules/ppcp-wc-gateway/assets/css
*.zip *.zip
.env .env
auth.json auth.json

View file

@ -127,7 +127,6 @@ return array(
return new PartnerReferrals( return new PartnerReferrals(
$container->get( 'api.host' ), $container->get( 'api.host' ),
$container->get( 'api.bearer' ), $container->get( 'api.bearer' ),
$container->get( 'api.repository.partner-referrals-data' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
@ -209,9 +208,8 @@ return array(
}, },
'api.repository.partner-referrals-data' => static function ( ContainerInterface $container ) : PartnerReferralsData { 'api.repository.partner-referrals-data' => static function ( ContainerInterface $container ) : PartnerReferralsData {
$merchant_email = $container->get( 'api.merchant_email' );
$dcc_applies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
return new PartnerReferralsData( $merchant_email, $dcc_applies ); return new PartnerReferralsData( $dcc_applies );
}, },
'api.repository.cart' => static function ( ContainerInterface $container ): CartRepository { 'api.repository.cart' => static function ( ContainerInterface $container ): CartRepository {
$factory = $container->get( 'api.factory.purchase-unit' ); $factory = $container->get( 'api.factory.purchase-unit' );

View file

@ -12,7 +12,6 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -36,13 +35,6 @@ class PartnerReferrals {
*/ */
private $bearer; private $bearer;
/**
* The PartnerReferralsData.
*
* @var PartnerReferralsData
*/
private $data;
/** /**
* The logger. * The logger.
* *
@ -53,32 +45,29 @@ class PartnerReferrals {
/** /**
* PartnerReferrals constructor. * PartnerReferrals constructor.
* *
* @param string $host The host. * @param string $host The host.
* @param Bearer $bearer The bearer. * @param Bearer $bearer The bearer.
* @param PartnerReferralsData $data The partner referrals data. * @param LoggerInterface $logger The logger.
* @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
string $host, string $host,
Bearer $bearer, Bearer $bearer,
PartnerReferralsData $data,
LoggerInterface $logger LoggerInterface $logger
) { ) {
$this->host = $host; $this->host = $host;
$this->bearer = $bearer; $this->bearer = $bearer;
$this->data = $data;
$this->logger = $logger; $this->logger = $logger;
} }
/** /**
* Fetch the signup link. * Fetch the signup link.
* *
* @param array $data The partner referrals data.
* @return string * @return string
* @throws RuntimeException If the request fails. * @throws RuntimeException If the request fails.
*/ */
public function signup_link(): string { public function signup_link( array $data ): string {
$data = $this->data->data();
$bearer = $this->bearer->bearer(); $bearer = $this->bearer->bearer();
$args = array( $args = array(
'method' => 'POST', 'method' => 'POST',

View file

@ -15,14 +15,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
* Class PartnerReferralsData * Class PartnerReferralsData
*/ */
class PartnerReferralsData { class PartnerReferralsData {
/**
* The merchant email.
*
* @var string
*/
private $merchant_email;
/** /**
* The DCC Applies Helper object. * The DCC Applies Helper object.
* *
@ -30,19 +22,39 @@ class PartnerReferralsData {
*/ */
private $dcc_applies; private $dcc_applies;
/**
* The list of products ('PPCP', 'EXPRESS_CHECKOUT').
*
* @var string[]
*/
private $products;
/** /**
* PartnerReferralsData constructor. * PartnerReferralsData constructor.
* *
* @param string $merchant_email The email of the merchant.
* @param DccApplies $dcc_applies The DCC Applies helper. * @param DccApplies $dcc_applies The DCC Applies helper.
*/ */
public function __construct( public function __construct(
string $merchant_email,
DccApplies $dcc_applies DccApplies $dcc_applies
) { ) {
$this->dcc_applies = $dcc_applies;
$this->products = array(
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
);
}
$this->merchant_email = $merchant_email; /**
$this->dcc_applies = $dcc_applies; * Returns a new copy of this object with the given value set.
*
* @param string[] $products The list of products ('PPCP', 'EXPRESS_CHECKOUT').
* @return static
*/
public function with_products( array $products ): self {
$obj = clone $this;
$obj->products = $products;
return $obj;
} }
/** /**
@ -60,17 +72,6 @@ class PartnerReferralsData {
* @return array * @return array
*/ */
public function data(): array { public function data(): array {
$data = $this->default_data();
return $data;
}
/**
* Returns the default data.
*
* @return array
*/
private function default_data(): array {
return array( return array(
'partner_config_override' => array( 'partner_config_override' => array(
'partner_logo_url' => 'https://connect.woocommerce.com/images/woocommerce_logo.png', 'partner_logo_url' => 'https://connect.woocommerce.com/images/woocommerce_logo.png',
@ -84,9 +85,7 @@ class PartnerReferralsData {
), ),
'show_add_credit_card' => true, 'show_add_credit_card' => true,
), ),
'products' => array( 'products' => $this->products,
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
),
'legal_consents' => array( 'legal_consents' => array(
array( array(
'type' => 'SHARE_DATA_CONSENT', 'type' => 'SHARE_DATA_CONSENT',

View file

@ -52,7 +52,7 @@ return array(
* *
* @var State $state * @var State $state
*/ */
if ( $state->current_state() <= State::STATE_PROGRESSIVE ) { if ( $state->current_state() !== State::STATE_ONBOARDED ) {
return new DisabledSmartButton(); return new DisabledSmartButton();
} }
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );

View file

@ -2,41 +2,28 @@
display: none; display: none;
} }
#field-merchant_email_production, .ppcp-onboarded .ppcp-onboarding-element:not(.ppcp-always-shown-element) {
#field-ppcp_disconnect_sandbox,
#field-ppcp_disconnect_production,
#field-merchant_id_production,
#field-client_id_production,
#field-client_secret_production,
#field-merchant_email_sandbox,
#field-merchant_id_sandbox,
#field-client_id_sandbox,
#field-client_secret_sandbox{
display: none; display: none;
} }
#field-merchant_email_production.show,
#field-ppcp_disconnect_sandbox.show, .ppcp-onboarding .ppcp-settings-field:not(.ppcp-onboarding-element):not(.ppcp-always-shown-element) {
#field-ppcp_disconnect_production.show, display: none;
#field-merchant_id_production.show, }
#field-client_id_production.show,
#field-client_secret_production.show, .ppcp-settings-field.hide {
#field-merchant_email_sandbox.show, display: none;
#field-merchant_id_sandbox.show, }
#field-client_id_sandbox.show,
#field-client_secret_sandbox.show { .ppcp-settings-field.show {
display: table-row; display: table-row;
} }
#field-toggle_manual_input span.hide, label.error {
#field-toggle_manual_input.show span.show{ color: red;
display: none; font-weight: bold;
}
#field-toggle_manual_input.show span.hide {
display: unset;
} }
#field-production_toggle_manual_input button, #field-toggle_manual_input button {
#field-sandbox_toggle_manual_input button {
color: #0073aa; color: #0073aa;
transition-property: border, background, color; transition-property: border, background, color;
transition-duration: .05s; transition-duration: .05s;
@ -49,39 +36,8 @@
padding: 0; padding: 0;
} }
#field-sandbox_toggle_manual_input.onboarded,
#field-production_toggle_manual_input.onboarded {
display: none;
}
#field-ppcp_disconnect_sandbox.onboarded,
#field-ppcp_disconnect_production.onboarded,
#field-merchant_email_sandbox.onboarded,
#field-merchant_id_sandbox.onboarded,
#field-client_id_sandbox.onboarded,
#field-client_secret_sandbox.onboarded,
#field-merchant_email_production.onboarded,
#field-merchant_id_production.onboarded,
#field-client_id_production.onboarded,
#field-client_secret_production.onboarded {
display:table-row;
}
#field-ppcp_disconnect_sandbox.onboarded.hide,
#field-ppcp_disconnect_production.onboarded.hide,
#field-merchant_email_sandbox.onboarded.hide,
#field-merchant_id_sandbox.onboarded.hide,
#field-client_id_sandbox.onboarded.hide,
#field-client_secret_sandbox.onboarded.hide,
#field-merchant_email_production.onboarded.hide,
#field-merchant_id_production.onboarded.hide,
#field-client_id_production.onboarded.hide,
#field-client_secret_production.onboarded.hide {
display:none;
}
/* Probably not the best location for this but will do until there's a general purpose settings CSS file. */ /* Probably not the best location for this but will do until there's a general purpose settings CSS file. */
.ppcp-settings-field-heading td, .ppcp-settings-field-heading th { .ppcp-settings-field-heading td, .ppcp-settings-field-heading th, .ppcp-settings-no-title-col td {
padding-left: 0; padding-left: 0;
} }
@ -92,3 +48,40 @@
.input-text[pattern]:invalid { .input-text[pattern]:invalid {
border: red solid 2px; border: red solid 2px;
} }
ul.ppcp-onboarding-options, ul.ppcp-onboarding-options-sublist {
list-style: none;
}
ul.ppcp-onboarding-options-sublist {
margin-left: 15px;
}
.ppcp-muted-text {
opacity: 0.6;
}
.ppcp-onboarding-header {
display: flex;
width: 1000px;
}
.ppcp-onboarding-header-left, .ppcp-onboarding-header-right {
flex: 50%;
}
.ppcp-onboarding-header h2 {
margin-top: 0;
}
.ppcp-onboarding-header-cards img, .ppcp-onboarding-header-paypal-logos img {
margin: 5px;
}
.ppcp-onboarding-header-cards img {
height: 40px;
}
.ppcp-onboarding-header-paypal-logos img {
height: 45px;
}

View file

@ -1,9 +1,11 @@
// Onboarding.
const ppcp_onboarding = { const ppcp_onboarding = {
BUTTON_SELECTOR: '[data-paypal-onboard-button]', BUTTON_SELECTOR: '[data-paypal-onboard-button]',
PAYPAL_JS_ID: 'ppcp-onboarding-paypal-js', PAYPAL_JS_ID: 'ppcp-onboarding-paypal-js',
_timeout: false, _timeout: false,
STATE_START: 'start',
STATE_ONBOARDED: 'onboarded',
init: function() { init: function() {
document.addEventListener('DOMContentLoaded', this.reload); document.addEventListener('DOMContentLoaded', this.reload);
}, },
@ -15,7 +17,7 @@ const ppcp_onboarding = {
return; return;
} }
// Add event listeners to buttons. // Add event listeners to buttons preventing link clicking if PayPal init failed.
buttons.forEach( buttons.forEach(
(element) => { (element) => {
if (element.hasAttribute('data-ppcp-button-initialized')) { if (element.hasAttribute('data-ppcp-button-initialized')) {
@ -89,8 +91,6 @@ const ppcp_onboarding = {
} }
); );
}, },
}; };
function ppcp_onboarding_sandboxCallback(...args) { function ppcp_onboarding_sandboxCallback(...args) {
@ -101,150 +101,142 @@ function ppcp_onboarding_productionCallback(...args) {
return ppcp_onboarding.loginSeller('production', ...args); return ppcp_onboarding.loginSeller('production', ...args);
} }
/**
* Since the PayPal modal will redirect the user a dirty form
* provokes an alert if the user wants to leave the page. Since the user
* needs to toggle the sandbox switch, we disable this dirty state with the
* following workaround for checkboxes.
*
* @param event
*/
const checkBoxOnClick = (event) => {
const value = event.target.checked;
if (event.target.getAttribute('id') === 'ppcp-sandbox_on') {
toggleSandboxProduction(! value);
}
event.preventDefault();
event.stopPropagation();
setTimeout( () => {
event.target.checked = value;
},1
);
};
/**
* Toggles the credential input fields.
*
* @param forProduction
*/
const credentialToggle = (forProduction) => {
const sandboxClassSelectors = [
'#field-ppcp_disconnect_sandbox',
'#field-merchant_email_sandbox',
'#field-merchant_id_sandbox',
'#field-client_id_sandbox',
'#field-client_secret_sandbox',
];
const productionClassSelectors = [
'#field-ppcp_disconnect_production',
'#field-merchant_email_production',
'#field-merchant_id_production',
'#field-client_id_production',
'#field-client_secret_production',
];
const selectors = forProduction ? productionClassSelectors : sandboxClassSelectors;
document.querySelectorAll(selectors.join()).forEach(
(element) => {element.classList.toggle('show')}
)
};
/**
* Toggles the visibility of the sandbox/production input fields.
*
* @param showProduction
*/
const toggleSandboxProduction = (showProduction) => {
const productionDisplaySelectors = [
'#field-credentials_production_heading',
'#field-production_toggle_manual_input',
'#field-ppcp_onboarding_production',
];
const productionClassSelectors = [
'#field-ppcp_disconnect_production',
'#field-merchant_email_production',
'#field-merchant_id_production',
'#field-client_id_production',
'#field-client_secret_production',
];
const sandboxDisplaySelectors = [
'#field-credentials_sandbox_heading',
'#field-sandbox_toggle_manual_input',
'#field-ppcp_onboarding_sandbox',
];
const sandboxClassSelectors = [
'#field-ppcp_disconnect_sandbox',
'#field-merchant_email_sandbox',
'#field-merchant_id_sandbox',
'#field-client_id_sandbox',
'#field-client_secret_sandbox',
];
if (showProduction) {
document.querySelectorAll(productionDisplaySelectors.join()).forEach(
(element) => {element.style.display = ''}
);
document.querySelectorAll(sandboxDisplaySelectors.join()).forEach(
(element) => {element.style.display = 'none'}
);
document.querySelectorAll(productionClassSelectors.join()).forEach(
(element) => {element.classList.remove('hide')}
);
document.querySelectorAll(sandboxClassSelectors.join()).forEach(
(element) => {
element.classList.remove('show');
element.classList.add('hide');
}
);
return;
}
document.querySelectorAll(productionDisplaySelectors.join()).forEach(
(element) => {element.style.display = 'none'}
);
document.querySelectorAll(sandboxDisplaySelectors.join()).forEach(
(element) => {element.style.display = ''}
);
document.querySelectorAll(sandboxClassSelectors.join()).forEach(
(element) => {element.classList.remove('hide')}
);
document.querySelectorAll(productionClassSelectors.join()).forEach(
(element) => {
element.classList.remove('show');
element.classList.add('hide');
}
)
};
const disconnect = (event) => {
event.preventDefault();
const fields = event.target.classList.contains('production') ? [
'#field-merchant_email_production input',
'#field-merchant_id_production input',
'#field-client_id_production input',
'#field-client_secret_production input',
] : [
'#field-merchant_email_sandbox input',
'#field-merchant_id_sandbox input',
'#field-client_id_sandbox input',
'#field-client_secret_sandbox input',
];
document.querySelectorAll(fields.join()).forEach(
(element) => {
element.value = '';
}
);
document.querySelector('.woocommerce-save-button').click();
};
(() => { (() => {
const sandboxSwitchElement = document.querySelector('#ppcp-sandbox_on'); const productionCredentialElementsSelectors = [
if (sandboxSwitchElement) { '#field-merchant_email_production',
toggleSandboxProduction(! sandboxSwitchElement.checked); '#field-merchant_id_production',
} '#field-client_id_production',
'#field-client_secret_production',
];
const sandboxCredentialElementsSelectors = [
'#field-merchant_email_sandbox',
'#field-merchant_id_sandbox',
'#field-client_id_sandbox',
'#field-client_secret_sandbox',
];
const updateOptionsState = () => {
const cardsChk = document.querySelector('#ppcp-onboarding-accept-cards');
if (!cardsChk) {
return;
}
document.querySelectorAll('#ppcp-onboarding-dcc-options input').forEach(input => {
input.disabled = !cardsChk.checked;
});
const basicRb = document.querySelector('#ppcp-onboarding-dcc-basic');
const isExpress = !cardsChk.checked || basicRb.checked;
const expressButtonSelectors = [
'#field-ppcp_onboarding_production_express',
'#field-ppcp_onboarding_sandbox_express',
];
const ppcpButtonSelectors = [
'#field-ppcp_onboarding_production_ppcp',
'#field-ppcp_onboarding_sandbox_ppcp',
];
document.querySelectorAll(expressButtonSelectors.join()).forEach(
element => element.style.display = isExpress ? '' : 'none'
);
document.querySelectorAll(ppcpButtonSelectors.join()).forEach(
element => element.style.display = !isExpress ? '' : 'none'
);
};
const updateManualInputControls = (shown, isSandbox, isAnyEnvOnboarded) => {
const productionElementsSelectors = productionCredentialElementsSelectors;
const sandboxElementsSelectors = sandboxCredentialElementsSelectors;
const otherElementsSelectors = [
'.woocommerce-save-button',
];
if (!isAnyEnvOnboarded) {
otherElementsSelectors.push('#field-sandbox_on');
}
document.querySelectorAll(productionElementsSelectors.join()).forEach(
element => {
element.classList.remove('hide', 'show');
element.classList.add((shown && !isSandbox) ? 'show' : 'hide');
}
);
document.querySelectorAll(sandboxElementsSelectors.join()).forEach(
element => {
element.classList.remove('hide', 'show');
element.classList.add((shown && isSandbox) ? 'show' : 'hide');
}
);
document.querySelectorAll(otherElementsSelectors.join()).forEach(
element => element.style.display = shown ? '' : 'none'
);
};
const updateEnvironmentControls = (isSandbox) => {
const productionElementsSelectors = [
'#field-ppcp_disconnect_production',
'#field-credentials_production_heading',
];
const sandboxElementsSelectors = [
'#field-ppcp_disconnect_sandbox',
'#field-credentials_sandbox_heading',
];
document.querySelectorAll(productionElementsSelectors.join()).forEach(
element => element.style.display = !isSandbox ? '' : 'none'
);
document.querySelectorAll(sandboxElementsSelectors.join()).forEach(
element => element.style.display = isSandbox ? '' : 'none'
);
};
let isDisconnecting = false;
const disconnect = (event) => {
event.preventDefault();
const fields = event.target.classList.contains('production') ? productionCredentialElementsSelectors : sandboxCredentialElementsSelectors;
document.querySelectorAll(fields.map(f => f + ' input').join()).forEach(
(element) => {
element.value = '';
}
);
isDisconnecting = true;
document.querySelector('.woocommerce-save-button').click();
};
// Prevent the message about unsaved checkbox/radiobutton when reloading the page.
// (WC listens for changes on all inputs and sets dirty flag until form submission)
const preventDirtyCheckboxPropagation = event => {
event.preventDefault();
event.stopPropagation();
const value = event.target.checked;
setTimeout( () => {
event.target.checked = value;
}, 1
);
};
const sandboxSwitchElement = document.querySelector('#ppcp-sandbox_on');
const validate = () => {
const selectors = sandboxSwitchElement.checked ? sandboxCredentialElementsSelectors : productionCredentialElementsSelectors;
const values = selectors.map(s => document.querySelector(s + ' input')).map(el => el.value);
const errors = [];
if (values.some(v => !v)) {
errors.push(PayPalCommerceGatewayOnboarding.error_messages.no_credentials);
}
return errors;
};
const isAnyEnvOnboarded = PayPalCommerceGatewayOnboarding.sandbox_state === ppcp_onboarding.STATE_ONBOARDED ||
PayPalCommerceGatewayOnboarding.production_state === ppcp_onboarding.STATE_ONBOARDED;
document.querySelectorAll('.ppcp-disconnect').forEach( document.querySelectorAll('.ppcp-disconnect').forEach(
(button) => { (button) => {
@ -255,43 +247,89 @@ const disconnect = (event) => {
} }
); );
// Prevent a possibly dirty form arising from this particular checkbox. document.querySelectorAll('.ppcp-onboarding-options input').forEach(
if (sandboxSwitchElement) { (element) => {
sandboxSwitchElement.addEventListener( element.addEventListener('click', event => {
'click', updateOptionsState();
(event) => {
const value = event.target.checked;
toggleSandboxProduction( ! value ); preventDirtyCheckboxPropagation(event);
});
}
);
event.preventDefault(); const isSandboxInBackend = PayPalCommerceGatewayOnboarding.current_env === 'sandbox';
event.stopPropagation(); if (sandboxSwitchElement.checked !== isSandboxInBackend) {
setTimeout( () => { sandboxSwitchElement.checked = isSandboxInBackend;
event.target.checked = value; }
}, 1
);
}
);
}
// document.querySelectorAll('#mainform input[type="checkbox"]').forEach( updateOptionsState();
// (checkbox) => {
// checkbox.addEventListener('click', checkBoxOnClick);
// }
// );
document.querySelectorAll('#field-sandbox_toggle_manual_input button, #field-production_toggle_manual_input button').forEach( const settingsContainer = document.querySelector('#mainform .form-table');
(button) => {
button.addEventListener( const markCurrentOnboardingState = (isOnboarded) => {
'click', settingsContainer.classList.remove('ppcp-onboarded', 'ppcp-onboarding');
(event) => { settingsContainer.classList.add(isOnboarded ? 'ppcp-onboarded' : 'ppcp-onboarding');
event.preventDefault(); }
const isProduction = event.target.classList.contains('production-toggle');
credentialToggle(isProduction); markCurrentOnboardingState(PayPalCommerceGatewayOnboarding.current_state === ppcp_onboarding.STATE_ONBOARDED);
}
) const manualInputToggleButton = document.querySelector('#field-toggle_manual_input button');
} let isManualInputShown = PayPalCommerceGatewayOnboarding.current_state === ppcp_onboarding.STATE_ONBOARDED;
);
manualInputToggleButton.addEventListener(
'click',
(event) => {
event.preventDefault();
isManualInputShown = !isManualInputShown;
updateManualInputControls(isManualInputShown, sandboxSwitchElement.checked, isAnyEnvOnboarded);
}
);
sandboxSwitchElement.addEventListener(
'click',
(event) => {
const isSandbox = sandboxSwitchElement.checked;
if (isAnyEnvOnboarded) {
const onboardingState = isSandbox ? PayPalCommerceGatewayOnboarding.sandbox_state : PayPalCommerceGatewayOnboarding.production_state;
const isOnboarded = onboardingState === ppcp_onboarding.STATE_ONBOARDED;
markCurrentOnboardingState(isOnboarded);
isManualInputShown = isOnboarded;
}
updateManualInputControls(isManualInputShown, isSandbox, isAnyEnvOnboarded);
updateEnvironmentControls(isSandbox);
preventDirtyCheckboxPropagation(event);
}
);
updateManualInputControls(isManualInputShown, sandboxSwitchElement.checked, isAnyEnvOnboarded);
updateEnvironmentControls(sandboxSwitchElement.checked);
document.querySelector('#mainform').addEventListener('submit', e => {
if (isDisconnecting) {
return;
}
const errors = validate();
if (errors.length) {
e.preventDefault();
const errorLabel = document.querySelector('#ppcp-form-errors-label');
errorLabel.parentElement.parentElement.classList.remove('hide');
errorLabel.innerHTML = errors.join('<br/>');
errorLabel.scrollIntoView();
window.scrollBy(0, -120); // WP + WC floating header
}
});
// Onboarding buttons. // Onboarding buttons.
ppcp_onboarding.init(); ppcp_onboarding.init();

View file

@ -23,7 +23,7 @@ document.addEventListener(
} }
group.forEach( (elementToShow) => { group.forEach( (elementToShow) => {
document.querySelector(elementToShow).style.display = 'table-row'; document.querySelector(elementToShow).style.display = '';
}) })
if('ppcp-message_enabled' === event.target.getAttribute('id')){ if('ppcp-message_enabled' === event.target.getAttribute('id')){
@ -56,7 +56,7 @@ document.addEventListener(
return; return;
} }
if (value === elementToToggle.value && domElement.style.display !== 'none') { if (value === elementToToggle.value && domElement.style.display !== 'none') {
domElement.style.display = 'table-row'; domElement.style.display = '';
return; return;
} }
domElement.style.display = 'none'; domElement.style.display = 'none';
@ -69,7 +69,7 @@ document.addEventListener(
const value = event.target.value; const value = event.target.value;
group.forEach( (elementToToggle) => { group.forEach( (elementToToggle) => {
if (value === elementToToggle.value) { if (value === elementToToggle.value) {
document.querySelector(elementToToggle.selector).style.display = 'table-row'; document.querySelector(elementToToggle.selector).style.display = '';
return; return;
} }
document.querySelector(elementToToggle.selector).style.display = 'none'; document.querySelector(elementToToggle.selector).style.display = 'none';

View file

@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController; use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
@ -117,9 +118,8 @@ return array(
); );
}, },
'onboarding.state' => function( ContainerInterface $container ) : State { 'onboarding.state' => function( ContainerInterface $container ) : State {
$environment = $container->get( 'onboarding.environment' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new State( $environment, $settings ); return new State( $settings );
}, },
'onboarding.environment' => function( ContainerInterface $container ) : Environment { 'onboarding.environment' => function( ContainerInterface $container ) : Environment {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
@ -132,6 +132,7 @@ return array(
return new OnboardingAssets( return new OnboardingAssets(
$container->get( 'onboarding.url' ), $container->get( 'onboarding.url' ),
$state, $state,
$container->get( 'onboarding.environment' ),
$login_seller_endpoint $login_seller_endpoint
); );
}, },
@ -188,7 +189,6 @@ return array(
return new PartnerReferrals( return new PartnerReferrals(
CONNECT_WOO_SANDBOX_URL, CONNECT_WOO_SANDBOX_URL,
new ConnectBearer(), new ConnectBearer(),
$container->get( 'api.repository.partner-referrals-data' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
@ -197,7 +197,6 @@ return array(
return new PartnerReferrals( return new PartnerReferrals(
CONNECT_WOO_URL, CONNECT_WOO_URL,
new ConnectBearer(), new ConnectBearer(),
$container->get( 'api.repository.partner-referrals-data' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
@ -205,13 +204,18 @@ return array(
$partner_referrals = $container->get( 'api.endpoint.partner-referrals-production' ); $partner_referrals = $container->get( 'api.endpoint.partner-referrals-production' );
$partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' ); $partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' );
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new OnboardingRenderer( return new OnboardingRenderer(
$settings, $settings,
$partner_referrals, $partner_referrals,
$partner_referrals_sandbox $partner_referrals_sandbox,
$partner_referrals_data
); );
}, },
'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer {
return new OnboardingOptionsRenderer();
},
'onboarding.rest' => static function( $container ) : OnboardingRESTController { 'onboarding.rest' => static function( $container ) : OnboardingRESTController {
return new OnboardingRESTController( $container ); return new OnboardingRESTController( $container );
}, },

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding\Assets; namespace WooCommerce\PayPalCommerce\Onboarding\Assets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
/** /**
@ -31,6 +32,13 @@ class OnboardingAssets {
*/ */
private $state; private $state;
/**
* The Environment.
*
* @var Environment
*/
private $environment;
/** /**
* The LoginSeller Endpoint. * The LoginSeller Endpoint.
* *
@ -43,16 +51,19 @@ class OnboardingAssets {
* *
* @param string $module_url The URL to the module. * @param string $module_url The URL to the module.
* @param State $state The State object. * @param State $state The State object.
* @param Environment $environment The Environment.
* @param LoginSellerEndpoint $login_seller_endpoint The LoginSeller endpoint. * @param LoginSellerEndpoint $login_seller_endpoint The LoginSeller endpoint.
*/ */
public function __construct( public function __construct(
string $module_url, string $module_url,
State $state, State $state,
Environment $environment,
LoginSellerEndpoint $login_seller_endpoint LoginSellerEndpoint $login_seller_endpoint
) { ) {
$this->module_url = untrailingslashit( $module_url ); $this->module_url = untrailingslashit( $module_url );
$this->state = $state; $this->state = $state;
$this->environment = $environment;
$this->login_seller_endpoint = $login_seller_endpoint; $this->login_seller_endpoint = $login_seller_endpoint;
} }
@ -103,9 +114,16 @@ class OnboardingAssets {
*/ */
public function get_script_data() { public function get_script_data() {
return array( return array(
'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ), 'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ), 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ),
'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js',
'sandbox_state' => State::get_state_name( $this->state->sandbox_state() ),
'production_state' => State::get_state_name( $this->state->production_state() ),
'current_state' => State::get_state_name( $this->state->current_state() ),
'current_env' => $this->environment->current_environment(),
'error_messages' => array(
'no_credentials' => __( 'Enter the credentials.', 'woocommerce-paypal-payments' ),
),
); );
} }

View file

@ -65,16 +65,15 @@ class OnboardingModule implements ModuleInterface {
if ( 'ppcp_onboarding' !== $config['type'] ) { if ( 'ppcp_onboarding' !== $config['type'] ) {
return $field; return $field;
} }
$renderer = $c->get( 'onboarding.render' );
$is_production = 'production' === $config['env'];
/** $renderer = $c->get( 'onboarding.render' );
* The OnboardingRenderer. assert( $renderer instanceof OnboardingRenderer );
*
* @var OnboardingRenderer $renderer $is_production = 'production' === $config['env'];
*/ $products = $config['products'];
ob_start(); ob_start();
$renderer->render( $is_production ); $renderer->render( $is_production, $products );
$content = ob_get_contents(); $content = ob_get_contents();
ob_end_clean(); ob_end_clean();
return $content; return $content;

View file

@ -10,7 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding; namespace WooCommerce\PayPalCommerce\Onboarding;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/** /**
@ -138,13 +137,13 @@ class OnboardingRESTController {
return array( return array(
'environment' => $environment->current_environment(), 'environment' => $environment->current_environment(),
'onboarded' => ( $state->current_state() >= State::STATE_ONBOARDED ), 'onboarded' => ( $state->current_state() >= State::STATE_ONBOARDED ),
'state' => $this->get_onboarding_state_name( $state->current_state() ), 'state' => State::get_state_name( $state->current_state() ),
'sandbox' => array( 'sandbox' => array(
'state' => $this->get_onboarding_state_name( $state->sandbox_state() ), 'state' => State::get_state_name( $state->sandbox_state() ),
'onboarded' => ( $state->sandbox_state() >= State::STATE_ONBOARDED ), 'onboarded' => ( $state->sandbox_state() >= State::STATE_ONBOARDED ),
), ),
'production' => array( 'production' => array(
'state' => $this->get_onboarding_state_name( $state->production_state() ), 'state' => State::get_state_name( $state->production_state() ),
'onboarded' => ( $state->production_state() >= State::STATE_ONBOARDED ), 'onboarded' => ( $state->production_state() >= State::STATE_ONBOARDED ),
), ),
); );
@ -265,34 +264,6 @@ class OnboardingRESTController {
return add_query_arg( $this->return_url_args, $url ); return add_query_arg( $this->return_url_args, $url );
} }
/**
* Translates an onboarding state to a string.
*
* @param int $state An onboarding state to translate as returned by {@link State} methods.
* @return string A string representing the state: "start", "progressive" or "onboarded".
* @see State::current_state(), State::sandbox_state(), State::production_state().
*/
public function get_onboarding_state_name( $state ) {
$name = 'unknown';
switch ( absint( $state ) ) {
case State::STATE_START:
$name = 'start';
break;
case State::STATE_PROGRESSIVE:
$name = 'progressive';
break;
case State::STATE_ONBOARDED:
$name = 'onboarded';
break;
default:
break;
}
return $name;
}
/** /**
* Generates a signup link for onboarding for a given environment and optionally adding certain URL arguments * Generates a signup link for onboarding for a given environment and optionally adding certain URL arguments
* to the URL users are redirected after completing the onboarding flow. * to the URL users are redirected after completing the onboarding flow.

View file

@ -0,0 +1,68 @@
<?php
/**
* Renders the onboarding options.
*
* @package WooCommerce\PayPalCommerce\Onboarding\Render
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding\Render;
/**
* Class OnboardingRenderer
*/
class OnboardingOptionsRenderer {
/**
* Renders the onboarding options.
*
* @param bool $is_shop_supports_dcc Whether the shop can use DCC (country, currency).
*/
public function render( bool $is_shop_supports_dcc ): string {
return '
<ul class="ppcp-onboarding-options">
<li>
<label><input type="checkbox" disabled checked> ' .
__( 'Accept PayPal, Venmo, Pay Later and local payment methods', 'woocommerce-paypal-payments' ) . '
</label>
</li>
<li>
<label><input type="checkbox" id="ppcp-onboarding-accept-cards" checked> ' .
__( 'Securely accept all major credit & debit cards on the strength of the PayPal network', 'woocommerce-paypal-payments' ) . '
</label>
</li>
<li>' . $this->render_dcc( $is_shop_supports_dcc ) . '</li>
</ul>';
}
/**
* Renders the onboarding DCC options.
*
* @param bool $is_shop_supports_dcc Whether the shop can use DCC (country, currency).
*/
private function render_dcc( bool $is_shop_supports_dcc ): string {
$items = array();
if ( $is_shop_supports_dcc ) {
$items[] = '
<li>
<label><input type="radio" id="ppcp-onboarding-dcc-acdc" name="ppcp_onboarding_dcc" value="acdc" checked> ' .
__( 'Advanced credit and debit card processing', 'woocommerce-paypal-payments' ) . '*<br/> ' .
__( '(With advanced fraud protection and fully customizable card fields)', 'woocommerce-paypal-payments' ) . '
<span class="ppcp-muted-text">*' . __( 'Additional onboarding steps required', 'woocommerce-paypal-payments' ) . '</span>
</label>
</li>';
}
$items[] = '
<li>
<label><input type="radio" id="ppcp-onboarding-dcc-basic" name="ppcp_onboarding_dcc" value="basic" ' . ( ! $is_shop_supports_dcc ? 'checked' : '' ) . '> ' .
__( 'Basic credit and debit card processing', 'woocommerce-paypal-payments' ) . '
</label>
</li>';
return '<ul id="ppcp-onboarding-dcc-options" class="ppcp-onboarding-options-sublist">' .
implode( '', $items ) .
'</ul>';
}
}

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Onboarding\Render;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
@ -39,31 +40,50 @@ class OnboardingRenderer {
*/ */
private $sandbox_partner_referrals; private $sandbox_partner_referrals;
/**
* The default partner referrals data.
*
* @var PartnerReferralsData
*/
private $partner_referrals_data;
/** /**
* OnboardingRenderer constructor. * OnboardingRenderer constructor.
* *
* @param Settings $settings The settings. * @param Settings $settings The settings.
* @param PartnerReferrals $production_partner_referrals The PartnerReferrals for production. * @param PartnerReferrals $production_partner_referrals The PartnerReferrals for production.
* @param PartnerReferrals $sandbox_partner_referrals The PartnerReferrals for sandbox. * @param PartnerReferrals $sandbox_partner_referrals The PartnerReferrals for sandbox.
* @param PartnerReferralsData $partner_referrals_data The default partner referrals data.
*/ */
public function __construct( Settings $settings, PartnerReferrals $production_partner_referrals, PartnerReferrals $sandbox_partner_referrals ) { public function __construct(
Settings $settings,
PartnerReferrals $production_partner_referrals,
PartnerReferrals $sandbox_partner_referrals,
PartnerReferralsData $partner_referrals_data
) {
$this->settings = $settings; $this->settings = $settings;
$this->production_partner_referrals = $production_partner_referrals; $this->production_partner_referrals = $production_partner_referrals;
$this->sandbox_partner_referrals = $sandbox_partner_referrals; $this->sandbox_partner_referrals = $sandbox_partner_referrals;
$this->partner_referrals_data = $partner_referrals_data;
} }
/** /**
* Returns the action URL for the onboarding button/link. * Returns the action URL for the onboarding button/link.
* *
* @param boolean $is_production Whether the production or sandbox button should be rendered. * @param boolean $is_production Whether the production or sandbox button should be rendered.
* @param string[] $products The list of products ('PPCP', 'EXPRESS_CHECKOUT').
* @return string URL. * @return string URL.
*/ */
public function get_signup_link( bool $is_production ) { public function get_signup_link( bool $is_production, array $products ) {
$args = array( $args = array(
'displayMode' => 'minibrowser', 'displayMode' => 'minibrowser',
); );
$url = $is_production ? $this->production_partner_referrals->signup_link() : $this->sandbox_partner_referrals->signup_link(); $data = $this->partner_referrals_data
->with_products( $products )
->data();
$url = $is_production ? $this->production_partner_referrals->signup_link( $data ) : $this->sandbox_partner_referrals->signup_link( $data );
$url = add_query_arg( $args, $url ); $url = add_query_arg( $args, $url );
return $url; return $url;
@ -72,14 +92,18 @@ class OnboardingRenderer {
/** /**
* Renders the "Connect to PayPal" button. * Renders the "Connect to PayPal" button.
* *
* @param bool $is_production Whether the production or sandbox button should be rendered. * @param bool $is_production Whether the production or sandbox button should be rendered.
* @param string[] $products The list of products ('PPCP', 'EXPRESS_CHECKOUT').
*/ */
public function render( bool $is_production ) { public function render( bool $is_production, array $products ) {
try { try {
$id = 'connect-to' . ( $is_production ? 'production' : 'sandbox' ) . strtolower( implode( '-', $products ) );
$this->render_button( $this->render_button(
$this->get_signup_link( $is_production ), $this->get_signup_link( $is_production, $products ),
$is_production ? 'connect-to-production' : 'connect-to-sandbox', $id,
$is_production ? __( 'Connect to PayPal', 'woocommerce-paypal-payments' ) : __( 'Connect to PayPal Sandbox', 'woocommerce-paypal-payments' ), $is_production ? __( 'Connect with PayPal', 'woocommerce-paypal-payments' ) : __( 'Test payments with PayPal sandbox', 'woocommerce-paypal-payments' ),
$is_production ? 'primary' : 'secondary',
$is_production ? 'production' : 'sandbox' $is_production ? 'production' : 'sandbox'
); );
} catch ( RuntimeException $exception ) { } catch ( RuntimeException $exception ) {
@ -96,13 +120,14 @@ class OnboardingRenderer {
* @param string $url The url of the button. * @param string $url The url of the button.
* @param string $id The ID of the button. * @param string $id The ID of the button.
* @param string $label The button text. * @param string $label The button text.
* @param string $class The CSS class for button ('primary', 'secondary').
* @param string $env The environment ('production' or 'sandbox'). * @param string $env The environment ('production' or 'sandbox').
*/ */
private function render_button( string $url, string $id, string $label, string $env ) { private function render_button( string $url, string $id, string $label, string $class, string $env ) {
?> ?>
<a <a
target="_blank" target="_blank"
class="button-primary" class="button-<?php echo esc_attr( $class ); ?>"
id="<?php echo esc_attr( $id ); ?>" id="<?php echo esc_attr( $id ); ?>"
data-paypal-onboard-complete="ppcp_onboarding_<?php echo esc_attr( $env ); ?>Callback" data-paypal-onboard-complete="ppcp_onboarding_<?php echo esc_attr( $env ); ?>Callback"
data-paypal-onboard-button="true" data-paypal-onboard-button="true"

View file

@ -16,16 +16,8 @@ use Psr\Container\ContainerInterface;
*/ */
class State { class State {
const STATE_START = 0; const STATE_START = 0;
const STATE_PROGRESSIVE = 4; const STATE_ONBOARDED = 8;
const STATE_ONBOARDED = 8;
/**
* The Environment.
*
* @var Environment
*/
private $environment;
/** /**
* The Settings. * The Settings.
@ -37,16 +29,29 @@ class State {
/** /**
* State constructor. * State constructor.
* *
* @param Environment $environment The Environment.
* @param ContainerInterface $settings The Settings. * @param ContainerInterface $settings The Settings.
*/ */
public function __construct( public function __construct(
Environment $environment,
ContainerInterface $settings ContainerInterface $settings
) { ) {
$this->environment = $environment; $this->settings = $settings;
$this->settings = $settings; }
/**
* Returns the state of the specified environment (or the active environment if null).
*
* @param string|null $environment 'sandbox', 'production'.
* @return int
*/
public function environment_state( ?string $environment = null ): int {
switch ( $environment ) {
case Environment::PRODUCTION:
return $this->production_state();
case Environment::SANDBOX:
return $this->sandbox_state();
}
return $this->current_state();
} }
/** /**
@ -57,9 +62,6 @@ class State {
public function current_state(): int { public function current_state(): int {
return $this->state_by_keys( return $this->state_by_keys(
array(
'merchant_email',
),
array( array(
'merchant_email', 'merchant_email',
'merchant_id', 'merchant_id',
@ -77,9 +79,6 @@ class State {
public function sandbox_state() : int { public function sandbox_state() : int {
return $this->state_by_keys( return $this->state_by_keys(
array(
'merchant_email_sandbox',
),
array( array(
'merchant_email_sandbox', 'merchant_email_sandbox',
'merchant_id_sandbox', 'merchant_id_sandbox',
@ -97,9 +96,6 @@ class State {
public function production_state() : int { public function production_state() : int {
return $this->state_by_keys( return $this->state_by_keys(
array(
'merchant_email_production',
),
array( array(
'merchant_email_production', 'merchant_email_production',
'merchant_id_production', 'merchant_id_production',
@ -110,36 +106,36 @@ class State {
} }
/** /**
* Returns the state based on progressive and onboarded values being looked up in the settings. * Translates an onboarding state to a string.
*
* @param int $state An onboarding state to translate.
* @return string A string representing the state: "start" or "onboarded".
*/
public static function get_state_name( int $state ) : string {
switch ( $state ) {
case self::STATE_START:
return 'start';
case self::STATE_ONBOARDED:
return 'onboarded';
default:
return 'unknown';
}
}
/**
* Returns the state based on onboarding settings values.
* *
* @param array $progressive_keys The keys which need to be present to be at least in progressive state.
* @param array $onboarded_keys The keys which need to be present to be in onboarded state. * @param array $onboarded_keys The keys which need to be present to be in onboarded state.
* *
* @return int * @return int
*/ */
private function state_by_keys( array $progressive_keys, array $onboarded_keys ) : int { private function state_by_keys( array $onboarded_keys ) : int {
$state = self::STATE_START;
$is_progressive = true;
foreach ( $progressive_keys as $key ) {
if ( ! $this->settings->has( $key ) || ! $this->settings->get( $key ) ) {
$is_progressive = false;
}
}
if ( $is_progressive ) {
$state = self::STATE_PROGRESSIVE;
}
$is_onboarded = true;
foreach ( $onboarded_keys as $key ) { foreach ( $onboarded_keys as $key ) {
if ( ! $this->settings->has( $key ) || ! $this->settings->get( $key ) ) { if ( ! $this->settings->has( $key ) || ! $this->settings->get( $key ) ) {
$is_onboarded = false; return self::STATE_START;
} }
} }
if ( $is_onboarded ) { return self::STATE_ONBOARDED;
$state = self::STATE_ONBOARDED;
}
return $state;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because it is too large Load diff

View file

@ -71,6 +71,6 @@ class ConnectAdminNotice {
* @return bool * @return bool
*/ */
protected function should_display(): bool { protected function should_display(): bool {
return $this->state->current_state() < State::STATE_PROGRESSIVE; return $this->state->current_state() !== State::STATE_ONBOARDED;
} }
} }

View file

@ -377,8 +377,18 @@ $data_rows_html
?> ?>
<input type="hidden" name="ppcp-nonce" value="<?php echo esc_attr( $nonce ); ?>"> <input type="hidden" name="ppcp-nonce" value="<?php echo esc_attr( $nonce ); ?>">
<?php <?php
// Create a hidden first row with 2 cells to avoid issues with table-layout: fixed
// when the first visible row needs to have one cell.
?>
<tr style="height: 1px; padding-top: 0; padding-bottom: 0;">
<th style="padding-top: 0; padding-bottom: 0;"></th>
<td style="padding-top: 0; padding-bottom: 0;"></td>
</tr>
<?php
foreach ( $this->fields as $field => $config ) : foreach ( $this->fields as $field => $config ) :
if ( ! in_array( $this->state->current_state(), $config['screens'], true ) ) { if ( ! in_array( $this->state->environment_state( $config['state_from'] ?? null ), $config['screens'], true ) ) {
continue; continue;
} }
if ( ! $this->field_matches_page( $config, $this->page_id ) ) { if ( ! $this->field_matches_page( $config, $this->page_id ) ) {
@ -406,14 +416,18 @@ $data_rows_html
$key = 'ppcp[' . $field . ']'; $key = 'ppcp[' . $field . ']';
$id = 'ppcp-' . $field; $id = 'ppcp-' . $field;
$config['id'] = $id; $config['id'] = $id;
$colspan = 'ppcp-heading' !== $config['type'] ? 1 : 2; $colspan = ( 'ppcp-heading' !== $config['type'] && isset( $config['title'] ) ) ? 1 : 2;
$classes = isset( $config['classes'] ) ? $config['classes'] : array(); $classes = isset( $config['classes'] ) ? $config['classes'] : array();
$classes[] = 'ppcp-settings-field';
$classes[] = sprintf( 'ppcp-settings-field-%s', str_replace( 'ppcp-', '', $config['type'] ) ); $classes[] = sprintf( 'ppcp-settings-field-%s', str_replace( 'ppcp-', '', $config['type'] ) );
$description = isset( $config['description'] ) ? $config['description'] : ''; if ( 1 !== $colspan ) {
$classes[] = 'ppcp-settings-no-title-col';
}
$description = isset( $config['description'] ) ? $config['description'] : '';
unset( $config['description'] ); unset( $config['description'] );
?> ?>
<tr valign="top" id="<?php echo esc_attr( 'field-' . $field ); ?>" class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>"> <tr valign="top" id="<?php echo esc_attr( 'field-' . $field ); ?>" class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
<?php if ( 'ppcp-heading' !== $config['type'] ) : ?> <?php if ( 'ppcp-heading' !== $config['type'] && isset( $config['title'] ) ) : ?>
<th scope="row"> <th scope="row">
<label <label
for="<?php echo esc_attr( $id ); ?>" for="<?php echo esc_attr( $id ); ?>"
@ -462,7 +476,14 @@ $data_rows_html
* @param array $config The configuration array. * @param array $config The configuration array.
*/ */
private function render_text( array $config ) { private function render_text( array $config ) {
echo wp_kses_post( $config['text'] ); $raw = $config['raw'] ?? false;
if ( $raw ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $config['text'];
} else {
echo wp_kses_post( $config['text'] );
}
if ( isset( $config['hidden'] ) ) { if ( isset( $config['hidden'] ) ) {
$value = $this->settings->has( $config['hidden'] ) ? $value = $this->settings->has( $config['hidden'] ) ?
(string) $this->settings->get( $config['hidden'] ) (string) $this->settings->get( $config['hidden'] )

View file

@ -19,7 +19,6 @@ return array(
'title' => __( 'Subscribed webhooks', 'woocommerce-paypal-payments' ), 'title' => __( 'Subscribed webhooks', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-table', 'type' => 'ppcp-table',
'screens' => array( 'screens' => array(
State::STATE_PROGRESSIVE,
State::STATE_ONBOARDED, State::STATE_ONBOARDED,
), ),
'requirements' => array(), 'requirements' => array(),
@ -34,7 +33,6 @@ return array(
'type' => 'ppcp-text', 'type' => 'ppcp-text',
'text' => '<button type="button" class="button ppcp-webhooks-resubscribe">' . esc_html__( 'Resubscribe', 'woocommerce-paypal-payments' ) . '</button>', 'text' => '<button type="button" class="button ppcp-webhooks-resubscribe">' . esc_html__( 'Resubscribe', 'woocommerce-paypal-payments' ) . '</button>',
'screens' => array( 'screens' => array(
State::STATE_PROGRESSIVE,
State::STATE_ONBOARDED, State::STATE_ONBOARDED,
), ),
'requirements' => array(), 'requirements' => array(),
@ -53,7 +51,6 @@ return array(
'type' => 'ppcp-text', 'type' => 'ppcp-text',
'text' => '<button type="button" class="button ppcp-webhooks-simulate">' . esc_html__( 'Simulate', 'woocommerce-paypal-payments' ) . '</button>', 'text' => '<button type="button" class="button ppcp-webhooks-simulate">' . esc_html__( 'Simulate', 'woocommerce-paypal-payments' ) . '</button>',
'screens' => array( 'screens' => array(
State::STATE_PROGRESSIVE,
State::STATE_ONBOARDED, State::STATE_ONBOARDED,
), ),
'requirements' => array(), 'requirements' => array(),

View file

@ -270,7 +270,6 @@ class WcGatewayTest extends TestCase
{ {
return [ return [
[State::STATE_START, true], [State::STATE_START, true],
[State::STATE_PROGRESSIVE, true],
[State::STATE_ONBOARDED, false] [State::STATE_ONBOARDED, false]
]; ];
} }