Merge remote-tracking branch 'origin/trunk' into PCP-3814-Create-dashboard-placeholder-page-in-new-settings-module

This commit is contained in:
inpsyde-maticluznar 2024-10-25 06:46:51 +02:00
commit 637a9c6dca
No known key found for this signature in database
GPG key ID: D005973F231309F6
55 changed files with 2102 additions and 244 deletions

View file

@ -67,7 +67,7 @@ class BillingSubscriptions {
*/
public function suspend( string $id ):void {
$data = array(
'reason' => 'Suspended by customer',
'reason' => sprintf( 'Suspended by %s.', is_admin() ? 'merchant' : 'customer' ),
);
$bearer = $this->bearer->bearer();
@ -107,7 +107,7 @@ class BillingSubscriptions {
*/
public function activate( string $id ): void {
$data = array(
'reason' => 'Reactivated by customer',
'reason' => sprintf( 'Reactivated by %s.', is_admin() ? 'merchant' : 'customer' ),
);
$bearer = $this->bearer->bearer();
@ -148,7 +148,7 @@ class BillingSubscriptions {
*/
public function cancel( string $id ): void {
$data = array(
'reason' => 'Cancelled by customer',
'reason' => sprintf( 'Cancelled by %s.', is_admin() ? 'merchant' : 'customer' ),
);
$bearer = $this->bearer->bearer();

View file

@ -113,7 +113,7 @@ class BlocksPaymentMethod extends AbstractPaymentMethodType {
'id' => $this->name,
'title' => $paypal_data['title'], // TODO : see if we should use another.
'description' => $paypal_data['description'], // TODO : see if we should use another.
'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
'enabled' => $paypal_data['smartButtonsEnabled'], // This button is enabled when PayPal buttons are.
'scriptData' => $script_data,
);
}

View file

@ -19,11 +19,9 @@ export function CardFields( {
config,
eventRegistration,
emitResponse,
components,
} ) {
const { onPaymentSetup } = eventRegistration;
const { responseTypes } = emitResponse;
const { PaymentMethodIcons } = components;
const [ cardFieldsForm, setCardFieldsForm ] = useState();
const getCardFieldsForm = ( cardFieldsForm ) => {
@ -95,10 +93,6 @@ export function CardFields( {
} }
>
<PayPalCardFieldsForm />
<PaymentMethodIcons
icons={ config.card_icons }
align="left"
/>
<CheckoutHandler
getCardFieldsForm={ getCardFieldsForm }
getSavePayment={ getSavePayment }

View file

@ -1,19 +1,30 @@
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { CardFields } from './Components/card-fields';
import {registerPaymentMethod} from '@woocommerce/blocks-registry';
import {CardFields} from './Components/card-fields';
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
registerPaymentMethod( {
name: config.id,
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <CardFields config={ config } />,
edit: <CardFields config={ config } />,
ariaLabel: config.title,
canMakePayment: () => {
return true;
},
supports: {
showSavedCards: true,
features: config.supports,
},
} );
const Label = ({components, config}) => {
const {PaymentMethodIcons} = components;
return <>
<span dangerouslySetInnerHTML={{__html: config.title}}/>
<PaymentMethodIcons
icons={ config.card_icons }
align="right"
/>
</>
}
registerPaymentMethod({
name: config.id,
label: <Label config={config}/>,
content: <CardFields config={config}/>,
edit: <CardFields config={config}/>,
ariaLabel: config.title,
canMakePayment: () => {
return true;
},
supports: {
showSavedCards: true,
features: config.supports,
},
});

View file

@ -739,11 +739,8 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
features.push( 'subscriptions' );
}
if ( block_enabled && config.enabled ) {
if (
( config.addPlaceOrderMethod || config.usePlaceOrder ) &&
! config.scriptData.continuation
) {
if ( block_enabled ) {
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
let descriptionElement = (
<div
dangerouslySetInnerHTML={ { __html: config.description } }
@ -776,7 +773,7 @@ if ( block_enabled && config.enabled ) {
placeOrderButtonLabel: config.placeOrderButtonText,
ariaLabel: config.title,
canMakePayment: () => {
return config.enabled;
return true;
},
supports: {
features,
@ -798,7 +795,7 @@ if ( block_enabled && config.enabled ) {
features: [ ...features, 'ppcp_continuation' ],
},
} );
} else if ( ! config.usePlaceOrder ) {
} else if ( config.smartButtonsEnabled ) {
for ( const fundingSource of [
'paypal',
...config.enabledFundingSources,

View file

@ -47,6 +47,7 @@ return array(
$container->get( 'blocks.settings.final_review_enabled' ),
$container->get( 'session.cancellation.view' ),
$container->get( 'session.handler' ),
$container->get( 'wc-subscriptions.helper' ),
$container->get( 'blocks.add-place-order-method' ),
$container->get( 'wcgateway.use-place-order-button' ),
$container->get( 'wcgateway.place-order-button-text' ),

View file

@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class PayPalPaymentMethod
@ -87,6 +88,13 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
*/
private $session_handler;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
private $subscription_helper;
/**
* Whether to create a non-express method with the standard "Place order" button.
*
@ -141,6 +149,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
* @param bool $final_review_enabled Whether the final review is enabled.
* @param CancelView $cancellation_view The cancellation view.
* @param SessionHandler $session_handler The Session handler.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $add_place_order_method Whether to create a non-express method with the standard "Place order" button.
* @param bool $use_place_order Whether to use the standard "Place order" button instead of PayPal buttons.
* @param string $place_order_button_text The text for the standard "Place order" button.
@ -158,6 +167,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
bool $final_review_enabled,
CancelView $cancellation_view,
SessionHandler $session_handler,
SubscriptionHelper $subscription_helper,
bool $add_place_order_method,
bool $use_place_order,
string $place_order_button_text,
@ -175,6 +185,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
$this->final_review_enabled = $final_review_enabled;
$this->cancellation_view = $cancellation_view;
$this->session_handler = $session_handler;
$this->subscription_helper = $subscription_helper;
$this->add_place_order_method = $add_place_order_method;
$this->use_place_order = $use_place_order;
$this->place_order_button_text = $place_order_button_text;
@ -195,9 +206,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
// Do not load when definitely not needed,
// but we still need to check the locations later and handle in JS
// because has_block cannot be called here (too early).
return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' )
&& ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) ||
$this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) );
return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' );
}
/**
@ -245,15 +254,19 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
);
}
$smart_buttons_enabled = ! $this->use_place_order
&& $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'block-checkout' );
$place_order_enabled = ( $this->use_place_order || $this->add_place_order_method )
&& ! $this->subscription_helper->cart_contains_subscription();
return array(
'id' => $this->gateway->id,
'title' => $this->gateway->title,
'description' => $this->gateway->description,
'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'checkout' ),
'smartButtonsEnabled' => $smart_buttons_enabled,
'placeOrderEnabled' => $place_order_enabled,
'fundingSource' => $this->session_handler->funding_source(),
'finalReviewEnabled' => $this->final_review_enabled,
'addPlaceOrderMethod' => $this->add_place_order_method,
'usePlaceOrder' => $this->use_place_order,
'placeOrderButtonText' => $this->place_order_button_text,
'placeOrderButtonDescription' => $this->place_order_button_description,
'enabledFundingSources' => $funding_sources,

View file

@ -233,6 +233,15 @@ class SingleProductBootstap {
this.form(),
this.errorHandler
);
if (
! this.gateway.vaultingEnabled &&
[ 'subscription', 'variable-subscription' ].includes(
this.gateway.productType
) &&
this.gateway.manualRenewalEnabled !== '1'
) {
return;
}
if (
PayPalCommerceGateway.data_client_id.has_subscriptions &&

View file

@ -37,6 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
return array(
'button.client_id' => static function ( ContainerInterface $container ): string {
@ -101,12 +102,20 @@ return array(
return $obj->get_context();
},
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
$state = $container->get( 'onboarding.state' );
if ( $container->get( 'wcgateway.use-place-order-button' )
&& in_array( $container->get( 'button.context' ), array( 'checkout', 'pay-now' ), true )
) {
return new DisabledSmartButton();
$context = $container->get( 'button.context' );
$settings_status = $container->get( 'wcgateway.settings.status' );
assert( $settings_status instanceof SettingsStatus );
if ( in_array( $context, array( 'checkout', 'pay-now' ), true ) ) {
if ( $container->get( 'wcgateway.use-place-order-button' )
|| ! $settings_status->is_smart_button_enabled_for_location( $context )
) {
return new DisabledSmartButton();
}
}
$state = $container->get( 'onboarding.state' );
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
return new DisabledSmartButton();
}
@ -125,7 +134,6 @@ return array(
$messages_apply = $container->get( 'button.helper.messages-apply' );
$environment = $container->get( 'onboarding.environment' );
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$settings_status = $container->get( 'wcgateway.settings.status' );
return new SmartButton(
$container->get( 'button.url' ),
$container->get( 'ppcp.asset-version' ),

View file

@ -1031,8 +1031,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
* @return bool
*/
private function has_subscriptions(): bool {
if ( ! $this->subscription_helper->plugin_is_active() ) {
return false;
}
if (
! $this->subscription_helper->accept_only_automatic_payment_gateways()
$this->subscription_helper->accept_manual_renewals()
&& $this->paypal_subscriptions_enabled() !== true
) {
return false;
@ -1318,8 +1321,17 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
'needShipping' => $this->need_shipping(),
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
'productType' => null,
'manualRenewalEnabled' => $this->subscription_helper->accept_manual_renewals(),
);
if ( is_product() ) {
$product = wc_get_product( get_the_ID() );
if ( is_a( $product, \WC_Product::class ) ) {
$localize['productType'] = $product->get_type();
}
}
if ( 'pay-now' === $this->context() ) {
$localize['pay_now'] = $this->pay_now_script_data();
}

View file

@ -101,7 +101,7 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
add_action(
'woocommerce_init',
function() {
if ( is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
if ( is_admin() && is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
PPEC\DeactivateNote::init();
}
}

View file

@ -174,6 +174,7 @@ return array(
$container->get( 'googlepay.sdk_url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'session.handler' ),
$container->get( 'wc-subscriptions.helper' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.settings.status' ),

View file

@ -107,7 +107,7 @@ class BlocksPaymentMethod extends AbstractPaymentMethodType {
'id' => $this->name,
'title' => $paypal_data['title'], // See if we should use another.
'description' => $paypal_data['description'], // See if we should use another.
'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
'enabled' => $paypal_data['smartButtonsEnabled'], // This button is enabled when PayPal buttons are.
'scriptData' => $this->button->script_data(),
);
}

View file

@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class Button
@ -85,37 +86,47 @@ class Button implements ButtonInterface {
*/
private $session_handler;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
private $subscription_helper;
/**
* SmartButton constructor.
*
* @param string $module_url The URL to the module.
* @param string $sdk_url The URL to the SDK.
* @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings.
* @param Environment $environment The environment object.
* @param SettingsStatus $settings_status The Settings status helper.
* @param LoggerInterface $logger The logger.
* @param string $module_url The URL to the module.
* @param string $sdk_url The URL to the SDK.
* @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param Settings $settings The Settings.
* @param Environment $environment The environment object.
* @param SettingsStatus $settings_status The Settings status helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
string $module_url,
string $sdk_url,
string $version,
SessionHandler $session_handler,
SubscriptionHelper $subscription_helper,
Settings $settings,
Environment $environment,
SettingsStatus $settings_status,
LoggerInterface $logger
) {
$this->module_url = $module_url;
$this->sdk_url = $sdk_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->environment = $environment;
$this->settings_status = $settings_status;
$this->logger = $logger;
$this->module_url = $module_url;
$this->sdk_url = $sdk_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->subscription_helper = $subscription_helper;
$this->settings = $settings;
$this->environment = $environment;
$this->settings_status = $settings_status;
$this->logger = $logger;
}
/**
@ -233,6 +244,21 @@ class Button implements ButtonInterface {
$button_enabled_payorder = true;
$button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
if (
$this->subscription_helper->plugin_is_active()
&& ! $this->subscription_helper->accept_manual_renewals()
) {
if ( is_product() && $this->subscription_helper->current_product_is_subscription() ) {
return false;
}
if ( $this->subscription_helper->order_pay_contains_subscription() ) {
return false;
}
if ( $this->subscription_helper->cart_contains_subscription() ) {
return false;
}
}
/**
* Param types removed to avoid third-party issues.
*

View file

@ -1,3 +1,8 @@
import {
setVisible,
setVisibleByClass,
} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
document.addEventListener( 'DOMContentLoaded', () => {
const payLaterMessagingSelectableLocations = [
'product',
@ -216,6 +221,18 @@ document.addEventListener( 'DOMContentLoaded', () => {
replace();
};
const hideElements = ( selectorGroup ) => {
selectorGroup.forEach( ( selector ) =>
setVisibleByClass( selector, false, 'hide' )
);
};
const showElements = ( selectorGroup ) => {
selectorGroup.forEach( ( selector ) =>
setVisibleByClass( selector, true, 'hide' )
);
};
const toggleInputsBySelectedLocations = (
stylingPerSelector,
locationsSelector,
@ -226,30 +243,30 @@ document.addEventListener( 'DOMContentLoaded', () => {
const payLaterMessagingEnabled = document.querySelector(
payLaterMessagingEnabledSelector
);
const stylingPerElement = document.querySelector( stylingPerSelector );
const locationsElement = document.querySelector( locationsSelector );
const stylingPerElementWrapper = stylingPerElement?.closest( 'tr' );
const stylingPerElementWrapperSelector =
'#' + stylingPerElementWrapper?.getAttribute( 'id' );
const stylingPerElement = document.querySelector( stylingPerSelector );
if ( ! stylingPerElement ) {
return;
}
const stylingPerElementWrapper = stylingPerElement.closest( 'tr' );
const toggleElementsBySelectedLocations = () => {
stylingPerElementWrapper.style.display = '';
const selectedLocations = getSelectedLocations( locationsSelector );
const emptySmartButtonLocationMessage = jQuery(
'.ppcp-empty-smart-button-location'
setVisibleByClass(
stylingPerElementWrapper,
selectedLocations.length > 0,
'hide'
);
if ( selectedLocations.length === 0 ) {
hideElements(
groupToHideOnChecked.concat(
stylingPerElementWrapperSelector
)
hideElements( groupToHideOnChecked );
const emptySmartButtonLocationMessage = document.querySelector(
'.ppcp-empty-smart-button-location'
);
if ( emptySmartButtonLocationMessage.length === 0 ) {
if ( ! emptySmartButtonLocationMessage ) {
jQuery(
PayPalCommerceSettings.empty_smart_button_location_message
).insertAfter(
@ -277,11 +294,11 @@ document.addEventListener( 'DOMContentLoaded', () => {
);
groupToShowOnChecked.forEach( ( element ) => {
if ( inputSelectors.includes( element ) ) {
document.querySelector( element ).style.display = '';
return;
}
document.querySelector( element ).style.display = 'none';
setVisibleByClass(
element,
inputSelectors.includes( element ),
'hide'
);
} );
if ( inputType === 'messages' ) {
@ -289,18 +306,6 @@ document.addEventListener( 'DOMContentLoaded', () => {
}
};
const hideElements = ( selectroGroup ) => {
selectroGroup.forEach( ( elementToHide ) => {
document.querySelector( elementToHide ).style.display = 'none';
} );
};
const showElements = ( selectroGroup ) => {
selectroGroup.forEach( ( elementToShow ) => {
document.querySelector( elementToShow ).style.display = '';
} );
};
groupToggle( stylingPerSelector, groupToShowOnChecked );
toggleElementsBySelectedLocations();
@ -327,7 +332,7 @@ document.addEventListener( 'DOMContentLoaded', () => {
} );
// We need to use jQuery here as the select might be a select2 element, which doesn't use native events.
jQuery( locationsElement ).on( 'change', function () {
jQuery( locationsSelector ).on( 'change', function () {
const emptySmartButtonLocationMessage = jQuery(
'.ppcp-empty-smart-button-location'
);
@ -457,6 +462,38 @@ document.addEventListener( 'DOMContentLoaded', () => {
}
};
/**
* Hide the subscription settings when smart buttons are disabled for checkout,
* since the basic redirect gateway is disabled for subscriptions.
*/
const initSettingsHidingForPlaceOrderGateway = () => {
const selectors = [
'#field-paypal_saved_payments',
'#field-subscriptions_mode',
'#field-vault_enabled',
];
const updateSettingsVisibility = () => {
const selectedLocations = getSelectedLocations(
smartButtonLocationsSelect
);
const hasCheckoutSmartButtons =
selectedLocations.includes( 'checkout' ) ||
selectedLocations.includes( 'checkout-block-express' );
selectors.forEach( ( selector ) => {
setVisibleByClass( selector, hasCheckoutSmartButtons, 'hide' );
} );
};
updateSettingsVisibility();
jQuery( smartButtonLocationsSelect ).on(
'change',
updateSettingsVisibility
);
};
( () => {
removeDisabledCardIcons(
'select[name="ppcp[disable_cards][]"]',
@ -488,6 +525,8 @@ document.addEventListener( 'DOMContentLoaded', () => {
toggleMessagingEnabled();
initSettingsHidingForPlaceOrderGateway();
groupToggle( '#ppcp-vault_enabled', [
'#field-subscription_behavior_when_vault_fails',
] );

View file

@ -117,7 +117,7 @@ class OnboardingAssets {
array(
'empty_smart_button_location_message' => sprintf(
'<p class="description ppcp-empty-smart-button-location">%1$s</p>',
__( 'Note: If no button location is selected, the PayPal gateway will not be available.', 'woocommerce-paypal-payments' )
__( 'Note: PayPal buttons and advanced payment features (Alternative Payment Methods, Subscriptions, etc.) are unavailable if no Smart Button Location is configured.', 'woocommerce-paypal-payments' )
),
)
);

View file

@ -124,7 +124,7 @@ class MetaBoxRenderer {
<label for="ppcp-tracking-items"><?php echo esc_html__( 'Select items for this shipment', 'woocommerce-paypal-payments' ); ?></label>
<select multiple class="wc-enhanced-select ppcp-tracking-items" id="ppcp-tracking-items" name="ppcp-tracking[items]">
<?php foreach ( $order_items as $item ) : ?>
<option value="<?php echo intval( $item->get_id() ); ?>"><?php echo esc_html( $item->get_name() ); ?></option>
<option value="<?php echo intval( $item->get_id() ); ?>"><?php echo esc_attr( wp_strip_all_tags( $item->get_name() ) ); ?></option>
<?php endforeach; ?>
</select>
</div>

View file

@ -40,4 +40,13 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) );
},
'paypal-subscriptions.status' => static function ( ContainerInterface $container ): SubscriptionStatus {
return new SubscriptionStatus(
$container->get( 'api.endpoint.billing-subscriptions' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
);

View file

@ -187,6 +187,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
30
);
/**
* Executed when updating WC Subscription.
*/
add_action(
'woocommerce_process_shop_subscription_meta',
/**
@ -194,65 +197,41 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
*
* @psalm-suppress MissingClosureParamType
*/
function( $id, $post ) use ( $c ) {
function( $id ) use ( $c ) {
$subscription = wcs_get_subscription( $id );
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
if ( $subscription === false ) {
return;
}
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( ! $subscription_id ) {
return;
}
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
if ( $subscription->get_status() === 'cancelled' ) {
try {
$subscriptions_endpoint->cancel( $subscription_id );
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
$subscription_status = $c->get( 'paypal-subscriptions.status' );
assert( $subscription_status instanceof SubscriptionStatus );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$logger->error( 'Could not cancel subscription product on PayPal. ' . $error );
}
}
if ( $subscription->get_status() === 'pending-cancel' ) {
try {
$subscriptions_endpoint->suspend( $subscription_id );
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
}
}
if ( $subscription->get_status() === 'active' ) {
try {
$current_subscription = $subscriptions_endpoint->subscription( $subscription_id );
if ( $current_subscription->status === 'SUSPENDED' ) {
$subscriptions_endpoint->activate( $subscription_id );
}
} catch ( RuntimeException $exception ) {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$logger->error( 'Could not reactivate subscription product on PayPal. ' . $error );
}
}
$subscription_status->update_status( $subscription->get_status(), $subscription_id );
},
20,
2
20
);
/**
* Update subscription status from WC Subscriptions list page action link.
*/
add_action(
'woocommerce_subscription_status_updated',
function( WC_Subscription $subscription ) use ( $c ) {
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
if ( ! $subscription_id ) {
return;
}
$subscription_status = $c->get( 'paypal-subscriptions.status' );
assert( $subscription_status instanceof SubscriptionStatus );
$subscription_status->update_status( $subscription->get_status(), $subscription_id );
}
);
add_filter(
@ -717,11 +696,18 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
* @param WC_Product $product The product.
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
* @return void
*
* @psalm-suppress PossiblyInvalidCast
*/
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
// phpcs:ignore WordPress.Security.NonceVerification
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
$enable_subscription_product = wc_string_to_bool( (string) wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) ) );
$product->update_meta_data( '_ppcp_enable_subscription_product', wc_bool_to_string( $enable_subscription_product ) );
if ( ! $enable_subscription_product ) {
$product->save();
return;
}
if ( ! $product->get_sold_individually() ) {
$product->set_sold_individually( true );
@ -729,7 +715,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
$product->save();
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
if ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
$subscriptions_api_handler->update_product( $product );
$subscriptions_api_handler->update_plan( $product );

View file

@ -0,0 +1,107 @@
<?php
/**
* Subscriptions renewal handler.
*
* @package WooCommerce\PayPalCommerce\WcSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use Psr\Log\LoggerInterface;
use WC_Data_Exception;
use WC_Order;
use WC_Subscription;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/**
* Class RenewalHandler
*/
class RenewalHandler {
use TransactionIdHandlingTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* RenewalHandler constructor.
*
* @param LoggerInterface $logger The logger.
*/
public function __construct( LoggerInterface $logger ) {
$this->logger = $logger;
}
/**
* Process subscription renewal.
*
* @param WC_Subscription[] $subscriptions WC Subscriptions.
* @param string $transaction_id PayPal transaction ID.
* @return void
* @throws WC_Data_Exception If something goes wrong while setting payment method.
*/
public function process( array $subscriptions, string $transaction_id ): void {
foreach ( $subscriptions as $subscription ) {
if ( $this->is_for_renewal_order( $subscription ) ) {
$renewal_order = wcs_create_renewal_order( $subscription );
if ( is_a( $renewal_order, WC_Order::class ) ) {
$this->logger->info(
sprintf(
'Processing renewal order #%s for subscription #%s',
$renewal_order->get_id(),
$subscription->get_id()
)
);
$renewal_order->set_payment_method( $subscription->get_payment_method() );
$renewal_order->payment_complete();
$this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
break;
}
}
$parent_order = wc_get_order( $subscription->get_parent() );
if ( is_a( $parent_order, WC_Order::class ) ) {
$this->logger->info(
sprintf(
'Processing parent order #%s for subscription #%s',
$parent_order->get_id(),
$subscription->get_id()
)
);
$subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
$subscription->save_meta_data();
$this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
}
}
}
/**
* Checks whether subscription order is for renewal or not.
*
* @param WC_Subscription $subscription WC Subscription.
* @return bool
*/
private function is_for_renewal_order( WC_Subscription $subscription ): bool {
$subscription_renewal_meta = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
if ( $subscription_renewal_meta === 'true' ) {
return true;
}
if (
time() >= $subscription->get_time( 'start' )
&& ( time() - $subscription->get_time( 'start' ) ) <= ( 8 * HOUR_IN_SECONDS )
) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,144 @@
<?php
/**
* Handles PayPal subscription status.
*
* @package WooCommerce\PayPalCommerce\WcSubscriptions
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class SubscriptionStatus
*/
class SubscriptionStatus {
/**
* Billing subscriptions endpoint.
*
* @var BillingSubscriptions
*/
private $subscriptions_endpoint;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* SubscriptionStatus constructor.
*
* @param BillingSubscriptions $subscriptions_endpoint Billing subscriptions endpoint.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
BillingSubscriptions $subscriptions_endpoint,
LoggerInterface $logger
) {
$this->subscriptions_endpoint = $subscriptions_endpoint;
$this->logger = $logger;
}
/**
* Updates PayPal subscription status from the given WC Subscription status.
*
* @param string $subscription_status The WC Subscription status.
* @param string $subscription_id The PayPal Subscription ID.
* @return void
*/
public function update_status( string $subscription_status, string $subscription_id ): void {
if ( $subscription_status === 'pending-cancel' || $subscription_status === 'cancelled' ) {
try {
$current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
if ( $current_subscription->status === 'CANCELLED' ) {
return;
}
$this->logger->info(
sprintf(
'Canceling PayPal subscription #%s.',
$subscription_id
)
);
$this->subscriptions_endpoint->cancel( $subscription_id );
} catch ( RuntimeException $exception ) {
$this->logger->error(
sprintf(
'Could not cancel PayPal subscription #%s. %s',
$subscription_id,
$this->get_error( $exception )
)
);
}
}
if ( $subscription_status === 'on-hold' ) {
try {
$this->logger->info(
sprintf(
'Suspending PayPal subscription #%s.',
$subscription_id
)
);
$this->subscriptions_endpoint->suspend( $subscription_id );
} catch ( RuntimeException $exception ) {
$this->logger->error(
sprintf(
'Could not suspend PayPal subscription #%s. %s',
$subscription_id,
$this->get_error( $exception )
)
);
}
}
if ( $subscription_status === 'active' ) {
try {
$current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
if ( $current_subscription->status === 'SUSPENDED' ) {
$this->logger->info(
sprintf(
'Activating suspended PayPal subscription #%s.',
$subscription_id
)
);
$this->subscriptions_endpoint->activate( $subscription_id );
}
} catch ( RuntimeException $exception ) {
$this->logger->error(
sprintf(
'Could not reactivate PayPal subscription #%s. %s',
$subscription_id,
$this->get_error( $exception )
)
);
}
}
}
/**
* Get error from exception.
*
* @param RuntimeException $exception The exception.
* @return string
*/
private function get_error( RuntimeException $exception ): string {
$error = $exception->getMessage();
if ( is_a( $exception, PayPalApiException::class ) ) {
$error = $exception->get_details( $error );
}
return $error;
}
}

View file

@ -8,5 +8,9 @@
},
"devDependencies": {
"@wordpress/scripts": "^30.3.0"
},
"dependencies": {
"@wordpress/data": "^10.10.0",
"@wordpress/data-controls": "^4.10.0"
}
}

View file

@ -0,0 +1,2 @@
export const NAMESPACE = '/wc/v3/wc_paypal';
export const STORE_NAME = 'wc/paypal';

View file

@ -0,0 +1,7 @@
import { STORE_NAME } from './constants';
import { initStore } from './store';
initStore();
export const WC_PAYPAL_STORE_NAME = STORE_NAME;
export * from './onboarding/hooks';

View file

@ -0,0 +1,5 @@
export default {
SET_ONBOARDING_DETAILS: 'SET_ONBOARDING_DETAILS',
SET_IS_SAVING_ONBOARDING_DETAILS: 'SET_IS_SAVING_ONBOARDING_DETAILS',
SET_ONBOARDING_STEP: 'SET_ONBOARDING_STEP',
};

View file

@ -0,0 +1,79 @@
import { dispatch, select } from '@wordpress/data';
import { apiFetch } from '@wordpress/data-controls';
import { __ } from '@wordpress/i18n';
import ACTION_TYPES from './action-types';
import { NAMESPACE, STORE_NAME } from '../constants';
/**
* Persistent. Set the full onboarding details, usually during app initialization.
*
* @param {Object} payload
* @return {{payload, type: string}} The action.
*/
export const updateOnboardingDetails = ( payload ) => {
return {
type: ACTION_TYPES.SET_ONBOARDING_DETAILS,
payload,
};
};
/**
* Persistent. Sets the onboarding wizard to a new step.
*
* @param {number} step
* @return {{type: string, step}} An action.
*/
export const setOnboardingStep = ( step ) => {
return {
type: ACTION_TYPES.SET_ONBOARDING_STEP,
step,
};
};
/**
* Non-persistent. Changes the "saving" flag.
*
* @param {boolean} isSaving
* @return {{type: string, isSaving}} The action.
*/
export const updateIsSaving = ( isSaving ) => {
return {
type: ACTION_TYPES.SET_IS_SAVING_ONBOARDING_DETAILS,
isSaving,
};
};
/**
* Saves the persistent details to the WP database.
*
* @return {Generator<any>} A generator function that handles the saving process.
*/
export function* persist() {
let error = null;
try {
const path = `${ NAMESPACE }/onboarding`;
const data = select( STORE_NAME ).getOnboardingData();
yield updateIsSaving( true );
yield apiFetch( {
path,
method: 'post',
data,
} );
yield dispatch( 'core/notices' ).createSuccessNotice(
__( 'Progress saved.', 'woocommerce-paypal-payments' )
);
} catch ( e ) {
error = e;
yield dispatch( 'core/notices' ).createErrorNotice(
__( 'Error saving progress.', 'woocommerce-paypal-payments' )
);
} finally {
yield updateIsSaving( false );
}
return error === null;
}

View file

@ -0,0 +1,23 @@
import { useSelect, useDispatch } from '@wordpress/data';
import { STORE_NAME } from '../constants';
export const useOnboardingDetails = () => {
const { setOnboardingStep, persist } = useDispatch( STORE_NAME );
const onboardingStep = useSelect( ( select ) => {
return select( STORE_NAME ).getOnboardingStep();
}, [] );
const isSaving = useSelect( ( select ) => {
return select( STORE_NAME ).isSaving();
}, [] );
return {
onboardingStep,
isSaving,
setOnboardingStep: async ( step ) => {
setOnboardingStep( step );
await persist();
},
};
};

View file

@ -0,0 +1,6 @@
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
import * as resolvers from './resolvers';
export { reducer, selectors, actions, resolvers };

View file

@ -0,0 +1,42 @@
import ACTION_TYPES from './action-types';
const defaultState = {
isSaving: false,
data: {
step: 0,
},
};
export const onboardingReducer = (
state = defaultState,
{ type, ...action }
) => {
switch ( type ) {
case ACTION_TYPES.SET_ONBOARDING_DETAILS:
return {
...state,
data: action.payload,
};
case ACTION_TYPES.SET_IS_SAVING_ONBOARDING_DETAILS:
return {
...state,
isSaving: action.isSaving,
};
case ACTION_TYPES.SET_ONBOARDING_STEP:
return {
...state,
data: {
...( state.data || {} ),
step: action.step,
},
};
default:
}
return state;
};
export default onboardingReducer;

View file

@ -0,0 +1,24 @@
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { apiFetch } from '@wordpress/data-controls';
import { NAMESPACE } from '../constants';
import { updateOnboardingDetails } from './actions';
/**
* Retrieve settings from the site's REST API.
*/
export function* getOnboardingData() {
const path = `${ NAMESPACE }/onboarding`;
try {
const result = yield apiFetch( { path } );
yield updateOnboardingDetails( result );
} catch ( e ) {
yield dispatch( 'core/notices' ).createErrorNotice(
__(
'Error retrieving onboarding details.',
'woocommerce-paypal-payments'
)
);
}
}

View file

@ -0,0 +1,21 @@
const EMPTY_OBJ = {};
const getOnboardingState = ( state ) => {
if ( ! state ) {
return EMPTY_OBJ;
}
return state.onboarding || EMPTY_OBJ;
};
export const getOnboardingData = ( state ) => {
return getOnboardingState( state ).data || EMPTY_OBJ;
};
export const isSaving = ( state ) => {
return getOnboardingState( state ).isSaving || false;
};
export const getOnboardingStep = ( state ) => {
return getOnboardingData( state ).step || 0;
};

View file

@ -0,0 +1,30 @@
import { createReduxStore, register, combineReducers } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import { STORE_NAME } from './constants';
import * as onboarding from './onboarding';
const actions = {};
const selectors = {};
const resolvers = {};
[ onboarding ].forEach( ( item ) => {
Object.assign( actions, { ...item.actions } );
Object.assign( selectors, { ...item.selectors } );
Object.assign( resolvers, { ...item.resolvers } );
} );
const reducer = combineReducers( {
onboarding: onboarding.reducer,
} );
export const initStore = () => {
const store = createReduxStore( STORE_NAME, {
reducer,
controls,
actions,
selectors,
resolvers,
} );
register( store );
};

View file

@ -10,9 +10,11 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
return array(
'settings.url' => static function ( ContainerInterface $container ) : string {
'settings.url' => static function ( ContainerInterface $container ) : string {
/**
* The path cannot be false.
*
@ -23,4 +25,10 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'settings.data.onboarding' => static function ( ContainerInterface $container ) : OnboardingProfile {
return new OnboardingProfile();
},
'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint {
return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) );
},
);

View file

@ -0,0 +1,42 @@
<?php
/**
* Settings container class
*
* @package WooCommerce\PayPalCommerce\Settings\Data
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Data;
/**
* This class serves as a container for managing the onboarding profile details
* within the WooCommerce PayPal Commerce plugin. It provides methods to retrieve
* and save the onboarding profile data using WordPress options.
*/
class OnboardingProfile {
/**
* Options key where profile details are stored.
*
* @var string
*/
private const KEY = 'woocommerce-ppcp-data-onboarding';
/**
* Returns the current onboarding profile details.
*
* @return array
*/
public function get_data() : array {
return get_option( self::KEY, array() );
}
/**
* Saves the onboarding profile details.
*
* @param array $data The profile details to save.
*/
public function save_data( array $data ) : void {
update_option( self::KEY, $data );
}
}

View file

@ -0,0 +1,114 @@
<?php
/**
* REST endpoint to manage the onboarding module.
*
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
/**
* REST controller for the onboarding module.
*
* Responsible for persisting and loading the state of the onboarding wizard.
*/
class OnboardingRestEndpoint extends RestEndpoint {
/**
* The base path for this REST controller.
*
* @var string
*/
protected $rest_base = 'onboarding';
/**
* The settings instance.
*
* @var OnboardingProfile
*/
protected $profile;
/**
* Constructor.
*
* @param OnboardingProfile $profile The settings instance.
*/
public function __construct( OnboardingProfile $profile ) {
$this->profile = $profile;
}
/**
* Configure REST API routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_details' ),
'permission_callback' => array( $this, 'check_permission' ),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_details' ),
'permission_callback' => array( $this, 'check_permission' ),
),
)
);
}
/**
* Returns an object with all details of the current onboarding wizard
* progress.
*
* @return WP_REST_Response The current state of the onboarding wizard.
*/
public function get_details() : WP_REST_Response {
$details = $this->profile->get_data();
return rest_ensure_response( $details );
}
/**
* Receives an object with onboarding details and persists it in the DB.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_REST_Response The current state of the onboarding wizard.
*/
public function update_details( WP_REST_Request $request ) : WP_REST_Response {
$details = $this->profile->get_data();
$get_param = fn( $key ) => wc_clean( wp_unslash( $request->get_param( $key ) ) );
$raw_step = $get_param( 'step' );
$raw_completed = $get_param( 'completed' );
if ( is_numeric( $raw_step ) ) {
$details['step'] = intval( $raw_step );
}
if ( null !== $raw_completed ) {
$details['completed'] = (bool) $raw_completed;
}
$this->profile->save_data( $details );
return rest_ensure_response( $details );
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* REST endpoint to manage the onboarding module.
*
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WC_REST_Controller;
/**
* Base class for REST controllers in the settings module.
*
* This is a base class for specific REST endpoints; do not instantiate this
* class directly.
*/
class RestEndpoint extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3/wc_paypal';
/**
* Verify access.
*
* Override this method if custom permissions required.
*/
public function check_permission() : bool {
return current_user_can( 'manage_woocommerce' );
}
}

View file

@ -2,10 +2,11 @@
/**
* The Settings module.
*
* @package WooCommerce\PayPalCommerce\AxoBlock
* @package WooCommerce\PayPalCommerce\Settings
*/
declare(strict_types=1);
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
@ -22,14 +23,14 @@ class SettingsModule implements ServiceModule, ExecutableModule {
/**
* {@inheritDoc}
*/
public function services(): array {
public function services() : array {
return require __DIR__ . '/../services.php';
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $container ): bool {
public function run( ContainerInterface $container ) : bool {
add_action(
'admin_enqueue_scripts',
/**
@ -37,7 +38,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
*
* @psalm-suppress MissingClosureParamType
*/
static function( $hook_suffix ) use ( $container ) {
static function ( $hook_suffix ) use ( $container ) {
if ( 'woocommerce_page_wc-settings' !== $hook_suffix ) {
return;
}
@ -92,7 +93,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
add_action(
'woocommerce_paypal_payments_gateway_admin_options_wrapper',
static function(): void {
static function () : void {
global $hide_save_button;
$hide_save_button = true;
@ -100,6 +101,14 @@ class SettingsModule implements ServiceModule, ExecutableModule {
}
);
add_action(
'rest_api_init',
static function () use ( $container ) : void {
$onboarding_endpoint = $container->get( 'settings.rest.onboarding' );
$onboarding_endpoint->register_routes();
}
);
return true;
}
}

View file

@ -1095,7 +1095,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.25.7"
"@babel/plugin-transform-typescript" "^7.25.7"
"@babel/runtime@7.25.7", "@babel/runtime@^7.8.4":
"@babel/runtime@7.25.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==
@ -1871,6 +1871,31 @@
"@svgr/plugin-jsx" "8.1.0"
"@svgr/plugin-svgo" "8.1.0"
"@tannin/compile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@tannin/compile/-/compile-1.1.0.tgz#1e4d1c5364cbfeffa1c20352c053e19ef20ffe93"
integrity sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==
dependencies:
"@tannin/evaluate" "^1.2.0"
"@tannin/postfix" "^1.1.0"
"@tannin/evaluate@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@tannin/evaluate/-/evaluate-1.2.0.tgz#468a13c45eff45340108836fc46c708457199c3f"
integrity sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg==
"@tannin/plural-forms@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@tannin/plural-forms/-/plural-forms-1.1.0.tgz#cffbb060d2640a56a314e3c77cbf6ea6072b51d5"
integrity sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==
dependencies:
"@tannin/compile" "^1.1.0"
"@tannin/postfix@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@tannin/postfix/-/postfix-1.1.0.tgz#6071f4204ae26c2e885cf3a3f1203a9f71e3f291"
integrity sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
@ -2074,6 +2099,11 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==
"@types/mousetrap@^1.6.8":
version "1.6.15"
resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.15.tgz#f144a0c539a4cef553a631824651d48267e53c86"
integrity sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==
"@types/node-forge@^1.3.0":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
@ -2098,6 +2128,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
"@types/prop-types@*":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
"@types/qs@*":
version "6.9.16"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794"
@ -2108,6 +2143,21 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/react-dom@^18.2.25":
version "18.3.1"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07"
integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.79":
version "18.3.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.11.tgz#9d530601ff843ee0d7030d4227ea4360236bd537"
integrity sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/retry@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@ -2498,6 +2548,15 @@
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e"
integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
"@wordpress/api-fetch@^7.10.0":
version "7.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/api-fetch/-/api-fetch-7.10.0.tgz#25fc63c531c905e6a917074dd0fd6b945bab809e"
integrity sha512-sL71KkvtdGdaZmy7T0+nsTaBVhN0LSwkm3nh/qnebIYzpjZeiPWF/QeoGtJk/lKB15HjbTbfwvFEjiMI4dFvhA==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/i18n" "^5.10.0"
"@wordpress/url" "^4.10.0"
"@wordpress/babel-preset-default@^8.10.0":
version "8.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/babel-preset-default/-/babel-preset-default-8.10.0.tgz#4a95c09ec206c3a219698488cfc3731dbe0b67f4"
@ -2525,6 +2584,56 @@
resolved "https://registry.yarnpkg.com/@wordpress/browserslist-config/-/browserslist-config-6.10.0.tgz#09e569dd518649c011f5acbc743eae8645f75cd8"
integrity sha512-X5BG4xWvr1Qq9S2x5ERCF7V4bpa24zbj8cWYbIJaGiCfi6vp6dFI1SbvuZPXfKyThyytTVYBvEIr6CSm6G8fuQ==
"@wordpress/compose@^7.10.0":
version "7.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/compose/-/compose-7.10.0.tgz#b2e094dfb82f8b19c4619516ab78fc3f520029da"
integrity sha512-/j4+wXthaV/KMt0VANvhhRJEJfPc21c7Tq1ZeLxgsbkq4xmi9qXeDT91cvP/U+Ta3phf15K8vdxMr8MqHHiFoQ==
dependencies:
"@babel/runtime" "7.25.7"
"@types/mousetrap" "^1.6.8"
"@wordpress/deprecated" "^4.10.0"
"@wordpress/dom" "^4.10.0"
"@wordpress/element" "^6.10.0"
"@wordpress/is-shallow-equal" "^5.10.0"
"@wordpress/keycodes" "^4.10.0"
"@wordpress/priority-queue" "^3.10.0"
"@wordpress/undo-manager" "^1.10.0"
change-case "^4.1.2"
clipboard "^2.0.11"
mousetrap "^1.6.5"
use-memo-one "^1.1.1"
"@wordpress/data-controls@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/data-controls/-/data-controls-4.10.0.tgz#100dcf4700f7efcd289823d76e019d85b31343ea"
integrity sha512-a7IBnxmzZYIYQzj5iPgBfHZPR65gj+AONNFHM7EUIe9FuyqWtPHy/1jUB7z95ataadh+G4d62ZwZiR1O+prSxg==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/api-fetch" "^7.10.0"
"@wordpress/data" "^10.10.0"
"@wordpress/deprecated" "^4.10.0"
"@wordpress/data@^10.10.0":
version "10.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/data/-/data-10.10.0.tgz#da8fc4004bd9f7376f7ac17148a68158ae651174"
integrity sha512-oyYl89p86+U9W6vKDqScKhUGKKzsnETj9rg8zOnT4K9ceOScjGCgdCE+XxcY9exeRg33aSYDjmvnsXXYStBYmA==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/compose" "^7.10.0"
"@wordpress/deprecated" "^4.10.0"
"@wordpress/element" "^6.10.0"
"@wordpress/is-shallow-equal" "^5.10.0"
"@wordpress/priority-queue" "^3.10.0"
"@wordpress/private-apis" "^1.10.0"
"@wordpress/redux-routine" "^5.10.0"
deepmerge "^4.3.0"
equivalent-key-map "^0.2.2"
is-plain-object "^5.0.0"
is-promise "^4.0.0"
redux "^4.1.2"
rememo "^4.0.2"
use-memo-one "^1.1.1"
"@wordpress/dependency-extraction-webpack-plugin@^6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.10.0.tgz#45ed2e9e4dfbb60bd131de755d30f07e74f68d1c"
@ -2532,6 +2641,22 @@
dependencies:
json2php "^0.0.7"
"@wordpress/deprecated@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/deprecated/-/deprecated-4.10.0.tgz#26f4d44e929779ebaf730545799aa07a3544a213"
integrity sha512-lktJKX3AxrskTuLbJuKY/Mzg9De6MYcOzEEL+RUHxfIx8wMtiDnVTAf7epur9XuHVOmdgCCRT6D44I23MoS0sw==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/hooks" "^4.10.0"
"@wordpress/dom@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/dom/-/dom-4.10.0.tgz#f6fcfbbb813b657315c1416f51e9c3f9ad557ce1"
integrity sha512-1ZRCrDB2TV44GLwaUH9HRGQGQqXcawSEmzVPABQwfwzkUKijfbRdsWqpHrTLqlSZRImHEdp6oSON+1JmCNhXSw==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/deprecated" "^4.10.0"
"@wordpress/e2e-test-utils-playwright@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.10.0.tgz#41c1a9293c70afae6a2c214bf539c8bdb288f945"
@ -2544,6 +2669,27 @@
mime "^3.0.0"
web-vitals "^4.2.1"
"@wordpress/element@^6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/element/-/element-6.10.0.tgz#17ff5970576c96c3b2ba4f5e81c81dc99f2ce221"
integrity sha512-7zW+14vHqEn45nszSLMUqE5IbzOtvgUUgF56qlMhwabpG4l/zhaj3gO3wLDI19C13ih1vOdSjzPc3At4fB3tRQ==
dependencies:
"@babel/runtime" "7.25.7"
"@types/react" "^18.2.79"
"@types/react-dom" "^18.2.25"
"@wordpress/escape-html" "^3.10.0"
change-case "^4.1.2"
is-plain-object "^5.0.0"
react "^18.3.0"
react-dom "^18.3.0"
"@wordpress/escape-html@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/escape-html/-/escape-html-3.10.0.tgz#bdc51085a77e12de55c2bd90bd223883a58e0702"
integrity sha512-3glY3MhXEHlPP0/hrS3vkRmAOHtutvoHGhkr8vnva6TLg4CsAeo42nYbuFJ+ukVMWdCtmV+28UjOeiYtG/fZOA==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/eslint-plugin@^21.3.0":
version "21.3.0"
resolved "https://registry.yarnpkg.com/@wordpress/eslint-plugin/-/eslint-plugin-21.3.0.tgz#3857262f10446afa207357232f05086db47ed387"
@ -2567,6 +2713,32 @@
globals "^13.12.0"
requireindex "^1.2.0"
"@wordpress/hooks@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/hooks/-/hooks-4.10.0.tgz#de020f6dacb250387fbbfa8f797ec0ede7fddbad"
integrity sha512-LcorV5Z9XoJCKyj5Ulgw1HPHyM2mxsSInC7wl5cuIgDFmuwPTfRndUDGWz/v86GX1GnUIB0h/ggd53vx1HiW4A==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/i18n@^5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/i18n/-/i18n-5.10.0.tgz#55db9299240d92496a3d3ecb046eb6c682ef7734"
integrity sha512-HZ6UcMHsjOocDI0zVAuP4JIl97LRmpGo/lVxzVIreaLoYitmYVDUzji02u1o7sEdRWc1Hpkm2/oO/9275rJg1w==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/hooks" "^4.10.0"
gettext-parser "^1.3.1"
memize "^2.1.0"
sprintf-js "^1.1.1"
tannin "^1.2.0"
"@wordpress/is-shallow-equal@^5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/is-shallow-equal/-/is-shallow-equal-5.10.0.tgz#71f2cc3ad3fc7cfc509dc1837dd0bc7c2b953917"
integrity sha512-KOkZzOnmjpH7hzPiaXUjhUlfKIGTzL7qUdNHBC1SFDOYpnRUSw8f1AtWxRpPBHl5dieYVx0x1qjOWjm/DtTOXg==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/jest-console@^8.10.0":
version "8.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/jest-console/-/jest-console-8.10.0.tgz#80c1942bb0effbbed1a831c2a71d046c36a83369"
@ -2583,6 +2755,14 @@
"@wordpress/jest-console" "^8.10.0"
babel-jest "29.7.0"
"@wordpress/keycodes@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/keycodes/-/keycodes-4.10.0.tgz#306b56a9188d6545f669acbe5a1225e547d49d10"
integrity sha512-2i+N90HBMqQegtGqeVB8pJz8ZgKAY1eZmQegE9MXczYVac85DDOoxhY/41c44s6Kwl3waJ2Zght6UXE0OUFMxw==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/i18n" "^5.10.0"
"@wordpress/npm-package-json-lint-config@^5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.10.0.tgz#5b8951370c82b74d71e38023df29059e4c4fa552"
@ -2601,6 +2781,31 @@
resolved "https://registry.yarnpkg.com/@wordpress/prettier-config/-/prettier-config-4.10.0.tgz#33376a39005b1645287c511853aa9666ce0e7b78"
integrity sha512-zT06uXepAWoXiBY8t1M5dz+DcyZ00Sm005YTJvjrLeMRLCEX9lZuZtqA/rYZsABzT90KJvdDTNP+2FsoZSOQcQ==
"@wordpress/priority-queue@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/priority-queue/-/priority-queue-3.10.0.tgz#63ce26931279b5a863520feb29e7199da366463e"
integrity sha512-Gjbw5NmRLrZ9KkiROJlL4I/s96bMlpd7gGkQbcCyyeLIZduGxQDzI4Jih5s0Xrm7Gj8WFd57wRDe/voZJR0ZsQ==
dependencies:
"@babel/runtime" "7.25.7"
requestidlecallback "^0.3.0"
"@wordpress/private-apis@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/private-apis/-/private-apis-1.10.0.tgz#90d69128566a0a1d0b41e510098b4ed99892380a"
integrity sha512-gH6ZHmkc01MC431nMyjxFmU/77jVliOwjuv6SffQUgHMJyM75LiKC8CU8LEeLWbn3obG87m/n7Quj5p2MjtaeA==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/redux-routine@^5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/redux-routine/-/redux-routine-5.10.0.tgz#08ba44240040eec18e2c2b64948a488d9cbad581"
integrity sha512-oDqZDjz8H/bt02IEoIZCwsUsL17UOEnMg/heV0PoJxo3k5MTrvqJqzgBLoSC0PFzx/pwOo4TwvwCL+kjjm5gCQ==
dependencies:
"@babel/runtime" "7.25.7"
is-plain-object "^5.0.0"
is-promise "^4.0.0"
rungen "^0.3.2"
"@wordpress/scripts@^30.3.0":
version "30.3.0"
resolved "https://registry.yarnpkg.com/@wordpress/scripts/-/scripts-30.3.0.tgz#d3ab491de79b31a2a12490ff08ef6b1f7db18619"
@ -2677,6 +2882,22 @@
stylelint-config-recommended "^14.0.1"
stylelint-config-recommended-scss "^14.1.0"
"@wordpress/undo-manager@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/undo-manager/-/undo-manager-1.10.0.tgz#beec1d89d44e01e3fa3fa01b28eafe897101c47d"
integrity sha512-WaLwZ+AlfXQm9PhLf6kwCBaD5DoKaIqelRsgAaqa4APjgMBlxktQ1dadime0CO9+e8R2kLwAE3rxQXhGjicRMw==
dependencies:
"@babel/runtime" "7.25.7"
"@wordpress/is-shallow-equal" "^5.10.0"
"@wordpress/url@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/url/-/url-4.10.0.tgz#6ac02f3de91bfcce06378ba4a8038e3ccd08551b"
integrity sha512-SKlXocsTlaSee2trXcB0N3jdIfEGMnPiqNxxvTjeeBmsP/47MMXu5lXYslYyYlQbluhAR5/RMf0o3WqZAF2uOg==
dependencies:
"@babel/runtime" "7.25.7"
remove-accents "^0.5.0"
"@wordpress/warning@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@wordpress/warning/-/warning-3.10.0.tgz#29276d268cb89f554df3bc1ae48eab27787c8337"
@ -3544,6 +3765,15 @@ clean-webpack-plugin@^3.0.0:
"@types/webpack" "^4.4.31"
del "^4.1.1"
clipboard@^2.0.11:
version "2.0.11"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@ -4002,6 +4232,11 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
cwd@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567"
@ -4146,7 +4381,7 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^4.2.2, deepmerge@^4.3.1:
deepmerge@^4.2.2, deepmerge@^4.3.0, deepmerge@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
@ -4208,6 +4443,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -4383,6 +4623,13 @@ encodeurl@~2.0.0:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
encoding@^0.1.12:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -4426,6 +4673,11 @@ envinfo@^7.7.3:
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae"
integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==
equivalent-key-map@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz#be4d57049bb8d46a81d6e256c1628465620c2a13"
integrity sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -5342,6 +5594,14 @@ get-uri@^6.0.1:
debug "^4.3.4"
fs-extra "^11.2.0"
gettext-parser@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/gettext-parser/-/gettext-parser-1.4.0.tgz#f8baf34a292f03d5e42f02df099d301f167a7ace"
integrity sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==
dependencies:
encoding "^0.1.12"
safe-buffer "^5.1.1"
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -5467,6 +5727,13 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==
dependencies:
delegate "^3.1.2"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@ -5707,7 +5974,7 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3, iconv-lite@^0.6.3:
iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@ -6071,6 +6338,11 @@ is-potential-custom-element-name@^1.0.1:
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
is-promise@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@ -7194,6 +7466,11 @@ memfs@^3.4.3:
dependencies:
fs-monkey "^1.0.4"
memize@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/memize/-/memize-2.1.0.tgz#6ddd4717887d94825748149ece00d04cf868ce0d"
integrity sha512-yywVJy8ctVlN5lNPxsep5urnZ6TTclwPEyigM9M3Bi8vseJBOfqNrGWN/r8NzuIt3PovM323W04blJfGQfQSVg==
meow@^13.2.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f"
@ -7367,6 +7644,11 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mousetrap@^1.6.5:
version "1.6.5"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==
mrmime@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4"
@ -8423,6 +8705,14 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-dom@^18.3.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -8520,6 +8810,13 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
redux@^4.1.2:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
reflect.getprototypeof@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859"
@ -8596,6 +8893,21 @@ regjsparser@^0.11.0:
dependencies:
jsesc "~3.0.2"
rememo@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/rememo/-/rememo-4.0.2.tgz#8af1f09fd3bf5809ca0bfd0b803926c67ead8c1e"
integrity sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ==
remove-accents@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687"
integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==
requestidlecallback@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/requestidlecallback/-/requestidlecallback-0.3.0.tgz#6fb74e0733f90df3faa4838f9f6a2a5f9b742ac5"
integrity sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -8735,6 +9047,11 @@ run-parallel@^1.1.4, run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rungen@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/rungen/-/rungen-0.3.2.tgz#400c09ebe914e7b17e0b6ef3263400fc2abc7cb3"
integrity sha512-zWl10xu2D7zoR8zSC2U6bg5bYF6T/Wk7rxwp8IPaJH7f0Ge21G03kNHVgHR7tyVkSSfAOG0Rqf/Cl38JftSmtw==
rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
@ -8757,7 +9074,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -8801,6 +9118,13 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
@ -8825,6 +9149,11 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
selfsigned@^2.1.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
@ -9190,7 +9519,7 @@ speedline-core@^1.4.3:
image-ssim "^0.2.0"
jpeg-js "^0.4.1"
sprintf-js@^1.1.3:
sprintf-js@^1.1.1, sprintf-js@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
@ -9541,6 +9870,13 @@ table@^6.8.2:
string-width "^4.2.3"
strip-ansi "^6.0.1"
tannin@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tannin/-/tannin-1.2.0.tgz#1da6fe65280dca4c3d84efb075b077b1b94362a6"
integrity sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA==
dependencies:
"@tannin/plural-forms" "^1.1.0"
tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
@ -9630,6 +9966,11 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@ -9958,6 +10299,11 @@ urlpattern-polyfill@10.0.0:
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz#f0a03a97bfb03cdf33553e5e79a2aadd22cac8ec"
integrity sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==
use-memo-one@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"

View file

@ -0,0 +1,53 @@
import {
hide,
show,
} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
document.addEventListener( 'DOMContentLoaded', function () {
const refundButton = document.querySelector( 'button.refund-items' );
if ( ! refundButton ) {
return;
}
refundButton.insertAdjacentHTML(
'afterend',
`<button class="button" type="button" id="pcpVoid">${ PcpVoidButton.button_text }</button>`
);
hide( refundButton );
const voidButton = document.querySelector( '#pcpVoid' );
voidButton.addEventListener( 'click', async () => {
if ( ! window.confirm( PcpVoidButton.popup_text ) ) {
return;
}
voidButton.setAttribute( 'disabled', 'disabled' );
const res = await fetch( PcpVoidButton.ajax.void.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: PcpVoidButton.ajax.void.nonce,
wc_order_id: PcpVoidButton.wc_order_id,
} ),
} );
const data = await res.json();
if ( ! data.success ) {
hide( voidButton );
show( refundButton );
alert( PcpVoidButton.error_text );
throw Error( data.data.message );
}
location.reload();
} );
} );

View file

@ -25,8 +25,10 @@ use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction;
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater;
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactory;
@ -46,7 +48,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSessionId;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
@ -515,12 +516,20 @@ return array(
);
},
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {
$order_endpoint = $container->get( 'api.endpoint.order' );
$payments_endpoint = $container->get( 'api.endpoint.payments' );
$refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
$prefix = $container->get( 'api.prefix' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new RefundProcessor( $order_endpoint, $payments_endpoint, $refund_fees_updater, $prefix, $logger );
return new RefundProcessor(
$container->get( 'api.endpoint.order' ),
$container->get( 'api.endpoint.payments' ),
$container->get( 'wcgateway.helper.refund-fees-updater' ),
$container->get( 'wcgateway.allowed_refund_payment_methods' ),
$container->get( 'api.prefix' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.allowed_refund_payment_methods' => static function ( ContainerInterface $container ): array {
return apply_filters(
'woocommerce_paypal_payments_allowed_refund_payment_methods',
array( PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID, PayUponInvoiceGateway::ID )
);
},
'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor {
$order_endpoint = $container->get( 'api.endpoint.order' );
@ -1421,7 +1430,10 @@ return array(
return new OXXO(
$container->get( 'wcgateway.checkout-helper' ),
$container->get( 'wcgateway.url' ),
$container->get( 'ppcp.asset-version' )
$container->get( 'ppcp.asset-version' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'api.factory.capture' )
);
},
'wcgateway.oxxo-gateway' => static function( ContainerInterface $container ): OXXOGateway {
@ -1916,6 +1928,24 @@ return array(
return $simple_redirect_tasks;
},
'wcgateway.void-button.assets' => function( ContainerInterface $container ) : VoidButtonAssets {
return new VoidButtonAssets(
$container->get( 'wcgateway.url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.allowed_refund_payment_methods' )
);
},
'wcgateway.void-button.endpoint' => function( ContainerInterface $container ) : VoidOrderEndpoint {
return new VoidOrderEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.settings.admin-settings-enabled' => static function( ContainerInterface $container ): bool {
return $container->has( 'settings.url' );
},

View file

@ -0,0 +1,171 @@
<?php
/**
* Register and configure assets for the void button.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Assets
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
use Exception;
use WC_AJAX;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WP_Screen;
/**
* Class VoidButtonAssets
*/
class VoidButtonAssets {
/**
* The URL of this module.
*
* @var string
*/
private $module_url;
/**
* The assets version.
*
* @var string
*/
private $version;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The Refund Processor.
*
* @var RefundProcessor
*/
private $refund_processor;
/**
* The methods that can be refunded.
*
* @var array
*/
private $allowed_refund_payment_methods;
/**
* VoidButtonAssets constructor.
*
* @param string $module_url The url of this module.
* @param string $version The assets version.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param array $allowed_refund_payment_methods The methods that can be refunded.
*/
public function __construct(
string $module_url,
string $version,
OrderEndpoint $order_endpoint,
RefundProcessor $refund_processor,
array $allowed_refund_payment_methods
) {
$this->module_url = $module_url;
$this->version = $version;
$this->order_endpoint = $order_endpoint;
$this->refund_processor = $refund_processor;
$this->allowed_refund_payment_methods = $allowed_refund_payment_methods;
}
/**
* Checks if should register assets on the current page.
*/
public function should_register(): bool {
if ( ! is_admin() || wp_doing_ajax() ) {
return false;
}
global $theorder;
if ( ! $theorder instanceof WC_Order ) {
return false;
}
$current_screen = get_current_screen();
if ( ! $current_screen instanceof WP_Screen ) {
return false;
}
if ( $current_screen->post_type !== 'shop_order' ) {
return false;
}
if ( ! in_array( $theorder->get_payment_method(), $this->allowed_refund_payment_methods, true ) ) {
return false;
}
// Skip if there are refunds already, it is probably not voidable anymore + void cannot be partial.
if ( $theorder->get_remaining_refund_amount() !== $theorder->get_total() ) {
return false;
}
$order_id = $theorder->get_meta( PayPalGateway::ORDER_ID_META_KEY );
if ( ! $order_id ) {
return false;
}
try {
$order = $this->order_endpoint->order( $order_id );
if ( $this->refund_processor->determine_refund_mode( $order ) !== RefundProcessor::REFUND_MODE_VOID ) {
return false;
}
} catch ( Exception $exception ) {
return false;
}
return true;
}
/**
* Enqueues the assets.
*/
public function register(): void {
global $theorder;
assert( $theorder instanceof WC_Order );
wp_enqueue_script(
'ppcp-void-button',
trailingslashit( $this->module_url ) . 'assets/js/void-button.js',
array(),
$this->version,
true
);
wp_localize_script(
'ppcp-void-button',
'PcpVoidButton',
array(
'button_text' => __( 'Void authorization', 'woocommerce-paypal-payments' ),
'popup_text' => __(
'After voiding an authorized transaction, you cannot capture any funds associated with that transaction, and the funds are returned to the customer. Voiding an authorization cancels the entire open amount.',
'woocommerce-paypal-payments'
),
'error_text' => __(
'The operation failed. Use the Refund button if the funds were already captured.',
'woocommerce-paypal-payments'
),
'wc_order_id' => $theorder->get_id(),
'ajax' => array(
'void' => array(
'endpoint' => WC_AJAX::get_endpoint( VoidOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( VoidOrderEndpoint::nonce() ),
),
),
),
);
}
}

View file

@ -97,7 +97,7 @@ class DisableGateways {
if ( ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) ) {
unset( $methods[ CardButtonGateway::ID ] );
if ( ! $this->session_handler->order() && is_checkout() ) {
if ( $this->subscription_helper->cart_contains_subscription() ) {
unset( $methods[ PayPalGateway::ID ] );
}
}

View file

@ -112,6 +112,7 @@ class ReturnUrlEndpoint {
}
if ( $wc_order->get_payment_method() === OXXOGateway::ID ) {
$this->session_handler->destroy_session_data();
wp_safe_redirect( wc_get_checkout_url() );
exit();
}

View file

@ -0,0 +1,198 @@
<?php
/**
* The Void Order endpoint.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Order_Item_Fee;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
/**
* Class VoidOrderEndpoint
*/
class VoidOrderEndpoint {
const ENDPOINT = 'ppc-void-order';
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The Refund Processor.
*
* @var RefundProcessor
*/
private $refund_processor;
/**
* The Request Data Helper.
*
* @var RequestData
*/
private $request_data;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* VoidOrderEndpoint constructor.
*
* @param RequestData $request_data The Request Data Helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
RefundProcessor $refund_processor,
LoggerInterface $logger
) {
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->refund_processor = $refund_processor;
$this->logger = $logger;
}
/**
* Returns the nonce.
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the incoming request.
*/
public function handle_request(): void {
$request = $this->request_data->read_request( self::nonce() );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error(
array(
'message' => 'Invalid request.',
)
);
return;
}
$wc_order_id = (int) $request['wc_order_id'];
$wc_order = wc_get_order( $wc_order_id );
if ( ! $wc_order instanceof WC_Order ) {
wp_send_json_error(
array(
'message' => 'WC order not found.',
)
);
return;
}
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
if ( ! $order_id ) {
wp_send_json_error(
array(
'message' => 'PayPal order ID not found in meta.',
)
);
return;
}
try {
$order = $this->order_endpoint->order( $order_id );
$this->refund_processor->void( $order );
$this->make_refunded( $wc_order );
} catch ( Exception $exception ) {
wp_send_json_error(
array(
'message' => 'Void failed. ' . $exception->getMessage(),
)
);
$this->logger->error( 'Void failed. ' . $exception->getMessage() );
return;
}
wp_send_json_success();
}
/**
* Returns the list of items for the wc_create_refund data,
* making all items refunded (max qty, total, taxes).
*
* @param WC_Order $wc_order The WC order.
*/
protected function refund_items( WC_Order $wc_order ): array {
$refunded_items = array();
foreach ( $wc_order->get_items( array( 'line_item', 'fee', 'shipping' ) ) as $item ) {
// Some methods like get_taxes() are not defined in WC_Order_Item.
if (
! $item instanceof WC_Order_Item_Product
&& ! $item instanceof WC_Order_Item_Fee
&& ! $item instanceof WC_Order_Item_Shipping
) {
continue;
}
$taxes = array();
$item_taxes = $item->get_taxes();
/**
* The type is not really guaranteed in the code.
*
* @psalm-suppress RedundantConditionGivenDocblockType
*/
if ( is_array( $item_taxes ) && isset( $item_taxes['total'] ) ) {
$taxes = $item_taxes['total'];
}
$refunded_items[ $item->get_id() ] = array(
'qty' => $item->get_type() === 'line_item' ? $item->get_quantity() : 0,
'refund_total' => $item->get_total(),
'refund_tax' => $taxes,
);
}
return $refunded_items;
}
/**
* Creates a full refund.
*
* @param WC_Order $wc_order The WC order.
*/
private function make_refunded( WC_Order $wc_order ): void {
wc_create_refund(
array(
'amount' => $wc_order->get_total(),
'reason' => __( 'Voided authorization', 'woocommerce-paypal-payments' ),
'order_id' => $wc_order->get_id(),
'line_items' => $this->refund_items( $wc_order ),
'refund_payment' => false,
'restock_items' => (bool) apply_filters( 'woocommerce_paypal_payments_void_restock_items', true ),
)
);
$wc_order->set_status( 'refunded' );
}
}

View file

@ -10,13 +10,19 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
/**
* Class OXXO.
*/
class OXXO {
public const ID = 'ppcp-oxxo-gateway';
/**
* The checkout helper.
@ -40,21 +46,51 @@ class OXXO {
protected $asset_version;
/**
* OXXO constructor.
* The order endpoint.
*
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param string $module_url The module URL.
* @param string $asset_version The asset version.
* @var OrderEndpoint
*/
protected $order_endpoint;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* The capture factory.
*
* @var CaptureFactory
*/
protected $capture_factory;
/**
* OXXO constructor
*
* @param CheckoutHelper $checkout_helper The checkout helper.
* @param string $module_url The module URL.
* @param string $asset_version The asset version.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param LoggerInterface $logger The logger.
* @param CaptureFactory $capture_factory The capture factory.
*/
public function __construct(
CheckoutHelper $checkout_helper,
string $module_url,
string $asset_version
string $asset_version,
OrderEndpoint $order_endpoint,
LoggerInterface $logger,
CaptureFactory $capture_factory
) {
$this->checkout_helper = $checkout_helper;
$this->module_url = $module_url;
$this->asset_version = $asset_version;
$this->order_endpoint = $order_endpoint;
$this->logger = $logger;
$this->capture_factory = $capture_factory;
}
/**
@ -199,6 +235,46 @@ class OXXO {
}
}
);
/**
* Process PayPal fees
*/
add_action(
'woocommerce_paypal_payments_payment_capture_completed_webhook_handler',
function ( WC_Order $wc_order, string $order_id ) {
try {
if ( $wc_order->get_payment_method() !== OXXO::ID ) {
return;
}
$order = $this->order_endpoint->order( $order_id );
$payments = $order->purchase_units()[0]->payments();
if ( ! $payments ) {
return;
}
$capture = $payments->captures()[0] ?? null;
if ( $capture ) {
$breakdown = $capture->seller_receivable_breakdown();
if ( $breakdown ) {
$wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() );
$paypal_fee = $breakdown->paypal_fee();
if ( $paypal_fee ) {
$wc_order->update_meta_data( 'PayPal Transaction Fee', (string) $paypal_fee->value() );
}
$wc_order->save_meta_data();
}
}
} catch ( RuntimeException $exception ) {
$this->logger->error( $exception->getMessage() );
}
},
10,
2
);
}
/**

View file

@ -366,7 +366,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
'type' => 'checkbox',
'desc_tip' => true,
'description' => __( 'In order to use PayPal or Advanced Card Processing, you need to enable the Gateway.', 'woocommerce-paypal-payments' ),
'label' => __( 'Enable PayPal features for your store', 'woocommerce-paypal-payments' ),
'label' => __( 'Enable the PayPal gateway and more features for your store.', 'woocommerce-paypal-payments' ),
'default' => 'no',
),
'ppcp' => array(

View file

@ -33,9 +33,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
class RefundProcessor {
use RefundMetaTrait;
private const REFUND_MODE_REFUND = 'refund';
private const REFUND_MODE_VOID = 'void';
private const REFUND_MODE_UNKNOWN = 'unknown';
public const REFUND_MODE_REFUND = 'refund';
public const REFUND_MODE_VOID = 'void';
public const REFUND_MODE_UNKNOWN = 'unknown';
/**
* The order endpoint.
@ -72,12 +72,20 @@ class RefundProcessor {
*/
private $refund_fees_updater;
/**
* The methods that can be refunded.
*
* @var array
*/
private $allowed_refund_payment_methods;
/**
* RefundProcessor constructor.
*
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
* @param array $allowed_refund_payment_methods The methods that can be refunded.
* @param string $prefix The prefix.
* @param LoggerInterface $logger The logger.
*/
@ -85,15 +93,17 @@ class RefundProcessor {
OrderEndpoint $order_endpoint,
PaymentsEndpoint $payments_endpoint,
RefundFeesUpdater $refund_fees_updater,
array $allowed_refund_payment_methods,
string $prefix,
LoggerInterface $logger
) {
$this->order_endpoint = $order_endpoint;
$this->payments_endpoint = $payments_endpoint;
$this->refund_fees_updater = $refund_fees_updater;
$this->prefix = $prefix;
$this->logger = $logger;
$this->order_endpoint = $order_endpoint;
$this->payments_endpoint = $payments_endpoint;
$this->refund_fees_updater = $refund_fees_updater;
$this->allowed_refund_payment_methods = $allowed_refund_payment_methods;
$this->prefix = $prefix;
$this->logger = $logger;
}
/**
@ -109,11 +119,7 @@ class RefundProcessor {
*/
public function process( WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
try {
$allowed_refund_payment_methods = apply_filters(
'woocommerce_paypal_payments_allowed_refund_payment_methods',
array( PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID, PayUponInvoiceGateway::ID )
);
if ( ! in_array( $wc_order->get_payment_method(), $allowed_refund_payment_methods, true ) ) {
if ( ! in_array( $wc_order->get_payment_method(), $this->allowed_refund_payment_methods, true ) ) {
return true;
}
@ -134,7 +140,7 @@ class RefundProcessor {
)
);
$mode = $this->determine_refund_mode( $payments );
$mode = $this->determine_refund_mode( $order );
switch ( $mode ) {
case self::REFUND_MODE_REFUND:
@ -226,11 +232,13 @@ class RefundProcessor {
/**
* Determines the refunding mode.
*
* @param Payments $payments The order payments state.
* @param Order $order The order.
*
* @return string One of the REFUND_MODE_ constants.
*/
private function determine_refund_mode( Payments $payments ): string {
public function determine_refund_mode( Order $order ): string {
$payments = $this->get_payments( $order );
$authorizations = $payments->authorizations();
if ( $authorizations ) {
foreach ( $authorizations as $authorization ) {

View file

@ -226,14 +226,11 @@ return function ( ContainerInterface $container, array $fields ): array {
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'Customize the appearance of the PayPal smart buttons on the %1$sClassic Checkout page%2$s.
%3$sCheckout Buttons must be enabled to display the PayPal gateway on the Checkout page.
',
'Customize the appearance of the PayPal smart buttons on the %1$sClassic Checkout page%2$s.',
'woocommerce-paypal-payments'
),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-checkout" target="_blank">',
'</a>',
'<br />'
'</a>'
),
'type' => 'ppcp-heading',
'screens' => array(

View file

@ -21,7 +21,9 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait;
use WC_Order;
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
@ -90,6 +92,7 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
$this->register_columns( $c );
$this->register_checkout_paypal_address_preset( $c );
$this->register_wc_tasks( $c );
$this->register_void_button( $c );
add_action(
'woocommerce_sections_checkout',
@ -848,21 +851,19 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
* @return void
*/
protected function register_wc_tasks( ContainerInterface $container ): void {
$simple_redirect_tasks = $container->get( 'wcgateway.settings.wc-tasks.simple-redirect-tasks' );
if ( empty( $simple_redirect_tasks ) ) {
return;
}
$task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' );
assert( $task_registrar instanceof TaskRegistrarInterface );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
add_action(
'init',
static function () use ( $simple_redirect_tasks, $task_registrar, $logger ): void {
static function () use ( $container ): void {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
try {
$simple_redirect_tasks = $container->get( 'wcgateway.settings.wc-tasks.simple-redirect-tasks' );
if ( empty( $simple_redirect_tasks ) ) {
return;
}
$task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' );
assert( $task_registrar instanceof TaskRegistrarInterface );
$task_registrar->register( $simple_redirect_tasks );
} catch ( Exception $exception ) {
$logger->error( "Failed to create a task in the 'Things to do next' section of WC. " . $exception->getMessage() );
@ -870,4 +871,33 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
},
);
}
/**
* Registers the assets and ajax endpoint for the void button.
*
* @param ContainerInterface $container The container.
*/
protected function register_void_button( ContainerInterface $container ): void {
add_action(
'admin_enqueue_scripts',
static function () use ( $container ) {
$assets = $container->get( 'wcgateway.void-button.assets' );
assert( $assets instanceof VoidButtonAssets );
if ( $assets->should_register() ) {
$assets->register();
}
}
);
add_action(
'wc_ajax_' . VoidOrderEndpoint::ENDPOINT,
static function () use ( $container ) {
$endpoint = $container->get( 'wcgateway.void-button.endpoint' );
assert( $endpoint instanceof VoidOrderEndpoint );
$endpoint->handle_request();
}
);
}
}

View file

@ -10,6 +10,7 @@ module.exports = {
'gateway-settings': path.resolve('./resources/js/gateway-settings.js'),
'fraudnet': path.resolve('./resources/js/fraudnet.js'),
'oxxo': path.resolve('./resources/js/oxxo.js'),
'void-button': path.resolve('./resources/js/void-button.js'),
'gateway-settings-style': path.resolve('./resources/css/gateway-settings.scss'),
'common-style': path.resolve('./resources/css/common.scss'),
},

View file

@ -17,6 +17,7 @@ use WC_Product_Subscription_Variation;
use WC_Subscription;
use WC_Subscriptions;
use WC_Subscriptions_Product;
use WCS_Manual_Renewal_Manager;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
/**
@ -83,20 +84,15 @@ class SubscriptionHelper {
}
/**
* Whether only automatic payment gateways are accepted.
* Whether manual renewals are accepted.
*
* @return bool
*/
public function accept_only_automatic_payment_gateways(): bool {
if ( ! $this->plugin_is_active() ) {
public function accept_manual_renewals(): bool {
if ( ! class_exists( WCS_Manual_Renewal_Manager::class ) ) {
return false;
}
$accept_manual_renewals = 'no' !== get_option(
\WC_Subscriptions_Admin::$option_prefix . '_accept_manual_renewals',
'no'
);
return ! $accept_manual_renewals;
return WCS_Manual_Renewal_Manager::is_manual_renewal_enabled();
}
/**

View file

@ -99,7 +99,7 @@ return array(
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory, $payment_token_helper ),
new VaultPaymentTokenDeleted( $logger ),
new PaymentCapturePending( $logger ),
new PaymentSaleCompleted( $logger ),
new PaymentSaleCompleted( $logger, $container->get( 'paypal-subscriptions.renewal-handler' ) ),
new PaymentSaleRefunded( $logger, $refund_fees_updater ),
new BillingSubscriptionCancelled( $logger ),
new BillingPlanPricingChangeActivated( $logger ),

View file

@ -10,8 +10,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WC_Data_Exception;
use WooCommerce\PayPalCommerce\PayPalSubscriptions\RenewalHandler;
use WP_REST_Request;
use WP_REST_Response;
@ -20,7 +20,14 @@ use WP_REST_Response;
*/
class PaymentSaleCompleted implements RequestHandler {
use TransactionIdHandlingTrait, RequestHandlerTrait;
use RequestHandlerTrait;
/**
* Renewal handler.
*
* @var RenewalHandler
*/
private $renewal_handler;
/**
* The logger.
@ -33,9 +40,11 @@ class PaymentSaleCompleted implements RequestHandler {
* PaymentSaleCompleted constructor.
*
* @param LoggerInterface $logger The logger.
* @param RenewalHandler $renewal_handler Renewal handler.
*/
public function __construct( LoggerInterface $logger ) {
$this->logger = $logger;
public function __construct( LoggerInterface $logger, RenewalHandler $renewal_handler ) {
$this->logger = $logger;
$this->renewal_handler = $renewal_handler;
}
/**
@ -68,7 +77,7 @@ class PaymentSaleCompleted implements RequestHandler {
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
if ( is_null( $request['resource'] ) ) {
return $this->failure_response();
return $this->failure_response( 'Could not retrieve resource.' );
}
if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
@ -85,7 +94,7 @@ class PaymentSaleCompleted implements RequestHandler {
return $this->failure_response( 'Could not retrieve transaction id for subscription.' );
}
$args = array(
$args = array(
// phpcs:ignore WordPress.DB.SlowDBQuery
'meta_query' => array(
array(
@ -95,24 +104,13 @@ class PaymentSaleCompleted implements RequestHandler {
),
),
);
$subscriptions = wcs_get_subscriptions( $args );
foreach ( $subscriptions as $subscription ) {
$is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
if ( $is_renewal ) {
$renewal_order = wcs_create_renewal_order( $subscription );
if ( is_a( $renewal_order, WC_Order::class ) ) {
$renewal_order->set_payment_method( $subscription->get_payment_method() );
$renewal_order->payment_complete();
$this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
break;
}
}
$parent_order = wc_get_order( $subscription->get_parent() );
if ( is_a( $parent_order, WC_Order::class ) ) {
$subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
$subscription->save_meta_data();
$this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
$subscriptions = wcs_get_subscriptions( $args );
if ( $subscriptions ) {
try {
$this->renewal_handler->process( $subscriptions, $transaction_id );
} catch ( WC_Data_Exception $exception ) {
return $this->failure_response( 'Could not update payment method.' );
}
}