Merge branch 'trunk' into PCP-2006-google-pay-settings-improvements

This commit is contained in:
Pedro Silva 2023-09-21 16:10:28 +01:00
commit ad018766a6
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
57 changed files with 1367 additions and 2686 deletions

View file

@ -0,0 +1,11 @@
.ppcp-field-hidden {
display: none !important;
}
.ppcp-field-disabled {
cursor: not-allowed;
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
opacity: 0.5;
}

View file

@ -1,51 +0,0 @@
class SubElementsHandler {
constructor(element, options) {
const fieldSelector = 'input, select, textarea';
this.element = element;
this.values = options.values;
this.elements = options.elements;
this.elementsSelector = this.elements.join(',');
this.input = jQuery(this.element).is(fieldSelector)
? this.element
: jQuery(this.element).find(fieldSelector).get(0);
this.updateElementsVisibility();
jQuery(this.input).change(() => {
this.updateElementsVisibility();
});
}
updateElementsVisibility() {
const $elements = jQuery(this.elementsSelector);
let value = this.getValue(this.input);
value = (value !== null ? value.toString() : value);
if (this.values.indexOf(value) !== -1) {
$elements.removeClass('hide');
} else {
$elements.addClass('hide');
}
}
getValue(element) {
const $el = jQuery(element);
if ($el.is(':checkbox') || $el.is(':radio')) {
if ($el.is(':checked')) {
return $el.val();
} else {
return null;
}
} else {
return $el.val();
}
}
}
export default SubElementsHandler;

View file

@ -1,10 +1,28 @@
import DisplayManager from "./common/display-manager/DisplayManager";
import moveWrappedElements from "./common/wrapped-elements";
document.addEventListener(
'DOMContentLoaded',
() => {
// Wait for current execution context to end.
setTimeout(function () {
moveWrappedElements();
}, 0);
// Initialize DisplayManager.
const displayManager = new DisplayManager();
jQuery( '*[data-ppcp-display]' ).each( (index, el) => {
const rules = jQuery(el).data('ppcpDisplay');
console.log('rules', rules);
for (const rule of rules) {
displayManager.addRule(rule);
}
});
displayManager.register();
}
);

View file

@ -0,0 +1,14 @@
import ElementAction from "./action/ElementAction";
class ActionFactory {
static make(actionConfig) {
switch (actionConfig.type) {
case 'element':
return new ElementAction(actionConfig);
}
throw new Error('[ActionFactory] Unknown action: ' + actionConfig.type);
}
}
export default ActionFactory;

View file

@ -0,0 +1,17 @@
import ElementCondition from "./condition/ElementCondition";
import BoolCondition from "./condition/BoolCondition";
class ConditionFactory {
static make(conditionConfig, triggerUpdate) {
switch (conditionConfig.type) {
case 'element':
return new ElementCondition(conditionConfig, triggerUpdate);
case 'bool':
return new BoolCondition(conditionConfig, triggerUpdate);
}
throw new Error('[ConditionFactory] Unknown condition: ' + conditionConfig.type);
}
}
export default ConditionFactory;

View file

@ -0,0 +1,32 @@
import Rule from "./Rule";
class DisplayManager {
constructor() {
this.rules = {};
this.ruleStatus = {}; // The current status for each rule. Maybe not necessary, for now just for logging.
document.ppcpDisplayManagerLog = () => {
console.log('DisplayManager', this);
}
}
addRule(ruleConfig) {
const updateStatus = () => {
this.ruleStatus[ruleConfig.key] = this.rules[ruleConfig.key].status;
console.log('ruleStatus', this.ruleStatus);
}
this.rules[ruleConfig.key] = new Rule(ruleConfig, updateStatus.bind(this));
console.log('Rule', this.rules[ruleConfig.key]);
}
register() {
this.ruleStatus = {};
for (const [key, rule] of Object.entries(this.rules)) {
rule.register();
}
}
}
export default DisplayManager;

View file

