Merge branch 'trunk' of github.com:woocommerce/woocommerce-paypal-payments into PCP-3202-retrieve-button-styling-properties-from-woo-commerce-checkout-block-ver-2

This commit is contained in:
Daniel Dudzic 2024-08-14 12:46:18 +02:00
commit c69c4ed0f3
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
45 changed files with 1175 additions and 1795 deletions

View file

@ -1,6 +1,23 @@
*** Changelog *** *** Changelog ***
= 2.8.2 - xxxx-xx-xx = = 2.8.3 - 2024-08-12 =
* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474
* Fix - Do not add tax info into order meta during order creation #2471
* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425
* Fix - PayPal js files loaded on non PayPal pages #2411
* Fix - Google Pay: Fix the incorrect popup triggering #2414
* Fix - Add tax configurator when programmatically creating WC orders #2431
* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402
* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334
* Fix - Prevent displaying smart button multiple times on variable product page #2420
* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404
* Fix - Use client credentials for user tokens #2491
* Fix - Apple Pay: Fix the shipping callback #2492
* Enhancement - Separate Google Pay button for Classic Checkout #2430
* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468
* Enhancement - Add AMEX support for Advanced Card Processing in China #2469
= 2.8.2 - 2024-07-22 =
* Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415
* Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400
* Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 * Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient; namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ClientCredentials;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
@ -1443,6 +1444,7 @@ return array(
'CN' => array( 'CN' => array(
'mastercard' => array(), 'mastercard' => array(),
'visa' => array(), 'visa' => array(),
'amex' => array(),
), ),
'CY' => array( 'CY' => array(
'mastercard' => array(), 'mastercard' => array(),
@ -1655,18 +1657,27 @@ return array(
return new PurchaseUnitSanitizer( $behavior, $line_name ); return new PurchaseUnitSanitizer( $behavior, $line_name );
} }
), ),
'api.client-credentials' => static function( ContainerInterface $container ): ClientCredentials {
return new ClientCredentials(
$container->get( 'wcgateway.settings' )
);
},
'api.client-credentials-cache' => static function( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-client-credentials-cache' );
},
'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken { 'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken {
return new UserIdToken( return new UserIdToken(
$container->get( 'api.host' ), $container->get( 'api.host' ),
$container->get( 'api.bearer' ), $container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'api.client-credentials' )
); );
}, },
'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken {
return new SdkClientToken( return new SdkClientToken(
$container->get( 'api.host' ), $container->get( 'api.host' ),
$container->get( 'api.bearer' ), $container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'api.client-credentials' ),
$container->get( 'api.client-credentials-cache' )
); );
}, },
); );

View file

@ -10,6 +10,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient; namespace WooCommerce\PayPalCommerce\ApiClient;
use WC_Order; use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;

View file

@ -0,0 +1,49 @@
<?php
/**
* The client credentials.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Authentication
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Authentication;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
/**
* Class ClientCredentials
*/
class ClientCredentials {
/**
* The settings.
*
* @var Settings
*/
protected $settings;
/**
* ClientCredentials constructor.
*
* @param Settings $settings The settings.
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
/**
* Returns encoded client credentials.
*
* @return string
* @throws NotFoundException If setting does not found.
*/
public function credentials(): string {
$client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : '';
$client_secret = $this->settings->has( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : '';
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return 'Basic ' . base64_encode( $client_id . ':' . $client_secret );
}
}

View file

@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WP_Error; use WP_Error;
/** /**
@ -20,6 +21,8 @@ class SdkClientToken {
use RequestTrait; use RequestTrait;
const CACHE_KEY = 'sdk-client-token-key';
/** /**
* The host. * The host.
* *
@ -27,13 +30,6 @@ class SdkClientToken {
*/ */
private $host; private $host;
/**
* The bearer.
*
* @var Bearer
*/
private $bearer;
/** /**
* The logger. * The logger.
* *
@ -41,35 +37,52 @@ class SdkClientToken {
*/ */
private $logger; private $logger;
/**
* The client credentials.
*
* @var ClientCredentials
*/
private $client_credentials;
/**
* The cache.
*
* @var Cache
*/
private $cache;
/** /**
* SdkClientToken constructor. * SdkClientToken constructor.
* *
* @param string $host The host. * @param string $host The host.
* @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger.
* @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials.
* @param Cache $cache The cache.
*/ */
public function __construct( public function __construct(
string $host, string $host,
Bearer $bearer, LoggerInterface $logger,
LoggerInterface $logger ClientCredentials $client_credentials,
Cache $cache
) { ) {
$this->host = $host; $this->host = $host;
$this->bearer = $bearer; $this->logger = $logger;
$this->logger = $logger; $this->client_credentials = $client_credentials;
$this->cache = $cache;
} }
/** /**
* Returns `sdk_client_token` which uniquely identifies the payer. * Returns the client token for SDK `data-sdk-client-token`.
*
* @param string $target_customer_id Vaulted customer id.
* *
* @return string * @return string
* *
* @throws PayPalApiException If the request fails. * @throws PayPalApiException If the request fails.
* @throws RuntimeException If something unexpected happens. * @throws RuntimeException If something unexpected happens.
*/ */
public function sdk_client_token( string $target_customer_id = '' ): string { public function sdk_client_token(): string {
$bearer = $this->bearer->bearer(); if ( $this->cache->has( self::CACHE_KEY ) ) {
return $this->cache->get( self::CACHE_KEY );
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' );
@ -77,19 +90,10 @@ class SdkClientToken {
$url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=client_token&intent=sdk_init&domains[]=' . $domain; $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=client_token&intent=sdk_init&domains[]=' . $domain;
if ( $target_customer_id ) {
$url = add_query_arg(
array(
'target_customer_id' => $target_customer_id,
),
$url
);
}
$args = array( $args = array(
'method' => 'POST', 'method' => 'POST',
'headers' => array( 'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(), 'Authorization' => $this->client_credentials->credentials(),
'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Type' => 'application/x-www-form-urlencoded',
), ),
); );
@ -105,6 +109,11 @@ class SdkClientToken {
throw new PayPalApiException( $json, $status_code ); throw new PayPalApiException( $json, $status_code );
} }
return $json->access_token; $access_token = $json->access_token;
$expires_in = (int) $json->expires_in;
$this->cache->set( self::CACHE_KEY, $access_token, $expires_in );
return $access_token;
} }
} }

View file

