mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 12:25:15 +08:00
Move PayPal subscriptions api logic to its own module
This commit is contained in:
parent
b3c66f4cbb
commit
94674adc25
29 changed files with 707 additions and 604 deletions
11
modules/ppcp-paypal-subscriptions/.babelrc
Normal file
11
modules/ppcp-paypal-subscriptions/.babelrc
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": "3.25.0"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
3
modules/ppcp-paypal-subscriptions/.gitignore
vendored
Normal file
3
modules/ppcp-paypal-subscriptions/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
assets/js
|
||||
assets/css
|
17
modules/ppcp-paypal-subscriptions/composer.json
Normal file
17
modules/ppcp-paypal-subscriptions/composer.json
Normal file
|
@ -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
|
||||
}
|
14
modules/ppcp-paypal-subscriptions/extensions.php
Normal file
14
modules/ppcp-paypal-subscriptions/extensions.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* The PayPalSubscriptions module extensions.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array();
|
16
modules/ppcp-paypal-subscriptions/module.php
Normal file
16
modules/ppcp-paypal-subscriptions/module.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* The PayPalSubscriptions module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return new PayPalSubscriptionsModule();
|
||||
};
|
31
modules/ppcp-paypal-subscriptions/package.json
Normal file
31
modules/ppcp-paypal-subscriptions/package.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "ppcp-paypal-subscriptions",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"Safari >= 8",
|
||||
"Chrome >= 41",
|
||||
"Firefox >= 43",
|
||||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.1.0",
|
||||
"webpack": "^5.76",
|
||||
"webpack-cli": "^4.10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
|
||||
"dev": "cross-env BABEL_ENV=default webpack --watch"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
const variations = document.querySelector('.woocommerce_variations');
|
||||
const disableFields = (productId) => {
|
||||
if(variations) {
|
||||
const children = variations.children;
|
||||
for(let i=0; i < children.length; i++) {
|
||||
const variableId = children[i].querySelector('h3').getElementsByClassName('variable_post_id')[0].value
|
||||
if (parseInt(variableId) === productId) {
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_period_interval')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_period')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_trial_length')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_trial_period')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_length')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const periodInterval = document.querySelector('#_subscription_period_interval');
|
||||
periodInterval.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 subscriptionTrial = document.querySelector('._subscription_trial_length_field');
|
||||
subscriptionTrial.style.display = 'none';
|
||||
}
|
||||
|
||||
const setupProducts = () => {
|
||||
PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach((product) => {
|
||||
if(product.product_connected === 'yes') {
|
||||
disableFields(product.product_id);
|
||||
}
|
||||
|
||||
const unlinkBtn = document.getElementById(`ppcp-unlink-sub-plan-${product.product_id}`);
|
||||
unlinkBtn?.addEventListener('click', (event)=>{
|
||||
event.preventDefault();
|
||||
unlinkBtn.disabled = true;
|
||||
const spinner = document.getElementById('spinner-unlink-plan');
|
||||
spinner.style.display = 'inline-block';
|
||||
|
||||
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.plan_id,
|
||||
product_id: product.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 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 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();
|
||||
});
|
||||
});
|
43
modules/ppcp-paypal-subscriptions/services.php
Normal file
43
modules/ppcp-paypal-subscriptions/services.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/**
|
||||
* The PayPalSubscriptions module services.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'paypal-subscriptions.deactivate-plan-endpoint' => 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'
|
||||
);
|
||||
},
|
||||
);
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/**
|
||||
* The deactivate Subscription Plan Endpoint.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcSubscriptions
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use Exception;
|
||||
use WC_Product;
|
||||
use WC_Subscriptions_Product;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
|
||||
/**
|
||||
* Class DeactivatePlanEndpoint
|
||||
*/
|
||||
class DeactivatePlanEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-deactivate-plan';
|
||||
|
||||
/**
|
||||
* The request data.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The billing plans.
|
||||
*
|
||||
* @var BillingPlans
|
||||
*/
|
||||
private $billing_plans;
|
||||
|
||||
/**
|
||||
* DeactivatePlanEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The request data.
|
||||
* @param BillingPlans $billing_plans The billing plans.
|
||||
*/
|
||||
public function __construct( RequestData $request_data, BillingPlans $billing_plans ) {
|
||||
$this->request_data = $request_data;
|
||||
$this->billing_plans = $billing_plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_request(): void {
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_send_json_error( 'Not admin.', 403 );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->request_data->read_request( self::ENDPOINT );
|
||||
|
||||
$plan_id = $data['plan_id'] ?? '';
|
||||
if ( $plan_id ) {
|
||||
$this->billing_plans->deactivate_plan( $plan_id );
|
||||
}
|
||||
|
||||
$product_id = $data['product_id'] ?? '';
|
||||
if ( $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( is_a( $product, WC_Product::class ) && WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
$product->delete_meta_data( '_ppcp_enable_subscription_product' );
|
||||
$product->delete_meta_data( '_ppcp_subscription_plan_name' );
|
||||
$product->delete_meta_data( 'ppcp_subscription_product' );
|
||||
$product->delete_meta_data( 'ppcp_subscription_plan' );
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,778 @@
|
|||
<?php
|
||||
/**
|
||||
* The PayPalSubscriptions module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\PayPalSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use ActionScheduler_Store;
|
||||
use WC_Order;
|
||||
use WC_Product;
|
||||
use WC_Product_Subscription;
|
||||
use WC_Product_Subscription_Variation;
|
||||
use WC_Product_Variable;
|
||||
use WC_Product_Variable_Subscription;
|
||||
use WC_Subscription;
|
||||
use WC_Subscriptions_Product;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class SavedPaymentCheckerModule
|
||||
*/
|
||||
class PayPalSubscriptionsModule implements ModuleInterface {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
add_action(
|
||||
'save_post',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $product_id ) use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce = wc_clean( wp_unslash( $_POST['_wcsnonce'] ?? '' ) );
|
||||
if (
|
||||
$subscriptions_mode !== 'subscriptions_api'
|
||||
|| ! is_string( $nonce )
|
||||
|| ! wp_verify_nonce( $nonce, 'wcs_subscription_meta' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( ! is_a( $product, WC_Product::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' );
|
||||
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
|
||||
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
|
||||
},
|
||||
12
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_save_product_variation',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $variation_id ) use ( $c ) {
|
||||
$wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) );
|
||||
|
||||
$subscriptions_helper = $c->get( 'wc-subscriptions.helper' );
|
||||
assert( $subscriptions_helper instanceof SubscriptionHelper );
|
||||
|
||||
if (
|
||||
! $subscriptions_helper->plugin_is_active()
|
||||
|| ! WC_Subscriptions_Product::is_subscription( $variation_id )
|
||||
|| ! is_string( $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;
|
||||
}
|
||||
|
||||
$subscriptions_api_handler = $c->get( 'paypal-subscriptions.api-handler' );
|
||||
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
|
||||
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
|
||||
},
|
||||
30
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_process_shop_subscription_meta',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $id, $post ) use ( $c ) {
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
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 );
|
||||
}
|
||||
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
},
|
||||
20,
|
||||
2
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_order_actions',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $actions, $subscription ): array {
|
||||
if ( ! is_array( $actions ) || ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id && isset( $actions['wcs_process_renewal'] ) ) {
|
||||
unset( $actions['wcs_process_renewal'] );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
},
|
||||
20,
|
||||
2
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'wcs_view_subscription_actions',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $actions, $subscription ): array {
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id && $subscription->get_status() === 'active' ) {
|
||||
$url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'change_subscription_to' => 'cancelled',
|
||||
'ppcp_cancel_subscription' => $subscription->get_id(),
|
||||
)
|
||||
),
|
||||
'ppcp_cancel_subscription_nonce'
|
||||
);
|
||||
|
||||
array_unshift(
|
||||
$actions,
|
||||
array(
|
||||
'url' => esc_url( $url ),
|
||||
'name' => esc_html__( 'Cancel', 'woocommerce-paypal-payments' ),
|
||||
)
|
||||
);
|
||||
|
||||
$actions['cancel']['name'] = esc_html__( 'Suspend', 'woocommerce-paypal-payments' );
|
||||
unset( $actions['subscription_renewal_early'] );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
},
|
||||
11,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wp_loaded',
|
||||
function() use ( $c ) {
|
||||
if ( ! function_exists( 'wcs_get_subscription' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cancel_subscription_id = wc_clean( wp_unslash( $_GET['ppcp_cancel_subscription'] ?? '' ) );
|
||||
$subscription = wcs_get_subscription( absint( $cancel_subscription_id ) );
|
||||
if ( ! wcs_is_subscription( $subscription ) || $subscription === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
$nonce = wc_clean( wp_unslash( $_GET['_wpnonce'] ?? '' ) );
|
||||
if ( ! is_string( $nonce ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$subscription_id
|
||||
&& $cancel_subscription_id
|
||||
&& $nonce
|
||||
) {
|
||||
if (
|
||||
! wp_verify_nonce( $nonce, 'ppcp_cancel_subscription_nonce' )
|
||||
|| ! user_can( get_current_user_id(), 'edit_shop_subscription_status', $subscription->get_id() )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' );
|
||||
try {
|
||||
$subscriptions_endpoint->cancel( $subscription_id );
|
||||
|
||||
$subscription->update_status( 'cancelled' );
|
||||
$subscription->add_order_note( __( 'Subscription cancelled by the subscriber from their account page.', 'woocommerce-paypal-payments' ) );
|
||||
wc_add_notice( __( 'Your subscription has been cancelled.', 'woocommerce-paypal-payments' ) );
|
||||
|
||||
wp_safe_redirect( $subscription->get_view_order_url() );
|
||||
exit;
|
||||
} 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 cancel subscription product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
},
|
||||
100
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_subscription_before_actions',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $subscription ) use ( $c ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id ) {
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
?>
|
||||
<tr>
|
||||
<td><?php esc_html_e( 'PayPal Subscription', 'woocommerce-paypal-payments' ); ?></td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $host . "/myaccount/autopay/connect/{$subscription_id}" ); ?>" id="ppcp-subscription-id" target="_blank"><?php echo esc_html( $subscription_id ); ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_order_data_store_cpt_get_orders_query',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $query, $query_vars ): array {
|
||||
if ( ! empty( $query_vars['ppcp_subscription'] ) ) {
|
||||
$query['meta_query'][] = array(
|
||||
'key' => 'ppcp_subscription',
|
||||
'value' => esc_attr( $query_vars['ppcp_subscription'] ),
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_customer_changed_subscription_to_cancelled',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $subscription ) use ( $c ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id ) {
|
||||
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
|
||||
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_customer_changed_subscription_to_active',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $subscription ) use ( $c ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id ) {
|
||||
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
|
||||
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
|
||||
|
||||
try {
|
||||
$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 active subscription product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_product_options_general_product_data',
|
||||
function() use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
if ( $subscriptions_mode === 'subscriptions_api' ) {
|
||||
/**
|
||||
* Needed for getting global post object.
|
||||
*
|
||||
* @psalm-suppress InvalidGlobal
|
||||
*/
|
||||
global $post;
|
||||
$product = wc_get_product( $post->ID );
|
||||
if ( ! is_a( $product, WC_Product::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
echo '<div class="options_group subscription_pricing show_if_subscription hidden">';
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
echo '</div>';
|
||||
|
||||
}
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_variation_options_pricing',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $loop, $variation_data, $variation ) use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
if ( $subscriptions_mode === 'subscriptions_api' ) {
|
||||
$product = wc_get_product( $variation->ID );
|
||||
if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
|
||||
}
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
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 '<p>' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '</p>';
|
||||
echo '<p><strong>' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . '</strong> <a href="' . esc_url( $url ) . '" target="_blank">' . esc_attr( $subscription_id ) . '</a></p>';
|
||||
},
|
||||
$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 ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render PayPal Subscriptions fields.
|
||||
*
|
||||
* @param WC_Product $product WC Product.
|
||||
* @param Environment $environment The environment.
|
||||
* @return void
|
||||
*/
|
||||
private function render_paypal_subscription_fields( WC_Product $product, Environment $environment ): void {
|
||||
$enable_subscription_product = $product->get_meta( '_ppcp_enable_subscription_product' );
|
||||
$style = $product->get_type() === 'subscription_variation' ? 'float:left; width:150px;' : '';
|
||||
|
||||
echo '<p class="form-field">';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are label open and close tags.
|
||||
esc_html__( '%1$sConnect to PayPal%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<label for="_ppcp_enable_subscription_product" style="' . esc_attr( $style ) . '">',
|
||||
'</label>'
|
||||
);
|
||||
echo '<input type="checkbox" id="ppcp_enable_subscription_product" name="_ppcp_enable_subscription_product" value="yes" ' . checked( $enable_subscription_product, 'yes', false ) . '/>';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are label open and close tags.
|
||||
esc_html__( '%1$sConnect Product to PayPal Subscriptions Plan%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<span class="description">',
|
||||
'</span>'
|
||||
);
|
||||
|
||||
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 '</p>';
|
||||
|
||||
$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' ),
|
||||
'<p class="form-field" id="ppcp-enable-subscription"><label></label><button class="button" id="ppcp-unlink-sub-plan-' . esc_attr( (string) $product->get_id() ) . '">',
|
||||
'</button><span class="spinner is-active" id="spinner-unlink-plan" style="float: none; display:none;"></span></p>'
|
||||
);
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s is open and closing paragraph tag.
|
||||
esc_html__( '%1$sPlan unlinked successfully ✔️%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-plan-unlinked" style="display: none;">',
|
||||
'</p>'
|
||||
);
|
||||
}
|
||||
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sProduct%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-product"><label style="' . esc_attr( $style ) . '">',
|
||||
'</label><a href="' . esc_url( $host . '/billing/plans/products/' . $subscription_product['id'] ) . '" target="_blank">' . esc_attr( $subscription_product['id'] ) . '</a></p>'
|
||||
);
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sPlan%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-plan"><label style="' . esc_attr( $style ) . '">',
|
||||
'</label><a href="' . esc_url( $host . '/billing/plans/' . $subscription_plan['id'] ) . '" target="_blank">' . esc_attr( $subscription_plan['id'] ) . '</a></p>'
|
||||
);
|
||||
} else {
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sPlan Name%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field"><label for="_ppcp_subscription_plan_name">',
|
||||
'</label><input type="text" class="short" id="ppcp_subscription_plan_name" name="_ppcp_subscription_plan_name" value="' . esc_attr( $subscription_plan_name ) . '"></p>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
/**
|
||||
* The subscription module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Product;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\BillingCycle;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ItemTrait;
|
||||
|
||||
/**
|
||||
* Class SubscriptionsApiHandler
|
||||
*/
|
||||
class SubscriptionsApiHandler {
|
||||
|
||||
use ItemTrait;
|
||||
|
||||
/**
|
||||
* Catalog products.
|
||||
*
|
||||
* @var CatalogProducts
|
||||
*/
|
||||
private $products_endpoint;
|
||||
|
||||
/**
|
||||
* Product factory.
|
||||
*
|
||||
* @var ProductFactory
|
||||
*/
|
||||
private $product_factory;
|
||||
|
||||
/**
|
||||
* Billing plans.
|
||||
*
|
||||
* @var BillingPlans
|
||||
*/
|
||||
private $billing_plans_endpoint;
|
||||
|
||||
/**
|
||||
* Billing cycle factory.
|
||||
*
|
||||
* @var BillingCycleFactory
|
||||
*/
|
||||
private $billing_cycle_factory;
|
||||
|
||||
/**
|
||||
* Payment preferences factory.
|
||||
*
|
||||
* @var PaymentPreferencesFactory
|
||||
*/
|
||||
private $payment_preferences_factory;
|
||||
|
||||
/**
|
||||
* The currency.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $currency;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* SubscriptionsApiHandler constructor.
|
||||
*
|
||||
* @param CatalogProducts $products_endpoint Products endpoint.
|
||||
* @param ProductFactory $product_factory Product factory.
|
||||
* @param BillingPlans $billing_plans_endpoint Billing plans endpoint.
|
||||
* @param BillingCycleFactory $billing_cycle_factory Billing cycle factory.
|
||||
* @param PaymentPreferencesFactory $payment_preferences_factory Payment preferences factory.
|
||||
* @param string $currency The currency.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
CatalogProducts $products_endpoint,
|
||||
ProductFactory $product_factory,
|
||||
BillingPlans $billing_plans_endpoint,
|
||||
BillingCycleFactory $billing_cycle_factory,
|
||||
PaymentPreferencesFactory $payment_preferences_factory,
|
||||
string $currency,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->products_endpoint = $products_endpoint;
|
||||
$this->product_factory = $product_factory;
|
||||
$this->billing_plans_endpoint = $billing_plans_endpoint;
|
||||
$this->billing_cycle_factory = $billing_cycle_factory;
|
||||
$this->payment_preferences_factory = $payment_preferences_factory;
|
||||
$this->currency = $currency;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Catalog Product and adds it as WC product meta.
|
||||
*
|
||||
* @param WC_Product $product The WC product.
|
||||
* @return void
|
||||
*/
|
||||
public function create_product( WC_Product $product ) {
|
||||
try {
|
||||
$subscription_product = $this->products_endpoint->create( $product->get_title(), $this->prepare_description( $product->get_description() ) );
|
||||
$product->update_meta_data( 'ppcp_subscription_product', $subscription_product->to_array() );
|
||||
$product->save();
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$this->logger->error( 'Could not create catalog product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a subscription plan.
|
||||
*
|
||||
* @param string $plan_name The plan name.
|
||||
* @param WC_Product $product The WC product.
|
||||
* @return void
|
||||
*/
|
||||
public function create_plan( string $plan_name, WC_Product $product ): void {
|
||||
try {
|
||||
$subscription_plan = $this->billing_plans_endpoint->create(
|
||||
$plan_name ?: $product->get_title(),
|
||||
$product->get_meta( 'ppcp_subscription_product' )['id'] ?? '',
|
||||
$this->billing_cycles( $product ),
|
||||
$this->payment_preferences_factory->from_wc_product( $product )->to_array()
|
||||
);
|
||||
|
||||
$product->update_meta_data( 'ppcp_subscription_plan', $subscription_plan->to_array() );
|
||||
$product->save();
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$this->logger->error( 'Could not create subscription plan on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a product.
|
||||
*
|
||||
* @param WC_Product $product The WC product.
|
||||
* @return void
|
||||
*/
|
||||
public function update_product( WC_Product $product ): void {
|
||||
try {
|
||||
$catalog_product_id = $product->get_meta( 'ppcp_subscription_product' )['id'] ?? '';
|
||||
if ( $catalog_product_id ) {
|
||||
$catalog_product = $this->products_endpoint->product( $catalog_product_id );
|
||||
$catalog_product_name = $catalog_product->name() ?: '';
|
||||
$catalog_product_description = $catalog_product->description() ?: '';
|
||||
|
||||
$wc_product_description = $this->prepare_description( $product->get_description() ) ?: $product->get_title();
|
||||
|
||||
if ( $catalog_product_name !== $product->get_title() || $catalog_product_description !== $wc_product_description ) {
|
||||
$data = array();
|
||||
if ( $catalog_product_name !== $product->get_title() ) {
|
||||
$data[] = (object) array(
|
||||
'op' => 'replace',
|
||||
'path' => '/name',
|
||||
'value' => $product->get_title(),
|
||||
);
|
||||
}
|
||||
if ( $catalog_product_description !== $wc_product_description ) {
|
||||
$data[] = (object) array(
|
||||
'op' => 'replace',
|
||||
'path' => '/description',
|
||||
'value' => $wc_product_description,
|
||||
);
|
||||
}
|
||||
|
||||
$this->products_endpoint->update( $catalog_product_id, $data );
|
||||
}
|
||||
}
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$this->logger->error( 'Could not update catalog product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a plan.
|
||||
*
|
||||
* @param WC_Product $product The WC product.
|
||||
* @return void
|
||||
*/
|
||||
public function update_plan( WC_Product $product ): void {
|
||||
try {
|
||||
$subscription_plan_id = $product->get_meta( 'ppcp_subscription_plan' )['id'] ?? '';
|
||||
if ( $subscription_plan_id ) {
|
||||
$subscription_plan = $this->billing_plans_endpoint->plan( $subscription_plan_id );
|
||||
|
||||
$price = $subscription_plan->billing_cycles()[0]->pricing_scheme()['fixed_price']['value'] ?? '';
|
||||
if ( $price && round( (float) $price, 2 ) !== round( (float) $product->get_price(), 2 ) ) {
|
||||
$this->billing_plans_endpoint->update_pricing(
|
||||
$subscription_plan_id,
|
||||
$this->billing_cycle_factory->from_wc_product( $product )
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$this->logger->error( 'Could not update subscription plan on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns billing cycles based on WC Subscription product.
|
||||
*
|
||||
* @param WC_Product $product The WC Subscription product.
|
||||
* @return array
|
||||
*/
|
||||
private function billing_cycles( WC_Product $product ): array {
|
||||
$billing_cycles = array();
|
||||
$sequence = 1;
|
||||
|
||||
$trial_length = $product->get_meta( '_subscription_trial_length' ) ?? '';
|
||||
if ( $trial_length ) {
|
||||
$billing_cycles[] = ( new BillingCycle(
|
||||
array(
|
||||
'interval_unit' => $product->get_meta( '_subscription_trial_period' ),
|
||||
'interval_count' => $product->get_meta( '_subscription_trial_length' ),
|
||||
),
|
||||
$sequence,
|
||||
'TRIAL',
|
||||
array(
|
||||
'fixed_price' => array(
|
||||
'value' => '0',
|
||||
'currency_code' => $this->currency,
|
||||
),
|
||||
),
|
||||
1
|
||||
) )->to_array();
|
||||
|
||||
$sequence++;
|
||||
}
|
||||
|
||||
$billing_cycles[] = ( new BillingCycle(
|
||||
array(
|
||||
'interval_unit' => $product->get_meta( '_subscription_period' ),
|
||||
'interval_count' => $product->get_meta( '_subscription_period_interval' ),
|
||||
),
|
||||
$sequence,
|
||||
'REGULAR',
|
||||
array(
|
||||
'fixed_price' => array(
|
||||
'value' => $product->get_meta( '_subscription_price' ) ?: $product->get_price(),
|
||||
'currency_code' => $this->currency,
|
||||
),
|
||||
),
|
||||
(int) $product->get_meta( '_subscription_length' )
|
||||
) )->to_array();
|
||||
|
||||
return $billing_cycles;
|
||||
}
|
||||
}
|
35
modules/ppcp-paypal-subscriptions/webpack.config.js
Normal file
35
modules/ppcp-paypal-subscriptions/webpack.config.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const path = require('path');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
target: 'web',
|
||||
entry: {
|
||||
'paypal-subscription': path.resolve('./resources/js/paypal-subscription.js'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
filename: 'js/[name].js',
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.js?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'css/[name].css',
|
||||
}
|
||||
},
|
||||
{loader:'sass-loader'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
};
|
2185
modules/ppcp-paypal-subscriptions/yarn.lock
Normal file
2185
modules/ppcp-paypal-subscriptions/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue