diff --git a/modules.php b/modules.php index 0cb467161..6d3f714ef 100644 --- a/modules.php +++ b/modules.php @@ -27,6 +27,7 @@ return function ( string $root_dir ): iterable { ( require "$modules_dir/ppcp-order-tracking/module.php" )(), ( require "$modules_dir/ppcp-uninstall/module.php" )(), ( require "$modules_dir/ppcp-blocks/module.php" )(), + ( require "$modules_dir/ppcp-paypal-subscriptions/module.php" )(), ); if ( apply_filters( // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index a222f0782..0458eab0e 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -204,7 +204,7 @@ return array( $intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE'; $application_context_repository = $container->get( 'api.repository.application-context' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); return new OrderEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 5ca7fb38f..8661f54a6 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -83,7 +83,7 @@ return array( $request_data = $container->get( 'button.request-data' ); $client_id = $container->get( 'button.client_id' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); $messages_apply = $container->get( 'button.helper.messages-apply' ); $environment = $container->get( 'onboarding.environment' ); $payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index cb21e195b..035851b04 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -27,7 +27,7 @@ return array( }, 'compat.ppec.subscriptions-handler' => static function ( ContainerInterface $container ) { - $ppcp_renewal_handler = $container->get( 'subscription.renewal-handler' ); + $ppcp_renewal_handler = $container->get( 'wc-subscriptions.renewal-handler' ); $gateway = $container->get( 'compat.ppec.mock-gateway' ); return new PPEC\SubscriptionsHandler( $ppcp_renewal_handler, $gateway ); diff --git a/modules/ppcp-wc-subscriptions/.babelrc b/modules/ppcp-paypal-subscriptions/.babelrc similarity index 100% rename from modules/ppcp-wc-subscriptions/.babelrc rename to modules/ppcp-paypal-subscriptions/.babelrc diff --git a/modules/ppcp-paypal-subscriptions/.gitignore b/modules/ppcp-paypal-subscriptions/.gitignore new file mode 100644 index 000000000..0bd2b9f58 --- /dev/null +++ b/modules/ppcp-paypal-subscriptions/.gitignore @@ -0,0 +1,3 @@ +node_modules +assets/js +assets/css diff --git a/modules/ppcp-paypal-subscriptions/composer.json b/modules/ppcp-paypal-subscriptions/composer.json new file mode 100644 index 000000000..161c4b9ce --- /dev/null +++ b/modules/ppcp-paypal-subscriptions/composer.json @@ -0,0 +1,17 @@ +{ + "name": "woocommerce/ppcp-paypal-subscriptions", + "type": "dhii-mod", + "description": "Module for PayPal Subscriptions API integration", + "license": "GPL-2.0", + "require": { + "php": "^7.2 | ^8.0", + "dhii/module-interface": "^0.3.0-alpha1" + }, + "autoload": { + "psr-4": { + "WooCommerce\\PayPalCommerce\\PayPalSubscriptions\\": "src" + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/modules/ppcp-paypal-subscriptions/extensions.php b/modules/ppcp-paypal-subscriptions/extensions.php new file mode 100644 index 000000000..fc91cd245 --- /dev/null +++ b/modules/ppcp-paypal-subscriptions/extensions.php @@ -0,0 +1,14 @@ + static function ( ContainerInterface $container ): DeactivatePlanEndpoint { + return new DeactivatePlanEndpoint( + $container->get( 'button.request-data' ), + $container->get( 'api.endpoint.billing-plans' ) + ); + }, + 'paypal-subscriptions.api-handler' => static function( ContainerInterface $container ): SubscriptionsApiHandler { + return new SubscriptionsApiHandler( + $container->get( 'api.endpoint.catalog-products' ), + $container->get( 'api.factory.product' ), + $container->get( 'api.endpoint.billing-plans' ), + $container->get( 'api.factory.billing-cycle' ), + $container->get( 'api.factory.payment-preferences' ), + $container->get( 'api.shop.currency' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, + 'paypal-subscriptions.module.url' => static function ( ContainerInterface $container ): string { + /** + * The path cannot be false. + * + * @psalm-suppress PossiblyFalseArgument + */ + return plugins_url( + '/modules/ppcp-paypal-subscriptions/', + dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' + ); + }, +); diff --git a/modules/ppcp-wc-subscriptions/src/DeactivatePlanEndpoint.php b/modules/ppcp-paypal-subscriptions/src/DeactivatePlanEndpoint.php similarity index 97% rename from modules/ppcp-wc-subscriptions/src/DeactivatePlanEndpoint.php rename to modules/ppcp-paypal-subscriptions/src/DeactivatePlanEndpoint.php index 922a0347b..4bbdc4e28 100644 --- a/modules/ppcp-wc-subscriptions/src/DeactivatePlanEndpoint.php +++ b/modules/ppcp-paypal-subscriptions/src/DeactivatePlanEndpoint.php @@ -7,7 +7,7 @@ declare( strict_types=1 ); -namespace WooCommerce\PayPalCommerce\WcSubscriptions; +namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; use Exception; use WC_Product; diff --git a/modules/ppcp-wc-subscriptions/src/SubscriptionModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php similarity index 70% rename from modules/ppcp-wc-subscriptions/src/SubscriptionModule.php rename to modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 73340b652..800660fbf 100644 --- a/modules/ppcp-wc-subscriptions/src/SubscriptionModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -1,49 +1,40 @@ renew( $order, $c ); - }, - 10, - 2 - ); - - add_action( - 'woocommerce_scheduled_subscription_payment_' . CreditCardGateway::ID, - function ( $amount, $order ) use ( $c ) { - $this->renew( $order, $c ); - }, - 10, - 2 - ); - - add_action( - 'woocommerce_subscription_payment_complete', - function ( $subscription ) use ( $c ) { - $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( $paypal_subscription_id ) { - return; - } - - $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - - $this->add_payment_token_id( $subscription, $payment_token_repository, $logger ); - - if ( count( $subscription->get_related_orders() ) === 1 ) { - $parent_order = $subscription->get_parent(); - if ( is_a( $parent_order, WC_Order::class ) ) { - $order_repository = $c->get( 'api.repository.order' ); - $order = $order_repository->for_wc_order( $parent_order ); - $transaction_id = $this->get_paypal_order_transaction_id( $order ); - if ( $transaction_id ) { - $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id ); - $subscription->save(); - } - } - } - } - ); - - add_filter( - 'woocommerce_gateway_description', - function ( $description, $id ) use ( $c ) { - $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); - $settings = $c->get( 'wcgateway.settings' ); - $subscription_helper = $c->get( 'subscription.helper' ); - - return $this->display_saved_paypal_payments( $settings, (string) $id, $payment_token_repository, (string) $description, $subscription_helper ); - }, - 10, - 2 - ); - - add_filter( - 'woocommerce_credit_card_form_fields', - function ( $default_fields, $id ) use ( $c ) { - $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); - $settings = $c->get( 'wcgateway.settings' ); - $subscription_helper = $c->get( 'subscription.helper' ); - - return $this->display_saved_credit_cards( $settings, $id, $payment_token_repository, $default_fields, $subscription_helper ); - }, - 20, - 2 - ); - - add_filter( - 'ppcp_create_order_request_body_data', - function( array $data ) use ( $c ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); - - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) ); - if ( ! $subscription_id ) { - return $data; - } - $subscription = wc_get_order( $subscription_id ); - if ( ! is_a( $subscription, WC_Subscription::class ) ) { - return $data; - } - - if ( - $wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID - && isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN' - && isset( $data['payment_source']['token']['source']->card ) - ) { - $data['payment_source'] = array( - 'card' => array( - 'vault_id' => $data['payment_source']['token']['id'], - 'stored_credential' => array( - 'payment_initiator' => 'MERCHANT', - 'payment_type' => 'RECURRING', - 'usage' => 'SUBSEQUENT', - ), - ), - ); - - $previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' ); - if ( $previous_transaction_reference ) { - $data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference; - } - } - - return $data; - } - ); - - $this->subscriptions_api_integration( $c ); - - add_action( - 'admin_enqueue_scripts', - /** - * Param types removed to avoid third-party issues. - * - * @psalm-suppress MissingClosureParamType - */ - function( $hook ) use ( $c ) { - if ( ! is_string( $hook ) ) { - return; - } - $settings = $c->get( 'wcgateway.settings' ); - $subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; - if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) { - return; - } - - //phpcs:disable WordPress.Security.NonceVerification.Recommended - $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) ); - $product = wc_get_product( $post_id ); - if ( ! ( is_a( $product, WC_Product::class ) ) ) { - return; - } - - $subscriptions_helper = $c->get( 'subscription.helper' ); - assert( $subscriptions_helper instanceof SubscriptionHelper ); - - if ( - ! $subscriptions_helper->plugin_is_active() - || ! ( - is_a( $product, WC_Product_Subscription::class ) - || is_a( $product, WC_Product_Variable_Subscription::class ) - || is_a( $product, WC_Product_Subscription_Variation::class ) - ) - || ! WC_Subscriptions_Product::is_subscription( $product ) - ) { - return; - } - - $module_url = $c->get( 'subscription.module.url' ); - wp_enqueue_script( - 'ppcp-paypal-subscription', - untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js', - array( 'jquery' ), - $c->get( 'ppcp.asset-version' ), - true - ); - - $products = array( $this->set_product_config( $product ) ); - if ( $product->get_type() === 'variable-subscription' ) { - $products = array(); - - /** - * Suppress pslam. - * - * @psalm-suppress TypeDoesNotContainType - * - * WC_Product_Variable_Subscription extends WC_Product_Variable. - */ - assert( $product instanceof WC_Product_Variable ); - $available_variations = $product->get_available_variations(); - foreach ( $available_variations as $variation ) { - /** - * The method is defined in WooCommerce. - * - * @psalm-suppress UndefinedMethod - */ - $variation = wc_get_product_object( 'variation', $variation['variation_id'] ); - $products[] = $this->set_product_config( $variation ); - } - } - - wp_localize_script( - 'ppcp-paypal-subscription', - 'PayPalCommerceGatewayPayPalSubscriptionProducts', - $products - ); - } - ); - - $endpoint = $c->get( 'subscription.deactivate-plan-endpoint' ); - assert( $endpoint instanceof DeactivatePlanEndpoint ); - add_action( - 'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT, - array( $endpoint, 'handle_request' ) - ); - - add_action( - 'add_meta_boxes', - /** - * Param types removed to avoid third-party issues. - * - * @psalm-suppress MissingClosureParamType - */ - function( string $post_type, $post_or_order_object ) use ( $c ) { - if ( ! function_exists( 'wcs_get_subscription' ) ) { - return; - } - - $order = ( $post_or_order_object instanceof WP_Post ) - ? wc_get_order( $post_or_order_object->ID ) - : $post_or_order_object; - - if ( ! is_a( $order, WC_Order::class ) ) { - return; - } - - $subscription = wcs_get_subscription( $order->get_id() ); - if ( ! is_a( $subscription, WC_Subscription::class ) ) { - return; - } - - $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( ! $subscription_id ) { - return; - } - - $screen_id = wc_get_page_screen_id( 'shop_subscription' ); - remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' ); - - $environment = $c->get( 'onboarding.environment' ); - add_meta_box( - 'ppcp_paypal_subscription', - __( 'PayPal Subscription', 'woocommerce-paypal-payments' ), - function() use ( $subscription_id, $environment ) { - $host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com'; - $url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id; - echo '

' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '

'; - echo '

' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . ' ' . esc_attr( $subscription_id ) . '

'; - }, - $post_type, - 'side' - ); - - }, - 30, - 2 - ); - - add_action( - 'action_scheduler_before_execute', - /** - * Param types removed to avoid third-party issues. - * - * @psalm-suppress MissingClosureParamType - */ - function( $action_id ) { - /** - * Class exist in WooCommerce. - * - * @psalm-suppress UndefinedClass - */ - $store = ActionScheduler_Store::instance(); - $action = $store->fetch_action( $action_id ); - - $subscription_id = $action->get_args()['subscription_id'] ?? null; - if ( $subscription_id ) { - $subscription = wcs_get_subscription( $subscription_id ); - if ( is_a( $subscription, WC_Subscription::class ) ) { - $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( $paypal_subscription_id ) { - as_unschedule_action( $action->get_hook(), $action->get_args() ); - } - } - } - } - ); - } - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } - - /** - * Handles a Subscription product renewal. - * - * @param \WC_Order $order WooCommerce order. - * @param ContainerInterface|null $container The container. - * @return void - */ - protected function renew( $order, $container ) { - if ( ! ( $order instanceof \WC_Order ) ) { - return; - } - - $handler = $container->get( 'subscription.renewal-handler' ); - $handler->renew( $order ); - } - - /** - * Adds Payment token ID to subscription. - * - * @param \WC_Subscription $subscription The subscription. - * @param PaymentTokenRepository $payment_token_repository The payment repository. - * @param LoggerInterface $logger The logger. - */ - protected function add_payment_token_id( - \WC_Subscription $subscription, - PaymentTokenRepository $payment_token_repository, - LoggerInterface $logger - ) { - try { - $tokens = $payment_token_repository->all_for_user_id( $subscription->get_customer_id() ); - if ( $tokens ) { - $latest_token_id = end( $tokens )->id() ? end( $tokens )->id() : ''; - $subscription->update_meta_data( 'payment_token_id', $latest_token_id ); - $subscription->save(); - } - } catch ( RuntimeException $error ) { - $message = sprintf( - // translators: %1$s is the payment token Id, %2$s is the error message. - __( - 'Could not add token Id to subscription %1$s: %2$s', - 'woocommerce-paypal-payments' - ), - $subscription->get_id(), - $error->getMessage() - ); - - $logger->log( 'warning', $message ); - } - } - - /** - * Displays saved PayPal payments. - * - * @param Settings $settings The settings. - * @param string $id The payment gateway Id. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param string $description The payment gateway description. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @return string - */ - protected function display_saved_paypal_payments( - Settings $settings, - string $id, - PaymentTokenRepository $payment_token_repository, - string $description, - SubscriptionHelper $subscription_helper - ): string { - if ( $settings->has( 'vault_enabled' ) - && $settings->get( 'vault_enabled' ) - && PayPalGateway::ID === $id - && $subscription_helper->is_subscription_change_payment() - ) { - $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); - if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) { - return esc_html__( - 'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.', - 'woocommerce-paypal-payments' - ); - } - - $output = sprintf( - '

'; - - return $output; - } - - return $description; - } - - /** - * Displays saved credit cards. - * - * @param Settings $settings The settings. - * @param string $id The payment gateway Id. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param array $default_fields Default payment gateway fields. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @return array|mixed|string - * @throws NotFoundException When setting was not found. - */ - protected function display_saved_credit_cards( - Settings $settings, - string $id, - PaymentTokenRepository $payment_token_repository, - array $default_fields, - SubscriptionHelper $subscription_helper - ) { - - if ( $settings->has( 'vault_enabled_dcc' ) - && $settings->get( 'vault_enabled_dcc' ) - && $subscription_helper->is_subscription_change_payment() - && CreditCardGateway::ID === $id - ) { - $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); - if ( ! $tokens || ! $payment_token_repository->tokens_contains_card( $tokens ) ) { - $default_fields = array(); - $default_fields['saved-credit-card'] = esc_html__( - 'No Credit Card saved, in order to use a saved Credit Card you first need to create it through a purchase.', - 'woocommerce-paypal-payments' - ); - return $default_fields; - } - - $output = sprintf( - '

'; - - $default_fields = array(); - $default_fields['saved-credit-card'] = $output; - return $default_fields; - } - - return $default_fields; - } - - /** - * Adds PayPal subscriptions API integration. - * - * @param ContainerInterface $c The container. - * @return void - * @throws Exception When something went wrong. - */ - protected function subscriptions_api_integration( ContainerInterface $c ): void { add_action( 'save_post', /** @@ -549,7 +80,7 @@ class SubscriptionModule implements ModuleInterface { return; } - $subscriptions_api_handler = $c->get( 'subscription.api-handler' ); + $subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' ); assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler ); $this->update_subscription_product_meta( $product, $subscriptions_api_handler ); }, @@ -566,7 +97,7 @@ class SubscriptionModule implements ModuleInterface { function( $variation_id ) use ( $c ) { $wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) ); - $subscriptions_helper = $c->get( 'subscription.helper' ); + $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); if ( @@ -583,7 +114,7 @@ class SubscriptionModule implements ModuleInterface { return; } - $subscriptions_api_handler = $c->get( 'subscription.api-handler' ); + $subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' ); assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler ); $this->update_subscription_product_meta( $product, $subscriptions_api_handler ); }, @@ -942,6 +473,236 @@ class SubscriptionModule implements ModuleInterface { 10, 3 ); + + add_action( + 'admin_enqueue_scripts', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + function( $hook ) use ( $c ) { + if ( ! is_string( $hook ) ) { + return; + } + $settings = $c->get( 'wcgateway.settings' ); + $subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; + if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) { + return; + } + + //phpcs:disable WordPress.Security.NonceVerification.Recommended + $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) ); + $product = wc_get_product( $post_id ); + if ( ! ( is_a( $product, WC_Product::class ) ) ) { + return; + } + + $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); + assert( $subscriptions_helper instanceof SubscriptionHelper ); + + if ( + ! $subscriptions_helper->plugin_is_active() + || ! ( + is_a( $product, WC_Product_Subscription::class ) + || is_a( $product, WC_Product_Variable_Subscription::class ) + || is_a( $product, WC_Product_Subscription_Variation::class ) + ) + || ! WC_Subscriptions_Product::is_subscription( $product ) + ) { + return; + } + + $module_url = $c->get( 'paypal-subscriptions.module.url' ); + wp_enqueue_script( + 'ppcp-paypal-subscription', + untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js', + array( 'jquery' ), + $c->get( 'ppcp.asset-version' ), + true + ); + + $products = array( $this->set_product_config( $product ) ); + if ( $product->get_type() === 'variable-subscription' ) { + $products = array(); + + /** + * Suppress pslam. + * + * @psalm-suppress TypeDoesNotContainType + * + * WC_Product_Variable_Subscription extends WC_Product_Variable. + */ + assert( $product instanceof WC_Product_Variable ); + $available_variations = $product->get_available_variations(); + foreach ( $available_variations as $variation ) { + /** + * The method is defined in WooCommerce. + * + * @psalm-suppress UndefinedMethod + */ + $variation = wc_get_product_object( 'variation', $variation['variation_id'] ); + $products[] = $this->set_product_config( $variation ); + } + } + + wp_localize_script( + 'ppcp-paypal-subscription', + 'PayPalCommerceGatewayPayPalSubscriptionProducts', + $products + ); + } + ); + + $endpoint = $c->get( 'paypal-subscriptions.deactivate-plan-endpoint' ); + assert( $endpoint instanceof DeactivatePlanEndpoint ); + add_action( + 'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT, + array( $endpoint, 'handle_request' ) + ); + + add_action( + 'add_meta_boxes', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + function( string $post_type, $post_or_order_object ) use ( $c ) { + if ( ! function_exists( 'wcs_get_subscription' ) ) { + return; + } + + $order = ( $post_or_order_object instanceof WP_Post ) + ? wc_get_order( $post_or_order_object->ID ) + : $post_or_order_object; + + if ( ! is_a( $order, WC_Order::class ) ) { + return; + } + + $subscription = wcs_get_subscription( $order->get_id() ); + if ( ! is_a( $subscription, WC_Subscription::class ) ) { + return; + } + + $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; + if ( ! $subscription_id ) { + return; + } + + $screen_id = wc_get_page_screen_id( 'shop_subscription' ); + remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' ); + + $environment = $c->get( 'onboarding.environment' ); + add_meta_box( + 'ppcp_paypal_subscription', + __( 'PayPal Subscription', 'woocommerce-paypal-payments' ), + function() use ( $subscription_id, $environment ) { + $host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com'; + $url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id; + echo '

' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '

'; + echo '

' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . ' ' . esc_attr( $subscription_id ) . '

'; + }, + $post_type, + 'side' + ); + + }, + 30, + 2 + ); + + add_action( + 'action_scheduler_before_execute', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + function( $action_id ) { + /** + * Class exist in WooCommerce. + * + * @psalm-suppress UndefinedClass + */ + $store = ActionScheduler_Store::instance(); + $action = $store->fetch_action( $action_id ); + + $subscription_id = $action->get_args()['subscription_id'] ?? null; + if ( $subscription_id ) { + $subscription = wcs_get_subscription( $subscription_id ); + if ( is_a( $subscription, WC_Subscription::class ) ) { + $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; + if ( $paypal_subscription_id ) { + as_unschedule_action( $action->get_hook(), $action->get_args() ); + } + } + } + } + ); + } + + /** + * Updates subscription product meta. + * + * @param WC_Product $product The product. + * @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler. + * @return void + */ + 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 ); + $product->save(); + + if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) { + 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 ); + return; + } + + if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) { + $subscriptions_api_handler->create_product( $product ); + } + + if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) { + // phpcs:ignore WordPress.Security.NonceVerification + $subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) ); + if ( ! is_string( $subscription_plan_name ) ) { + return; + } + + $product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name ); + $product->save(); + + $subscriptions_api_handler->create_plan( $subscription_plan_name, $product ); + } + } + } + + /** + * Returns subscription product configuration. + * + * @param WC_Product $product The product. + * @return array + */ + private function set_product_config( WC_Product $product ): array { + $plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array(); + $plan_id = $plan['id'] ?? ''; + + return array( + 'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '', + 'plan_id' => $plan_id, + 'product_id' => $product->get_id(), + 'ajax' => array( + 'deactivate_plan' => array( + 'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ), + ), + ), + ); } /** @@ -1014,66 +775,4 @@ class SubscriptionModule implements ModuleInterface { ); } } - - /** - * Updates subscription product meta. - * - * @param WC_Product $product The product. - * @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler. - * @return void - */ - 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 ); - $product->save(); - - if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) { - 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 ); - return; - } - - if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) { - $subscriptions_api_handler->create_product( $product ); - } - - if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) { - // phpcs:ignore WordPress.Security.NonceVerification - $subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) ); - if ( ! is_string( $subscription_plan_name ) ) { - return; - } - - $product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name ); - $product->save(); - - $subscriptions_api_handler->create_plan( $subscription_plan_name, $product ); - } - } - } - - /** - * Returns subscription product configuration. - * - * @param WC_Product $product The product. - * @return array - */ - private function set_product_config( WC_Product $product ): array { - $plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array(); - $plan_id = $plan['id'] ?? ''; - - return array( - 'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '', - 'plan_id' => $plan_id, - 'product_id' => $product->get_id(), - 'ajax' => array( - 'deactivate_plan' => array( - 'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ), - 'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ), - ), - ), - ); - } } diff --git a/modules/ppcp-wc-subscriptions/src/SubscriptionsApiHandler.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php similarity index 99% rename from modules/ppcp-wc-subscriptions/src/SubscriptionsApiHandler.php rename to modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php index b03560d9a..4d56558ab 100644 --- a/modules/ppcp-wc-subscriptions/src/SubscriptionsApiHandler.php +++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionsApiHandler.php @@ -7,7 +7,7 @@ declare(strict_types=1); -namespace WooCommerce\PayPalCommerce\WcSubscriptions; +namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; use Psr\Log\LoggerInterface; use WC_Product; diff --git a/modules/ppcp-wc-subscriptions/webpack.config.js b/modules/ppcp-paypal-subscriptions/webpack.config.js similarity index 100% rename from modules/ppcp-wc-subscriptions/webpack.config.js rename to modules/ppcp-paypal-subscriptions/webpack.config.js diff --git a/modules/ppcp-wc-subscriptions/yarn.lock b/modules/ppcp-paypal-subscriptions/yarn.lock similarity index 100% rename from modules/ppcp-wc-subscriptions/yarn.lock rename to modules/ppcp-paypal-subscriptions/yarn.lock diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index dcaa0bd89..5e9966dcb 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -14,6 +14,9 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +/** + * Class SavePaymentMethodsModule + */ class SavePaymentMethodsModule implements ModuleInterface { /** @@ -26,5 +29,8 @@ class SavePaymentMethodsModule implements ModuleInterface { ); } - public function run(ContainerInterface $c): void {} + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): void {} } diff --git a/modules/ppcp-saved-payment-checker/extensions.php b/modules/ppcp-saved-payment-checker/extensions.php index c392ea6da..e712a03fc 100644 --- a/modules/ppcp-saved-payment-checker/extensions.php +++ b/modules/ppcp-saved-payment-checker/extensions.php @@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); $insert_after = function( array $array, string $key, array $new ): array { diff --git a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php index 5c2731336..6a6ae3c79 100644 --- a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php +++ b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php @@ -43,7 +43,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { add_filter( 'woocommerce_paypal_payments_order_intent', function( string $intent ) use ( $c ) { - $subscription_helper = $c->get( 'subscription.helper' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->current_product_is_subscription() ) { @@ -60,7 +60,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { add_action( 'woocommerce_paypal_payments_before_handle_payment_success', function( WC_Order $wc_order ) use ( $c ) { - $subscription_helper = $c->get( 'subscription.helper' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); if ( $subscription_helper->has_subscription( $wc_order->get_id() ) ) { @@ -93,7 +93,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { add_action( 'woocommerce_email_before_order_table', function( WC_Order $order ) use ( $c ) { - $subscription_helper = $c->get( 'subscription.helper' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); $logger = $c->get( 'woocommerce.logger.woocommerce' ); assert( $logger instanceof LoggerInterface ); @@ -119,7 +119,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { add_action( 'woocommerce_email_after_order_table', function( WC_Order $order ) use ( $c ) { - $subscription_helper = $c->get( 'subscription.helper' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); $logger = $c->get( 'woocommerce.logger.woocommerce' ); assert( $logger instanceof LoggerInterface ); diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index ceef8301d..f26412778 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -65,7 +65,7 @@ class StatusReportModule implements ModuleInterface { $messages_apply = $c->get( 'button.helper.messages-apply' ); /* @var SubscriptionHelper $subscription_helper The subscription helper class. */ - $subscription_helper = $c->get( 'subscription.helper' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); $last_webhook_storage = $c->get( 'webhook.last-webhook-storage' ); assert( $last_webhook_storage instanceof WebhookEventStorage ); diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php index 101e0bb4c..44d042a7e 100644 --- a/modules/ppcp-vaulting/services.php +++ b/modules/ppcp-vaulting/services.php @@ -31,7 +31,7 @@ return array( }, 'vaulting.credit-card-handler' => function( ContainerInterface $container ): VaultedCreditCardHandler { return new VaultedCreditCardHandler( - $container->get( 'subscription.helper' ), + $container->get( 'wc-subscriptions.helper' ), $container->get( 'vaulting.repository.payment-token' ), $container->get( 'api.factory.purchase-unit' ), $container->get( 'api.factory.payer' ), diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 4da9a32bb..ea7b74f47 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -51,7 +51,7 @@ class VaultingModule implements ModuleInterface { $listener->listen(); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); add_action( 'woocommerce_created_customer', function( int $customer_id ) use ( $subscription_helper ) { diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 48b474191..e048c0504 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -76,7 +76,7 @@ return array( $refund_processor = $container->get( 'wcgateway.processor.refunds' ); $state = $container->get( 'onboarding.state' ); $transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); $page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' ); $payment_token_repository = $container->get( 'vaulting.repository.payment-token' ); $environment = $container->get( 'onboarding.environment' ); @@ -109,7 +109,7 @@ return array( $refund_processor = $container->get( 'wcgateway.processor.refunds' ); $state = $container->get( 'onboarding.state' ); $transaction_url_provider = $container->get( 'wcgateway.transaction-url-provider' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); $payments_endpoint = $container->get( 'api.endpoint.payments' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); $vaulted_credit_card_handler = $container->get( 'vaulting.credit-card-handler' ); @@ -137,7 +137,7 @@ return array( $container->get( 'wcgateway.processor.refunds' ), $container->get( 'onboarding.state' ), $container->get( 'wcgateway.transaction-url-provider' ), - $container->get( 'subscription.helper' ), + $container->get( 'wc-subscriptions.helper' ), $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ), $container->get( 'onboarding.environment' ), $container->get( 'vaulting.repository.payment-token' ), @@ -148,7 +148,7 @@ return array( $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); $settings_status = $container->get( 'wcgateway.settings.status' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); return new DisableGateways( $session_handler, $settings, $settings_status, $subscription_helper ); }, @@ -331,7 +331,7 @@ return array( $settings = $container->get( 'wcgateway.settings' ); $environment = $container->get( 'onboarding.environment' ); $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); $order_helper = $container->get( 'api.order-helper' ); return new OrderProcessor( $session_handler, @@ -359,7 +359,7 @@ return array( $logger = $container->get( 'woocommerce.logger.woocommerce' ); $notice = $container->get( 'wcgateway.notice.authorize-order-action' ); $settings = $container->get( 'wcgateway.settings' ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); return new AuthorizedPaymentsProcessor( $order_endpoint, $payments_endpoint, @@ -439,7 +439,7 @@ return array( $onboarding_options_renderer = $container->get( 'onboarding.render-options' ); assert( $onboarding_options_renderer instanceof OnboardingOptionsRenderer ); - $subscription_helper = $container->get( 'subscription.helper' ); + $subscription_helper = $container->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); $fields = array( diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 5214a4166..2784635f7 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -177,7 +177,7 @@ class WCGatewayModule implements ModuleInterface { $assets = new SettingsPageAssets( $c->get( 'wcgateway.url' ), $c->get( 'ppcp.asset-version' ), - $c->get( 'subscription.helper' ), + $c->get( 'wc-subscriptions.helper' ), $c->get( 'button.client_id_for_admin' ), $c->get( 'api.shop.currency' ), $c->get( 'api.shop.country' ), diff --git a/modules/ppcp-wc-subscriptions/module.php b/modules/ppcp-wc-subscriptions/module.php index 7a66fe031..a298bac91 100644 --- a/modules/ppcp-wc-subscriptions/module.php +++ b/modules/ppcp-wc-subscriptions/module.php @@ -12,5 +12,5 @@ namespace WooCommerce\PayPalCommerce\WcSubscriptions; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; return static function (): ModuleInterface { - return new SubscriptionModule(); + return new WcSubscriptionsModule(); }; diff --git a/modules/ppcp-wc-subscriptions/services.php b/modules/ppcp-wc-subscriptions/services.php index 469c34d1b..47aaf1772 100644 --- a/modules/ppcp-wc-subscriptions/services.php +++ b/modules/ppcp-wc-subscriptions/services.php @@ -9,15 +9,17 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcSubscriptions; -use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\PayPalSubscriptions\DeactivatePlanEndpoint; +use WooCommerce\PayPalCommerce\PayPalSubscriptions\SubscriptionsApiHandler; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; return array( - 'subscription.helper' => static function ( ContainerInterface $container ): SubscriptionHelper { + 'wc-subscriptions.helper' => static function ( ContainerInterface $container ): SubscriptionHelper { return new SubscriptionHelper( $container->get( 'wcgateway.settings' ) ); }, - 'subscription.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler { + 'wc-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler { $logger = $container->get( 'woocommerce.logger.woocommerce' ); $repository = $container->get( 'vaulting.repository.payment-token' ); $endpoint = $container->get( 'api.endpoint.order' ); @@ -38,37 +40,9 @@ return array( $authorized_payments_processor ); }, - 'subscription.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { + 'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { $factory = $container->get( 'api.factory.payment-token' ); $endpoint = $container->get( 'api.endpoint.payment-token' ); return new PaymentTokenRepository( $factory, $endpoint ); }, - 'subscription.api-handler' => static function( ContainerInterface $container ): SubscriptionsApiHandler { - return new SubscriptionsApiHandler( - $container->get( 'api.endpoint.catalog-products' ), - $container->get( 'api.factory.product' ), - $container->get( 'api.endpoint.billing-plans' ), - $container->get( 'api.factory.billing-cycle' ), - $container->get( 'api.factory.payment-preferences' ), - $container->get( 'api.shop.currency' ), - $container->get( 'woocommerce.logger.woocommerce' ) - ); - }, - 'subscription.module.url' => static function ( ContainerInterface $container ): string { - /** - * The path cannot be false. - * - * @psalm-suppress PossiblyFalseArgument - */ - return plugins_url( - '/modules/ppcp-subscription/', - dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' - ); - }, - 'subscription.deactivate-plan-endpoint' => static function ( ContainerInterface $container ): DeactivatePlanEndpoint { - return new DeactivatePlanEndpoint( - $container->get( 'button.request-data' ), - $container->get( 'api.endpoint.billing-plans' ) - ); - }, ); diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php new file mode 100644 index 000000000..7ddc2e84e --- /dev/null +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -0,0 +1,330 @@ +renew( $order, $c ); + }, + 10, + 2 + ); + + add_action( + 'woocommerce_scheduled_subscription_payment_' . CreditCardGateway::ID, + function ( $amount, $order ) use ( $c ) { + $this->renew( $order, $c ); + }, + 10, + 2 + ); + + add_action( + 'woocommerce_subscription_payment_complete', + function ( $subscription ) use ( $c ) { + $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; + if ( $paypal_subscription_id ) { + return; + } + + $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); + $logger = $c->get( 'woocommerce.logger.woocommerce' ); + + $this->add_payment_token_id( $subscription, $payment_token_repository, $logger ); + + if ( count( $subscription->get_related_orders() ) === 1 ) { + $parent_order = $subscription->get_parent(); + if ( is_a( $parent_order, WC_Order::class ) ) { + $order_repository = $c->get( 'api.repository.order' ); + $order = $order_repository->for_wc_order( $parent_order ); + $transaction_id = $this->get_paypal_order_transaction_id( $order ); + if ( $transaction_id ) { + $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id ); + $subscription->save(); + } + } + } + } + ); + + add_filter( + 'woocommerce_gateway_description', + function ( $description, $id ) use ( $c ) { + $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); + $settings = $c->get( 'wcgateway.settings' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); + + return $this->display_saved_paypal_payments( $settings, (string) $id, $payment_token_repository, (string) $description, $subscription_helper ); + }, + 10, + 2 + ); + + add_filter( + 'woocommerce_credit_card_form_fields', + function ( $default_fields, $id ) use ( $c ) { + $payment_token_repository = $c->get( 'vaulting.repository.payment-token' ); + $settings = $c->get( 'wcgateway.settings' ); + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); + + return $this->display_saved_credit_cards( $settings, $id, $payment_token_repository, $default_fields, $subscription_helper ); + }, + 20, + 2 + ); + + add_filter( + 'ppcp_create_order_request_body_data', + function( array $data ) use ( $c ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) ); + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) ); + if ( ! $subscription_id ) { + return $data; + } + $subscription = wc_get_order( $subscription_id ); + if ( ! is_a( $subscription, WC_Subscription::class ) ) { + return $data; + } + + if ( + $wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID + && isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN' + && isset( $data['payment_source']['token']['source']->card ) + ) { + $data['payment_source'] = array( + 'card' => array( + 'vault_id' => $data['payment_source']['token']['id'], + 'stored_credential' => array( + 'payment_initiator' => 'MERCHANT', + 'payment_type' => 'RECURRING', + 'usage' => 'SUBSEQUENT', + ), + ), + ); + + $previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' ); + if ( $previous_transaction_reference ) { + $data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference; + } + } + + return $data; + } + ); + } + + /** + * Returns the key for the module. + * + * @return string|void + */ + public function getKey() { + } + + /** + * Handles a Subscription product renewal. + * + * @param \WC_Order $order WooCommerce order. + * @param ContainerInterface|null $container The container. + * @return void + */ + protected function renew( $order, $container ) { + if ( ! ( $order instanceof \WC_Order ) ) { + return; + } + + $handler = $container->get( 'wc-subscriptions.renewal-handler' ); + $handler->renew( $order ); + } + + /** + * Adds Payment token ID to subscription. + * + * @param \WC_Subscription $subscription The subscription. + * @param PaymentTokenRepository $payment_token_repository The payment repository. + * @param LoggerInterface $logger The logger. + */ + protected function add_payment_token_id( + \WC_Subscription $subscription, + PaymentTokenRepository $payment_token_repository, + LoggerInterface $logger + ) { + try { + $tokens = $payment_token_repository->all_for_user_id( $subscription->get_customer_id() ); + if ( $tokens ) { + $latest_token_id = end( $tokens )->id() ? end( $tokens )->id() : ''; + $subscription->update_meta_data( 'payment_token_id', $latest_token_id ); + $subscription->save(); + } + } catch ( RuntimeException $error ) { + $message = sprintf( + // translators: %1$s is the payment token Id, %2$s is the error message. + __( + 'Could not add token Id to subscription %1$s: %2$s', + 'woocommerce-paypal-payments' + ), + $subscription->get_id(), + $error->getMessage() + ); + + $logger->log( 'warning', $message ); + } + } + + /** + * Displays saved PayPal payments. + * + * @param Settings $settings The settings. + * @param string $id The payment gateway Id. + * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param string $description The payment gateway description. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @return string + */ + protected function display_saved_paypal_payments( + Settings $settings, + string $id, + PaymentTokenRepository $payment_token_repository, + string $description, + SubscriptionHelper $subscription_helper + ): string { + if ( $settings->has( 'vault_enabled' ) + && $settings->get( 'vault_enabled' ) + && PayPalGateway::ID === $id + && $subscription_helper->is_subscription_change_payment() + ) { + $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); + if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) { + return esc_html__( + 'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.', + 'woocommerce-paypal-payments' + ); + } + + $output = sprintf( + '

'; + + return $output; + } + + return $description; + } + + /** + * Displays saved credit cards. + * + * @param Settings $settings The settings. + * @param string $id The payment gateway Id. + * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param array $default_fields Default payment gateway fields. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @return array|mixed|string + * @throws NotFoundException When setting was not found. + */ + protected function display_saved_credit_cards( + Settings $settings, + string $id, + PaymentTokenRepository $payment_token_repository, + array $default_fields, + SubscriptionHelper $subscription_helper + ) { + + if ( $settings->has( 'vault_enabled_dcc' ) + && $settings->get( 'vault_enabled_dcc' ) + && $subscription_helper->is_subscription_change_payment() + && CreditCardGateway::ID === $id + ) { + $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() ); + if ( ! $tokens || ! $payment_token_repository->tokens_contains_card( $tokens ) ) { + $default_fields = array(); + $default_fields['saved-credit-card'] = esc_html__( + 'No Credit Card saved, in order to use a saved Credit Card you first need to create it through a purchase.', + 'woocommerce-paypal-payments' + ); + return $default_fields; + } + + $output = sprintf( + '

'; + + $default_fields = array(); + $default_fields['saved-credit-card'] = $output; + return $default_fields; + } + + return $default_fields; + } +} diff --git a/package.json b/package.json index bb8005908..c18090e0f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install", "install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install", "install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install", - "install:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn install", + "install:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn install", "install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install", "install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install", "install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install", @@ -27,7 +27,7 @@ "build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build", "build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build", "build:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run build", - "build:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn run build", + "build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build", "build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build", "build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build", "build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build", @@ -39,7 +39,7 @@ "watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch", "watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch", "watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch", - "watch:modules:ppcp-wc-subscriptions": "cd modules/ppcp-wc-subscriptions && yarn run watch", + "watch:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run watch", "watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch", "watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch", "watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",