mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-4110-incorrect-subscription-cancellation-handling-with-pay-pal-subscriptions
This commit is contained in:
commit
65e1ca4eb0
259 changed files with 9482 additions and 3476 deletions
40
.env.integration
Normal file
40
.env.integration
Normal file
|
@ -0,0 +1,40 @@
|
|||
PPCP_INTEGRATION_WP_DIR=${ROOT_DIR}/.ddev/wordpress
|
||||
|
||||
BASEURL="https://woocommerce-paypal-payments.ddev.site"
|
||||
AUTHORIZATION="Bearer ABC123"
|
||||
|
||||
CHECKOUT_URL="/checkout"
|
||||
CHECKOUT_PAGE_ID=7
|
||||
CART_URL="/cart"
|
||||
BLOCK_CHECKOUT_URL="/checkout-block"
|
||||
BLOCK_CHECKOUT_PAGE_ID=22
|
||||
BLOCK_CART_URL="/cart-block"
|
||||
|
||||
PRODUCT_URL="/product/prod"
|
||||
PRODUCT_ID=123
|
||||
|
||||
SUBSCRIPTION_URL="/product/sub"
|
||||
|
||||
PAYPAL_SUBSCRIPTIONS_PRODUCT_ID=252
|
||||
|
||||
APM_ID="sofort"
|
||||
|
||||
WP_MERCHANT_USER="admin"
|
||||
WP_MERCHANT_PASSWORD="admin"
|
||||
|
||||
WP_CUSTOMER_USER="customer"
|
||||
WP_CUSTOMER_PASSWORD="password"
|
||||
|
||||
CUSTOMER_EMAIL="customer@example.com"
|
||||
CUSTOMER_PASSWORD="password"
|
||||
CUSTOMER_FIRST_NAME="John"
|
||||
CUSTOMER_LAST_NAME="Doe"
|
||||
CUSTOMER_COUNTRY="DE"
|
||||
CUSTOMER_ADDRESS="street 1"
|
||||
CUSTOMER_POSTCODE="12345"
|
||||
CUSTOMER_CITY="city"
|
||||
CUSTOMER_PHONE="1234567890"
|
||||
|
||||
CREDIT_CARD_NUMBER="1234567890"
|
||||
CREDIT_CARD_EXPIRATION="01/2042"
|
||||
CREDIT_CARD_CVV="123"
|
|
@ -1,4 +1,4 @@
|
|||
PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress
|
||||
PPCP_INTEGRATION_WP_DIR=${ROOT_DIR}/.ddev/wordpress
|
||||
|
||||
BASEURL="https://woocommerce-paypal-payments.ddev.site"
|
||||
AUTHORIZATION="Bearer ABC123"
|
|
@ -1,4 +1,4 @@
|
|||
name: e2e tests
|
||||
name: Integration tests
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
|
@ -7,8 +7,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.2']
|
||||
wc-versions: ['6.9.4', '7.7.2']
|
||||
php-versions: ['7.4']
|
||||
wc-versions: ['9.7.1']
|
||||
|
||||
name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }}
|
||||
steps:
|
||||
|
@ -30,10 +30,10 @@ jobs:
|
|||
run: ddev orchestrate -f
|
||||
|
||||
- name: Create config
|
||||
run: cp -n .env.e2e.example .env.e2e
|
||||
run: cp -n .env.integration.example .env.integration
|
||||
|
||||
- name: Setup tests
|
||||
run: ddev php tests/e2e/PHPUnit/setup.php
|
||||
run: ddev php tests/integration/PHPUnit/setup.php
|
||||
|
||||
- name: Run PHPUnit
|
||||
run: ddev exec phpunit -c tests/e2e/phpunit.xml.dist
|
||||
run: ddev exec phpunit -c tests/integration/phpunit.xml.dist
|
|
@ -94,6 +94,45 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group =
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of times a filter has been applied during the current request.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @global int[] $wp_filters Stores the number of times each filter was triggered.
|
||||
*
|
||||
* @param string $hook_name The name of the filter hook.
|
||||
* @return int The number of times the filter hook has been applied.
|
||||
*/
|
||||
function did_filter( $hook_name ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a filter hook is currently being processed.
|
||||
*
|
||||
* The function current_filter() only returns the most recent filter being executed.
|
||||
* did_filter() returns the number of times a filter has been applied during
|
||||
* the current request.
|
||||
*
|
||||
* This function allows detection for any filter currently being executed
|
||||
* (regardless of whether it's the most recent filter to fire, in the case of
|
||||
* hooks called from hook callbacks) to be verified.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*
|
||||
* @see current_filter()
|
||||
* @see did_filter()
|
||||
* @global string[] $wp_current_filter Current filter.
|
||||
*
|
||||
* @param string|null $hook_name Optional. Filter hook to check. Defaults to null,
|
||||
* which checks if any filter is currently being run.
|
||||
* @return bool Whether the filter is currently in the stack.
|
||||
*/
|
||||
function doing_filter( $hook_name = null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML API: WP_HTML_Tag_Processor class
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 3.0.0 - 2025-03-17 =
|
||||
* Enhancement - Redesigned settings UI for new users #2908
|
||||
* Enhancement - Enable Fastlane by default on new store setups when eligible #3199
|
||||
* Enhancement - Enable support for advanced card payments and features for Hong Kong & Singapore #3089
|
||||
* Fix - Dependency conflict with more recent psr/log versions on PHP8+ #2993
|
||||
* Fix - PayPal Checkout Gateway subscription migration layer not renewing subscriptions #2699
|
||||
* Fix - Fatal error when gateway settings initialized too early by third-party plugin #2766
|
||||
* Fix - Next Payment date for Subscriptions not updating when processing a PayPal Subscriptions renewal order #2959
|
||||
* Fix - Changing the subscription payment method to ACDC triggers error #2891
|
||||
* Fix - Standard Card button not appearing in standalone gateway for free trial subscription products #2935
|
||||
* Fix - Validation error when using Trustly payment method #3031
|
||||
* Fix - Error in continuation mode due to wrong gateway selection on Checkout block #2996
|
||||
* Fix - Error in error in PayLaterConfigurator #2989
|
||||
* Tweak - Removed currency requirement for Vault v3 #2919
|
||||
* Tweak - Update plugin author from WooCommerce to PayPal
|
||||
|
||||
= 2.9.6 - 2025-01-06 =
|
||||
* Fix - NOT_ENABLED_TO_VAULT_PAYMENT_SOURCE on PayPal transactions when using ACDC Vaulting without PayPal Vault approval #2955
|
||||
* Fix - Express buttons for Free Trial Subscription products on Block Cart/Checkout trigger CANNOT_BE_ZERO_OR_NEGATIVE error #2872
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"WooCommerce\\PayPalCommerce\\": "tests/PHPUnit/",
|
||||
"WooCommerce\\PayPalCommerce\\Tests\\E2e\\": "tests/e2e/PHPUnit/"
|
||||
"WooCommerce\\PayPalCommerce\\Tests\\Integration\\": "tests/integration/PHPUnit/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
|
|
|
@ -91,9 +91,12 @@ return function ( string $root_dir ): iterable {
|
|||
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
|
||||
}
|
||||
|
||||
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
|
||||
$preview_new_ux = '1' === getenv( 'PCP_SETTINGS_ENABLED' );
|
||||
|
||||
if ( apply_filters(
|
||||
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
|
||||
getenv( 'PCP_SETTINGS_ENABLED' ) === '1'
|
||||
$show_new_ux || $preview_new_ux
|
||||
) ) {
|
||||
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
|
||||
}
|
||||
|
|
|
@ -80,12 +80,11 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
|
||||
return array(
|
||||
'api.host' => static function( ContainerInterface $container ) : string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
|
||||
if ( $environment->is_sandbox() ) {
|
||||
|
@ -671,6 +670,7 @@ return array(
|
|||
'FR' => $default_currencies,
|
||||
'DE' => $default_currencies,
|
||||
'GR' => $default_currencies,
|
||||
'HK' => $default_currencies,
|
||||
'HU' => $default_currencies,
|
||||
'IE' => $default_currencies,
|
||||
'IT' => $default_currencies,
|
||||
|
@ -688,6 +688,7 @@ return array(
|
|||
'PT' => $default_currencies,
|
||||
'RO' => $default_currencies,
|
||||
'SK' => $default_currencies,
|
||||
'SG' => $default_currencies,
|
||||
'SI' => $default_currencies,
|
||||
'ES' => $default_currencies,
|
||||
'SE' => $default_currencies,
|
||||
|
@ -736,6 +737,7 @@ return array(
|
|||
'FR' => $mastercard_visa_amex,
|
||||
'GB' => $mastercard_visa_amex,
|
||||
'GR' => $mastercard_visa_amex,
|
||||
'HK' => $mastercard_visa_amex,
|
||||
'HU' => $mastercard_visa_amex,
|
||||
'IE' => $mastercard_visa_amex,
|
||||
'IT' => $mastercard_visa_amex,
|
||||
|
@ -765,6 +767,7 @@ return array(
|
|||
'SE' => $mastercard_visa_amex,
|
||||
'SI' => $mastercard_visa_amex,
|
||||
'SK' => $mastercard_visa_amex,
|
||||
'SG' => $mastercard_visa_amex,
|
||||
'JP' => array(
|
||||
'mastercard' => array(),
|
||||
'visa' => array(),
|
||||
|
|
|
@ -160,7 +160,7 @@ class PartnersEndpoint {
|
|||
|
||||
$this->failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY );
|
||||
|
||||
$status = $this->seller_status_factory->from_paypal_reponse( $json );
|
||||
$status = $this->seller_status_factory->from_paypal_response( $json );
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,23 @@ class SellerStatus {
|
|||
*/
|
||||
private $capabilities;
|
||||
|
||||
/**
|
||||
* Merchant country on PayPal.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $country;
|
||||
|
||||
/**
|
||||
* SellerStatus constructor.
|
||||
*
|
||||
* @param SellerStatusProduct[] $products The products.
|
||||
* @param SellerStatusCapability[] $capabilities The capabilities.
|
||||
* @param string $country Merchant country on PayPal.
|
||||
*
|
||||
* @psalm-suppress RedundantConditionGivenDocblockType
|
||||
*/
|
||||
public function __construct( array $products, array $capabilities ) {
|
||||
public function __construct( array $products, array $capabilities, string $country = '' ) {
|
||||
foreach ( $products as $key => $product ) {
|
||||
if ( is_a( $product, SellerStatusProduct::class ) ) {
|
||||
continue;
|
||||
|
@ -52,6 +60,7 @@ class SellerStatus {
|
|||
|
||||
$this->products = $products;
|
||||
$this->capabilities = $capabilities;
|
||||
$this->country = $country;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,7 +82,16 @@ class SellerStatus {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the enitity as array.
|
||||
* Returns merchant's country on PayPal.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function country() : string {
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -95,6 +113,7 @@ class SellerStatus {
|
|||
return array(
|
||||
'products' => $products,
|
||||
'capabilities' => $capabilities,
|
||||
'country' => $this->country,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class SellerStatusFactory {
|
|||
*
|
||||
* @return SellerStatus
|
||||
*/
|
||||
public function from_paypal_reponse( \stdClass $json ) : SellerStatus {
|
||||
public function from_paypal_response( \stdClass $json ) : SellerStatus {
|
||||
$products = array_map(
|
||||
function( $json ) : SellerStatusProduct {
|
||||
$product = new SellerStatusProduct(
|
||||
|
@ -49,6 +49,6 @@ class SellerStatusFactory {
|
|||
isset( $json->capabilities ) ? (array) $json->capabilities : array()
|
||||
);
|
||||
|
||||
return new SellerStatus( $products, $capabilities );
|
||||
return new SellerStatus( $products, $capabilities, $json->country ?? '' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,43 +18,21 @@ class PartnerReferralsData {
|
|||
/**
|
||||
* The DCC Applies Helper object.
|
||||
*
|
||||
* @deprecated Deprecates with the new UI. In this class, the products are
|
||||
* always explicit, and should not be deducted from the
|
||||
* DccApplies state at this point.
|
||||
* Remove this with the legacy UI code.
|
||||
* @var DccApplies
|
||||
*/
|
||||
private $dcc_applies;
|
||||
|
||||
/**
|
||||
* The list of products ('PPCP', 'EXPRESS_CHECKOUT').
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $products;
|
||||
private DccApplies $dcc_applies;
|
||||
|
||||
/**
|
||||
* PartnerReferralsData constructor.
|
||||
*
|
||||
* @param DccApplies $dcc_applies The DCC Applies helper.
|
||||
*/
|
||||
public function __construct(
|
||||
DccApplies $dcc_applies
|
||||
) {
|
||||
public function __construct( DccApplies $dcc_applies ) {
|
||||
$this->dcc_applies = $dcc_applies;
|
||||
$this->products = array(
|
||||
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new copy of this object with the given value set.
|
||||
*
|
||||
* @param string[] $products The list of products ('PPCP', 'EXPRESS_CHECKOUT').
|
||||
* @return static
|
||||
*/
|
||||
public function with_products( array $products ): self {
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->products = $products;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,33 +47,74 @@ class PartnerReferralsData {
|
|||
/**
|
||||
* Returns the data.
|
||||
*
|
||||
* @param string[] $products The list of products to use ('PPCP', 'EXPRESS_CHECKOUT').
|
||||
* Default is based on DCC availability.
|
||||
* @param string $onboarding_token A security token to finalize the onboarding process.
|
||||
* @param bool $use_subscriptions If the merchant requires subscription features.
|
||||
* @param bool $use_card_payments If the merchant wants to process credit card payments.
|
||||
* @return array
|
||||
*/
|
||||
public function data(): array {
|
||||
public function data( array $products = array(), string $onboarding_token = '', bool $use_subscriptions = null, bool $use_card_payments = true ) : array {
|
||||
if ( ! $products ) {
|
||||
$products = array(
|
||||
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the partners referrals data.
|
||||
* Filter the return-URL, which is called at the end of the OAuth onboarding
|
||||
* process, when the merchant clicks the "Return to your shop" button.
|
||||
*/
|
||||
return apply_filters(
|
||||
'ppcp_partner_referrals_data',
|
||||
array(
|
||||
'partner_config_override' => array(
|
||||
/**
|
||||
* Returns the URL which will be opened at the end of onboarding.
|
||||
*/
|
||||
'return_url' => apply_filters(
|
||||
$return_url = apply_filters(
|
||||
'woocommerce_paypal_payments_partner_config_override_return_url',
|
||||
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' )
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the description of the URL which will be opened at the end of onboarding.
|
||||
* Filter the label of the "Return to your shop" button.
|
||||
* It's displayed on the very last page of the onboarding popup.
|
||||
*/
|
||||
'return_url_description' => apply_filters(
|
||||
$return_url_label = apply_filters(
|
||||
'woocommerce_paypal_payments_partner_config_override_return_url_description',
|
||||
__( 'Return to your shop.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
|
||||
$capabilities = array();
|
||||
$first_party_features = array(
|
||||
'PAYMENT',
|
||||
'REFUND',
|
||||
'ADVANCED_TRANSACTIONS_SEARCH',
|
||||
'TRACKING_SHIPMENT_READWRITE',
|
||||
);
|
||||
|
||||
if ( true === $use_subscriptions ) {
|
||||
$capabilities[] = 'PAYPAL_WALLET_VAULTING_ADVANCED';
|
||||
$first_party_features[] = 'BILLING_AGREEMENT';
|
||||
}
|
||||
|
||||
// Backwards compatibility. Keep those features in the legacy UI (null-value).
|
||||
// Move this into the previous condition, once legacy code is removed.
|
||||
if ( false !== $use_subscriptions ) {
|
||||
$first_party_features[] = 'FUTURE_PAYMENT';
|
||||
$first_party_features[] = 'VAULT';
|
||||
}
|
||||
|
||||
if ( false === $use_subscriptions ) {
|
||||
// Only use "ADVANCED_VAULTING" product for onboarding with subscriptions.
|
||||
$products = array_filter(
|
||||
$products,
|
||||
static fn( $product ) => $product !== 'ADVANCED_VAULTING'
|
||||
);
|
||||
}
|
||||
|
||||
$payload = array(
|
||||
'partner_config_override' => array(
|
||||
'return_url' => $return_url,
|
||||
'return_url_description' => $return_url_label,
|
||||
'show_add_credit_card' => $use_card_payments,
|
||||
),
|
||||
'show_add_credit_card' => true,
|
||||
),
|
||||
'products' => $this->products,
|
||||
'products' => $products,
|
||||
'capabilities' => $capabilities,
|
||||
'legal_consents' => array(
|
||||
array(
|
||||
'type' => 'SHARE_DATA_CONSENT',
|
||||
|
@ -110,34 +129,31 @@ class PartnerReferralsData {
|
|||
'integration_method' => 'PAYPAL',
|
||||
'integration_type' => 'FIRST_PARTY',
|
||||
'first_party_details' => array(
|
||||
'features' => array(
|
||||
'PAYMENT',
|
||||
'FUTURE_PAYMENT',
|
||||
'REFUND',
|
||||
'ADVANCED_TRANSACTIONS_SEARCH',
|
||||
'VAULT',
|
||||
'TRACKING_SHIPMENT_READWRITE',
|
||||
),
|
||||
'features' => $first_party_features,
|
||||
'seller_nonce' => $this->nonce(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the validation token to the return_url
|
||||
*
|
||||
* @param array $data The referral data.
|
||||
* @param string $token The token to be appended.
|
||||
* @return array
|
||||
* Filter the final partners referrals data collection.
|
||||
*/
|
||||
public function append_onboarding_token( array $data, string $token ): array {
|
||||
$data['partner_config_override']['return_url'] =
|
||||
add_query_arg( 'ppcpToken', $token, $data['partner_config_override']['return_url'] );
|
||||
return $data;
|
||||
$payload = apply_filters( 'ppcp_partner_referrals_data', $payload );
|
||||
|
||||
// An empty array is not permitted.
|
||||
if ( isset( $payload['capabilities'] ) && ! $payload['capabilities'] ) {
|
||||
unset( $payload['capabilities'] );
|
||||
}
|
||||
|
||||
// Add the nonce in the end, to maintain backwards compatibility of filters.
|
||||
$payload['partner_config_override']['return_url'] = add_query_arg(
|
||||
array( 'ppcpToken' => $onboarding_token ),
|
||||
$payload['partner_config_override']['return_url']
|
||||
);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ return array(
|
|||
assert( $display_manager instanceof DisplayManager );
|
||||
|
||||
// Domain registration.
|
||||
$env = $container->get( 'onboarding.environment' );
|
||||
$env = $container->get( 'settings.environment' );
|
||||
assert( $env instanceof Environment );
|
||||
|
||||
$domain_registration_url = 'https://www.paypal.com/uccservicing/apm/applepay';
|
||||
|
|
|
@ -20,7 +20,6 @@ use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies;
|
|||
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
|
||||
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
|
@ -191,6 +190,7 @@ return array(
|
|||
'FR', // France
|
||||
'DE', // Germany
|
||||
'GR', // Greece
|
||||
'HK', // Hong Kong
|
||||
'HU', // Hungary
|
||||
'IE', // Ireland
|
||||
'IT', // Italy
|
||||
|
@ -204,6 +204,7 @@ return array(
|
|||
'PL', // Poland
|
||||
'PT', // Portugal
|
||||
'RO', // Romania
|
||||
'SG', // Singapore
|
||||
'SK', // Slovakia
|
||||
'SI', // Slovenia
|
||||
'ES', // Spain
|
||||
|
@ -233,6 +234,7 @@ return array(
|
|||
'CZK', // Czech Koruna
|
||||
'DKK', // Danish Krone
|
||||
'EUR', // Euro
|
||||
'HKD', // Hong Kong Dollar
|
||||
'GBP', // British Pound Sterling
|
||||
'HUF', // Hungarian Forint
|
||||
'ILS', // Israeli New Shekel
|
||||
|
@ -242,6 +244,7 @@ return array(
|
|||
'NZD', // New Zealand Dollar
|
||||
'PHP', // Philippine Peso
|
||||
'PLN', // Polish Zloty
|
||||
'SGD', // Singapur-Dollar
|
||||
'SEK', // Swedish Krona
|
||||
'THB', // Thai Baht
|
||||
'TWD', // New Taiwan Dollar
|
||||
|
@ -260,15 +263,15 @@ return array(
|
|||
},
|
||||
|
||||
'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
if ( $state->current_state() < State::STATE_ONBOARDED ) {
|
||||
$is_connected = $container->get( 'settings.flag.is-connected' );
|
||||
if ( ! $is_connected ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product_status = $container->get( 'applepay.apple-product-status' );
|
||||
assert( $product_status instanceof AppleProductStatus );
|
||||
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
|
||||
$enabled = $product_status->is_active();
|
||||
|
|
|
@ -368,7 +368,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
|
|||
if ( ! $button->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
$env = $c->get( 'onboarding.environment' );
|
||||
$env = $c->get( 'settings.environment' );
|
||||
assert( $env instanceof Environment );
|
||||
$is_sandobx = $env->current_environment_is( Environment::SANDBOX );
|
||||
$this->load_domain_association_file( $is_sandobx );
|
||||
|
|
|
@ -1018,6 +1018,10 @@ class ApplePayButton implements ButtonInterface {
|
|||
* @return void
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
if ( ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_register_script(
|
||||
'wc-ppcp-applepay',
|
||||
untrailingslashit( $this->module_url ) . '/assets/js/boot.js',
|
||||
|
|
|
@ -89,6 +89,7 @@ class AppleProductStatus extends ProductStatus {
|
|||
}
|
||||
}
|
||||
|
||||
// Settings used as a cache; `settings->set` is compatible with new UI.
|
||||
if ( $has_capability ) {
|
||||
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
|
||||
} else {
|
||||
|
|
|
@ -36,7 +36,7 @@ return array(
|
|||
fn(): SmartButtonInterface => $container->get( 'button.smart-button' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'wcgateway.configuration.dcc' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$container->get( 'wcgateway.url' ),
|
||||
$container->get( 'axo.payment_method_selected_map' ),
|
||||
$container->get( 'axo.supported-country-card-type-matrix' )
|
||||
|
|
|
@ -107,11 +107,9 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule
|
|||
'woocommerce_blocks_payment_method_type_registration',
|
||||
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
|
||||
/*
|
||||
* Only register the method if we are not in the admin
|
||||
* (to avoid two Debit & Credit Cards gateways in the
|
||||
* checkout block in the editor: one from ACDC one from Axo).
|
||||
* Only register the method if we are not in the admin or the customer is not logged in.
|
||||
*/
|
||||
if ( ! is_admin() ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
$payment_method_registry->register( $c->get( 'axoblock.method' ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Axo;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\NoticeRenderer;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace WooCommerce\PayPalCommerce\Axo;
|
|||
use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager;
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\CompatibilityChecker;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
@ -38,8 +38,8 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator {
|
||||
return new SettingsNoticeGenerator( $container->get( 'axo.fastlane-incompatible-plugin-names' ) );
|
||||
'axo.helpers.compatibility-checker' => static function ( ContainerInterface $container ) : CompatibilityChecker {
|
||||
return new CompatibilityChecker( $container->get( 'axo.fastlane-incompatible-plugin-names' ) );
|
||||
},
|
||||
|
||||
// If AXO is configured and onboarded.
|
||||
|
@ -66,7 +66,7 @@ return array(
|
|||
$container->get( 'ppcp.asset-version' ),
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$container->get( 'axo.insights' ),
|
||||
$container->get( 'wcgateway.settings.status' ),
|
||||
$container->get( 'api.shop.currency.getter' ),
|
||||
|
@ -89,7 +89,7 @@ return array(
|
|||
$container->get( 'api.factory.purchase-unit' ),
|
||||
$container->get( 'api.factory.shipping-preference' ),
|
||||
$container->get( 'wcgateway.transaction-url-provider' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
@ -190,29 +190,44 @@ return array(
|
|||
);
|
||||
},
|
||||
'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
|
||||
assert( $compatibility_checker instanceof CompatibilityChecker );
|
||||
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
return $settings_notice_generator->generate_settings_conflict_notice( $settings );
|
||||
return $compatibility_checker->generate_settings_conflict_notice( $settings );
|
||||
},
|
||||
|
||||
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
|
||||
assert( $compatibility_checker instanceof CompatibilityChecker );
|
||||
|
||||
return $settings_notice_generator->generate_checkout_notice();
|
||||
return $compatibility_checker->generate_checkout_notice();
|
||||
},
|
||||
|
||||
'axo.checkout-config-notice.raw' => static function ( ContainerInterface $container ) : string {
|
||||
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
|
||||
assert( $compatibility_checker instanceof CompatibilityChecker );
|
||||
|
||||
return $compatibility_checker->generate_checkout_notice( true );
|
||||
},
|
||||
|
||||
'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.compatibility-checker' );
|
||||
assert( $settings_notice_generator instanceof CompatibilityChecker );
|
||||
|
||||
return $settings_notice_generator->generate_incompatible_plugins_notice();
|
||||
},
|
||||
|
||||
'axo.incompatible-plugins-notice.raw' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = new CompatibilityChecker(
|
||||
$container->get( 'axo.fastlane-incompatible-plugin-names' )
|
||||
);
|
||||
|
||||
return $settings_notice_generator->generate_incompatible_plugins_notice( true );
|
||||
},
|
||||
|
||||
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' );
|
||||
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
|
||||
|
|
|
@ -475,7 +475,7 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
|||
* @return void
|
||||
*/
|
||||
private function add_feature_detection_tag( bool $axo_enabled ) {
|
||||
$show_tag = is_checkout() || is_cart() || is_shop();
|
||||
$show_tag = is_home() || is_checkout() || is_cart() || is_shop();
|
||||
|
||||
if ( ! $show_tag ) {
|
||||
return;
|
||||
|
|
254
modules/ppcp-axo/src/Helper/CompatibilityChecker.php
Normal file
254
modules/ppcp-axo/src/Helper/CompatibilityChecker.php
Normal file
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/**
|
||||
* Fastlane compatibility checker.
|
||||
* Detects compatibility issues and generates relevant notices.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Axo\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Axo\Helper;
|
||||
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class CompatibilityChecker
|
||||
*/
|
||||
class CompatibilityChecker {
|
||||
/**
|
||||
* The list of Fastlane incompatible plugin names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $incompatible_plugin_names;
|
||||
|
||||
/**
|
||||
* Stores the result of checkout compatibility checks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $checkout_compatibility;
|
||||
|
||||
/**
|
||||
* Stores whether DCC is enabled.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
protected ?bool $is_dcc_enabled = null;
|
||||
|
||||
/**
|
||||
* CompatibilityChecker constructor.
|
||||
*
|
||||
* @param string[] $incompatible_plugin_names The list of Fastlane incompatible plugin names.
|
||||
*/
|
||||
public function __construct( array $incompatible_plugin_names ) {
|
||||
$this->incompatible_plugin_names = $incompatible_plugin_names;
|
||||
$this->checkout_compatibility = array(
|
||||
'has_elementor_checkout' => null,
|
||||
'has_classic_checkout' => null,
|
||||
'has_block_checkout' => null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the checkout uses Elementor.
|
||||
*
|
||||
* @return bool Whether the checkout uses Elementor.
|
||||
*/
|
||||
protected function has_elementor_checkout(): bool {
|
||||
if ( $this->checkout_compatibility['has_elementor_checkout'] === null ) {
|
||||
$this->checkout_compatibility['has_elementor_checkout'] = CartCheckoutDetector::has_elementor_checkout();
|
||||
}
|
||||
|
||||
return $this->checkout_compatibility['has_elementor_checkout'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the checkout uses classic checkout.
|
||||
*
|
||||
* @return bool Whether the checkout uses classic checkout.
|
||||
*/
|
||||
protected function has_classic_checkout(): bool {
|
||||
if ( $this->checkout_compatibility['has_classic_checkout'] === null ) {
|
||||
$this->checkout_compatibility['has_classic_checkout'] = CartCheckoutDetector::has_classic_checkout();
|
||||
}
|
||||
|
||||
return $this->checkout_compatibility['has_classic_checkout'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the checkout uses block checkout.
|
||||
*
|
||||
* @return bool Whether the checkout uses block checkout.
|
||||
*/
|
||||
protected function has_block_checkout(): bool {
|
||||
if ( $this->checkout_compatibility['has_block_checkout'] === null ) {
|
||||
$this->checkout_compatibility['has_block_checkout'] = CartCheckoutDetector::has_block_checkout();
|
||||
}
|
||||
|
||||
return $this->checkout_compatibility['has_block_checkout'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if DCC is enabled.
|
||||
*
|
||||
* @param Settings $settings The plugin settings container.
|
||||
* @return bool Whether DCC is enabled.
|
||||
*/
|
||||
protected function is_dcc_enabled( Settings $settings ): bool {
|
||||
if ( $this->is_dcc_enabled === null ) {
|
||||
try {
|
||||
$this->is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' );
|
||||
} catch ( NotFoundException $ignored ) {
|
||||
$this->is_dcc_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->is_dcc_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the full HTML of the notification.
|
||||
*
|
||||
* @param string $message HTML of the inner message contents.
|
||||
* @param bool $is_error Whether the provided message is an error. Affects the notice color.
|
||||
* @param bool $raw_message Whether to return raw message without HTML wrappers.
|
||||
*
|
||||
* @return string The full HTML code of the notification, or an empty string, or raw message.
|
||||
*/
|
||||
private function render_notice( string $message, bool $is_error = false, bool $raw_message = false ) : string {
|
||||
if ( ! $message ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $raw_message ) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<div class="ppcp-notice %1$s"><p>%2$s</p></div>',
|
||||
$is_error ? 'ppcp-notice-error' : '',
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there aren't any incompatibilities that would prevent Fastlane from working properly.
|
||||
*
|
||||
* @return bool Whether the setup is compatible.
|
||||
*/
|
||||
public function is_fastlane_compatible(): bool {
|
||||
// Check for incompatible plugins.
|
||||
if ( ! empty( $this->incompatible_plugin_names ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for checkout page incompatibilities.
|
||||
if ( $this->has_elementor_checkout() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->has_classic_checkout() && ! $this->has_block_checkout() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No incompatibilities found.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the checkout notice.
|
||||
*
|
||||
* @param bool $raw_message Whether to return raw message without HTML wrappers.
|
||||
* @return string
|
||||
*/
|
||||
public function generate_checkout_notice( bool $raw_message = false ): string {
|
||||
$notice_content = '';
|
||||
|
||||
// Check for checkout incompatibilities.
|
||||
$has_checkout_incompatibility = $this->has_elementor_checkout() ||
|
||||
( ! $this->has_classic_checkout() && ! $this->has_block_checkout() );
|
||||
|
||||
if ( ! $has_checkout_incompatibility ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' );
|
||||
$block_checkout_docs_link = __(
|
||||
'https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout/#using-the-cart-and-checkout-blocks',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
|
||||
if ( $this->has_elementor_checkout() ) {
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||
__(
|
||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the <code>Elementor Checkout widget</code>. To enable Fastlane and accelerate payments, the page must include either the <code>Checkout</code> block, <code>Classic Checkout</code>, or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the Checkout block.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
esc_url( $checkout_page_link ),
|
||||
esc_url( $block_checkout_docs_link )
|
||||
);
|
||||
} elseif ( ! $this->has_classic_checkout() && ! $this->has_block_checkout() ) {
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||
__(
|
||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store does not seem to be properly configured or uses an incompatible <code>third-party Checkout</code> solution. To enable Fastlane and accelerate payments, the page must include either the <code>Checkout</code> block, <code>Classic Checkout</code>, or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the Checkout block.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
esc_url( $checkout_page_link ),
|
||||
esc_url( $block_checkout_docs_link )
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render_notice( $notice_content, true, $raw_message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the incompatible plugins notice.
|
||||
*
|
||||
* @param bool $raw_message Whether to return raw message without HTML wrappers.
|
||||
* @return string
|
||||
*/
|
||||
public function generate_incompatible_plugins_notice( bool $raw_message = false ): string {
|
||||
if ( empty( $this->incompatible_plugin_names ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$plugins_settings_link = esc_url( admin_url( 'plugins.php' ) );
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */
|
||||
__(
|
||||
'<span class="highlight">Note:</span> The accelerated guest buyer experience provided by Fastlane may not be fully compatible with some of the following <a href="%1$s">active plugins</a>: <ul class="ppcp-notice-list">%2$s</ul>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$plugins_settings_link,
|
||||
implode( '', $this->incompatible_plugin_names )
|
||||
);
|
||||
|
||||
return $this->render_notice( $notice_content, false, $raw_message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a warning notice with instructions on conflicting plugin-internal settings.
|
||||
*
|
||||
* @param Settings $settings The plugin settings container, which is checked for conflicting values.
|
||||
* @param bool $raw_message Whether to return raw message without HTML wrappers.
|
||||
* @return string
|
||||
*/
|
||||
public function generate_settings_conflict_notice( Settings $settings, bool $raw_message = false ) : string {
|
||||
if ( $this->is_dcc_enabled( $settings ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$notice_content = __(
|
||||
'<span class="highlight">Warning:</span> To enable Fastlane and accelerate payments, the <strong>Advanced Card Processing</strong> payment method must also be enabled.',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
|
||||
return $this->render_notice( $notice_content, true, $raw_message );
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Settings notice generator.
|
||||
* Generates the settings notices.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Axo\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Axo\Helper;
|
||||
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class SettingsNoticeGenerator
|
||||
*/
|
||||
class SettingsNoticeGenerator {
|
||||
/**
|
||||
* The list of Fastlane incompatible plugin names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $incompatible_plugin_names;
|
||||
|
||||
/**
|
||||
* SettingsNoticeGenerator constructor.
|
||||
*
|
||||
* @param string[] $incompatible_plugin_names The list of Fastlane incompatible plugin names.
|
||||
*/
|
||||
public function __construct( array $incompatible_plugin_names ) {
|
||||
$this->incompatible_plugin_names = $incompatible_plugin_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the full HTML of the notification.
|
||||
*
|
||||
* @param string $message HTML of the inner message contents.
|
||||
* @param bool $is_error Whether the provided message is an error. Affects the notice color.
|
||||
*
|
||||
* @return string The full HTML code of the notification, or an empty string.
|
||||
*/
|
||||
private function render_notice( string $message, bool $is_error = false ) : string {
|
||||
if ( ! $message ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<div class="ppcp-notice %1$s"><p>%2$s</p></div>',
|
||||
$is_error ? 'ppcp-notice-error' : '',
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the checkout notice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_checkout_notice(): string {
|
||||
$checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' );
|
||||
$block_checkout_docs_link = __(
|
||||
'https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout/#using-the-cart-and-checkout-blocks',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
|
||||
$notice_content = '';
|
||||
|
||||
if ( CartCheckoutDetector::has_elementor_checkout() ) {
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||
__(
|
||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the <code>Elementor Checkout widget</code>. To enable Fastlane and accelerate payments, the page must include either the <code>Checkout</code> block, <code>Classic Checkout</code>, or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the Checkout block.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
esc_url( $checkout_page_link ),
|
||||
esc_url( $block_checkout_docs_link )
|
||||
);
|
||||
} elseif ( ! CartCheckoutDetector::has_classic_checkout() && ! CartCheckoutDetector::has_block_checkout() ) {
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||
__(
|
||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store does not seem to be properly configured or uses an incompatible <code>third-party Checkout</code> solution. To enable Fastlane and accelerate payments, the page must include either the <code>Checkout</code> block, <code>Classic Checkout</code>, or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the Checkout block.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
esc_url( $checkout_page_link ),
|
||||
esc_url( $block_checkout_docs_link )
|
||||
);
|
||||
}
|
||||
|
||||
return $notice_content ? '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the incompatible plugins notice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_incompatible_plugins_notice(): string {
|
||||
if ( empty( $this->incompatible_plugin_names ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$plugins_settings_link = esc_url( admin_url( 'plugins.php' ) );
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */
|
||||
__(
|
||||
'<span class="highlight">Note:</span> The accelerated guest buyer experience provided by Fastlane may not be fully compatible with some of the following <a href="%1$s">active plugins</a>: <ul class="ppcp-notice-list">%2$s</ul>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$plugins_settings_link,
|
||||
implode( '', $this->incompatible_plugin_names )
|
||||
);
|
||||
|
||||
return '<div class="ppcp-notice"><p>' . $notice_content . '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a warning notice with instructions on conflicting plugin-internal settings.
|
||||
*
|
||||
* @param Settings $settings The plugin settings container, which is checked for conflicting
|
||||
* values.
|
||||
* @return string
|
||||
*/
|
||||
public function generate_settings_conflict_notice( Settings $settings ) : string {
|
||||
$notice_content = '';
|
||||
$is_dcc_enabled = false;
|
||||
|
||||
try {
|
||||
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' );
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
} catch ( NotFoundException $ignored ) {
|
||||
// Never happens.
|
||||
}
|
||||
|
||||
if ( ! $is_dcc_enabled ) {
|
||||
$notice_content = __(
|
||||
'<span class="highlight">Warning:</span> To enable Fastlane and accelerate payments, the <strong>Advanced Card Processing</strong> payment method must also be enabled.',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render_notice( $notice_content, true );
|
||||
}
|
||||
}
|
|
@ -67,27 +67,6 @@ export const PayPalComponent = ( {
|
|||
? `${ config.id }-${ fundingSource }`
|
||||
: config.id;
|
||||
|
||||
/**
|
||||
* The block cart displays express checkout buttons. Those buttons are handled by the
|
||||
* PAYPAL_GATEWAY_ID method on the server ("PayPal Smart Buttons").
|
||||
*
|
||||
* A possible bug in WooCommerce does not use the correct payment method ID for the express
|
||||
* payment buttons inside the cart, but sends the ID of the _first_ active payment method.
|
||||
*
|
||||
* This function uses an internal WooCommerce dispatcher method to set the correct method ID.
|
||||
*/
|
||||
const enforcePaymentMethodForCart = () => {
|
||||
// Do nothing, unless we're handling block cart express payment buttons.
|
||||
if ( 'cart-block' !== config.scriptData.context ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the active payment method to PAYPAL_GATEWAY_ID.
|
||||
wp.data
|
||||
.dispatch( 'wc/store/payment' )
|
||||
.__internalSetActivePaymentMethod( PAYPAL_GATEWAY_ID, {} );
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
// fill the form if in continuation (for product or mini-cart buttons)
|
||||
if ( continuationFilled || ! config.scriptData.continuation?.order ) {
|
||||
|
@ -339,7 +318,6 @@ export const PayPalComponent = ( {
|
|||
shouldskipFinalConfirmation,
|
||||
getCheckoutRedirectUrl,
|
||||
setGotoContinuationOnError,
|
||||
enforcePaymentMethodForCart,
|
||||
onSubmit,
|
||||
onError,
|
||||
onClose
|
||||
|
@ -439,7 +417,6 @@ export const PayPalComponent = ( {
|
|||
shouldskipFinalConfirmation,
|
||||
getCheckoutRedirectUrl,
|
||||
setGotoContinuationOnError,
|
||||
enforcePaymentMethodForCart,
|
||||
onSubmit,
|
||||
onError,
|
||||
onClose
|
||||
|
@ -476,7 +453,6 @@ export const PayPalComponent = ( {
|
|||
shouldskipFinalConfirmation,
|
||||
getCheckoutRedirectUrl,
|
||||
setGotoContinuationOnError,
|
||||
enforcePaymentMethodForCart,
|
||||
onSubmit,
|
||||
onError,
|
||||
onClose
|
||||
|
|
|
@ -81,12 +81,14 @@ export const paypalShippingToWc = ( shipping ) => {
|
|||
export const paypalPayerToWc = ( payer ) => {
|
||||
const firstName = payer?.name?.given_name ?? '';
|
||||
const lastName = payer?.name?.surname ?? '';
|
||||
const phone = payer?.phone?.phone_number?.national_number ?? '';
|
||||
const address = payer.address ? paypalAddressToWc( payer.address ) : {};
|
||||
return {
|
||||
...address,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email: payer.email_address,
|
||||
phone: phone
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ export const handleApprove = async (
|
|||
shouldskipFinalConfirmation,
|
||||
getCheckoutRedirectUrl,
|
||||
setGotoContinuationOnError,
|
||||
enforcePaymentMethodForCart,
|
||||
onSubmit,
|
||||
onError,
|
||||
onClose
|
||||
|
@ -132,7 +131,6 @@ export const handleApprove = async (
|
|||
location.href = getCheckoutRedirectUrl();
|
||||
} else {
|
||||
setGotoContinuationOnError( true );
|
||||
enforcePaymentMethodForCart();
|
||||
onSubmit();
|
||||
}
|
||||
} catch ( err ) {
|
||||
|
@ -171,7 +169,6 @@ export const handleApproveSubscription = async (
|
|||
shouldskipFinalConfirmation,
|
||||
getCheckoutRedirectUrl,
|
||||
setGotoContinuationOnError,
|
||||
enforcePaymentMethodForCart,
|
||||
onSubmit,
|
||||
onError,
|
||||
onClose
|
||||
|
@ -242,7 +239,6 @@ export const handleApproveSubscription = async (
|
|||
location.href = getCheckoutRedirectUrl();
|
||||
} else {
|
||||
setGotoContinuationOnError( true );
|
||||
enforcePaymentMethodForCart();
|
||||
onSubmit();
|
||||
}
|
||||
} catch ( err ) {
|
||||
|
|
|
@ -28,13 +28,6 @@ return array(
|
|||
);
|
||||
},
|
||||
'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod {
|
||||
/**
|
||||
* Cart instance; might be null, esp. in customizer or in Block Editor.
|
||||
*
|
||||
* @var null|WC_Cart $cart
|
||||
*/
|
||||
$cart = WC()->cart;
|
||||
|
||||
return new PayPalPaymentMethod(
|
||||
$container->get( 'blocks.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
|
@ -53,7 +46,6 @@ return array(
|
|||
$container->get( 'wcgateway.place-order-button-text' ),
|
||||
$container->get( 'wcgateway.place-order-button-description' ),
|
||||
$container->get( 'wcgateway.all-funding-sources' ),
|
||||
$cart && $cart->needs_shipping()
|
||||
);
|
||||
},
|
||||
'blocks.advanced-card-method' => static function( ContainerInterface $container ): AdvancedCardPaymentMethod {
|
||||
|
|
|
@ -23,7 +23,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
|||
*/
|
||||
class UpdateShippingEndpoint implements EndpointInterface {
|
||||
const ENDPOINT = 'ppc-update-shipping';
|
||||
const WC_STORE_API_ENDPOINT = '/wp-json/wc/store/cart/';
|
||||
const WC_STORE_API_ENDPOINT = '/wp-json/wc/store/v1/cart/';
|
||||
|
||||
/**
|
||||
* The Request Data Helper.
|
||||
|
|
|
@ -130,13 +130,6 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
*/
|
||||
private $all_funding_sources;
|
||||
|
||||
/**
|
||||
* Whether shipping details must be collected during checkout; i.e. paying for physical goods?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $need_shipping;
|
||||
|
||||
/**
|
||||
* Assets constructor.
|
||||
*
|
||||
|
@ -155,7 +148,6 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
* @param string $place_order_button_text The text for the standard "Place order" button.
|
||||
* @param string $place_order_button_description The text for additional "Place order" description.
|
||||
* @param array $all_funding_sources All existing funding sources for PayPal buttons.
|
||||
* @param bool $need_shipping Whether shipping details are required for the purchase.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
|
@ -172,8 +164,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
bool $use_place_order,
|
||||
string $place_order_button_text,
|
||||
string $place_order_button_description,
|
||||
array $all_funding_sources,
|
||||
bool $need_shipping
|
||||
array $all_funding_sources
|
||||
) {
|
||||
$this->name = PayPalGateway::ID;
|
||||
$this->module_url = $module_url;
|
||||
|
@ -191,7 +182,6 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
$this->place_order_button_text = $place_order_button_text;
|
||||
$this->place_order_button_description = $place_order_button_description;
|
||||
$this->all_funding_sources = $all_funding_sources;
|
||||
$this->need_shipping = $need_shipping;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,6 +248,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
&& $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'block-checkout' );
|
||||
$place_order_enabled = ( $this->use_place_order || $this->add_place_order_method )
|
||||
&& ! $this->subscription_helper->cart_contains_subscription();
|
||||
$cart = WC()->cart;
|
||||
|
||||
return array(
|
||||
'id' => $this->gateway->id,
|
||||
|
@ -284,7 +275,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
),
|
||||
),
|
||||
'scriptData' => $script_data,
|
||||
'needShipping' => $this->need_shipping,
|
||||
'needShipping' => $cart && $cart->needs_shipping(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export const handleShippingOptionsChange = async ( data, actions, config ) => {
|
|||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WC-Store-API-Nonce':
|
||||
'Nonce':
|
||||
config.ajax.update_customer_shipping.wp_rest_nonce,
|
||||
},
|
||||
body: JSON.stringify( {
|
||||
|
@ -106,7 +106,7 @@ export const handleShippingAddressChange = async ( data, actions, config ) => {
|
|||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WC-Store-API-Nonce':
|
||||
'Nonce':
|
||||
config.ajax.update_customer_shipping
|
||||
.wp_rest_nonce,
|
||||
},
|
||||
|
|
|
@ -36,7 +36,6 @@ use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
|
|||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
|
||||
|
||||
|
@ -49,7 +48,7 @@ return array(
|
|||
return $client_id;
|
||||
}
|
||||
|
||||
$env = $container->get( 'onboarding.environment' );
|
||||
$env = $container->get( 'settings.environment' );
|
||||
/**
|
||||
* The environment.
|
||||
*
|
||||
|
@ -125,8 +124,8 @@ return array(
|
|||
}
|
||||
}
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
|
||||
$is_connected = $container->get( 'settings.flag.is-connected' );
|
||||
if ( ! $is_connected ) {
|
||||
return new DisabledSmartButton();
|
||||
}
|
||||
|
||||
|
@ -142,7 +141,7 @@ return array(
|
|||
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
|
||||
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
|
||||
$messages_apply = $container->get( 'button.helper.messages-apply' );
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
return new SmartButton(
|
||||
$container->get( 'button.url' ),
|
||||
|
@ -241,11 +240,11 @@ return array(
|
|||
);
|
||||
},
|
||||
'button.helper.early-order-handler' => static function ( ContainerInterface $container ) : EarlyOrderHandler {
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
$order_processor = $container->get( 'wcgateway.order-processor' );
|
||||
$session_handler = $container->get( 'session.handler' );
|
||||
return new EarlyOrderHandler( $state, $order_processor, $session_handler );
|
||||
return new EarlyOrderHandler(
|
||||
$container->get( 'settings.flag.is-connected' ),
|
||||
$container->get( 'wcgateway.order-processor' ),
|
||||
$container->get( 'session.handler' )
|
||||
);
|
||||
},
|
||||
'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint {
|
||||
$request_data = $container->get( 'button.request-data' );
|
||||
|
|
|
@ -272,8 +272,9 @@ class ApproveOrderEndpoint implements EndpointInterface {
|
|||
* @return void
|
||||
*/
|
||||
protected function toggle_final_review_enabled_setting(): void {
|
||||
// TODO new-ux: This flag must also be updated in the new settings.
|
||||
$final_review_enabled_setting = $this->settings->has( 'blocks_final_review_enabled' ) && $this->settings->get( 'blocks_final_review_enabled' );
|
||||
$final_review_enabled_setting ? $this->settings->set( 'blocks_final_review_enabled', false ) : $this->settings->set( 'blocks_final_review_enabled', true );
|
||||
$this->settings->set( 'blocks_final_review_enabled', ! $final_review_enabled_setting );
|
||||
$this->settings->persist();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,6 @@ class DisabledFundingSources {
|
|||
$disable_funding = $all_sources;
|
||||
}
|
||||
|
||||
return $disable_funding;
|
||||
return apply_filters( 'woocommerce_paypal_payments_disabled_funding_sources', $disable_funding );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ namespace WooCommerce\PayPalCommerce\Button\Helper;
|
|||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
|
@ -23,11 +21,11 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
|||
class EarlyOrderHandler {
|
||||
|
||||
/**
|
||||
* The State.
|
||||
* Whether the merchant is connected to PayPal (onboarding completed).
|
||||
*
|
||||
* @var State
|
||||
* @var bool
|
||||
*/
|
||||
private $state;
|
||||
private bool $is_connected;
|
||||
|
||||
/**
|
||||
* The Order Processor.
|
||||
|
@ -46,17 +44,17 @@ class EarlyOrderHandler {
|
|||
/**
|
||||
* EarlyOrderHandler constructor.
|
||||
*
|
||||
* @param State $state The State.
|
||||
* @param bool $is_connected Whether onboarding was completed.
|
||||
* @param OrderProcessor $order_processor The Order Processor.
|
||||
* @param SessionHandler $session_handler The Session Handler.
|
||||
*/
|
||||
public function __construct(
|
||||
State $state,
|
||||
bool $is_connected,
|
||||
OrderProcessor $order_processor,
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
|
||||
$this->state = $state;
|
||||
$this->is_connected = $is_connected;
|
||||
$this->order_processor = $order_processor;
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
@ -67,7 +65,7 @@ class EarlyOrderHandler {
|
|||
* @return bool
|
||||
*/
|
||||
public function should_create_early_order(): bool {
|
||||
return $this->state->current_state() === State::STATE_ONBOARDED;
|
||||
return $this->is_connected;
|
||||
}
|
||||
|
||||
//phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
|
|
|
@ -201,6 +201,7 @@ class WooCommerceOrderCreator {
|
|||
if ( $payer ) {
|
||||
$address = $payer->address();
|
||||
$payer_name = $payer->name();
|
||||
$payer_phone = $payer->phone();
|
||||
|
||||
$wc_email = null;
|
||||
$wc_customer = WC()->customer;
|
||||
|
@ -220,6 +221,7 @@ class WooCommerceOrderCreator {
|
|||
'state' => $address ? $address->admin_area_1() : '',
|
||||
'postcode' => $address ? $address->postal_code() : '',
|
||||
'country' => $address ? $address->country_code() : '',
|
||||
'phone' => $payer_phone ? $payer_phone->phone()->national_number() : '',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ return array(
|
|||
'FR',
|
||||
'DE',
|
||||
'GR',
|
||||
'HK',
|
||||
'HU',
|
||||
'IE',
|
||||
'IT',
|
||||
|
@ -56,6 +57,7 @@ return array(
|
|||
'PT',
|
||||
'RO',
|
||||
'SK',
|
||||
'SG',
|
||||
'SI',
|
||||
'ES',
|
||||
'SE',
|
||||
|
|
|
@ -10,9 +10,13 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Compat;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\GeneralSettingsMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\PaymentMethodSettingsMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\SettingsMap;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\SettingsMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\SettingsTabMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\StylingSettingsMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\SubscriptionSettingsMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
|
@ -133,30 +137,26 @@ return array(
|
|||
$styling_settings_map_helper = $container->get( 'compat.settings.styling_map_helper' );
|
||||
assert( $styling_settings_map_helper instanceof StylingSettingsMapHelper );
|
||||
|
||||
$settings_tab_map_helper = $container->get( 'compat.settings.settings_tab_map_helper' );
|
||||
assert( $settings_tab_map_helper instanceof SettingsTabMapHelper );
|
||||
|
||||
$subscription_map_helper = $container->get( 'compat.settings.subscription_map_helper' );
|
||||
assert( $subscription_map_helper instanceof SubscriptionSettingsMapHelper );
|
||||
|
||||
$general_map_helper = $container->get( 'compat.settings.general_map_helper' );
|
||||
assert( $general_map_helper instanceof GeneralSettingsMapHelper );
|
||||
|
||||
$payment_methods_map_helper = $container->get( 'compat.settings.payment_methods_map_helper' );
|
||||
assert( $payment_methods_map_helper instanceof PaymentMethodSettingsMapHelper );
|
||||
|
||||
return array(
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.general' ),
|
||||
/**
|
||||
* The new GeneralSettings class stores the current connection
|
||||
* details, without adding an environment-suffix (no `_sandbox`
|
||||
* or `_production` in the field name)
|
||||
* Only the `sandbox_merchant` flag indicates, which environment
|
||||
* the credentials are used for.
|
||||
*/
|
||||
array(
|
||||
'merchant_id' => 'merchant_id',
|
||||
'client_id' => 'client_id',
|
||||
'client_secret' => 'client_secret',
|
||||
'sandbox_on' => 'sandbox_merchant',
|
||||
'live_client_id' => 'client_id',
|
||||
'live_client_secret' => 'client_secret',
|
||||
'live_merchant_id' => 'merchant_id',
|
||||
'live_merchant_email' => 'merchant_email',
|
||||
'sandbox_client_id' => 'client_id',
|
||||
'sandbox_client_secret' => 'client_secret',
|
||||
'sandbox_merchant_id' => 'merchant_id',
|
||||
'sandbox_merchant_email' => 'merchant_email',
|
||||
)
|
||||
$general_map_helper->map()
|
||||
),
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.settings' ),
|
||||
$settings_tab_map_helper->map()
|
||||
),
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.styling' ),
|
||||
|
@ -172,15 +172,50 @@ return array(
|
|||
*/
|
||||
$styling_settings_map_helper->map()
|
||||
),
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.settings' ),
|
||||
$subscription_map_helper->map()
|
||||
),
|
||||
/**
|
||||
* We need to pass the PaymentSettings model instance to use it in some helpers.
|
||||
* Once the new settings module is permanently enabled,
|
||||
* this model can be passed as a dependency to the appropriate helper classes.
|
||||
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
|
||||
*/
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.payment' ),
|
||||
array()
|
||||
),
|
||||
new SettingsMap(
|
||||
$container->get( 'settings.data.payment' ),
|
||||
$payment_methods_map_helper->map()
|
||||
),
|
||||
);
|
||||
},
|
||||
'compat.settings.settings_map_helper' => static function( ContainerInterface $container ) : SettingsMapHelper {
|
||||
return new SettingsMapHelper(
|
||||
$container->get( 'compat.setting.new-to-old-map' ),
|
||||
$container->get( 'compat.settings.styling_map_helper' )
|
||||
$container->get( 'compat.settings.styling_map_helper' ),
|
||||
$container->get( 'compat.settings.settings_tab_map_helper' ),
|
||||
$container->get( 'compat.settings.subscription_map_helper' ),
|
||||
$container->get( 'compat.settings.general_map_helper' ),
|
||||
$container->get( 'compat.settings.payment_methods_map_helper' ),
|
||||
$container->get( 'wcgateway.settings.admin-settings-enabled' )
|
||||
);
|
||||
},
|
||||
'compat.settings.styling_map_helper' => static function() : StylingSettingsMapHelper {
|
||||
return new StylingSettingsMapHelper();
|
||||
},
|
||||
'compat.settings.settings_tab_map_helper' => static function() : SettingsTabMapHelper {
|
||||
return new SettingsTabMapHelper();
|
||||
},
|
||||
'compat.settings.subscription_map_helper' => static function( ContainerInterface $container ) : SubscriptionSettingsMapHelper {
|
||||
return new SubscriptionSettingsMapHelper( $container->get( 'wc-subscriptions.helper' ) );
|
||||
},
|
||||
'compat.settings.general_map_helper' => static function() : GeneralSettingsMapHelper {
|
||||
return new GeneralSettingsMapHelper();
|
||||
},
|
||||
'compat.settings.payment_methods_map_helper' => static function() : PaymentMethodSettingsMapHelper {
|
||||
return new PaymentMethodSettingsMapHelper();
|
||||
},
|
||||
);
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* A helper for mapping old and new general settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Compat\Settings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
/**
|
||||
* Handles mapping between old and new general settings.
|
||||
*
|
||||
* @psalm-import-type newSettingsKey from SettingsMap
|
||||
* @psalm-import-type oldSettingsKey from SettingsMap
|
||||
*/
|
||||
class GeneralSettingsMapHelper {
|
||||
|
||||
/**
|
||||
* Maps old setting keys to new setting keys.
|
||||
*
|
||||
* The new GeneralSettings class stores the current connection
|
||||
* details, without adding an environment-suffix (no `_sandbox`
|
||||
* or `_production` in the field name)
|
||||
* Only the `sandbox_merchant` flag indicates, which environment
|
||||
* the credentials are used for.
|
||||
*
|
||||
* @psalm-return array<oldSettingsKey, newSettingsKey>
|
||||
*/
|
||||
public function map(): array {
|
||||
return array(
|
||||
'merchant_id' => 'merchant_id',
|
||||
'client_id' => 'client_id',
|
||||
'client_secret' => 'client_secret',
|
||||
'sandbox_on' => 'sandbox_merchant',
|
||||
'live_client_id' => 'client_id',
|
||||
'live_client_secret' => 'client_secret',
|
||||
'live_merchant_id' => 'merchant_id',
|
||||
'live_merchant_email' => 'merchant_email',
|
||||
'merchant_email' => 'merchant_email',
|
||||
'sandbox_client_id' => 'client_id',
|
||||
'sandbox_client_secret' => 'client_secret',
|
||||
'sandbox_merchant_id' => 'merchant_id',
|
||||
'sandbox_merchant_email' => 'merchant_email',
|
||||
'enabled' => '',
|
||||
'allow_local_apm_gateways' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the given key from the new settings.
|
||||
*
|
||||
* @param string $old_key The key from the legacy settings.
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return mixed The value of the mapped setting, or null if not applicable.
|
||||
*/
|
||||
public function mapped_value( string $old_key, array $settings_model ) {
|
||||
$settings_map = $this->map();
|
||||
$new_key = $settings_map[ $old_key ] ?? false;
|
||||
switch ( $old_key ) {
|
||||
case 'enabled':
|
||||
case 'allow_local_apm_gateways':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return $settings_model[ $new_key ] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/**
|
||||
* A helper for mapping the old/new payment method settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Compat\Settings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
|
||||
/**
|
||||
* A map of old to new payment method settings.
|
||||
*
|
||||
* @psalm-import-type newSettingsKey from SettingsMap
|
||||
* @psalm-import-type oldSettingsKey from SettingsMap
|
||||
*/
|
||||
class PaymentMethodSettingsMapHelper {
|
||||
|
||||
/**
|
||||
* Maps old setting keys to new payment method settings names.
|
||||
*
|
||||
* @psalm-return array<oldSettingsKey, newSettingsKey>
|
||||
*/
|
||||
public function map(): array {
|
||||
return array(
|
||||
'dcc_enabled' => CreditCardGateway::ID,
|
||||
'axo_enabled' => AxoGateway::ID,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a mapped key from the new settings.
|
||||
*
|
||||
* @param string $old_key The key from the legacy settings.
|
||||
* @return mixed The value of the mapped setting, (null if not found).
|
||||
*/
|
||||
public function mapped_value( string $old_key ): ?bool {
|
||||
|
||||
$payment_method = $this->map()[ $old_key ] ?? false;
|
||||
|
||||
if ( ! $payment_method ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->is_gateway_enabled( $payment_method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the payment gateway with the given name is enabled.
|
||||
*
|
||||
* @param string $gateway_name The gateway name.
|
||||
* @return bool True if the payment gateway with the given name is enabled, otherwise false.
|
||||
*/
|
||||
protected function is_gateway_enabled( string $gateway_name ): bool {
|
||||
$gateway_settings = get_option( "woocommerce_{$gateway_name}_settings", array() );
|
||||
$gateway_enabled = $gateway_settings['enabled'] ?? false;
|
||||
|
||||
return $gateway_enabled === 'yes';
|
||||
}
|
||||
}
|
|
@ -10,6 +10,10 @@ declare( strict_types = 1 );
|
|||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
use RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
|
||||
|
||||
/**
|
||||
|
@ -49,17 +53,70 @@ class SettingsMapHelper {
|
|||
*/
|
||||
protected StylingSettingsMapHelper $styling_settings_map_helper;
|
||||
|
||||
/**
|
||||
* A helper for mapping the old/new settings tab settings.
|
||||
*
|
||||
* @var SettingsTabMapHelper
|
||||
*/
|
||||
protected SettingsTabMapHelper $settings_tab_map_helper;
|
||||
|
||||
/**
|
||||
* A helper for mapping old and new subscription settings.
|
||||
*
|
||||
* @var SubscriptionSettingsMapHelper
|
||||
*/
|
||||
protected SubscriptionSettingsMapHelper $subscription_map_helper;
|
||||
|
||||
/**
|
||||
* A helper for mapping old and new general settings.
|
||||
*
|
||||
* @var GeneralSettingsMapHelper
|
||||
*/
|
||||
protected GeneralSettingsMapHelper $general_settings_map_helper;
|
||||
|
||||
/**
|
||||
* A helper for mapping old and new payment method settings.
|
||||
*
|
||||
* @var PaymentMethodSettingsMapHelper
|
||||
*/
|
||||
protected PaymentMethodSettingsMapHelper $payment_method_settings_map_helper;
|
||||
|
||||
/**
|
||||
* Whether the new settings module is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $new_settings_module_enabled;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param SettingsMap[] $settings_map A list of settings maps containing key definitions.
|
||||
* @param StylingSettingsMapHelper $styling_settings_map_helper A helper for mapping the old/new styling settings.
|
||||
* @param SettingsTabMapHelper $settings_tab_map_helper A helper for mapping the old/new settings tab settings.
|
||||
* @param SubscriptionSettingsMapHelper $subscription_map_helper A helper for mapping old and new subscription settings.
|
||||
* @param GeneralSettingsMapHelper $general_settings_map_helper A helper for mapping old and new general settings.
|
||||
* @param PaymentMethodSettingsMapHelper $payment_method_settings_map_helper A helper for mapping old and new payment method settings.
|
||||
* @param bool $new_settings_module_enabled Whether the new settings module is enabled.
|
||||
* @throws RuntimeException When an old key has multiple mappings.
|
||||
*/
|
||||
public function __construct( array $settings_map, StylingSettingsMapHelper $styling_settings_map_helper ) {
|
||||
public function __construct(
|
||||
array $settings_map,
|
||||
StylingSettingsMapHelper $styling_settings_map_helper,
|
||||
SettingsTabMapHelper $settings_tab_map_helper,
|
||||
SubscriptionSettingsMapHelper $subscription_map_helper,
|
||||
GeneralSettingsMapHelper $general_settings_map_helper,
|
||||
PaymentMethodSettingsMapHelper $payment_method_settings_map_helper,
|
||||
bool $new_settings_module_enabled
|
||||
) {
|
||||
$this->validate_settings_map( $settings_map );
|
||||
$this->settings_map = $settings_map;
|
||||
$this->styling_settings_map_helper = $styling_settings_map_helper;
|
||||
$this->settings_tab_map_helper = $settings_tab_map_helper;
|
||||
$this->subscription_map_helper = $subscription_map_helper;
|
||||
$this->general_settings_map_helper = $general_settings_map_helper;
|
||||
$this->payment_method_settings_map_helper = $payment_method_settings_map_helper;
|
||||
$this->new_settings_module_enabled = $new_settings_module_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +146,10 @@ class SettingsMapHelper {
|
|||
* @return mixed|null The value of the mapped setting, or null if not found.
|
||||
*/
|
||||
public function mapped_value( string $old_key ) {
|
||||
if ( ! $this->new_settings_module_enabled ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->ensure_map_initialized();
|
||||
if ( ! isset( $this->key_to_model[ $old_key ] ) ) {
|
||||
return null;
|
||||
|
@ -112,6 +173,10 @@ class SettingsMapHelper {
|
|||
* @return bool True if the key exists in the new settings, false otherwise.
|
||||
*/
|
||||
public function has_mapped_key( string $old_key ) : bool {
|
||||
if ( ! $this->new_settings_module_enabled ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->ensure_map_initialized();
|
||||
|
||||
return isset( $this->key_to_model[ $old_key ] );
|
||||
|
@ -134,7 +199,23 @@ class SettingsMapHelper {
|
|||
|
||||
switch ( true ) {
|
||||
case $model instanceof StylingSettings:
|
||||
return $this->styling_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
||||
return $this->styling_settings_map_helper->mapped_value(
|
||||
$old_key,
|
||||
$this->model_cache[ $model_id ],
|
||||
$this->get_payment_settings_model()
|
||||
);
|
||||
|
||||
case $model instanceof GeneralSettings:
|
||||
return $this->general_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
||||
|
||||
case $model instanceof SettingsModel:
|
||||
return $old_key === 'subscriptions_mode'
|
||||
? $this->subscription_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] )
|
||||
: $this->settings_tab_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
||||
|
||||
case $model instanceof PaymentSettings:
|
||||
return $this->payment_method_settings_map_helper->mapped_value( $old_key );
|
||||
|
||||
default:
|
||||
return $this->model_cache[ $model_id ][ $new_key ] ?? null;
|
||||
}
|
||||
|
@ -173,4 +254,23 @@ class SettingsMapHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the PaymentSettings model instance.
|
||||
*
|
||||
* Once the new settings module is permanently enabled,
|
||||
* this model can be passed as a dependency to the appropriate helper classes.
|
||||
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
|
||||
*
|
||||
* @return AbstractDataModel|null
|
||||
*/
|
||||
protected function get_payment_settings_model() : ?AbstractDataModel {
|
||||
foreach ( $this->settings_map as $settings_map_instance ) {
|
||||
if ( $settings_map_instance->get_model() instanceof PaymentSettings ) {
|
||||
return $settings_map_instance->get_model();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
146
modules/ppcp-compat/src/Settings/SettingsTabMapHelper.php
Normal file
146
modules/ppcp-compat/src/Settings/SettingsTabMapHelper.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
/**
|
||||
* A helper for mapping the old/new settings tab settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Compat\Settings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
|
||||
/**
|
||||
* A map of old to new styling settings.
|
||||
*
|
||||
* @psalm-import-type newSettingsKey from SettingsMap
|
||||
* @psalm-import-type oldSettingsKey from SettingsMap
|
||||
*/
|
||||
class SettingsTabMapHelper {
|
||||
|
||||
use ContextTrait;
|
||||
|
||||
/**
|
||||
* Maps old setting keys to new setting keys.
|
||||
*
|
||||
* @psalm-return array<oldSettingsKey, newSettingsKey>
|
||||
*/
|
||||
public function map(): array {
|
||||
return array(
|
||||
'disable_cards' => 'disabled_cards',
|
||||
'brand_name' => 'brand_name',
|
||||
'soft_descriptor' => 'soft_descriptor',
|
||||
'payee_preferred' => 'instant_payments_only',
|
||||
'subtotal_mismatch_behavior' => 'subtotal_adjustment',
|
||||
'landing_page' => 'landing_page',
|
||||
'smart_button_language' => 'button_language',
|
||||
'prefix' => 'invoice_prefix',
|
||||
'intent' => '',
|
||||
'vault_enabled_dcc' => 'save_card_details',
|
||||
'blocks_final_review_enabled' => 'enable_pay_now',
|
||||
'logging_enabled' => 'enable_logging',
|
||||
'vault_enabled' => 'save_paypal_and_venmo',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a mapped key from the new settings.
|
||||
*
|
||||
* @param string $old_key The key from the legacy settings.
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return mixed The value of the mapped setting, (null if not found).
|
||||
*/
|
||||
public function mapped_value( string $old_key, array $settings_model ) {
|
||||
$settings_map = $this->map();
|
||||
$new_key = $settings_map[ $old_key ] ?? false;
|
||||
switch ( $old_key ) {
|
||||
case 'subtotal_mismatch_behavior':
|
||||
return $this->mapped_mismatch_behavior_value( $settings_model );
|
||||
|
||||
case 'landing_page':
|
||||
return $this->mapped_landing_page_value( $settings_model );
|
||||
|
||||
case 'intent':
|
||||
return $this->mapped_intent_value( $settings_model );
|
||||
|
||||
case 'blocks_final_review_enabled':
|
||||
return $this->mapped_pay_now_value( $settings_model );
|
||||
|
||||
default:
|
||||
return $settings_model[ $new_key ] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the 'mismatch_behavior' from the new settings.
|
||||
*
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return 'extra_line'|'ditch'|null The mapped 'mismatch_behavior' setting value.
|
||||
*/
|
||||
protected function mapped_mismatch_behavior_value( array $settings_model ): ?string {
|
||||
$subtotal_adjustment = $settings_model['subtotal_adjustment'] ?? false;
|
||||
|
||||
if ( ! $subtotal_adjustment ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $subtotal_adjustment === 'correction' ? PurchaseUnitSanitizer::MODE_EXTRA_LINE : PurchaseUnitSanitizer::MODE_DITCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the 'landing_page' from the new settings.
|
||||
*
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return 'LOGIN'|'BILLING'|'NO_PREFERENCE'|null The mapped 'landing_page' setting value.
|
||||
*/
|
||||
protected function mapped_landing_page_value( array $settings_model ): ?string {
|
||||
$landing_page = $settings_model['landing_page'] ?? false;
|
||||
|
||||
if ( ! $landing_page ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $landing_page === 'login'
|
||||
? ApplicationContext::LANDING_PAGE_LOGIN
|
||||
: ( $landing_page === 'guest_checkout'
|
||||
? ApplicationContext::LANDING_PAGE_BILLING
|
||||
: ApplicationContext::LANDING_PAGE_NO_PREFERENCE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the order intent from the new settings.
|
||||
*
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return 'AUTHORIZE'|'CAPTURE'|null The mapped 'intent' setting value.
|
||||
*/
|
||||
protected function mapped_intent_value( array $settings_model ): ?string {
|
||||
$authorize_only = $settings_model['authorize_only'] ?? null;
|
||||
$capture_virtual_orders = $settings_model['capture_virtual_orders'] ?? null;
|
||||
|
||||
if ( is_null( $authorize_only ) && is_null( $capture_virtual_orders ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $authorize_only ? 'AUTHORIZE' : 'CAPTURE';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the "Pay Now Experience" from the new settings.
|
||||
*
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
* @return bool|null The mapped 'Pay Now Experience' setting value.
|
||||
*/
|
||||
protected function mapped_pay_now_value( array $settings_model ): ?bool {
|
||||
$enable_pay_now = $settings_model['enable_pay_now'] ?? null;
|
||||
|
||||
if ( is_null( $enable_pay_now ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ! $enable_pay_now;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,11 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
use RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO;
|
||||
|
||||
/**
|
||||
|
@ -23,6 +27,8 @@ class StylingSettingsMapHelper {
|
|||
|
||||
use ContextTrait;
|
||||
|
||||
protected const BUTTON_NAMES = array( GooglePayGateway::ID, ApplePayGateway::ID );
|
||||
|
||||
/**
|
||||
* Maps old setting keys to new setting style names.
|
||||
*
|
||||
|
@ -45,6 +51,8 @@ class StylingSettingsMapHelper {
|
|||
'disable_funding' => '',
|
||||
'googlepay_button_enabled' => '',
|
||||
'applepay_button_enabled' => '',
|
||||
'smart_button_enable_styling_per_location' => '',
|
||||
'pay_later_button_enabled' => '',
|
||||
);
|
||||
|
||||
foreach ( $this->locations_map() as $old_location_name => $new_location_name ) {
|
||||
|
@ -62,25 +70,32 @@ class StylingSettingsMapHelper {
|
|||
*
|
||||
* @param string $old_key The key from the legacy settings.
|
||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||
* @param AbstractDataModel|null $payment_settings The payment settings model.
|
||||
*
|
||||
* @return mixed The value of the mapped setting, (null if not found).
|
||||
*/
|
||||
public function mapped_value( string $old_key, array $styling_models ) {
|
||||
public function mapped_value( string $old_key, array $styling_models, ?AbstractDataModel $payment_settings ) {
|
||||
switch ( $old_key ) {
|
||||
case 'smart_button_locations':
|
||||
return $this->mapped_smart_button_locations_value( $styling_models );
|
||||
|
||||
case 'smart_button_enable_styling_per_location':
|
||||
return true;
|
||||
|
||||
case 'pay_later_button_locations':
|
||||
return $this->mapped_pay_later_button_locations_value( $styling_models );
|
||||
|
||||
case 'disable_funding':
|
||||
return $this->mapped_disabled_funding_value( $styling_models );
|
||||
return $this->mapped_disabled_funding_value( $styling_models, $payment_settings );
|
||||
|
||||
case 'googlepay_button_enabled':
|
||||
return $this->mapped_google_pay_or_apple_pay_enabled_value( $styling_models, 'googlepay' );
|
||||
return $this->mapped_button_enabled_value( $styling_models, GooglePayGateway::ID );
|
||||
|
||||
case 'applepay_button_enabled':
|
||||
return $this->mapped_google_pay_or_apple_pay_enabled_value( $styling_models, 'applepay' );
|
||||
return $this->mapped_button_enabled_value( $styling_models, ApplePayGateway::ID );
|
||||
|
||||
case 'pay_later_button_enabled':
|
||||
return $this->mapped_pay_later_button_enabled_value( $styling_models, $payment_settings );
|
||||
|
||||
default:
|
||||
foreach ( $this->locations_map() as $old_location_name => $new_location_name ) {
|
||||
|
@ -198,7 +213,7 @@ class StylingSettingsMapHelper {
|
|||
$enabled_locations = array();
|
||||
$locations = array_flip( $this->locations_map() );
|
||||
foreach ( $styling_models as $model ) {
|
||||
if ( ! $model->enabled || ! in_array( 'paylater', $model->methods, true ) ) {
|
||||
if ( ! $model->enabled || ! in_array( 'pay-later', $model->methods, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -215,51 +230,115 @@ class StylingSettingsMapHelper {
|
|||
* Retrieves the mapped disabled funding value from the new settings.
|
||||
*
|
||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||
* @param AbstractDataModel|null $payment_settings The payment settings model.
|
||||
* @return array|null The list of disabled funding, or null if none are disabled.
|
||||
*/
|
||||
protected function mapped_disabled_funding_value( array $styling_models ): ?array {
|
||||
$disabled_funding = array();
|
||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||
|
||||
foreach ( $styling_models as $model ) {
|
||||
if ( $model->location !== $locations_to_context_map[ $this->context() ] || in_array( 'venmo', $model->methods, true ) ) {
|
||||
continue;
|
||||
protected function mapped_disabled_funding_value( array $styling_models, ?AbstractDataModel $payment_settings ): ?array {
|
||||
if ( is_null( $payment_settings ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$disabled_funding = array();
|
||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
||||
assert( $payment_settings instanceof PaymentSettings );
|
||||
|
||||
foreach ( $styling_models as $model ) {
|
||||
if ( $model->location === $current_context ) {
|
||||
if ( ! in_array( 'venmo', $model->methods, true ) || ! $payment_settings->get_venmo_enabled() ) {
|
||||
$disabled_funding[] = 'venmo';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $disabled_funding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped enabled or disabled PayLater button value from the new settings.
|
||||
*
|
||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||
* @param AbstractDataModel|null $payment_settings The payment settings model.
|
||||
* @return int|null The enabled (1) or disabled (0) state or null if it should fall back to old settings value.
|
||||
*/
|
||||
protected function mapped_pay_later_button_enabled_value( array $styling_models, ?AbstractDataModel $payment_settings ): ?int {
|
||||
|
||||
if ( ! $payment_settings instanceof PaymentSettings ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped enabled/disabled Google Pay or Apple Pay value from the new settings.
|
||||
*
|
||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||
* @param 'googlepay'|'applepay' $button_name The button name ('googlepay' or 'applepay').
|
||||
* @return int The enabled (1) or disabled (0) state.
|
||||
* @throws RuntimeException If an invalid button name is provided.
|
||||
*/
|
||||
protected function mapped_google_pay_or_apple_pay_enabled_value( array $styling_models, string $button_name ): ?int {
|
||||
if ( $button_name !== 'googlepay' && $button_name !== 'applepay' ) {
|
||||
throw new RuntimeException( 'Wrong button name is provided. Either "googlepay" or "applepay" can be used' );
|
||||
}
|
||||
|
||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
||||
|
||||
foreach ( $styling_models as $model ) {
|
||||
if ( ! $model->enabled
|
||||
|| $model->location !== $locations_to_context_map[ $this->context() ]
|
||||
|| ! in_array( $button_name, $model->methods, true )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $model->enabled && $model->location === $current_context ) {
|
||||
if ( in_array( 'pay-later', $model->methods, true ) && $payment_settings->get_paylater_enabled() ) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped enabled or disabled button value from the new settings.
|
||||
*
|
||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||
* @param string $button_name The button name (see {@link self::BUTTON_NAMES}).
|
||||
* @return int The enabled (1) or disabled (0) state.
|
||||
* @throws RuntimeException If an invalid button name is provided.
|
||||
*/
|
||||
protected function mapped_button_enabled_value( array $styling_models, string $button_name ): ?int {
|
||||
if ( ! in_array( $button_name, self::BUTTON_NAMES, true ) ) {
|
||||
throw new RuntimeException( 'Wrong button name is provided.' );
|
||||
}
|
||||
|
||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
||||
|
||||
foreach ( $styling_models as $model ) {
|
||||
if ( $model->enabled && $model->location === $current_context ) {
|
||||
if ( in_array( $button_name, $model->methods, true ) && $this->is_gateway_enabled( $button_name ) ) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $current_context === 'classic_checkout' ) {
|
||||
/**
|
||||
* Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout)
|
||||
* In case if the button is disabled from the styling settings but the gateway itself is enabled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
add_action(
|
||||
'woocommerce_paypal_payments_checkout_button_render',
|
||||
static function (): void {
|
||||
?>
|
||||
<style data-hide-gateway='<?php echo esc_attr( GooglePayGateway::ID ); ?>'>
|
||||
.wc_payment_method.payment_method_ppcp-googlepay {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the payment gateway with the given name is enabled.
|
||||
*
|
||||
* @param string $gateway_name The gateway name.
|
||||
* @return bool True if the payment gateway with the given name is enabled, otherwise false.
|
||||
*/
|
||||
protected function is_gateway_enabled( string $gateway_name ): bool {
|
||||
$gateway_settings = get_option( "woocommerce_{$gateway_name}_settings", array() );
|
||||
$gateway_enabled = $gateway_settings['enabled'] ?? false;
|
||||
|
||||
return $gateway_enabled === 'yes';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* A helper for mapping old and new subscription settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Compat\Settings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
|
||||
/**
|
||||
* Handles mapping between old and new subscription settings.
|
||||
*
|
||||
* In the new settings UI, the Subscriptions mode value is set automatically based on the merchant type.
|
||||
* This class fakes the mapping and injects the appropriate value based on the merchant:
|
||||
* - Non-vaulting merchants will use PayPal Subscriptions.
|
||||
* - Merchants with vaulting will use PayPal Vaulting.
|
||||
* - Disabled subscriptions can be controlled using a filter.
|
||||
*
|
||||
* @psalm-import-type newSettingsKey from SettingsMap
|
||||
* @psalm-import-type oldSettingsKey from SettingsMap
|
||||
*/
|
||||
class SubscriptionSettingsMapHelper {
|
||||
|
||||
public const OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_VAULTING = 'vaulting_api';
|
||||
public const OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_SUBSCRIPTIONS = 'subscriptions_api';
|
||||
public const OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_DISABLED = 'disable_paypal_subscriptions';
|
||||
|
||||
/**
|
||||
* The subscription helper.
|
||||
*
|
||||
* @var SubscriptionHelper $subscription_helper
|
||||
*/
|
||||
protected SubscriptionHelper $subscription_helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
*/
|
||||
public function __construct( SubscriptionHelper $subscription_helper ) {
|
||||
$this->subscription_helper = $subscription_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the old subscription setting key.
|
||||
*
|
||||
* This method creates a placeholder mapping as this setting doesn't exist in the new settings.
|
||||
* The Subscriptions mode value is set automatically based on the merchant type.
|
||||
*
|
||||
* @psalm-return array<oldSettingsKey, newSettingsKey>
|
||||
*/
|
||||
public function map(): array {
|
||||
return array( 'subscriptions_mode' => '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapped value for the subscriptions_mode key from the new settings.
|
||||
*
|
||||
* @param string $old_key The key from the legacy settings.
|
||||
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
|
||||
*
|
||||
* @return 'vaulting_api'|'subscriptions_api'|'disable_paypal_subscriptions'|null The mapped subscriptions_mode value, or null if not applicable.
|
||||
*/
|
||||
public function mapped_value( string $old_key, array $settings_model ): ?string {
|
||||
if ( $old_key !== 'subscriptions_mode' || ! $this->subscription_helper->plugin_is_active() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$vaulting = $settings_model['save_paypal_and_venmo'] ?? false;
|
||||
$subscription_mode_value = $vaulting ? self::OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_VAULTING : self::OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_SUBSCRIPTIONS;
|
||||
|
||||
/**
|
||||
* Allows disabling the subscription mode when using the new settings UI.
|
||||
*
|
||||
* @returns bool true if the subscription mode should be disabled, false otherwise (default is false).
|
||||
*/
|
||||
$subscription_mode_disabled = (bool) apply_filters( 'woocommerce_paypal_payments_subscription_mode_disabled', false );
|
||||
|
||||
return $subscription_mode_disabled ? self::OLD_SETTINGS_SUBSCRIPTION_MODE_VALUE_DISABLED : $subscription_mode_value;
|
||||
}
|
||||
}
|
|
@ -81,7 +81,7 @@ const GooglePayComponent = ( { isEditing, buttonAttributes } ) => {
|
|||
};
|
||||
|
||||
const features = [ 'products' ];
|
||||
|
||||
if ( buttonConfig?.is_enabled ) {
|
||||
registerExpressPaymentMethod( {
|
||||
name: buttonData.id,
|
||||
title: `PayPal - ${ buttonData.title }`,
|
||||
|
@ -100,3 +100,4 @@ registerExpressPaymentMethod( {
|
|||
style: [ 'height', 'borderRadius' ],
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies;
|
|||
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
|
||||
use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
|
@ -106,6 +105,7 @@ return array(
|
|||
'FR', // France
|
||||
'DE', // Germany
|
||||
'GR', // Greece
|
||||
'HK', // Hong Kong
|
||||
'HU', // Hungary
|
||||
'IE', // Ireland
|
||||
'IT', // Italy
|
||||
|
@ -119,6 +119,7 @@ return array(
|
|||
'PL', // Poland
|
||||
'PT', // Portugal
|
||||
'RO', // Romania
|
||||
'SG', // Singapore
|
||||
'SK', // Slovakia
|
||||
'SI', // Slovenia
|
||||
'ES', // Spain
|
||||
|
@ -148,6 +149,7 @@ return array(
|
|||
'CZK', // Czech Koruna
|
||||
'DKK', // Danish Krone
|
||||
'EUR', // Euro
|
||||
'HKD', // Hong Kong Dollar
|
||||
'GBP', // British Pound Sterling
|
||||
'HUF', // Hungarian Forint
|
||||
'ILS', // Israeli New Shekel
|
||||
|
@ -157,6 +159,7 @@ return array(
|
|||
'NZD', // New Zealand Dollar
|
||||
'PHP', // Philippine Peso
|
||||
'PLN', // Polish Zloty
|
||||
'SGD', // Singapur-Dollar
|
||||
'SEK', // Swedish Krona
|
||||
'THB', // Thai Baht
|
||||
'TWD', // New Taiwan Dollar
|
||||
|
@ -174,7 +177,7 @@ return array(
|
|||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wc-subscriptions.helper' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$container->get( 'wcgateway.settings.status' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
|
@ -221,15 +224,15 @@ return array(
|
|||
},
|
||||
|
||||
'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
if ( $state->current_state() < State::STATE_ONBOARDED ) {
|
||||
$is_connected = $container->get( 'settings.flag.is-connected' );
|
||||
if ( ! $is_connected ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product_status = $container->get( 'googlepay.helpers.apm-product-status' );
|
||||
assert( $product_status instanceof ApmProductStatus );
|
||||
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
|
||||
$enabled = $product_status->is_active();
|
||||
|
|
|
@ -89,6 +89,7 @@ class ApmProductStatus extends ProductStatus {
|
|||
}
|
||||
}
|
||||
|
||||
// Settings used as a cache; `settings->set` is compatible with new UI.
|
||||
if ( $has_capability ) {
|
||||
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
|
||||
} else {
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
|
|||
|
||||
use WC_Order;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
|
@ -44,7 +43,30 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ) : bool {
|
||||
add_action( 'after_setup_theme', fn() => $this->run_with_translations( $c ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up WP hooks that depend on translation features.
|
||||
* Runs after the theme setup, when translations are available, which is fired
|
||||
* before the `init` hook, which usually contains most of the logic.
|
||||
*
|
||||
* @param ContainerInterface $c The DI container.
|
||||
* @return void
|
||||
*/
|
||||
private function run_with_translations( ContainerInterface $c ) : void {
|
||||
// When Local APMs are disabled, none of the following hooks are needed.
|
||||
if ( ! $this->should_add_local_apm_gateways( $c ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "woocommerce_payment_gateways" filter is responsible for ADDING
|
||||
* custom payment gateways to WooCommerce. Here, we add all the local
|
||||
* APM gateways to the filtered list, so they become available later on.
|
||||
*/
|
||||
add_filter(
|
||||
'woocommerce_payment_gateways',
|
||||
/**
|
||||
|
@ -53,14 +75,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function ( $methods ) use ( $c ) {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return $methods;
|
||||
}
|
||||
$onboarding_state = $c->get( 'onboarding.state' );
|
||||
if ( $onboarding_state->current_state() === State::STATE_START ) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
if ( ! is_array( $methods ) ) {
|
||||
return $methods;
|
||||
}
|
||||
|
@ -74,6 +88,10 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the "available gateways" list by REMOVING gateways that
|
||||
* are not available for the current customer.
|
||||
*/
|
||||
add_filter(
|
||||
'woocommerce_available_payment_gateways',
|
||||
/**
|
||||
|
@ -82,42 +100,38 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function ( $methods ) use ( $c ) {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return $methods;
|
||||
}
|
||||
if ( ! is_array( $methods ) ) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
if ( ! isset( WC()->customer ) ) {
|
||||
if ( ! is_array( $methods ) || is_admin() || empty( WC()->customer ) ) {
|
||||
// Don't restrict the gateway list on wp-admin or when no customer is known.
|
||||
return $methods;
|
||||
}
|
||||
|
||||
$payment_methods = $c->get( 'ppcp-local-apms.payment-methods' );
|
||||
$customer_country = WC()->customer->get_billing_country() ?: WC()->customer->get_shipping_country();
|
||||
$site_currency = get_woocommerce_currency();
|
||||
|
||||
$payment_methods = $c->get( 'ppcp-local-apms.payment-methods' );
|
||||
// Remove unsupported gateways from the customer's payment options.
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
if (
|
||||
! in_array( $customer_country, $payment_method['countries'], true )
|
||||
|| ! in_array( $site_currency, $payment_method['currencies'], true )
|
||||
) {
|
||||
$is_currency_supported = in_array( $site_currency, $payment_method['currencies'], true );
|
||||
$is_country_supported = in_array( $customer_country, $payment_method['countries'], true );
|
||||
|
||||
if ( ! $is_currency_supported || ! $is_country_supported ) {
|
||||
unset( $methods[ $payment_method['id'] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds all local APM gateways in the "payment_method_type" block registry
|
||||
* to make the payment methods available in the Block Checkout.
|
||||
*
|
||||
* @see IntegrationRegistry::initialize
|
||||
*/
|
||||
add_action(
|
||||
'woocommerce_blocks_payment_method_type_registration',
|
||||
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return;
|
||||
}
|
||||
$payment_methods = $c->get( 'ppcp-local-apms.payment-methods' );
|
||||
foreach ( $payment_methods as $key => $value ) {
|
||||
$payment_method_registry->register( $c->get( 'ppcp-local-apms.' . $key . '.payment-method' ) );
|
||||
|
@ -128,9 +142,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
add_filter(
|
||||
'woocommerce_paypal_payments_localized_script_data',
|
||||
function ( array $data ) use ( $c ) {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return $data;
|
||||
}
|
||||
$payment_methods = $c->get( 'ppcp-local-apms.payment-methods' );
|
||||
|
||||
$default_disable_funding = $data['url_params']['disable-funding'] ?? '';
|
||||
|
@ -149,9 +160,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $order_id ) use ( $c ) {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return;
|
||||
}
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order instanceof WC_Order ) {
|
||||
return;
|
||||
|
@ -184,9 +192,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
add_action(
|
||||
'woocommerce_paypal_payments_payment_capture_completed_webhook_handler',
|
||||
function( WC_Order $wc_order, string $order_id ) use ( $c ) {
|
||||
if ( ! self::should_add_local_apm_gateways( $c ) ) {
|
||||
return;
|
||||
}
|
||||
$payment_methods = $c->get( 'ppcp-local-apms.payment-methods' );
|
||||
if (
|
||||
! $this->is_local_apm( $wc_order->get_payment_method(), $payment_methods )
|
||||
|
@ -202,8 +207,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,11 +233,41 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* @return bool
|
||||
*/
|
||||
private function should_add_local_apm_gateways( ContainerInterface $container ) : bool {
|
||||
// APMs are only available after merchant onboarding is completed.
|
||||
$is_connected = $container->get( 'settings.flag.is-connected' );
|
||||
if ( ! $is_connected ) {
|
||||
/**
|
||||
* When the merchant is _not_ connected yet, we still need to
|
||||
* register the APM gateways in one case:
|
||||
*
|
||||
* During the authentication process (which happens via a REST call)
|
||||
* the gateways need to be present, so they can be correctly
|
||||
* pre-configured for new merchants.
|
||||
*/
|
||||
return $this->is_rest_request();
|
||||
}
|
||||
|
||||
// The general plugin functionality must be enabled.
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
return $settings->has( 'enabled' )
|
||||
&& $settings->get( 'enabled' ) === true
|
||||
&& $settings->has( 'allow_local_apm_gateways' )
|
||||
if ( ! $settings->has( 'enabled' ) || ! $settings->get( 'enabled' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register APM gateways, when the relevant setting is active.
|
||||
return $settings->has( 'allow_local_apm_gateways' )
|
||||
&& $settings->get( 'allow_local_apm_gateways' ) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, whether the current request is trying to access a WooCommerce REST endpoint.
|
||||
*
|
||||
* @return bool True, if the request path matches the WC-Rest namespace.
|
||||
*/
|
||||
private function is_rest_request(): bool {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '' );
|
||||
|
||||
return str_contains( $request_uri, '/wp-json/wc/' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ class LocalApmProductStatus extends ProductStatus {
|
|||
}
|
||||
}
|
||||
|
||||
// Settings used as a cache; `settings->set` is compatible with new UI.
|
||||
if ( $has_capability ) {
|
||||
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
|
||||
} else {
|
||||
|
|
|
@ -346,7 +346,9 @@ window.ppcp_onboarding_productionCallback = function ( ...args ) {
|
|||
const sandboxSwitchElement = document.querySelector( '#ppcp-sandbox_on' );
|
||||
|
||||
sandboxSwitchElement?.addEventListener( 'click', () => {
|
||||
document.querySelector( '.woocommerce-save-button' )?.removeAttribute( 'disabled' );
|
||||
document
|
||||
.querySelector( '.woocommerce-save-button' )
|
||||
?.removeAttribute( 'disabled' );
|
||||
} );
|
||||
|
||||
const validate = () => {
|
||||
|
@ -389,7 +391,8 @@ window.ppcp_onboarding_productionCallback = function ( ...args ) {
|
|||
|
||||
const isSandboxInBackend =
|
||||
PayPalCommerceGatewayOnboarding.current_env === 'sandbox';
|
||||
if ( sandboxSwitchElement.checked !== isSandboxInBackend ) {
|
||||
|
||||
if ( sandboxSwitchElement?.checked !== isSandboxInBackend ) {
|
||||
sandboxSwitchElement.checked = isSandboxInBackend;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
return array(
|
||||
'api.paypal-host' => function( ContainerInterface $container ) : string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
/**
|
||||
* The current environment.
|
||||
*
|
||||
|
@ -34,7 +35,7 @@ return array(
|
|||
|
||||
},
|
||||
'api.paypal-website-url' => function( ContainerInterface $container ) : string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$environment = $container->get( 'settings.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
if ( $environment->current_environment_is( Environment::SANDBOX ) ) {
|
||||
return $container->get( 'api.paypal-website-url-sandbox' );
|
||||
|
@ -56,9 +57,16 @@ return array(
|
|||
|
||||
return $state->current_state() >= State::STATE_ONBOARDED;
|
||||
},
|
||||
'onboarding.environment' => function( ContainerInterface $container ) : Environment {
|
||||
'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return new Environment( $settings );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
return $settings->has( 'sandbox_on' ) && $settings->get( 'sandbox_on' );
|
||||
},
|
||||
'settings.environment' => function ( ContainerInterface $container ) : Environment {
|
||||
return new Environment(
|
||||
$container->get( 'settings.flag.is-sandbox' )
|
||||
);
|
||||
},
|
||||
|
||||
'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets {
|
||||
|
@ -68,7 +76,7 @@ return array(
|
|||
$container->get( 'onboarding.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
$state,
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$login_seller_endpoint,
|
||||
$container->get( 'wcgateway.current-ppcp-settings-page-id' )
|
||||
);
|
||||
|
|
|
@ -133,9 +133,11 @@ class OnboardingRESTController {
|
|||
* @return array
|
||||
*/
|
||||
public function get_status( $request ) {
|
||||
$environment = $this->container->get( 'onboarding.environment' );
|
||||
$environment = $this->container->get( 'settings.environment' );
|
||||
$state = $this->container->get( 'onboarding.state' );
|
||||
|
||||
// Legacy onboarding module; using `State::STATE_ONBOARDED` checks is valid here.
|
||||
|
||||
return array(
|
||||
'environment' => $environment->current_environment(),
|
||||
'onboarded' => ( $state->current_state() >= State::STATE_ONBOARDED ),
|
||||
|
|
|
@ -103,12 +103,8 @@ class OnboardingRenderer {
|
|||
'displayMode' => 'minibrowser',
|
||||
);
|
||||
|
||||
$data = $this->partner_referrals_data
|
||||
->with_products( $products )
|
||||
->data();
|
||||
|
||||
$environment = $is_production ? 'production' : 'sandbox';
|
||||
$product = 'PPCP' === $data['products'][0] ? 'ppcp' : 'express_checkout';
|
||||
$product = strtolower( $products[0] ?? 'express_checkout' );
|
||||
$cache_key = $environment . '-' . $product;
|
||||
|
||||
$onboarding_url = new OnboardingUrl( $this->cache, $cache_key, get_current_user_id() );
|
||||
|
@ -122,8 +118,7 @@ class OnboardingRenderer {
|
|||
|
||||
$onboarding_url->init();
|
||||
|
||||
$data = $this->partner_referrals_data
|
||||
->append_onboarding_token( $data, $onboarding_url->token() ?: '' );
|
||||
$data = $this->partner_referrals_data->data( $products, $onboarding_url->token() ?: '' );
|
||||
|
||||
$url = $is_production ? $this->production_partner_referrals->signup_link( $data ) : $this->sandbox_partner_referrals->signup_link( $data );
|
||||
$url = add_query_arg( $args, $url );
|
||||
|
|
|
@ -95,6 +95,7 @@ class SaveConfig {
|
|||
* @param array $config The configurator config.
|
||||
*/
|
||||
public function save_config( array $config ): void {
|
||||
// TODO new-ux: We should convert this to a new AbstractDataModel class in the settings folder!
|
||||
$this->settings->set( 'pay_later_enable_styling_per_messaging_location', true );
|
||||
$this->settings->set( 'pay_later_messaging_enabled', true );
|
||||
|
||||
|
|
|
@ -374,7 +374,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
function( $subscription ) use ( $c ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id ) {
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$environment = $c->get( 'settings.environment' );
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
?>
|
||||
<tr>
|
||||
|
@ -488,7 +488,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$environment = $c->get( 'settings.environment' );
|
||||
echo '<div class="options_group subscription_pricing show_if_subscription hidden">';
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
echo '</div>';
|
||||
|
@ -522,7 +522,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$environment = $c->get( 'settings.environment' );
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# Applying Default Configuration After Onboarding
|
||||
|
||||
The `OnboardingProfile` has a property named `setup_done`, which indicated whether the default
|
||||
configuration was set up.
|
||||
|
||||
### `OnboardingProfile::is_setup_done()`
|
||||
|
||||
This flag indicated, whether the default plugin configuration was applied or not.
|
||||
It's set to true after the merchant's authentication attempt was successful, and settings were
|
||||
adjusted.
|
||||
|
||||
The only way to reset this flag, is to enable the "**Start Over**" toggle and disconnecting the
|
||||
merchant:
|
||||
https://example.com/wp-admin/admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&panel=settings#disconnect-merchant
|
||||
|
||||
### `class SettingsDataManager`
|
||||
|
||||
The `SettingsDataManager` service is responsible for applying all defaults options at the end of the
|
||||
onboarding process.
|
||||
|
||||
### `SettingsDataManager::set_defaults_for_new_merchant()`
|
||||
|
||||
This method expects a DTO argument (`ConfigurationFlagsDTO`) that provides relevant details about
|
||||
the merchant and onboarding choices.
|
||||
|
||||
It verifies, if default settings were already applied (by checking the
|
||||
`OnboardingProfile::is_setup_done()` state). If not done yet, the DTO object is inspected to
|
||||
initialize the plugin's configuration, before marking the `setup_done` flag as completed.
|
||||
|
||||
## Default Settings Matrix
|
||||
|
||||
### Decision Flags
|
||||
|
||||
- **Country**: The merchant country.
|
||||
- According to PayPal settings, not the WooCommerce country
|
||||
- Test case: Set Woo country to Germany and sign in with a US merchant account; this should
|
||||
trigger the "Country: US" branches below.
|
||||
- **Seller Type**: Business or Casual.
|
||||
- According to PayPal, not the onboarding choice
|
||||
- Test case: Choose "Personal" during onboarding but log in with a business account; this should
|
||||
trigger the "Account: Business" branches below.
|
||||
- **Subscriptions**: An onboarding choice on the "Products" screen.
|
||||
- **Cards**: An onboarding choice, on the "Checkout Options" screen.
|
||||
- Refers to the first option on the checkout options screen ("Custom Card Fields", etc.)
|
||||
|
||||
### Payment Methods
|
||||
|
||||
By default, all payment methods are turned off after onboarding, unless the conditions specified in
|
||||
the following table are met.
|
||||
|
||||
| Payment Method | Country | Seller Type | Subscriptions | Cards | Notes |
|
||||
|----------------|---------|-------------|---------------|-------|-------------------------------|
|
||||
| Venmo | US | *any* | *any* | *any* | Always |
|
||||
| Pay Later | US | *any* | *any* | *any* | Always |
|
||||
| ACDC | US | Business | *any* | ✅ | Greyed out for Casual Sellers |
|
||||
| BCDC | US | *any* | *any* | ✅ | |
|
||||
| Apple Pay | US | Business | *any* | ✅ | Based on feature eligibility |
|
||||
| Google Pay | US | Business | *any* | ✅ | Based on feature eligibility |
|
||||
| All APMs | US | Business | *any* | ✅ | Based on feature eligibility |
|
||||
|
||||
### Settings
|
||||
|
||||
| Feature | Country | Seller-Type | Subscriptions | Cards | Notes |
|
||||
|-----------------------------|---------|-------------|---------------|-------|----------------------------|
|
||||
| Pay Now Experience | US | _any_ | _any_ | _any_ | |
|
||||
| Save PayPal and Venmo | US | Business | ✅ | _any_ | |
|
||||
| Save Credit and Debit Cards | US | Business | ✅ | ✅ | Requires ACDC eligibility* |
|
||||
|
||||
- `*` If merchant has no ACDC eligibility, the setting should be disabled (not toggleable).
|
||||
|
||||
### Styling
|
||||
|
||||
All US merchants use the same settings, regardless of onboarding choices.
|
||||
|
||||
| Button Location | Enabled | Displayed Payment Methods |
|
||||
|------------------|---------|-------------------------------------------------|
|
||||
| Cart | ✅ | PayPal, Venmo, Pay Later, Google Pay, Apple Pay |
|
||||
| Classic Checkout | ✅ | PayPal, Venmo, Pay Later, Google Pay, Apple Pay |
|
||||
| Express Checkout | ✅ | PayPal, Venmo, Pay Later, Google Pay, Apple Pay |
|
||||
| Mini Cart | ✅ | PayPal, Venmo, Pay Later, Google Pay, Apple Pay |
|
||||
| Product Page | ✅ | PayPal, Venmo, Pay Later |
|
||||
|
||||
### Pay Later Messaging
|
||||
|
||||
All US merchants use the same settings, regardless of onboarding choices.
|
||||
|
||||
| Location | Enabled |
|
||||
|-------------------|---------|
|
||||
| Product | ✅ |
|
||||
| Cart | ✅ |
|
||||
| Checkout | ✅ |
|
||||
| Home | ❌ |
|
||||
| Shop | ❌ |
|
||||
| WooCommerce Block | ❌ |
|
120
modules/ppcp-settings/docs/authentication-flows.md
Normal file
120
modules/ppcp-settings/docs/authentication-flows.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Authentication Flows
|
||||
|
||||
The settings UI offers two distinct authentication methods:
|
||||
|
||||
- OAuth
|
||||
- Direct API
|
||||
|
||||
## OAuth
|
||||
|
||||
This is the usual authentication UI for most users. It opens a "PayPal popup" with a login mask.
|
||||
The authentication flow consists of **three steps**:
|
||||
|
||||
- Generate a referral URL with a special token
|
||||
- Translate a one-time OAuth secret into permanent API credentials
|
||||
- Complete authentication by confirming the token from step 1
|
||||
|
||||
**Usage:**
|
||||
|
||||
1. Available on the first onboarding page (for sandbox login), or on the last page of the onboarding wizard.
|
||||
2. Authentication is initiated by clicking a "Connect" button which opens a popup with a PayPal login mask
|
||||
- Sometimes the login opens in a new tab, mainly on Mac when using the browser in full-screen mode
|
||||
3. After completing the login, the final page shows a "Return to your store" button; clicking that button closes the popup/tab and completes the authentication process
|
||||
|
||||
**More details on what happens:**
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant R as React API
|
||||
participant S as PHP Server
|
||||
|
||||
R->>S: Request partner referral URL
|
||||
Note over S: Generate and store a one-time token
|
||||
create participant W as WooCommerce API
|
||||
S->>W: Request referral URL
|
||||
destroy W
|
||||
W->>S: Generate the full partner referral URL
|
||||
S->>R: Return referral URL
|
||||
create participant P as PayPal Popup
|
||||
R->>P: Open PayPal popup, which was generated by WooCommerce APi
|
||||
Note over P: Complete login inside Popup
|
||||
P->>R: Call JS function with OAuth ID and shared secret
|
||||
R->>S: Send OAuth data to REST endpoint
|
||||
create participant PP as PayPal API
|
||||
S->>PP: Request permanent credentials
|
||||
PP->>S: Translate one-time secret to permanent credentials
|
||||
destroy P
|
||||
P->>R: Redirect browser tab with unique token
|
||||
Note over R: App unmounts during redirect
|
||||
|
||||
Note over S: During page load
|
||||
Note over S: Verify token and finalize authentication
|
||||
S->>PP: Request merchant details
|
||||
destroy PP
|
||||
PP->>S: Return merchant details (e.g. country)
|
||||
Note over S: Render the settings page with React app
|
||||
S->>R: Boot react app in "settings mode"
|
||||
```
|
||||
|
||||
1. Authentication starts _before_ the "Connect" button is rendered, as we generate a one-time partner referral URL
|
||||
- See `ConnectionUrlGenerator::generate()`
|
||||
- This referral URL configures PayPal: Which items render inside the Popup? What is the "return
|
||||
URL" for the final step? Is it a sandbox or live login?
|
||||
2. _...The merchant completes the login or account creation flow inside the popup..._
|
||||
3. During page-load of the final confirmation page inside the popup: PayPal directly calls a JS function on the WooCommerce settings page, i.e. the popup communicates with the open WooCommerce tab. This JS function sends an oauth ID and shared secret (OTP) to a REST endpoint
|
||||
- See `AuthenticatoinRestEndpoint::connect_oauth()`
|
||||
- See `AuthenticationManager::authenticate_via_oauth()` → translates the one-time shared secret
|
||||
into a permanent client secret
|
||||
- At this stage, the authentication is _incomplete_, as some details are only provided by the
|
||||
final step
|
||||
4. When clicking the "Return to store" button, the popup closes and the WooCommerce settings page "reloads"; it's actually a _redirect_ which is initiated by PayPal and receives a unique token (which was generated by the `ConnectionUrlGenerator`) that is required to complete authentication.
|
||||
- See `ConnectionListener::process()`
|
||||
- See `AuthenticationManager::finish_oauth_authentication()`
|
||||
- This listener runs on every wp-admin page load and bails if the required token is not present
|
||||
5. After the final page reload, the React app directly enters "Settings mode"
|
||||
|
||||
## Direct API
|
||||
|
||||
This method is only available for business accounts, as it requires the merchant to create a PayPal REST app that's linked to their account.
|
||||
|
||||
<details>
|
||||
<summary><strong>Setup the PayPal REST app</strong></summary>
|
||||
|
||||
1. Visit https://developer.paypal.com/
|
||||
2. In section "Apps & Credentials" click "Create App"
|
||||
3. After the app is ready, it displays the `Client ID` and `Secret Key` values
|
||||
|
||||
</details>
|
||||
|
||||
**Usage:**
|
||||
|
||||
1. Available on the first onboarding screen, via the "See advanced options" form at the bottom of the page
|
||||
2. Activate the "Manual Connection" toggle; then enter the `Client ID` and `Secret Key` and hit Enter
|
||||
|
||||
**What happens:**
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as React
|
||||
participant S as Server
|
||||
participant P as PayPal API
|
||||
|
||||
R->>S: Send credentials to REST endpoint
|
||||
S->>P: Authenticate via Direct API
|
||||
P->>S: Return authentication result
|
||||
S->>P: Request merchant details
|
||||
P->>S: Return merchant details (e.g. country)
|
||||
|
||||
Note over S: Process authentication result
|
||||
S->>R: Return authentication status
|
||||
Note over R: Update UI to authenticated state<br/>(no page reload)
|
||||
|
||||
```
|
||||
|
||||
1. Client ID and Secret are sent to a REST endpoint of the plugin. The authentication happens on server-side.
|
||||
- See `AuthenticatoinRestEndpoint::connect_direct()`
|
||||
- See `AuthenticationManager::authenticate_via_direct_api()`
|
||||
2. After authentication is completed, the merchant account is prepared on server side and a confirmation is returned to the React app.
|
||||
- See `AuthenticationManager::update_connection_details()` → condition `is_merchant_connected()`
|
||||
3. The React app directly switches to the "Settings mode" without a page reload.
|
35
modules/ppcp-settings/docs/glossary.md
Normal file
35
modules/ppcp-settings/docs/glossary.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Glossary
|
||||
|
||||
This document provides definitions and explanations of key terms used in the plugin.
|
||||
|
||||
---
|
||||
|
||||
## Eligibility
|
||||
|
||||
**Eligibility** determines whether a merchant can access a specific feature within the plugin. It is a boolean value (`true` or `false`) that depends on certain criteria, such as:
|
||||
|
||||
- **Country**: The merchant's location or the country where their business operates.
|
||||
- **Other Factors**: Additional conditions, such as subscription plans, business type, or compliance requirements.
|
||||
|
||||
If a merchant is **eligible** (`true`) for a feature, the feature will be visible and accessible in the plugin. If they are **not eligible** (`false`), the feature will be hidden or unavailable.
|
||||
|
||||
---
|
||||
|
||||
## Capability
|
||||
|
||||
**Capability** refers to the activation status of a feature for an eligible merchant. Even if a merchant is eligible for a feature, they may need to activate it in their PayPal dashboard to use it. Capability has two states:
|
||||
|
||||
- **Active**: The feature is enabled, and the merchant can configure and use it.
|
||||
- **Inactive**: The feature is not enabled, and the merchant will be guided on how to activate it (e.g., through instructions or prompts).
|
||||
|
||||
Capability ensures that eligible merchants have control over which features they want to use and configure within the plugin.
|
||||
|
||||
---
|
||||
|
||||
### Example Workflow
|
||||
|
||||
1. A merchant is **eligible** for a feature based on their country and other factors.
|
||||
2. If the feature is **active** (capability is enabled), the merchant can configure and use it.
|
||||
3. If the feature is **inactive**, the plugin will provide instructions on how to activate it.
|
||||
|
||||
---
|
|
@ -17,6 +17,7 @@ $color-text-text: #070707;
|
|||
$color-border: #AEAEAE;
|
||||
$color-divider: #F0F0F0;
|
||||
$color-error-red: #cc1818;
|
||||
$color-warning: #e2a030;
|
||||
|
||||
$shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15);
|
||||
$color-gradient-dark: #001435;
|
||||
|
@ -66,6 +67,7 @@ $card-vertical-gap: 48px;
|
|||
--color-text-teriary: #{$color-text-tertiary};
|
||||
--color-text-description: #{$color-gray-700};
|
||||
--color-error: #{$color-error-red};
|
||||
--color-warning: #{$color-warning};
|
||||
|
||||
// Default settings-block theme.
|
||||
--block-item-gap: 16px;
|
||||
|
|
|
@ -63,6 +63,10 @@
|
|||
&:hover {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&.components-button {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp--method-icon {
|
||||
|
@ -79,6 +83,234 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.ppcp--method-toggle-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-payment-methods {
|
||||
.ppcp-highlight {
|
||||
animation: ppcp-highlight-fade 2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid $color-blueberry;
|
||||
border-radius: var(--container-border-radius);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes ppcp-highlight-fade {
|
||||
0%, 20% {
|
||||
background-color: rgba($color-blueberry, 0.08);
|
||||
border-color: $color-blueberry;
|
||||
border-width: 1px;
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
border-color: $color-gray-300;
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled state styling.
|
||||
.ppcp--method-item--disabled {
|
||||
position: relative;
|
||||
|
||||
// Apply grayscale and disable interactions.
|
||||
.ppcp--method-inner {
|
||||
opacity: 0.7;
|
||||
filter: grayscale(1);
|
||||
pointer-events: none;
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
// Override text colors.
|
||||
.ppcp--method-title {
|
||||
color: $color-gray-700 !important;
|
||||
}
|
||||
|
||||
.ppcp--method-description p {
|
||||
color: $color-gray-500 !important;
|
||||
}
|
||||
|
||||
.ppcp--method-disabled-message {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
// Style all buttons and toggle controls.
|
||||
.components-button,
|
||||
.components-form-toggle {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// Hover state - only blur the inner content.
|
||||
&:hover {
|
||||
.ppcp--method-inner {
|
||||
filter: blur(2px) grayscale(1);
|
||||
}
|
||||
|
||||
.ppcp--method-disabled-message {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled overlay.
|
||||
.ppcp--method-disabled-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba($color-white, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 8;
|
||||
border-radius: var(--container-border-radius);
|
||||
pointer-events: auto;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.ppcp--method-item--disabled:hover .ppcp--method-disabled-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ppcp--method-disabled-message {
|
||||
padding: 14px 18px;
|
||||
text-align: center;
|
||||
@include font(13, 20, 500);
|
||||
color: $color-text-tertiary;
|
||||
position: relative;
|
||||
z-index: 9;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Warning message */
|
||||
.ppcp--method-warning {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
cursor: help;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
color: $color-warning;
|
||||
}
|
||||
|
||||
/* Add invisible bridge to prevent gap between icon and popover */
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 30px;
|
||||
height: 15px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// Popover bubble
|
||||
.ppcp--method-warning-message {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 15px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 250px;
|
||||
padding: 16px;
|
||||
background-color: $color-white;
|
||||
border: 1px solid $color-gray-200;
|
||||
border-radius: 4px;
|
||||
z-index: 9;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease, visibility 0.2s;
|
||||
pointer-events: none;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: $color-white;
|
||||
border-right: 1px solid $color-gray-200;
|
||||
border-bottom: 1px solid $color-gray-200;
|
||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.01);
|
||||
transform: rotate(45deg);
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp--method-notice-list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: 700;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .ppcp--method-warning-message,
|
||||
& .ppcp--method-warning-message:hover {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// For RTL support
|
||||
html[dir="rtl"] .ppcp--method-warning {
|
||||
&:before {
|
||||
left: auto;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.ppcp--method-warning-message {
|
||||
left: auto;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
|
||||
&:after {
|
||||
left: auto;
|
||||
right: 50%;
|
||||
margin-right: -6px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@
|
|||
gap: 8px;
|
||||
|
||||
&--save {
|
||||
margin-top: -4px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
|
@ -87,7 +86,7 @@
|
|||
&__field-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 18px;
|
||||
|
||||
&--acdc {
|
||||
gap: 18px;
|
||||
|
@ -98,19 +97,11 @@
|
|||
}
|
||||
|
||||
.components-radio-control {
|
||||
.components-flex {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
label {
|
||||
@include font(14, 20, 400);
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
&__option {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
&__input {
|
||||
border-color: $color-gray-700;
|
||||
margin-right: 0;
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
padding-top: var(--block-separator-gap, 32px);
|
||||
border-top: var(--block-separator-size, 1px) solid var(--block-separator-color);
|
||||
}
|
||||
|
||||
&.ppcp--pull-right {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-settings-block {
|
||||
|
|
|
@ -69,4 +69,34 @@ $width_gap: 24px;
|
|||
color: var(--color-text-teriary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
+ .ppcp-r-settings-card {
|
||||
margin-top: $card-vertical-gap;
|
||||
padding-top: $card-vertical-gap;
|
||||
border-top: 1px solid $color-gray-200;
|
||||
}
|
||||
|
||||
.ppcp--card-actions {
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&.ppcp--dimmed {
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.components-button.is-tertiary {
|
||||
transition: color 0.3s, background 0.3s;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,3 @@
|
|||
padding-bottom: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-settings {
|
||||
> * {
|
||||
margin-bottom: $card-vertical-gap;
|
||||
}
|
||||
|
||||
> *:not(:last-child) {
|
||||
padding-bottom: $card-vertical-gap;
|
||||
border-bottom: 1px solid $color-gray-200;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Modal for disconnecting the merchant from the current PayPal account.
|
||||
*/
|
||||
.ppcp--modal-disconnect {
|
||||
.ppcp--toggle-danger .components-form-toggle {
|
||||
&.is-checked {
|
||||
--wp-components-color-accent: #cc1818;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp--action-buttons {
|
||||
text-align: right;
|
||||
margin-top: 32px;
|
||||
|
||||
.components-button {
|
||||
transition: background 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -226,30 +226,91 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Payment Methods
|
||||
.ppcp-r-payment-methods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.ppcp-r-settings {
|
||||
.ppcp-highlight {
|
||||
animation: ppcp-highlight-fade 2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid $color-blueberry;
|
||||
border-radius: var(--container-border-radius);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -12px;
|
||||
right: -12px;
|
||||
bottom: -8px;
|
||||
border: 1px solid $color-blueberry;
|
||||
border-radius: 4px;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
animation: ppcp-setting-highlight-bg 2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes ppcp-highlight-fade {
|
||||
0%, 20% {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -12px;
|
||||
width: 4px;
|
||||
bottom: -8px;
|
||||
background-color: $color-blueberry;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
animation: ppcp-setting-highlight-accent 2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ppcp-setting-highlight-bg {
|
||||
0%, 15% {
|
||||
background-color: rgba($color-blueberry, 0.08);
|
||||
border-color: $color-blueberry;
|
||||
border-width: 1px;
|
||||
}
|
||||
70% {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
border-color: $color-gray-300;
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ppcp-setting-highlight-accent {
|
||||
0%, 15% {
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-settings-section {
|
||||
.ppcp--setting-row {
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
margin: 0 -12px;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($color-gray-100, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RTL support
|
||||
html[dir="rtl"] {
|
||||
.ppcp-highlight {
|
||||
&::after {
|
||||
left: auto;
|
||||
right: -12px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#configurator-eligibleContainer.css-4nclxm.e1vy3g880 {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 48px 0px 48px 48px;
|
||||
padding: 16px 0px 16px 16px;
|
||||
|
||||
#configurator-controlPanelContainer.css-5urmrq.e1vy3g880 {
|
||||
width: 374px;
|
||||
|
@ -25,6 +25,7 @@
|
|||
|
||||
.css-7xkxom, .css-8tvj6u {
|
||||
height: auto;
|
||||
width: 1.2rem;
|
||||
}
|
||||
|
||||
.css-10nkerk.ej6n7t60 {
|
||||
|
@ -37,14 +38,19 @@
|
|||
}
|
||||
|
||||
.css-1vc34jy-handler {
|
||||
height: 1.6rem;
|
||||
width: 1.6rem;
|
||||
height: 1.7em;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.css-8vwtr6-state {
|
||||
height: 1.6rem;
|
||||
height: 1.4rem;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.css-1s8clkf.etu8a6w2 {
|
||||
width: 374px;
|
||||
}
|
||||
}
|
||||
|
||||
&__subheader, #configurator-controlPanelSubHeader {
|
||||
|
@ -68,6 +74,7 @@
|
|||
|
||||
.css-rok10q, .css-dfgbdq-text_body_strong {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__publish-button {
|
||||
|
@ -110,4 +117,30 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.css-n4cwz8 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.css-1ce6bcu-container {
|
||||
width: 3rem;
|
||||
height: 1.8rem;
|
||||
}
|
||||
|
||||
#configurator-previewSectionSubHeaderText {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.css-zcyvrz.ej6n7t60 {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.css-3xbhoy-svg-size_md-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.css-7i5kpm-icon-button_base-size_xl-size_sm-secondary {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
--block-separator-gap: 24px;
|
||||
--block-header-gap: 18px;
|
||||
--panel-width: 422px;
|
||||
--sticky-offset-top: 92px; // 32px admin-bar + 60px TopNavigation height
|
||||
--preview-height-reduction: 236px; // 32px admin-bar + 60px TopNavigation height + 48px TopNavigation margin + 48px TabList height + 48px TabList margin
|
||||
--sticky-offset-top: 132px; // 32px admin-bar + 100px TopNavigation height
|
||||
--preview-height-reduction: 276px; // 32px admin-bar + 100px TopNavigation height + 48px TopNavigation margin + 48px TabList height + 48px TabList margin
|
||||
|
||||
display: flex;
|
||||
border: 1px solid var(--color-separators);
|
||||
|
|
|
@ -11,3 +11,4 @@
|
|||
|
||||
@import './components/reusable-components/payment-method-modal';
|
||||
@import './components/screens/fullscreen';
|
||||
@import './components/screens/modals';
|
||||
|
|
|
@ -6,13 +6,13 @@ import SpinnerOverlay from './ReusableComponents/SpinnerOverlay';
|
|||
import SendOnlyMessage from './Screens/SendOnlyMessage';
|
||||
import OnboardingScreen from './Screens/Onboarding';
|
||||
import SettingsScreen from './Screens/Settings';
|
||||
import { getQuery } from '../utils/navigation';
|
||||
import { getQuery, cleanUrlQueryParams } from '../utils/navigation';
|
||||
|
||||
const SettingsApp = () => {
|
||||
const { isReady: onboardingIsReady, completed: onboardingCompleted } =
|
||||
OnboardingHooks.useSteps();
|
||||
const { isReady: merchantIsReady } = CommonHooks.useStore();
|
||||
const {
|
||||
isReady: merchantIsReady,
|
||||
merchant: { isSendOnlyCountry },
|
||||
} = CommonHooks.useMerchantInfo();
|
||||
|
||||
|
@ -32,9 +32,19 @@ const SettingsApp = () => {
|
|||
loading: ! onboardingIsReady,
|
||||
} );
|
||||
|
||||
const [ activePanel, setActivePanel ] = useState(
|
||||
getQuery().panel || 'overview'
|
||||
);
|
||||
const [ activePanel, setActivePanel ] = useState( getQuery().panel );
|
||||
|
||||
const removeUnsupportedArgs = () => {
|
||||
const urlWasCleaned = cleanUrlQueryParams( [
|
||||
'page',
|
||||
'tab',
|
||||
'section',
|
||||
] );
|
||||
|
||||
if ( urlWasCleaned ) {
|
||||
setActivePanel( '' );
|
||||
}
|
||||
};
|
||||
|
||||
const Content = useMemo( () => {
|
||||
if ( ! onboardingIsReady || ! merchantIsReady ) {
|
||||
|
@ -42,16 +52,18 @@ const SettingsApp = () => {
|
|||
}
|
||||
|
||||
if ( isSendOnlyCountry ) {
|
||||
removeUnsupportedArgs();
|
||||
return <SendOnlyMessage />;
|
||||
}
|
||||
|
||||
if ( ! onboardingCompleted ) {
|
||||
removeUnsupportedArgs();
|
||||
return <OnboardingScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsScreen
|
||||
activePanel={ activePanel }
|
||||
activePanel={ activePanel || 'overview' }
|
||||
setActivePanel={ setActivePanel }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Icon } from '@wordpress/components';
|
|||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useAccordionState } from '../../hooks/useAccordionState';
|
||||
import { useToggleState } from '../../hooks/useToggleState';
|
||||
import {
|
||||
Content,
|
||||
Description,
|
||||
|
@ -21,7 +21,7 @@ const Accordion = ( {
|
|||
children = null,
|
||||
className = '',
|
||||
} ) => {
|
||||
const { isOpen, toggleOpen } = useAccordionState( { id, initiallyOpen } );
|
||||
const { isOpen, toggleOpen } = useToggleState( id, initiallyOpen );
|
||||
const wrapperClasses = classNames( 'ppcp-r-accordion', className, {
|
||||
'ppcp--is-open': isOpen,
|
||||
} );
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import { ToggleControl } from '@wordpress/components';
|
||||
import { Action, Description } from '../Elements';
|
||||
|
||||
const ControlToggleButton = ( { label, description, value, onChange } ) => (
|
||||
<Action>
|
||||
const ControlToggleButton = ( {
|
||||
id = '',
|
||||
label,
|
||||
description,
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
} ) => (
|
||||
<Action id={ id }>
|
||||
<ToggleControl
|
||||
className="ppcp--control-toggle"
|
||||
__nextHasNoMarginBottom
|
||||
|
@ -12,6 +19,7 @@ const ControlToggleButton = ( { label, description, value, onChange } ) => (
|
|||
help={
|
||||
description ? <Description>{ description }</Description> : null
|
||||
}
|
||||
disabled={ disabled }
|
||||
/>
|
||||
</Action>
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ const DataStoreControl = React.forwardRef(
|
|||
control: ControlComponent,
|
||||
value: externalValue,
|
||||
onChange,
|
||||
onConfirm = null,
|
||||
delay = 300,
|
||||
...props
|
||||
},
|
||||
|
@ -25,7 +26,9 @@ const DataStoreControl = React.forwardRef(
|
|||
) => {
|
||||
const [ internalValue, setInternalValue ] = useState( externalValue );
|
||||
const onChangeRef = useRef( onChange );
|
||||
const onConfirmRef = useRef( onConfirm );
|
||||
onChangeRef.current = onChange;
|
||||
onConfirmRef.current = onConfirm;
|
||||
|
||||
const debouncedUpdate = useRef(
|
||||
debounce( ( value ) => {
|
||||
|
@ -36,7 +39,7 @@ const DataStoreControl = React.forwardRef(
|
|||
useEffect( () => {
|
||||
setInternalValue( externalValue );
|
||||
debouncedUpdate?.cancel();
|
||||
}, [ externalValue ] );
|
||||
}, [ debouncedUpdate, externalValue ] );
|
||||
|
||||
useEffect( () => {
|
||||
return () => debouncedUpdate?.cancel();
|
||||
|
@ -50,12 +53,25 @@ const DataStoreControl = React.forwardRef(
|
|||
[ debouncedUpdate ]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
( event ) => {
|
||||
if ( onConfirmRef.current && event.key === 'Enter' ) {
|
||||
event.preventDefault();
|
||||
debouncedUpdate.flush();
|
||||
onConfirmRef.current();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[ debouncedUpdate ]
|
||||
);
|
||||
|
||||
return (
|
||||
<ControlComponent
|
||||
ref={ ref }
|
||||
{ ...props }
|
||||
value={ internalValue }
|
||||
onChange={ handleChange }
|
||||
onKeyDown={ handleKeyDown }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const Action = ( { children } ) => (
|
||||
<div className="ppcp--action">{ children }</div>
|
||||
const Action = ( { id, children } ) => (
|
||||
<div className="ppcp--action" { ...( id ? { id } : {} ) }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Action;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import classNames from 'classnames';
|
||||
|
||||
const CardActions = ( { isDimmed = false, children } ) => {
|
||||
const className = classNames( 'ppcp--card-actions', {
|
||||
'ppcp--dimmed': isDimmed,
|
||||
} );
|
||||
|
||||
return <div className={ className }>{ children }</div>;
|
||||
};
|
||||
|
||||
export default CardActions;
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
export { default as Action } from './Action';
|
||||
export { default as CardActions } from './CardActions';
|
||||
export { default as Content } from './Content';
|
||||
export { default as ContentWrapper } from './ContentWrapper';
|
||||
export { default as Description } from './Description';
|
||||
|
|
|
@ -9,7 +9,13 @@ const OptionSelector = ( {
|
|||
} ) => (
|
||||
<div className="ppcp-r-select-box-wrapper">
|
||||
{ options.map(
|
||||
( { value: itemValue, title, description, contents } ) => {
|
||||
( {
|
||||
value: itemValue,
|
||||
title,
|
||||
description,
|
||||
contents,
|
||||
isDisabled = false,
|
||||
} ) => {
|
||||
let isSelected;
|
||||
|
||||
if ( Array.isArray( value ) ) {
|
||||
|
@ -27,6 +33,7 @@ const OptionSelector = ( {
|
|||
onChange={ onChange }
|
||||
isMulti={ multiSelect }
|
||||
isSelected={ isSelected }
|
||||
isDisabled={ isDisabled }
|
||||
>
|
||||
{ contents }
|
||||
</OptionItem>
|
||||
|
@ -46,13 +53,13 @@ const OptionItem = ( {
|
|||
isMulti,
|
||||
isSelected,
|
||||
children,
|
||||
isDisabled = false,
|
||||
} ) => {
|
||||
const boxClassName = classNames( 'ppcp-r-select-box', {
|
||||
'ppcp--selected': isSelected,
|
||||
'ppcp--multiselect': isMulti,
|
||||
'ppcp--no-title': ! itemTitle,
|
||||
} );
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/label-has-associated-control -- label has a nested input control.
|
||||
<label className={ boxClassName }>
|
||||
|
@ -61,6 +68,7 @@ const OptionItem = ( {
|
|||
isRadio={ ! isMulti }
|
||||
onChange={ onChange }
|
||||
isSelected={ isSelected }
|
||||
isDisabled={ isDisabled }
|
||||
/>
|
||||
|
||||
<div className="ppcp--box-content">
|
||||
|
@ -80,7 +88,7 @@ const OptionItem = ( {
|
|||
);
|
||||
};
|
||||
|
||||
const InputField = ( { value, onChange, isRadio, isSelected } ) => {
|
||||
const InputField = ( { value, onChange, isRadio, isSelected, isDisabled } ) => {
|
||||
if ( isRadio ) {
|
||||
return (
|
||||
<PayPalRdb
|
||||
|
@ -96,6 +104,7 @@ const InputField = ( { value, onChange, isRadio, isSelected } ) => {
|
|||
value={ value }
|
||||
onChange={ onChange }
|
||||
checked={ isSelected }
|
||||
disabled={ isDisabled }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import { Icon } from '@wordpress/components';
|
||||
|
||||
import data from '../../utils/data';
|
||||
|
||||
const PaymentMethodIcon = ( { icons, type } ) => {
|
||||
const validIcon = Array.isArray( icons ) && icons.includes( type );
|
||||
|
||||
if ( validIcon || icons === 'all' ) {
|
||||
return (
|
||||
const PaymentMethodIcon = ( { type } ) => (
|
||||
<Icon
|
||||
icon={ data().getImage( 'icon-button-' + type + '.svg' ) }
|
||||
icon={ data().getImage( `icon-button-${ type }.svg` ) }
|
||||
className="ppcp--method-icon"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default PaymentMethodIcon;
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import PaymentMethodIcon from './PaymentMethodIcon';
|
||||
|
||||
const PaymentMethodIcons = ( props ) => {
|
||||
return (
|
||||
const PaymentMethodIcons = ( { icons = [] } ) => (
|
||||
<div className="ppcp-r-payment-method-icons">
|
||||
<PaymentMethodIcon type="paypal" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="venmo" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="visa" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="mastercard" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="amex" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="discover" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="apple-pay" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="google-pay" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="ideal" icons={ props.icons } />
|
||||
<PaymentMethodIcon type="bancontact" icons={ props.icons } />
|
||||
{ icons.map( ( type ) => (
|
||||
<PaymentMethodIcon key={ type } type={ type } />
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentMethodIcons;
|
||||
|
|
|
@ -1,39 +1,44 @@
|
|||
import { ToggleControl, Icon, Button } from '@wordpress/components';
|
||||
import { cog } from '@wordpress/icons';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { useActiveHighlight } from '../../../data/common/hooks';
|
||||
|
||||
import SettingsBlock from '../SettingsBlock';
|
||||
import PaymentMethodIcon from '../PaymentMethodIcon';
|
||||
import WarningMessages from '../../../Components/Screens/Settings/Components/Payment/WarningMessages';
|
||||
|
||||
const PaymentMethodItemBlock = ( {
|
||||
paymentMethod,
|
||||
onTriggerModal,
|
||||
onSelect,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
disabledMessage,
|
||||
warningMessages,
|
||||
} ) => {
|
||||
const { activeHighlight, setActiveHighlight } = useActiveHighlight();
|
||||
const isHighlighted = activeHighlight === paymentMethod.id;
|
||||
const hasWarning =
|
||||
warningMessages && Object.keys( warningMessages ).length > 0;
|
||||
|
||||
// Reset the active highlight after 2 seconds
|
||||
useEffect( () => {
|
||||
if ( isHighlighted ) {
|
||||
const timer = setTimeout( () => {
|
||||
setActiveHighlight( null );
|
||||
}, 2000 );
|
||||
|
||||
return () => clearTimeout( timer );
|
||||
}
|
||||
}, [ isHighlighted, setActiveHighlight ] );
|
||||
// Determine class names based on states
|
||||
const methodItemClasses = [
|
||||
'ppcp--method-item',
|
||||
isDisabled ? 'ppcp--method-item--disabled' : '',
|
||||
hasWarning && ! isDisabled ? 'ppcp--method-item--warning' : '',
|
||||
]
|
||||
.filter( Boolean )
|
||||
.join( ' ' );
|
||||
|
||||
return (
|
||||
<SettingsBlock
|
||||
id={ paymentMethod.id }
|
||||
className={ `ppcp--method-item ${
|
||||
isHighlighted ? 'ppcp-highlight' : ''
|
||||
}` }
|
||||
className={ methodItemClasses }
|
||||
separatorAndGap={ false }
|
||||
>
|
||||
{ isDisabled && (
|
||||
<div className="ppcp--method-disabled-overlay">
|
||||
<p className="ppcp--method-disabled-message">
|
||||
{ disabledMessage }
|
||||
</p>
|
||||
</div>
|
||||
) }
|
||||
<div className="ppcp--method-inner">
|
||||
<div className="ppcp--method-title-wrapper">
|
||||
{ paymentMethod?.icon && (
|
||||
|
@ -50,11 +55,18 @@ const PaymentMethodItemBlock = ( {
|
|||
{ paymentMethod.itemDescription }
|
||||
</p>
|
||||
<div className="ppcp--method-footer">
|
||||
<div className="ppcp--method-toggle-wrapper">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom
|
||||
checked={ isSelected }
|
||||
onChange={ onSelect }
|
||||
/>
|
||||
{ hasWarning && ! isDisabled && isSelected && (
|
||||
<WarningMessages
|
||||
warningMessages={ warningMessages }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
{ paymentMethod?.fields && onTriggerModal && (
|
||||
<Button
|
||||
className="ppcp--method-settings"
|
||||
|
|
|
@ -19,20 +19,25 @@ const PaymentMethodsBlock = ( { paymentMethods = [], onTriggerModal } ) => {
|
|||
<SettingsBlock className="ppcp--grid ppcp-r-settings-block__payment-methods">
|
||||
{ paymentMethods
|
||||
// Remove empty/invalid payment method entries.
|
||||
.filter( ( m ) => m.id )
|
||||
.map( ( paymentMethod ) => (
|
||||
.filter( ( m ) => m && m.id )
|
||||
.map( ( paymentMethod ) => {
|
||||
return (
|
||||
<PaymentMethodItemBlock
|
||||
key={ paymentMethod.id }
|
||||
paymentMethod={ paymentMethod }
|
||||
isSelected={ paymentMethod.enabled }
|
||||
isDisabled={ paymentMethod.isDisabled }
|
||||
disabledMessage={ paymentMethod.disabledMessage }
|
||||
onSelect={ ( checked ) =>
|
||||
handleSelect( paymentMethod.id, checked )
|
||||
}
|
||||
onTriggerModal={ () =>
|
||||
onTriggerModal?.( paymentMethod.id )
|
||||
}
|
||||
warningMessages={ paymentMethod.warningMessages }
|
||||
/>
|
||||
) ) }
|
||||
);
|
||||
} ) }
|
||||
</SettingsBlock>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ const TodoSettingsBlock = ( {
|
|||
todosData,
|
||||
className = '',
|
||||
setActiveModal,
|
||||
setActiveHighlight,
|
||||
onDismissTodo,
|
||||
} ) => {
|
||||
const [ dismissingIds, setDismissingIds ] = useState( new Set() );
|
||||
|
@ -44,29 +43,30 @@ const TodoSettingsBlock = ( {
|
|||
};
|
||||
|
||||
const handleClick = async ( todo ) => {
|
||||
if ( todo.action.type === 'tab' ) {
|
||||
const tabId = TAB_IDS[ todo.action.tab.toUpperCase() ];
|
||||
await selectTab( tabId, todo.action.section );
|
||||
} else if ( todo.action.type === 'external' ) {
|
||||
window.open( todo.action.url, '_blank' );
|
||||
// If it has completeOnClick flag, trigger the action
|
||||
if ( todo.action.completeOnClick === true ) {
|
||||
await completeOnClick( todo.id );
|
||||
}
|
||||
const { action } = todo;
|
||||
const highlight = Boolean( action.highlight );
|
||||
|
||||
// Handle different action types.
|
||||
if ( action.type === 'tab' ) {
|
||||
const tabId = TAB_IDS[ action.tab.toUpperCase() ];
|
||||
await selectTab( tabId, action.section, highlight );
|
||||
} else if ( action.type === 'external' ) {
|
||||
window.open( action.url, '_blank' );
|
||||
}
|
||||
|
||||
if ( todo.action.modal ) {
|
||||
setActiveModal( todo.action.modal );
|
||||
if ( action.completeOnClick ) {
|
||||
await completeOnClick( todo.id );
|
||||
}
|
||||
if ( todo.action.highlight ) {
|
||||
setActiveHighlight( todo.action.highlight );
|
||||
|
||||
if ( action.modal ) {
|
||||
setActiveModal( action.modal );
|
||||
}
|
||||
};
|
||||
|
||||
// Filter out dismissed todos for display
|
||||
const visibleTodos = todosData.filter(
|
||||
( todo ) => ! dismissedTodos.includes( todo.id )
|
||||
);
|
||||
// Filter out dismissed todos for display and limit to 5.
|
||||
const visibleTodos = todosData
|
||||
.filter( ( todo ) => ! dismissedTodos.includes( todo.id ) )
|
||||
.slice( 0, 5 );
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -32,7 +32,6 @@ const ButtonOrPlaceholder = ( {
|
|||
|
||||
if ( href ) {
|
||||
buttonProps.href = href;
|
||||
buttonProps.target = 'PPFrame';
|
||||
buttonProps[ 'data-paypal-button' ] = 'true';
|
||||
buttonProps[ 'data-paypal-onboard-button' ] = 'true';
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@ const ManualConnectionForm = () => {
|
|||
label={ clientIdLabel }
|
||||
value={ manualClientId }
|
||||
onChange={ setManualClientId }
|
||||
onConfirm={ handleManualConnect }
|
||||
className={ classNames( {
|
||||
'ppcp--has-error': ! clientValid,
|
||||
} ) }
|
||||
|
@ -173,6 +174,7 @@ const ManualConnectionForm = () => {
|
|||
label={ secretKeyLabel }
|
||||
value={ manualClientSecret }
|
||||
onChange={ setManualClientSecret }
|
||||
onConfirm={ handleManualConnect }
|
||||
type="password"
|
||||
/>
|
||||
<Button
|
||||
|
|
|
@ -8,7 +8,6 @@ import { usePaymentConfig } from '../hooks/usePaymentConfig';
|
|||
const PaymentFlow = ( {
|
||||
useAcdc,
|
||||
isFastlane,
|
||||
isPayLater,
|
||||
storeCountry,
|
||||
onlyOptional = false,
|
||||
} ) => {
|
||||
|
@ -18,7 +17,8 @@ const PaymentFlow = ( {
|
|||
optionalTitle,
|
||||
optionalDescription,
|
||||
learnMoreConfig,
|
||||
} = usePaymentConfig( storeCountry, isPayLater, useAcdc, isFastlane );
|
||||
paypalCheckoutDescription,
|
||||
} = usePaymentConfig( storeCountry, useAcdc, isFastlane );
|
||||
|
||||
if ( onlyOptional ) {
|
||||
return (
|
||||
|
@ -34,6 +34,7 @@ const PaymentFlow = ( {
|
|||
<DefaultMethodsSection
|
||||
methods={ includedMethods }
|
||||
learnMoreConfig={ learnMoreConfig }
|
||||
paypalCheckoutDescription={ paypalCheckoutDescription }
|
||||
/>
|
||||
|
||||
<OptionalMethodsSection
|
||||
|
@ -48,10 +49,17 @@ const PaymentFlow = ( {
|
|||
|
||||
export default PaymentFlow;
|
||||
|
||||
const DefaultMethodsSection = ( { methods, learnMoreConfig } ) => {
|
||||
const DefaultMethodsSection = ( {
|
||||
methods,
|
||||
learnMoreConfig,
|
||||
paypalCheckoutDescription,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="ppcp-r-welcome-docs__col">
|
||||
<PayPalCheckout learnMore={ learnMoreConfig.PayPalCheckout } />
|
||||
<PayPalCheckout
|
||||
learnMore={ learnMoreConfig.PayPalCheckout }
|
||||
description={ paypalCheckoutDescription }
|
||||
/>
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Included in PayPal Checkout',
|
||||
|
|
|
@ -5,15 +5,15 @@ import BadgeBox from '../../../../ReusableComponents/BadgeBox';
|
|||
|
||||
const PayPalCheckout = ( {
|
||||
learnMore = 'https://www.paypal.com/us/business/accept-payments/checkout',
|
||||
description,
|
||||
} ) => {
|
||||
const title = __( 'PayPal Checkout', 'woocommerce-paypal-payments' );
|
||||
|
||||
return (
|
||||
<BadgeBox
|
||||
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
|
||||
title={ title }
|
||||
textBadge={ <PricingTitleBadge item="checkout" /> }
|
||||
description={ __(
|
||||
'Our all-in-one checkout solution lets you offer PayPal, Venmo, Pay Later options, and more to help maximise conversion',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ description }
|
||||
learnMoreLink={ learnMore }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ const PricingDescription = () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const lastDate = 'October 25th, 2024'; // TODO -- needs to be the last plugin update date.
|
||||
const lastDate = 'February 1st, 2025'; // TODO -- needs to be the last plugin update date.
|
||||
const countryLinks = learnMoreLinks[ storeCountry ] || learnMoreLinks.US;
|
||||
|
||||
const label = sprintf(
|
||||
|
|
|
@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n';
|
|||
import PricingDescription from './PricingDescription';
|
||||
import PaymentFlow from './PaymentFlow';
|
||||
|
||||
const WelcomeDocs = ( { useAcdc, isFastlane, isPayLater, storeCountry } ) => {
|
||||
const WelcomeDocs = ( { useAcdc, isFastlane, storeCountry } ) => {
|
||||
return (
|
||||
<div className="ppcp-r-welcome-docs">
|
||||
<h2 className="ppcp-r-welcome-docs__title">
|
||||
|
@ -15,7 +15,6 @@ const WelcomeDocs = ( { useAcdc, isFastlane, isPayLater, storeCountry } ) => {
|
|||
<PaymentFlow
|
||||
useAcdc={ useAcdc }
|
||||
isFastlane={ isFastlane }
|
||||
isPayLater={ isPayLater }
|
||||
storeCountry={ storeCountry }
|
||||
/>
|
||||
<PricingDescription />
|
||||
|
|
|
@ -22,9 +22,34 @@ const StepBusiness = ( {} ) => {
|
|||
);
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! businessChoice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCasualSeller( BUSINESS_TYPES.CASUAL_SELLER === businessChoice );
|
||||
}, [ businessChoice, setIsCasualSeller ] );
|
||||
|
||||
const { canUseSubscriptions } = OnboardingHooks.useFlags();
|
||||
const businessChoices = [
|
||||
{
|
||||
value: BUSINESS_TYPES.BUSINESS,
|
||||
title: __( 'Business', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: BUSINESS_TYPES.CASUAL_SELLER,
|
||||
title: __( 'Personal Account', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Ideal for those who primarily make purchases or send personal transactions to family and friends.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: canUseSubscriptions ? <DetailsAccountType /> : null,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-page-business">
|
||||
<OnboardingHeader
|
||||
|
@ -45,23 +70,13 @@ const StepBusiness = ( {} ) => {
|
|||
);
|
||||
};
|
||||
|
||||
const businessChoices = [
|
||||
{
|
||||
value: BUSINESS_TYPES.BUSINESS,
|
||||
title: __( 'Business', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
|
||||
const DetailsAccountType = () => (
|
||||
<p>
|
||||
{ __(
|
||||
'* Business account is required for subscriptions.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: BUSINESS_TYPES.CASUAL_SELLER,
|
||||
title: __( 'Personal Account', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Ideal for those who primarily make purchases or send personal transactions to family and friends.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
},
|
||||
];
|
||||
) }
|
||||
</p>
|
||||
);
|
||||
|
||||
export default StepBusiness;
|
||||
|
|
|
@ -59,33 +59,18 @@ const StepPaymentMethods = () => {
|
|||
export default StepPaymentMethods;
|
||||
|
||||
const PaymentStepTitle = () => {
|
||||
const { storeCountry } = CommonHooks.useWooSettings();
|
||||
|
||||
if ( 'US' === storeCountry ) {
|
||||
return __(
|
||||
'Add Expanded Checkout for More Ways to Pay',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
}
|
||||
|
||||
return __(
|
||||
'Add optional payment methods to your Checkout',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
return __( 'Add Credit and Debit Cards', 'woocommerce-paypal-payments' );
|
||||
};
|
||||
|
||||
const OptionalMethodDescription = () => {
|
||||
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
||||
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
||||
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
||||
const { canUseCardPayments } = OnboardingHooks.useFlags();
|
||||
|
||||
/**
|
||||
* Casual sellers = Personal accounts. Those accounts have no ACDC-capabilities, but should get
|
||||
* the choice for BCDC-payments.
|
||||
*/
|
||||
return (
|
||||
<PaymentFlow
|
||||
onlyOptional={ true }
|
||||
useAcdc={ ! isCasualSeller }
|
||||
useAcdc={ ! isCasualSeller && canUseCardPayments }
|
||||
isFastlane={ true }
|
||||
isPayLater={ true }
|
||||
storeCountry={ storeCountry }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
|
||||
import { OptionSelector } from '../../../ReusableComponents/Fields';
|
||||
|
@ -10,26 +10,28 @@ const StepProducts = () => {
|
|||
const { canUseSubscriptions } = OnboardingHooks.useFlags();
|
||||
const [ optionState, setOptionState ] = useState( null );
|
||||
const [ productChoices, setProductChoices ] = useState( [] );
|
||||
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
||||
|
||||
useEffect( () => {
|
||||
const initChoices = () => {
|
||||
if ( optionState === canUseSubscriptions ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let choices = productChoicesFull;
|
||||
|
||||
// Remove subscription details, if not available.
|
||||
if ( ! canUseSubscriptions ) {
|
||||
choices = choices.filter(
|
||||
( { value } ) => value !== PRODUCT_TYPES.SUBSCRIPTIONS
|
||||
);
|
||||
setProducts(
|
||||
products.filter(
|
||||
( value ) => value !== PRODUCT_TYPES.SUBSCRIPTIONS
|
||||
)
|
||||
);
|
||||
const choices = productChoicesFull.map( ( choice ) => {
|
||||
if (
|
||||
choice.value === PRODUCT_TYPES.SUBSCRIPTIONS &&
|
||||
! canUseSubscriptions
|
||||
) {
|
||||
return {
|
||||
...choice,
|
||||
isDisabled: true,
|
||||
contents: (
|
||||
<DetailsSubscriptions
|
||||
showLink={ true }
|
||||
showNotice={ isCasualSeller }
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
return choice;
|
||||
} );
|
||||
|
||||
setProductChoices( choices );
|
||||
setOptionState( canUseSubscriptions );
|
||||
|
@ -48,7 +50,46 @@ const StepProducts = () => {
|
|||
|
||||
setProducts( getNewValue() );
|
||||
};
|
||||
|
||||
const productChoicesFull = [
|
||||
{
|
||||
value: PRODUCT_TYPES.VIRTUAL,
|
||||
title: __( 'Virtual', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Items do not require shipping.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: <DetailsVirtual />,
|
||||
},
|
||||
{
|
||||
value: PRODUCT_TYPES.PHYSICAL,
|
||||
title: __( 'Physical Goods', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Items require shipping.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: <DetailsPhysical />,
|
||||
},
|
||||
{
|
||||
value: PRODUCT_TYPES.SUBSCRIPTIONS,
|
||||
title: __( 'Subscriptions', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Recurring payments for either physical goods or services.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
isDisabled: isCasualSeller,
|
||||
contents: (
|
||||
/*
|
||||
* Note: The link should be only displayed if the subscriptions plugin is not installed.
|
||||
* But when the plugin is not active, this option is completely hidden;
|
||||
* This means: In the current configuration, we never show the link.
|
||||
*/
|
||||
<DetailsSubscriptions
|
||||
showLink={ false }
|
||||
showNotice={ isCasualSeller }
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="ppcp-r-page-products">
|
||||
<OnboardingHeader
|
||||
|
@ -87,41 +128,29 @@ const DetailsPhysical = () => (
|
|||
</ul>
|
||||
);
|
||||
|
||||
const DetailsSubscriptions = () => (
|
||||
<a
|
||||
target="__blank"
|
||||
href="https://woocommerce.com/document/woocommerce-paypal-payments/#subscriptions-faq"
|
||||
>
|
||||
{ __( 'WooCommerce Subscriptions', 'woocommerce-paypal-payments' ) }
|
||||
</a>
|
||||
const DetailsSubscriptions = ( { showLink, showNotice } ) => (
|
||||
<>
|
||||
{ showLink && (
|
||||
<p
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: sprintf(
|
||||
/* translators: %s is the URL to the WooCommerce Subscriptions product page */
|
||||
__(
|
||||
'* To use subscriptions, you must have <a target="_blank" href="%s">WooCommerce Subscriptions</a> enabled.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/products/woocommerce-subscriptions/'
|
||||
),
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
{ showNotice && (
|
||||
<p>
|
||||
{ __(
|
||||
'* Business account is required for subscriptions.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
|
||||
const productChoicesFull = [
|
||||
{
|
||||
value: PRODUCT_TYPES.VIRTUAL,
|
||||
title: __( 'Virtual', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Items do not require shipping.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: <DetailsVirtual />,
|
||||
},
|
||||
{
|
||||
value: PRODUCT_TYPES.PHYSICAL,
|
||||
title: __( 'Physical Goods', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Items require shipping.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: <DetailsPhysical />,
|
||||
},
|
||||
{
|
||||
value: PRODUCT_TYPES.SUBSCRIPTIONS,
|
||||
title: __( 'Subscriptions', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Recurring payments for either physical goods or services.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
contents: <DetailsSubscriptions />,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -4,14 +4,32 @@ import { Button } from '@wordpress/components';
|
|||
import PaymentMethodIcons from '../../../ReusableComponents/PaymentMethodIcons';
|
||||
import { Separator } from '../../../ReusableComponents/Elements';
|
||||
import Accordion from '../../../ReusableComponents/AccordionSection';
|
||||
import { CommonHooks } from '../../../../data';
|
||||
import { CommonHooks, OnboardingHooks } from '../../../../data';
|
||||
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||
import OnboardingHeader from '../Components/OnboardingHeader';
|
||||
import WelcomeDocs from '../Components/WelcomeDocs';
|
||||
import AdvancedOptionsForm from '../Components/AdvancedOptionsForm';
|
||||
import { usePaymentConfig } from '../hooks/usePaymentConfig';
|
||||
|
||||
const StepWelcome = ( { setStep, currentStep } ) => {
|
||||
const { storeCountry } = CommonHooks.useWooSettings();
|
||||
const { canUseCardPayments, canUseFastlane } = OnboardingHooks.useFlags();
|
||||
|
||||
const { acdcIcons, bcdcIcons } = usePaymentConfig(
|
||||
storeCountry,
|
||||
canUseCardPayments,
|
||||
canUseFastlane
|
||||
);
|
||||
|
||||
const onboardingHeaderDescription = canUseCardPayments
|
||||
? __(
|
||||
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
: __(
|
||||
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, and more.',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-page-welcome">
|
||||
|
@ -20,17 +38,16 @@ const StepWelcome = ( { setStep, currentStep } ) => {
|
|||
'Welcome to PayPal Payments',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ onboardingHeaderDescription }
|
||||
/>
|
||||
<div className="ppcp-r-inner-container">
|
||||
<WelcomeFeatures />
|
||||
<PaymentMethodIcons icons="all" />
|
||||
<PaymentMethodIcons
|
||||
icons={ canUseCardPayments ? acdcIcons : bcdcIcons }
|
||||
/>
|
||||
<p className="ppcp-r-button__description">
|
||||
{ __(
|
||||
`Click the button below to be guided through connecting your existing PayPal account or creating a new one.You will be able to choose the payment options that are right for your store.`,
|
||||
'Click the button below to be guided through connecting your existing PayPal account or creating a new one. You will be able to choose the payment options that are right for your store.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
|
@ -49,9 +66,8 @@ const StepWelcome = ( { setStep, currentStep } ) => {
|
|||
</div>
|
||||
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
||||
<WelcomeDocs
|
||||
useAcdc={ true }
|
||||
isFastlane={ true }
|
||||
isPayLater={ true }
|
||||
useAcdc={ canUseCardPayments }
|
||||
isFastlane={ canUseFastlane }
|
||||
storeCountry={ storeCountry }
|
||||
/>
|
||||
<Separator text={ __( 'or', 'woocommerce-paypal-payments' ) } />
|
||||
|
|
|
@ -36,8 +36,7 @@ const ALL_STEPS = [
|
|||
id: 'methods',
|
||||
title: __( 'Choose checkout options', 'woocommerce-paypal-payments' ),
|
||||
StepComponent: StepPaymentMethods,
|
||||
canProceed: ( { methods } ) =>
|
||||
methods.areOptionalPaymentMethodsEnabled !== null,
|
||||
canProceed: ( { methods } ) => methods.optionalMethods !== null,
|
||||
},
|
||||
{
|
||||
id: 'complete',
|
||||
|
@ -60,8 +59,8 @@ export const getSteps = ( flags ) => {
|
|||
const steps = filterSteps( ALL_STEPS, [
|
||||
// Casual selling: Unlock the "Personal Account" choice.
|
||||
( step ) => flags.canUseCasualSelling || step.id !== 'business',
|
||||
// Card payments: Unlocks the "Extended Checkout" choice.
|
||||
( step ) => flags.canUseCardPayments || step.id !== 'methods',
|
||||
// Skip payment methods screen.
|
||||
( step ) => ! flags.shouldSkipPaymentMethods || step.id !== 'methods',
|
||||
] );
|
||||
|
||||
const totalStepsCount = steps.length;
|
||||
|
|
|
@ -28,6 +28,7 @@ const defaultConfig = {
|
|||
|
||||
// Extended: Items on right side for ACDC-flow.
|
||||
extendedMethods: [
|
||||
{ name: 'CardFields', Component: CardFields },
|
||||
{ name: 'DigitalWallets', Component: DigitalWallets },
|
||||
{ name: 'APMs', Component: AlternativePaymentMethods },
|
||||
],
|
||||
|
@ -41,6 +42,26 @@ const defaultConfig = {
|
|||
'with additional application',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
|
||||
// PayPal Checkout description.
|
||||
paypalCheckoutDescription: __(
|
||||
'Our all-in-one checkout solution lets you offer PayPal, Pay Later options, and more to help maximise conversion',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
|
||||
// Icon groups.
|
||||
bcdcIcons: [ 'paypal', 'visa', 'mastercard', 'amex', 'discover' ],
|
||||
acdcIcons: [
|
||||
'paypal',
|
||||
'visa',
|
||||
'mastercard',
|
||||
'amex',
|
||||
'discover',
|
||||
'apple-pay',
|
||||
'google-pay',
|
||||
'ideal',
|
||||
'bancontact',
|
||||
],
|
||||
};
|
||||
|
||||
const countrySpecificConfigs = {
|
||||
|
@ -60,11 +81,35 @@ const countrySpecificConfigs = {
|
|||
{ name: 'APMs', Component: AlternativePaymentMethods },
|
||||
{ name: 'Fastlane', Component: Fastlane },
|
||||
],
|
||||
paypalCheckoutDescription: __(
|
||||
'Our all-in-one checkout solution lets you offer PayPal, Venmo, Pay Later options, and more to help maximise conversion',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
optionalTitle: __( 'Expanded Checkout', 'woocommerce-paypal-payments' ),
|
||||
optionalDescription: __(
|
||||
'Accept debit/credit cards, PayPal, Apple Pay, Google Pay, and more. Note: Additional application required for more methods',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
bcdcIcons: [
|
||||
'paypal',
|
||||
'venmo',
|
||||
'visa',
|
||||
'mastercard',
|
||||
'amex',
|
||||
'discover',
|
||||
],
|
||||
acdcIcons: [
|
||||
'paypal',
|
||||
'venmo',
|
||||
'visa',
|
||||
'mastercard',
|
||||
'amex',
|
||||
'discover',
|
||||
'apple-pay',
|
||||
'google-pay',
|
||||
'ideal',
|
||||
'bancontact',
|
||||
],
|
||||
},
|
||||
GB: {
|
||||
includedMethods: [
|
||||
|
@ -72,6 +117,12 @@ const countrySpecificConfigs = {
|
|||
{ name: 'PayInThree', Component: PayInThree },
|
||||
],
|
||||
},
|
||||
MX: {
|
||||
extendedMethods: [
|
||||
{ name: 'CardFields', Component: CardFields },
|
||||
{ name: 'APMs', Component: AlternativePaymentMethods },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const filterMethods = ( methods, conditions ) => {
|
||||
|
@ -80,22 +131,12 @@ const filterMethods = ( methods, conditions ) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const usePaymentConfig = (
|
||||
country,
|
||||
isPayLater,
|
||||
useAcdc,
|
||||
isFastlane
|
||||
) => {
|
||||
export const usePaymentConfig = ( country, useAcdc, isFastlane ) => {
|
||||
return useMemo( () => {
|
||||
const countryConfig = countrySpecificConfigs[ country ] || {};
|
||||
const config = { ...defaultConfig, ...countryConfig };
|
||||
const learnMoreConfig = learnMoreLinks[ country ] || {};
|
||||
|
||||
// Filter the "left side" list. PayLater is conditional.
|
||||
const includedMethods = filterMethods( config.includedMethods, [
|
||||
( method ) => isPayLater || method.name !== 'PayLater',
|
||||
] );
|
||||
|
||||
// Determine the "right side" items: Either BCDC or ACDC items.
|
||||
const optionalMethods = useAcdc
|
||||
? config.extendedMethods
|
||||
|
@ -108,9 +149,10 @@ export const usePaymentConfig = (
|
|||
|
||||
return {
|
||||
...config,
|
||||
includedMethods,
|
||||
optionalMethods: availableOptionalMethods,
|
||||
learnMoreConfig,
|
||||
acdcIcons: config.acdcIcons,
|
||||
bcdcIcons: config.bcdcIcons,
|
||||
};
|
||||
}, [ country, isPayLater, useAcdc, isFastlane ] );
|
||||
}, [ country, useAcdc, isFastlane ] );
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { PayLaterMessagingHooks } from '../../../data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
|
||||
const TabPayLaterMessaging = () => {
|
||||
const {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n';
|
|||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||
|
||||
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
||||
import { useSaveSettings } from '../../../../hooks/useSaveSettings';
|
||||
import { useStoreManager } from '../../../../hooks/useStoreManager';
|
||||
import { CommonHooks } from '../../../../data';
|
||||
import TabBar from '../../../ReusableComponents/TabBar';
|
||||
import classNames from 'classnames';
|
||||
|
@ -20,7 +20,7 @@ const SettingsNavigation = ( {
|
|||
activePanel,
|
||||
setActivePanel,
|
||||
} ) => {
|
||||
const { persistAll } = useSaveSettings();
|
||||
const { persistAll } = useStoreManager();
|
||||
|
||||
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
import { reusableBlock } from '@wordpress/icons';
|
||||
|
||||
const FeatureDescription = ( { refreshHandler, isRefreshing } ) => {
|
||||
const buttonLabel = isRefreshing
|
||||
? __( 'Refreshing…', 'woocommerce-paypal-payments' )
|
||||
: __( 'Refresh', 'woocommerce-paypal-payments' );
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{ __(
|
||||
'Enable additional features and capabilities on your WooCommerce store.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ __(
|
||||
'Click Refresh to update your current features after making changes.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ refreshHandler }
|
||||
disabled={ isRefreshing }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ buttonLabel }
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureDescription;
|
|
@ -0,0 +1,74 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { FeatureSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
import { Content } from '../../../../../ReusableComponents/Elements';
|
||||
import { TITLE_BADGE_POSITIVE } from '../../../../../ReusableComponents/TitleBadge';
|
||||
import { selectTab, TAB_IDS } from '../../../../../../utils/tabSelector';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
|
||||
|
||||
const FeatureItem = ( {
|
||||
isBusy,
|
||||
isSandbox,
|
||||
title,
|
||||
description,
|
||||
buttons,
|
||||
enabled,
|
||||
notes,
|
||||
} ) => {
|
||||
const { setActiveModal } = useDispatch( COMMON_STORE_NAME );
|
||||
const getButtonUrl = ( button ) => {
|
||||
if ( button.urls ) {
|
||||
return isSandbox ? button.urls.sandbox : button.urls.live;
|
||||
}
|
||||
|
||||
return button.url;
|
||||
};
|
||||
|
||||
const visibleButtons = buttons.filter(
|
||||
( button ) =>
|
||||
! button.showWhen || // Learn more buttons
|
||||
( enabled && button.showWhen === 'enabled' ) ||
|
||||
( ! enabled && button.showWhen === 'disabled' )
|
||||
);
|
||||
const handleClick = async ( feature ) => {
|
||||
if ( feature.action?.type === 'tab' ) {
|
||||
const highlight = Boolean( feature.action?.highlight );
|
||||
const tabId = TAB_IDS[ feature.action.tab.toUpperCase() ];
|
||||
await selectTab( tabId, feature.action.section, highlight );
|
||||
}
|
||||
|
||||
if ( feature.action?.modal ) {
|
||||
setActiveModal( feature.action.modal );
|
||||
}
|
||||
};
|
||||
|
||||
const actionProps = {
|
||||
isBusy,
|
||||
enabled,
|
||||
notes,
|
||||
buttons: visibleButtons.map( ( button ) => ( {
|
||||
...button,
|
||||
url: getButtonUrl( button ),
|
||||
onClick: () => handleClick( button ),
|
||||
} ) ),
|
||||
};
|
||||
|
||||
if ( enabled ) {
|
||||
actionProps.badge = {
|
||||
text: __( 'Active', 'woocommerce-paypal-payments' ),
|
||||
type: TITLE_BADGE_POSITIVE,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ title }
|
||||
description={ description }
|
||||
actionProps={ actionProps }
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureItem;
|
|
@ -0,0 +1,95 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import FeatureItem from './FeatureItem';
|
||||
import FeatureDescription from './FeatureDescription';
|
||||
import { ContentWrapper } from '../../../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
|
||||
import { useMerchantInfo } from '../../../../../../data/common/hooks';
|
||||
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
|
||||
import {
|
||||
NOTIFICATION_ERROR,
|
||||
NOTIFICATION_SUCCESS,
|
||||
} from '../../../../../ReusableComponents/Icons';
|
||||
import { useFeatures } from '../../../../../../data/features/hooks';
|
||||
|
||||
const Features = () => {
|
||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||
const { merchant } = useMerchantInfo();
|
||||
const { features, fetchFeatures } = useFeatures();
|
||||
const { refreshFeatureStatuses } = useDispatch( COMMON_STORE_NAME );
|
||||
const { createSuccessNotice, createErrorNotice } =
|
||||
useDispatch( noticesStore );
|
||||
|
||||
if ( ! features || features.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const refreshHandler = async () => {
|
||||
setIsRefreshing( true );
|
||||
try {
|
||||
const statusResult = await refreshFeatureStatuses();
|
||||
if ( ! statusResult?.success ) {
|
||||
throw new Error(
|
||||
statusResult?.message || 'Failed to refresh status'
|
||||
);
|
||||
}
|
||||
|
||||
const featuresResult = await fetchFeatures();
|
||||
if ( featuresResult.success ) {
|
||||
createSuccessNotice(
|
||||
__(
|
||||
'Features refreshed successfully.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
{ icon: NOTIFICATION_SUCCESS }
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
featuresResult?.message || 'Failed to fetch features'
|
||||
);
|
||||
}
|
||||
} catch ( error ) {
|
||||
createErrorNotice(
|
||||
sprintf(
|
||||
/* translators: %s: error message */
|
||||
__( 'Operation failed: %s', 'woocommerce-paypal-payments' ),
|
||||
error.message ||
|
||||
__( 'Unknown error', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
{ icon: NOTIFICATION_ERROR }
|
||||
);
|
||||
} finally {
|
||||
setIsRefreshing( false );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-features"
|
||||
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<FeatureDescription
|
||||
refreshHandler={ refreshHandler }
|
||||
isRefreshing={ isRefreshing }
|
||||
/>
|
||||
}
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
{ features.map( ( { id, enabled, ...feature } ) => (
|
||||
<FeatureItem
|
||||
key={ id }
|
||||
isBusy={ isRefreshing }
|
||||
isSandbox={ merchant.isSandbox }
|
||||
enabled={ enabled }
|
||||
{ ...feature }
|
||||
/>
|
||||
) ) }
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default Features;
|
|
@ -0,0 +1,72 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { FeatureSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
import {
|
||||
Content,
|
||||
ContentWrapper,
|
||||
} from '../../../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
|
||||
|
||||
const Help = () => {
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-help"
|
||||
title={ __( 'Help Center', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Access detailed guides and responsive support to streamline setup and enhance your experience.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __(
|
||||
'Documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Find detailed guides and resources to help you set up, manage, and optimize your PayPal integration.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View full documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __( 'Support', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Need help? Access troubleshooting tips or contact our support team for personalized assistance.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View support options',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help ',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue