Merge branch 'trunk' into modularity-module-migration

# Conflicts:
#	modules/ppcp-wc-gateway/src/WCGatewayModule.php
This commit is contained in:
Pedro Silva 2024-04-19 11:59:28 +01:00
commit 161e933d39
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
96 changed files with 6599 additions and 223 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;
@ -103,7 +104,10 @@ return array(
$api_shop_country,
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.paypal-checkout-url' ),
$container->get( 'wcgateway.place-order-button-text' )
$container->get( 'wcgateway.place-order-button-text' ),
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'vaulting.wc-payment-tokens' )
);
},
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
@ -806,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',
@ -881,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(
@ -919,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() ) {
@ -962,10 +1019,10 @@ return array(
'ideal' => _x( 'iDEAL', 'Name of payment method', 'woocommerce-paypal-payments' ),
'mybank' => _x( 'MyBank', 'Name of payment method', 'woocommerce-paypal-payments' ),
'p24' => _x( 'Przelewy24', 'Name of payment method', 'woocommerce-paypal-payments' ),
'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', 'Name of payment method', 'woocommerce-paypal-payments' ),
'trustly' => _x( 'Trustly', 'Name of payment method', 'woocommerce-paypal-payments' ),
'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
'paylater' => _x( 'PayPal Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
'paypal' => _x( 'PayPal', 'Name of payment method', 'woocommerce-paypal-payments' ),
);
},
@ -988,6 +1045,7 @@ return array(
array_flip(
array(
'paylater',
'paypal',
)
)
);
@ -1505,6 +1563,7 @@ return array(
PayUponInvoiceGateway::ID,
CardButtonGateway::ID,
OXXOGateway::ID,
AxoGateway::ID,
);
},
'wcgateway.gateway-repository' => static function ( ContainerInterface $container ): GatewayRepository {

View file

@ -34,7 +34,7 @@ class FundingSourceRenderer {
*
* @var string[]
*/
protected $own_funding_sources = array( 'venmo', 'paylater' );
protected $own_funding_sources = array( 'venmo', 'paylater', 'paypal' );
/**
* FundingSourceRenderer constructor.
@ -63,7 +63,7 @@ class FundingSourceRenderer {
return $this->funding_sources[ $id ];
}
return sprintf(
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
/* translators: %s - BLIK, iDeal, Mercado Pago, etc. */
__( '%s (via PayPal)', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);
@ -84,7 +84,7 @@ class FundingSourceRenderer {
if ( array_key_exists( $id, $this->funding_sources ) ) {
return sprintf(
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
/* translators: %s - BLIK, iDeal, Mercado Pago, etc. */
__( 'Pay via %s.', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);

View file

@ -33,6 +33,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
@ -40,7 +41,7 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
*/
class CreditCardGateway extends \WC_Payment_Gateway_CC {
use ProcessPaymentTrait, GatewaySettingsRendererTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait;
use ProcessPaymentTrait, GatewaySettingsRendererTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait, FreeTrialHandlerTrait;
const ID = 'ppcp-credit-card-gateway';
@ -454,6 +455,17 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) );
if ( $this->is_free_trial_order( $wc_order ) && $card_payment_token_id ) {
$customer_tokens = $this->wc_payment_tokens->customer_tokens( get_current_user_id() );
foreach ( $customer_tokens as $token ) {
if ( $token['payment_source']->name() === 'card' ) {
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
}
}
if ( $card_payment_token_id ) {
$customer_tokens = $this->wc_payment_tokens->customer_tokens( get_current_user_id() );

View file

@ -14,12 +14,14 @@ use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
@ -48,12 +50,18 @@ class PayPalGateway extends \WC_Payment_Gateway {
const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
const ORDER_PAYER_EMAIL_META_KEY = '_ppcp_paypal_payer_email';
const FEES_META_KEY = '_ppcp_paypal_fees';
const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
const THREE_D_AUTH_RESULT_META_KEY = '_ppcp_paypal_3DS_auth_result';
const FRAUD_RESULT_META_KEY = '_ppcp_paypal_fraud_result';
/**
* List of payment sources wich we are expected to store the payer email in the WC Order metadata.
*/
const PAYMENT_SOURCES_WITH_PAYER_EMAIL = array( 'paypal', 'paylater', 'venmo' );
/**
* The Settings Renderer.
*
@ -173,26 +181,50 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
private $paypal_checkout_url_factory;
/**
* Payment tokens endpoint.
*
* @var PaymentTokensEndpoint
*/
private $payment_tokens_endpoint;
/**
* Whether Vault v3 module is enabled.
*
* @var bool
*/
private $vault_v3_enabled;
/**
* WooCommerce payment tokens.
*
* @var WooCommercePaymentTokens
*/
private $wc_payment_tokens;
/**
* PayPalGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
* @param string $api_shop_country The api shop country.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID.
* @param string $place_order_button_text The text for the standard "Place order" button.
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
* @param string $api_shop_country The api shop country.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID.
* @param string $place_order_button_text The text for the standard "Place order" button.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@ -211,7 +243,10 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $api_shop_country,
OrderEndpoint $order_endpoint,
callable $paypal_checkout_url_factory,
string $place_order_button_text
string $place_order_button_text,
PaymentTokensEndpoint $payment_tokens_endpoint,
bool $vault_v3_enabled,
WooCommercePaymentTokens $wc_payment_tokens
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
@ -231,6 +266,10 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->api_shop_country = $api_shop_country;
$this->paypal_checkout_url_factory = $paypal_checkout_url_factory;
$this->order_button_text = $place_order_button_text;
$this->order_endpoint = $order_endpoint;
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->vault_v3_enabled = $vault_v3_enabled;
$this->wc_payment_tokens = $wc_payment_tokens;
if ( $this->onboarded ) {
$this->supports = array( 'refunds', 'tokenization' );
@ -259,6 +298,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
'subscription_payment_method_change_admin',
'multiple_subscriptions'
);
} elseif ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' ) {
$this->supports[] = 'gateway_scheduled_payments';
} elseif ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) {
$this->supports[] = 'tokenization';
}
@ -293,8 +334,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
'process_admin_options',
)
);
$this->order_endpoint = $order_endpoint;
}
/**
@ -494,7 +533,49 @@ class PayPalGateway extends \WC_Payment_Gateway {
$wc_order->save();
}
if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) && ! $this->subscription_helper->paypal_subscription_id() ) {
if (
'card' !== $funding_source
&& $this->is_free_trial_order( $wc_order )
&& ! $this->subscription_helper->paypal_subscription_id()
) {
$ppcp_guest_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null;
if ( $this->vault_v3_enabled && $ppcp_guest_payment_for_free_trial ) {
$customer_id = $ppcp_guest_payment_for_free_trial->customer->id ?? '';
if ( $customer_id ) {
update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
}
if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal ) ) {
$email = '';
if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address ) ) {
$email = $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address;
}
$this->wc_payment_tokens->create_payment_token_paypal(
$wc_order->get_customer_id(),
$ppcp_guest_payment_for_free_trial->id,
$email
);
}
WC()->session->set( 'ppcp_guest_payment_for_free_trial', null );
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
$customer_id = get_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', true );
if ( $customer_id ) {
$customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id );
foreach ( $customer_tokens as $token ) {
$payment_source_name = $token['payment_source']->name() ?? '';
if ( $payment_source_name === 'paypal' || $payment_source_name === 'venmo' ) {
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}
}
}
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
if ( ! array_filter(
@ -507,7 +588,6 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
$wc_order->payment_complete();
return $this->handle_payment_success( $wc_order );
}

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

@ -76,7 +76,7 @@ trait CreditCardOrderInfoHandlingTrait {
/**
* Fired when the 3DS information is added to WC order.
*/
do_action( 'woocommerce_paypal_payments_thee_d_secure_added', $wc_order, $order );
do_action( 'woocommerce_paypal_payments_three_d_secure_added', $wc_order, $order );
}
}
@ -96,8 +96,9 @@ trait CreditCardOrderInfoHandlingTrait {
return;
}
$fraud_responses = $fraud->to_array();
$card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
$fraud_responses = $fraud->to_array();
$card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
$card_last_digits = $payment_source->properties()->last_digits ?? __( 'N/A', 'woocommerce-paypal-payments' );
$avs_response_order_note_title = __( 'Address Verification Result', 'woocommerce-paypal-payments' );
/* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
@ -109,6 +110,7 @@ trait CreditCardOrderInfoHandlingTrait {
<li>%3$s</li>
</ul>
<li>%4$s</li>
<li>%5$s</li>
</ul>';
$avs_response_order_note_result = sprintf(
$avs_response_order_note_result_format,
@ -119,7 +121,9 @@ trait CreditCardOrderInfoHandlingTrait {
/* translators: %s is fraud AVS postal match */
sprintf( __( 'Postal Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['postal_match'] ) ),
/* translators: %s is card brand */
sprintf( __( 'Card Brand: %s', 'woocommerce-paypal-payments' ), esc_html( $card_brand ) )
sprintf( __( 'Card Brand: %s', 'woocommerce-paypal-payments' ), esc_html( $card_brand ) ),
/* translators: %s card last digits */
sprintf( __( 'Card Last Digits: %s', 'woocommerce-paypal-payments' ), esc_html( $card_last_digits ) )
);
$avs_response_order_note = sprintf(
$avs_response_order_note_format,
@ -136,7 +140,13 @@ trait CreditCardOrderInfoHandlingTrait {
);
$wc_order->add_order_note( $cvv_response_order_note );
$meta_details = array_merge( $fraud_responses, array( 'card_brand' => $card_brand ) );
$meta_details = array_merge(
$fraud_responses,
array(
'card_brand' => $card_brand,
'card_last_digits' => $card_last_digits,
)
);
$wc_order->update_meta_data( PayPalGateway::FRAUD_RESULT_META_KEY, $meta_details );
$wc_order->save_meta_data();

View file

@ -45,6 +45,18 @@ trait OrderMetaTrait {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY, $payment_source );
}
$payer = $order->payer();
if (
$payer
&& $payment_source
&& in_array( $payment_source, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true )
) {
$payer_email = $payer->email_address();
if ( $payer_email ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
}
}
$wc_order->save();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );

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

@ -65,7 +65,7 @@ return function ( ContainerInterface $container, array $fields ): array {
<a href="https://woo.com/document/woocommerce-paypal-payments/#paypal-card-processing-acdc" target="_blank"><img alt="American Express" src="' . esc_url( $module_url ) . 'assets/images/amex.svg"/></a>
<a href="https://woo.com/document/woocommerce-paypal-payments/#paypal-card-processing-acdc" target="_blank"><img alt="Discover" src="' . esc_url( $module_url ) . 'assets/images/discover.svg"/></a>
<a href="https://woo.com/document/woocommerce-paypal-payments/#alternative-payment-methods" target="_blank"><img alt="iDEAL" src="' . esc_url( $module_url ) . 'assets/images/ideal-dark.svg"/></a>
<a href="https://woo.com/document/woocommerce-paypal-payments/#alternative-payment-methods" target="_blank"><img alt="Sofort" src="' . esc_url( $module_url ) . 'assets/images/sofort.svg"/></a>
<a href="https://woo.com/document/woocommerce-paypal-payments/#alternative-payment-methods" target="_blank"><img alt="BLIK" src="' . esc_url( $module_url ) . 'assets/images/blik.svg"/></a>
</div>
<div class="ppcp-onboarding-header-apm-logos">
<a href="https://woo.com/document/woocommerce-paypal-payments/#apple-pay" target="_blank"><img alt="Apple Pay" src="' . esc_url( $module_url ) . 'assets/images/button-Apple-Pay.png"/></a>
@ -436,7 +436,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'desc_tip' => true,
'label' => $container->get( 'wcgateway.settings.fraudnet-label' ),
'description' => __( 'FraudNet is a JavaScript library developed by PayPal and embedded into a merchants web page to collect browser-based data to help reduce fraud.', 'woocommerce-paypal-payments' ),
'default' => false,
'default' => true,
'screens' => array(
State::STATE_ONBOARDED,
),
@ -522,8 +522,8 @@ return function ( ContainerInterface $container, array $fields ): array {
'woocommerce-paypal-payments'
),
'options' => array(
PurchaseUnitSanitizer::MODE_DITCH => __( 'Do not send line items to PayPal', 'woocommerce-paypal-payments' ),
PurchaseUnitSanitizer::MODE_EXTRA_LINE => __( 'Add another line item', 'woocommerce-paypal-payments' ),
PurchaseUnitSanitizer::MODE_DITCH => __( 'Do not send line items to PayPal', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@ -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

@ -91,7 +91,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'title' => __( 'Customize Smart Buttons Per Location', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'Customize smart button style per location', 'woocommerce-paypal-payments' ),
'default' => true,
'default' => false,
'screens' => array( State::STATE_START, State::STATE_ONBOARDED ),
'requirements' => array(),
'gateway' => 'paypal',

View file

@ -142,7 +142,7 @@ class Settings implements ContainerInterface {
'woocommerce-paypal-payments'
),
'smart_button_locations' => $this->default_button_locations,
'smart_button_enable_styling_per_location' => true,
'smart_button_enable_styling_per_location' => false,
'pay_later_messaging_enabled' => true,
'pay_later_button_enabled' => true,
'pay_later_button_locations' => $this->default_pay_later_button_locations,

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"

View file

@ -456,6 +456,53 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
}
);
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
add_filter(
'woocommerce_admin_billing_fields',
function ( $fields ) {
global $theorder;
if ( ! apply_filters( 'woocommerce_paypal_payments_order_details_show_paypal_email', true ) ) {
return $fields;
}
if ( ! is_array( $fields ) ) {
return $fields;
}
if ( ! $theorder instanceof WC_Order ) {
return $fields;
}
$email = $theorder->get_meta( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY ) ?: '';
if ( ! $email ) {
return $fields;
}
// Is payment source is paypal exclude all non paypal funding sources.
$payment_source = $theorder->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ?: '';
$is_paypal_funding_source = ( strpos( $theorder->get_payment_method_title(), '(via PayPal)' ) === false );
if ( $payment_source === 'paypal' && ! $is_paypal_funding_source ) {
return $fields;
}
$fields['paypal_email'] = array(
'label' => __( 'PayPal email address', 'woocommerce-paypal-payments' ),
'value' => $email,
'wrapper_class' => 'form-field-wide',
'custom_attributes' => array( 'disabled' => 'disabled' ),
);
return $fields;
}
);
return true;
}