Merge branch 'PCP-3398-critical-error-when-changing-subscription-payment-method-to-advanced-card-processing' into PCP-4110-incorrect-subscription-cancellation-handling-with-pay-pal-subscriptions

This commit is contained in:
Daniel Hüsken 2025-02-07 12:37:53 +01:00
commit 13f17410ea
No known key found for this signature in database
GPG key ID: 9F732DA37FA709E8
229 changed files with 6074 additions and 3425 deletions

View file

@ -80,10 +80,19 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig; use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
return array( return array(
'api.host' => function( ContainerInterface $container ) : string { 'api.host' => static function( ContainerInterface $container ) : string {
return PAYPAL_API_URL; $environment = $container->get( 'onboarding.environment' );
assert( $environment instanceof Environment );
if ( $environment->is_sandbox() ) {
return (string) $container->get( 'api.sandbox-host' );
}
return (string) $container->get( 'api.production-host' );
}, },
'api.paypal-host' => function( ContainerInterface $container ) : string { 'api.paypal-host' => function( ContainerInterface $container ) : string {
return PAYPAL_API_URL; return PAYPAL_API_URL;
@ -116,6 +125,12 @@ return array(
return 'WC-'; return 'WC-';
}, },
'api.bearer' => static function ( ContainerInterface $container ): Bearer { 'api.bearer' => static function ( ContainerInterface $container ): Bearer {
$is_connected = $container->get( 'settings.flag.is-connected' );
if ( ! $is_connected ) {
return new ConnectBearer();
}
return new PayPalBearer( return new PayPalBearer(
$container->get( 'api.paypal-bearer-cache' ), $container->get( 'api.paypal-bearer-cache' ),
$container->get( 'api.host' ), $container->get( 'api.host' ),
@ -811,7 +826,7 @@ return array(
return new OrderHelper(); return new OrderHelper();
}, },
'api.helper.order-transient' => static function( ContainerInterface $container ): OrderTransient { 'api.helper.order-transient' => static function( ContainerInterface $container ): OrderTransient {
$cache = new Cache( 'ppcp-paypal-bearer' ); $cache = $container->get( 'api.paypal-bearer-cache' );
$purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' ); $purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' );
return new OrderTransient( $cache, $purchase_unit_sanitizer ); return new OrderTransient( $cache, $purchase_unit_sanitizer );
}, },
@ -927,4 +942,22 @@ return array(
$container->get( 'api.endpoint.partner-referrals-sandbox' ) $container->get( 'api.endpoint.partner-referrals-sandbox' )
); );
}, },
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
$is_connected = $container->get( 'settings.flag.is-connected' );
if ( $is_connected ) {
return PAYPAL_SANDBOX_API_URL;
}
return CONNECT_WOO_SANDBOX_URL;
},
'api.production-host' => static function ( ContainerInterface $container ): string {
$is_connected = $container->get( 'settings.flag.is-connected' );
if ( $is_connected ) {
return PAYPAL_API_URL;
}
return CONNECT_WOO_URL;
},
); );

View file

@ -0,0 +1,208 @@
<?php
/**
* Eligibility status.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
use RuntimeException;
use Exception;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class ProductStatus
*
* Base class to check the eligibility of a product for the current merchant.
*/
abstract class ProductStatus {
/**
* Caches the SellerStatus API response to avoid duplicate API calls
* during the same request.
*
* @var ?SellerStatus
*/
private static ?SellerStatus $seller_status = null;
/**
* The current status stored in memory.
*
* @var bool|null
*/
private ?bool $is_eligible = null;
/**
* If there was a request failure.
*
* @var bool
*/
private bool $has_request_failure = false;
/**
* Whether the merchant onboarding process was completed and the
* merchant API is available.
*
* @var bool
*/
private bool $is_connected;
/**
* The partners endpoint.
*
* @var PartnersEndpoint
*/
private PartnersEndpoint $partners_endpoint;
/**
* The API failure registry
*
* @var FailureRegistry
*/
private FailureRegistry $api_failure_registry;
/**
* AppleProductStatus constructor.
*
* @param bool $is_connected Whether the merchant is connected.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
bool $is_connected,
PartnersEndpoint $partners_endpoint,
FailureRegistry $api_failure_registry
) {
$this->is_connected = $is_connected;
$this->partners_endpoint = $partners_endpoint;
$this->api_failure_registry = $api_failure_registry;
}
/**
* Uses local data (DB values, hooks) to determine if the feature is eligible.
*
* Returns true when the feature is available, and false if ineligible.
* On failure, an RuntimeException is thrown.
*
* @return null|bool Boolean to indicate the status; null if the status not locally defined.
* @throws RuntimeException When the check failed.
* @throws NotFoundException When a relevant service or setting was not found.
*/
abstract protected function check_local_state() : ?bool;
/**
* Inspects the API response of the SellerStatus to determine feature eligibility.
*
* Returns true when the feature is available, and false if ineligible.
* On failure, an RuntimeException is thrown.
*
* @param SellerStatus $seller_status The seller status, returned from the API.
* @return bool
* @throws RuntimeException When the check failed.
*/
abstract protected function check_active_state( SellerStatus $seller_status ) : bool;
/**
* Clears the eligibility status from the local cache/DB to enforce a new
* API call on the next eligibility check.
*
* @param Settings|null $settings See description in {@see self::clear()}.
* @return void
*/
abstract protected function clear_state( Settings $settings = null ) : void;
/**
* Whether the merchant has access to the feature.
*
* @return bool
*/
public function is_active() : bool {
if ( null !== $this->is_eligible ) {
return $this->is_eligible;
}
$this->is_eligible = false;
$this->has_request_failure = false;
if ( ! $this->is_onboarded() ) {
return $this->is_eligible;
}
try {
// Try to use filters and DB values to determine the state.
$local_state = $this->check_local_state();
if ( null !== $local_state ) {
$this->is_eligible = $local_state;
return $this->is_eligible;
}
// Check using the merchant-API.
$seller_status = $this->get_seller_status_object();
$this->is_eligible = $this->check_active_state( $seller_status );
} catch ( Exception $exception ) {
$this->has_request_failure = true;
}
return $this->is_eligible;
}
/**
* Fetches the seller-status object from the PayPal merchant API.
*
* @return SellerStatus
* @throws RuntimeException When the check failed.
*/
protected function get_seller_status_object() : SellerStatus {
if ( null === self::$seller_status ) {
// Check API failure registry to prevent multiple failed API requests.
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, MINUTE_IN_SECONDS ) ) {
throw new RuntimeException( 'Timeout for re-check not reached yet' );
}
// Request seller status via PayPal API, might throw an Exception.
self::$seller_status = $this->partners_endpoint->seller_status();
}
return self::$seller_status;
}
/**
* Whether the merchant was fully onboarded, and we have valid API credentials.
*
* @return bool True, if we can use the merchant API endpoints.
*/
public function is_onboarded() : bool {
return $this->is_connected;
}
/**
* Returns if there was a request failure.
*
* @return bool
*/
public function has_request_failure() : bool {
return $this->has_request_failure;
}
/**
* Clears the persisted result to force a recheck.
*
* Accepts a Settings object to don't override other sequential settings that are being updated
* elsewhere.
*
* @param Settings|null $settings The settings object.
* @return void
*/
public function clear( Settings $settings = null ) : void {
$this->is_eligible = null;
$this->has_request_failure = false;
$this->clear_state( $settings );
}
}

View file

@ -10,7 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Applepay; namespace WooCommerce\PayPalCommerce\Applepay;
use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary; use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;

View file

@ -19,7 +19,7 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -79,7 +79,7 @@ return array(
return new AppleProductStatus( return new AppleProductStatus(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ), $container->get( 'api.endpoint.partners' ),
$container->get( 'onboarding.state' ), $container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' ) $container->get( 'api.helper.failure-registry' )
); );
} }
@ -308,5 +308,4 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' )
); );
}, },
); );

View file

@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
@ -183,7 +183,7 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
); );
add_filter( add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data', 'woocommerce_paypal_payments_rest_common_merchant_features',
function( array $features ) use ( $c ): array { function( array $features ) use ( $c ): array {
$product_status = $c->get( 'applepay.apple-product-status' ); $product_status = $c->get( 'applepay.apple-product-status' );
assert( $product_status instanceof AppleProductStatus ); assert( $product_status instanceof AppleProductStatus );

View file

@ -5,134 +5,70 @@
* @package WooCommerce\PayPalCommerce\Applepay\Assets * @package WooCommerce\PayPalCommerce\Applepay\Assets
*/ */
declare(strict_types=1); declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Applepay\Assets; namespace WooCommerce\PayPalCommerce\Applepay\Assets;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusCapability; use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusCapability;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ProductStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
/** /**
* Class AppleProductStatus * Class AppleProductStatus
*/ */
class AppleProductStatus { class AppleProductStatus extends ProductStatus {
const CAPABILITY_NAME = 'APPLE_PAY'; public const CAPABILITY_NAME = 'APPLE_PAY';
const SETTINGS_KEY = 'products_apple_enabled'; public const SETTINGS_KEY = 'products_apple_enabled';
const SETTINGS_VALUE_ENABLED = 'yes'; public const SETTINGS_VALUE_ENABLED = 'yes';
const SETTINGS_VALUE_DISABLED = 'no'; public const SETTINGS_VALUE_DISABLED = 'no';
const SETTINGS_VALUE_UNDEFINED = ''; public const SETTINGS_VALUE_UNDEFINED = '';
/**
* The current status stored in memory.
*
* @var bool|null
*/
private $current_status = null;
/**
* If there was a request failure.
*
* @var bool
*/
private $has_request_failure = false;
/** /**
* The settings. * The settings.
* *
* @var Settings * @var Settings
*/ */
private $settings; private Settings $settings;
/**
* The partners endpoint.
*
* @var PartnersEndpoint
*/
private $partners_endpoint;
/**
* The onboarding status
*
* @var State
*/
private $onboarding_state;
/**
* The API failure registry
*
* @var FailureRegistry
*/
private $api_failure_registry;
/** /**
* AppleProductStatus constructor. * AppleProductStatus constructor.
* *
* @param Settings $settings The Settings. * @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint. * @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param State $onboarding_state The onboarding state. * @param bool $is_connected The onboarding state.
* @param FailureRegistry $api_failure_registry The API failure registry. * @param FailureRegistry $api_failure_registry The API failure registry.
*/ */
public function __construct( public function __construct(
Settings $settings, Settings $settings,
PartnersEndpoint $partners_endpoint, PartnersEndpoint $partners_endpoint,
State $onboarding_state, bool $is_connected,
FailureRegistry $api_failure_registry FailureRegistry $api_failure_registry
) { ) {
$this->settings = $settings; parent::__construct( $is_connected, $partners_endpoint, $api_failure_registry );
$this->partners_endpoint = $partners_endpoint;
$this->onboarding_state = $onboarding_state; $this->settings = $settings;
$this->api_failure_registry = $api_failure_registry;
} }
/** /** {@inheritDoc} */
* Whether the active/subscribed products support Applepay. protected function check_local_state() : ?bool {
*
* @return bool
*/
public function is_active() : bool {
// If not onboarded then makes no sense to check status.
if ( ! $this->is_onboarded() ) {
return false;
}
$status_override = apply_filters( 'woocommerce_paypal_payments_apple_pay_product_status', null ); $status_override = apply_filters( 'woocommerce_paypal_payments_apple_pay_product_status', null );
if ( null !== $status_override ) { if ( null !== $status_override ) {
return $status_override; return $status_override;
} }
// If status was already checked on this request return the same result.
if ( null !== $this->current_status ) {
return $this->current_status;
}
// Check if status was checked on previous requests.
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) { if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
$this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) ); return wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
return $this->current_status;
} }
// Check API failure registry to prevent multiple failed API requests. return null;
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { }
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
// Request seller status via PayPal API.
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
/** {@inheritDoc} */
protected function check_active_state( SellerStatus $seller_status ) : bool {
// Check the seller status for the intended capability. // Check the seller status for the intended capability.
$has_capability = false; $has_capability = false;
foreach ( $seller_status->products() as $product ) { foreach ( $seller_status->products() as $product ) {
@ -145,65 +81,33 @@ class AppleProductStatus {
} }
} }
foreach ( $seller_status->capabilities() as $capability ) { if ( ! $has_capability ) {
if ( $capability->name() === self::CAPABILITY_NAME && $capability->status() === SellerStatusCapability::STATUS_ACTIVE ) { foreach ( $seller_status->capabilities() as $capability ) {
$has_capability = true; if ( $capability->name() === self::CAPABILITY_NAME && $capability->status() === SellerStatusCapability::STATUS_ACTIVE ) {
$has_capability = true;
}
} }
} }
if ( $has_capability ) { if ( $has_capability ) {
// Capability found, persist status and return true.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED ); $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
$this->settings->persist(); } else {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->current_status = true;
return $this->current_status;
} }
// Capability not found, persist status and return false.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->settings->persist(); $this->settings->persist();
$this->current_status = false; return $has_capability;
return $this->current_status;
} }
/** /** {@inheritDoc} */
* Returns if the seller is onboarded. protected function clear_state( Settings $settings = null ) : void {
*
* @return bool
*/
public function is_onboarded(): bool {
return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED;
}
/**
* Returns if there was a request failure.
*
* @return bool
*/
public function has_request_failure(): bool {
return $this->has_request_failure;
}
/**
* Clears the persisted result to force a recheck.
*
* @param Settings|null $settings The settings object.
* We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
* @return void
*/
public function clear( Settings $settings = null ): void {
if ( null === $settings ) { if ( null === $settings ) {
$settings = $this->settings; $settings = $this->settings;
} }
$this->current_status = null;
if ( $settings->has( self::SETTINGS_KEY ) ) { if ( $settings->has( self::SETTINGS_KEY ) ) {
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED ); $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist(); $settings->persist();
} }
} }
} }

View file

@ -13,7 +13,7 @@ use WC_Payment_Gateway;
use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint; use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;

View file

@ -12,7 +12,7 @@ namespace WooCommerce\PayPalCommerce\Axo\Assets;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint; use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;

View file

