module_url = $module_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->payer_factory = $payer_factory;
$this->client_id = $client_id;
$this->request_data = $request_data;
$this->dcc_applies = $dcc_applies;
$this->subscription_helper = $subscription_helper;
$this->messages_apply = $messages_apply;
$this->environment = $environment;
$this->payment_token_repository = $payment_token_repository;
$this->settings_status = $settings_status;
$this->currency = $currency;
$this->all_funding_sources = $all_funding_sources;
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
$this->early_validation_enabled = $early_validation_enabled;
$this->pay_now_contexts = $pay_now_contexts;
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
$this->logger = $logger;
}
/**
* Registers the necessary action hooks to render the HTML depending on the settings.
*
* @return bool
*/
public function render_wrapper(): bool {
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
$this->render_button_wrapper_registrar();
$this->render_message_wrapper_registrar();
}
if (
$this->settings->has( 'dcc_enabled' )
&& $this->settings->get( 'dcc_enabled' )
) {
add_action(
$this->checkout_dcc_button_renderer_hook(),
array(
$this,
'dcc_renderer',
),
11
);
add_action(
$this->pay_order_renderer_hook(),
array(
$this,
'dcc_renderer',
),
11
);
$subscription_helper = $this->subscription_helper;
add_filter(
'woocommerce_credit_card_form_fields',
function ( array $default_fields, $id ) use ( $subscription_helper ) : array {
if ( is_user_logged_in() && $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ) && CreditCardGateway::ID === $id ) {
$default_fields['card-vault'] = sprintf(
'
',
esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' )
);
if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->order_pay_contains_subscription() ) {
$default_fields['card-vault'] = '';
}
$tokens = $this->payment_token_repository->all_for_user_id( get_current_user_id() );
if ( $tokens && $this->payment_token_repository->tokens_contains_card( $tokens ) ) {
$output = sprintf(
'';
$default_fields['saved-credit-card'] = $output;
}
}
return $default_fields;
},
10,
2
);
}
if ( $this->is_free_trial_cart() ) {
add_action(
'woocommerce_review_order_after_submit',
function () {
$vaulted_email = $this->get_vaulted_paypal_email();
if ( ! $vaulted_email ) {
return;
}
?>
',
''
)
);
?>
sanitize_woocommerce_filters();
return true;
}
/**
* Registers the hooks to render the credit messaging HTML depending on the settings.
*
* @return bool
* @throws NotFoundException When a setting was not found.
*/
private function render_message_wrapper_registrar(): bool {
if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) {
return false;
}
$selected_locations = $this->settings->has( 'pay_later_messaging_locations' ) ? $this->settings->get( 'pay_later_messaging_locations' ) : array();
$not_enabled_on_cart = ! in_array( 'cart', $selected_locations, true );
add_action(
$this->proceed_to_checkout_button_renderer_hook(),
function() use ( $not_enabled_on_cart ) {
if ( ! is_cart() || $not_enabled_on_cart ) {
return;
}
$this->message_renderer();
},
19
);
$not_enabled_on_product_page = ! in_array( 'product', $selected_locations, true );
if (
( is_product() || wc_post_content_has_shortcode( 'product_page' ) )
&& ! $not_enabled_on_product_page
&& ! is_checkout()
) {
add_action(
$this->single_product_renderer_hook(),
array( $this, 'message_renderer' ),
30
);
}
$not_enabled_on_checkout = ! in_array( 'checkout', $selected_locations, true );
if ( ! $not_enabled_on_checkout ) {
add_action(
$this->checkout_dcc_button_renderer_hook(),
array( $this, 'message_renderer' ),
11
);
add_action(
$this->pay_order_renderer_hook(),
array( $this, 'message_renderer' ),
15
);
}
return true;
}
/**
* Registers the hooks where to render the button HTML according to the settings.
*
* @return bool
* @throws NotFoundException When a setting was not found.
*/
private function render_button_wrapper_registrar(): bool {
if (
( is_product() || wc_post_content_has_shortcode( 'product_page' ) )
&& $this->settings_status->is_smart_button_enabled_for_location( 'product' )
// TODO: it seems like there is no easy way to properly handle vaulted PayPal free trial,
// so disable the buttons for now everywhere except checkout for free trial.
&& ! $this->is_free_trial_product()
&& ! is_checkout()
) {
add_action(
$this->single_product_renderer_hook(),
function () {
$product = wc_get_product();
if (
is_a( $product, WC_Product::class )
&& ! $this->product_supports_payment( $product )
) {
return;
}
$this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_single_product_button_render' );
},
31
);
}
if (
$this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' )
&& ! $this->is_free_trial_cart()
) {
add_action(
$this->mini_cart_button_renderer_hook(),
function () {
if ( $this->is_cart_price_total_zero() || $this->is_free_trial_cart() ) {
return;
}
echo '';
echo '';
do_action( 'woocommerce_paypal_payments_minicart_button_render' );
echo '';
},
30
);
}
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
if ( isset( $available_gateways['ppcp-gateway'] ) ) {
add_action(
$this->pay_order_renderer_hook(),
function (): void {
$this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_payorder_button_render' );
$this->button_renderer( CardButtonGateway::ID );
},
20
);
add_action(
$this->checkout_button_renderer_hook(),
function (): void {
$this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_checkout_button_render' );
$this->button_renderer( CardButtonGateway::ID );
}
);
$enabled_on_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' );
add_action(
$this->proceed_to_checkout_button_renderer_hook(),
function() use ( $enabled_on_cart ) {
if ( ! is_cart() || ! $enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() ) {
return;
}
$this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_cart_button_render' );
},
20
);
}
return true;
}
/**
* Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
*/
public function should_load_ppcp_script(): bool {
$buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
if ( ! $buttons_enabled ) {
return false;
}
if ( in_array( $this->context(), array( 'checkout-block', 'cart-block' ), true ) ) {
return false;
}
return $this->should_load_buttons() || $this->can_render_dcc();
}
/**
* Determines whether the button component should be loaded.
*/
public function should_load_buttons() : bool {
$buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
if ( ! $buttons_enabled ) {
return false;
}
$smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() );
$smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
$messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() );
switch ( $this->context() ) {
case 'checkout':
case 'cart':
case 'pay-now':
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location;
case 'checkout-block':
case 'cart-block':
return $smart_button_enabled_for_current_location;
case 'product':
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
default:
return $smart_button_enabled_for_mini_cart;
}
}
/**
* Whether DCC fields can be rendered.
*/
public function can_render_dcc() : bool {
return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' )
&& $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' )
&& $this->dcc_applies->for_country_currency()
&& in_array( $this->context(), array( 'checkout', 'pay-now' ), true );
}
/**
* Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
*/
public function enqueue(): void {
if ( $this->can_render_dcc() ) {
wp_enqueue_style(
'ppcp-hosted-fields',
untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css',
array(),
$this->version
);
}
wp_enqueue_style(
'gateway',
untrailingslashit( $this->module_url ) . '/assets/css/gateway.css',
array(),
$this->version
);
wp_enqueue_script(
'ppcp-smart-button',
untrailingslashit( $this->module_url ) . '/assets/js/button.js',
array( 'jquery' ),
$this->version,
true
);
wp_localize_script(
'ppcp-smart-button',
'PayPalCommerceGateway',
$this->script_data()
);
}
/**
* Renders the HTML for the buttons.
*
* @param string $gateway_id The gateway ID, like 'ppcp-gateway'.
* @param string|null $action_name The action name to be called.
*/
public function button_renderer( string $gateway_id, string $action_name = null ) {
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
if ( ! isset( $available_gateways[ $gateway_id ] ) ) {
return;
}
// The wrapper is needed for the loading spinner,
// otherwise jQuery block() prevents buttons rendering.
echo '
';
}
/**
* Renders the HTML for the credit messaging.
*/
public function message_renderer() {
$product = wc_get_product();
if (
! is_checkout() && is_a( $product, WC_Product::class )
/**
* The filter returning true if PayPal buttons can be rendered, or false otherwise.
*/
&& ! $this->product_supports_payment( $product )
) {
return;
}
echo '';
}
/**
* The values for the credit messaging.
*
* @return array
* @throws NotFoundException When a setting was not found.
*/
private function message_values(): array {
if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) {
return array();
}
$placement = is_checkout() ? 'payment' : ( is_cart() ? 'cart' : 'product' );
$product = wc_get_product();
$amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0;
if ( is_checkout() || is_cart() ) {
$amount = WC()->cart->get_total( 'raw' );
}
$styling_per_location = $this->settings->has( 'pay_later_enable_styling_per_messaging_location' ) && $this->settings->get( 'pay_later_enable_styling_per_messaging_location' );
$per_location = is_checkout() ? 'checkout' : ( is_cart() ? 'cart' : 'product' );
$location = $styling_per_location ? $per_location : 'general';
$setting_name_prefix = "pay_later_{$location}_message";
$layout = $this->settings->has( "{$setting_name_prefix}_layout" ) ? $this->settings->get( "{$setting_name_prefix}_layout" ) : 'text';
$logo_type = $this->settings->has( "{$setting_name_prefix}_logo" ) ? $this->settings->get( "{$setting_name_prefix}_logo" ) : 'primary';
$logo_position = $this->settings->has( "{$setting_name_prefix}_position" ) ? $this->settings->get( "{$setting_name_prefix}_position" ) : 'left';
$text_color = $this->settings->has( "{$setting_name_prefix}_color" ) ? $this->settings->get( "{$setting_name_prefix}_color" ) : 'black';
$style_color = $this->settings->has( "{$setting_name_prefix}_flex_color" ) ? $this->settings->get( "{$setting_name_prefix}_flex_color" ) : 'blue';
$ratio = $this->settings->has( "{$setting_name_prefix}_flex_ratio" ) ? $this->settings->get( "{$setting_name_prefix}_flex_ratio" ) : '1x1';
return array(
'wrapper' => '#ppcp-messages',
'is_hidden' => ! $this->is_pay_later_filter_enabled_for_location( $this->context() ),
'amount' => $amount,
'placement' => $placement,
'style' => array(
'layout' => $layout,
'logo' => array(
'type' => $logo_type,
'position' => $logo_position,
),
'text' => array(
'color' => $text_color,
),
'color' => $style_color,
'ratio' => $ratio,
),
);
}
/**
* Renders the HTML for the DCC fields.
*/
public function dcc_renderer() {
$id = 'ppcp-hosted-fields';
if ( ! $this->can_render_dcc() ) {
return;
}
/**
* The WC filter returning the WC order button text.
* phpcs:disable WordPress.WP.I18n.TextDomainMismatch
*/
$label = 'checkout' === $this->context() ? apply_filters( 'woocommerce_order_button_text', __( 'Place order', 'woocommerce' ) ) : __( 'Pay for order', 'woocommerce' );
// phpcs:enable WordPress.WP.I18n.TextDomainMismatch
printf(
'
',
esc_attr( $id ),
esc_html( $label )
);
}
/**
* Whether we can store vault tokens or not.
*
* @return bool
*/
public function can_save_vault_token(): bool {
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
return false;
}
if ( ! $this->settings->has( 'vault_enabled' ) || ! $this->settings->get( 'vault_enabled' ) ) {
return false;
}
return true;
}
/**
* Whether we need to initialize the script to enable tokenization for subscriptions or not.
*
* @return bool
*/
private function has_subscriptions(): bool {
if ( ! $this->subscription_helper->accept_only_automatic_payment_gateways() ) {
return false;
}
if ( is_product() ) {
return $this->subscription_helper->current_product_is_subscription();
}
if ( is_wc_endpoint_url( 'order-pay' ) ) {
return $this->subscription_helper->order_pay_contains_subscription();
}
return $this->subscription_helper->cart_contains_subscription();
}
/**
* Whether PayPal subscriptions is enabled or not.
*
* @return bool
*/
private function paypal_subscriptions_enabled(): bool {
try {
$subscriptions_mode = $this->settings->get( 'subscriptions_mode' );
} catch ( NotFoundException $exception ) {
return false;
}
return $subscriptions_mode === 'subscriptions_api';
}
/**
* Retrieves the 3D Secure contingency settings.
*
* @return string
*/
private function get_3ds_contingency(): string {
if ( $this->settings->has( '3d_secure_contingency' ) ) {
$value = $this->settings->get( '3d_secure_contingency' );
if ( $value ) {
return $value;
}
}
return 'SCA_WHEN_REQUIRED';
}
/**
* The configuration for the smart buttons.
*
* @return array
*/
public function script_data(): array {
$is_free_trial_cart = $this->is_free_trial_cart();
$url_params = $this->url_params();
$this->request_data->enqueue_nonce_fix();
$localize = array(
'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ),
'url_params' => $url_params,
'script_attributes' => $this->attributes(),
'client_id' => $this->client_id,
'currency' => $this->currency,
'data_client_id' => array(
'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
'user' => get_current_user_id(),
'has_subscriptions' => $this->has_subscriptions(),
'paypal_subscriptions_enabled' => $this->paypal_subscriptions_enabled(),
),
'redirect' => wc_get_checkout_url(),
'context' => $this->context(),
'ajax' => array(
'simulate_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( SimulateCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SimulateCartEndpoint::nonce() ),
),
'change_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
),
'create_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ),
),
'approve_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
),
'approve_subscription' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveSubscriptionEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveSubscriptionEndpoint::nonce() ),
),
'vault_paypal' => array(
'endpoint' => \WC_AJAX::get_endpoint( StartPayPalVaultingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( StartPayPalVaultingEndpoint::nonce() ),
),
'save_checkout_form' => array(
'endpoint' => \WC_AJAX::get_endpoint( SaveCheckoutFormEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( SaveCheckoutFormEndpoint::nonce() ),
),
'validate_checkout' => array(
'endpoint' => \WC_AJAX::get_endpoint( ValidateCheckoutEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ValidateCheckoutEndpoint::nonce() ),
),
'cart_script_params' => array(
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),
'enforce_vault' => $this->has_subscriptions(),
'can_save_vault_token' => $this->can_save_vault_token(),
'is_free_trial_cart' => $is_free_trial_cart,
'vaulted_paypal_email' => ( is_checkout() && $is_free_trial_cart ) ? $this->get_vaulted_paypal_email() : '',
'bn_codes' => $this->bn_codes(),
'payer' => $this->payerData(),
'button' => array(
'wrapper' => '#ppc-button-' . PayPalGateway::ID,
'is_disabled' => $this->is_button_disabled(),
'mini_cart_wrapper' => '#ppc-button-minicart',
'is_mini_cart_disabled' => $this->is_button_disabled( 'mini-cart' ),
'cancel_wrapper' => '#ppcp-cancel',
'mini_cart_style' => array(
'layout' => $this->style_for_context( 'layout', 'mini-cart' ),
'color' => $this->style_for_context( 'color', 'mini-cart' ),
'shape' => $this->style_for_context( 'shape', 'mini-cart' ),
'label' => $this->style_for_context( 'label', 'mini-cart' ),
'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ),
'height' => $this->settings->has( 'button_mini-cart_height' ) && $this->settings->get( 'button_mini-cart_height' ) ? $this->normalize_height( (int) $this->settings->get( 'button_mini-cart_height' ) ) : 35,
),
'style' => array(
'layout' => $this->style_for_context( 'layout', $this->context() ),
'color' => $this->style_for_context( 'color', $this->context() ),
'shape' => $this->style_for_context( 'shape', $this->context() ),
'label' => $this->style_for_context( 'label', $this->context() ),
'tagline' => $this->style_for_context( 'tagline', $this->context() ),
),
),
'separate_buttons' => array(
'card' => array(
'id' => CardButtonGateway::ID,
'wrapper' => '#ppc-button-' . CardButtonGateway::ID,
'style' => array(
'shape' => $this->style_for_context( 'shape', $this->context() ),
// TODO: color black, white from the gateway settings.
),
),
),
'hosted_fields' => array(
'wrapper' => '#ppcp-hosted-fields',
'labels' => array(
'credit_card_number' => '',
'cvv' => '',
'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ),
'fields_empty' => __(
'Card payment details are missing. Please fill in all required fields.',
'woocommerce-paypal-payments'
),
'fields_not_valid' => __(
'Unfortunately, your credit card details are not valid.',
'woocommerce-paypal-payments'
),
'card_not_supported' => __(
'Unfortunately, we do not support your credit card.',
'woocommerce-paypal-payments'
),
'cardholder_name_required' => __( 'Cardholder\'s first and last name are required, please fill the checkout form required fields.', 'woocommerce-paypal-payments' ),
),
'valid_cards' => $this->dcc_applies->valid_cards(),
'contingency' => $this->get_3ds_contingency(),
),
'messages' => $this->message_values(),
'labels' => array(
'error' => array(
'generic' => __(
'Something went wrong. Please try again or choose another payment source.',
'woocommerce-paypal-payments'
),
'required' => array(
'generic' => __(
'Required form fields are not filled.',
'woocommerce-paypal-payments'
),
// phpcs:ignore WordPress.WP.I18n
'field' => __( '%s is a required field.', 'woocommerce' ),
'elements' => array( // Map