mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-08-04 03:40:45 +08:00
778 lines
44 KiB
PHP
778 lines
44 KiB
PHP
<?php
|
|
|
|
/**
|
|
* The Settings module.
|
|
*
|
|
* @package WooCommerce\PayPalCommerce\Settings
|
|
*/
|
|
declare (strict_types=1);
|
|
namespace WooCommerce\PayPalCommerce\Settings;
|
|
|
|
use WC_Payment_Gateway;
|
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Log\LoggerInterface;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
|
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
|
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
|
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
|
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
|
use WooCommerce\PayPalCommerce\Settings\Enum\InstallationPathEnum;
|
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\PathRepository;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\LoadingScreenService;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\Migration\MigrationManager;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\Migration\PaymentSettingsMigration;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\PaymentMethodsEligibilityService;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\ScriptDataHandler;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\SellerTypeResolver;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
|
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\OXXOGateway;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
|
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\PayUponInvoice\PayUponInvoiceGateway;
|
|
use WooCommerce\PayPalCommerce\Settings\Service\SettingsDataManager;
|
|
use WooCommerce\PayPalCommerce\Settings\DTO\ConfigurationFlagsDTO;
|
|
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
|
use WooCommerce\PayPalCommerce\Settings\Enum\ProductChoicesEnum;
|
|
use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
|
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
|
use WooCommerce\PayPalCommerce\Axo\Helper\CompatibilityChecker;
|
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
|
use Throwable;
|
|
/**
|
|
* Class SettingsModule
|
|
*/
|
|
class SettingsModule implements ServiceModule, ExecutableModule
|
|
{
|
|
use ModuleClassNameIdTrait;
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function services(): array
|
|
{
|
|
return require __DIR__ . '/../services.php';
|
|
}
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function run(ContainerInterface $container): bool
|
|
{
|
|
// Suppress WooCommerce Settings UI elements via CSS to improve the loading experience.
|
|
$loading_screen_service = $container->get('settings.services.loading-screen-service');
|
|
assert($loading_screen_service instanceof LoadingScreenService);
|
|
$loading_screen_service->register();
|
|
add_action('init', fn() => $this->apply_branded_only_limitations($container), 1);
|
|
add_action(
|
|
'woocommerce_paypal_payments_gateway_migrate',
|
|
/**
|
|
* Auto-trigger settings migration to new UI when upgrading from a legacy (pre-4.0) version.
|
|
*
|
|
* Migration is skipped when:
|
|
* - No previous version exists (fresh install — nothing to migrate)
|
|
* - Previous version is 4.0 or newer (already on the new UI)
|
|
* - OPTION_NAME_MIGRATION_IS_DONE flag is already set (migration completed previously)
|
|
*/
|
|
static function ($previous_version) use ($container): void {
|
|
if (!$previous_version || version_compare((string) $previous_version, '4.0', '>=')) {
|
|
return;
|
|
}
|
|
if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) === '1') {
|
|
return;
|
|
}
|
|
self::pre_populate_credentials($container);
|
|
$run_migration = static function () use ($container): void {
|
|
$migration_manager = $container->get('settings.service.data-migration');
|
|
assert($migration_manager instanceof MigrationManager);
|
|
$migration_manager->migrate();
|
|
};
|
|
// Timing is important - migration saves gateway options, which triggers WooCommerce
|
|
// hooks that require WC to be fully initialized.
|
|
if (did_action('woocommerce_init')) {
|
|
$run_migration();
|
|
} else {
|
|
add_action('woocommerce_init', $run_migration);
|
|
}
|
|
}
|
|
);
|
|
add_action('admin_init', function () use ($container): void {
|
|
if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) === '1') {
|
|
return;
|
|
}
|
|
$legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
|
|
if (empty($legacy_settings['client_id'])) {
|
|
return;
|
|
}
|
|
self::pre_populate_credentials($container);
|
|
$migration_manager = $container->get('settings.service.data-migration');
|
|
assert($migration_manager instanceof MigrationManager);
|
|
$migration_manager->migrate();
|
|
$migration_done = get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE);
|
|
if ((string) $migration_done !== '1') {
|
|
add_action('admin_notices', static function (): void {
|
|
printf('<div class="notice notice-warning"><p>%s</p></div>', esc_html__('PayPal Payments: Settings migration could not be completed because the PayPal API is temporarily unavailable. It will retry automatically on the next page load.', 'woocommerce-paypal-payments'));
|
|
});
|
|
}
|
|
});
|
|
/**
|
|
* Override ACDC status with BCDC for eligible merchants.
|
|
*
|
|
* When the BCDC migration override is active, forces BCDC (Standard Card buttons)
|
|
* classification instead of ACDC (Advanced Card processing), and suppresses ACDC
|
|
* eligibility so the payment methods panel shows BCDC instead of ACDC.
|
|
*
|
|
* @param bool|null $use_bcdc Whether to use BCDC instead of ACDC.
|
|
*
|
|
* @return bool|null True to force BCDC classification, false/null otherwise.
|
|
*/
|
|
add_filter('woocommerce_paypal_payments_override_acdc_status_with_bcdc', static function (?bool $use_bcdc) use ($container) {
|
|
$check_override = $container->get('settings.migration.bcdc-override-check');
|
|
assert(is_callable($check_override));
|
|
if ($check_override()) {
|
|
$use_bcdc = \true;
|
|
add_filter('woocommerce_paypal_payments_is_acdc_active', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_card_fields', '__return_false');
|
|
}
|
|
return $use_bcdc;
|
|
});
|
|
add_action(
|
|
'woocommerce_paypal_payments_gateway_migrate',
|
|
/**
|
|
* Set the BCDC override flag during plugin update, if the merchant has enabled BCDC
|
|
* in the legacy settings.
|
|
*
|
|
* Corrects the BCDC flag for already-migrated merchants, as the previous migration logic
|
|
* did not create this flag. This ensures merchants who migrated before the override flag
|
|
* implementation don't lose their Standard Card button functionality.
|
|
*
|
|
* @param false|string $previous_version The previously installed plugin version,
|
|
* or false on first installation.
|
|
*/
|
|
static function ($previous_version) use ($container): void {
|
|
// Only run this migration logic when updating from version 3.1.1 or older.
|
|
// Skip on fresh installs (no previous version) since there's nothing to migrate.
|
|
if (!$previous_version || version_compare($previous_version, '3.1.1', 'gt')) {
|
|
return;
|
|
}
|
|
try {
|
|
$payment_settings_migration = $container->get('settings.service.data-migration.payment-settings');
|
|
assert($payment_settings_migration instanceof PaymentSettingsMigration);
|
|
$is_bcdc_merchant = $payment_settings_migration->is_bcdc_enabled_for_acdc_merchant();
|
|
// Fallback: when API-based check fails (no cached DCC product status after major
|
|
// version upgrade), detect BCDC usage from legacy settings directly.
|
|
if (!$is_bcdc_merchant) {
|
|
$dcc_applies = $container->get('api.helpers.dccapplies');
|
|
if ($dcc_applies->for_country_currency()) {
|
|
$legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
|
|
$disable_funding = (array) ($legacy_settings['disable_funding'] ?? array());
|
|
$card_was_active = !in_array('card', $disable_funding, \true);
|
|
$dcc_not_enabled = empty($legacy_settings['dcc_enabled']);
|
|
$is_bcdc_merchant = $card_was_active && $dcc_not_enabled;
|
|
}
|
|
}
|
|
if (!$is_bcdc_merchant) {
|
|
return;
|
|
}
|
|
$payment_settings = $container->get('settings.data.payment');
|
|
assert($payment_settings instanceof PaymentSettings);
|
|
// One-time fix: Set override flag for already-migrated merchants with BCDC evidence.
|
|
update_option(PaymentSettingsMigration::OPTION_NAME_BCDC_MIGRATION_OVERRIDE, \true);
|
|
$payment_settings->toggle_method_state(CardButtonGateway::ID, \true);
|
|
} catch (Throwable $error) {
|
|
// Something failed - ignore the error and assume there is no migration data.
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
/**
|
|
* Clean up migration-related options on settings reset.
|
|
*
|
|
* Removes migration state flags when merchant disconnects via "Start Over"
|
|
* to ensure a clean state for subsequent merchant connections.
|
|
*
|
|
* Removed options:
|
|
* - BCDC migration override flag (OPTION_NAME_BCDC_MIGRATION_OVERRIDE)
|
|
*/
|
|
add_action('woocommerce_paypal_payments_reset_settings', static function (): void {
|
|
delete_option(PaymentSettingsMigration::OPTION_NAME_BCDC_MIGRATION_OVERRIDE);
|
|
});
|
|
add_action(
|
|
'admin_enqueue_scripts',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($hook_suffix) use ($container): void {
|
|
if (!is_string($hook_suffix)) {
|
|
return;
|
|
}
|
|
$script_data_handler = $container->get('settings.service.script-data-handler');
|
|
assert($script_data_handler instanceof ScriptDataHandler);
|
|
$script_data_handler->localize_scripts($hook_suffix);
|
|
}
|
|
);
|
|
add_action('woocommerce_paypal_payments_gateway_admin_options_wrapper', function () use ($container): void {
|
|
global $hide_save_button;
|
|
$hide_save_button = \true;
|
|
$this->initialize_branded_only($container);
|
|
$this->render_header();
|
|
$this->render_content();
|
|
});
|
|
add_action('rest_api_init', static function () use ($container): void {
|
|
$endpoints = array('onboarding' => $container->get('settings.rest.onboarding'), 'common' => $container->get('settings.rest.common'), 'connect_manual' => $container->get('settings.rest.authentication'), 'login_link' => $container->get('settings.rest.login_link'), 'webhooks' => $container->get('settings.rest.webhooks'), 'refresh_feature_status' => $container->get('settings.rest.refresh_feature_status'), 'payment' => $container->get('settings.rest.payment'), 'settings' => $container->get('settings.rest.settings'), 'styling' => $container->get('settings.rest.styling'), 'todos' => $container->get('settings.rest.todos'), 'pay_later_messaging' => $container->get('settings.rest.pay_later_messaging'), 'features' => $container->get('settings.rest.features'), 'migrate_to_acdc' => $container->get('settings.rest.migrate_to_acdc'), 'agentic_beta_banner' => $container->get('settings.rest.agentic_beta_banner'));
|
|
foreach ($endpoints as $endpoint) {
|
|
assert($endpoint instanceof RestEndpoint);
|
|
$endpoint->register_routes();
|
|
}
|
|
});
|
|
add_action('admin_init', static function () use ($container): void {
|
|
$connection_handler = $container->get('settings.handler.connection-listener');
|
|
assert($connection_handler instanceof ConnectionListener);
|
|
// @phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no nonce; sanitation done by the handler
|
|
$connection_handler->process(get_current_user_id(), $_GET);
|
|
});
|
|
add_action('woocommerce_paypal_payments_merchant_disconnected', static function () use ($container): void {
|
|
$logger = $container->get('woocommerce.logger.woocommerce');
|
|
assert($logger instanceof LoggerInterface);
|
|
$logger->info('Merchant disconnected, reset onboarding');
|
|
// Reset onboarding profile.
|
|
$onboarding_profile = $container->get('settings.data.onboarding');
|
|
assert($onboarding_profile instanceof OnboardingProfile);
|
|
$onboarding_profile->set_completed(\false);
|
|
$onboarding_profile->set_step(0);
|
|
$onboarding_profile->set_gateways_synced(\false);
|
|
$onboarding_profile->set_gateways_refreshed(\false);
|
|
$onboarding_profile->save();
|
|
// Reset dismissed and completed on click todos.
|
|
$todos = $container->get('settings.data.todos');
|
|
assert($todos instanceof TodosModel);
|
|
$todos->reset_dismissed_todos();
|
|
$todos->reset_completed_onclick_todos();
|
|
});
|
|
add_action('woocommerce_paypal_payments_authenticated_merchant', static function () use ($container): void {
|
|
$logger = $container->get('woocommerce.logger.woocommerce');
|
|
assert($logger instanceof LoggerInterface);
|
|
$logger->info('Merchant connected, complete onboarding and set defaults.');
|
|
$general_settings = $container->get('settings.data.general');
|
|
assert($general_settings instanceof GeneralSettings);
|
|
// Resolve an unknown seller type once, at connect time.
|
|
// Clear the stale seller-status cache and failure registry first:
|
|
// fresh credentials warrant a fresh lookup. Only does work when
|
|
// seller_type is 'unknown'.
|
|
$partners_endpoint = $container->get('api.endpoint.partners');
|
|
$seller_type_resolver = $container->get('settings.service.seller-type-resolver');
|
|
assert($partners_endpoint instanceof PartnersEndpoint);
|
|
assert($seller_type_resolver instanceof SellerTypeResolver);
|
|
do_action('woocommerce_paypal_payments_clear_apm_product_status');
|
|
$seller_type_resolver->resolve_unknown_seller_type($general_settings, $partners_endpoint, $logger);
|
|
$onboarding_profile = $container->get('settings.data.onboarding');
|
|
assert($onboarding_profile instanceof OnboardingProfile);
|
|
$onboarding_profile->set_completed(\true);
|
|
$onboarding_profile->save();
|
|
// Try to apply a default configuration for the current store.
|
|
$data_manager = $container->get('settings.service.data-manager');
|
|
assert($data_manager instanceof SettingsDataManager);
|
|
$flags = new ConfigurationFlagsDTO();
|
|
$flags->country_code = $general_settings->get_merchant_country();
|
|
$flags->is_business_seller = $general_settings->is_business_seller();
|
|
$flags->use_card_payments = $onboarding_profile->get_accept_card_payments();
|
|
$flags->use_subscriptions = in_array(ProductChoicesEnum::SUBSCRIPTIONS, $onboarding_profile->get_products(), \true);
|
|
$data_manager->set_defaults_for_new_merchant($flags);
|
|
});
|
|
add_filter('woocommerce_paypal_payments_payment_methods', function (array $payment_methods) use ($container): array {
|
|
$payment_methods_eligibility_service = $container->get('settings.service.payment_methods_eligibilities');
|
|
assert($payment_methods_eligibility_service instanceof PaymentMethodsEligibilityService);
|
|
foreach ($payment_methods_eligibility_service->get_eligibility_checks() as $payment_method_id => $payment_method_check) {
|
|
if (isset($payment_methods[$payment_method_id]) && call_user_func($payment_method_check) === \false) {
|
|
unset($payment_methods[$payment_method_id]);
|
|
}
|
|
}
|
|
return $payment_methods;
|
|
});
|
|
add_filter(
|
|
'woocommerce_payment_gateways',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
function ($methods) use ($container): array {
|
|
$is_onboarded = $container->get('api.merchant_id') !== '';
|
|
if (!is_array($methods) || !$is_onboarded) {
|
|
return $methods;
|
|
}
|
|
$card_button_gateway = $container->get('wcgateway.card-button-gateway');
|
|
assert($card_button_gateway instanceof CardButtonGateway);
|
|
$googlepay_gateway = $container->get('googlepay.wc-gateway');
|
|
assert($googlepay_gateway instanceof WC_Payment_Gateway);
|
|
$applepay_gateway = $container->get('applepay.wc-gateway');
|
|
assert($applepay_gateway instanceof WC_Payment_Gateway);
|
|
$methods[] = $card_button_gateway;
|
|
$methods[] = $googlepay_gateway;
|
|
$methods[] = $applepay_gateway;
|
|
if ($container->has('axo.eligible') && $container->get('axo.eligible')) {
|
|
$axo_gateway = $container->get('axo.gateway');
|
|
assert($axo_gateway instanceof WC_Payment_Gateway);
|
|
$methods[] = $axo_gateway;
|
|
}
|
|
// Remove gateways where the merchant is not eligible.
|
|
$eligibility_service = $container->get('settings.service.payment_methods_eligibilities');
|
|
$eligibility_checks = $eligibility_service->get_eligibility_checks();
|
|
$methods = array_filter($methods, static function ($gateway) use ($eligibility_checks) {
|
|
$id = $gateway instanceof WC_Payment_Gateway ? $gateway->id : '';
|
|
return !isset($eligibility_checks[$id]) || $eligibility_checks[$id]();
|
|
});
|
|
return $methods;
|
|
},
|
|
99
|
|
);
|
|
/**
|
|
* Filters the available payment gateways in the WooCommerce admin settings.
|
|
*
|
|
* Ensures that only enabled PayPal payment gateways are displayed.
|
|
*
|
|
* @hook woocommerce_admin_field_payment_gateways
|
|
* @priority 5 Allows modifying the registered gateways before they are displayed.
|
|
*/
|
|
add_action('woocommerce_admin_field_payment_gateways', function () use ($container): void {
|
|
$all_gateway_ids = $container->get('settings.config.all-gateway-ids');
|
|
$payment_gateways = WC()->payment_gateways->payment_gateways;
|
|
foreach ($payment_gateways as $index => $payment_gateway) {
|
|
$payment_gateway_id = $payment_gateway->id;
|
|
if (!in_array($payment_gateway_id, $all_gateway_ids, \true) || $payment_gateway_id === PayPalGateway::ID || $this->is_gateway_enabled($payment_gateway_id)) {
|
|
continue;
|
|
}
|
|
unset(WC()->payment_gateways->payment_gateways[$index]);
|
|
}
|
|
$card_config = $container->get('wcgateway.configuration.card-configuration');
|
|
$store_country = $container->get('api.merchant.country');
|
|
if ($card_config->use_acdc() && $store_country !== 'MX') {
|
|
foreach (WC()->payment_gateways->payment_gateways as $index => $gateway) {
|
|
if ($gateway->id === CardButtonGateway::ID) {
|
|
unset(WC()->payment_gateways->payment_gateways[$index]);
|
|
}
|
|
}
|
|
}
|
|
}, 5);
|
|
// Remove the Fastlane gateway if the customer is logged in, ensuring that we don't interfere with the Fastlane gateway status in the settings UI.
|
|
add_filter(
|
|
'woocommerce_available_payment_gateways',
|
|
/**
|
|
* Param types removed to avoid third-party issues.
|
|
*
|
|
* @psalm-suppress MissingClosureParamType
|
|
*/
|
|
static function ($methods) {
|
|
if (!is_array($methods)) {
|
|
return $methods;
|
|
}
|
|
if (is_user_logged_in() && !is_admin()) {
|
|
foreach ($methods as $key => $method) {
|
|
if ($method instanceof WC_Payment_Gateway && $method->id === 'ppcp-axo-gateway') {
|
|
unset($methods[$key]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return $methods;
|
|
}
|
|
);
|
|
add_filter('woocommerce_paypal_payments_gateway_title', function (string $title, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('title', $title);
|
|
}, 10, 2);
|
|
add_filter('woocommerce_paypal_payments_gateway_description', function (string $description, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('description', $description);
|
|
}, 10, 2);
|
|
add_filter('woocommerce_paypal_payments_paypal_gateway_icon', function (string $icon_url) use ($container) {
|
|
$payment_settings = $container->get('settings.data.payment');
|
|
assert($payment_settings instanceof PaymentSettings);
|
|
// If "Show logo" is disabled, return an empty string to hide the icon.
|
|
return $payment_settings->get_paypal_show_logo() ? $icon_url : '';
|
|
});
|
|
add_filter('woocommerce_paypal_payments_card_button_gateway_should_register_gateway', '__return_true');
|
|
add_filter('woocommerce_paypal_payments_credit_card_gateway_form_fields', function (array $form_fields) {
|
|
$form_fields['enabled'] = array('title' => __('Enable/Disable', 'woocommerce-paypal-payments'), 'type' => 'checkbox', 'desc_tip' => \true, 'description' => __('Once enabled, the Credit Card option will show up in the checkout.', 'woocommerce-paypal-payments'), 'label' => __('Enable Advanced Card Processing', 'woocommerce-paypal-payments'), 'default' => 'no');
|
|
return $form_fields;
|
|
});
|
|
add_filter('woocommerce_paypal_payments_credit_card_gateway_should_update_enabled', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_credit_card_gateway_title', function (string $title, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('title', $title);
|
|
}, 10, 2);
|
|
add_filter('woocommerce_paypal_payments_credit_card_gateway_description', function (string $description, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('description', $description);
|
|
}, 10, 2);
|
|
if (is_admin()) {
|
|
add_filter('woocommerce_paypal_payments_axo_gateway_should_update_enabled', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_axo_gateway_title', function (string $title, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('title', $title);
|
|
}, 10, 2);
|
|
add_filter('woocommerce_paypal_payments_axo_gateway_description', function (string $description, WC_Payment_Gateway $gateway) {
|
|
return $gateway->get_option('description', $description);
|
|
}, 10, 2);
|
|
}
|
|
// Enable Fastlane after onboarding if the store is compatible.
|
|
add_action('woocommerce_paypal_payments_toggle_payment_gateways', function (PaymentSettings $payment_methods, ConfigurationFlagsDTO $flags) use ($container) {
|
|
if ($flags->is_business_seller && $flags->use_card_payments) {
|
|
$compatibility_checker = $container->get('axo.helpers.compatibility-checker');
|
|
assert($compatibility_checker instanceof CompatibilityChecker);
|
|
if ($compatibility_checker->is_fastlane_compatible()) {
|
|
$payment_methods->toggle_method_state(AxoGateway::ID, \true);
|
|
}
|
|
}
|
|
$general_settings = $container->get('settings.data.general');
|
|
assert($general_settings instanceof GeneralSettings);
|
|
$merchant_data = $general_settings->get_merchant_data();
|
|
$merchant_country = $merchant_data->merchant_country;
|
|
}, 10, 2);
|
|
// Enable APMs after onboarding if the country is compatible.
|
|
add_action('woocommerce_paypal_payments_toggle_payment_gateways_apms', function (PaymentSettings $payment_methods, array $methods_apm, ConfigurationFlagsDTO $flags) use ($container) {
|
|
$general_settings = $container->get('settings.data.general');
|
|
assert($general_settings instanceof GeneralSettings);
|
|
$merchant_data = $general_settings->get_merchant_data();
|
|
$merchant_country = $merchant_data->merchant_country;
|
|
// Enable all APM methods.
|
|
foreach ($methods_apm as $method) {
|
|
if ($flags->use_card_payments === \false) {
|
|
$payment_methods->toggle_method_state($method['id'], $flags->use_card_payments);
|
|
continue;
|
|
}
|
|
// Skip PayUponInvoice if merchant is not in Germany.
|
|
if (PayUponInvoiceGateway::ID === $method['id'] && 'DE' !== $merchant_country) {
|
|
continue;
|
|
}
|
|
// For OXXO: enable ONLY if merchant is in Mexico.
|
|
if (OXXOGateway::ID === $method['id']) {
|
|
if ('MX' === $merchant_country) {
|
|
$payment_methods->toggle_method_state($method['id'], \true);
|
|
}
|
|
continue;
|
|
}
|
|
$payment_methods->toggle_method_state($method['id'], \true);
|
|
}
|
|
}, 10, 3);
|
|
// Toggle payment gateways after onboarding based on flags.
|
|
add_action('woocommerce_paypal_payments_sync_gateways', static function () use ($container) {
|
|
$settings_data_manager = $container->get('settings.service.data-manager');
|
|
assert($settings_data_manager instanceof SettingsDataManager);
|
|
$settings_data_manager->sync_gateway_settings();
|
|
});
|
|
// Redirect payment method links in the WC Payment Gateway to the new UI Payment Methods tab.
|
|
$gateway_redirect_service = $container->get('settings.service.gateway-redirect');
|
|
assert($gateway_redirect_service instanceof GatewayRedirectService);
|
|
$gateway_redirect_service->register();
|
|
// Do not render Pay Later messaging if the "Save PayPal and Venmo" setting is enabled.
|
|
add_filter('woocommerce_paypal_payments_should_render_pay_later_messaging', static function () use ($container): bool {
|
|
$settings_model = $container->get('settings.data.settings');
|
|
assert($settings_model instanceof SettingsModel);
|
|
return !$settings_model->get_save_paypal_and_venmo();
|
|
});
|
|
// Migration code to update BN code of merchants that are on whitelabel mode (own_brand_only false) to use the whitelabel BN code (direct).
|
|
add_action('woocommerce_paypal_payments_gateway_migrate_on_update', static function () use ($container) {
|
|
$general_settings = $container->get('settings.data.general');
|
|
assert($general_settings instanceof GeneralSettings);
|
|
$partner_attribution = $container->get('api.helper.partner-attribution');
|
|
assert($partner_attribution instanceof PartnerAttribution);
|
|
$own_brand_only = $general_settings->own_brand_only();
|
|
$installation_path = $general_settings->get_installation_path();
|
|
if (!$own_brand_only && $installation_path !== InstallationPathEnum::DIRECT) {
|
|
$partner_attribution->initialize_bn_code(InstallationPathEnum::DIRECT, \true);
|
|
}
|
|
});
|
|
add_action(
|
|
'woocommerce_paypal_payments_gateway_migrate',
|
|
/**
|
|
* Retroactive fix for CardButtonGateway not enabled after migration.
|
|
*
|
|
* In versions up to 3.4.1, the migration only enabled CardButtonGateway for
|
|
* ACDC-eligible merchants using BCDC. Non-ACDC merchants who had the card
|
|
* funding source active (the default) were missed, causing the card button
|
|
* to disappear after upgrade.
|
|
*
|
|
* @param false|string $previous_version The previously installed plugin version,
|
|
* or false on first installation.
|
|
*/
|
|
static function ($previous_version) use ($container): void {
|
|
if ($previous_version && version_compare($previous_version, '3.4.1', 'gt')) {
|
|
return;
|
|
}
|
|
if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) !== '1') {
|
|
return;
|
|
}
|
|
$payment_settings = $container->get('settings.data.payment');
|
|
assert($payment_settings instanceof PaymentSettings);
|
|
if ($payment_settings->is_method_enabled(CardButtonGateway::ID)) {
|
|
return;
|
|
}
|
|
$legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
|
|
$disable_funding = (array) ($legacy_settings['disable_funding'] ?? array());
|
|
if (!in_array('card', $disable_funding, \true)) {
|
|
$payment_settings->toggle_method_state(CardButtonGateway::ID, \true);
|
|
$payment_settings->save();
|
|
}
|
|
}
|
|
);
|
|
add_action(
|
|
'woocommerce_paypal_payments_gateway_migrate',
|
|
/**
|
|
* Retroactive fix for local APMs not enabled after migration when
|
|
* allow_local_apm_gateways was false.
|
|
*
|
|
* In versions up to 3.4.1, the migration only enabled local APMs when
|
|
* allow_local_apm_gateways was truthy. When it was false, APMs were shown
|
|
* inside the PayPal button, not as separate gateways. The new UI always
|
|
* treats APMs as separate gateways, so skipping them left them invisible.
|
|
*
|
|
* @param false|string $previous_version The previously installed plugin version,
|
|
* or false on first installation.
|
|
*/
|
|
static function ($previous_version) use ($container): void {
|
|
if ($previous_version && version_compare($previous_version, '3.4.1', 'gt')) {
|
|
return;
|
|
}
|
|
if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) !== '1') {
|
|
return;
|
|
}
|
|
$legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
|
|
// Only fix merchants who had allow_local_apm_gateways falsy.
|
|
// Truthy merchants were migrated correctly.
|
|
if (!empty($legacy_settings['allow_local_apm_gateways'])) {
|
|
return;
|
|
}
|
|
$payment_settings = $container->get('settings.data.payment');
|
|
assert($payment_settings instanceof PaymentSettings);
|
|
$local_apms = $container->get('ppcp-local-apms.payment-methods');
|
|
$disable_funding = (array) ($legacy_settings['disable_funding'] ?? array());
|
|
$changed = \false;
|
|
foreach ($local_apms as $apm) {
|
|
if (!in_array($apm['id'], $disable_funding, \true) && !$payment_settings->is_method_enabled($apm['id'])) {
|
|
$payment_settings->toggle_method_state($apm['id'], \true);
|
|
$changed = \true;
|
|
}
|
|
}
|
|
if ($changed) {
|
|
$payment_settings->save();
|
|
}
|
|
}
|
|
);
|
|
add_action(
|
|
'woocommerce_paypal_payments_gateway_migrate',
|
|
/**
|
|
* Migrates payment level processing setting during plugin update.
|
|
*
|
|
* For merchants updating from version 3.3.2 or older, disables Level 2/3
|
|
* processing if they previously opted out of automatic updates (stay_updated=false).
|
|
* Merchants who opted into updates inherit the default enabled state.
|
|
*
|
|
* @param false|string $previous_version The previously installed plugin version,
|
|
* or false on first installation.
|
|
*/
|
|
static function ($previous_version) use ($container): void {
|
|
// Only run this migration logic when updating from version 3.3.2 or older.
|
|
if ($previous_version && version_compare($previous_version, '3.3.2', 'gt')) {
|
|
return;
|
|
}
|
|
try {
|
|
$settings_model = $container->get('settings.data.settings');
|
|
assert($settings_model instanceof SettingsModel);
|
|
if (!$settings_model->get_stay_updated()) {
|
|
$settings_model->set_payment_level_processing(\false);
|
|
$settings_model->save();
|
|
}
|
|
} catch (Throwable $error) {
|
|
// Something failed - ignore the error and assume there is no migration data.
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
/**
|
|
* Disable ACDC gateway for merchants not eligible for ACDC
|
|
* after onboarding is completed.
|
|
*/
|
|
add_action('woocommerce_paypal_payments_toggle_payment_gateways', function (PaymentSettings $payment_methods, ConfigurationFlagsDTO $flags) use ($container) {
|
|
$dcc_configuration = $container->get('wcgateway.configuration.card-configuration');
|
|
assert($dcc_configuration instanceof CardPaymentsConfiguration);
|
|
if ($flags->is_business_seller && $flags->use_card_payments && !$dcc_configuration->use_acdc()) {
|
|
$payment_methods->toggle_method_state(CreditCardGateway::ID, \false);
|
|
}
|
|
}, 10, 2);
|
|
/**
|
|
* Disable Apple Pay/Google Pay gateways for merchants not eligible
|
|
* after onboarding is completed.
|
|
*/
|
|
add_action('woocommerce_paypal_payments_toggle_payment_gateways', function (PaymentSettings $payment_methods, ConfigurationFlagsDTO $flags) use ($container) {
|
|
if (!$flags->is_business_seller || !$flags->use_digital_wallets) {
|
|
return;
|
|
}
|
|
$applepay_product_status = $container->get('applepay.apple-product-status');
|
|
$applepay_eligibility = $container->get('applepay.eligibility.check');
|
|
$apple_pay_available = $applepay_product_status->is_active() && $applepay_eligibility();
|
|
if (!$apple_pay_available) {
|
|
$payment_methods->toggle_method_state(ApplePayGateway::ID, \false);
|
|
}
|
|
$googlepay_product_status = $container->get('googlepay.helpers.apm-product-status');
|
|
$googlepay_eligibility = $container->get('googlepay.eligibility.check');
|
|
$google_pay_available = $googlepay_product_status->is_active() && $googlepay_eligibility();
|
|
if (!$google_pay_available) {
|
|
$payment_methods->toggle_method_state(GooglePayGateway::ID, \false);
|
|
}
|
|
}, 10, 2);
|
|
return \true;
|
|
}
|
|
/**
|
|
* Pre-populates GeneralSettings with legacy credentials before migration
|
|
* DI services are resolved.
|
|
*
|
|
* DI services like api.key, api.secret, api.merchant_id are resolved from
|
|
* SettingsProvider → GeneralSettings → woocommerce-ppcp-data-common.
|
|
* This option does not exist before migration, so these DI services resolve
|
|
* to empty strings. Pre-populating ensures PartnersEndpoint has working
|
|
* credentials during migration so seller_status() API call succeeds.
|
|
*
|
|
* @param ContainerInterface $container The DI container.
|
|
*/
|
|
private static function pre_populate_credentials(ContainerInterface $container): void
|
|
{
|
|
$general = $container->get('settings.data.general');
|
|
assert($general instanceof GeneralSettings);
|
|
if ($general->is_merchant_connected()) {
|
|
return;
|
|
}
|
|
$legacy = (array) get_option('woocommerce-ppcp-settings', array());
|
|
if (empty($legacy['client_id']) || empty($legacy['merchant_id'])) {
|
|
return;
|
|
}
|
|
$general->set_merchant_data(new MerchantConnectionDTO(!empty($legacy['sandbox_on']), $legacy['client_id'], $legacy['client_secret'] ?? '', $legacy['merchant_id'], $legacy['merchant_email'] ?? '', '', SellerTypeEnum::UNKNOWN));
|
|
}
|
|
/**
|
|
* Checks the branded-only state and applies relevant site-wide feature limitations, if needed.
|
|
*
|
|
* @param ContainerInterface $container The DI container provider.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function apply_branded_only_limitations(ContainerInterface $container): void
|
|
{
|
|
$settings = $container->get('settings.data.general');
|
|
assert($settings instanceof GeneralSettings);
|
|
if (!$settings->own_brand_only()) {
|
|
return;
|
|
}
|
|
/**
|
|
* Prevent white-label payment methods from being enabled during onboarding.
|
|
*
|
|
* During the onboarding flow, toggle_payment_gateways() enables ACDC, Apple Pay,
|
|
* and Google Pay for business sellers. In branded-only mode, these white-label
|
|
* methods should never be enabled.
|
|
*
|
|
* This hook runs during the 'woocommerce_paypal_payments_toggle_payment_gateways_apms'
|
|
* action, immediately disabling these methods before payment settings are saved.
|
|
* This prevents them from being enabled even temporarily during onboarding.
|
|
*
|
|
* Without this hook, white-label methods would be enabled during onboarding and
|
|
* then disabled afterward, creating an inconsistent state during the upgrade process.
|
|
*/
|
|
add_action('woocommerce_paypal_payments_toggle_payment_gateways_apms', static function (PaymentSettings $payment_settings): void {
|
|
$payment_settings->toggle_method_state(CreditCardGateway::ID, \false);
|
|
$payment_settings->toggle_method_state(ApplePayGateway::ID, \false);
|
|
$payment_settings->toggle_method_state(GooglePayGateway::ID, \false);
|
|
});
|
|
$payment_settings = $container->get('settings.data.payment');
|
|
assert($payment_settings instanceof PaymentSettings);
|
|
$gateway_name = CardButtonGateway::ID;
|
|
$gateway_settings = get_option("woocommerce_{$gateway_name}_settings", array());
|
|
$gateway_enabled = $gateway_settings['enabled'] ?? \false;
|
|
if ($payment_settings->is_method_enabled(CreditCardGateway::ID)) {
|
|
$payment_settings->toggle_method_state(CreditCardGateway::ID, \false);
|
|
if ($gateway_enabled === 'yes') {
|
|
$payment_settings->toggle_method_state(CardButtonGateway::ID, \true);
|
|
}
|
|
$payment_settings->save();
|
|
}
|
|
if ($payment_settings->is_method_enabled(ApplePayGateway::ID)) {
|
|
$payment_settings->toggle_method_state(ApplePayGateway::ID, \false);
|
|
$payment_settings->save();
|
|
}
|
|
if ($payment_settings->is_method_enabled(GooglePayGateway::ID)) {
|
|
$payment_settings->toggle_method_state(GooglePayGateway::ID, \false);
|
|
$payment_settings->save();
|
|
}
|
|
/**
|
|
* In branded-only mode, we completely disable all white label features.
|
|
*/
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_applepay', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_googlepay', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_axo', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_save_payment_methods', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_eligible_for_card_fields', '__return_false');
|
|
add_filter('woocommerce_paypal_payments_is_acdc_active', '__return_false');
|
|
}
|
|
/**
|
|
* Initializes the branded-only flags if they are not set.
|
|
*
|
|
* This method can be called multiple times:
|
|
* The flags are only initialized once but does not change afterward.
|
|
*
|
|
* Also, this check has no impact on performance for two reasons:
|
|
* 1. The GeneralSettings class is already initialized and will short-circuit
|
|
* the check if the settings are already initialized.
|
|
* 2. The settings UI is a React app, this method only runs when the React app
|
|
* is injected to the DOM, and not while the UI is used.
|
|
*
|
|
* @param ContainerInterface $container The DI container provider.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function initialize_branded_only(ContainerInterface $container): void
|
|
{
|
|
$path_repository = $container->get('settings.service.branded-experience.path-repository');
|
|
assert($path_repository instanceof PathRepository);
|
|
$partner_attribution = $container->get('api.helper.partner-attribution');
|
|
assert($partner_attribution instanceof PartnerAttribution);
|
|
$general_settings = $container->get('settings.data.general');
|
|
assert($general_settings instanceof GeneralSettings);
|
|
$path_repository->persist();
|
|
$partner_attribution->initialize_bn_code($general_settings->get_installation_path());
|
|
}
|
|
/**
|
|
* Outputs the settings page header (title and back-link).
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function render_header(): void
|
|
{
|
|
echo '<h2>' . esc_html__('PayPal', 'woocommerce-paypal-payments');
|
|
wc_back_link(__('Return to payments', 'woocommerce-paypal-payments'), admin_url('admin.php?page=wc-settings&tab=checkout'));
|
|
echo '</h2>';
|
|
}
|
|
/**
|
|
* Renders the container for the React app.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function render_content(): void
|
|
{
|
|
echo '<div id="ppcp-settings-container"></div>';
|
|
}
|
|
/**
|
|
* Checks if the payment gateway with the given name is enabled.
|
|
*
|
|
* @param string $gateway_name The gateway name.
|
|
*
|
|
* @return bool True if the payment gateway with the given name is enabled, otherwise false.
|
|
*/
|
|
protected function is_gateway_enabled(string $gateway_name): bool
|
|
{
|
|
$gateway_settings = get_option("woocommerce_{$gateway_name}_settings", array());
|
|
$gateway_enabled = $gateway_settings['enabled'] ?? \false;
|
|
return $gateway_enabled === 'yes';
|
|
}
|
|
}
|