@ -27,13 +27,6 @@ class UserIdToken {
*/ */
private $host; private $host;
/**
* The bearer.
*
* @var Bearer
*/
private $bearer;
/** /**
* The logger. * The logger.
* *
@ -41,21 +34,28 @@ class UserIdToken {
*/ */
private $logger; private $logger;
/**
* The client credentials.
*
* @var ClientCredentials
*/
private $client_credentials;
/** /**
* UserIdToken constructor. * UserIdToken constructor.
* *
* @param string $host The host. * @param string $host The host.
* @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger.
* @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials.
*/ */
public function __construct( public function __construct(
string $host, string $host,
Bearer $bearer, LoggerInterface $logger,
LoggerInterface $logger ClientCredentials $client_credentials
) { ) {
$this->host = $host; $this->host = $host;
$this->bearer = $bearer; $this->logger = $logger;
$this->logger = $logger; $this->client_credentials = $client_credentials;
} }
/** /**
@ -69,8 +69,6 @@ class UserIdToken {
* @throws RuntimeException If something unexpected happens. * @throws RuntimeException If something unexpected happens.
*/ */
public function id_token( string $target_customer_id = '' ): string { public function id_token( string $target_customer_id = '' ): string {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token';
if ( $target_customer_id ) { if ( $target_customer_id ) {
$url = add_query_arg( $url = add_query_arg(
@ -84,7 +82,7 @@ class UserIdToken {
$args = array( $args = array(
'method' => 'POST', 'method' => 'POST',
'headers' => array( 'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(), 'Authorization' => $this->client_credentials->credentials(),
'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Type' => 'application/x-www-form-urlencoded',
), ),
); );

View file

@ -644,10 +644,12 @@ class ApplepayButton {
return { return {
action: 'ppcp_update_shipping_method', action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod, shipping_method: event.shippingMethod,
simplified_contact: simplified_contact: this.hasValidContactInfo(
this.updatedContactInfo || this.updatedContactInfo
this.initialPaymentRequest.shippingContact || )
this.initialPaymentRequest.billingContact, ? this.updatedContactInfo
: this.initialPaymentRequest?.shippingContact ??
this.initialPaymentRequest?.billingContact,
product_id, product_id,
products: JSON.stringify( this.products ), products: JSON.stringify( this.products ),
caller_page: 'productDetail', caller_page: 'productDetail',
@ -662,10 +664,12 @@ class ApplepayButton {
return { return {
action: 'ppcp_update_shipping_method', action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod, shipping_method: event.shippingMethod,
simplified_contact: simplified_contact: this.hasValidContactInfo(
this.updatedContactInfo || this.updatedContactInfo
this.initialPaymentRequest.shippingContact || )
this.initialPaymentRequest.billingContact, ? this.updatedContactInfo
: this.initialPaymentRequest?.shippingContact ??
this.initialPaymentRequest?.billingContact,
caller_page: 'cart', caller_page: 'cart',
'woocommerce-process-checkout-nonce': this.nonce, 'woocommerce-process-checkout-nonce': this.nonce,
}; };
@ -948,6 +952,12 @@ class ApplepayButton {
return btoa( utf8Str ); return btoa( utf8Str );
} }
hasValidContactInfo( value ) {
return Array.isArray( value )
? value.length > 0
: Object.keys( value || {} ).length > 0;
}
} }
export default ApplepayButton; export default ApplepayButton;

View file

@ -1,6 +1,5 @@
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription';
class BaseHandler { class BaseHandler {
constructor( buttonConfig, ppcpConfig ) { constructor( buttonConfig, ppcpConfig ) {
@ -24,7 +23,7 @@ class BaseHandler {
} }
shippingAllowed() { shippingAllowed() {
return this.buttonConfig.product.needsShipping; return this.buttonConfig.product.needShipping;
} }
transactionInfo() { transactionInfo() {
@ -76,13 +75,6 @@ class BaseHandler {
document.querySelector( '.woocommerce-notices-wrapper' ) document.querySelector( '.woocommerce-notices-wrapper' )
); );
} }
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector( '.woocommerce-notices-wrapper' )
);
}
} }
export default BaseHandler; export default BaseHandler;

View file

@ -24,32 +24,33 @@ use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array( return array(
'applepay.eligible' => static function ( ContainerInterface $container ): bool { 'applepay.eligible' => static function ( ContainerInterface $container ): bool {
$apm_applies = $container->get( 'applepay.helpers.apm-applies' ); $apm_applies = $container->get( 'applepay.helpers.apm-applies' );
assert( $apm_applies instanceof ApmApplies ); assert( $apm_applies instanceof ApmApplies );
return $apm_applies->for_country_currency(); return $apm_applies->for_country() && $apm_applies->for_currency();
}, },
'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
return new ApmApplies( return new ApmApplies(
$container->get( 'applepay.supported-country-currency-matrix' ), $container->get( 'applepay.supported-countries' ),
$container->get( 'applepay.supported-currencies' ),
$container->get( 'api.shop.currency' ), $container->get( 'api.shop.currency' ),
$container->get( 'api.shop.country' ) $container->get( 'api.shop.country' )
); );
}, },
'applepay.status-cache' => static function( ContainerInterface $container ): Cache { 'applepay.status-cache' => static function( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-apple-status-cache' ); return new Cache( 'ppcp-paypal-apple-status-cache' );
}, },
// We assume it's a referral if we can check product status without API request failures. // We assume it's a referral if we can check product status without API request failures.
'applepay.is_referral' => static function ( ContainerInterface $container ): bool { 'applepay.is_referral' => static function ( ContainerInterface $container ): bool {
$status = $container->get( 'applepay.apple-product-status' ); $status = $container->get( 'applepay.apple-product-status' );
assert( $status instanceof AppleProductStatus ); assert( $status instanceof AppleProductStatus );
return ! $status->has_request_failure(); return ! $status->has_request_failure();
}, },
'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new AvailabilityNotice( return new AvailabilityNotice(
@ -63,17 +64,17 @@ return array(
); );
}, },
'applepay.has_validated' => static function ( ContainerInterface $container ): bool { 'applepay.has_validated' => static function ( ContainerInterface $container ): bool {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return $settings->has( 'applepay_validated' ); return $settings->has( 'applepay_validated' );
}, },
'applepay.is_validated' => static function ( ContainerInterface $container ): bool { 'applepay.is_validated' => static function ( ContainerInterface $container ): bool {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false; return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false;
}, },
'applepay.apple-product-status' => SingletonDecorator::make( 'applepay.apple-product-status' => SingletonDecorator::make(
static function( ContainerInterface $container ): AppleProductStatus { static function( ContainerInterface $container ): AppleProductStatus {
return new AppleProductStatus( return new AppleProductStatus(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
@ -83,7 +84,7 @@ return array(
); );
} }
), ),
'applepay.available' => static function ( ContainerInterface $container ): bool { 'applepay.available' => static function ( ContainerInterface $container ): bool {
if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', true ) ) { if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', true ) ) {
$status = $container->get( 'applepay.apple-product-status' ); $status = $container->get( 'applepay.apple-product-status' );
assert( $status instanceof AppleProductStatus ); assert( $status instanceof AppleProductStatus );
@ -94,10 +95,10 @@ return array(
} }
return true; return true;
}, },
'applepay.server_supported' => static function ( ContainerInterface $container ): bool { 'applepay.server_supported' => static function ( ContainerInterface $container ): bool {
return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off'; return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off';
}, },
'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ); $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' );
if ( $user_agent ) { if ( $user_agent ) {
@ -127,7 +128,7 @@ return array(
} }
return false; return false;
}, },
'applepay.url' => static function ( ContainerInterface $container ): string { 'applepay.url' => static function ( ContainerInterface $container ): string {
$path = realpath( __FILE__ ); $path = realpath( __FILE__ );
if ( false === $path ) { if ( false === $path ) {
return ''; return '';
@ -137,13 +138,13 @@ return array(
dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
); );
}, },
'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string {
return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js';
}, },
'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts {
return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) ); return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) );
}, },
'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton {
return new ApplePayButton( return new ApplePayButton(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'woocommerce.logger.woocommerce' ),
@ -155,7 +156,7 @@ return array(
$container->get( 'button.helper.cart-products' ) $container->get( 'button.helper.cart-products' )
); );
}, },
'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
return new BlocksPaymentMethod( return new BlocksPaymentMethod(
'ppcp-applepay', 'ppcp-applepay',
$container->get( 'applepay.url' ), $container->get( 'applepay.url' ),
@ -164,781 +165,103 @@ return array(
$container->get( 'blocks.method' ) $container->get( 'blocks.method' )
); );
}, },
/** /**
* The matrix which countries and currency combinations can be used for ApplePay. * The list of which countries can be used for ApplePay.
*/ */
'applepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { 'applepay.supported-countries' => static function ( ContainerInterface $container ) : array {
/** /**
* Returns which countries and currency combinations can be used for ApplePay. * Returns which countries can be used for ApplePay.
*/ */
return apply_filters( return apply_filters(
'woocommerce_paypal_payments_applepay_supported_country_currency_matrix', 'woocommerce_paypal_payments_applepay_supported_countries',
// phpcs:disable Squiz.Commenting.InlineComment
array( array(
'AU' => array( 'AU', // Australia
'AUD', 'AT', // Austria
'BRL', 'BE', // Belgium
'CAD', 'BG', // Bulgaria
'CHF', 'CA', // Canada
'CZK', 'CN', // China
'DKK', 'CY', // Cyprus
'EUR', 'CZ', // Czech Republic
'GBP', 'DK', // Denmark
'HKD', 'EE', // Estonia
'HUF', 'FI', // Finland
'ILS', 'FR', // France
'JPY', 'DE', // Germany
'MXN', 'GR', // Greece
'NOK', 'HU', // Hungary
'NZD', 'IE', // Ireland
'PHP', 'IT', // Italy
'PLN', 'LV', // Latvia
'SEK', 'LI', // Liechtenstein
'SGD', 'LT', // Lithuania
'THB', 'LU', // Luxembourg
'TWD', 'MT', // Malta
'USD', 'NL', // Netherlands
), 'NO', // Norway
'AT' => array( 'PL', // Poland
'AUD', 'PT', // Portugal
'BRL', 'RO', // Romania
'CAD', 'SK', // Slovakia
'CHF', 'SI', // Slovenia
'CZK', 'ES', // Spain
'DKK', 'SE', // Sweden
'EUR', 'US', // United States
'GBP', 'GB', // United Kingdom
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'BE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'BG' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CA' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CY' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CZ' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'DK' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'EE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'FI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'FR' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'DE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'GR' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'HU' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'IE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'IT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LV' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LU' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'MT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'NO' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'NL' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'PL' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'PT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'RO' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SK' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'ES' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'GB' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
) )
// phpcs:enable Squiz.Commenting.InlineComment
); );
}, },
'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { /**
* The list of which currencies can be used for ApplePay.
*/
'applepay.supported-currencies' => static function ( ContainerInterface $container ) : array {
/**
* Returns which currencies can be used for ApplePay.
*/
return apply_filters(
'woocommerce_paypal_payments_applepay_supported_currencies',
// phpcs:disable Squiz.Commenting.InlineComment
array(
'AUD', // Australian Dollar
'BRL', // Brazilian Real
'CAD', // Canadian Dollar
'CHF', // Swiss Franc
'CZK', // Czech Koruna
'DKK', // Danish Krone
'EUR', // Euro
'GBP', // British Pound Sterling
'HKD', // Hong Kong Dollar
'HUF', // Hungarian Forint
'ILS', // Israeli New Shekel
'JPY', // Japanese Yen
'MXN', // Mexican Peso
'NOK', // Norwegian Krone
'NZD', // New Zealand Dollar
'PHP', // Philippine Peso
'PLN', // Polish Zloty
'SEK', // Swedish Krona
'SGD', // Singapore Dollar
'THB', // Thai Baht
'TWD', // New Taiwan Dollar
'USD', // United States Dollar
)
// phpcs:enable Squiz.Commenting.InlineComment
);
},
'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string {
return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY';
}, },
'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string {
return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY';
}, },
'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
if ( $state->current_state() < State::STATE_ONBOARDED ) { if ( $state->current_state() < State::STATE_ONBOARDED ) {
return ''; return '';

View file

@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Applepay\Helper;
class ApmApplies { class ApmApplies {
/** /**
* The matrix which countries and currency combinations can be used for ApplePay. * The list of which countries can be used for ApplePay.
* *
* @var array * @var array
*/ */
private $allowed_country_currency_matrix; private $allowed_countries;
/**
* The list of which currencies can be used for ApplePay.
*
* @var array
*/
private $allowed_currencies;
/** /**
* 3-letter currency code of the shop. * 3-letter currency code of the shop.
@ -37,32 +44,41 @@ class ApmApplies {
private $country; private $country;
/** /**
* ApmApplies constructor. * DccApplies constructor.
* *
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for ApplePay. * @param array $allowed_countries The list of which countries can be used for ApplePay.
* @param array $allowed_currencies The list of which currencies can be used for ApplePay.
* @param string $currency 3-letter currency code of the shop. * @param string $currency 3-letter currency code of the shop.
* @param string $country 2-letter country code of the shop. * @param string $country 2-letter country code of the shop.
*/ */
public function __construct( public function __construct(
array $allowed_country_currency_matrix, array $allowed_countries,
array $allowed_currencies,
string $currency, string $currency,
string $country string $country
) { ) {
$this->allowed_country_currency_matrix = $allowed_country_currency_matrix; $this->allowed_countries = $allowed_countries;
$this->currency = $currency; $this->allowed_currencies = $allowed_currencies;
$this->country = $country; $this->currency = $currency;
$this->country = $country;
} }
/** /**
* Returns whether ApplePay can be used in the current country and the current currency used. * Returns whether ApplePay can be used in the current country used.
* *
* @return bool * @return bool
*/ */
public function for_country_currency(): bool { public function for_country(): bool {
if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { return in_array( $this->country, $this->allowed_countries, true );
return false; }
}
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); /**
* Returns whether ApplePay can be used in the current currency used.
*
* @return bool
*/
public function for_currency(): bool {
return in_array( $this->currency, $this->allowed_currencies, true );
} }
} }

View file

@ -280,15 +280,7 @@ class AxoModule implements ModuleInterface {
array $localized_script_data array $localized_script_data
): array { ): array {
try { try {
$target_customer_id = ''; $sdk_client_token = $api->sdk_client_token();
if ( is_user_logged_in() ) {
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
if ( ! $target_customer_id ) {
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
}
}
$sdk_client_token = $api->sdk_client_token( $target_customer_id );
$localized_script_data['axo'] = array( $localized_script_data['axo'] = array(
'sdk_client_token' => $sdk_client_token, 'sdk_client_token' => $sdk_client_token,
); );

View file

@ -52,7 +52,7 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
* *
* @var Settings * @var Settings
*/ */
protected $settings; protected $plugin_settings;
/** /**
* AdvancedCardPaymentMethod constructor. * AdvancedCardPaymentMethod constructor.
@ -70,12 +70,12 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
$smart_button, $smart_button,
Settings $settings Settings $settings
) { ) {
$this->name = CreditCardGateway::ID; $this->name = CreditCardGateway::ID;
$this->module_url = $module_url; $this->module_url = $module_url;
$this->version = $version; $this->version = $version;
$this->gateway = $gateway; $this->gateway = $gateway;
$this->smart_button = $smart_button; $this->smart_button = $smart_button;
$this->settings = $settings; $this->plugin_settings = $settings;
} }
/** /**
@ -118,8 +118,8 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
'scriptData' => $script_data, 'scriptData' => $script_data,
'supports' => $this->gateway->supports, 'supports' => $this->gateway->supports,
'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ), 'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ),
'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ), 'is_vaulting_enabled' => $this->plugin_settings->has( 'vault_enabled_dcc' ) && $this->plugin_settings->get( 'vault_enabled_dcc' ),
'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(), 'card_icons' => $this->plugin_settings->has( 'card_icons' ) ? (array) $this->plugin_settings->get( 'card_icons' ) : array(),
); );
} }

View file

@ -181,9 +181,14 @@ class CheckoutBootstap {
const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes( const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes(
currentPaymentMethod currentPaymentMethod
); );
const isGooglePayMethod =
currentPaymentMethod === PaymentMethods.GOOGLEPAY;
const isSavedCard = isCard && isSavedCardSelected(); const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = const isNotOurGateway =
! isPaypal && ! isCard && ! isSeparateButtonGateway; ! isPaypal &&
! isCard &&
! isSeparateButtonGateway &&
! isGooglePayMethod;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = const hasVaultedPaypal =
PayPalCommerceGateway.vaulted_paypal_email !== ''; PayPalCommerceGateway.vaulted_paypal_email !== '';
@ -227,6 +232,8 @@ class CheckoutBootstap {
} }
} }
setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
} }

View file

@ -7,6 +7,7 @@ import { getPlanIdFromVariation } from '../Helper/Subscriptions';
import SimulateCart from '../Helper/SimulateCart'; import SimulateCart from '../Helper/SimulateCart';
import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils'; import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils';
import merge from 'deepmerge'; import merge from 'deepmerge';
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
class SingleProductBootstap { class SingleProductBootstap {
constructor( gateway, renderer, errorHandler ) { constructor( gateway, renderer, errorHandler ) {
@ -20,9 +21,13 @@ class SingleProductBootstap {
// Prevent simulate cart being called too many times in a burst. // Prevent simulate cart being called too many times in a burst.
this.simulateCartThrottled = throttle( this.simulateCartThrottled = throttle(
this.simulateCart, this.simulateCart.bind( this ),
this.gateway.simulate_cart.throttling || 5000 this.gateway.simulate_cart.throttling || 5000
); );
this.debouncedHandleChange = debounce(
this.handleChange.bind( this ),
100
);
this.renderer.onButtonsInit( this.renderer.onButtonsInit(
this.gateway.button.wrapper, this.gateway.button.wrapper,
@ -74,7 +79,7 @@ class SingleProductBootstap {
} }
jQuery( document ).on( 'change', this.formSelector, () => { jQuery( document ).on( 'change', this.formSelector, () => {
this.handleChange(); this.debouncedHandleChange();
} ); } );
this.mutationObserver.observe( form, { this.mutationObserver.observe( form, {
childList: true, childList: true,

View file

@ -3,6 +3,7 @@ export const PaymentMethods = {
CARDS: 'ppcp-credit-card-gateway', CARDS: 'ppcp-credit-card-gateway',
OXXO: 'ppcp-oxxo-gateway', OXXO: 'ppcp-oxxo-gateway',
CARD_BUTTON: 'ppcp-card-button-gateway', CARD_BUTTON: 'ppcp-card-button-gateway',
GOOGLEPAY: 'ppcp-googlepay',
}; };
export const ORDER_BUTTON_SELECTOR = '#place_order'; export const ORDER_BUTTON_SELECTOR = '#place_order';

View file

@ -71,7 +71,10 @@ export const loadPaypalScript = ( config, onLoaded, onError = null ) => {
} }
// Load PayPal script for special case with data-client-token // Load PayPal script for special case with data-client-token
if ( config.data_client_id?.set_attribute ) { if (
config.data_client_id?.set_attribute &&
config.vault_v3_enabled !== '1'
) {
dataClientIdAttributeHandler( dataClientIdAttributeHandler(
scriptOptions, scriptOptions,
config.data_client_id, config.data_client_id,

View file

@ -14,8 +14,10 @@ use WC_Cart;
use WC_Order; use WC_Order;
use WC_Order_Item_Product; use WC_Order_Item_Product;
use WC_Order_Item_Shipping; use WC_Order_Item_Shipping;
use WC_Product;
use WC_Subscription; use WC_Subscription;
use WC_Subscriptions_Product; use WC_Subscriptions_Product;
use WC_Tax;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
@ -106,6 +108,7 @@ class WooCommerceOrderCreator {
* @param Payer|null $payer The payer. * @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping. * @param Shipping|null $shipping The shipping.
* @return void * @return void
* @psalm-suppress InvalidScalarArgument
*/ */
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void { protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void {
$cart_contents = $wc_cart->get_cart(); $cart_contents = $wc_cart->get_cart();
@ -130,18 +133,21 @@ class WooCommerceOrderCreator {
return; return;
} }
$total = $product->get_price() * $quantity; $subtotal = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$subtotal = apply_filters( 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', $subtotal, $cart_item );
$item->set_name( $product->get_name() ); $item->set_name( $product->get_name() );
$item->set_subtotal( $total ); $item->set_subtotal( $subtotal );
$item->set_total( $total ); $item->set_total( $subtotal );
$this->configure_taxes( $product, $item, $subtotal );
$product_id = $product->get_id(); $product_id = $product->get_id();
if ( $this->is_subscription( $product_id ) ) { if ( $this->is_subscription( $product_id ) ) {
$subscription = $this->create_subscription( $wc_order, $product_id ); $subscription = $this->create_subscription( $wc_order, $product_id );
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
$subscription_total = $total + $sign_up_fee; $subscription_total = (float) $subtotal + (float) $sign_up_fee;
$item->set_subtotal( $subscription_total ); $item->set_subtotal( $subscription_total );
$item->set_total( $subscription_total ); $item->set_total( $subscription_total );
@ -282,6 +288,23 @@ class WooCommerceOrderCreator {
} }
} }
/**
* Configures the taxes.
*
* @param WC_Product $product The Product.
* @param WC_Order_Item_Product $item The line item.
* @param float|string $subtotal The subtotal.
* @return void
* @psalm-suppress InvalidScalarArgument
*/
protected function configure_taxes( WC_Product $product, WC_Order_Item_Product $item, $subtotal ): void {
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
$taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, true );
$item->set_tax_class( $product->get_tax_class() );
$item->set_total_tax( (float) array_sum( $taxes ) );
}
/** /**
* Checks if the product with given ID is WC subscription. * Checks if the product with given ID is WC subscription.
* *

View file

@ -83,6 +83,9 @@ return array(
'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool { 'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool {
return class_exists( 'WC_Connect_Loader' ); return class_exists( 'WC_Connect_Loader' );
}, },
'compat.nyp.is_supported_plugin_version_active' => function (): bool {
return function_exists( 'wc_nyp_init' );
},
'compat.module.url' => static function ( ContainerInterface $container ): string { 'compat.module.url' => static function ( ContainerInterface $container ): string {
/** /**

View file

@ -56,6 +56,11 @@ class CompatModule implements ModuleInterface {
$this->fix_page_builders(); $this->fix_page_builders();
$this->exclude_cache_plugins_js_minification( $c ); $this->exclude_cache_plugins_js_minification( $c );
$this->set_elementor_checkout_context(); $this->set_elementor_checkout_context();
$is_nyp_active = $c->get( 'compat.nyp.is_supported_plugin_version_active' );
if ( $is_nyp_active ) {
$this->initialize_nyp_compat_layer();
}
} }
/** /**
@ -387,4 +392,24 @@ class CompatModule implements ModuleInterface {
3 3
); );
} }
/**
* Sets up the compatibility layer for PayPal Shipping callback & WooCommerce Name Your Price plugin.
*
* @return void
*/
protected function initialize_nyp_compat_layer(): void {
add_filter(
'woocommerce_paypal_payments_shipping_callback_cart_line_item_total',
static function( string $total, array $cart_item ) {
if ( ! isset( $cart_item['nyp'] ) ) {
return $total;
}
return $cart_item['nyp'];
},
10,
2
);
}
} }

View file

@ -13,3 +13,7 @@
min-width: 0 !important; min-width: 0 !important;
} }
} }
#ppc-button-ppcp-googlepay {
display: none;
}

