From b066fc7ccf752eb15d5a0dac736a492d0073ac57 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 17 Jul 2023 15:13:25 +0200 Subject: [PATCH] Add subscription support for variable subscriptions --- .../resources/js/paypal-subscription.js | 111 +++++++------- .../src/SubscriptionModule.php | 136 ++++++++++-------- 2 files changed, 137 insertions(+), 110 deletions(-) diff --git a/modules/ppcp-subscription/resources/js/paypal-subscription.js b/modules/ppcp-subscription/resources/js/paypal-subscription.js index c42ca2597..a00bf57a0 100644 --- a/modules/ppcp-subscription/resources/js/paypal-subscription.js +++ b/modules/ppcp-subscription/resources/js/paypal-subscription.js @@ -1,64 +1,73 @@ document.addEventListener( 'DOMContentLoaded', () => { - if(PayPalCommerceGatewayPayPalSubscription.product_connected === 'yes') { - const periodInterval = document.querySelector('#_subscription_period_interval'); - periodInterval.setAttribute('disabled', 'disabled'); + const setupProducts = () => { + PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach((product) => { + if(product.product_connected === 'yes') { + const periodInterval = document.querySelector('#_subscription_period_interval'); + periodInterval.setAttribute('disabled', 'disabled'); - const subscriptionPeriod = document.querySelector('#_subscription_period'); - subscriptionPeriod.setAttribute('disabled', 'disabled'); + const subscriptionPeriod = document.querySelector('#_subscription_period'); + subscriptionPeriod.setAttribute('disabled', 'disabled'); - const subscriptionLength = document.querySelector('._subscription_length_field'); - subscriptionLength.style.display = 'none'; + const subscriptionLength = document.querySelector('._subscription_length_field'); + subscriptionLength.style.display = 'none'; - const subscriptionTrial = document.querySelector('._subscription_trial_length_field'); - subscriptionTrial.style.display = 'none'; - } - - const unlinkBtn = document.getElementById('ppcp_unlink_sub_plan'); - unlinkBtn?.addEventListener('click', (event)=>{ - event.preventDefault(); - unlinkBtn.disabled = true; - const spinner = document.getElementById('spinner-unlink-plan'); - spinner.style.display = 'inline-block'; - - fetch(PayPalCommerceGatewayPayPalSubscription.ajax.deactivate_plan.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'same-origin', - body: JSON.stringify({ - nonce: PayPalCommerceGatewayPayPalSubscription.ajax.deactivate_plan.nonce, - plan_id: PayPalCommerceGatewayPayPalSubscription.ajax.deactivate_plan.plan_id, - product_id: PayPalCommerceGatewayPayPalSubscription.ajax.deactivate_plan.product_id - }) - }).then(function (res) { - return res.json(); - }).then(function (data) { - if (!data.success) { - unlinkBtn.disabled = false; - spinner.style.display = 'none'; - console.error(data); - throw Error(data.data.message); + const subscriptionTrial = document.querySelector('._subscription_trial_length_field'); + subscriptionTrial.style.display = 'none'; } - const enableSubscription = document.getElementById('ppcp-enable-subscription'); - const product = document.getElementById('pcpp-product'); - const plan = document.getElementById('pcpp-plan'); - enableSubscription.style.display = 'none'; - product.style.display = 'none'; - plan.style.display = 'none'; + const unlinkBtn = document.getElementById(`ppcp-unlink-sub-plan-${product.ajax.deactivate_plan.product_id}`); + unlinkBtn?.addEventListener('click', (event)=>{ + event.preventDefault(); + unlinkBtn.disabled = true; + const spinner = document.getElementById('spinner-unlink-plan'); + spinner.style.display = 'inline-block'; - const enable_subscription_product = document.getElementById('ppcp_enable_subscription_product'); - enable_subscription_product.disabled = true; + fetch(product.ajax.deactivate_plan.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + body: JSON.stringify({ + nonce: product.ajax.deactivate_plan.nonce, + plan_id: product.ajax.deactivate_plan.plan_id, + product_id: product.ajax.deactivate_plan.product_id + }) + }).then(function (res) { + return res.json(); + }).then(function (data) { + if (!data.success) { + unlinkBtn.disabled = false; + spinner.style.display = 'none'; + console.error(data); + throw Error(data.data.message); + } - const planUnlinked = document.getElementById('pcpp-plan-unlinked'); - planUnlinked.style.display = 'block'; + const enableSubscription = document.getElementById('ppcp-enable-subscription'); + const product = document.getElementById('pcpp-product'); + const plan = document.getElementById('pcpp-plan'); + enableSubscription.style.display = 'none'; + product.style.display = 'none'; + plan.style.display = 'none'; - setTimeout(() => { - location.reload(); - }, 1000) - }); + const enable_subscription_product = document.getElementById('ppcp_enable_subscription_product'); + enable_subscription_product.disabled = true; + + const planUnlinked = document.getElementById('pcpp-plan-unlinked'); + planUnlinked.style.display = 'block'; + + setTimeout(() => { + location.reload(); + }, 1000) + }); + }); + }) + } + + setupProducts(); + jQuery( '#woocommerce-product-data' ).on('woocommerce_variations_loaded', () => { + setupProducts(); }); }); diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php index d7e39bf7c..5b2978e35 100644 --- a/modules/ppcp-subscription/src/SubscriptionModule.php +++ b/modules/ppcp-subscription/src/SubscriptionModule.php @@ -180,7 +180,7 @@ class SubscriptionModule implements ModuleInterface { //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) || is_a( $product, WC_Product_Subscription_Variation::class) ) || ! WC_Subscriptions_Product::is_subscription( $product ) ) { + if ( ! ( is_a( $product, WC_Product::class ) || is_a( $product, WC_Product_Subscription_Variation::class ) ) || ! WC_Subscriptions_Product::is_subscription( $product ) ) { return; } @@ -193,22 +193,20 @@ class SubscriptionModule implements ModuleInterface { true ); - $plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array(); - $plan_id = $plan['id'] ?? ''; + $products = array( $this->set_product_config( $product ) ); + if ( $product->get_type() === 'variable-subscription' ) { + $products = array(); + $available_variations = $product->get_available_variations(); + foreach ( $available_variations as $variation ) { + $variation = wc_get_product_object( 'variation', $variation['variation_id'] ); + $products[] = $this->set_product_config( $variation ); + } + } + wp_localize_script( 'ppcp-paypal-subscription', - 'PayPalCommerceGatewayPayPalSubscription', - array( - 'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '', - 'ajax' => array( - 'deactivate_plan' => array( - 'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ), - 'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ), - 'plan_id' => $plan_id, - 'product_id' => $product->get_id(), - ), - ), - ) + 'PayPalCommerceGatewayPayPalSubscriptionProducts', + $products ); } ); @@ -465,27 +463,31 @@ class SubscriptionModule implements ModuleInterface { return; } - $subscriptions_api_handler = $c->get('subscription.api-handler'); - assert($subscriptions_api_handler instanceof SubscriptionsApiHandler); - $this->update_subscription_product_meta($product, $subscriptions_api_handler); + $subscriptions_api_handler = $c->get( 'subscription.api-handler' ); + assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler ); + $this->update_subscription_product_meta( $product, $subscriptions_api_handler ); }, 12 ); - add_action('woocommerce_save_product_variation', function($variation_id) use($c){ - if ( ! WC_Subscriptions_Product::is_subscription( $variation_id ) || empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( $_POST['_wcsnonce_save_variations'], 'wcs_subscription_variations' ) ) { - return; - } + add_action( + 'woocommerce_save_product_variation', + function( $variation_id ) use ( $c ) { + $wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) ); + if ( ! WC_Subscriptions_Product::is_subscription( $variation_id ) || empty( $wcsnonce_save_variations ) || ! wp_verify_nonce( $wcsnonce_save_variations, 'wcs_subscription_variations' ) ) { + return; + } - $product = wc_get_product( $variation_id ); - if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) { - return; - } + $product = wc_get_product( $variation_id ); + if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) { + return; + } - $subscriptions_api_handler = $c->get('subscription.api-handler'); - assert($subscriptions_api_handler instanceof SubscriptionsApiHandler); - $this->update_subscription_product_meta($product, $subscriptions_api_handler); - }); + $subscriptions_api_handler = $c->get( 'subscription.api-handler' ); + assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler ); + $this->update_subscription_product_meta( $product, $subscriptions_api_handler ); + } + ); add_action( 'woocommerce_process_shop_subscription_meta', @@ -839,16 +841,7 @@ class SubscriptionModule implements ModuleInterface { * @return void */ private function render_paypal_subscription_fields( WC_Product $product, Environment $environment ): void { - if(is_a($product, WC_Product_Variable_Subscription::class)) { - $variations = $product->get_available_variations(); - foreach ($variations as $variation) { - $a = 1; - // $variation['variation_id'] - } - } - $enable_subscription_product = $product->get_meta( '_ppcp_enable_subscription_product' ); - $subscription_plan_name = $product->get_meta( '_ppcp_subscription_plan_name' ); echo '

'; echo sprintf( @@ -868,14 +861,15 @@ class SubscriptionModule implements ModuleInterface { echo wc_help_tip( esc_html__( 'Create a subscription product and plan to bill customers at regular intervals. Be aware that certain subscription settings cannot be modified once the PayPal Subscription is linked to this product. Unlink the product to edit disabled fields.', 'woocommerce-paypal-payments' ) ); echo '

'; - $subscription_product = $product->get_meta( 'ppcp_subscription_product' ); - $subscription_plan = $product->get_meta( 'ppcp_subscription_plan' ); + $subscription_product = $product->get_meta( 'ppcp_subscription_product' ); + $subscription_plan = $product->get_meta( 'ppcp_subscription_plan' ); + $subscription_plan_name = $product->get_meta( '_ppcp_subscription_plan_name' ); if ( $subscription_product && $subscription_plan ) { if ( $enable_subscription_product !== 'yes' ) { echo sprintf( // translators: %1$s and %2$s are button and wrapper html tags. esc_html__( '%1$sUnlink PayPal Subscription Plan%2$s', 'woocommerce-paypal-payments' ), - '

' ); echo sprintf( @@ -912,38 +906,62 @@ class SubscriptionModule implements ModuleInterface { /** * Updates subscription product meta. * - * @param WC_Product $product - * @param SubscriptionsApiHandler $subscriptions_api_handler + * @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 - { - $enable_subscription_product = wc_clean(wp_unslash($_POST['_ppcp_enable_subscription_product'] ?? '')); - $product->update_meta_data('_ppcp_enable_subscription_product', $enable_subscription_product); + 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); + 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' ) ) { + $subscriptions_api_handler->create_product( $product ); } - if ($product->meta_exists('ppcp_subscription_product') && !$product->meta_exists('ppcp_subscription_plan')) { - $subscription_plan_name = wc_clean(wp_unslash($_POST['_ppcp_subscription_plan_name'] ?? '')); - if (!is_string($subscription_plan_name)) { + 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->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name ); $product->save(); - $subscriptions_api_handler->create_plan($subscription_plan_name, $product); + $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' ) ?? '', + 'ajax' => array( + 'deactivate_plan' => array( + 'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ), + 'plan_id' => $plan_id, + 'product_id' => $product->get_id(), + ), + ), + ); + } }