@ -0,0 +1,68 @@
import ConditionFactory from "./ConditionFactory";
import ActionFactory from "./ActionFactory";
class Rule {
constructor(config, triggerUpdate) {
this.config = config;
this.conditions = {};
this.actions = {};
this.triggerUpdate = triggerUpdate;
this.status = null;
const updateStatus = this.updateStatus.bind(this);
for (const conditionConfig of this.config.conditions) {
const condition = ConditionFactory.make(conditionConfig, updateStatus);
this.conditions[condition.key] = condition;
console.log('Condition', condition);
}
for (const actionConfig of this.config.actions) {
const action = ActionFactory.make(actionConfig);
this.actions[action.key] = action;
console.log('Action', action);
}
}
get key() {
return this.config.key;
}
updateStatus(forceRunActions = false) {
let status = true;
for (const [key, condition] of Object.entries(this.conditions)) {
status &= condition.status;
}
if (status !== this.status) {
this.status = status;
this.triggerUpdate();
this.runActions();
} else if (forceRunActions) {
this.runActions();
}
}
runActions() {
for (const [key, action] of Object.entries(this.actions)) {
action.run(this.status);
}
}
register() {
for (const [key, condition] of Object.entries(this.conditions)) {
condition.register(this.updateStatus.bind(this));
}
for (const [key, action] of Object.entries(this.actions)) {
action.register();
}
this.updateStatus(true);
}
}
export default Rule;

View file

@ -0,0 +1,21 @@
class BaseAction {
constructor(config) {
this.config = config;
}
get key() {
return this.config.key;
}
register() {
// To override.
}
run(status) {
// To override.
}
}
export default BaseAction;

View file

@ -0,0 +1,35 @@
import BaseAction from "./BaseAction";
class ElementAction extends BaseAction {
run(status) {
if (status) {
if (this.config.action === 'visible') {
jQuery(this.config.selector).removeClass('ppcp-field-hidden');
}
if (this.config.action === 'enable') {
jQuery(this.config.selector).removeClass('ppcp-field-disabled')
.off('mouseup')
.find('> *')
.css('pointer-events', '');
}
} else {
if (this.config.action === 'visible') {
jQuery(this.config.selector).addClass('ppcp-field-hidden');
}
if (this.config.action === 'enable') {
jQuery(this.config.selector).addClass('ppcp-field-disabled')
.on('mouseup', function(event) {
event.stopImmediatePropagation();
})
.find('> *')
.css('pointer-events', 'none');
}
}
}
}
export default ElementAction;

View file

@ -0,0 +1,19 @@
class BaseCondition {
constructor(config, triggerUpdate) {
this.config = config;
this.status = false;
this.triggerUpdate = triggerUpdate;
}
get key() {
return this.config.key;
}
register() {
// To override.
}
}
export default BaseCondition;

View file

@ -0,0 +1,15 @@
import BaseCondition from "./BaseCondition";
class BoolCondition extends BaseCondition {
register() {
this.status = this.check();
}
check() {
return !! this.config.value;
}
}
export default BoolCondition;

View file

@ -0,0 +1,27 @@
import BaseCondition from "./BaseCondition";
import {inputValue} from "../../../helper/form";
class ElementCondition extends BaseCondition {
register() {
jQuery(document).on('change', this.config.selector, () => {
const status = this.check();
if (status !== this.status) {
this.status = status;
this.triggerUpdate();
}
});
this.status = this.check();
}
check() {
let value = inputValue(this.config.selector);
value = (value !== null ? value.toString() : value);
return this.config.value === value;
}
}
export default ElementCondition;

View file

@ -4,7 +4,6 @@ import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Rendere
import MessageRenderer from "../../../ppcp-button/resources/js/modules/Renderer/MessageRenderer";
import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding";
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
import SubElementsHandler from "./SettingsHandler/SubElementsHandler";
document.addEventListener(
'DOMContentLoaded',
@ -309,16 +308,5 @@ document.addEventListener(
createButtonPreview(() => getButtonDefaultSettings('#ppcpPayLaterButtonPreview'));
});
}
// Generic behaviours, can be moved to common.js once it's on trunk branch.
jQuery( '*[data-ppcp-handlers]' ).each( (index, el) => {
const handlers = jQuery(el).data('ppcpHandlers');
for (const handlerConfig of handlers) {
new {
SubElementsHandler: SubElementsHandler
}[handlerConfig.handler](el, handlerConfig.options)
}
});
}
);

View file

@ -0,0 +1,13 @@
export const inputValue = (element) => {
const $el = jQuery(element);
if ($el.is(':checkbox') || $el.is(':radio')) {
if ($el.is(':checked')) {
return $el.val();
} else {
return null;
}
} else {
return $el.val();
}
}

View file

@ -11,21 +11,18 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
@ -35,6 +32,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSessionId;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
@ -42,15 +42,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSessionId;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFactory;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
@ -441,7 +439,7 @@ return array(
assert( $subscription_helper instanceof SubscriptionHelper );
$fields = array(
'checkout_settings_heading' => array(
'checkout_settings_heading' => array(
'heading' => __( 'Standard Payments Settings', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
@ -451,7 +449,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'title' => array(
'title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
@ -467,7 +465,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'dcc_enabled' => array(
'dcc_enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'desc_tip' => true,
'description' => __( 'Once enabled, the Credit Card option will show up in the checkout.', 'woocommerce-paypal-payments' ),
@ -482,7 +480,7 @@ return array(
State::STATE_ONBOARDED,
),
),
'dcc_gateway_title' => array(
'dcc_gateway_title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
@ -499,7 +497,7 @@ return array(
),
'gateway' => 'dcc',
),
'description' => array(
'description' => array(
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
'type' => 'text',
'desc_tip' => true,
@ -518,7 +516,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'intent' => array(
'intent' => array(
'title' => __( 'Intent', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@ -540,7 +538,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'capture_on_status_change' => array(
'capture_on_status_change' => array(
'title' => __( 'Capture On Status Change', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -557,7 +555,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'capture_for_virtual_only' => array(
'capture_for_virtual_only' => array(
'title' => __( 'Capture Virtual-Only Orders ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -574,7 +572,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'payee_preferred' => array(
'payee_preferred' => array(
'title' => __( 'Instant Payments ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -591,7 +589,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'brand_name' => array(
'brand_name' => array(
'title' => __( 'Brand Name', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => get_bloginfo( 'name' ),
@ -607,7 +605,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'landing_page' => array(
'landing_page' => array(
'title' => __( 'Landing Page', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@ -629,7 +627,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'alternative_payment_methods' => array(
'alternative_payment_methods' => array(
'heading' => __( 'Alternative Payment Methods', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s, %2$s, %3$s and %4$s are a link tags.
@ -645,7 +643,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'disable_funding' => array(
'disable_funding' => array(
'title' => __( 'Disable Alternative Payment Methods', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -669,7 +667,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'card_billing_data_mode' => array(
'card_billing_data_mode' => array(
'title' => __( 'Send checkout billing data to card fields', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@ -689,7 +687,7 @@ return array(
'requirements' => array(),
'gateway' => array( 'paypal', CardButtonGateway::ID ),
),
'allow_card_button_gateway' => array(
'allow_card_button_gateway' => array(
'title' => __( 'Create gateway for Standard Card Button', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -703,7 +701,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'disable_cards' => array(
'disable_cards' => array(
'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -731,7 +729,7 @@ return array(
),
'gateway' => 'dcc',
),
'card_icons' => array(
'card_icons' => array(
'title' => __( 'Show logo of the following credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -761,7 +759,7 @@ return array(
),
'gateway' => 'dcc',
),
'vault_enabled_dcc' => array(
'vault_enabled_dcc' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -780,7 +778,7 @@ return array(
'gateway' => 'dcc',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'3d_secure_heading' => array(
'3d_secure_heading' => array(
'heading' => __( '3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
@ -808,7 +806,7 @@ return array(
),
'gateway' => 'dcc',
),
'3d_secure_contingency' => array(
'3d_secure_contingency' => array(
'title' => __( 'Contingency for 3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'select',
'description' => sprintf(
@ -836,7 +834,7 @@ return array(
),
'gateway' => 'dcc',
),
'paypal_saved_payments' => array(
'paypal_saved_payments' => array(
'heading' => __( 'Saved payments', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s, %2$s, %3$s and %4$s are a link tags.
@ -854,8 +852,8 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'subscriptions_mode' => $container->get( 'wcgateway.settings.fields.subscriptions_mode' ),
'vault_enabled' => array(
'subscriptions_mode' => $container->get( 'wcgateway.settings.fields.subscriptions_mode' ),
'vault_enabled' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -874,26 +872,6 @@ return array(
'gateway' => 'paypal',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'subscription_behavior_when_vault_fails' => array(
'title' => __( 'Subscription capture behavior if Vault fails', 'woocommerce-paypal-payments' ),
'type' => 'select',
'classes' => $subscription_helper->plugin_is_active() ? array() : array( 'hide' ),
'input_class' => array( 'wc-enhanced-select' ),
'default' => 'void_auth',
'desc_tip' => true,
'description' => __( 'By default, subscription payments are captured only when saving the payment method was successful. Without a saved payment method, automatic renewal payments are not possible.', 'woocommerce-paypal-payments' ),
'description_with_tip' => __( 'Determines whether authorized payments for subscription orders are captured or voided if there is no saved payment method. This only applies when the intent Capture is used for the subscription order.', 'woocommerce-paypal-payments' ),
'options' => array(
'void_auth' => __( 'Void authorization & fail the order/subscription', 'woocommerce-paypal-payments' ),
'capture_auth' => __( 'Capture authorized payment & set subscription to Manual Renewal', 'woocommerce-paypal-payments' ),
'capture_auth_ignore' => __( 'Capture authorized payment & disregard missing payment method', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => array( 'paypal' ),
),
);
if ( ! $subscription_helper->plugin_is_active() ) {
@ -953,6 +931,7 @@ return array(
'wcgateway.extra-funding-sources' => static function( ContainerInterface $container ): array {
return array(
'googlepay' => _x( 'Google Pay', 'Name of payment method', 'woocommerce-paypal-payments' ),
'applepay' => _x( 'Apple Pay', 'Name of payment method', 'woocommerce-paypal-payments' ),
);
},
@ -1419,4 +1398,10 @@ return array(
$container->get( 'wcgateway.settings' )
);
},
'wcgateway.display-manager' => SingletonDecorator::make(
static function( ContainerInterface $container ): DisplayManager {
$settings = $container->get( 'wcgateway.settings' );
return new DisplayManager( $settings );
}
),
);

View file

@ -233,6 +233,13 @@ class SettingsPageAssets {
* Register assets for PayPal admin pages.
*/
private function register_admin_assets(): void {
wp_enqueue_style(
'ppcp-admin-common',
trailingslashit( $this->module_url ) . 'assets/css/common.css',
array(),
$this->version
);
wp_enqueue_script(
'ppcp-admin-common',
trailingslashit( $this->module_url ) . 'assets/js/common.js',

View file

@ -299,9 +299,7 @@ class CardButtonGateway extends \WC_Payment_Gateway {
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {

View file

@ -396,9 +396,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {

View file

@ -556,9 +556,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
);
}
if ( $this->subscription_helper->has_subscription( $order_id ) ) {
$this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
}
do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {

View file

@ -33,32 +33,6 @@ trait ProcessPaymentTrait {
return false;
}
/**
* Scheduled the vaulted payment check.
*
* @param int $wc_order_id The WC order ID.
* @param int $customer_id The customer ID.
*/
protected function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void {
$timestamp = 3 * MINUTE_IN_SECONDS;
if (
$this->config->has( 'subscription_behavior_when_vault_fails' )
&& $this->config->get( 'subscription_behavior_when_vault_fails' ) === 'capture_auth'
) {
$timestamp = 0;
}
as_schedule_single_action(
time() + $timestamp,
'woocommerce_paypal_payments_check_saved_payment',
array(
'order_id' => $wc_order_id,
'customer_id' => $customer_id,
'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
)
);
}
/**
* Handles the payment failure.
*

View file

@ -0,0 +1,60 @@
<?php
/**
* Helper to manage the field display behaviour.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* DisplayManager class.
*/
class DisplayManager {
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The rules.
*
* @var array
*/
protected $rules = array();
/**
* FieldDisplayManager constructor.
*
* @param Settings $settings The settings.
* @return void
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
/**
* Creates and returns a rule.
*
* @param string|null $key The rule key.
* @return DisplayRule
*/
public function rule( string $key = null ): DisplayRule {
if ( null === $key ) {
$key = '_rule_' . ( (string) count( $this->rules ) );
}
$rule = new DisplayRule( $key, $this->settings );
$this->rules[ $key ] = $rule;
return $rule;
}
}

View file

@ -0,0 +1,262 @@
<?php
/**
* Element used by field display manager.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* DisplayRule class.
*/
class DisplayRule {
const CONDITION_TYPE_ELEMENT = 'element';
const CONDITION_TYPE_BOOL = 'bool';
const CONDITION_OPERATION_EQUALS = 'equals';
const CONDITION_OPERATION_NOT_EQUALS = 'not_equals';
const CONDITION_OPERATION_IN = 'in';
const CONDITION_OPERATION_NOT_IN = 'not_in';
const CONDITION_OPERATION_EMPTY = 'empty';
const CONDITION_OPERATION_NOT_EMPTY = 'not_empty';
const ACTION_TYPE_ELEMENT = 'element';
const ACTION_VISIBLE = 'visible';
const ACTION_ENABLE = 'enable';
/**
* The element selector.
*
* @var string
*/
protected $key;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The conditions of this rule.
*
* @var array
*/
protected $conditions = array();
/**
* The actions of this rule.
*
* @var array
*/
protected $actions = array();
/**
* Indicates if this class should add selector prefixes.
*
* @var bool
*/
protected $add_selector_prefixes = true;
/**
* FieldDisplayElement constructor.
*
* @param string $key The rule key.
* @param Settings $settings The settings.
*/
public function __construct( string $key, Settings $settings ) {
$this->key = $key;
$this->settings = $settings;
}
/**
* Adds a condition related to an HTML element.
*
* @param string $selector The condition selector.
* @param mixed $value The value to compare against.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return self
*/
public function condition_element( string $selector, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
$this->add_condition(
array(
'type' => self::CONDITION_TYPE_ELEMENT,
'selector' => $selector,
'operation' => $operation,
'value' => $value,
)
);
return $this;
}
/**
* Adds a condition related to a bool check.
*
* @param bool $value The value to enable / disable the condition.
* @return self
*/
public function condition_is_true( bool $value ): self {
$this->add_condition(
array(
'type' => self::CONDITION_TYPE_BOOL,
'value' => $value,
)
);
return $this;
}
/**
* Adds a condition related to the settings.
*
* @param string $settings_key The settings key.
* @param mixed $value The value to compare against.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return self
*/
public function condition_is_settings( string $settings_key, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
$settings_value = null;
if ( $this->settings->has( $settings_key ) ) {
$settings_value = $this->settings->get( $settings_key );
}
$this->condition_is_true( $this->resolve_operation( $settings_value, $value, $operation ) );
return $this;
}
/**
* Adds a condition to show/hide the element.
*
* @param string $selector The condition selector.
*/
public function action_visible( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'selector' => $selector,
'action' => self::ACTION_VISIBLE,
)
);
return $this;
}
/**
* Adds a condition to enable/disable the element.
*
* @param string $selector The condition selector.
*/
public function action_enable( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'selector' => $selector,
'action' => self::ACTION_ENABLE,
)
);
return $this;
}
/**
* Adds a condition to the rule.
*
* @param array $options The condition options.
* @return void
*/
private function add_condition( array $options ): void {
if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
$options['selector'] = '#ppcp-' . $options['selector']; // Refers to the input.
}
if ( ! isset( $options['key'] ) ) {
$options['key'] = '_condition_' . ( (string) count( $this->conditions ) );
}
$this->conditions[] = $options;
}
/**
* Adds an action to do.
*
* @param array $options The action options.
* @return void
*/
private function add_action( array $options ): void {
if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
$options['selector'] = '#field-' . $options['selector']; // Refers to the whole field.
}
if ( ! isset( $options['key'] ) ) {
$options['key'] = '_action_' . ( (string) count( $this->actions ) );
}
$this->actions[] = $options;
}
/**
* Set if selector prefixes like, "#ppcp-" or "#field-" should be added to condition or action selectors.
*
* @param bool $add_selector_prefixes If should add prefixes.
* @return self
*/
public function should_add_selector_prefixes( bool $add_selector_prefixes = true ): self {
$this->add_selector_prefixes = $add_selector_prefixes;
return $this;
}
/**
* Adds a condition related to the settings.
*
* @param mixed $value_1 The value 1.
* @param mixed $value_2 The value 2.
* @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
* @return bool
*/
private function resolve_operation( $value_1, $value_2, string $operation ): bool {
switch ( $operation ) {
case self::CONDITION_OPERATION_EQUALS:
return $value_1 === $value_2;
case self::CONDITION_OPERATION_NOT_EQUALS:
return $value_1 !== $value_2;
case self::CONDITION_OPERATION_IN:
return in_array( $value_1, $value_2, true );
case self::CONDITION_OPERATION_NOT_IN:
return ! in_array( $value_1, $value_2, true );
case self::CONDITION_OPERATION_EMPTY:
return empty( $value_1 );
case self::CONDITION_OPERATION_NOT_EMPTY:
return ! empty( $value_1 );
}
return false;
}
/**
* Returns array representation.
*
* @return array
*/
public function to_array(): array {
return array(
'key' => $this->key,
'conditions' => $this->conditions,
'actions' => $this->actions,
);
}
/**
* Returns JSON representation.
*
* @return string
*/
public function json(): string {
return wp_json_encode( $this->to_array() ) ?: '';
}
}

View file

@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
return function ( ContainerInterface $container, array $fields ): array {
@ -39,6 +40,9 @@ return function ( ContainerInterface $container, array $fields ): array {
$module_url = $container->get( 'wcgateway.url' );
$display_manager = $container->get( 'wcgateway.display-manager' );
assert( $display_manager instanceof DisplayManager );
$connection_fields = array(
'ppcp_onboarading_header' => array(
'type' => 'ppcp-text',
@ -238,7 +242,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'merchant_email_production' => array(
'title' => __( 'Live Email address', 'woocommerce-paypal-payments' ),
'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
'type' => 'text',
'type' => 'email',
'required' => true,
'desc_tip' => true,
'description' => __( 'The email address of your PayPal account.', 'woocommerce-paypal-payments' ),
@ -304,7 +308,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'merchant_email_sandbox' => array(
'title' => __( 'Sandbox Email address', 'woocommerce-paypal-payments' ),
'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
'type' => 'text',
'type' => 'email',
'required' => true,
'desc_tip' => true,
'description' => __( 'The email address of your PayPal account.', 'woocommerce-paypal-payments' ),
@ -504,15 +508,13 @@ return function ( ContainerInterface $container, array $fields ): array {
'requirements' => array(),
'gateway' => Settings::CONNECTION_TAB_ID,
'custom_attributes' => array(
'data-ppcp-handlers' => wp_json_encode(
'data-ppcp-display' => wp_json_encode(
array(
array(
'handler' => 'SubElementsHandler',
'options' => array(
'values' => array( PurchaseUnitSanitizer::MODE_EXTRA_LINE ),
'elements' => array( '#field-subtotal_mismatch_line_name' ),
),
),
$display_manager
->rule()
->condition_element( 'subtotal_mismatch_behavior', PurchaseUnitSanitizer::MODE_EXTRA_LINE )
->action_visible( 'subtotal_mismatch_line_name' )
->to_array(),
)
),
),

View file

@ -568,6 +568,7 @@ class SettingsListener {
break;
case 'text':
case 'number':
case 'email':
$settings[ $key ] = isset( $raw_data[ $key ] ) ? wp_kses_post( $raw_data[ $key ] ) : '';
break;
case 'ppcp-password':

View file

@ -11,6 +11,7 @@ module.exports = {
'fraudnet': path.resolve('./resources/js/fraudnet.js'),
'oxxo': path.resolve('./resources/js/oxxo.js'),
'gateway-settings-style': path.resolve('./resources/css/gateway-settings.scss'),
'common-style': path.resolve('./resources/css/common.scss'),
},
output: {
path: path.resolve(__dirname, 'assets/'),