View file

@ -4,7 +4,7 @@ import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/
import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator';
class CheckoutHandler extends BaseHandler { class CheckoutHandler extends BaseHandler {
transactionInfo() { validateForm() {
return new Promise( async ( resolve, reject ) => { return new Promise( async ( resolve, reject ) => {
try { try {
const spinner = new Spinner(); const spinner = new Spinner();
@ -23,7 +23,7 @@ class CheckoutHandler extends BaseHandler {
: null; : null;
if ( ! formValidator ) { if ( ! formValidator ) {
resolve( super.transactionInfo() ); resolve();
return; return;
} }
@ -42,7 +42,7 @@ class CheckoutHandler extends BaseHandler {
reject(); reject();
} else { } else {
resolve( super.transactionInfo() ); resolve();
} }
} ); } );
} catch ( error ) { } catch ( error ) {

View file

@ -1,4 +1,3 @@
import ContextHandlerFactory from './Context/ContextHandlerFactory';
import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
@ -6,7 +5,13 @@ import UpdatePaymentData from './Helper/UpdatePaymentData';
import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons';
class GooglepayButton { class GooglepayButton {
constructor( context, externalHandler, buttonConfig, ppcpConfig ) { constructor(
context,
externalHandler,
buttonConfig,
ppcpConfig,
contextHandler
) {
apmButtonsInit( ppcpConfig ); apmButtonsInit( ppcpConfig );
this.isInitialized = false; this.isInitialized = false;
@ -15,16 +20,10 @@ class GooglepayButton {
this.externalHandler = externalHandler; this.externalHandler = externalHandler;
this.buttonConfig = buttonConfig; this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig; this.ppcpConfig = ppcpConfig;
this.contextHandler = contextHandler;
this.paymentsClient = null; this.paymentsClient = null;
this.contextHandler = ContextHandlerFactory.create(
this.context,
this.buttonConfig,
this.ppcpConfig,
this.externalHandler
);
this.log = function () { this.log = function () {
if ( this.buttonConfig.is_debug ) { if ( this.buttonConfig.is_debug ) {
//console.log('[GooglePayButton]', ...arguments); //console.log('[GooglePayButton]', ...arguments);
@ -32,7 +31,7 @@ class GooglepayButton {
}; };
} }
init( config ) { init( config, transactionInfo ) {
if ( this.isInitialized ) { if ( this.isInitialized ) {
return; return;
} }
@ -47,6 +46,7 @@ class GooglepayButton {
} }
this.googlePayConfig = config; this.googlePayConfig = config;
this.transactionInfo = transactionInfo;
this.allowedPaymentMethods = config.allowedPaymentMethods; this.allowedPaymentMethods = config.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
@ -62,6 +62,39 @@ class GooglepayButton {
) )
.then( ( response ) => { .then( ( response ) => {
if ( response.result ) { if ( response.result ) {
if (
( this.context === 'checkout' ||
this.context === 'pay-now' ) &&
this.buttonConfig.is_wc_gateway_enabled === '1'
) {
const wrapper = document.getElementById(
'ppc-button-ppcp-googlepay'
);
if ( wrapper ) {
const { ppcpStyle, buttonStyle } =
this.contextConfig();
wrapper.classList.add(
`ppcp-button-${ ppcpStyle.shape }`,
'ppcp-button-apm',
'ppcp-button-googlepay'
);
if ( ppcpStyle.height ) {
wrapper.style.height = `${ ppcpStyle.height }px`;
}
this.addButtonCheckout(
this.baseCardPaymentMethod,
wrapper,
buttonStyle
);
return;
}
}
this.addButton( this.baseCardPaymentMethod ); this.addButton( this.baseCardPaymentMethod );
} }
} ) } )
@ -76,7 +109,7 @@ class GooglepayButton {
} }
this.isInitialized = false; this.isInitialized = false;
this.init( this.googlePayConfig ); this.init( this.googlePayConfig, this.transactionInfo );
} }
validateConfig() { validateConfig() {
@ -221,6 +254,19 @@ class GooglepayButton {
} ); } );
} }
addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) {
const button = this.paymentsClient.createButton( {
onClick: this.onButtonClick.bind( this ),
allowedPaymentMethods: [ baseCardPaymentMethod ],
buttonColor: buttonStyle.color || 'black',
buttonType: buttonStyle.type || 'pay',
buttonLocale: buttonStyle.language || 'en',
buttonSizeMode: 'fill',
} );
wrapper.appendChild( button );
}
waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) {
const startTime = Date.now(); const startTime = Date.now();
const interval = setInterval( () => { const interval = setInterval( () => {
@ -243,22 +289,39 @@ class GooglepayButton {
/** /**
* Show Google Pay payment sheet when Google Pay payment button is clicked * Show Google Pay payment sheet when Google Pay payment button is clicked
*/ */
async onButtonClick() { onButtonClick() {
this.log( 'onButtonClick', this.context ); this.log( 'onButtonClick', this.context );
const paymentDataRequest = await this.paymentDataRequest(); const initiatePaymentRequest = () => {
this.log( window.ppcpFundingSource = 'googlepay';
'onButtonClick: paymentDataRequest',
paymentDataRequest,
this.context
);
window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. const paymentDataRequest = this.paymentDataRequest();
this.paymentsClient.loadPaymentData( paymentDataRequest ); this.log(
'onButtonClick: paymentDataRequest',
paymentDataRequest,
this.context
);
this.paymentsClient.loadPaymentData( paymentDataRequest );
};
if ( 'function' === typeof this.contextHandler.validateForm ) {
// During regular checkout, validate the checkout form before initiating the payment.
this.contextHandler
.validateForm()
.then( initiatePaymentRequest, () => {
console.error(
'[GooglePayButton] Form validation failed.'
);
} );
} else {
// This is the flow on product page, cart, and other non-checkout pages.
initiatePaymentRequest();
}
} }
async paymentDataRequest() { paymentDataRequest() {
const baseRequest = { const baseRequest = {
apiVersion: 2, apiVersion: 2,
apiVersionMinor: 0, apiVersionMinor: 0,
@ -268,8 +331,7 @@ class GooglepayButton {
const paymentDataRequest = Object.assign( {}, baseRequest ); const paymentDataRequest = Object.assign( {}, baseRequest );
paymentDataRequest.allowedPaymentMethods = paymentDataRequest.allowedPaymentMethods =
googlePayConfig.allowedPaymentMethods; googlePayConfig.allowedPaymentMethods;
paymentDataRequest.transactionInfo = paymentDataRequest.transactionInfo = this.transactionInfo;
await this.contextHandler.transactionInfo();
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
if ( if (
@ -308,43 +370,51 @@ class GooglepayButton {
this.log( 'paymentData', paymentData ); this.log( 'paymentData', paymentData );
return new Promise( async ( resolve, reject ) => { return new Promise( async ( resolve, reject ) => {
const paymentDataRequestUpdate = {}; try {
const paymentDataRequestUpdate = {};
const updatedData = await new UpdatePaymentData( const updatedData = await new UpdatePaymentData(
this.buttonConfig.ajax.update_payment_data this.buttonConfig.ajax.update_payment_data
).update( paymentData ); ).update( paymentData );
const transactionInfo = await this.contextHandler.transactionInfo(); const transactionInfo = this.transactionInfo;
this.log( 'onPaymentDataChanged:updatedData', updatedData ); this.log( 'onPaymentDataChanged:updatedData', updatedData );
this.log( 'onPaymentDataChanged:transactionInfo', transactionInfo ); this.log(
'onPaymentDataChanged:transactionInfo',
transactionInfo
);
updatedData.country_code = transactionInfo.countryCode; updatedData.country_code = transactionInfo.countryCode;
updatedData.currency_code = transactionInfo.currencyCode; updatedData.currency_code = transactionInfo.currencyCode;
updatedData.total_str = transactionInfo.totalPrice; updatedData.total_str = transactionInfo.totalPrice;
// Handle unserviceable address.
if ( ! updatedData.shipping_options?.shippingOptions?.length ) {
paymentDataRequestUpdate.error =
this.unserviceableShippingAddressError();
resolve( paymentDataRequestUpdate );
return;
}
switch ( paymentData.callbackTrigger ) {
case 'INITIALIZE':
case 'SHIPPING_ADDRESS':
paymentDataRequestUpdate.newShippingOptionParameters =
updatedData.shipping_options;
paymentDataRequestUpdate.newTransactionInfo =
this.calculateNewTransactionInfo( updatedData );
break;
case 'SHIPPING_OPTION':
paymentDataRequestUpdate.newTransactionInfo =
this.calculateNewTransactionInfo( updatedData );
break;
}
// Handle unserviceable address.
if ( ! updatedData.shipping_options?.shippingOptions?.length ) {
paymentDataRequestUpdate.error =
this.unserviceableShippingAddressError();
resolve( paymentDataRequestUpdate ); resolve( paymentDataRequestUpdate );
return; } catch ( error ) {
console.error( 'Error during onPaymentDataChanged:', error );
reject( error );
} }
switch ( paymentData.callbackTrigger ) {
case 'INITIALIZE':
case 'SHIPPING_ADDRESS':
paymentDataRequestUpdate.newShippingOptionParameters =
updatedData.shipping_options;
paymentDataRequestUpdate.newTransactionInfo =
this.calculateNewTransactionInfo( updatedData );
break;
case 'SHIPPING_OPTION':
paymentDataRequestUpdate.newTransactionInfo =
this.calculateNewTransactionInfo( updatedData );
break;
}
resolve( paymentDataRequestUpdate );
} ); } );
} }

View file

@ -1,39 +1,76 @@
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
import GooglepayButton from './GooglepayButton'; import GooglepayButton from './GooglepayButton';
import ContextHandlerFactory from './Context/ContextHandlerFactory';
class GooglepayManager { class GooglepayManager {
constructor( buttonConfig, ppcpConfig ) { constructor( buttonConfig, ppcpConfig ) {
this.buttonConfig = buttonConfig; this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig; this.ppcpConfig = ppcpConfig;
this.googlePayConfig = null; this.googlePayConfig = null;
this.transactionInfo = null;
this.contextHandler = null;
this.buttons = []; this.buttons = [];
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => {
this.contextHandler = ContextHandlerFactory.create(
bootstrap.context,
buttonConfig,
ppcpConfig,
bootstrap.handler
);
const button = new GooglepayButton( const button = new GooglepayButton(
bootstrap.context, bootstrap.context,
bootstrap.handler, bootstrap.handler,
buttonConfig, buttonConfig,
ppcpConfig ppcpConfig,
this.contextHandler
); );
this.buttons.push( button ); this.buttons.push( button );
if ( this.googlePayConfig ) { // Initialize button only if googlePayConfig and transactionInfo are already fetched.
button.init( this.googlePayConfig ); if ( this.googlePayConfig && this.transactionInfo ) {
button.init( this.googlePayConfig, this.transactionInfo );
} else {
await this.init();
if ( this.googlePayConfig && this.transactionInfo ) {
button.init( this.googlePayConfig, this.transactionInfo );
}
} }
} ); } );
} }
init() { async init() {
( async () => { try {
// Gets GooglePay configuration of the PayPal merchant. if ( ! this.googlePayConfig ) {
this.googlePayConfig = await paypal.Googlepay().config(); // Gets GooglePay configuration of the PayPal merchant.
this.googlePayConfig = await paypal.Googlepay().config();
}
if ( ! this.transactionInfo ) {
this.transactionInfo = await this.fetchTransactionInfo();
}
for ( const button of this.buttons ) { for ( const button of this.buttons ) {
button.init( this.googlePayConfig ); button.init( this.googlePayConfig, this.transactionInfo );
} }
} )(); } catch ( error ) {
console.error( 'Error during initialization:', error );
}
}
async fetchTransactionInfo() {
try {
if ( ! this.contextHandler ) {
throw new Error( 'ContextHandler is not initialized' );
}
return await this.contextHandler.transactionInfo();
} catch ( error ) {
console.error( 'Error fetching transaction info:', error );
throw error;
}
} }
reinit() { reinit() {

View file

@ -1,6 +1,7 @@
import GooglepayButton from './GooglepayButton'; import GooglepayButton from './GooglepayButton';
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
import ContextHandlerFactory from './Context/ContextHandlerFactory';
/** /**
* Accessor that creates and returns a single PreviewButtonManager instance. * Accessor that creates and returns a single PreviewButtonManager instance.
@ -95,14 +96,22 @@ class GooglePayPreviewButton extends PreviewButton {
} }
createButton( buttonConfig ) { createButton( buttonConfig ) {
const contextHandler = ContextHandlerFactory.create(
'preview',
buttonConfig,
this.ppcpConfig,
null
);
const button = new GooglepayButton( const button = new GooglepayButton(
'preview', 'preview',
null, null,
buttonConfig, buttonConfig,
this.ppcpConfig this.ppcpConfig,
contextHandler
); );
button.init( this.apiConfig ); button.init( this.apiConfig, null );
} }
/** /**

View file

@ -25,23 +25,24 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array( return array(
// If GooglePay can be configured. // If GooglePay can be configured.
'googlepay.eligible' => static function ( ContainerInterface $container ): bool { 'googlepay.eligible' => static function ( ContainerInterface $container ): bool {
$apm_applies = $container->get( 'googlepay.helpers.apm-applies' ); $apm_applies = $container->get( 'googlepay.helpers.apm-applies' );
assert( $apm_applies instanceof ApmApplies ); assert( $apm_applies instanceof ApmApplies );
return $apm_applies->for_country_currency(); return $apm_applies->for_country() && $apm_applies->for_currency();
}, },
'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
return new ApmApplies( return new ApmApplies(
$container->get( 'googlepay.supported-country-currency-matrix' ), $container->get( 'googlepay.supported-countries' ),
$container->get( 'googlepay.supported-currencies' ),
$container->get( 'api.shop.currency' ), $container->get( 'api.shop.currency' ),
$container->get( 'api.shop.country' ) $container->get( 'api.shop.country' )
); );
}, },
// If GooglePay is configured and onboarded. // If GooglePay is configured and onboarded.
'googlepay.available' => static function ( ContainerInterface $container ): bool { 'googlepay.available' => static function ( ContainerInterface $container ): bool {
if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) { if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) {
$status = $container->get( 'googlepay.helpers.apm-product-status' ); $status = $container->get( 'googlepay.helpers.apm-product-status' );
assert( $status instanceof ApmProductStatus ); assert( $status instanceof ApmProductStatus );
@ -54,14 +55,14 @@ return array(
}, },
// We assume it's a referral if we can check product status without API request failures. // We assume it's a referral if we can check product status without API request failures.
'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool {
$status = $container->get( 'googlepay.helpers.apm-product-status' ); $status = $container->get( 'googlepay.helpers.apm-product-status' );
assert( $status instanceof ApmProductStatus ); assert( $status instanceof ApmProductStatus );
return ! $status->has_request_failure(); return ! $status->has_request_failure();
}, },
'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice {
return new AvailabilityNotice( return new AvailabilityNotice(
$container->get( 'googlepay.helpers.apm-product-status' ), $container->get( 'googlepay.helpers.apm-product-status' ),
$container->get( 'wcgateway.is-wc-gateways-list-page' ), $container->get( 'wcgateway.is-wc-gateways-list-page' ),
@ -69,7 +70,7 @@ return array(
); );
}, },
'googlepay.helpers.apm-product-status' => SingletonDecorator::make( 'googlepay.helpers.apm-product-status' => SingletonDecorator::make(
static function( ContainerInterface $container ): ApmProductStatus { static function( ContainerInterface $container ): ApmProductStatus {
return new ApmProductStatus( return new ApmProductStatus(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
@ -81,772 +82,93 @@ return array(
), ),
/** /**
* The matrix which countries and currency combinations can be used for GooglePay. * The list of which countries can be used for GooglePay.
*/ */
'googlepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { 'googlepay.supported-countries' => static function ( ContainerInterface $container ) : array {
/** /**
* Returns which countries and currency combinations can be used for GooglePay. * Returns which countries can be used for GooglePay.
*/ */
return apply_filters( return apply_filters(
'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix', 'woocommerce_paypal_payments_googlepay_supported_countries',
// phpcs:disable Squiz.Commenting.InlineComment
array( array(
'AU' => array( 'AU', // Australia
'AUD', 'AT', // Austria
'BRL', 'BE', // Belgium
'CAD', 'BG', // Bulgaria
'CHF', 'CA', // Canada
'CZK', 'CN', // China
'DKK', 'CY', // Cyprus
'EUR', 'CZ', // Czech Republic
'GBP', 'DK', // Denmark
'HKD', 'EE', // Estonia
'HUF', 'FI', // Finland
'ILS', 'FR', // France
'JPY', 'DE', // Germany
'MXN', 'GR', // Greece
'NOK', 'HU', // Hungary
'NZD', 'IE', // Ireland
'PHP', 'IT', // Italy
'PLN', 'LV', // Latvia
'SEK', 'LI', // Liechtenstein
'SGD', 'LT', // Lithuania
'THB', 'LU', // Luxembourg
'TWD', 'MT', // Malta
'USD', 'NL', // Netherlands
), 'NO', // Norway
'AT' => array( 'PL', // Poland
'AUD', 'PT', // Portugal
'BRL', 'RO', // Romania
'CAD', 'SK', // Slovakia
'CHF', 'SI', // Slovenia
'CZK', 'ES', // Spain
'DKK', 'SE', // Sweden
'EUR', 'US', // United States
'GBP', 'GB', // United Kingdom
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'BE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'BG' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CA' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CY' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'CZ' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'DK' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'EE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'FI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'FR' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'DE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'GR' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'HU' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'IE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'IT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LV' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'LU' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'MT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'NO' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'NL' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'PL' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'PT' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'RO' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SK' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SI' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'ES' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'SE' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'GB' => array(
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'MXN',
'NOK',
'NZD',
'PHP',
'PLN',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
),
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
) )
// phpcs:enable Squiz.Commenting.InlineComment
); );
}, },
'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface { /**
* The list of which currencies can be used for GooglePay.
*/
'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array {
/**
* Returns which currencies can be used for GooglePay.
*/
return apply_filters(
'woocommerce_paypal_payments_googlepay_supported_currencies',
// phpcs:disable Squiz.Commenting.InlineComment
array(
'AUD', // Australian Dollar
'BRL', // Brazilian Real
'CAD', // Canadian Dollar
'CHF', // Swiss Franc
'CZK', // Czech Koruna
'DKK', // Danish Krone
'EUR', // Euro
'GBP', // British Pound Sterling
'HKD', // Hong Kong Dollar
'HUF', // Hungarian Forint
'ILS', // Israeli New Shekel
'JPY', // Japanese Yen
'MXN', // Mexican Peso
'NOK', // Norwegian Krone
'NZD', // New Zealand Dollar
'PHP', // Philippine Peso
'PLN', // Polish Zloty
'SEK', // Swedish Krona
'SGD', // Singapore Dollar
'THB', // Thai Baht
'TWD', // New Taiwan Dollar
'USD', // United States Dollar
)
// phpcs:enable Squiz.Commenting.InlineComment
);
},
'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface {
return new Button( return new Button(
$container->get( 'googlepay.url' ), $container->get( 'googlepay.url' ),
$container->get( 'googlepay.sdk_url' ), $container->get( 'googlepay.sdk_url' ),
@ -860,7 +182,7 @@ return array(
); );
}, },
'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
return new BlocksPaymentMethod( return new BlocksPaymentMethod(
'ppcp-googlepay', 'ppcp-googlepay',
$container->get( 'googlepay.url' ), $container->get( 'googlepay.url' ),
@ -870,7 +192,7 @@ return array(
); );
}, },
'googlepay.url' => static function ( ContainerInterface $container ): string { 'googlepay.url' => static function ( ContainerInterface $container ): string {
$path = realpath( __FILE__ ); $path = realpath( __FILE__ );
if ( false === $path ) { if ( false === $path ) {
return ''; return '';
@ -881,26 +203,26 @@ return array(
); );
}, },
'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string {
return 'https://pay.google.com/gp/p/js/pay.js'; return 'https://pay.google.com/gp/p/js/pay.js';
}, },
'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint {
return new UpdatePaymentDataEndpoint( return new UpdatePaymentDataEndpoint(
$container->get( 'button.request-data' ), $container->get( 'button.request-data' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string {
return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY';
}, },
'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string {
return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY';
}, },
'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
if ( $state->current_state() < State::STATE_ONBOARDED ) { if ( $state->current_state() < State::STATE_ONBOARDED ) {
return ''; return '';
@ -938,5 +260,14 @@ return array(
esc_html( $button_text ) esc_html( $button_text )
); );
}, },
'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway {
return new GooglePayGateway(
$container->get( 'wcgateway.order-processor' ),
$container->get( 'api.factory.paypal-checkout-url' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'session.handler' ),
$container->get( 'googlepay.url' )
);
},
); );

View file

@ -13,7 +13,9 @@ use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WC_Countries; use WC_Countries;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
@ -25,6 +27,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/ */
class Button implements ButtonInterface { class Button implements ButtonInterface {
use ContextTrait;
/** /**
* The URL to the module. * The URL to the module.
* *
@ -421,19 +425,23 @@ class Button implements ButtonInterface {
$is_enabled = $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ); $is_enabled = $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' );
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$is_wc_gateway_enabled = isset( $available_gateways[ GooglePayGateway::ID ] );
return array( return array(
'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION',
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'is_enabled' => $is_enabled, 'is_enabled' => $is_enabled,
'sdk_url' => $this->sdk_url, 'is_wc_gateway_enabled' => $is_wc_gateway_enabled,
'button' => array( 'sdk_url' => $this->sdk_url,
'button' => array(
'wrapper' => '#ppc-button-googlepay-container', 'wrapper' => '#ppc-button-googlepay-container',
'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary. 'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary.
'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart', 'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart',
'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ),
), ),
'shipping' => $shipping, 'shipping' => $shipping,
'ajax' => array( 'ajax' => array(
'update_payment_data' => array( 'update_payment_data' => array(
'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ), 'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ),

View file

@ -0,0 +1,238 @@
<?php
/**
* The Google Pay Payment Gateway
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay;
use Exception;
use WC_Order;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
/**
* Class GooglePayGateway
*/
class GooglePayGateway extends WC_Payment_Gateway {
use ProcessPaymentTrait;
const ID = 'ppcp-googlepay';
/**
* The processor for orders.
*
* @var OrderProcessor
*/
protected $order_processor;
/**
* The function return the PayPal checkout URL for the given order ID.
*
* @var callable(string):string
*/
private $paypal_checkout_url_factory;
/**
* The Refund Processor.
*
* @var RefundProcessor
*/
private $refund_processor;
/**
* Service able to provide transaction url for an order.
*
* @var TransactionUrlProvider
*/
protected $transaction_url_provider;
/**
* The Session Handler.
*
* @var SessionHandler
*/
protected $session_handler;
/**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* GooglePayGateway constructor.
*
* @param OrderProcessor $order_processor The Order Processor.
* @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SessionHandler $session_handler The Session Handler.
* @param string $module_url The URL to the module.
*/
public function __construct(
OrderProcessor $order_processor,
callable $paypal_checkout_url_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider,
SessionHandler $session_handler,
string $module_url
) {
$this->id = self::ID;
$this->method_title = __( 'Google Pay (via PayPal) ', 'woocommerce-paypal-payments' );
$this->method_description = __( 'The separate payment gateway with the Google Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' );
$this->title = $this->get_option( 'title', __( 'Google Pay', 'woocommerce-paypal-payments' ) );
$this->description = $this->get_option( 'description', '' );
$this->module_url = $module_url;
$this->icon = esc_url( $this->module_url ) . 'assets/images/googlepay.png';
$this->init_form_fields();
$this->init_settings();
$this->order_processor = $order_processor;
$this->paypal_checkout_url_factory = $paypal_checkout_url_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->session_handler = $session_handler;
add_action(
'woocommerce_update_options_payment_gateways_' . $this->id,
array(
$this,
'process_admin_options',
)
);
}
/**
* Initialize the form fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'Google Pay', 'woocommerce-paypal-payments' ),
'default' => 'no',
'desc_tip' => true,
'description' => __( 'Enable/Disable Google Pay payment gateway.', 'woocommerce-paypal-payments' ),
),
'title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => $this->title,
'desc_tip' => true,
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ),
),
'description' => array(
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => $this->description,
'desc_tip' => true,
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ),
),
);
}
/**
* Process payment for a WooCommerce order.
*
* @param int $order_id The WooCommerce order id.
*
* @return array
*/
public function process_payment( $order_id ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $this->handle_payment_failure(
null,
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
);
}
/**
* If the WC_Order is paid through the approved webhook.
*/
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return $this->handle_payment_success( $wc_order );
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order );
try {
try {
$this->order_processor->process( $wc_order );
do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalOrderMissingException $exc ) {
$order = $this->order_processor->create_order( $wc_order );
return array(
'result' => 'success',
'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ),
);
}
} catch ( PayPalApiException $error ) {
return $this->handle_payment_failure(
$wc_order,
new Exception(
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
$error->getCode(),
$error
)
);
} catch ( Exception $error ) {
return $this->handle_payment_failure( $wc_order, $error );
}
}
/**
* Process refund.
*
* If the gateway declares 'refunds' support, this will allow it to refund.
* a passed in amount.
*
* @param int $order_id Order ID.
* @param float $amount Refund amount.
* @param string $reason Refund reason.
* @return boolean True or false based on success, or a WP_Error object.
*/
public function process_refund( $order_id, $amount = null, $reason = '' ) {
$order = wc_get_order( $order_id );
if ( ! is_a( $order, \WC_Order::class ) ) {
return false;
}
return $this->refund_processor->process( $order, (float) $amount, (string) $reason );
}
/**
* Return transaction url for this gateway and given order.
*
* @param \WC_Order $order WC order to get transaction url by.
*
* @return string
*/
public function get_transaction_url( $order ): string {
$this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order );
return parent::get_transaction_url( $order );
}
}

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay; namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
@ -159,6 +160,46 @@ class GooglepayModule implements ModuleInterface {
}, },
1 1
); );
add_filter(
'woocommerce_payment_gateways',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $methods ) use ( $c ): array {
if ( ! is_array( $methods ) ) {
return $methods;
}
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $settings->has( 'googlepay_button_enabled' ) && $settings->get( 'googlepay_button_enabled' ) ) {
$googlepay_gateway = $c->get( 'googlepay.wc-gateway' );
assert( $googlepay_gateway instanceof WC_Payment_Gateway );
$methods[] = $googlepay_gateway;
}
return $methods;
}
);
add_action(
'woocommerce_review_order_after_submit',
function () {
echo '<div id="ppc-button-' . esc_attr( GooglePayGateway::ID ) . '"></div>';
}
);
add_action(
'woocommerce_pay_order_after_submit',
function () {
echo '<div id="ppc-button-' . esc_attr( GooglePayGateway::ID ) . '"></div>';
}
);
} }
/** /**

View file

@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
class ApmApplies { class ApmApplies {
/** /**
* The matrix which countries and currency combinations can be used for GooglePay. * The list of which countries can be used for GooglePay.
* *
* @var array * @var array
*/ */
private $allowed_country_currency_matrix; private $allowed_countries;
/**
* The list of which currencies can be used for GooglePay.
*
* @var array
*/
private $allowed_currencies;
/** /**
* 3-letter currency code of the shop. * 3-letter currency code of the shop.
@ -39,30 +46,39 @@ class ApmApplies {
/** /**
* DccApplies constructor. * DccApplies constructor.
* *
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for GooglePay. * @param array $allowed_countries The list of which countries can be used for GooglePay.
* @param array $allowed_currencies The list of which currencies can be used for GooglePay.
* @param string $currency 3-letter currency code of the shop. * @param string $currency 3-letter currency code of the shop.
* @param string $country 2-letter country code of the shop. * @param string $country 2-letter country code of the shop.
*/ */
public function __construct( public function __construct(
array $allowed_country_currency_matrix, array $allowed_countries,
array $allowed_currencies,
string $currency, string $currency,
string $country string $country
) { ) {
$this->allowed_country_currency_matrix = $allowed_country_currency_matrix; $this->allowed_countries = $allowed_countries;
$this->currency = $currency; $this->allowed_currencies = $allowed_currencies;
$this->country = $country; $this->currency = $currency;
$this->country = $country;
} }
/** /**
* Returns whether GooglePay can be used in the current country and the current currency used. * Returns whether GooglePay can be used in the current country used.
* *
* @return bool * @return bool
*/ */
public function for_country_currency(): bool { public function for_country(): bool {
if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { return in_array( $this->country, $this->allowed_countries, true );
return false; }
}
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); /**
* Returns whether GooglePay can be used in the current currency used.
*
* @return bool
*/
public function for_currency(): bool {
return in_array( $this->currency, $this->allowed_currencies, true );
} }
} }

View file

@ -189,9 +189,8 @@ return array(
$login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' ); $login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' );
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' ); $partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$cache = new Cache( 'ppcp-paypal-bearer' ); $cache = new Cache( 'ppcp-paypal-bearer' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new LoginSellerEndpoint( return new LoginSellerEndpoint(
$request_data, $request_data,
$login_seller_production, $login_seller_production,
@ -199,7 +198,8 @@ return array(
$partner_referrals_data, $partner_referrals_data,
$settings, $settings,
$cache, $cache,
$logger $logger,
new Cache( 'ppcp-client-credentials-cache' )
); );
}, },
'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint {

View file

@ -96,21 +96,21 @@ class OnboardingAssets {
*/ */
public function register(): bool { public function register(): bool {
$url = untrailingslashit( $this->module_url ) . '/assets/css/onboarding.css';
wp_register_style( wp_register_style(
'ppcp-onboarding', 'ppcp-onboarding',
$url, $this->module_url . '/assets/css/onboarding.css',
array(), array(),
$this->version $this->version
); );
$url = untrailingslashit( $this->module_url ) . '/assets/js/settings.js';
wp_register_script( wp_register_script(
'ppcp-settings', 'ppcp-settings',
$url, $this->module_url . '/assets/js/settings.js',
array(), array(),
$this->version, $this->version,
true true
); );
wp_localize_script( wp_localize_script(
'ppcp-settings', 'ppcp-settings',
'PayPalCommerceSettings', 'PayPalCommerceSettings',
@ -122,14 +122,14 @@ class OnboardingAssets {
) )
); );
$url = untrailingslashit( $this->module_url ) . '/assets/js/onboarding.js';
wp_register_script( wp_register_script(
'ppcp-onboarding', 'ppcp-onboarding',
$url, $this->module_url . '/assets/js/onboarding.js',
array( 'jquery' ), array( 'jquery' ),
$this->version, $this->version,
true true
); );
wp_localize_script( wp_localize_script(
'ppcp-onboarding', 'ppcp-onboarding',
'PayPalCommerceGatewayOnboarding', 'PayPalCommerceGatewayOnboarding',
@ -164,17 +164,22 @@ class OnboardingAssets {
/** /**
* Enqueues the necessary scripts. * Enqueues the necessary scripts.
* *
* @return bool * @return void
*/ */
public function enqueue(): bool { public function enqueue(): void {
wp_enqueue_style( 'ppcp-onboarding' ); // Do not enqueue anything when we are not on a PayPal Payments settings tab.
wp_enqueue_script( 'ppcp-settings' ); if ( ! $this->page_id ) {
if ( ! $this->should_render_onboarding_script() ) { return;
return false;
} }
wp_enqueue_script( 'ppcp-onboarding' ); // Enqueue general assets for the plugin's settings page.
return true; wp_enqueue_script( 'ppcp-settings' );
wp_enqueue_style( 'ppcp-onboarding' ); // File also contains general settings styles.
// Conditionally enqueue the onboarding script, when needed.
if ( $this->should_render_onboarding_script() ) {
wp_enqueue_script( 'ppcp-onboarding' );
}
} }
/** /**

View file

@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\Onboarding\Endpoint;
use Exception; use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData; use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
@ -76,6 +78,13 @@ class LoginSellerEndpoint implements EndpointInterface {
*/ */
protected $logger; protected $logger;
/**
* The client credentials cache.
*
* @var Cache
*/
private $client_credentials_cache;
/** /**
* LoginSellerEndpoint constructor. * LoginSellerEndpoint constructor.
* *
@ -86,6 +95,7 @@ class LoginSellerEndpoint implements EndpointInterface {
* @param Settings $settings The Settings. * @param Settings $settings The Settings.
* @param Cache $cache The Cache. * @param Cache $cache The Cache.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param Cache $client_credentials_cache The client credentials cache.
*/ */
public function __construct( public function __construct(
RequestData $request_data, RequestData $request_data,
@ -94,16 +104,18 @@ class LoginSellerEndpoint implements EndpointInterface {
PartnerReferralsData $partner_referrals_data, PartnerReferralsData $partner_referrals_data,
Settings $settings, Settings $settings,
Cache $cache, Cache $cache,
LoggerInterface $logger LoggerInterface $logger,
Cache $client_credentials_cache
) { ) {
$this->request_data = $request_data; $this->request_data = $request_data;
$this->login_seller_production = $login_seller_production; $this->login_seller_production = $login_seller_production;
$this->login_seller_sandbox = $login_seller_sandbox; $this->login_seller_sandbox = $login_seller_sandbox;
$this->partner_referrals_data = $partner_referrals_data; $this->partner_referrals_data = $partner_referrals_data;
$this->settings = $settings; $this->settings = $settings;
$this->cache = $cache; $this->cache = $cache;
$this->logger = $logger; $this->logger = $logger;
$this->client_credentials_cache = $client_credentials_cache;
} }
/** /**
@ -175,6 +187,9 @@ class LoginSellerEndpoint implements EndpointInterface {
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY ); $this->cache->delete( PayPalBearer::CACHE_KEY );
} }
if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) {
$this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY );
}
wp_schedule_single_event( wp_schedule_single_event(
time() + 5, time() + 5,

View file

@ -31,6 +31,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod; use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/** /**
* Class SavePaymentMethodsModule * Class SavePaymentMethodsModule
@ -84,7 +85,9 @@ class SavePaymentMethodsModule implements ModuleInterface {
add_filter( add_filter(
'woocommerce_paypal_payments_localized_script_data', 'woocommerce_paypal_payments_localized_script_data',
function( array $localized_script_data ) use ( $c ) { function( array $localized_script_data ) use ( $c ) {
if ( ! is_user_logged_in() ) { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper );
if ( ! is_user_logged_in() && ! $subscriptions_helper->cart_contains_subscription() ) {
return $localized_script_data; return $localized_script_data;
} }

View file

@ -188,10 +188,6 @@ document.addEventListener( 'DOMContentLoaded', () => {
} }
function shouldDisableCardButton() { function shouldDisableCardButton() {
if ( currentTabId() === 'ppcp-card-button-gateway' ) {
return false;
}
return ( return (
PayPalCommerceGatewaySettings.is_acdc_enabled || PayPalCommerceGatewaySettings.is_acdc_enabled ||
jQuery( '#ppcp-allow_card_button_gateway' ).is( ':checked' ) jQuery( '#ppcp-allow_card_button_gateway' ).is( ':checked' )
@ -230,6 +226,14 @@ document.addEventListener( 'DOMContentLoaded', () => {
} }
if ( shouldDisableCardButton() ) { if ( shouldDisableCardButton() ) {
const standardCardButtonInput = document.querySelector(
'#woocommerce_ppcp-card-button-gateway_enabled'
);
if ( standardCardButtonInput ) {
standardCardButtonInput.disabled = true;
}
disabledSources = disabledSources.concat( 'card' ); disabledSources = disabledSources.concat( 'card' );
} }

View file

@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers; use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
@ -197,6 +198,7 @@ return array(
OXXOGateway::ID, OXXOGateway::ID,
Settings::PAY_LATER_TAB_ID, Settings::PAY_LATER_TAB_ID,
AxoGateway::ID, AxoGateway::ID,
GooglePayGateway::ID,
), ),
true true
); );
@ -218,6 +220,7 @@ return array(
CardButtonGateway::ID, CardButtonGateway::ID,
Settings::PAY_LATER_TAB_ID, Settings::PAY_LATER_TAB_ID,
Settings::CONNECTION_TAB_ID, Settings::CONNECTION_TAB_ID,
GooglePayGateway::ID,
), ),
true true
); );
@ -364,7 +367,8 @@ return array(
$container->get( 'api.partner_merchant_id-production' ), $container->get( 'api.partner_merchant_id-production' ),
$container->get( 'api.partner_merchant_id-sandbox' ), $container->get( 'api.partner_merchant_id-sandbox' ),
$container->get( 'api.endpoint.billing-agreements' ), $container->get( 'api.endpoint.billing-agreements' ),
$logger $logger,
new Cache( 'ppcp-client-credentials-cache' )
); );
}, },
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {

View file

@ -106,6 +106,12 @@ class DisableGateways {
return $methods; return $methods;
} }
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) );
if ( $payment_method && is_string( $payment_method ) ) {
return array( $payment_method => $methods[ $payment_method ] );
}
return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] ); return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] );
} }

View file

@ -19,9 +19,10 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
* Creates the admin message about the gateway being enabled without the PayPal gateway. * Creates the admin message about the gateway being enabled without the PayPal gateway.
*/ */
class GatewayWithoutPayPalAdminNotice { class GatewayWithoutPayPalAdminNotice {
private const NOTICE_OK = ''; private const NOTICE_OK = '';
private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway'; private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway';
private const NOTICE_DISABLED_LOCATION = 'disabled_location'; private const NOTICE_DISABLED_LOCATION = 'disabled_location';
private const NOTICE_DISABLED_CARD_BUTTON = 'disabled_card';
/** /**
* The gateway ID. * The gateway ID.
@ -99,6 +100,9 @@ class GatewayWithoutPayPalAdminNotice {
public function message(): ?Message { public function message(): ?Message {
$notice_type = $this->check(); $notice_type = $this->check();
$url1 = '';
$url2 = '';
switch ( $notice_type ) { switch ( $notice_type ) {
case self::NOTICE_DISABLED_GATEWAY: case self::NOTICE_DISABLED_GATEWAY:
/* translators: %1$s the gateway name, %2$s URL. */ /* translators: %1$s the gateway name, %2$s URL. */
@ -114,6 +118,15 @@ class GatewayWithoutPayPalAdminNotice {
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
); );
break; break;
case self::NOTICE_DISABLED_CARD_BUTTON:
/* translators: %1$s Standard Card Button section URL, %2$s Advanced Card Processing section URL. */
$text = __(
'The <a href="%1$s">Standard Card Button</a> cannot be used while <a href="%2$s">Advanced Card Processing</a> is enabled.',
'woocommerce-paypal-payments'
);
$url1 = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-card-button-gateway' );
$url2 = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-credit-card-gateway' );
break;
default: default:
return null; return null;
} }
@ -130,6 +143,15 @@ class GatewayWithoutPayPalAdminNotice {
$name, $name,
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ) admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' )
); );
if ( $notice_type === self::NOTICE_DISABLED_CARD_BUTTON ) {
$message = sprintf(
$text,
$url1,
$url2
);
}
return new Message( $message, 'warning' ); return new Message( $message, 'warning' );
} }
@ -160,6 +182,13 @@ class GatewayWithoutPayPalAdminNotice {
return self::NOTICE_DISABLED_LOCATION; return self::NOTICE_DISABLED_LOCATION;
} }
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ?? false;
$is_card_button_allowed = $this->settings->has( 'allow_card_button_gateway' ) && $this->settings->get( 'allow_card_button_gateway' );
if ( $is_dcc_enabled && $is_card_button_allowed ) {
return self::NOTICE_DISABLED_CARD_BUTTON;
}
return self::NOTICE_OK; return self::NOTICE_OK;
} }

