Merge pull request #2155 from woocommerce/PCP-2347-new-feature-accelerated-checkout

New feature: Accelerated Checkout (2347)
This commit is contained in:
Emili Castells 2024-04-18 16:04:39 +02:00 committed by GitHub
commit a3bbcfeec5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 5702 additions and 58 deletions

View file

@ -1,5 +1,8 @@
@use "../../../ppcp-button/resources/css/mixins/apm-button" as apm-button;
$border-color: #c3c3c3;
$background-ident-color: #fbfbfb;
.ppcp-field-hidden {
display: none !important;
}
@ -11,9 +14,10 @@
opacity: 0.5;
}
.ppcp-field-indent {
th {
padding-left: 20px;
.ppcp-active-spacer {
th, td {
padding: 0;
height: 1rem;
}
}
@ -39,3 +43,51 @@
font-weight: bold;
}
}
.ppcp-align-label-center {
th {
text-align: center;
}
}
.ppcp-valign-label-middle {
th {
vertical-align: middle;
}
}
// Box indented fields.
@media screen and (min-width: 800px) {
.ppcp-settings-field {
border-left: 1px solid transparent;
border-right: 1px solid transparent;
&.active {
background-color: $background-ident-color;
border: 1px solid $border-color;
th {
padding-left: 20px;
}
}
&.ppcp-field-indent {
background-color: $background-ident-color;
border: 1px solid $border-color;
th, &.ppcp-settings-field-heading td {
padding-left: 40px;
}
th, td {
border-top: 1px solid $border-color;
}
& + .ppcp-field-indent {
th, td {
border-top: 1px solid $background-ident-color;
}
}
}
}
}

View file

