{
- return config.enabled;
+ return true;
},
supports: {
features,
@@ -798,7 +795,7 @@ if ( block_enabled && config.enabled ) {
features: [ ...features, 'ppcp_continuation' ],
},
} );
- } else if ( ! config.usePlaceOrder ) {
+ } else if ( config.smartButtonsEnabled ) {
for ( const fundingSource of [
'paypal',
...config.enabledFundingSources,
diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php
index 4f2bba04c..e4226ca2a 100644
--- a/modules/ppcp-blocks/services.php
+++ b/modules/ppcp-blocks/services.php
@@ -47,6 +47,7 @@ return array(
$container->get( 'blocks.settings.final_review_enabled' ),
$container->get( 'session.cancellation.view' ),
$container->get( 'session.handler' ),
+ $container->get( 'wc-subscriptions.helper' ),
$container->get( 'blocks.add-place-order-method' ),
$container->get( 'wcgateway.use-place-order-button' ),
$container->get( 'wcgateway.place-order-button-text' ),
diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
index 50340b574..32fbae98b 100644
--- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php
+++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
@@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class PayPalPaymentMethod
@@ -87,6 +88,13 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
*/
private $session_handler;
+ /**
+ * The Subscription Helper.
+ *
+ * @var SubscriptionHelper
+ */
+ private $subscription_helper;
+
/**
* Whether to create a non-express method with the standard "Place order" button.
*
@@ -141,6 +149,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
* @param bool $final_review_enabled Whether the final review is enabled.
* @param CancelView $cancellation_view The cancellation view.
* @param SessionHandler $session_handler The Session handler.
+ * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $add_place_order_method Whether to create a non-express method with the standard "Place order" button.
* @param bool $use_place_order Whether to use the standard "Place order" button instead of PayPal buttons.
* @param string $place_order_button_text The text for the standard "Place order" button.
@@ -158,6 +167,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
bool $final_review_enabled,
CancelView $cancellation_view,
SessionHandler $session_handler,
+ SubscriptionHelper $subscription_helper,
bool $add_place_order_method,
bool $use_place_order,
string $place_order_button_text,
@@ -175,6 +185,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
$this->final_review_enabled = $final_review_enabled;
$this->cancellation_view = $cancellation_view;
$this->session_handler = $session_handler;
+ $this->subscription_helper = $subscription_helper;
$this->add_place_order_method = $add_place_order_method;
$this->use_place_order = $use_place_order;
$this->place_order_button_text = $place_order_button_text;
@@ -195,9 +206,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
// Do not load when definitely not needed,
// but we still need to check the locations later and handle in JS
// because has_block cannot be called here (too early).
- return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' )
- && ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) ||
- $this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) );
+ return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' );
}
/**
@@ -245,15 +254,19 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
);
}
+ $smart_buttons_enabled = ! $this->use_place_order
+ && $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'block-checkout' );
+ $place_order_enabled = ( $this->use_place_order || $this->add_place_order_method )
+ && ! $this->subscription_helper->cart_contains_subscription();
+
return array(
'id' => $this->gateway->id,
'title' => $this->gateway->title,
'description' => $this->gateway->description,
- 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'checkout' ),
+ 'smartButtonsEnabled' => $smart_buttons_enabled,
+ 'placeOrderEnabled' => $place_order_enabled,
'fundingSource' => $this->session_handler->funding_source(),
'finalReviewEnabled' => $this->final_review_enabled,
- 'addPlaceOrderMethod' => $this->add_place_order_method,
- 'usePlaceOrder' => $this->use_place_order,
'placeOrderButtonText' => $this->place_order_button_text,
'placeOrderButtonDescription' => $this->place_order_button_description,
'enabledFundingSources' => $funding_sources,
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
index 59e3a0d2c..3bae016bf 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
@@ -233,6 +233,15 @@ class SingleProductBootstap {
this.form(),
this.errorHandler
);
+ if (
+ ! this.gateway.vaultingEnabled &&
+ [ 'subscription', 'variable-subscription' ].includes(
+ this.gateway.productType
+ ) &&
+ this.gateway.manualRenewalEnabled !== '1'
+ ) {
+ return;
+ }
if (
PayPalCommerceGateway.data_client_id.has_subscriptions &&
diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php
index 09783f436..5cd0c85e1 100644
--- a/modules/ppcp-button/services.php
+++ b/modules/ppcp-button/services.php
@@ -37,6 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
return array(
'button.client_id' => static function ( ContainerInterface $container ): string {
@@ -101,12 +102,20 @@ return array(
return $obj->get_context();
},
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
- $state = $container->get( 'onboarding.state' );
- if ( $container->get( 'wcgateway.use-place-order-button' )
- && in_array( $container->get( 'button.context' ), array( 'checkout', 'pay-now' ), true )
- ) {
- return new DisabledSmartButton();
+ $context = $container->get( 'button.context' );
+
+ $settings_status = $container->get( 'wcgateway.settings.status' );
+ assert( $settings_status instanceof SettingsStatus );
+
+ if ( in_array( $context, array( 'checkout', 'pay-now' ), true ) ) {
+ if ( $container->get( 'wcgateway.use-place-order-button' )
+ || ! $settings_status->is_smart_button_enabled_for_location( $context )
+ ) {
+ return new DisabledSmartButton();
+ }
}
+
+ $state = $container->get( 'onboarding.state' );
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
return new DisabledSmartButton();
}
@@ -125,7 +134,6 @@ return array(
$messages_apply = $container->get( 'button.helper.messages-apply' );
$environment = $container->get( 'onboarding.environment' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
- $settings_status = $container->get( 'wcgateway.settings.status' );
return new SmartButton(
$container->get( 'button.url' ),
$container->get( 'ppcp.asset-version' ),
diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php
index 5884ad317..c116796b9 100644
--- a/modules/ppcp-button/src/Assets/SmartButton.php
+++ b/modules/ppcp-button/src/Assets/SmartButton.php
@@ -1031,8 +1031,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
* @return bool
*/
private function has_subscriptions(): bool {
+ if ( ! $this->subscription_helper->plugin_is_active() ) {
+ return false;
+ }
if (
- ! $this->subscription_helper->accept_only_automatic_payment_gateways()
+ $this->subscription_helper->accept_manual_renewals()
&& $this->paypal_subscriptions_enabled() !== true
) {
return false;
@@ -1318,8 +1321,17 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
'needShipping' => $this->need_shipping(),
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
+ 'productType' => null,
+ 'manualRenewalEnabled' => $this->subscription_helper->accept_manual_renewals(),
);
+ if ( is_product() ) {
+ $product = wc_get_product( get_the_ID() );
+ if ( is_a( $product, \WC_Product::class ) ) {
+ $localize['productType'] = $product->get_type();
+ }
+ }
+
if ( 'pay-now' === $this->context() ) {
$localize['pay_now'] = $this->pay_now_script_data();
}
diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php
index b698cd62a..94a4d0d61 100644
--- a/modules/ppcp-compat/src/CompatModule.php
+++ b/modules/ppcp-compat/src/CompatModule.php
@@ -101,7 +101,7 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
add_action(
'woocommerce_init',
function() {
- if ( is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
+ if ( is_admin() && is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
PPEC\DeactivateNote::init();
}
}
diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php
index 96a264bbe..7fba5476c 100644
--- a/modules/ppcp-googlepay/services.php
+++ b/modules/ppcp-googlepay/services.php
@@ -174,6 +174,7 @@ return array(
$container->get( 'googlepay.sdk_url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'session.handler' ),
+ $container->get( 'wc-subscriptions.helper' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.settings.status' ),
diff --git a/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php b/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php
index 0f9ff871b..a8dd8f9c4 100644
--- a/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php
+++ b/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php
@@ -107,7 +107,7 @@ class BlocksPaymentMethod extends AbstractPaymentMethodType {
'id' => $this->name,
'title' => $paypal_data['title'], // See if we should use another.
'description' => $paypal_data['description'], // See if we should use another.
- 'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
+ 'enabled' => $paypal_data['smartButtonsEnabled'], // This button is enabled when PayPal buttons are.
'scriptData' => $this->button->script_data(),
);
}
diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php
index 925b65ad7..1d85b982f 100644
--- a/modules/ppcp-googlepay/src/Assets/Button.php
+++ b/modules/ppcp-googlepay/src/Assets/Button.php
@@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class Button
@@ -85,37 +86,47 @@ class Button implements ButtonInterface {
*/
private $session_handler;
+ /**
+ * The Subscription Helper.
+ *
+ * @var SubscriptionHelper
+ */
+ private $subscription_helper;
+
/**
* SmartButton constructor.
*
- * @param string $module_url The URL to the module.
- * @param string $sdk_url The URL to the SDK.
- * @param string $version The assets version.
- * @param SessionHandler $session_handler The Session handler.
- * @param Settings $settings The Settings.
- * @param Environment $environment The environment object.
- * @param SettingsStatus $settings_status The Settings status helper.
- * @param LoggerInterface $logger The logger.
+ * @param string $module_url The URL to the module.
+ * @param string $sdk_url The URL to the SDK.
+ * @param string $version The assets version.
+ * @param SessionHandler $session_handler The Session handler.
+ * @param SubscriptionHelper $subscription_helper The subscription helper.
+ * @param Settings $settings The Settings.
+ * @param Environment $environment The environment object.
+ * @param SettingsStatus $settings_status The Settings status helper.
+ * @param LoggerInterface $logger The logger.
*/
public function __construct(
string $module_url,
string $sdk_url,
string $version,
SessionHandler $session_handler,
+ SubscriptionHelper $subscription_helper,
Settings $settings,
Environment $environment,
SettingsStatus $settings_status,
LoggerInterface $logger
) {
- $this->module_url = $module_url;
- $this->sdk_url = $sdk_url;
- $this->version = $version;
- $this->session_handler = $session_handler;
- $this->settings = $settings;
- $this->environment = $environment;
- $this->settings_status = $settings_status;
- $this->logger = $logger;
+ $this->module_url = $module_url;
+ $this->sdk_url = $sdk_url;
+ $this->version = $version;
+ $this->session_handler = $session_handler;
+ $this->subscription_helper = $subscription_helper;
+ $this->settings = $settings;
+ $this->environment = $environment;
+ $this->settings_status = $settings_status;
+ $this->logger = $logger;
}
/**
@@ -233,6 +244,21 @@ class Button implements ButtonInterface {
$button_enabled_payorder = true;
$button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
+ if (
+ $this->subscription_helper->plugin_is_active()
+ && ! $this->subscription_helper->accept_manual_renewals()
+ ) {
+ if ( is_product() && $this->subscription_helper->current_product_is_subscription() ) {
+ return false;
+ }
+ if ( $this->subscription_helper->order_pay_contains_subscription() ) {
+ return false;
+ }
+ if ( $this->subscription_helper->cart_contains_subscription() ) {
+ return false;
+ }
+ }
+
/**
* Param types removed to avoid third-party issues.
*
diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js
index 1afc1890b..46862e390 100644
--- a/modules/ppcp-onboarding/resources/js/settings.js
+++ b/modules/ppcp-onboarding/resources/js/settings.js
@@ -1,3 +1,8 @@
+import {
+ setVisible,
+ setVisibleByClass,
+} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
+
document.addEventListener( 'DOMContentLoaded', () => {
const payLaterMessagingSelectableLocations = [
'product',
@@ -216,6 +221,18 @@ document.addEventListener( 'DOMContentLoaded', () => {
replace();
};
+ const hideElements = ( selectorGroup ) => {
+ selectorGroup.forEach( ( selector ) =>
+ setVisibleByClass( selector, false, 'hide' )
+ );
+ };
+
+ const showElements = ( selectorGroup ) => {
+ selectorGroup.forEach( ( selector ) =>
+ setVisibleByClass( selector, true, 'hide' )
+ );
+ };
+
const toggleInputsBySelectedLocations = (
stylingPerSelector,
locationsSelector,
@@ -226,30 +243,30 @@ document.addEventListener( 'DOMContentLoaded', () => {
const payLaterMessagingEnabled = document.querySelector(
payLaterMessagingEnabledSelector
);
- const stylingPerElement = document.querySelector( stylingPerSelector );
- const locationsElement = document.querySelector( locationsSelector );
- const stylingPerElementWrapper = stylingPerElement?.closest( 'tr' );
- const stylingPerElementWrapperSelector =
- '#' + stylingPerElementWrapper?.getAttribute( 'id' );
+ const stylingPerElement = document.querySelector( stylingPerSelector );
if ( ! stylingPerElement ) {
return;
}
+ const stylingPerElementWrapper = stylingPerElement.closest( 'tr' );
+
const toggleElementsBySelectedLocations = () => {
- stylingPerElementWrapper.style.display = '';
const selectedLocations = getSelectedLocations( locationsSelector );
- const emptySmartButtonLocationMessage = jQuery(
- '.ppcp-empty-smart-button-location'
+
+ setVisibleByClass(
+ stylingPerElementWrapper,
+ selectedLocations.length > 0,
+ 'hide'
);
if ( selectedLocations.length === 0 ) {
- hideElements(
- groupToHideOnChecked.concat(
- stylingPerElementWrapperSelector
- )
+ hideElements( groupToHideOnChecked );
+
+ const emptySmartButtonLocationMessage = document.querySelector(
+ '.ppcp-empty-smart-button-location'
);
- if ( emptySmartButtonLocationMessage.length === 0 ) {
+ if ( ! emptySmartButtonLocationMessage ) {
jQuery(
PayPalCommerceSettings.empty_smart_button_location_message
).insertAfter(
@@ -277,11 +294,11 @@ document.addEventListener( 'DOMContentLoaded', () => {
);
groupToShowOnChecked.forEach( ( element ) => {
- if ( inputSelectors.includes( element ) ) {
- document.querySelector( element ).style.display = '';
- return;
- }
- document.querySelector( element ).style.display = 'none';
+ setVisibleByClass(
+ element,
+ inputSelectors.includes( element ),
+ 'hide'
+ );
} );
if ( inputType === 'messages' ) {
@@ -289,18 +306,6 @@ document.addEventListener( 'DOMContentLoaded', () => {
}
};
- const hideElements = ( selectroGroup ) => {
- selectroGroup.forEach( ( elementToHide ) => {
- document.querySelector( elementToHide ).style.display = 'none';
- } );
- };
-
- const showElements = ( selectroGroup ) => {
- selectroGroup.forEach( ( elementToShow ) => {
- document.querySelector( elementToShow ).style.display = '';
- } );
- };
-
groupToggle( stylingPerSelector, groupToShowOnChecked );
toggleElementsBySelectedLocations();
@@ -327,7 +332,7 @@ document.addEventListener( 'DOMContentLoaded', () => {
} );
// We need to use jQuery here as the select might be a select2 element, which doesn't use native events.
- jQuery( locationsElement ).on( 'change', function () {
+ jQuery( locationsSelector ).on( 'change', function () {
const emptySmartButtonLocationMessage = jQuery(
'.ppcp-empty-smart-button-location'
);
@@ -457,6 +462,38 @@ document.addEventListener( 'DOMContentLoaded', () => {
}
};
+ /**
+ * Hide the subscription settings when smart buttons are disabled for checkout,
+ * since the basic redirect gateway is disabled for subscriptions.
+ */
+ const initSettingsHidingForPlaceOrderGateway = () => {
+ const selectors = [
+ '#field-paypal_saved_payments',
+ '#field-subscriptions_mode',
+ '#field-vault_enabled',
+ ];
+
+ const updateSettingsVisibility = () => {
+ const selectedLocations = getSelectedLocations(
+ smartButtonLocationsSelect
+ );
+ const hasCheckoutSmartButtons =
+ selectedLocations.includes( 'checkout' ) ||
+ selectedLocations.includes( 'checkout-block-express' );
+
+ selectors.forEach( ( selector ) => {
+ setVisibleByClass( selector, hasCheckoutSmartButtons, 'hide' );
+ } );
+ };
+
+ updateSettingsVisibility();
+
+ jQuery( smartButtonLocationsSelect ).on(
+ 'change',
+ updateSettingsVisibility
+ );
+ };
+
( () => {
removeDisabledCardIcons(
'select[name="ppcp[disable_cards][]"]',
@@ -488,6 +525,8 @@ document.addEventListener( 'DOMContentLoaded', () => {
toggleMessagingEnabled();
+ initSettingsHidingForPlaceOrderGateway();
+
groupToggle( '#ppcp-vault_enabled', [
'#field-subscription_behavior_when_vault_fails',
] );
diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
index c5385216d..663bdfee1 100644
--- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
+++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
@@ -117,7 +117,7 @@ class OnboardingAssets {
array(
'empty_smart_button_location_message' => sprintf(
'
%1$s
',
- __( 'Note: If no button location is selected, the PayPal gateway will not be available.', 'woocommerce-paypal-payments' )
+ __( 'Note: PayPal buttons and advanced payment features (Alternative Payment Methods, Subscriptions, etc.) are unavailable if no Smart Button Location is configured.', 'woocommerce-paypal-payments' )
),
)
);
diff --git a/modules/ppcp-order-tracking/src/MetaBoxRenderer.php b/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
index 9a19445f7..76cd5aac1 100644
--- a/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
+++ b/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
@@ -124,7 +124,7 @@ class MetaBoxRenderer {
diff --git a/modules/ppcp-paypal-subscriptions/services.php b/modules/ppcp-paypal-subscriptions/services.php
index 84bd96cc0..86fb81376 100644
--- a/modules/ppcp-paypal-subscriptions/services.php
+++ b/modules/ppcp-paypal-subscriptions/services.php
@@ -40,4 +40,13 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
+ 'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
+ return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) );
+ },
+ 'paypal-subscriptions.status' => static function ( ContainerInterface $container ): SubscriptionStatus {
+ return new SubscriptionStatus(
+ $container->get( 'api.endpoint.billing-subscriptions' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
);
diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php
index 1319feae9..53d045454 100644
--- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php
+++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php
@@ -187,6 +187,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
30
);
+ /**
+ * Executed when updating WC Subscription.
+ */
add_action(
'woocommerce_process_shop_subscription_meta',
/**
@@ -194,65 +197,41 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
*
* @psalm-suppress MissingClosureParamType
*/
- function( $id, $post ) use ( $c ) {
+ function( $id ) use ( $c ) {
$subscription = wcs_get_subscription( $id );
- if ( ! is_a( $subscription, WC_Subscription::class ) ) {
+ if ( $subscription === false ) {
return;
}
+
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( ! $subscription_id ) {
return;
}
- $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
- assert( $subscriptions_endpoint instanceof BillingSubscriptions );
- if ( $subscription->get_status() === 'cancelled' ) {
- try {
- $subscriptions_endpoint->cancel( $subscription_id );
- } catch ( RuntimeException $exception ) {
- $error = $exception->getMessage();
- if ( is_a( $exception, PayPalApiException::class ) ) {
- $error = $exception->get_details( $error );
- }
+ $subscription_status = $c->get( 'paypal-subscriptions.status' );
+ assert( $subscription_status instanceof SubscriptionStatus );
- $logger = $c->get( 'woocommerce.logger.woocommerce' );
- $logger->error( 'Could not cancel subscription product on PayPal. ' . $error );
- }
- }
-
- if ( $subscription->get_status() === 'pending-cancel' ) {
- try {
- $subscriptions_endpoint->suspend( $subscription_id );
- } catch ( RuntimeException $exception ) {
- $error = $exception->getMessage();
- if ( is_a( $exception, PayPalApiException::class ) ) {
- $error = $exception->get_details( $error );
- }
-
- $logger = $c->get( 'woocommerce.logger.woocommerce' );
- $logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
- }
- }
-
- if ( $subscription->get_status() === 'active' ) {
- try {
- $current_subscription = $subscriptions_endpoint->subscription( $subscription_id );
- if ( $current_subscription->status === 'SUSPENDED' ) {
- $subscriptions_endpoint->activate( $subscription_id );
- }
- } catch ( RuntimeException $exception ) {
- $error = $exception->getMessage();
- if ( is_a( $exception, PayPalApiException::class ) ) {
- $error = $exception->get_details( $error );
- }
-
- $logger = $c->get( 'woocommerce.logger.woocommerce' );
- $logger->error( 'Could not reactivate subscription product on PayPal. ' . $error );
- }
- }
+ $subscription_status->update_status( $subscription->get_status(), $subscription_id );
},
- 20,
- 2
+ 20
+ );
+
+ /**
+ * Update subscription status from WC Subscriptions list page action link.
+ */
+ add_action(
+ 'woocommerce_subscription_status_updated',
+ function( WC_Subscription $subscription ) use ( $c ) {
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( ! $subscription_id ) {
+ return;
+ }
+
+ $subscription_status = $c->get( 'paypal-subscriptions.status' );
+ assert( $subscription_status instanceof SubscriptionStatus );
+
+ $subscription_status->update_status( $subscription->get_status(), $subscription_id );
+ }
);
add_filter(
@@ -717,11 +696,18 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
* @param WC_Product $product The product.
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
* @return void
+ *
+ * @psalm-suppress PossiblyInvalidCast
*/
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
// phpcs:ignore WordPress.Security.NonceVerification
- $enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
- $product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
+ $enable_subscription_product = wc_string_to_bool( (string) wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) ) );
+ $product->update_meta_data( '_ppcp_enable_subscription_product', wc_bool_to_string( $enable_subscription_product ) );
+
+ if ( ! $enable_subscription_product ) {
+ $product->save();
+ return;
+ }
if ( ! $product->get_sold_individually() ) {
$product->set_sold_individually( true );
@@ -729,7 +715,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
$product->save();
- if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
+ if ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
$subscriptions_api_handler->update_product( $product );
$subscriptions_api_handler->update_plan( $product );
diff --git a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php
new file mode 100644
index 000000000..f88fea7d8
--- /dev/null
+++ b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php
@@ -0,0 +1,107 @@
+logger = $logger;
+ }
+
+ /**
+ * Process subscription renewal.
+ *
+ * @param WC_Subscription[] $subscriptions WC Subscriptions.
+ * @param string $transaction_id PayPal transaction ID.
+ * @return void
+ * @throws WC_Data_Exception If something goes wrong while setting payment method.
+ */
+ public function process( array $subscriptions, string $transaction_id ): void {
+ foreach ( $subscriptions as $subscription ) {
+ if ( $this->is_for_renewal_order( $subscription ) ) {
+ $renewal_order = wcs_create_renewal_order( $subscription );
+ if ( is_a( $renewal_order, WC_Order::class ) ) {
+ $this->logger->info(
+ sprintf(
+ 'Processing renewal order #%s for subscription #%s',
+ $renewal_order->get_id(),
+ $subscription->get_id()
+ )
+ );
+
+ $renewal_order->set_payment_method( $subscription->get_payment_method() );
+ $renewal_order->payment_complete();
+ $this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
+ break;
+ }
+ }
+
+ $parent_order = wc_get_order( $subscription->get_parent() );
+ if ( is_a( $parent_order, WC_Order::class ) ) {
+ $this->logger->info(
+ sprintf(
+ 'Processing parent order #%s for subscription #%s',
+ $parent_order->get_id(),
+ $subscription->get_id()
+ )
+ );
+
+ $subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
+ $subscription->save_meta_data();
+ $this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
+ }
+ }
+ }
+
+ /**
+ * Checks whether subscription order is for renewal or not.
+ *
+ * @param WC_Subscription $subscription WC Subscription.
+ * @return bool
+ */
+ private function is_for_renewal_order( WC_Subscription $subscription ): bool {
+ $subscription_renewal_meta = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
+ if ( $subscription_renewal_meta === 'true' ) {
+ return true;
+ }
+
+ if (
+ time() >= $subscription->get_time( 'start' )
+ && ( time() - $subscription->get_time( 'start' ) ) <= ( 8 * HOUR_IN_SECONDS )
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php
new file mode 100644
index 000000000..1fbec3926
--- /dev/null
+++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php
@@ -0,0 +1,144 @@
+subscriptions_endpoint = $subscriptions_endpoint;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Updates PayPal subscription status from the given WC Subscription status.
+ *
+ * @param string $subscription_status The WC Subscription status.
+ * @param string $subscription_id The PayPal Subscription ID.
+ * @return void
+ */
+ public function update_status( string $subscription_status, string $subscription_id ): void {
+ if ( $subscription_status === 'pending-cancel' || $subscription_status === 'cancelled' ) {
+ try {
+ $current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
+ if ( $current_subscription->status === 'CANCELLED' ) {
+ return;
+ }
+
+ $this->logger->info(
+ sprintf(
+ 'Canceling PayPal subscription #%s.',
+ $subscription_id
+ )
+ );
+
+ $this->subscriptions_endpoint->cancel( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $this->logger->error(
+ sprintf(
+ 'Could not cancel PayPal subscription #%s. %s',
+ $subscription_id,
+ $this->get_error( $exception )
+ )
+ );
+ }
+ }
+
+ if ( $subscription_status === 'on-hold' ) {
+ try {
+ $this->logger->info(
+ sprintf(
+ 'Suspending PayPal subscription #%s.',
+ $subscription_id
+ )
+ );
+
+ $this->subscriptions_endpoint->suspend( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $this->logger->error(
+ sprintf(
+ 'Could not suspend PayPal subscription #%s. %s',
+ $subscription_id,
+ $this->get_error( $exception )
+ )
+ );
+ }
+ }
+
+ if ( $subscription_status === 'active' ) {
+ try {
+ $current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
+ if ( $current_subscription->status === 'SUSPENDED' ) {
+ $this->logger->info(
+ sprintf(
+ 'Activating suspended PayPal subscription #%s.',
+ $subscription_id
+ )
+ );
+
+ $this->subscriptions_endpoint->activate( $subscription_id );
+ }
+ } catch ( RuntimeException $exception ) {
+ $this->logger->error(
+ sprintf(
+ 'Could not reactivate PayPal subscription #%s. %s',
+ $subscription_id,
+ $this->get_error( $exception )
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Get error from exception.
+ *
+ * @param RuntimeException $exception The exception.
+ * @return string
+ */
+ private function get_error( RuntimeException $exception ): string {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ return $error;
+ }
+}
diff --git a/modules/ppcp-settings/images/icon-arrow-down.svg b/modules/ppcp-settings/images/icon-arrow-down.svg
new file mode 100644
index 000000000..3adce0c2e
--- /dev/null
+++ b/modules/ppcp-settings/images/icon-arrow-down.svg
@@ -0,0 +1,3 @@
+Loading...
;
+ }
+
+ if ( ! onboardingProgress.completed ) {
+ return