View file

@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
@ -164,6 +166,13 @@ class SettingsListener {
*/ */
private $logger; private $logger;
/**
* The client credentials cache.
*
* @var Cache
*/
private $client_credentials_cache;
/** /**
* SettingsListener constructor. * SettingsListener constructor.
* *
@ -183,6 +192,7 @@ class SettingsListener {
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox. * @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint. * @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
* @param ?LoggerInterface $logger The logger. * @param ?LoggerInterface $logger The logger.
* @param Cache $client_credentials_cache The client credentials cache.
*/ */
public function __construct( public function __construct(
Settings $settings, Settings $settings,
@ -200,7 +210,8 @@ class SettingsListener {
string $partner_merchant_id_production, string $partner_merchant_id_production,
string $partner_merchant_id_sandbox, string $partner_merchant_id_sandbox,
BillingAgreementsEndpoint $billing_agreements_endpoint, BillingAgreementsEndpoint $billing_agreements_endpoint,
LoggerInterface $logger = null LoggerInterface $logger = null,
Cache $client_credentials_cache
) { ) {
$this->settings = $settings; $this->settings = $settings;
@ -219,6 +230,7 @@ class SettingsListener {
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox; $this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
$this->billing_agreements_endpoint = $billing_agreements_endpoint; $this->billing_agreements_endpoint = $billing_agreements_endpoint;
$this->logger = $logger ?: new NullLogger(); $this->logger = $logger ?: new NullLogger();
$this->client_credentials_cache = $client_credentials_cache;
} }
/** /**
@ -490,6 +502,9 @@ class SettingsListener {
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY ); $this->cache->delete( PayPalBearer::CACHE_KEY );
} }
if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) {
$this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY );
}
if ( $this->pui_status_cache->has( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ) ) { if ( $this->pui_status_cache->has( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ) ) {
$this->pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ); $this->pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY );

View file

@ -528,6 +528,14 @@ class WCGatewayModule implements ModuleInterface {
return $methods; return $methods;
} }
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false;
$standard_card_button = get_option( 'woocommerce_ppcp-card-button-gateway_settings' );
if ( $is_dcc_enabled && isset( $standard_card_button['enabled'] ) ) {
$standard_card_button['enabled'] = 'no';
update_option( 'woocommerce_ppcp-card-button-gateway_settings', $standard_card_button );
}
$dcc_applies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
assert( $dcc_applies instanceof DccApplies ); assert( $dcc_applies instanceof DccApplies );

View file

@ -416,7 +416,9 @@ class WcSubscriptionsModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' ); $settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings ); assert( $settings instanceof Settings );
if ( $subscriptions_helper->plugin_is_active() ) { $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) {
$supports = array( $supports = array(
'subscriptions', 'subscriptions',
'subscription_cancellation', 'subscription_cancellation',
@ -442,7 +444,12 @@ class WcSubscriptionsModule implements ModuleInterface {
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); $subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper ); assert( $subscriptions_helper instanceof SubscriptionHelper );
if ( $subscriptions_helper->plugin_is_active() ) { $settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$vaulting_enabled = $settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' );
if ( $vaulting_enabled && $subscriptions_helper->plugin_is_active() ) {
$supports = array( $supports = array(
'subscriptions', 'subscriptions',
'subscription_cancellation', 'subscription_cancellation',
@ -467,7 +474,12 @@ class WcSubscriptionsModule implements ModuleInterface {
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); $subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscriptions_helper instanceof SubscriptionHelper ); assert( $subscriptions_helper instanceof SubscriptionHelper );
if ( $subscriptions_helper->plugin_is_active() ) { $settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) {
$supports = array( $supports = array(
'subscriptions', 'subscriptions',
'subscription_cancellation', 'subscription_cancellation',

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks; namespace WooCommerce\PayPalCommerce\Webhooks;
use WC_Order;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;

View file

@ -1,6 +1,6 @@
{ {
"name": "woocommerce-paypal-payments", "name": "woocommerce-paypal-payments",
"version": "2.8.2", "version": "2.8.3",
"description": "WooCommerce PayPal Payments", "description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0", "license": "GPL-2.0",

View file

@ -1,10 +1,10 @@
=== WooCommerce PayPal Payments === === WooCommerce PayPal Payments ===
Contributors: woocommerce, automattic, inpsyde Contributors: woocommerce, automattic, syde
Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay
Requires at least: 5.3 Requires at least: 5.3
Tested up to: 6.6 Tested up to: 6.6
Requires PHP: 7.2 Requires PHP: 7.2
Stable tag: 2.8.2 Stable tag: 2.8.3
License: GPLv2 License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -179,7 +179,24 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog == == Changelog ==
= 2.8.2 - xxxx-xx-xx = = 2.8.3 - 2024-08-12 =
* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474
* Fix - Do not add tax info into order meta during order creation #2471
* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425
* Fix - PayPal js files loaded on non PayPal pages #2411
* Fix - Google Pay: Fix the incorrect popup triggering #2414
* Fix - Add tax configurator when programmatically creating WC orders #2431
* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402
* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334
* Fix - Prevent displaying smart button multiple times on variable product page #2420
* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404
* Fix - Use client credentials for user tokens #2491
* Fix - Apple Pay: Fix the shipping callback #2492
* Enhancement - Separate Google Pay button for Classic Checkout #2430
* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468
* Enhancement - Add AMEX support for Advanced Card Processing in China #2469
= 2.8.2 - 2024-07-22 =
* Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415
* Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400
* Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 * Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403

View file

@ -43,6 +43,7 @@ class SettingsListenerTest extends ModularTestCase
$billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class); $billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class);
$logger = Mockery::mock(LoggerInterface::class); $logger = Mockery::mock(LoggerInterface::class);
$client_credentials_cache = Mockery::mock(Cache::class);
$testee = new SettingsListener( $testee = new SettingsListener(
$settings, $settings,
@ -60,7 +61,8 @@ class SettingsListenerTest extends ModularTestCase
'', '',
'', '',
$billing_agreement_endpoint, $billing_agreement_endpoint,
$logger $logger,
$client_credentials_cache
); );
$_GET['section'] = PayPalGateway::ID; $_GET['section'] = PayPalGateway::ID;
@ -94,6 +96,9 @@ class SettingsListenerTest extends ModularTestCase
->andReturn(false); ->andReturn(false);
$dcc_status_cache->shouldReceive('has') $dcc_status_cache->shouldReceive('has')
->andReturn(false); ->andReturn(false);
$client_credentials_cache->shouldReceive('has')->andReturn(true);
$client_credentials_cache->shouldReceive('delete');
$testee->listen(); $testee->listen();
} }

View file

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce PayPal Payments * Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/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. * 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.8.2 * Version: 2.8.3
* Author: WooCommerce * Author: WooCommerce
* Author URI: https://woocommerce.com/ * Author URI: https://woocommerce.com/
* License: GPL-2.0 * License: GPL-2.0
@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' );
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2024-07-17' ); define( 'PAYPAL_INTEGRATION_DATE', '2024-08-07' );
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
@ -223,6 +223,22 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
} }
); );
add_action(
'in_plugin_update_message-woocommerce-paypal-payments/woocommerce-paypal-payments.php',
static function( array $plugin_data, \stdClass $new_data ) {
if ( version_compare( $plugin_data['Version'], '3.0.0', '<' ) &&
version_compare( $new_data->new_version, '3.0.0', '>=' ) ) {
printf(
'<br /><strong>%s</strong>: %s',
esc_html__( 'Warning', 'woocommerce-paypal-payments' ),
esc_html__( 'WooCommerce PayPal Payments version 3.0.0 contains significant changes that may impact your website. We strongly recommend reviewing the changes and testing the update on a staging site before updating it on your production environment.', 'woocommerce-paypal-payments' )
);
}
},
10,
2
);
/** /**
* Check if WooCommerce is active. * Check if WooCommerce is active.
* *