@ -1,10 +1,13 @@
import ElementAction from "./action/ElementAction";
import VisibilityAction from "./action/VisibilityAction";
import AttributeAction from "./action/AttributeAction";
class ActionFactory {
static make(actionConfig) {
switch (actionConfig.type) {
case 'element':
return new ElementAction(actionConfig);
case 'visibility':
return new VisibilityAction(actionConfig);
case 'attribute':
return new AttributeAction(actionConfig);
}
throw new Error('[ActionFactory] Unknown action: ' + actionConfig.type);

View file

@ -1,5 +1,6 @@
import ElementCondition from "./condition/ElementCondition";
import BoolCondition from "./condition/BoolCondition";
import JsVariableCondition from "./condition/JsVariableCondition";
class ConditionFactory {
static make(conditionConfig, triggerUpdate) {
@ -8,6 +9,8 @@ class ConditionFactory {
return new ElementCondition(conditionConfig, triggerUpdate);
case 'bool':
return new BoolCondition(conditionConfig, triggerUpdate);
case 'js_variable':
return new JsVariableCondition(conditionConfig, triggerUpdate);
}
throw new Error('[ConditionFactory] Unknown condition: ' + conditionConfig.type);

View file

@ -0,0 +1,17 @@
import BaseAction from "./BaseAction";
class AttributeAction extends BaseAction {
run(status) {
if (status) {
jQuery(this.config.selector).addClass(this.config.html_class);
} else {
jQuery(this.config.selector).removeClass(this.config.html_class);
}
}
}
export default AttributeAction;

View file

@ -1,6 +1,6 @@
import BaseAction from "./BaseAction";
class ElementAction extends BaseAction {
class VisibilityAction extends BaseAction {
run(status) {
@ -32,4 +32,4 @@ class ElementAction extends BaseAction {
}
export default ElementAction;
export default VisibilityAction;

View file

@ -0,0 +1,24 @@
import BaseCondition from "./BaseCondition";
class JsVariableCondition extends BaseCondition {
register() {
jQuery(document).on('ppcp-display-change', () => {
const status = this.check();
if (status !== this.status) {
this.status = status;
this.triggerUpdate();
}
});
this.status = this.check();
}
check() {
let value = document[this.config.variable];
return this.config.value === value;
}
}
export default JsVariableCondition;

View file

@ -17,6 +17,7 @@ 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\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
@ -809,25 +810,6 @@ return array(
),
'gateway' => 'dcc',
),
'vault_enabled_dcc' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
'label' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__( 'Securely store your customers credit cards for a seamless checkout experience and subscription features. Payment methods are saved in the secure %1$sPayPal Vault%2$s.', 'woocommerce-paypal-payments' ),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#vaulting-saving-a-payment-method" target="_blank">',
'</a>'
),
'description' => __( 'Allow registered buyers to save Credit Card payments.', 'woocommerce-paypal-payments' ),
'default' => false,
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'dcc',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'3d_secure_heading' => array(
'heading' => __( '3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
@ -884,6 +866,52 @@ return array(
),
'gateway' => 'dcc',
),
'saved_payments_heading' => array(
'heading' => __( 'Saved Payments', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
sprintf(
// translators: %1$s and %2$s is a link tag.
__(
'PayPal can securely store your customers payment methods for
%1$sfuture payments and subscriptions%2$s, simplifying the checkout
process and enabling recurring transactions on your website.',
'woocommerce-paypal-payments'
),
'<a
rel="noreferrer noopener"
href="https://woo.com/document/woocommerce-paypal-payments/#vaulting-a-card"
>',
'</a>'
)
),
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(
'dcc',
),
'gateway' => 'dcc',
),
'vault_enabled_dcc' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
'label' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__( 'Securely store your customers credit cards for a seamless checkout experience and subscription features. Payment methods are saved in the secure %1$sPayPal Vault%2$s.', 'woocommerce-paypal-payments' ),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#vaulting-saving-a-payment-method" target="_blank">',
'</a>'
),
'description' => __( 'Allow registered buyers to save Credit Card payments.', 'woocommerce-paypal-payments' ),
'default' => false,
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'dcc',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'paypal_saved_payments' => array(
'heading' => __( 'Saved payments', 'woocommerce-paypal-payments' ),
'description' => sprintf(
@ -922,6 +950,32 @@ return array(
'gateway' => 'paypal',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'digital_wallet_heading' => array(
'heading' => __( 'Digital Wallet Services', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
sprintf(
// translators: %1$s and %2$s is a link tag.
__(
'PayPal supports digital wallet services like Apple Pay or Google Pay
to give your buyers more options to pay without a PayPal account.',
'woocommerce-paypal-payments'
),
'<a
rel="noreferrer noopener"
href="https://woo.com/document/woocommerce-paypal-payments/#vaulting-a-card"
>',
'</a>'
)
),
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(
'dcc',
),
'gateway' => 'dcc',
),
);
if ( ! $subscription_helper->plugin_is_active() ) {
@ -1509,6 +1563,7 @@ return array(
PayUponInvoiceGateway::ID,
CardButtonGateway::ID,
OXXOGateway::ID,
AxoGateway::ID,
);
},
'wcgateway.gateway-repository' => static function ( ContainerInterface $container ): GatewayRepository {

View file

@ -16,8 +16,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/
class DisplayRule {
const CONDITION_TYPE_ELEMENT = 'element';
const CONDITION_TYPE_BOOL = 'bool';
const CONDITION_TYPE_ELEMENT = 'element';
const CONDITION_TYPE_BOOL = 'bool';
const CONDITION_TYPE_JS_VARIABLE = 'js_variable';
const CONDITION_OPERATION_EQUALS = 'equals';
const CONDITION_OPERATION_NOT_EQUALS = 'not_equals';
@ -26,10 +27,12 @@ class DisplayRule {
const CONDITION_OPERATION_EMPTY = 'empty';
const CONDITION_OPERATION_NOT_EMPTY = 'not_empty';
const ACTION_TYPE_ELEMENT = 'element';
const ACTION_TYPE_VISIBILITY = 'visibility';
const ACTION_TYPE_ATTRIBUTE = 'attribute';
const ACTION_VISIBLE = 'visible';
const ACTION_ENABLE = 'enable';
const ACTION_CLASS = 'class';
/**
* The element selector.
@ -132,6 +135,24 @@ class DisplayRule {
return $this;
}
/**
* Adds a condition related to js variable check.
*
* @param string $variable_name The javascript variable name.
* @param mixed $value The value to enable / disable the condition.
* @return self
*/
public function condition_js_variable( string $variable_name, $value ): self {
$this->add_condition(
array(
'type' => self::CONDITION_TYPE_JS_VARIABLE,
'variable' => $variable_name,
'value' => $value,
)
);
return $this;
}
/**
* Adds a condition to show/hide the element.
*
@ -140,7 +161,7 @@ class DisplayRule {
public function action_visible( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'type' => self::ACTION_TYPE_VISIBILITY,
'selector' => $selector,
'action' => self::ACTION_VISIBLE,
)
@ -148,6 +169,24 @@ class DisplayRule {
return $this;
}
/**
* Adds a condition to add/remove a html class.
*
* @param string $selector The condition selector.
* @param string $class The class.
*/
public function action_class( string $selector, string $class ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ATTRIBUTE,
'selector' => $selector,
'html_class' => $class,
'action' => self::ACTION_CLASS,
)
);
return $this;
}
/**
* Adds a condition to enable/disable the element.
*
@ -156,7 +195,7 @@ class DisplayRule {
public function action_enable( string $selector ): self {
$this->add_action(
array(
'type' => self::ACTION_TYPE_ELEMENT,
'type' => self::ACTION_TYPE_VISIBILITY,
'selector' => $selector,
'action' => self::ACTION_ENABLE,
)

View file

@ -270,6 +270,40 @@ class OrderProcessor {
do_action( 'woocommerce_paypal_payments_after_order_processor', $wc_order, $order );
}
/**
* Processes a given WooCommerce order and captured/authorizes the connected PayPal orders.
*
* @param WC_Order $wc_order The WooCommerce order.
* @param Order $order The PayPal order.
*
* @throws Exception If processing fails.
*/
public function process_captured_and_authorized( WC_Order $wc_order, Order $order ): void {
$this->add_paypal_meta( $wc_order, $order, $this->environment );
if ( $order->intent() === 'AUTHORIZE' ) {
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' );
if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) ) {
$wc_order->update_meta_data( '_ppcp_captured_vault_webhook', 'false' );
}
}
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
}
$this->handle_new_order_status( $order, $wc_order );
if ( $this->capture_authorized_downloads( $order ) ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
do_action( 'woocommerce_paypal_payments_after_order_processor', $wc_order, $order );
}
/**
* Creates a PayPal order for the given WC order.
*

View file

@ -538,6 +538,7 @@ return function ( ContainerInterface $container, array $fields ): array {
->rule()
->condition_element( 'subtotal_mismatch_behavior', PurchaseUnitSanitizer::MODE_EXTRA_LINE )
->action_visible( 'subtotal_mismatch_line_name' )
->action_class( 'subtotal_mismatch_behavior', 'active' )
->to_array(),
)
),
@ -548,6 +549,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'type' => 'text',
'desc_tip' => true,
'description' => __( 'The name of the extra line that will be sent to PayPal to correct the subtotal mismatch.', 'woocommerce-paypal-payments' ),
'classes' => array( 'ppcp-field-indent' ),
'maxlength' => 22,
'default' => '',
'screens' => array(

View file

@ -262,7 +262,7 @@ class SettingsRenderer {
$html = sprintf(
'<h3 class="wc-settings-sub-title %s">%s</h3>',
esc_attr( implode( ' ', $config['class'] ) ),
esc_html( $config['heading'] )
isset( $config['heading_html'] ) ? $config['heading_html'] : esc_html( $config['heading'] )
);
return $html;
@ -388,7 +388,12 @@ $data_rows_html
<th scope="row">
<label
for="<?php echo esc_attr( $id ); ?>"
><?php echo esc_html( $config['title'] ); ?></label>
>
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo isset( $config['title_html'] ) ? $config['title_html'] : esc_html( $config['title'] );
?>
</label>
<?php if ( isset( $config['desc_tip'] ) && $config['desc_tip'] ) : ?>
<span
class="woocommerce-help-tip"