@ -19,7 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewaySettingsRendererTrait; use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewaySettingsRendererTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
@ -175,12 +175,13 @@ class AxoGateway extends WC_Payment_Gateway {
$this->method_title = __( 'Fastlane Debit & Credit Cards', 'woocommerce-paypal-payments' ); $this->method_title = __( 'Fastlane Debit & Credit Cards', 'woocommerce-paypal-payments' );
$this->method_description = __( 'Fastlane accelerates the checkout experience for guest shoppers and autofills their details so they can pay in seconds. When enabled, Fastlane is presented as the default payment method for guests.', 'woocommerce-paypal-payments' ); $this->method_description = __( 'Fastlane accelerates the checkout experience for guest shoppers and autofills their details so they can pay in seconds. When enabled, Fastlane is presented as the default payment method for guests.', 'woocommerce-paypal-payments' );
$is_axo_enabled = $this->dcc_configuration->use_fastlane(); if ( apply_filters( 'woocommerce_paypal_payments_axo_gateway_should_update_enabled', true ) ) {
$this->update_option( 'enabled', $is_axo_enabled ? 'yes' : 'no' ); $is_axo_enabled = $this->dcc_configuration->use_fastlane();
$this->update_option( 'enabled', $is_axo_enabled ? 'yes' : 'no' );
}
$this->title = $this->dcc_configuration->gateway_title( $this->get_option( 'title', $this->method_title ) ); $this->title = apply_filters( 'woocommerce_paypal_payments_axo_gateway_title', $this->dcc_configuration->gateway_title( $this->get_option( 'title', $this->method_title ) ), $this );
$this->description = apply_filters( 'woocommerce_paypal_payments_axo_gateway_description', __( 'Enter your email address above to continue.', 'woocommerce-paypal-payments' ), $this );
$this->description = __( 'Enter your email address above to continue.', 'woocommerce-paypal-payments' );
$this->init_form_fields(); $this->init_form_fields();
$this->init_settings(); $this->init_settings();

View file

@ -35,7 +35,7 @@ use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;

View file

@ -36,7 +36,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources; use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule; use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule;
use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksModule; use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksModule;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;

View file

@ -128,13 +128,6 @@ return array(
} }
return array( return array(
new SettingsMap(
$container->get( 'settings.data.general' ),
array(
'client_id' => 'client_id',
'client_secret' => 'client_secret',
)
),
new SettingsMap( new SettingsMap(
$container->get( 'settings.data.general' ), $container->get( 'settings.data.general' ),
/** /**
@ -145,7 +138,10 @@ return array(
* the credentials are used for. * the credentials are used for.
*/ */
array( array(
'is_sandbox' => 'sandbox_merchant', 'merchant_id' => 'merchant_id',
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'sandbox_on' => 'sandbox_merchant',
'live_client_id' => 'client_id', 'live_client_id' => 'client_id',
'live_client_secret' => 'client_secret', 'live_client_secret' => 'client_secret',
'live_merchant_id' => 'merchant_id', 'live_merchant_id' => 'merchant_id',

View file

@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -75,7 +75,7 @@ return array(
return new ApmProductStatus( return new ApmProductStatus(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ), $container->get( 'api.endpoint.partners' ),
$container->get( 'onboarding.state' ), $container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' ) $container->get( 'api.helper.failure-registry' )
); );
} }

View file

@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;

View file

@ -233,7 +233,7 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
); );
add_filter( add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data', 'woocommerce_paypal_payments_rest_common_merchant_features',
function ( array $features ) use ( $c ): array { function ( array $features ) use ( $c ): array {
$product_status = $c->get( 'googlepay.helpers.apm-product-status' ); $product_status = $c->get( 'googlepay.helpers.apm-product-status' );
assert( $product_status instanceof ApmProductStatus ); assert( $product_status instanceof ApmProductStatus );

View file

@ -9,130 +9,66 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Helper; namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusCapability; use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusCapability;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ProductStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
/** /**
* Class ApmProductStatus * Class ApmProductStatus
*/ */
class ApmProductStatus { class ApmProductStatus extends ProductStatus {
const CAPABILITY_NAME = 'GOOGLE_PAY'; public const CAPABILITY_NAME = 'GOOGLE_PAY';
const SETTINGS_KEY = 'products_googlepay_enabled'; public const SETTINGS_KEY = 'products_googlepay_enabled';
const SETTINGS_VALUE_ENABLED = 'yes'; public const SETTINGS_VALUE_ENABLED = 'yes';
const SETTINGS_VALUE_DISABLED = 'no'; public const SETTINGS_VALUE_DISABLED = 'no';
const SETTINGS_VALUE_UNDEFINED = ''; public const SETTINGS_VALUE_UNDEFINED = '';
/**
* The current status stored in memory.
*
* @var bool|null
*/
private $current_status = null;
/**
* If there was a request failure.
*
* @var bool
*/
private $has_request_failure = false;
/** /**
* The settings. * The settings.
* *
* @var Settings * @var Settings
*/ */
private $settings; private Settings $settings;
/**
* The partners endpoint.
*
* @var PartnersEndpoint
*/
private $partners_endpoint;
/**
* The onboarding status
*
* @var State
*/
private $onboarding_state;
/**
* The API failure registry
*
* @var FailureRegistry
*/
private $api_failure_registry;
/** /**
* ApmProductStatus constructor. * ApmProductStatus constructor.
* *
* @param Settings $settings The Settings. * @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint. * @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param State $onboarding_state The onboarding state. * @param bool $is_connected The onboarding state.
* @param FailureRegistry $api_failure_registry The API failure registry. * @param FailureRegistry $api_failure_registry The API failure registry.
*/ */
public function __construct( public function __construct(
Settings $settings, Settings $settings,
PartnersEndpoint $partners_endpoint, PartnersEndpoint $partners_endpoint,
State $onboarding_state, bool $is_connected,
FailureRegistry $api_failure_registry FailureRegistry $api_failure_registry
) { ) {
$this->settings = $settings; parent::__construct( $is_connected, $partners_endpoint, $api_failure_registry );
$this->partners_endpoint = $partners_endpoint;
$this->onboarding_state = $onboarding_state; $this->settings = $settings;
$this->api_failure_registry = $api_failure_registry;
} }
/** /** {@inheritDoc} */
* Whether the active/subscribed products support Googlepay. protected function check_local_state() : ?bool {
*
* @return bool
*/
public function is_active() : bool {
// If not onboarded then makes no sense to check status.
if ( ! $this->is_onboarded() ) {
return false;
}
$status_override = apply_filters( 'woocommerce_paypal_payments_google_pay_product_status', null ); $status_override = apply_filters( 'woocommerce_paypal_payments_google_pay_product_status', null );
if ( null !== $status_override ) { if ( null !== $status_override ) {
return $status_override; return $status_override;
} }
// If status was already checked on this request return the same result.
if ( null !== $this->current_status ) {
return $this->current_status;
}
// Check if status was checked on previous requests.
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) { if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
$this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) ); return wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
return $this->current_status;
} }
// Check API failure registry to prevent multiple failed API requests. return null;
if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { }
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
// Request seller status via PayPal API.
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
$this->has_request_failure = true;
$this->current_status = false;
return $this->current_status;
}
/** {@inheritDoc} */
protected function check_active_state( SellerStatus $seller_status ) : bool {
// Check the seller status for the intended capability. // Check the seller status for the intended capability.
$has_capability = false; $has_capability = false;
foreach ( $seller_status->products() as $product ) { foreach ( $seller_status->products() as $product ) {
@ -145,65 +81,33 @@ class ApmProductStatus {
} }
} }
foreach ( $seller_status->capabilities() as $capability ) { if ( ! $has_capability ) {
if ( $capability->name() === self::CAPABILITY_NAME && $capability->status() === SellerStatusCapability::STATUS_ACTIVE ) { foreach ( $seller_status->capabilities() as $capability ) {
$has_capability = true; if ( $capability->name() === self::CAPABILITY_NAME && $capability->status() === SellerStatusCapability::STATUS_ACTIVE ) {
$has_capability = true;
}
} }
} }
if ( $has_capability ) { if ( $has_capability ) {
// Capability found, persist status and return true.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED ); $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
$this->settings->persist(); } else {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->current_status = true;
return $this->current_status;
} }
// Capability not found, persist status and return false.
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->settings->persist(); $this->settings->persist();
$this->current_status = false; return $has_capability;
return $this->current_status;
} }
/** /** {@inheritDoc} */
* Returns if the seller is onboarded. protected function clear_state( Settings $settings = null ) : void {
*
* @return bool
*/
public function is_onboarded(): bool {
return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED;
}
/**
* Returns if there was a request failure.
*
* @return bool
*/
public function has_request_failure(): bool {
return $this->has_request_failure;
}
/**
* Clears the persisted result to force a recheck.
*
* @param Settings|null $settings The settings object.
* We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
* @return void
*/
public function clear( Settings $settings = null ): void {
if ( null === $settings ) { if ( null === $settings ) {
$settings = $this->settings; $settings = $this->settings;
} }
$this->current_status = null;
if ( $settings->has( self::SETTINGS_KEY ) ) { if ( $settings->has( self::SETTINGS_KEY ) ) {
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED ); $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist(); $settings->persist();
} }
} }
} }

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods; namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus;
return array( return array(
'ppcp-local-apms.url' => static function ( ContainerInterface $container ): string { 'ppcp-local-apms.url' => static function ( ContainerInterface $container ): string {
@ -67,6 +68,14 @@ return array(
), ),
); );
}, },
'ppcp-local-apms.product-status' => static function ( ContainerInterface $container ): LocalApmProductStatus {
return new LocalApmProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' )
);
},
'ppcp-local-apms.bancontact.wc-gateway' => static function ( ContainerInterface $container ): BancontactGateway { 'ppcp-local-apms.bancontact.wc-gateway' => static function ( ContainerInterface $container ): BancontactGateway {
return new BancontactGateway( return new BancontactGateway(
$container->get( 'api.endpoint.orders' ), $container->get( 'api.endpoint.orders' ),

View file

@ -0,0 +1,95 @@
<?php
/**
* Status of local alternative payment methods.
*
* @package WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ProductStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
/**
* Class LocalApmProductStatus
*/
class LocalApmProductStatus extends ProductStatus {
public const SETTINGS_KEY = 'products_local_apms_enabled';
public const SETTINGS_VALUE_ENABLED = 'yes';
public const SETTINGS_VALUE_DISABLED = 'no';
public const SETTINGS_VALUE_UNDEFINED = '';
/**
* The settings.
*
* @var Settings
*/
private Settings $settings;
/**
* ApmProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param bool $is_connected The onboarding state.
* @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
bool $is_connected,
FailureRegistry $api_failure_registry
) {
parent::__construct( $is_connected, $partners_endpoint, $api_failure_registry );
$this->settings = $settings;
}
/** {@inheritDoc} */
protected function check_local_state() : ?bool {
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
return wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
}
return null;
}
/** {@inheritDoc} */
protected function check_active_state( SellerStatus $seller_status ) : bool {
$has_capability = false;
foreach ( $seller_status->products() as $product ) {
if ( $product->name() === 'PAYMENT_METHODS' ) {
$has_capability = true;
break;
}
}
if ( $has_capability ) {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
} else {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
}
$this->settings->persist();
return $has_capability;
}
/** {@inheritDoc} */
protected function clear_state( Settings $settings = null ) : void {
if ( null === $settings ) {
$settings = $this->settings;
}
if ( $settings->has( self::SETTINGS_KEY ) ) {
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist();
}
}
}

View file

@ -141,6 +141,7 @@ class TrustlyGateway extends WC_Payment_Gateway {
'trustly' => array( 'trustly' => array(
'country_code' => $wc_order->get_billing_country(), 'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(), 'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
), ),
), ),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL', 'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',

View file

@ -11,58 +11,15 @@ namespace WooCommerce\PayPalCommerce\Onboarding;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
return array( return array(
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
$state = $container->get( 'onboarding.state' );
/**
* The State object.
*
* @var State $state
*/
if ( $state->current_state() >= State::STATE_ONBOARDED ) {
return PAYPAL_SANDBOX_API_URL;
}
return CONNECT_WOO_SANDBOX_URL;
},
'api.production-host' => static function ( ContainerInterface $container ): string {
$state = $container->get( 'onboarding.state' );
/**
* The Environment and State variables.
*
* @var Environment $environment
* @var State $state
*/
if ( $state->current_state() >= State::STATE_ONBOARDED ) {
return PAYPAL_API_URL;
}
return CONNECT_WOO_URL;
},
'api.host' => static function ( ContainerInterface $container ): string {
$environment = $container->get( 'onboarding.environment' );
/**
* The Environment and State variables.
*
* @var Environment $environment
*/
return $environment->current_environment_is( Environment::SANDBOX )
? (string) $container->get( 'api.sandbox-host' ) : (string) $container->get( 'api.production-host' );
},
'api.paypal-host' => function( ContainerInterface $container ) : string { 'api.paypal-host' => function( ContainerInterface $container ) : string {
$environment = $container->get( 'onboarding.environment' ); $environment = $container->get( 'onboarding.environment' );
/** /**
@ -85,38 +42,20 @@ return array(
return $container->get( 'api.paypal-website-url-production' ); return $container->get( 'api.paypal-website-url-production' );
}, },
'api.bearer' => static function ( ContainerInterface $container ): Bearer {
$state = $container->get( 'onboarding.state' );
/**
* The State.
*
* @var State $state
*/
if ( $state->current_state() < State::STATE_ONBOARDED ) {
return new ConnectBearer();
}
$cache = new Cache( 'ppcp-paypal-bearer' );
$key = $container->get( 'api.key' );
$secret = $container->get( 'api.secret' );
$host = $container->get( 'api.host' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$settings = $container->get( 'wcgateway.settings' );
return new PayPalBearer(
$cache,
$host,
$key,
$secret,
$logger,
$settings
);
},
'onboarding.state' => function( ContainerInterface $container ) : State { 'onboarding.state' => function( ContainerInterface $container ) : State {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new State( $settings ); return new State( $settings );
}, },
/**
* Checks if the onboarding process is completed and the merchant API can be used.
* This service is overwritten by the ppcp-settings module, when it's active.
*/
'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool {
$state = $container->get( 'onboarding.state' );
assert( $state instanceof State );
return $state->current_state() >= State::STATE_ONBOARDED;
},
'onboarding.environment' => function( ContainerInterface $container ) : Environment { 'onboarding.environment' => function( ContainerInterface $container ) : Environment {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new Environment( $settings ); return new Environment( $settings );
@ -149,8 +88,8 @@ return array(
$login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' ); $login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' );
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' ); $partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$cache = new Cache( 'ppcp-paypal-bearer' ); $cache = $container->get( 'api.paypal-bearer-cache' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
return new LoginSellerEndpoint( return new LoginSellerEndpoint(
$request_data, $request_data,
$login_seller_production, $login_seller_production,

View file

@ -11,7 +11,7 @@ namespace WooCommerce\PayPalCommerce\Onboarding\Assets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;

View file

@ -1,60 +0,0 @@
<?php
/**
* Used to detect the current environment.
*
* @package WooCommerce\PayPalCommerce\Onboarding
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* Class Environment
*/
class Environment {
const PRODUCTION = 'production';
const SANDBOX = 'sandbox';
/**
* The Settings.
*
* @var ContainerInterface
*/
private $settings;
/**
* Environment constructor.
*
* @param ContainerInterface $settings The Settings.
*/
public function __construct( ContainerInterface $settings ) {
$this->settings = $settings;
}
/**
* Returns the current environment.
*
* @return string
*/
public function current_environment(): string {
return (
$this->settings->has( 'sandbox_on' ) && $this->settings->get( 'sandbox_on' )
) ? self::SANDBOX : self::PRODUCTION;
}
/**
* Detect whether the current environment equals $environment
*
* @param string $environment The value to check against.
*
* @return bool
*/
public function current_environment_is( string $environment ): bool {
return $this->current_environment() === $environment;
}
}

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding; namespace WooCommerce\PayPalCommerce\Onboarding;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
/** /**
* Class State * Class State

View file

@ -94,7 +94,7 @@ class SaveConfig {
* *
* @param array $config The configurator config. * @param array $config The configurator config.
*/ */
private function save_config( array $config ): void { public function save_config( array $config ): void {
$this->settings->set( 'pay_later_enable_styling_per_messaging_location', true ); $this->settings->set( 'pay_later_enable_styling_per_messaging_location', true );
$this->settings->set( 'pay_later_messaging_enabled', true ); $this->settings->set( 'pay_later_messaging_enabled', true );

View file

@ -21,7 +21,7 @@ use WC_Subscriptions_Product;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;

View file

@ -8,8 +8,8 @@
} }
@mixin hide-input-field() { @mixin hide-input-field() {
width: 100%; width: auto;
height: 100%; height: auto;
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;

View file

@ -19,7 +19,6 @@ $color-divider: #F0F0F0;
$color-error-red: #cc1818; $color-error-red: #cc1818;
$shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15); $shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15);
$shadow-selection-box: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
$color-gradient-dark: #001435; $color-gradient-dark: #001435;
$gradient-header: linear-gradient(87.03deg, #003087 -0.49%, #001E51 29.22%, $color-gradient-dark 100%); $gradient-header: linear-gradient(87.03deg, #003087 -0.49%, #001E51 29.22%, $color-gradient-dark 100%);
@ -71,4 +70,15 @@ $card-vertical-gap: 48px;
--block-separator-size: 1px; --block-separator-size: 1px;
--block-separator-color: var(--color-gray-200); --block-separator-color: var(--color-gray-200);
--block-action-gap: 16px; // Space between two consecutive action blocks. --block-action-gap: 16px; // Space between two consecutive action blocks.
// Default visual effects.
--box-shadow-active-item: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
--container-border-radius: 8px;
// Spinner/loader.
--spinner-size: 20px;
--spinner-overlay-width: 320px;
--spinner-overlay-height: 320px;
--spinner-overlay-color: #fafafa;
--spinner-overlay-box-shadow: var(--box-shadow-active-item);
} }

View file

@ -5,7 +5,6 @@
@import './reusable-components/button'; @import './reusable-components/button';
@import './reusable-components/elements'; @import './reusable-components/elements';
@import './reusable-components/fields'; @import './reusable-components/fields';
@import './reusable-components/hstack';
@import './reusable-components/navigation'; @import './reusable-components/navigation';
@import './reusable-components/onboarding-header'; @import './reusable-components/onboarding-header';
@import './reusable-components/payment-method-icons'; @import './reusable-components/payment-method-icons';
@ -16,6 +15,7 @@
@import './reusable-components/settings-toggle-block'; @import './reusable-components/settings-toggle-block';
@import './reusable-components/settings-wrapper'; @import './reusable-components/settings-wrapper';
@import './reusable-components/spinner-overlay'; @import './reusable-components/spinner-overlay';
@import './reusable-components/stack';
@import './reusable-components/tab-navigation'; @import './reusable-components/tab-navigation';
@import './reusable-components/title-badge'; @import './reusable-components/title-badge';
@import './reusable-components/welcome-docs'; @import './reusable-components/welcome-docs';

View file

@ -1,21 +1,14 @@
.ppcp-r-badge-box { .ppcp-r-badge-box {
margin: 0 0 8px 0; margin: 0 0 8px 0;
&__title { .ppcp-r-badge-box__title {
@include font(14, 20, 700); @include font(14, 20, 700);
display: block; display: block;
margin: 0 0 8px 0; margin: 0 0 8px 0;
.ppcp-r-badge-box__title-text { .ppcp-r-badge-box__title-text {
@include font(16, 28, 700);
color: #000; color: #000;
&--big {
@include font(16, 28, 700);
}
&--small {
vertical-align: text-bottom;
}
} }
.ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge { .ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge {
@ -27,25 +20,32 @@
height: 24px; height: 24px;
} }
} }
}
&--has-image-badge .ppcp-r-title-badge--info { .ppcp-r-badge-box__title--has-image-badge .ppcp-r-title-badge--info {
display: block; display: block;
margin: 6px 0px 0px 0px; margin: 6px 0 0 0;
width: fit-content; width: fit-content;
} }
@media screen and (max-width: 480px) { .components-button.is-tertiary {
display: flex; padding: 0;
justify-content: center; margin: 0 0 0 4px;
align-items: center; height: 1em;
gap: 8px; }
flex-direction: column;
.ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge { @media screen and (max-width: 480px) {
margin: 0px; display: flex;
img:first-of-type { justify-content: center;
margin: 0px; align-items: center;
} gap: 8px;
flex-direction: column;
.ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge {
margin: 0;
img:first-of-type {
margin: 0;
} }
} }
} }

View file

@ -38,6 +38,8 @@ button.components-button, a.components-button {
/* style the button template */ /* style the button template */
text-align: center;
&:not(:disabled) { &:not(:disabled) {
@extend %button-style-default; @extend %button-style-default;
} }

View file

@ -29,7 +29,7 @@
.ppcp-r-title-badge { .ppcp-r-title-badge {
text-transform: none; text-transform: none;
margin-left: 6px; margin-left: 8px;
} }
} }

View file

@ -1,31 +0,0 @@
.ppcp-r-app {
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
-webkit-box-pack: center;
.components-h-stack {
flex-direction: row;
justify-content: flex-start;
gap: 32px;
}
.components-v-stack {
flex-direction: column;
justify-content: center;
}
// Fix layout for checkboxes inside a flex-stack.
.components-checkbox-control > .components-base-control__field > .components-flex {
flex-direction: row;
gap: 12px;
}
}
.ppcp--horizontal {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}

View file

@ -1,24 +1,33 @@
$margin_bottom: 48px;
.ppcp-r-navigation-container { .ppcp-r-navigation-container {
// Theming.
--wp-components-color-accent: #{$color-blueberry};
--color-text: #{$color-gray-900};
--color-disabled: #CCC;
--navbar-height: 40px;
--navbar-vertical-padding: 10px;
--subnavigation-height: 40px;
// Styling.
position: sticky; position: sticky;
top: var(--wp-admin--admin-bar--height); top: var(--wp-admin--admin-bar--height);
z-index: 10; z-index: 10;
padding: 10px 48px; padding: 0 48px;
margin: 0 -20px 48px -20px; margin: 0 -20px #{$margin_bottom} -20px;
box-shadow: 0 -1px 0 0 $color-gray-300 inset; box-shadow: 0 -1px 0 0 $color-gray-300 inset;
background: var(--ppcp-color-app-bg); background: var(--ppcp-color-app-bg);
transition: box-shadow 0.3s; transition: box-shadow 0.3s;
--wp-components-color-accent: #{$color-blueberry};
--color-text: #{$color-gray-900};
--color-disabled: #CCC;
.ppcp-r-navigation { .ppcp-r-navigation {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 40px; flex-direction: row;
height: calc(var(--navbar-height) + (2 * var(--navbar-vertical-padding)));
padding: var(--navbar-vertical-padding) 0;
.components-button { .components-button {
@include font(13, 20, 400); @include font(13, 20, 400);
@ -57,32 +66,46 @@
} }
} }
} }
}
&--left { .ppcp-r-navigation--left {
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
} }
&--right { .ppcp-r-navigation--right {
.is-link { .is-link {
padding: 10px 16px; padding: 10px 16px;
}
} }
}
&--progress-bar { .ppcp-r-navigation--progress-bar {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: var(--wp-components-color-accent); background-color: var(--wp-components-color-accent);
height: 4px; height: 4px;
transition: width 0.3s; transition: width 0.3s;
}
} }
&.ppcp--is-scrolled { &.ppcp--is-scrolled {
box-shadow: 0 -1px 0 0 $color-gray-300 inset, 0 8px 8px 0 rgba(85, 93, 102, .3); box-shadow: 0 -1px 0 0 $color-gray-300 inset, 0 8px 8px 0 rgba(85, 93, 102, .3);
} }
.ppcp--top-sub-navigation {
height: var(--subnavigation-height);
margin: 0;
padding: 0;
.ppcp-r-tabs {
margin: 0;
}
.components-tab-panel__tabs-item {
height: var(--subnavigation-height);
}
}
@media screen and (max-width: 782px) { @media screen and (max-width: 782px) {
padding: 10px 12px; padding: 10px 12px;

View file

@ -3,6 +3,7 @@
gap: 8px; gap: 8px;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
> img{ > img{
width: 38px; width: 38px;
height: 24px; height: 24px;

View file

@ -1,74 +1,80 @@
.ppcp-r-settings-block__payment-methods { // Grid layout.
&.ppcp-r-settings-block { .ppcp-r-settings-block.ppcp--grid > .ppcp--content {
display: flex; --block-separator-gap: 0;
flex-wrap: wrap;
flex-direction: row; display: grid;
gap: 16px; grid-template-columns: repeat(3, 1fr);
gap: 16px;
@media screen and (max-width: 767px) {
grid-template-columns: repeat(2, 1fr);
} }
&__item { @media screen and (max-width: 480px) {
grid-template-columns: 1fr;
}
}
// Theming & visual styles.
.ppcp-r-settings-block__payment-methods {
.ppcp-r-settings-block {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
width: calc(100% / 3 - 32px / 3);
border: 1px solid $color-gray-300; border: 1px solid $color-gray-300;
border-radius: var(--container-border-radius);
padding: 16px; padding: 16px;
border-radius: 8px;
min-height: 200px; min-height: 200px;
@media screen and (max-width: 767px) { .ppcp--method-inner {
width: calc(50% - 8px);
}
@media screen and (max-width: 480px) {
width: 100%;
}
&__inner {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
&__title-wrapper { .ppcp--method-title-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0 0 8px 0; margin: 0 0 8px 0;
gap: 12px; gap: 12px;
} }
&__description { .ppcp--method-description {
p { p {
@include font(13, 20, 400);
margin: 0; margin: 0;
color: $color-text-tertiary; color: $color-text-tertiary;
@include font(13, 20, 400);
} }
margin: 0 0 12px 0; margin: 0 0 12px 0;
} }
&__title { .ppcp--method-title {
@include font(13, 20, 500); @include font(13, 20, 500);
color: $color-black; color: $color-black;
display: block; display: block;
} }
&__settings { .ppcp--method-settings {
line-height: 0; padding: 0;
transition: 0.2s ease-out transform; transition: 0.2s ease-out transform;
transform: rotate(0deg); transform: rotate(0deg);
zoom: 1.005; zoom: 1.005;
&:hover { &:hover {
transform: rotate(45deg); transform: rotate(45deg);
cursor: pointer;
} }
} }
.ppcp--method-icon {
width: 30px;
height: 30px;
}
button.is-secondary { button.is-secondary {
@include small-button; @include small-button;
} }
&__footer { .ppcp--method-footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View file

@ -1,4 +1,8 @@
.ppcp-r-select-box-wrapper { .ppcp-r-select-box-wrapper {
--box-border-color: var(--color-gray-200);
--box-outline-color: transparent;
--box-shadow: 0;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
gap: 32px; gap: 32px;
@ -6,92 +10,62 @@
} }
.ppcp-r-select-box { .ppcp-r-select-box {
border: 1px solid var(--box-border-color);
outline: 1px solid var(--box-outline-color);
box-shadow: var(--box-shadow);
position: relative; position: relative;
width: 100%; width: 100%;
border: 1px solid $color-gray-200;
outline: 1px solid transparent;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
gap: 16px; gap: 16px;
padding: 24px 16px 24px 16px; padding: 24px 16px 24px 16px;
transition: all 0.3s;
cursor: pointer;
&:hover {
--box-border-color: var(--color-gray-500);
--box-shadow: var(--box-shadow-active-item);
}
&.ppcp--selected { &.ppcp--selected {
border-color: $color-blueberry; --box-border-color: var(--color-blueberry);
outline-color: $color-blueberry; --box-outline-color: var(--color-blueberry);
box-shadow: $shadow-selection-box; --box-shadow: var(--box-shadow-active-item);
} }
&__radio-value { .ppcp--box-content {
@include hide-input-field;
&:checked {
+ .ppcp-r-select-box__radio-presentation {
background: $color-white;
width: 20px;
height: 20px;
border: 6px solid $color-blueberry;
}
}
}
&__checkbox-value {
@include hide-input-field;
&:not(:checked) + .ppcp-r-select-box__checkbox-presentation img {
display: none;
}
&:checked {
+ .ppcp-r-select-box__checkbox-presentation {
width: 20px;
height: 20px;
border: none;
img {
border-radius: 2px;
}
}
}
}
&__content {
display: flex; display: flex;
position: relative; position: relative;
pointer-events: none; pointer-events: none;
*:not(a){
*:not(a) {
pointer-events: none; pointer-events: none;
} }
a { a {
pointer-events: all; pointer-events: all;
} }
} }
&__title { .ppcp--box-title {
@include font(14, 20, 700); @include font(14, 20, 700);
color: $color-black; color: $color-black;
margin: 0 0 4px 0; margin: 0 0 4px 0;
display: block; display: block;
} }
&__description { .ppcp--box-description {
@include font(13, 20, 400); @include font(13, 20, 400);
color: $color-gray-700; color: $color-gray-700;
margin:0; margin: 0;
&:not(:last-child){ &:not(:last-child) {
margin-block-end:18px; margin-block-end: 18px;
} }
} }
&__radio-presentation { .ppcp--box-details {
@include fake-input-field(20px);
}
&__checkbox-presentation {
@include fake-input-field(2px);
}
&__additional-content{
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
@ -99,15 +73,18 @@
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
gap: 16px; gap: 16px;
padding: 18px 16px; padding: 18px 16px;
&__description {
.ppcp--box-description {
margin: 0 0 8px 0; margin: 0 0 8px 0;
} }
&__content {
.ppcp--box-content {
gap: 12px; gap: 12px;
} }
} }
@media screen and (max-width: 380px) { @media screen and (max-width: 380px) {
&__content > img { .ppcp--box-content > img {
max-width: 32px; max-width: 32px;
} }
} }

View file

@ -1,3 +1,8 @@
// Configuration for this module.
$width_container: 938px;
$width_header: 280px;
$width_gap: 24px;
/* /*
Styles the `SettingsCard` layout component. Styles the `SettingsCard` layout component.
@ -14,9 +19,9 @@
--card-layout: block; --card-layout: block;
@media screen and (min-width: 960px) { @media screen and (min-width: 960px) {
--card-width-header: 280px; --card-width-header: #{$width_header};
--card-width-content: 610px; --card-width-content: #{$width_container - $width_header - $width_gap};
--card-gap: 48px; --card-gap: #{$width_gap};
--card-layout: flex; --card-layout: flex;
} }
@ -46,6 +51,7 @@
&.ppcp--is-card { &.ppcp--is-card {
max-width: var(--card-width-content); max-width: var(--card-width-content);
border: 1px solid var(--color-gray-200); border: 1px solid var(--color-gray-200);
width: 100%;
border-radius: 4px; border-radius: 4px;
padding: 24px; padding: 24px;
} }

View file

@ -1,28 +1,26 @@
.ppcp-r { .ppcp-r-container {
&-container { max-width: var(--max-container-width, none);
max-width: var(--max-container-width, none); margin: 0 auto 0 35px;
margin-right: auto; }
}
&-inner-container { .ppcp-r-inner-container {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 0 16px 48px; padding: 0 16px 48px;
box-sizing: content-box; box-sizing: content-box;
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
padding-bottom: 36px; padding-bottom: 36px;
} }
} }
&-settings { .ppcp-r-settings {
> * { > * {
margin-bottom: $card-vertical-gap; margin-bottom: $card-vertical-gap;
} }
> *:not(:last-child) { > *:not(:last-child) {
padding-bottom: $card-vertical-gap; padding-bottom: $card-vertical-gap;
border-bottom: 1px solid $color-gray-200; border-bottom: 1px solid $color-gray-200;
}
} }
} }

View file

@ -1,11 +1,13 @@
.ppcp-r-spinner-overlay { .ppcp-r-spinner-overlay {
position: absolute; width: var(--spinner-overlay-width);
left: 0; height: var(--spinner-overlay-height);
top: 0; box-shadow: var(--spinner-overlay-box-shadow);
right: 0;
bottom: 0;
z-index: 10;
background: var(--spinner-overlay-color); background: var(--spinner-overlay-color);
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 10;
.components-spinner { .components-spinner {
position: absolute; position: absolute;
@ -13,5 +15,15 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
margin: 0; margin: 0;
width: var(--spinner-size);
height: var(--spinner-size);
}
.ppcp--spinner-message {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: calc(50% + 20px + var(--spinner-size));
} }
} }

View file

@ -0,0 +1,24 @@
.ppcp-r-app {
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
-webkit-box-pack: center;
}
.components-flex.components-h-stack,
.ppcp--horizontal {
flex-direction: row;
align-items: center;
}
.ppcp--horizontal {
justify-content: space-between;
}
.components-flex.components-v-stack {
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
}

View file

@ -1,22 +1,17 @@
.ppcp-r-tabs { .ppcp-r-tabs {
--wp-components-color-accent: #{$color-blueberry}; --wp-components-color-accent: #{$color-blueberry};
--wp-admin-border-width-focus: 3px; --wp-admin-border-width-focus: 2px;
max-width: var(--max-container-width);
transition: max-width 0.2s; transition: max-width 0.2s;
padding:0 35px; margin-top: 10px;
width: 100%;
.components-tab-panel__tabs { .components-tab-panel__tabs {
box-shadow: 0 -1px 0 0 $color-gray-400 inset; gap: 0;
margin-bottom: 48px;
gap: 12px;
overflow: auto; overflow: auto;
.components-button { .components-button {
padding: 16px 20px; padding: 16px 20px;
&.is-active {
background-color: #fff4;
}
} }
} }
} }

View file

@ -1,18 +1,22 @@
.ppcp-r-title-badge{ .ppcp-r-title-badge {
--badge-bg-color: #F5F5F5;
--badge-text-color: #2F2F2F;
color: var(--badge-text-color);
background-color: var(--badge-bg-color);
@include font(12, 16, 400); @include font(12, 16, 400);
padding: 4px 8px; padding: 4px 8px;
border-radius: 2px; border-radius: 2px;
white-space: nowrap; white-space: nowrap;
&--positive{
color: #144722; &.ppcp-r-title-badge--positive {
background-color: #DAFFE0; --badge-bg-color: #DAFFE0;
--badge-text-color: #144722;
} }
&--negative{
color:#5c0000; &.ppcp-r-title-badge--negative {
background-color: #faeded; --badge-bg-color: #faeded;
} --badge-text-color: #5c0000;
&--info{
color: #2F2F2F;
background-color: #F5F5F5;
} }
} }

View file

@ -54,8 +54,4 @@
} }
} }
} }
.ppcp-r-page-welcome-mode-separator {
margin: 8px 0 24px 0 !important;
}
} }

View file

@ -11,6 +11,8 @@ body:has(.ppcp-r-container--onboarding) {
.nav-tab-wrapper.woo-nav-tab-wrapper, .nav-tab-wrapper.woo-nav-tab-wrapper,
.woocommerce-layout__header, .woocommerce-layout__header,
.wrap.woocommerce form > h2, .wrap.woocommerce form > h2,
#mainform .subsubsub,
#mainform .subsubsub + br.clear,
#screen-meta-links { #screen-meta-links {
display: none !important; display: none !important;
visibility: hidden; visibility: hidden;

View file

@ -7,7 +7,14 @@
.ppcp-r-container--onboarding { .ppcp-r-container--onboarding {
--max-container-width: var(--max-width-onboarding); --max-container-width: var(--max-width-onboarding);
margin-left: auto;
margin-right: auto;
.ppcp-r-inner-container { .ppcp-r-inner-container {
max-width: var(--max-width-onboarding-content); max-width: var(--max-width-onboarding-content);
} }
.ppcp-r-payment-method--separator {
margin: 8px 0 24px 0;
}
} }

View file

@ -15,12 +15,44 @@
} }
// Todo List and Feature Items // Todo List and Feature Items
.ppcp-r-todo-items {
display: flex;
flex-direction: column;
}
.ppcp-r-todo-item { .ppcp-r-todo-item {
position: relative; position: relative;
display: flex; border-bottom: 1px solid $color-gray-300;
align-items: center; padding-bottom: 16px;
gap: 18px; padding-top: 16px;
width: 100%; opacity: 1;
transform: translateY(0);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:first-child, &.is-dismissing:first-child + .ppcp-r-todo-item {
padding-top: 0;
}
&:last-child,
&:not(.is-dismissing):has(+ .is-dismissing:last-child) {
border-bottom: none;
padding-bottom: 0;
}
&.is-dismissing {
opacity: 0;
transform: translateY(-4px);
height: 0;
margin: 0;
padding: 0;
border: 0;
.ppcp-r-todo-item__inner {
height: 0;
padding: 0;
margin: 0;
}
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;
@ -32,13 +64,25 @@
} }
} }
&:not(:last-child) { &.is-completed {
border-bottom: 1px solid $color-gray-400; .ppcp-r-todo-item__icon {
padding-bottom: 16px; border-style: solid;
} background-color: $color-blueberry;
display: flex;
align-items: center;
justify-content: center;
&:not(:first-child) { .dashicons {
padding-top: 16px; color: #fff;
font-size: 18px;
width: 18px;
height: 18px;
}
}
.ppcp-r-todo-item__content {
text-decoration: line-through;
}
} }
p { p {
@ -47,17 +91,46 @@
&__inner { &__inner {
position: relative; position: relative;
height: auto;
overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 18px; gap: 18px;
width: 100%;
padding-right: 36px;
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
&__close { &__dismiss {
margin-left: auto; position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
background-color: transparent;
border: none;
padding: 4px;
cursor: pointer;
color: $color-gray-400;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
&:hover { &:hover {
cursor: pointer; background-color: $color-gray-100;
color: $color-blueberry; color: $color-gray-700;
}
.dashicons {
font-size: 14px;
width: 14px;
height: 14px;
display: flex;
align-items: center;
justify-content: center;
} }
} }
@ -83,36 +156,30 @@
border-radius: 50%; border-radius: 50%;
width: 24px; width: 24px;
height: 24px; height: 24px;
flex-shrink: 0;
} }
} }
.ppcp-r-feature-item { .ppcp-r-tab-overview-features {
padding-top: 32px; --block-header-gap: 12px;
border-top: 1px solid $color-gray-400; }
&__title { .ppcp-r-tab-overview-help {
@include font(16, 20, 600); --block-header-gap: 8px;
color: $color-black; }
display: block;
margin: 0 0 8px 0;
}
&__description { .ppcp-r-settings-block__feature {
@include font(14, 20, 400); .ppcp--action-buttons {
color: $color-gray-800;
margin: 0 0 18px 0;
}
&:not(:last-child) {
padding-bottom: 32px;
}
&__buttons {
display: flex; display: flex;
gap: 18px; gap: 18px;
.components-button.is-tertiary {
padding-left: 0;
padding-right: 0;
}
} }
&__notes { .ppcp--item-notes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -166,3 +233,23 @@
gap: 48px; gap: 48px;
} }
.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;
}
}

View file

@ -6,33 +6,31 @@
} }
} }
.ppcp-r-optional-payment-methods { .ppcp-r-optional-payment-methods__wrapper {
&__wrapper { .ppcp-r-badge-box {
.ppcp-r-badge-box {
margin: 0 0 24px 0;
&:last-child {
margin: 0;
}
}
.ppcp-r-badge-box__description {
margin: 12px 0 0 0;
@include font(14, 20, 400);
}
}
&__description {
margin: 32px 0 0 0;
text-align: center;
@include font(14, 22, 400);
font-style: italic;
a {
color: $color-gray-700;
}
}
&__separator {
margin: 0 0 24px 0; margin: 0 0 24px 0;
&:last-child {
margin: 0;
}
}
.ppcp-r-badge-box__description {
margin: 12px 0 0 0;
} }
} }
.ppcp-r-optional-payment-methods__description {
@include font(14, 22, 400);
margin: 32px 0 0 0;
text-align: center;
font-style: italic;
a {
color: $color-gray-700;
}
}
.ppcp-r-optional-payment-methods__separator {
margin: 0 0 24px 0;
}

View file

@ -68,6 +68,7 @@
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
flex-wrap: wrap; flex-wrap: wrap;
row-gap: 8px; row-gap: 8px;
&__col { &__col {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -77,7 +78,7 @@
border-right: 0; border-right: 0;
padding-right: 0; padding-right: 0;
padding-bottom: 8px; padding-bottom: 8px;
margin: 0px; margin: 0;
} }
} }
} }

View file

@ -5,3 +5,20 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
// Fix the checkbox layout (add gap between checkbox and label).
.components-checkbox-control > .components-base-control__field > .components-flex {
flex-direction: row;
gap: 12px;
}
// Fix layout for radio groups inside a horizontal flex-stack.
.components-flex.components-h-stack > .components-radio-control {
width: 100%;
.components-radio-control__group-wrapper {
justify-content: flex-start;
flex-direction: row;
gap: 12px;
}
}

View file

@ -1,7 +1,7 @@
.ppcp-r-paylater-configurator { .ppcp-r-paylater-configurator {
display: flex; display: flex;
border: 1px solid var(--color-separators); border: 1px solid var(--color-separators);
border-radius: 8px; border-radius: var(--container-border-radius);
overflow: hidden; overflow: hidden;
font-family: "PayPalPro", sans-serif; font-family: "PayPalPro", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View file

@ -7,3 +7,25 @@
--block-header-gap: 0; --block-header-gap: 0;
--block-separator-size: 0; --block-separator-size: 0;
} }
.ppcp--webhooks {
.ppcp--webhook-list li {
list-style: none;
&::before {
content: '✔︎';
opacity: 0.35;
font-size: 0.75em;
line-height: 1.35;
display: inline-block;
margin-right: 8px;
}
}
}
.ppcp--webhook-resubscribe,
.ppcp--webhook-simulation {
.ppcp--action .components-button {
min-width: 160px;
display: block;
}
}

View file

@ -8,7 +8,7 @@
display: flex; display: flex;
border: 1px solid var(--color-separators); border: 1px solid var(--color-separators);
border-radius: 8px; border-radius: var(--container-border-radius);
.ppcp-r-settings-block { .ppcp-r-settings-block {
&.header-section { &.header-section {

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo } from '@wordpress/element'; import { useEffect, useMemo, useState } from '@wordpress/element';
import classNames from 'classnames'; import classNames from 'classnames';
import { OnboardingHooks, CommonHooks } from '../data'; import { OnboardingHooks, CommonHooks } from '../data';
@ -6,6 +6,7 @@ import SpinnerOverlay from './ReusableComponents/SpinnerOverlay';
import SendOnlyMessage from './Screens/SendOnlyMessage'; import SendOnlyMessage from './Screens/SendOnlyMessage';
import OnboardingScreen from './Screens/Onboarding'; import OnboardingScreen from './Screens/Onboarding';
import SettingsScreen from './Screens/Settings'; import SettingsScreen from './Screens/Settings';
import { getQuery } from '../utils/navigation';
const SettingsApp = () => { const SettingsApp = () => {
const { isReady: onboardingIsReady, completed: onboardingCompleted } = const { isReady: onboardingIsReady, completed: onboardingCompleted } =
@ -31,6 +32,10 @@ const SettingsApp = () => {
loading: ! onboardingIsReady, loading: ! onboardingIsReady,
} ); } );
const [ activePanel, setActivePanel ] = useState(
getQuery().panel || 'overview'
);
const Content = useMemo( () => { const Content = useMemo( () => {
if ( ! onboardingIsReady || ! merchantIsReady ) { if ( ! onboardingIsReady || ! merchantIsReady ) {
return <SpinnerOverlay />; return <SpinnerOverlay />;
@ -44,12 +49,18 @@ const SettingsApp = () => {
return <OnboardingScreen />; return <OnboardingScreen />;
} }
return <SettingsScreen />; return (
<SettingsScreen
activePanel={ activePanel }
setActivePanel={ setActivePanel }
/>
);
}, [ }, [
isSendOnlyCountry, isSendOnlyCountry,
merchantIsReady, merchantIsReady,
onboardingCompleted, onboardingCompleted,
onboardingIsReady, onboardingIsReady,
activePanel,
] ); ] );
return <div className={ wrapperClass }>{ Content }</div>; return <div className={ wrapperClass }>{ Content }</div>;

View file

@ -1,4 +1,5 @@
import data from '../../utils/data'; import { LearnMore } from './Elements';
import { PPIcon } from './Icons';
const ImageBadge = ( { images } ) => { const ImageBadge = ( { images } ) => {
if ( ! images || ! images.length ) { if ( ! images || ! images.length ) {
@ -8,13 +9,19 @@ const ImageBadge = ( { images } ) => {
return ( return (
<BadgeContent> <BadgeContent>
<span className="ppcp-r-badge-box__title-image-badge"> <span className="ppcp-r-badge-box__title-image-badge">
{ images.map( ( badge ) => data().getImage( badge ) ) } { images.map( ( badge, index ) => (
<PPIcon
key={ `badge-${ index }` }
imageName={ badge }
className="ppcp-r-badge-box__image"
/>
) ) }
</span> </span>
</BadgeContent> </BadgeContent>
); );
}; };
// If `children` is not empty, it's output and wrapped in spaces. // If `children` is not empty, the `children` prop is output and wrapped in spaces.
const BadgeContent = ( { children } ) => { const BadgeContent = ( { children } ) => {
if ( ! children ) { if ( ! children ) {
return null; return null;
@ -22,22 +29,29 @@ const BadgeContent = ( { children } ) => {
return <> { children } </>; return <> { children } </>;
}; };
const BadgeDescription = ( { description, learnMoreLink } ) => {
if ( ! description && ! learnMoreLink ) {
return null;
}
return (
<div className="ppcp-r-badge-box__description">
<p className="ppcp-r-badge-box__description">
{ description }
<LearnMore url={ learnMoreLink } />
</p>
</div>
);
};
const BadgeBox = ( { const BadgeBox = ( {
title, title,
textBadge, textBadge,
imageBadge = [], imageBadge = [],
titleType = BADGE_BOX_TITLE_BIG,
description = '', description = '',
learnMoreLink = '',
} ) => { } ) => {
let titleSize = BADGE_BOX_TITLE_SMALL; const titleTextClassName = 'ppcp-r-badge-box__title-text';
if ( BADGE_BOX_TITLE_BIG === titleType ) {
titleSize = BADGE_BOX_TITLE_BIG;
}
const titleTextClassName =
'ppcp-r-badge-box__title-text ' +
`ppcp-r-badge-box__title-text--${ titleSize }`;
const titleBaseClassName = 'ppcp-r-badge-box__title'; const titleBaseClassName = 'ppcp-r-badge-box__title';
const titleClassName = imageBadge.length const titleClassName = imageBadge.length
? `${ titleBaseClassName } ppcp-r-badge-box__title--has-image-badge` ? `${ titleBaseClassName } ppcp-r-badge-box__title--has-image-badge`
@ -51,20 +65,13 @@ const BadgeBox = ( {
<ImageBadge images={ imageBadge } /> <ImageBadge images={ imageBadge } />
<BadgeContent>{ textBadge }</BadgeContent> <BadgeContent>{ textBadge }</BadgeContent>
</span> </span>
<div className="ppcp-r-badge-box__description">
{ description && ( <BadgeDescription
<p description={ description }
className="ppcp-r-badge-box__description" learnMoreLink={ learnMoreLink }
dangerouslySetInnerHTML={ { />
__html: description,
} }
></p>
) }
</div>
</div> </div>
); );
}; };
export const BADGE_BOX_TITLE_BIG = 'big';
export const BADGE_BOX_TITLE_SMALL = 'small';
export default BadgeBox; export default BadgeBox;

View file

@ -9,7 +9,12 @@ const ControlButton = ( {
buttonLabel, buttonLabel,
} ) => ( } ) => (
<Action> <Action>
<Button isBusy={ isBusy } variant={ type } onClick={ onClick }> <Button
className="small-button"
isBusy={ isBusy }
variant={ type }
onClick={ onClick }
>
{ buttonLabel } { buttonLabel }
</Button> </Button>
</Action> </Action>

View file

@ -10,6 +10,7 @@ const ControlTextInput = ( {
} ) => ( } ) => (
<Action> <Action>
<TextControl <TextControl
__nextHasNoMarginBottom
className="ppcp-r-vertical-text-control" className="ppcp-r-vertical-text-control"
placeholder={ placeholder } placeholder={ placeholder }
value={ value } value={ value }

View file

@ -5,7 +5,7 @@ const ControlToggleButton = ( { label, description, value, onChange } ) => (
<Action> <Action>
<ToggleControl <ToggleControl
className="ppcp--control-toggle" className="ppcp--control-toggle"
__nextHasNoMarginBottom={ true } __nextHasNoMarginBottom
checked={ value } checked={ value }
onChange={ onChange } onChange={ onChange }
label={ label } label={ label }

View file

@ -0,0 +1,19 @@
import ControlTextInput from './ControlTextInput';
const SoftDescriptorInput = ( { value, onChange, placeholder } ) => {
const handleChange = ( newValue ) => {
if ( newValue.length <= 22 ) {
onChange( newValue );
}
};
return (
<ControlTextInput
value={ value }
onChange={ handleChange }
placeholder={ placeholder }
/>
);
};
export default SoftDescriptorInput;

View file

@ -0,0 +1,16 @@
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
const LearnMore = ( { url } ) => {
if ( ! url || '#' === url ) {
return null;
}
return (
<Button href={ url } variant="tertiary" target="_blank">
{ __( 'Learn more', 'woocommerce-paypal-payments' ) }
</Button>
);
};
export default LearnMore;

View file

@ -7,7 +7,8 @@ export { default as Content } from './Content';
export { default as ContentWrapper } from './ContentWrapper'; export { default as ContentWrapper } from './ContentWrapper';
export { default as Description } from './Description'; export { default as Description } from './Description';
export { default as Header } from './Header'; export { default as Header } from './Header';
export { default as LearnMore } from './LearnMore';
export { default as Separator } from './Separator';
export { default as Title } from './Title'; export { default as Title } from './Title';
export { default as TitleExtra } from './TitleExtra'; export { default as TitleExtra } from './TitleExtra';
export { default as TitleWrapper } from './TitleWrapper'; export { default as TitleWrapper } from './TitleWrapper';
export { default as Separator } from './Separator';

View file

@ -24,6 +24,7 @@ const Checkbox = ( {
return ( return (
<CheckboxControl <CheckboxControl
__nextHasNoMarginBottom={ true }
label={ label } label={ label }
value={ value } value={ value }
checked={ checked } checked={ checked }

View file

@ -1,15 +1,39 @@
import { PayPalCheckbox } from './index'; import { PayPalCheckbox } from './index';
import { useCallback } from '@wordpress/element';
const CheckboxGroup = ( { options, value, onChange } ) => { const CheckboxGroup = ( { name, options, value, onChange } ) => {
const handleChange = ( key, checked ) => { const handleChange = useCallback(
const getNewValue = () => { ( key, checked ) => {
if ( checked ) { const getNewValue = () => {
return [ ...value, key ]; if ( 'boolean' === typeof value ) {
} return checked;
return value.filter( ( val ) => val !== key ); }
};
onChange( getNewValue() ); if ( checked ) {
return [ ...value, key ];
}
return value.filter( ( val ) => val !== key );
};
onChange( getNewValue() );
},
[ onChange, value ]
);
const isItemChecked = ( checked, itemValue ) => {
if ( typeof checked === 'boolean' ) {
return checked;
}
if ( Array.isArray( value ) ) {
return value.includes( itemValue );
}
if ( typeof value === 'boolean' ) {
return value;
}
return value === itemValue;
}; };
return ( return (
@ -21,16 +45,14 @@ const CheckboxGroup = ( { options, value, onChange } ) => {
checked, checked,
disabled, disabled,
description, description,
tooltip,
} ) => ( } ) => (
<PayPalCheckbox <PayPalCheckbox
key={ itemValue } key={ name + itemValue }
value={ itemValue } value={ itemValue }
label={ label } label={ label }
checked={ checked } checked={ isItemChecked( checked, itemValue ) }
disabled={ disabled } disabled={ disabled }
description={ description } description={ description }
tooltip={ tooltip }
changeCallback={ handleChange } changeCallback={ handleChange }
/> />
) )

View file

@ -49,10 +49,12 @@ const OptionItem = ( {
} ) => { } ) => {
const boxClassName = classNames( 'ppcp-r-select-box', { const boxClassName = classNames( 'ppcp-r-select-box', {
'ppcp--selected': isSelected, 'ppcp--selected': isSelected,
'ppcp--multiselect': isMulti,
} ); } );
return ( return (
<div className={ boxClassName }> // eslint-disable-next-line jsx-a11y/label-has-associated-control -- label has a nested input control.
<label className={ boxClassName }>
<InputField <InputField
value={ itemValue } value={ itemValue }
isRadio={ ! isMulti } isRadio={ ! isMulti }
@ -60,22 +62,18 @@ const OptionItem = ( {
isSelected={ isSelected } isSelected={ isSelected }
/> />
<div className="ppcp-r-select-box__content"> <div className="ppcp--box-content">
<div className="ppcp-r-select-box__content-inner"> <div className="ppcp--box-content-inner">
<span className="ppcp-r-select-box__title"> <span className="ppcp--box-title">{ itemTitle }</span>
{ itemTitle } <div className="ppcp--box-description">
</span>
<p className="ppcp-r-select-box__description">
{ itemDescription } { itemDescription }
</p> </div>
{ children && ( { children && (
<div className="ppcp-r-select-box__additional-content"> <div className="ppcp--box-details">{ children }</div>
{ children }
</div>
) } ) }
</div> </div>
</div> </div>
</div> </label>
); );
}; };

View file

@ -1,26 +0,0 @@
/**
* Temporary component, until the experimental HStack block editor component is stable.
*
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs
* @file
*/
import classNames from 'classnames';
const HStack = ( { className, spacing = 3, children } ) => {
const wrapperClass = classNames(
'components-flex components-h-stack',
className
);
const styles = {
gap: `calc(${ 4 * spacing }px)`,
};
return (
<div className={ wrapperClass } style={ styles }>
{ children }
</div>
);
};
export default HStack;

View file

@ -0,0 +1,15 @@
import React from 'react';
const GenericIcon = ( { imageName, className = '', alt = '' } ) => {
const pathToImages = global.ppcpSettings.assets.imagesUrl;
return (
<img
className={ className }
alt={ alt }
src={ `${ pathToImages }${ imageName }` }
/>
);
};
export default GenericIcon;

View file

@ -1,6 +1,6 @@
import { SVG, Path } from '@wordpress/primitives'; import { SVG, Path } from '@wordpress/primitives';
const logoPayPal = ( const LogoPayPal = (
<SVG fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 38"> <SVG fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 38">
<Path <Path
d="M109.583.683v27.359h-6.225V.683h6.225Zm-8.516 9.234v18.175h-5.534v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.259 7.259 0 0 1-2.892.583c-1.3 0-2.508-.242-3.616-.725a9.216 9.216 0 0 1-2.892-2.067 10.021 10.021 0 0 1-1.958-3.05c-.459-1.183-.684-2.458-.684-3.816 0-1.359.225-2.617.684-3.775.483-1.184 1.133-2.217 1.958-3.092a8.708 8.708 0 0 1 2.892-2.033c1.108-.509 2.316-.767 3.616-.767 1.034 0 2 .192 2.892.583a7.312 7.312 0 0 1 2.383 1.567V9.933h5.534v-.016Zm-9.809 13.225c1.134 0 2.059-.384 2.784-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.784-1.167-1.125 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.367 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM72.225.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925a9.439 9.439 0 0 1 1.917 2.908c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3h-6.333V.683h9.333Zm-.908 12.467c.85 0 1.491-.083 1.958-.258a3.853 3.853 0 0 0 1.192-.725c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.329 3.329 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1ZM39.558 9.917h6.875l4.667 8.716h.075l4.158-8.716H61.7l-13.642 27.4h-6.333l6.225-12.534-8.392-14.866Zm-1.225 0v18.175H32.8v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.258 7.258 0 0 1-2.892.583c-1.3 0-2.508-.242-3.617-.725a9.218 9.218 0 0 1-2.891-2.067 10.18 10.18 0 0 1-1.959-3.05c-.458-1.183-.683-2.458-.683-3.816 0-1.359.225-2.617.683-3.775.484-1.184 1.134-2.217 1.959-3.092a8.626 8.626 0 0 1 2.891-2.033c1.109-.509 2.317-.767 3.617-.767 1.033 0 2 .192 2.892.583A7.312 7.312 0 0 1 32.8 11.5V9.933h5.533v-.016Zm-9.808 13.225c1.133 0 2.058-.384 2.792-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.792-1.167-1.133 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.366 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM9.75.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925A9.439 9.439 0 0 1 18.8 6.208c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3H.417V.683H9.75Zm-.9 12.467c.85 0 1.492-.083 1.958-.258A3.855 3.855 0 0 0 12 12.167c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.33 3.33 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1Z" d="M109.583.683v27.359h-6.225V.683h6.225Zm-8.516 9.234v18.175h-5.534v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.259 7.259 0 0 1-2.892.583c-1.3 0-2.508-.242-3.616-.725a9.216 9.216 0 0 1-2.892-2.067 10.021 10.021 0 0 1-1.958-3.05c-.459-1.183-.684-2.458-.684-3.816 0-1.359.225-2.617.684-3.775.483-1.184 1.133-2.217 1.958-3.092a8.708 8.708 0 0 1 2.892-2.033c1.108-.509 2.316-.767 3.616-.767 1.034 0 2 .192 2.892.583a7.312 7.312 0 0 1 2.383 1.567V9.933h5.534v-.016Zm-9.809 13.225c1.134 0 2.059-.384 2.784-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.784-1.167-1.125 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.367 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM72.225.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925a9.439 9.439 0 0 1 1.917 2.908c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3h-6.333V.683h9.333Zm-.908 12.467c.85 0 1.491-.083 1.958-.258a3.853 3.853 0 0 0 1.192-.725c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.329 3.329 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1ZM39.558 9.917h6.875l4.667 8.716h.075l4.158-8.716H61.7l-13.642 27.4h-6.333l6.225-12.534-8.392-14.866Zm-1.225 0v18.175H32.8v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.258 7.258 0 0 1-2.892.583c-1.3 0-2.508-.242-3.617-.725a9.218 9.218 0 0 1-2.891-2.067 10.18 10.18 0 0 1-1.959-3.05c-.458-1.183-.683-2.458-.683-3.816 0-1.359.225-2.617.683-3.775.484-1.184 1.134-2.217 1.959-3.092a8.626 8.626 0 0 1 2.891-2.033c1.109-.509 2.317-.767 3.617-.767 1.033 0 2 .192 2.892.583A7.312 7.312 0 0 1 32.8 11.5V9.933h5.533v-.016Zm-9.808 13.225c1.133 0 2.058-.384 2.792-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.792-1.167-1.133 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.366 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM9.75.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925A9.439 9.439 0 0 1 18.8 6.208c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3H.417V.683H9.75Zm-.9 12.467c.85 0 1.492-.083 1.958-.258A3.855 3.855 0 0 0 12 12.167c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.33 3.33 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1Z"
@ -9,4 +9,4 @@ const logoPayPal = (
</SVG> </SVG>
); );
export default logoPayPal; export default LogoPayPal;

View file

@ -1,9 +1,9 @@
import { SVG, Path } from '@wordpress/primitives'; import { SVG, Path } from '@wordpress/primitives';
const openSignup = ( const OpenSignup = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24"> <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24">
<Path d="M12.4999 12.75V18.75C12.4999 18.9489 12.4209 19.1397 12.2803 19.2803C12.1396 19.421 11.9488 19.5 11.7499 19.5C11.551 19.5 11.3603 19.421 11.2196 19.2803C11.0789 19.1397 10.9999 18.9489 10.9999 18.75V14.5613L4.78055 20.7806C4.71087 20.8503 4.62815 20.9056 4.5371 20.9433C4.44606 20.981 4.34847 21.0004 4.24993 21.0004C4.15138 21.0004 4.0538 20.981 3.96276 20.9433C3.87171 20.9056 3.78899 20.8503 3.7193 20.7806C3.64962 20.7109 3.59435 20.6282 3.55663 20.5372C3.51892 20.4461 3.49951 20.3485 3.49951 20.25C3.49951 20.1515 3.51892 20.0539 3.55663 19.9628C3.59435 19.8718 3.64962 19.7891 3.7193 19.7194L9.93868 13.5H5.74993C5.55102 13.5 5.36025 13.421 5.2196 13.2803C5.07895 13.1397 4.99993 12.9489 4.99993 12.75C4.99993 12.5511 5.07895 12.3603 5.2196 12.2197C5.36025 12.079 5.55102 12 5.74993 12H11.7499C11.9488 12 12.1396 12.079 12.2803 12.2197C12.4209 12.3603 12.4999 12.5511 12.4999 12.75ZM19.9999 3H7.99993C7.6021 3 7.22057 3.15804 6.93927 3.43934C6.65796 3.72064 6.49993 4.10218 6.49993 4.5V9C6.49993 9.19891 6.57895 9.38968 6.7196 9.53033C6.86025 9.67098 7.05102 9.75 7.24993 9.75C7.44884 9.75 7.63961 9.67098 7.78026 9.53033C7.92091 9.38968 7.99993 9.19891 7.99993 9V4.5H19.9999V16.5H15.4999C15.301 16.5 15.1103 16.579 14.9696 16.7197C14.8289 16.8603 14.7499 17.0511 14.7499 17.25C14.7499 17.4489 14.8289 17.6397 14.9696 17.7803C15.1103 17.921 15.301 18 15.4999 18H19.9999C20.3978 18 20.7793 17.842 21.0606 17.5607C21.3419 17.2794 21.4999 16.8978 21.4999 16.5V4.5C21.4999 4.10218 21.3419 3.72064 21.0606 3.43934C20.7793 3.15804 20.3978 3 19.9999 3Z" /> <Path d="M12.4999 12.75V18.75C12.4999 18.9489 12.4209 19.1397 12.2803 19.2803C12.1396 19.421 11.9488 19.5 11.7499 19.5C11.551 19.5 11.3603 19.421 11.2196 19.2803C11.0789 19.1397 10.9999 18.9489 10.9999 18.75V14.5613L4.78055 20.7806C4.71087 20.8503 4.62815 20.9056 4.5371 20.9433C4.44606 20.981 4.34847 21.0004 4.24993 21.0004C4.15138 21.0004 4.0538 20.981 3.96276 20.9433C3.87171 20.9056 3.78899 20.8503 3.7193 20.7806C3.64962 20.7109 3.59435 20.6282 3.55663 20.5372C3.51892 20.4461 3.49951 20.3485 3.49951 20.25C3.49951 20.1515 3.51892 20.0539 3.55663 19.9628C3.59435 19.8718 3.64962 19.7891 3.7193 19.7194L9.93868 13.5H5.74993C5.55102 13.5 5.36025 13.421 5.2196 13.2803C5.07895 13.1397 4.99993 12.9489 4.99993 12.75C4.99993 12.5511 5.07895 12.3603 5.2196 12.2197C5.36025 12.079 5.55102 12 5.74993 12H11.7499C11.9488 12 12.1396 12.079 12.2803 12.2197C12.4209 12.3603 12.4999 12.5511 12.4999 12.75ZM19.9999 3H7.99993C7.6021 3 7.22057 3.15804 6.93927 3.43934C6.65796 3.72064 6.49993 4.10218 6.49993 4.5V9C6.49993 9.19891 6.57895 9.38968 6.7196 9.53033C6.86025 9.67098 7.05102 9.75 7.24993 9.75C7.44884 9.75 7.63961 9.67098 7.78026 9.53033C7.92091 9.38968 7.99993 9.19891 7.99993 9V4.5H19.9999V16.5H15.4999C15.301 16.5 15.1103 16.579 14.9696 16.7197C14.8289 16.8603 14.7499 17.0511 14.7499 17.25C14.7499 17.4489 14.8289 17.6397 14.9696 17.7803C15.1103 17.921 15.301 18 15.4999 18H19.9999C20.3978 18 20.7793 17.842 21.0606 17.5607C21.3419 17.2794 21.4999 16.8978 21.4999 16.5V4.5C21.4999 4.10218 21.3419 3.72064 21.0606 3.43934C20.7793 3.15804 20.3978 3 19.9999 3Z" />
</SVG> </SVG>
); );
export default openSignup; export default OpenSignup;

View file

@ -1,5 +1,6 @@
export { default as openSignup } from './open-signup'; export { default as PPIcon } from './GenericIcon';
export { default as logoPayPal } from './logo-paypal'; export { default as OpenSignup } from './OpenSignup';
export { default as LogoPayPal } from './LogoPayPal';
export const NOTIFICATION_SUCCESS = '✔️'; export const NOTIFICATION_SUCCESS = '✔️';
export const NOTIFICATION_ERROR = '❌'; export const NOTIFICATION_ERROR = '❌';

View file

@ -1,15 +1,20 @@
import { Icon } from '@wordpress/components';
import data from '../../utils/data'; import data from '../../utils/data';
const PaymentMethodIcon = ( props ) => { const PaymentMethodIcon = ( { icons, type } ) => {
if ( const validIcon = Array.isArray( icons ) && icons.includes( type );
( Array.isArray( props.icons ) &&
props.icons.includes( props.type ) ) || if ( validIcon || icons === 'all' ) {
props.icons === 'all' return (
) { <Icon
return data().getImage( 'icon-button-' + props.type + '.svg' ); icon={ data().getImage( 'icon-button-' + type + '.svg' ) }
className="ppcp--method-icon"
/>
);
} }
return <></>; return null;
}; };
export default PaymentMethodIcon; export default PaymentMethodIcon;

View file

@ -2,6 +2,7 @@ import classNames from 'classnames';
import { Description, Header, Title, TitleExtra, Content } from './Elements'; import { Description, Header, Title, TitleExtra, Content } from './Elements';
const SettingsBlock = ( { const SettingsBlock = ( {
id,
className, className,
children, children,
title, title,
@ -15,33 +16,37 @@ const SettingsBlock = ( {
'ppcp--horizontal': horizontalLayout, 'ppcp--horizontal': horizontalLayout,
} ); } );
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => { const props = {
if ( ! blockTitle && ! blockDescription ) { className: blockClassName,
return null; ...( id && { id } ),
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
}; };
return ( return (
<div className={ blockClassName }> <div { ...props }>
<BlockTitle <BlockTitle
blockTitle={ title } blockTitle={ title }
blockSuffix={ titleSuffix } blockSuffix={ titleSuffix }
blockDescription={ description } blockDescription={ description }
/> />
<Content asCard={ false }>{ children }</Content> <Content asCard={ false }>{ children }</Content>
</div> </div>
); );
}; };
export default SettingsBlock; export default SettingsBlock;
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => {
if ( ! blockTitle && ! blockDescription ) {
return null;
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
};

View file

@ -12,7 +12,7 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
} }
return ( return (
<span className="ppcp-r-feature-item__notes"> <span className="ppcp--item-notes">
{ notes.map( ( note, index ) => ( { notes.map( ( note, index ) => (
<span key={ index }>{ note }</span> <span key={ index }>{ note }</span>
) ) } ) ) }
@ -20,6 +20,32 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
); );
}; };
const FeatureButton = ( {
className,
variant,
text,
isBusy,
url,
urls,
onClick,
} ) => {
const buttonProps = {
className,
isBusy,
variant,
};
if ( url || urls ) {
buttonProps.href = urls ? urls.live : url;
buttonProps.target = '_blank';
}
if ( ! buttonProps.href ) {
buttonProps.onClick = onClick;
}
return <Button { ...buttonProps }>{ text }</Button>;
};
const renderDescription = () => { const renderDescription = () => {
return ( return (
<span <span
@ -29,32 +55,6 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
); );
}; };
const renderButton = ( button ) => {
const buttonElement = (
<Button
className={ button.class ? button.class : '' }
key={ button.text }
isBusy={ props.actionProps?.isBusy }
variant={ button.type }
onClick={ button.onClick }
>
{ button.text }
</Button>
);
// If there's a URL (either direct or in urls object), wrap in anchor tag
if ( button.url || button.urls ) {
const href = button.urls ? button.urls.live : button.url;
return (
<a href={ href } key={ button.text }>
{ buttonElement }
</a>
);
}
return buttonElement;
};
return ( return (
<SettingsBlock { ...props } className="ppcp-r-settings-block__feature"> <SettingsBlock { ...props } className="ppcp-r-settings-block__feature">
<Header> <Header>
@ -70,8 +70,28 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
</Description> </Description>
</Header> </Header>
<Action> <Action>
<div className="ppcp-r-feature-item__buttons"> <div className="ppcp--action-buttons">
{ props.actionProps?.buttons.map( renderButton ) } { props.actionProps?.buttons.map(
( {
class: className,
type,
text,
url,
urls,
onClick,
} ) => (
<FeatureButton
key={ text }
className={ className }
variant={ type }
text={ text }
isBusy={ props.actionProps.isBusy }
url={ url }
urls={ urls }
onClick={ onClick }
/>
)
) }
</div> </div>
</Action> </Action>
</SettingsBlock> </SettingsBlock>

View file

@ -1,8 +1,10 @@
import { ToggleControl } from '@wordpress/components'; 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 SettingsBlock from '../SettingsBlock';
import PaymentMethodIcon from '../PaymentMethodIcon'; import PaymentMethodIcon from '../PaymentMethodIcon';
import data from '../../../utils/data';
const PaymentMethodItemBlock = ( { const PaymentMethodItemBlock = ( {
paymentMethod, paymentMethod,
@ -10,34 +12,56 @@ const PaymentMethodItemBlock = ( {
onSelect, onSelect,
isSelected, isSelected,
} ) => { } ) => {
const { activeHighlight, setActiveHighlight } = useActiveHighlight();
const isHighlighted = activeHighlight === paymentMethod.id;
// Reset the active highlight after 2 seconds
useEffect( () => {
if ( isHighlighted ) {
const timer = setTimeout( () => {
setActiveHighlight( null );
}, 2000 );
return () => clearTimeout( timer );
}
}, [ isHighlighted, setActiveHighlight ] );
return ( return (
<SettingsBlock className="ppcp-r-settings-block__payment-methods__item"> <SettingsBlock
<div className="ppcp-r-settings-block__payment-methods__item__inner"> id={ paymentMethod.id }
<div className="ppcp-r-settings-block__payment-methods__item__title-wrapper"> className={ `ppcp--method-item ${
<PaymentMethodIcon isHighlighted ? 'ppcp-highlight' : ''
icons={ [ paymentMethod.icon ] } }` }
type={ paymentMethod.icon } separatorAndGap={ false }
/> >
<span className="ppcp-r-settings-block__payment-methods__item__title"> <div className="ppcp--method-inner">
<div className="ppcp--method-title-wrapper">
{ paymentMethod?.icon && (
<PaymentMethodIcon
icons={ [ paymentMethod.icon ] }
type={ paymentMethod.icon }
/>
) }
<span className="ppcp--method-title">
{ paymentMethod.itemTitle } { paymentMethod.itemTitle }
</span> </span>
</div> </div>
<p className="ppcp-r-settings-block__payment-methods__item__description"> <p className="ppcp--method-description">
{ paymentMethod.itemDescription } { paymentMethod.itemDescription }
</p> </p>
<div className="ppcp-r-settings-block__payment-methods__item__footer"> <div className="ppcp--method-footer">
<ToggleControl <ToggleControl
__nextHasNoMarginBottom={ true } __nextHasNoMarginBottom
checked={ isSelected } checked={ isSelected }
onChange={ onSelect } onChange={ onSelect }
/> />
{ paymentMethod?.fields && onTriggerModal && ( { paymentMethod?.fields && onTriggerModal && (
<div <Button
className="ppcp-r-settings-block__payment-methods__item__settings" className="ppcp--method-settings"
onClick={ onTriggerModal } onClick={ onTriggerModal }
> >
{ data().getImage( 'icon-settings.svg' ) } <Icon icon={ cog } />
</div> </Button>
) } ) }
</div> </div>
</div> </div>

View file

@ -1,42 +1,38 @@
import SettingsBlock from '../SettingsBlock'; import SettingsBlock from '../SettingsBlock';
import PaymentMethodItemBlock from './PaymentMethodItemBlock'; import PaymentMethodItemBlock from './PaymentMethodItemBlock';
import { usePaymentMethods } from '../../../data/payment/hooks'; import { PaymentHooks } from '../../../data';
const PaymentMethodsBlock = ( { // TODO: This is not a reusable component, as it's connected to the Redux store.
paymentMethods, const PaymentMethodsBlock = ( { paymentMethods = [], onTriggerModal } ) => {
className = '', const { changePaymentSettings } = PaymentHooks.useStore();
onTriggerModal,
} ) => {
const { setPersistent } = usePaymentMethods();
if ( ! paymentMethods?.length ) { const handleSelect = ( methodId, isSelected ) =>
changePaymentSettings( methodId, {
enabled: isSelected,
} );
if ( ! paymentMethods.length ) {
return null; return null;
} }
const handleSelect = ( paymentMethod, isSelected ) => {
setPersistent( paymentMethod.id, {
...paymentMethod,
enabled: isSelected,
} );
};
return ( return (
<SettingsBlock <SettingsBlock className="ppcp--grid ppcp-r-settings-block__payment-methods">
className={ `ppcp-r-settings-block__payment-methods ${ className }` } { paymentMethods
> // Remove empty/invalid payment method entries.
{ paymentMethods.map( ( paymentMethod ) => ( .filter( ( m ) => m.id )
<PaymentMethodItemBlock .map( ( paymentMethod ) => (
key={ paymentMethod.id } <PaymentMethodItemBlock
paymentMethod={ paymentMethod } key={ paymentMethod.id }
isSelected={ paymentMethod.enabled } paymentMethod={ paymentMethod }
onSelect={ ( checked ) => isSelected={ paymentMethod.enabled }
handleSelect( paymentMethod, checked ) onSelect={ ( checked ) =>
} handleSelect( paymentMethod.id, checked )
onTriggerModal={ () => }
onTriggerModal?.( paymentMethod.id ) onTriggerModal={ () =>
} onTriggerModal?.( paymentMethod.id )
/> }
) ) } />
) ) }
</SettingsBlock> </SettingsBlock>
); );
}; };

View file

@ -1,36 +1,131 @@
const TodoSettingsBlock = ( { todosData, className = '' } ) => { import { selectTab, TAB_IDS } from '../../../utils/tabSelector';
import { useEffect, useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { STORE_NAME as TODOS_STORE_NAME } from '../../../data/todos';
const TodoSettingsBlock = ( {
todosData,
className = '',
setActiveModal,
setActiveHighlight,
onDismissTodo,
} ) => {
const [ dismissingIds, setDismissingIds ] = useState( new Set() );
const { completedTodos, dismissedTodos } = useSelect(
( select ) => ( {
completedTodos:
select( TODOS_STORE_NAME ).getCompletedTodos() || [],
dismissedTodos:
select( TODOS_STORE_NAME ).getDismissedTodos() || [],
} ),
[]
);
const { completeOnClick } = useDispatch( TODOS_STORE_NAME );
useEffect( () => {
if ( dismissedTodos.length === 0 ) {
setDismissingIds( new Set() );
}
}, [ dismissedTodos ] );
if ( todosData.length === 0 ) { if ( todosData.length === 0 ) {
return null; return null;
} }
const handleDismiss = ( todoId, e ) => {
e.preventDefault();
e.stopPropagation();
setDismissingIds( ( prev ) => new Set( [ ...prev, todoId ] ) );
setTimeout( () => {
onDismissTodo( todoId );
}, 300 );
};
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 );
}
}
if ( todo.action.modal ) {
setActiveModal( todo.action.modal );
}
if ( todo.action.highlight ) {
setActiveHighlight( todo.action.highlight );
}
};
// Filter out dismissed todos for display
const visibleTodos = todosData.filter(
( todo ) => ! dismissedTodos.includes( todo.id )
);
return ( return (
<div <div
className={ `ppcp-r-settings-block__todo ppcp-r-todo-items ${ className }` } className={ `ppcp-r-settings-block__todo ppcp-r-todo-items ${ className }` }
> >
{ todosData { visibleTodos.map( ( todo ) => (
.slice( 0, 5 ) <TodoItem
.filter( ( todo ) => { key={ todo.id }
return ! todo.isCompleted(); id={ todo.id }
} ) title={ todo.title }
.map( ( todo ) => ( description={ todo.description }
<TodoItem isCompleted={ completedTodos.includes( todo.id ) }
key={ todo.id } isDismissing={ dismissingIds.has( todo.id ) }
title={ todo.title } onDismiss={ ( e ) => handleDismiss( todo.id, e ) }
onClick={ todo.onClick } onClick={ () => handleClick( todo ) }
/> />
) ) } ) ) }
</div> </div>
); );
}; };
const TodoItem = ( props ) => { const TodoItem = ( {
title,
description,
isCompleted,
isDismissing,
onClick,
onDismiss,
} ) => {
return ( return (
<div className="ppcp-r-todo-item" onClick={ props.onClick }> <div
className={ `ppcp-r-todo-item ${
isCompleted ? 'is-completed' : ''
} ${ isDismissing ? 'is-dismissing' : '' }` }
onClick={ onClick }
>
<div className="ppcp-r-todo-item__inner"> <div className="ppcp-r-todo-item__inner">
<div className="ppcp-r-todo-item__icon"></div> <div className="ppcp-r-todo-item__icon">
<div className="ppcp-r-todo-item__description"> { isCompleted && (
{ props.title } <span className="dashicons dashicons-yes"></span>
) }
</div> </div>
<div className="ppcp-r-todo-item__content">
<div className="ppcp-r-todo-item__description">
{ title }
</div>
{ description && (
<div className="ppcp-r-todo-item__secondary-description">
{ description }
</div>
) }
</div>
<button
className="ppcp-r-todo-item__dismiss"
onClick={ onDismiss }
aria-label="Dismiss todo item"
>
<span className="dashicons dashicons-no-alt"></span>
</button>
</div> </div>
</div> </div>
); );

View file

@ -1,55 +1,47 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { Content, ContentWrapper } from './Elements'; import { Content } from './Elements';
const SettingsCard = ( { const SettingsCard = ( {
id, id,
className: extraClassName, className,
title, title,
description, description,
children, children,
contentItems,
contentContainer = true, contentContainer = true,
} ) => { } ) => {
const className = classNames( 'ppcp-r-settings-card', extraClassName ); const cardClassNames = classNames( 'ppcp-r-settings-card', className );
const cardProps = {
const renderContent = () => { className: cardClassNames,
// If contentItems array is provided, wrap each item in Content component id,
if ( contentItems ) {
return (
<ContentWrapper>
{ contentItems.map( ( item ) => (
<Content key={ item.key } id={ item.key }>
{ item }
</Content>
) ) }
</ContentWrapper>
);
}
// Otherwise handle regular children with contentContainer prop
if ( contentContainer ) {
return <Content>{ children }</Content>;
}
return children;
}; };
return ( return (
<div id={ id } className={ className }> <div { ...cardProps }>
<div className="ppcp-r-settings-card__header"> <div className="ppcp-r-settings-card__header">
<div className="ppcp-r-settings-card__content-inner"> <div className="ppcp-r-settings-card__content-inner">
<span className="ppcp-r-settings-card__title"> <span className="ppcp-r-settings-card__title">
{ title } { title }
</span> </span>
<p className="ppcp-r-settings-card__description"> <div className="ppcp-r-settings-card__description">
{ description } { description }
</p> </div>
</div> </div>
</div> </div>
{ renderContent() }
<InnerContent showCards={ contentContainer }>
{ children }
</InnerContent>
</div> </div>
); );
}; };
export default SettingsCard; export default SettingsCard;
const InnerContent = ( { showCards, children } ) => {
if ( showCards ) {
return <Content>{ children }</Content>;
}
return children;
};

View file

@ -43,6 +43,7 @@ const SettingsToggleBlock = ( {
</div> </div>
<div className="ppcp-r-toggle-block__switch"> <div className="ppcp-r-toggle-block__switch">
<ToggleControl <ToggleControl
__nextHasNoMarginBottom
ref={ toggleRef } ref={ toggleRef }
checked={ isToggled } checked={ isToggled }
onChange={ ( newState ) => setToggled( newState ) } onChange={ ( newState ) => setToggled( newState ) }

View file

@ -9,9 +9,7 @@ const SpinnerOverlay = ( { message = null } ) => {
return ( return (
<div className="ppcp-r-spinner-overlay"> <div className="ppcp-r-spinner-overlay">
{ message && ( { message && (
<span className="ppcp-r-spinner-overlay__message"> <span className="ppcp--spinner-message">{ message }</span>
{ message }
</span>
) } ) }
<Spinner /> <Spinner />
</div> </div>

View file

@ -0,0 +1,42 @@
/**
* Temporary component, until the experimental VStack/HStack block editor component is stable.
*
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-vstack--docs
* @file
*/
import classNames from 'classnames';
const Stack = ( { type, className, spacing, children } ) => {
const wrapperClass = classNames(
'components-flex',
`components-${ type }-stack`,
className
);
const styles = {
gap: `calc(${ 4 * spacing }px)`,
};
return (
<div className={ wrapperClass } style={ styles }>
{ children }
</div>
);
};
export const HStack = ( { className, spacing = 3, children } ) => {
return (
<Stack type="h" className={ className } spacing={ spacing }>
{ children }
</Stack>
);
};
export const VStack = ( { className, spacing = 3, children } ) => {
return (
<Stack type="v" className={ className } spacing={ spacing }>
{ children }
</Stack>
);
};

View file

@ -1,26 +1,14 @@
import { useCallback, useEffect, useState } from '@wordpress/element'; import { useCallback, useEffect } from '@wordpress/element';
// TODO: Migrate to Tabs (TabPanel v2) once its API is publicly available, as it provides programmatic tab switching support: https://github.com/WordPress/gutenberg/issues/52997 // TODO: Migrate to Tabs (TabPanel v2) once its API is publicly available, as it provides programmatic tab switching support: https://github.com/WordPress/gutenberg/issues/52997
import { TabPanel } from '@wordpress/components'; import { TabPanel } from '@wordpress/components';
import { getQuery, updateQueryString } from '../../utils/navigation'; import { updateQueryString } from '../../utils/navigation';
const TabNavigation = ( { tabs } ) => {
const { panel } = getQuery();
const TabBar = ( { tabs, activePanel, setActivePanel } ) => {
const isValidTab = ( tabsList, checkTab ) => { const isValidTab = ( tabsList, checkTab ) => {
return tabsList.some( ( tab ) => tab.name === checkTab ); return tabsList.some( ( tab ) => tab.name === checkTab );
}; };
const getValidInitialPanel = () => {
if ( ! panel || ! isValidTab( tabs, panel ) ) {
return tabs[ 0 ].name;
}
return panel;
};
const [ activePanel, setActivePanel ] = useState( getValidInitialPanel );
const updateActivePanel = useCallback( const updateActivePanel = useCallback(
( tabName ) => { ( tabName ) => {
if ( isValidTab( tabs, tabName ) ) { if ( isValidTab( tabs, tabName ) ) {
@ -29,7 +17,7 @@ const TabNavigation = ( { tabs } ) => {
console.warn( `Invalid tab name: ${ tabName }` ); console.warn( `Invalid tab name: ${ tabName }` );
} }
}, },
[ tabs ] [ tabs, setActivePanel ]
); );
useEffect( () => { useEffect( () => {
@ -43,9 +31,9 @@ const TabNavigation = ( { tabs } ) => {
onSelect={ updateActivePanel } onSelect={ updateActivePanel }
tabs={ tabs } tabs={ tabs }
> >
{ ( { Component } ) => Component } { () => '' }
</TabPanel> </TabPanel>
); );
}; };
export default TabNavigation; export default TabBar;

View file

@ -15,6 +15,7 @@ const TopNavigation = ( {
onTitleClick = null, onTitleClick = null,
showProgressBar = false, showProgressBar = false,
progressBarPercent = 0, progressBarPercent = 0,
subNavigation = null,
} ) => { } ) => {
const { goToWooCommercePaymentsTab } = useNavigation(); const { goToWooCommercePaymentsTab } = useNavigation();
const { isScrolled } = useIsScrolled(); const { isScrolled } = useIsScrolled();
@ -40,35 +41,43 @@ const TopNavigation = ( {
}, [] ); }, [] );
return ( return (
<div className={ className }> <>
<div className="ppcp-r-navigation"> <nav className={ className }>
<BusyStateWrapper <div className="ppcp-r-navigation">
className="ppcp-r-navigation--left" <BusyStateWrapper
busySpinner={ false } className="ppcp-r-navigation--left"
enabled={ ! exitOnTitleClick } busySpinner={ false }
> enabled={ ! exitOnTitleClick }
<Button
variant="link"
onClick={ handleTitleClick }
className="is-title"
> >
<Icon icon={ chevronLeft } /> <Button
<span className={ titleClassName }>{ title }</span> variant="link"
</Button> onClick={ handleTitleClick }
</BusyStateWrapper> className="is-title"
>
<Icon icon={ chevronLeft } />
<span className={ titleClassName }>{ title }</span>
</Button>
</BusyStateWrapper>
<BusyStateWrapper <BusyStateWrapper
className="ppcp-r-navigation--right" className="ppcp-r-navigation--right"
busySpinner={ false } busySpinner={ false }
> >
{ children } { children }
</BusyStateWrapper> </BusyStateWrapper>
</div>
{ subNavigation && (
<section className="ppcp--top-sub-navigation">
{ subNavigation }
</section>
) }
{ showProgressBar && ( { showProgressBar && (
<ProgressBar percent={ progressBarPercent } /> <ProgressBar percent={ progressBarPercent } />
) } ) }
</div> </nav>
</div> </>
); );
}; };

View file

@ -1,272 +0,0 @@
import { __, sprintf } from '@wordpress/i18n';
import BadgeBox, {
BADGE_BOX_TITLE_BIG,
} from '../../../ReusableComponents/BadgeBox';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
import OptionalPaymentMethods from './OptionalPaymentMethods';
const AcdcFlow = ( { isFastlane, isPayLater, storeCountry } ) => {
if ( isFastlane && isPayLater && storeCountry === 'US' ) {
return (
<div className="ppcp-r-welcome-docs__wrapper">
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
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'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
<BadgeBox
title={ __(
'Pay with PayPal',
'woocommerce-paypal-payments'
) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Our brand recognition helps give customers the confidence to buy. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __(
'Pay Later',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-payment-method-paypal-small.svg',
] }
textBadge={ <PricingTitleBadge item="plater" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Venmo', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-venmo.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Automatically offer Venmo checkout to millions of active users. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Crypto', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-crypto.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Let customers checkout with Crypto while you get paid in cash. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
</div>
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Expanded Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'Accept debit/credit cards, PayPal, Apple Pay, Google Pay, and more. Note: Additional application required for more methods',
'woocommerce-paypal-payments'
) }
/>
<OptionalPaymentMethods
useAcdc={ true }
isFastlane={ isFastlane }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
</div>
</div>
);
}
if ( isPayLater && storeCountry === 'UK' ) {
return (
<div className="ppcp-r-welcome-docs__wrapper">
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
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'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
<BadgeBox
title={ __(
'Pay with PayPal',
'woocommerce-paypal-payments'
) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Our brand recognition helps give customers the confidence to buy. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __(
'Pay in 3',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-payment-method-paypal-small.svg',
] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Offer installment payment options and get paid upfront - at no extra cost to you. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
</div>
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Optional payment methods',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'woocommerce-paypal-payments'
) }
/>
<OptionalPaymentMethods
useAcdc={ true }
isFastlane={ isFastlane }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
</div>
</div>
);
}
return (
<div className="ppcp-r-welcome-docs__wrapper">
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
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'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
<BadgeBox
title={ __(
'Pay with PayPal',
'woocommerce-paypal-payments'
) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Our brand recognition helps give customers the confidence to buy. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Pay Later', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-paypal-small.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
</div>
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Optional payment methods',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'woocommerce-paypal-payments'
) }
/>
<OptionalPaymentMethods
useAcdc={ true }
isFastlane={ isFastlane }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
</div>
</div>
);
};
export default AcdcFlow;

View file

@ -1,233 +0,0 @@
import { __, sprintf } from '@wordpress/i18n';
import BadgeBox from '../../../ReusableComponents/BadgeBox';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
const AcdcOptionalPaymentMethods = ( {
isFastlane,
isPayLater,
storeCountry,
} ) => {
if ( isFastlane && isPayLater && storeCountry === 'US' ) {
return (
<div className="ppcp-r-optional-payment-methods__wrapper">
<BadgeBox
title={ __(
'Custom Card Fields',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="ccf" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __(
'Digital Wallets',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-apple-pay.svg',
'icon-button-google-pay.svg',
] }
textBadge={ <PricingTitleBadge item="dw" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __( '', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-fastlane-small.svg' ] }
textBadge={
<PricingTitleBadge item="fast country currency=storeCurrency=storeCountrylane" />
}
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Speed up guest checkout with Fatslane. Link a customer\'s email address to their payment details. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
</div>
);
}
if ( isPayLater && storeCountry === 'uk' ) {
return (
<div className="ppcp-r-optional-payment-methods__wrapper">
<BadgeBox
title={ __(
'Custom Card Fields',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="ccf" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __(
'Digital Wallets',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-apple-pay.svg',
'icon-button-google-pay.svg',
] }
textBadge={ <PricingTitleBadge item="dw" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-sepa.svg',
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
</div>
);
}
return (
<div className="ppcp-r-optional-payment-methods__wrapper">
<BadgeBox
title={ __(
'Custom Card Fields',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="ccf" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __( 'Digital Wallets', 'woocommerce-paypal-payments' ) }
imageBadge={ [
'icon-button-apple-pay.svg',
'icon-button-google-pay.svg',
] }
textBadge={ <PricingTitleBadge item="dw" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
<Separator className="ppcp-r-optional-payment-methods__separator" />
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-sepa.svg',
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ sprintf(
// translators: %s: Link to PayPal business fees guide
__(
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/paypal-business-fees'
) }
/>
</div>
);
};
export default AcdcOptionalPaymentMethods;

View file

@ -1,182 +0,0 @@
import { __, sprintf } from '@wordpress/i18n';
import BadgeBox, {
BADGE_BOX_TITLE_BIG,
} from '../../../ReusableComponents/BadgeBox';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
import OptionalPaymentMethods from './OptionalPaymentMethods';
const BcdcFlow = ( { isPayLater, storeCountry } ) => {
if ( isPayLater && storeCountry === 'US' ) {
return (
<div className="ppcp-r-welcome-docs__wrapper">
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
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'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
<BadgeBox
title={ __(
'Pay with PayPal',
'woocommerce-paypal-payments'
) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Our brand recognition helps give customers the confidence to buy. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __(
'Pay Later',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-payment-method-paypal-small.svg',
] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Venmo', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-venmo.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Automatically offer Venmo checkout to millions of active users. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Crypto', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-crypto.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Let customers checkout with Crypto while you get paid in cash. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
</div>
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Expanded Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'Accept debit/credit cards, PayPal, Apple Pay, Google Pay, and more. Note: Additional application required for more methods',
'woocommerce-paypal-payments'
) }
/>
<OptionalPaymentMethods
useAcdc={ false }
isFastlane={ false }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
</div>
</div>
);
}
return (
<div className="ppcp-r-welcome-docs__wrapper ppcp-r-welcome-docs__wrapper--one-col">
<BadgeBox
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
titleType={ BADGE_BOX_TITLE_BIG }
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'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
<BadgeBox
title={ __( 'Pay with PayPal', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Our brand recognition helps give customers the confidence to buy. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __( 'Pay Later', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-paypal-small.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<BadgeBox
title={ __(
'Optional payment methods',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'woocommerce-paypal-payments'
) }
/>
<OptionalPaymentMethods
useAcdc={ false }
isFastlane={ false }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
</div>
);
};
export default BcdcFlow;

View file

@ -1,64 +0,0 @@
import { __, sprintf } from '@wordpress/i18n';
import BadgeBox from '../../../ReusableComponents/BadgeBox';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
const BcdcOptionalPaymentMethods = ( { isPayLater, storeCountry } ) => {
if ( isPayLater && storeCountry === 'us' ) {
return (
<div className="ppcp-r-optional-payment-methods__wrapper">
<BadgeBox
title={ __(
'Credit and Debit Cards',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={
<PricingTitleBadge item="standardCardFields" />
}
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Process major credit and debit cards through PayPals card fields. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
</div>
);
}
return (
<div className="ppcp-r-optional-payment-methods__wrapper">
<BadgeBox
title={ __(
'Credit and Debit Cards',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="standardCardFields" /> }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
'Process major credit and debit cards through PayPals card fields. <a target="_blank" href="%s">Learn more</a>',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
</div>
);
};
export default BcdcOptionalPaymentMethods;

View file

@ -1,7 +1,7 @@
import { Button } from '@wordpress/components'; import { Button } from '@wordpress/components';
import { useEffect } from '@wordpress/element'; import { useEffect } from '@wordpress/element';
import classNames from 'classnames'; import classNames from 'classnames';
import { openSignup } from '../../../ReusableComponents/Icons'; import { OpenSignup } from '../../../ReusableComponents/Icons';
import { useHandleOnboardingButton } from '../../../../hooks/useHandleConnections'; import { useHandleOnboardingButton } from '../../../../hooks/useHandleConnections';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper'; import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
@ -27,7 +27,7 @@ const ButtonOrPlaceholder = ( {
const buttonProps = { const buttonProps = {
className, className,
variant, variant,
icon: showIcon ? openSignup : null, icon: showIcon ? OpenSignup : null,
}; };
if ( href ) { if ( href ) {

View file

@ -151,6 +151,7 @@ const ManualConnectionForm = () => {
setToggled={ setManualConnectionMode } setToggled={ setManualConnectionMode }
> >
<DataStoreControl <DataStoreControl
__nextHasNoMarginBottom
control={ TextControl } control={ TextControl }
ref={ refClientId } ref={ refClientId }
label={ clientIdLabel } label={ clientIdLabel }
@ -166,6 +167,7 @@ const ManualConnectionForm = () => {
</p> </p>
) } ) }
<DataStoreControl <DataStoreControl
__nextHasNoMarginBottom
control={ TextControl } control={ TextControl }
ref={ refClientSecret } ref={ refClientSecret }
label={ secretKeyLabel } label={ secretKeyLabel }

View file

@ -21,9 +21,37 @@ const OnboardingNavigation = ( { stepDetails, onNext, onPrev } ) => {
showProgressBar={ true } showProgressBar={ true }
progressBarPercent={ percentage * 0.9 } progressBarPercent={ percentage * 0.9 }
> >
<Button variant="link" onClick={ goToWooCommercePaymentsTab }> <OnboardingNavigationActions
onExit={ goToWooCommercePaymentsTab }
isFirst={ isFirst }
isDisabled={ isDisabled }
showNext={ showNext }
onNext={ onNext }
/>
</TopNavigation>
);
};
export default OnboardingNavigation;
const OnboardingNavigationActions = ( {
isFirst,
showNext,
isDisabled,
onExit,
onNext,
} ) => {
// On first page we don't have any actions.
if ( isFirst ) {
return null;
}
return (
<>
<Button variant="link" onClick={ onExit }>
{ __( 'Save and exit', 'woocommerce-paypal-payments' ) } { __( 'Save and exit', 'woocommerce-paypal-payments' ) }
</Button> </Button>
{ showNext && ( { showNext && (
<Button <Button
variant="primary" variant="primary"
@ -33,8 +61,6 @@ const OnboardingNavigation = ( { stepDetails, onNext, onPrev } ) => {
{ __( 'Continue', 'woocommerce-paypal-payments' ) } { __( 'Continue', 'woocommerce-paypal-payments' ) }
</Button> </Button>
) } ) }
</TopNavigation> </>
); );
}; };
export default OnboardingNavigation;

View file

@ -1,13 +1,13 @@
import { Icon } from '@wordpress/components'; import { Icon } from '@wordpress/components';
import { logoPayPal } from '../../../ReusableComponents/Icons'; import { LogoPayPal } from '../../../ReusableComponents/Icons';
const OnboardingHeader = ( props ) => { const OnboardingHeader = ( props ) => {
return ( return (
<section className="ppcp-r-onboarding-header"> <section className="ppcp-r-onboarding-header">
<div className="ppcp-r-onboarding-header__logo"> <div className="ppcp-r-onboarding-header__logo">
<div className="ppcp-r-onboarding-header__logo-wrapper"> <div className="ppcp-r-onboarding-header__logo-wrapper">
<Icon icon={ logoPayPal } width="auto" height={ 38 } /> <Icon icon={ LogoPayPal } width={ 110 } height={ 38 } />
</div> </div>
</div> </div>
<div className="ppcp-r-onboarding-header__content"> <div className="ppcp-r-onboarding-header__content">

View file

@ -1,28 +0,0 @@
import AcdcOptionalPaymentMethods from './AcdcOptionalPaymentMethods';
import BcdcOptionalPaymentMethods from './BcdcOptionalPaymentMethods';
const OptionalPaymentMethods = ( {
useAcdc,
isFastlane,
isPayLater,
storeCountry,
} ) => {
return (
<div className="ppcp-r-optional-payment-methods">
{ useAcdc ? (
<AcdcOptionalPaymentMethods
isFastlane={ isFastlane }
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
) : (
<BcdcOptionalPaymentMethods
isPayLater={ isPayLater }
storeCountry={ storeCountry }
/>
) }
</div>
);
};
export default OptionalPaymentMethods;

View file

@ -0,0 +1,94 @@
import { __ } from '@wordpress/i18n';
import BadgeBox from '../../../ReusableComponents/BadgeBox';
import PaymentMethodsGroup from './PaymentMethodsGroup';
import { PayPalCheckout } from './PaymentOptions';
import { usePaymentConfig } from '../hooks/usePaymentConfig';
const PaymentFlow = ( {
useAcdc,
isFastlane,
isPayLater,
storeCountry,
onlyOptional = false,
} ) => {
const {
includedMethods,
optionalMethods,
optionalTitle,
optionalDescription,
learnMoreConfig,
} = usePaymentConfig( storeCountry, isPayLater, useAcdc, isFastlane );
if ( onlyOptional ) {
return (
<OptionalMethodsSection
methods={ optionalMethods }
learnMoreConfig={ learnMoreConfig }
/>
);
}
return (
<div className="ppcp-r-welcome-docs__wrapper">
<DefaultMethodsSection
methods={ includedMethods }
learnMoreConfig={ learnMoreConfig }
/>
<OptionalMethodsSection
title={ optionalTitle }
description={ optionalDescription }
methods={ optionalMethods }
learnMoreConfig={ learnMoreConfig }
/>
</div>
);
};
export default PaymentFlow;
const DefaultMethodsSection = ( { methods, learnMoreConfig } ) => {
return (
<div className="ppcp-r-welcome-docs__col">
<PayPalCheckout learnMore={ learnMoreConfig.PayPalCheckout } />
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
/>
<PaymentMethodsGroup
methods={ methods }
learnMoreConfig={ learnMoreConfig }
/>
</div>
);
};
const OptionalMethodsSection = ( {
title = '',
description = '',
methods,
learnMoreConfig,
} ) => {
if ( ! methods.length ) {
return null;
}
return (
<div className="ppcp-r-welcome-docs__col">
{ title && (
<BadgeBox
title={ title }
description={ description }
learnMoreLink={ learnMoreConfig.OptionalMethods }
/>
) }
<PaymentMethodsGroup
methods={ methods }
learnMoreConfig={ learnMoreConfig }
/>
</div>
);
};

View file

@ -0,0 +1,29 @@
import { Separator } from '../../../ReusableComponents/Elements';
const PaymentMethodsGroup = ( { methods, learnMoreConfig } ) => {
return (
<>
{ methods.map( ( method, index ) => (
<PaymentMethodItem
key={ method.name }
{ ...method }
learnMore={ learnMoreConfig[ method.name ] }
showSeparator={ index < methods.length - 1 }
/>
) ) }
</>
);
};
export default PaymentMethodsGroup;
const PaymentMethodItem = ( { Component, learnMore, showSeparator } ) => {
return (
<>
<Component learnMore={ learnMore } />
{ showSeparator && (
<Separator className="ppcp-r-payment-method--separator" />
) }
</>
);
};

View file

@ -0,0 +1,28 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const AlternativePaymentMethods = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
// 'icon-button-sepa.svg', // Enable this when the SEPA-Gateway is ready.
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ __(
'Seamless payments for customers across the globe using their preferred payment methods.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default AlternativePaymentMethods;

View file

@ -0,0 +1,26 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const CardFields = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( 'Custom Card Fields', 'woocommerce-paypal-payments' ) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="ccf" /> }
description={ __(
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default CardFields;

View file

@ -0,0 +1,29 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const CreditDebitCards = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __(
'Credit and Debit Cards',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ <PricingTitleBadge item="standardCardFields" /> }
description={ __(
'Process major credit and debit cards through PayPals card fields.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default CreditDebitCards;

View file

@ -0,0 +1,19 @@
import { __ } from '@wordpress/i18n';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const Crypto = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( 'Crypto', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-crypto.svg' ] }
description={ __(
'Let customers checkout with Crypto while you get paid in cash.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default Crypto;

View file

@ -0,0 +1,23 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const DigitalWallets = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( 'Digital Wallets', 'woocommerce-paypal-payments' ) }
imageBadge={ [
'icon-button-apple-pay.svg',
'icon-button-google-pay.svg',
] }
textBadge={ <PricingTitleBadge item="dw" /> }
description={ __(
'Accept Apple Pay on eligible devices and Google Pay through mobile and web.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default DigitalWallets;

View file

@ -0,0 +1,22 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const Fastlane = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( '', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-fastlane-small.svg' ] }
textBadge={
<PricingTitleBadge item="fast country currency=storeCurrency=storeCountrylane" />
}
description={ __(
"Speed up guest checkout with Fatslane. Link a customer's email address to their payment details.",
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default Fastlane;

View file

@ -0,0 +1,19 @@
import { __ } from '@wordpress/i18n';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const PayInThree = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( 'Pay in 3', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-paypal-small.svg' ] }
description={ __(
'Offer installment payment options and get paid upfront - at no extra cost to you.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default PayInThree;

View file

@ -0,0 +1,19 @@
import { __ } from '@wordpress/i18n';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const PayLater = ( { learnMore = '' } ) => {
return (
<BadgeBox
title={ __( 'Pay Later', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-paypal-small.svg' ] }
description={ __(
'Offer installment payment options and get paid upfront.',
'woocommerce-paypal-payments'
) }
learnMoreLink={ learnMore }
/>
);
};
export default PayLater;

View file

@ -0,0 +1,22 @@
import { __ } from '@wordpress/i18n';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const PayPalCheckout = ( {
learnMore = 'https://www.paypal.com/us/business/accept-payments/checkout',
} ) => {
return (
<BadgeBox
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
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'
) }
learnMoreLink={ learnMore }
/>
);
};
export default PayPalCheckout;

Some files were not shown because too many files have changed in this diff Show more