diff --git a/changelog.txt b/changelog.txt index bd4294893..089810461 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,19 @@ *** Changelog *** += 2.6.1 - xxxx-xx-xx = +* Fix - Payment tokens fixes and adjustments #2106 +* Fix - Pay upon Invoice: Add input validation to Experience Context fields #2092 +* Fix - Disable markup in get_plugin_data() returns to fix an issue with wptexturize() #2094 +* Enhancement - Pay later messaging configurator improvements #2107 +* Enhancement - Replace the middleware URL from connect.woocommerce.com to api.woocommerce.com/integrations #2130 +* Enhancement - Remove all Sofort references as it has been deprecated #2124 +* Enhancement - Improve funding source names #2118 +* Enhancement - More fraud prevention capabilities by storing additional data in the order #2125 +* Enhancement - Update ACDC currency eligibility for AMEX #2129 +* Enhancement - Sync shipping options with Venmo when skipping final confirmation on Checkout #2108 +* Enhancement - Card Fields: Add a filter for the CVC field and update the placeholder to match the label #2089 +* Enhancement - Product Title: Sanitize before sending to PayPal #2090 + = 2.6.0 - 2024-03-20 = * Fix - invoice_id not included in API call when creating payment with saved card #2086 * Fix - Typo in SCA indicators for ACDC Vault transactions #2083 diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e2c64ac0c..22ff4e208 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1000,11 +1000,21 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages if ( $this->settings->has( '3d_secure_contingency' ) ) { $value = $this->settings->get( '3d_secure_contingency' ); if ( $value ) { - return $value; + return $this->return_3ds_contingency( $value ); } } - return 'SCA_WHEN_REQUIRED'; + return $this->return_3ds_contingency( 'SCA_WHEN_REQUIRED' ); + } + + /** + * Processes and returns the 3D Secure contingency. + * + * @param string $contingency The ThreeD secure contingency. + * @return string + */ + private function return_3ds_contingency( string $contingency ): string { + return apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $contingency ); } /** diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index c286b9f20..1ef06c3be 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -329,6 +329,21 @@ class CreateOrderEndpoint implements EndpointInterface { if ( 'pay-now' === $data['context'] && is_a( $wc_order, \WC_Order::class ) ) { $wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() ); $wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() ); + + $payment_source = $order->payment_source(); + $payment_source_name = $payment_source ? $payment_source->name() : null; + $payer = $order->payer(); + if ( + $payer + && $payment_source_name + && in_array( $payment_source_name, 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_meta_data(); do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order ); diff --git a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php index 03d8fbeed..46b4ddf41 100644 --- a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php +++ b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php @@ -159,6 +159,22 @@ class EarlyOrderHandler { $wc_order = wc_get_order( $order_id ); $wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() ); $wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() ); + + $payment_source = $order->payment_source(); + $payment_source_name = $payment_source ? $payment_source->name() : null; + $payer = $order->payer(); + if ( + $payer + && $payment_source_name + && in_array( $payment_source_name, PayPalGateway::PAYMENT_SOURCES_WITH_PAYER_EMAIL, true ) + && $wc_order instanceof \WC_Order + ) { + $payer_email = $payer->email_address(); + if ( $payer_email ) { + $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email ); + } + } + $wc_order->save_meta_data(); /** diff --git a/modules/ppcp-button/src/Helper/ThreeDSecure.php b/modules/ppcp-button/src/Helper/ThreeDSecure.php index 66c89e4be..1991112b5 100644 --- a/modules/ppcp-button/src/Helper/ThreeDSecure.php +++ b/modules/ppcp-button/src/Helper/ThreeDSecure.php @@ -57,21 +57,24 @@ class ThreeDSecure { * * @link https://developer.paypal.com/docs/business/checkout/add-capabilities/3d-secure/#authenticationresult * - * @param Order $order The order for which the decission is needed. + * @param Order $order The order for which the decision is needed. * * @return int */ public function proceed_with_order( Order $order ): int { + + do_action( 'woocommerce_paypal_payments_three_d_secure_before_check', $order ); + $payment_source = $order->payment_source(); if ( ! $payment_source ) { - return self::NO_DECISION; + return $this->return_decision( self::NO_DECISION, $order ); } if ( ! ( $payment_source->properties()->brand ?? '' ) ) { - return self::NO_DECISION; + return $this->return_decision( self::NO_DECISION, $order ); } if ( ! ( $payment_source->properties()->authentication_result ?? '' ) ) { - return self::NO_DECISION; + return $this->return_decision( self::NO_DECISION, $order ); } $authentication_result = $payment_source->properties()->authentication_result ?? null; @@ -81,18 +84,31 @@ class ThreeDSecure { $this->logger->info( '3DS Authentication Result: ' . wc_print_r( $result->to_array(), true ) ); if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_POSSIBLE ) { - return self::PROCCEED; + return $this->return_decision( self::PROCCEED, $order ); } if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_UNKNOWN ) { - return self::RETRY; + return $this->return_decision( self::RETRY, $order ); } if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_NO ) { - return $this->no_liability_shift( $result ); + return $this->return_decision( $this->no_liability_shift( $result ), $order ); } } - return self::NO_DECISION; + return $this->return_decision( self::NO_DECISION, $order ); + } + + /** + * Processes and returns a ThreeD secure decision. + * + * @param int $decision The ThreeD secure decision. + * @param Order $order The PayPal Order object. + * @return int + */ + public function return_decision( int $decision, Order $order ) { + $decision = apply_filters( 'woocommerce_paypal_payments_three_d_secure_decision', $decision, $order ); + do_action( 'woocommerce_paypal_payments_three_d_secure_after_check', $order, $decision ); + return $decision; } /** diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php index 213134857..6835108c3 100644 --- a/modules/ppcp-card-fields/src/CardFieldsModule.php +++ b/modules/ppcp-card-fields/src/CardFieldsModule.php @@ -115,17 +115,19 @@ class CardFieldsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); + $three_d_secure_contingency = + $settings->has( '3d_secure_contingency' ) + ? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) ) + : ''; + if ( - $settings->has( '3d_secure_contingency' ) - && ( - $settings->get( '3d_secure_contingency' ) === 'SCA_ALWAYS' - || $settings->get( '3d_secure_contingency' ) === 'SCA_WHEN_REQUIRED' - ) + $three_d_secure_contingency === 'SCA_ALWAYS' + || $three_d_secure_contingency === 'SCA_WHEN_REQUIRED' ) { $data['payment_source']['card'] = array( 'attributes' => array( 'verification' => array( - 'method' => $settings->get( '3d_secure_contingency' ), + 'method' => $three_d_secure_contingency, ), ), ); diff --git a/modules/ppcp-paylater-block/resources/js/edit.js b/modules/ppcp-paylater-block/resources/js/edit.js index c702d0ad9..44b4f895d 100644 --- a/modules/ppcp-paylater-block/resources/js/edit.js +++ b/modules/ppcp-paylater-block/resources/js/edit.js @@ -194,12 +194,11 @@ export default function Edit( { attributes, clientId, setAttributes } ) { help={ __( 'Used for the analytics dashboard in the merchant account.', 'woocommerce-paypal-payments' ) } options={ [ { label: __( 'Detect automatically', 'woocommerce-paypal-payments' ), value: 'auto' }, + { label: __( 'Product Page', 'woocommerce-paypal-payments' ), value: 'product' }, { label: __( 'Cart', 'woocommerce-paypal-payments' ), value: 'cart' }, - { label: __( 'Payment', 'woocommerce-paypal-payments' ), value: 'payment' }, - { label: __( 'Product', 'woocommerce-paypal-payments' ), value: 'product' }, - { label: __( 'Product list', 'woocommerce-paypal-payments' ), value: 'product-list' }, + { label: __( 'Checkout', 'woocommerce-paypal-payments' ), value: 'checkout' }, { label: __( 'Home', 'woocommerce-paypal-payments' ), value: 'home' }, - { label: __( 'Category', 'woocommerce-paypal-payments' ), value: 'category' }, + { label: __( 'Shop', 'woocommerce-paypal-payments' ), value: 'shop' }, ] } value={ placement } onChange={ ( value ) => setAttributes( { placement: value } ) } diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php index d6b9c7404..223ccd972 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockModule.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockModule.php @@ -40,7 +40,7 @@ class PayLaterBlockModule implements ModuleInterface { * @return bool true if the block is enabled, otherwise false. */ public static function is_block_enabled( SettingsStatus $settings_status ): bool { - return self::is_module_loading_required() && $settings_status->is_pay_later_messaging_enabled_for_location( 'woocommerceBlock' ); + return self::is_module_loading_required() && $settings_status->is_pay_later_messaging_enabled_for_location( 'custom_placement' ); } /** diff --git a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js index 15f65f183..9549ef2af 100644 --- a/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js +++ b/modules/ppcp-paylater-configurator/resources/js/paylater-configurator.js @@ -29,21 +29,17 @@ document.addEventListener( 'DOMContentLoaded', () => { setTimeout(() => { saveChangesButton.click(); // Trigger click event on saveChangesButton isSaving = false; // Reset flag when saving is complete - }, 500); // Adjust the delay as needed + }, 1000); // Adjust the delay as needed } }); - merchantConfigurators.Messaging({ config: PcpPayLaterConfigurator.config, merchantClientId: PcpPayLaterConfigurator.merchantClientId, partnerClientId: PcpPayLaterConfigurator.partnerClientId, partnerName: 'WooCommerce', bnCode: 'Woo_PPCP', - placements: ['cart', 'checkout', 'product', 'shop', 'home'], - custom_placement:[{ - message_reference: 'woocommerceBlock', - }], + placements: ['cart', 'checkout', 'product', 'shop', 'home', 'custom_placement'], styleOverrides: { button: publishButtonClassName, header: PcpPayLaterConfigurator.headerClassName, diff --git a/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php b/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php index 6584b7da3..df4e4affa 100644 --- a/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php +++ b/modules/ppcp-paylater-configurator/src/Endpoint/SaveConfig.php @@ -99,10 +99,13 @@ class SaveConfig { $this->settings->set( 'pay_later_messaging_enabled', true ); $enabled_locations = array(); - foreach ( $config as $placement => $data ) { $this->save_config_for_location( $data, $placement ); + if ( $placement === 'custom_placement' ) { + $data = $data[0] ?? array(); + } + if ( $data['status'] === 'enabled' ) { $enabled_locations[] = $placement; } @@ -129,6 +132,7 @@ class SaveConfig { $this->set_value_if_present( $config, 'logo-type', "pay_later_{$location}_message_logo" ); $this->set_value_if_present( $config, 'logo-color', "pay_later_{$location}_message_color" ); $this->set_value_if_present( $config, 'text-size', "pay_later_{$location}_message_text_size" ); + $this->set_value_if_present( $config, 'text-color', "pay_later_{$location}_message_color" ); } /** diff --git a/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php b/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php index d36fe91c3..c73bbd0ab 100644 --- a/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php +++ b/modules/ppcp-paylater-configurator/src/Factory/ConfigFactory.php @@ -27,7 +27,7 @@ class ConfigFactory { 'product' => $this->for_location( $settings, 'product' ), 'shop' => $this->for_location( $settings, 'shop' ), 'home' => $this->for_location( $settings, 'home' ), - 'woocommerceBlock' => $this->for_location( $settings, 'woocommerceBlock' ), + 'custom_placement' => array( $this->for_location( $settings, 'woocommerceBlock' ) ), ); } @@ -40,29 +40,87 @@ class ConfigFactory { private function for_location( Settings $settings, string $location ): array { $selected_locations = $settings->has( 'pay_later_messaging_locations' ) ? $settings->get( 'pay_later_messaging_locations' ) : array(); - if ( in_array( $location, array( 'shop', 'home' ), true ) ) { - $config = array( - 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'flex' ), - 'color' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_color", 'black' ), - 'ratio' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_ratio", '8x1' ), - ); - } elseif ( $location !== 'woocommerceBlock' ) { - $config = array( - 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'text' ), - 'logo-position' => $this->get_or_default( $settings, "pay_later_{$location}_message_position", 'left' ), - 'logo-type' => $this->get_or_default( $settings, "pay_later_{$location}_message_logo", 'inline' ), - 'text-color' => $this->get_or_default( $settings, "pay_later_{$location}_message_color", 'black' ), - 'text-size' => $this->get_or_default( $settings, "pay_later_{$location}_message_text_size", '12' ), - - ); + switch ( $location ) { + case 'shop': + case 'home': + $config = $this->for_shop_or_home( $settings, $location, $selected_locations ); + break; + case 'woocommerceBlock': + $config = $this->for_woocommerce_block( $selected_locations ); + break; + default: + $config = $this->for_default_location( $settings, $location, $selected_locations ); + break; } - return array_merge( - array( - 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled', - 'placement' => $location, - ), - $config ?? array() + return $config; + } + + /** + * Returns the configurator config for shop, home locations. + * + * @param Settings $settings The settings. + * @param string $location The location. + * @param string[] $selected_locations The list of selected locations. + * @return array{ + * layout: string, + * color: string, + * ratio: string, + * status: "disabled"|"enabled", + * placement: string + * } The configurator config map. + */ + private function for_shop_or_home( Settings $settings, string $location, array $selected_locations ): array { + return array( + 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'flex' ), + 'color' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_color", 'black' ), + 'ratio' => $this->get_or_default( $settings, "pay_later_{$location}_message_flex_ratio", '8x1' ), + 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled', + 'placement' => $location, + ); + } + + /** + * Returns the configurator config for woocommerceBlock location. + * + * @param array $selected_locations The list of selected locations. + * @return array{ + * status: "disabled"|"enabled", + * message_reference: string + * } The configurator config map. + */ + private function for_woocommerce_block( array $selected_locations ): array { + return array( + 'status' => in_array( 'custom_placement', $selected_locations, true ) ? 'enabled' : 'disabled', + 'message_reference' => 'woocommerceBlock', + ); + } + + /** + * Returns the configurator config for default locations. + * + * @param Settings $settings The settings. + * @param string $location The location. + * @param string[] $selected_locations The list of selected locations. + * @return array{ + * layout: string, + * logo-position: string, + * logo-type: string, + * text-color: string, + * text-size: string, + * status: "disabled"|"enabled", + * placement: string + * } The configurator config map. + */ + private function for_default_location( Settings $settings, string $location, array $selected_locations ): array { + return array( + 'layout' => $this->get_or_default( $settings, "pay_later_{$location}_message_layout", 'text' ), + 'logo-position' => $this->get_or_default( $settings, "pay_later_{$location}_message_position", 'left' ), + 'logo-type' => $this->get_or_default( $settings, "pay_later_{$location}_message_logo", 'inline' ), + 'text-color' => $this->get_or_default( $settings, "pay_later_{$location}_message_color", 'black' ), + 'text-size' => $this->get_or_default( $settings, "pay_later_{$location}_message_text_size", '12' ), + 'status' => in_array( $location, $selected_locations, true ) ? 'enabled' : 'disabled', + 'placement' => $location, ); } @@ -73,9 +131,9 @@ class ConfigFactory { * @param string $key The key. * @param mixed $default The default value. * @param array|null $allowed_values The list of allowed values, or null if all values are allowed. - * @return mixed + * @return string */ - private function get_or_default( Settings $settings, string $key, $default, ?array $allowed_values = null ) { + private function get_or_default( Settings $settings, string $key, $default, ?array $allowed_values = null ): string { if ( $settings->has( $key ) ) { $value = $settings->get( $key ); if ( ! $allowed_values || in_array( $value, $allowed_values, true ) ) { diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index f93a75a66..c8240c2c7 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -281,7 +281,11 @@ class SavePaymentMethodsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : ''; + + $verification_method = + $settings->has( '3d_secure_contingency' ) + ? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) ) + : ''; $change_payment_method = wc_clean( wp_unslash( $_GET['change_payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 73afb085b..19ebe9d08 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -48,12 +48,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. * diff --git a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php index 32569c60b..4cd9e07ed 100644 --- a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php +++ b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php @@ -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 {
  • %3$s
  • %4$s
  • +
  • %5$s
  • '; $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, diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php index b18468d7b..62a80456a 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php @@ -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 ); diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index e82f65e9e..4295b1b17 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -448,6 +448,49 @@ class WCGatewayModule implements ModuleInterface { delete_transient( 'ppcp_reference_transaction_enabled' ); } ); + + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + add_filter( + 'woocommerce_admin_billing_fields', + function ( $fields ) { + global $theorder; + + 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; + } + ); } /** diff --git a/package.json b/package.json index be3d3e101..a0e2d325a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "2.6.0", + "version": "2.6.1", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index d4b1f1b07..5c1181a21 100644 --- a/readme.txt +++ b/readme.txt @@ -2,9 +2,9 @@ Contributors: woocommerce, automattic, inpsyde Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay Requires at least: 5.3 -Tested up to: 6.4 +Tested up to: 6.5 Requires PHP: 7.2 -Stable tag: 2.6.0 +Stable tag: 2.6.1 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -179,6 +179,20 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == += 2.6.1 - xxxx-xx-xx = +* Fix - Payment tokens fixes and adjustments #2106 +* Fix - Pay upon Invoice: Add input validation to Experience Context fields #2092 +* Fix - Disable markup in get_plugin_data() returns to fix an issue with wptexturize() #2094 +* Enhancement - Pay later messaging configurator improvements #2107 +* Enhancement - Replace the middleware URL from connect.woocommerce.com to api.woocommerce.com/integrations #2130 +* Enhancement - Remove all Sofort references as it has been deprecated #2124 +* Enhancement - Improve funding source names #2118 +* Enhancement - More fraud prevention capabilities by storing additional data in the order #2125 +* Enhancement - Update ACDC currency eligibility for AMEX #2129 +* Enhancement - Sync shipping options with Venmo when skipping final confirmation on Checkout #2108 +* Enhancement - Card Fields: Add a filter for the CVC field and update the placeholder to match the label #2089 +* Enhancement - Product Title: Sanitize before sending to PayPal #2090 + = 2.6.0 - 2024-03-20 = * Fix - invoice_id not included in API call when creating payment with saved card #2086 * Fix - Typo in SCA indicators for ACDC Vault transactions #2083 diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php index fb728709a..c7d176c8e 100644 --- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -92,6 +92,8 @@ class VaultedCreditCardHandlerTest extends TestCase $customer = Mockery::mock(WC_Customer::class); $payer = Mockery::mock(Payer::class); + $payer->shouldReceive('email_address'); + $this->payerFactory->shouldReceive('from_wc_order') ->andReturn($payer); $this->shippingPreferenceFactory->shouldReceive('from_state') @@ -100,6 +102,7 @@ class VaultedCreditCardHandlerTest extends TestCase $order = Mockery::mock(Order::class); $order->shouldReceive('id')->andReturn('1'); $order->shouldReceive('intent')->andReturn('CAPTURE'); + $order->shouldReceive('payer')->andReturn($payer); $paymentSource = Mockery::mock(PaymentSource::class); $paymentSource->shouldReceive('name')->andReturn('card'); diff --git a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php index abff51a10..0f70f801c 100644 --- a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php @@ -89,6 +89,7 @@ private $testee; $order->shouldReceive('id')->andReturn('1'); $order->shouldReceive('intent'); $order->shouldReceive('payment_source'); + $order->shouldReceive('payer'); $this->orderEndpoint ->shouldReceive('create') diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index d27ec6ff7..95f1b7b48 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -93,6 +93,7 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('payment_source') ->andReturn(null); + $currentOrder->shouldReceive('payer'); $wcOrder ->shouldReceive('get_meta') @@ -230,6 +231,7 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('payment_source') ->andReturn(null); + $currentOrder->shouldReceive('payer'); $wcOrder ->shouldReceive('get_meta') @@ -357,6 +359,7 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('purchase_units') ->andReturn([$purchaseUnit]); + $currentOrder->shouldReceive('payer'); $wcOrder ->shouldReceive('get_meta') diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 5e6127104..446f88f64 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,13 +3,13 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.6.0 + * Version: 2.6.1 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 * Requires PHP: 7.2 * WC requires at least: 3.9 - * WC tested up to: 8.6 + * WC tested up to: 8.7 * Text Domain: woocommerce-paypal-payments * * @package WooCommerce\PayPalCommerce @@ -25,7 +25,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2024-03-12' ); +define( 'PAYPAL_INTEGRATION_DATE', '2024-04-03' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );