This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,176 @@
<?php
/**
* Class Blocks_Data_Extractor
*
* @package WooCommerce\Payments
*/
namespace WCPay;
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
defined( 'ABSPATH' ) || exit; // block direct access.
/**
* Extract data fields from certain block based plugins.
*/
class Blocks_Data_Extractor {
/**
* Instance of the integration registry.
*
* @var IntegrationRegistry
*/
private $integration_registry;
/**
* Constructor.
*/
public function __construct() {
$this->integration_registry = new IntegrationRegistry();
}
/**
* Get a list of available Blocks.
*
* @return array
*/
private function get_available_blocks() {
$blocks = [];
if ( class_exists( '\AutomateWoo\Blocks\Marketing_Optin_Block' ) ) {
// phpcs:ignore
/**
* @psalm-suppress UndefinedClass
* @phpstan-ignore-next-line
*/
$blocks[] = new \Automatewoo\Blocks\Marketing_Optin_Block();
}
if ( class_exists( '\Mailchimp_Woocommerce_Newsletter_Blocks_Integration' ) ) {
// phpcs:ignore
/**
* @psalm-suppress UndefinedClass
* @phpstan-ignore-next-line
*/
$blocks[] = new \Mailchimp_Woocommerce_Newsletter_Blocks_Integration();
}
if ( class_exists( '\WCK\Blocks\CheckoutIntegration' ) ) {
// phpcs:ignore
/**
* @psalm-suppress UndefinedClass
* @phpstan-ignore-next-line
*/
$blocks[] = new \WCK\Blocks\CheckoutIntegration();
}
return $blocks;
}
/**
* Register all the blocks.
*
* @param array $blocks A list of blocks to register.
* @return void
*/
private function register_blocks( $blocks ) {
foreach ( $blocks as $block ) {
$this->integration_registry->register( $block );
}
}
/**
* Unregister all blocks.
*
* @param array $blocks A list of blocks to unregister.
* @return void
*/
private function unregister_blocks( $blocks ) {
foreach ( $blocks as $block ) {
$this->integration_registry->unregister( $block );
}
}
/**
* Mailpoet's block registration is different from the other two plugins. Data fields are passed
* from the parent class. This method fetches the data fields without registering the plugin.
*
* @return array
*/
private function get_mailpoet_data() {
// phpcs:ignore
/**
* We check whether relevant MailPoet classes exists before invoking this method.
*
* @psalm-suppress UndefinedClass
* @phpstan-ignore-next-line
*/
$mailpoet_wc_subscription = \MailPoet\DI\ContainerWrapper::getInstance()->get( \MailPoet\WooCommerce\Subscription::class );
// phpcs:ignore
/**
* @psalm-suppress UndefinedClass
* @phpstan-ignore-next-line
*/
$settings_instance = \MailPoet\Settings\SettingsController::getInstance();
$settings = [
'defaultText' => $settings_instance->get( 'woocommerce.optin_on_checkout.message', '' ),
'optinEnabled' => $settings_instance->get( 'woocommerce.optin_on_checkout.enabled', false ),
'defaultStatus' => false,
];
if ( version_compare( \MAILPOET_VERSION, '4.18.0', '<=' ) ) {
$settings['defaultStatus'] = $mailpoet_wc_subscription->isCurrentUserSubscribed();
}
return $settings;
}
/**
* Retrieve data fields.
*
* @return array
*/
public function get_data() {
$blocks = $this->get_available_blocks();
$this->register_blocks( $blocks );
$blocks_data = $this->integration_registry->get_all_registered_script_data();
if ( class_exists( 'MailPoet\DI\ContainerWrapper' ) && class_exists( 'MailPoet\WooCommerce\Subscription' ) ) {
$blocks_data += [ 'mailpoet_data' => $this->get_mailpoet_data() ];
}
$this->unregister_blocks( $blocks );
return $blocks_data;
}
/**
* Retrieves the namespaces in the Store API checkout schema.
*
* @return array
*/
public function get_checkout_schema_namespaces(): array {
$namespaces = [];
if (
class_exists( 'Automattic\WooCommerce\StoreApi\StoreApi' ) &&
class_exists( 'Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema' ) &&
class_exists( 'Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema' )
) {
try {
$checkout_schema = StoreApi::container()->get( ExtendSchema::class )->get_endpoint_schema( CheckoutSchema::IDENTIFIER );
} catch ( \Exception $e ) {
return $namespaces;
}
$namespaces = array_keys( (array) $checkout_schema );
}
return $namespaces;
}
}
@@ -0,0 +1,186 @@
<?php
/**
* Class WC_Payments_Currency_Manager
*
* @package WooCommerce\Payments
*/
namespace WCPay;
use WC_Payment_Gateway_WCPay;
use WCPay\Constants\Payment_Method;
defined( 'ABSPATH' ) || exit;
/**
* It ensures that when a payment method is added and multi-currency is enabled, the needed currency is also added.
*/
class WC_Payments_Currency_Manager {
/**
* The WCPay gateway class instance.
*
* @var WC_Payment_Gateway_WCPay
*/
private $gateway;
/**
* Constructor
*
* @param WC_Payment_Gateway_WCPay $gateway The WCPay gateway class instance.
*/
public function __construct( WC_Payment_Gateway_WCPay $gateway ) {
$this->gateway = $gateway;
}
/**
* Initializes this class' WP hooks.
*
* @return void
*/
public function init_hooks() {
add_action( 'update_option_woocommerce_woocommerce_payments_settings', [ $this, 'maybe_add_missing_currencies' ] );
add_action( 'admin_head', [ $this, 'add_payment_method_currency_dependencies_script' ] );
}
/**
* Gets the multi-currency instance or returns null if it's not available.
* This method allows for easier testing by allowing the multi-currency instance to be mocked.
*
* @return \WCPay\MultiCurrency\MultiCurrency|null
*/
public function get_multi_currency_instance() {
if ( ! function_exists( 'WC_Payments_Multi_Currency' ) ) {
return null;
}
if ( ! WC_Payments_Multi_Currency()->is_initialized() ) {
return null;
}
return WC_Payments_Multi_Currency();
}
/**
* Returns the currencies needed per enabled payment method
*
* @return array The currencies keyed with the related payment method
*/
public function get_enabled_payment_method_currencies() {
$enabled_payment_method_ids = $this->gateway->get_upe_enabled_payment_method_ids();
$account_currency = $this->gateway->get_account_domestic_currency();
$payment_methods_needing_currency = array_reduce(
$enabled_payment_method_ids,
function ( $result, $method ) use ( $account_currency ) {
if ( in_array( $method, [ 'card', 'card_present' ], true ) ) {
return $result;
}
try {
$method_key = Payment_Method::search( $method );
} catch ( \InvalidArgumentException $e ) {
return $result;
}
$class_key = ucfirst( strtolower( $method_key ? $method_key : $method ) );
$class_name = "\\WCPay\\Payment_Methods\\{$class_key}_Payment_Method";
if ( ! class_exists( $class_name ) ) {
return $result;
}
$payment_method_instance = new $class_name( null );
$result[ $method ] = [
'currencies' => $payment_method_instance->has_domestic_transactions_restrictions() ? [ $account_currency ] : $payment_method_instance->get_currencies(),
'title' => $payment_method_instance->get_title( $this->gateway->get_account_country() ),
];
return $result;
},
[]
);
return $payment_methods_needing_currency;
}
/**
* Ensures that when a payment method is added from the settings, the needed currency is also added.
*/
public function maybe_add_missing_currencies() {
$multi_currency = $this->get_multi_currency_instance();
if ( is_null( $multi_currency ) ) {
return;
}
$payment_methods_needing_currency = $this->get_enabled_payment_method_currencies();
if ( empty( $payment_methods_needing_currency ) ) {
return;
}
$enabled_currencies = $multi_currency->get_enabled_currencies();
$available_currencies = $multi_currency->get_available_currencies();
$missing_currency_codes = [];
// TODO: we need to find something about having a currency not available for the method in case of having disabled currencies in the future.
// First option, not do display it if the available currency is blocked by something else (Stripe, merchant, WCPay etc.)
// Second option, showing a notice that it can't be selected because the currency is not available to use.
// we have payments needing some currency being enabled, let's ensure the currency is present.
foreach ( $payment_methods_needing_currency as $payment_method_data ) {
$needed_currency_codes = $payment_method_data['currencies'];
foreach ( $needed_currency_codes as $needed_currency_code ) {
if ( ! isset( $available_currencies[ $needed_currency_code ] ) ) {
continue;
}
if ( isset( $enabled_currencies[ $needed_currency_code ] ) ) {
continue;
}
$missing_currency_codes[] = $needed_currency_code;
}
}
$missing_currency_codes = array_unique( $missing_currency_codes );
if ( empty( $missing_currency_codes ) ) {
return;
}
/**
* The set_enabled_currencies method throws an exception if any currencies passed are not found in the current available currencies.
* Any currencies not found are filtered out above, so we shouldn't need a try/catch here.
*/
$multi_currency->set_enabled_currencies( array_merge( array_keys( $enabled_currencies ), $missing_currency_codes ) );
}
/**
* Adds the `multiCurrencyPaymentMethodsMap` JS object to the multi-currency settings page.
*
* This object maps currencies to payment methods that require them, so the multi-currency settings page displays a notice in case of dependencies.
*/
public function add_payment_method_currency_dependencies_script() {
$multi_currency = $this->get_multi_currency_instance();
if ( is_null( $multi_currency ) || ! $multi_currency->is_multi_currency_settings_page() ) {
return;
}
$payment_methods_needing_currency = $this->get_enabled_payment_method_currencies();
if ( empty( $payment_methods_needing_currency ) ) {
return;
}
$currency_methods_map = [];
foreach ( $payment_methods_needing_currency as $method => $data ) {
foreach ( $data['currencies'] as $currency ) {
if ( ! isset( $currency_methods_map[ $currency ] ) ) {
$currency_methods_map[ $currency ] = [];
}
$currency_methods_map[ $currency ][ $method ] = $data['title'];
}
}
?>
<script type='text/javascript'>
window.multiCurrencyPaymentMethodsMap = <?php echo wp_json_encode( $currency_methods_map ); ?>;
</script>
<?php
}
}
@@ -0,0 +1,76 @@
<?php
/**
* Main functions to start MultiCurrency class.
*
* @package WooCommerce\Payments
*/
defined( 'ABSPATH' ) || exit;
/**
* Load customer multi-currency if feature is enabled or if it is the setup page.
*/
function wcpay_multi_currency_onboarding_check() {
$is_setup_page = false;
// Skip checking the HTTP referer if it is a cron job.
if ( ! defined( 'DOING_CRON' ) ) {
$http_referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) );
if ( ! empty( $http_referer ) ) {
$is_setup_page = strpos( $http_referer, 'multi-currency-setup' ) !== false;
}
}
return $is_setup_page;
}
if ( ! WC_Payments_Features::is_customer_multi_currency_enabled() && ! wcpay_multi_currency_onboarding_check() ) {
return;
}
/**
* Returns the MultiCurrency singleton.
*
* @return WCPay\MultiCurrency\MultiCurrency
*/
function WC_Payments_Multi_Currency() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
static $instance = null;
if ( is_null( $instance ) ) {
$instance = new WCPay\MultiCurrency\MultiCurrency(
WC_Payments::get_settings_service(),
WC_Payments::get_payments_api_client(),
WC_Payments::get_account_service(),
WC_Payments::get_localization_service(),
WC_Payments::get_database_cache()
);
$instance->init_hooks();
}
return $instance;
}
add_action( 'plugins_loaded', 'WC_Payments_Multi_Currency', 12 );
register_deactivation_hook( WCPAY_PLUGIN_FILE, 'wcpay_multi_currency_deactivated' );
/**
* Plugin deactivation hook.
*/
function wcpay_multi_currency_deactivated() {
WCPay\MultiCurrency\MultiCurrency::remove_woo_admin_notes();
}
if ( ! function_exists( 'wc_get_currency_switcher_markup' ) ) {
/**
* Gets the switcher widget markup.
*
* @param array $instance The widget's instance settings.
* @param array $args The widget's arguments.
*
* @return string The widget markup.
*/
function wc_get_currency_switcher_markup( array $instance = [], array $args = [] ): string {
return WC_Payments_Multi_Currency()->get_switcher_widget_markup( $instance, $args );
}
}
@@ -0,0 +1,138 @@
<?php
/**
* Admin email about payment retry failed due to authentication
*
* Email sent to admins when an attempt to automatically process a subscription renewal payment has failed
* with the `authentication_needed` error, and a retry rule has been applied to retry the payment in the future.
*
* @extends WC_Email_Failed_Order
* @package WooCommerce\Payments
*/
use WCPay\Logger;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* An email sent to the admin when payment fails to go through due to authentication_required error.
*/
class WC_Payments_Email_Failed_Authentication_Retry extends WC_Email_Failed_Order {
/**
* The details of the last retry (if any) recorded for a given order
*
* @var WCS_Retry
*/
private $retry;
/**
* Constructor
*/
public function __construct() {
$this->id = 'failed_authentication_requested';
$this->title = __( 'Payment authentication requested email', 'woocommerce-payments' );
$this->description = __( 'Payment authentication requested emails are sent to chosen recipient(s) when an attempt to automatically process a subscription renewal payment fails because the transaction requires an SCA verification, the customer is requested to authenticate the payment, and a retry rule has been applied to notify the customer again within a certain time period.', 'woocommerce-payments' );
$this->heading = __( 'Automatic renewal payment failed due to authentication required', 'woocommerce-payments' );
$this->subject = __( '[{site_title}] Automatic payment failed for {order_number}. Customer asked to authenticate payment and will be notified again {retry_time}', 'woocommerce-payments' );
$this->template_html = 'failed-renewal-authentication-requested.php';
$this->template_plain = 'plain/failed-renewal-authentication-requested.php';
$this->template_base = __DIR__ . '/emails/';
$this->recipient = $this->get_option( 'recipient', get_option( 'admin_email' ) );
// We want all the parent's methods, with none of its properties, so call its parent's constructor, rather than my parent constructor.
WC_Email::__construct();
}
/**
* Get the default e-mail subject.
*
* @return string
*/
public function get_default_subject() {
return $this->subject;
}
/**
* Get the default e-mail heading.
*
* @return string
*/
public function get_default_heading() {
return $this->heading;
}
/**
* Trigger.
*
* @param int $order_id The order ID.
* @param WC_Order|null $order Order object.
*/
public function trigger( $order_id, $order = null ) {
$this->object = $order;
$this->find['retry-time'] = '{retry_time}';
if ( class_exists( 'WCS_Retry_Manager' ) && function_exists( 'wcs_get_human_time_diff' ) ) {
$this->retry = WCS_Retry_Manager::store()->get_last_retry_for_order( wcs_get_objects_property( $order, 'id' ) );
$this->replace['retry-time'] = wcs_get_human_time_diff( $this->retry->get_time() );
} else {
Logger::log( 'WCS_Retry_Manager class or does not exist. Not able to send admin email about customer notification for authentication required for renewal payment.' );
return;
}
$this->find['order-number'] = '{order_number}';
$this->replace['order-number'] = $this->object->get_order_number();
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
return;
}
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
}
/**
* Get content html.
*
* @return string
*/
public function get_content_html() {
return wc_get_template_html(
$this->template_html,
[
'order' => $this->object,
'retry' => $this->retry,
'email_heading' => $this->get_heading(),
'sent_to_admin' => true,
'plain_text' => false,
'email' => $this,
],
'',
$this->template_base
);
}
/**
* Get content plain.
*
* @return string
*/
public function get_content_plain() {
return wc_get_template_html(
$this->template_plain,
[
'order' => $this->object,
'retry' => $this->retry,
'email_heading' => $this->get_heading(),
'sent_to_admin' => true,
'plain_text' => true,
'email' => $this,
],
'',
$this->template_base
);
}
}
@@ -0,0 +1,231 @@
<?php
/**
* Class WC_Payments_Email_Failed_Renewal_Authentication
*
* @package WooCommerce\Payments
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Failed Renewal Authentication Notification.
*
* @extends WC_Email
*/
class WC_Payments_Email_Failed_Renewal_Authentication extends WC_Email {
/**
* An instance of the email, which would normally be sent after a failed payment.
*
* @var WC_Email_Failed_Order
*/
public $original_email;
/**
* Constructor.
*
* @param WC_Email_Failed_Order[] $email_classes All existing instances of WooCommerce emails.
*/
public function __construct( $email_classes = [] ) {
$this->id = 'failed_renewal_authentication';
$this->title = __( 'Failed subscription renewal SCA authentication', 'woocommerce-payments' );
$this->description = __( 'Sent to a customer when a renewal fails because the transaction requires an SCA verification. The email contains renewal order information and payment links.', 'woocommerce-payments' );
$this->customer_email = true;
$this->template_html = 'failed-renewal-authentication.php';
$this->template_plain = 'plain/failed-renewal-authentication.php';
$this->template_base = __DIR__ . '/emails/';
// Triggers the email at the correct hook.
add_action( 'woocommerce_woocommerce_payments_payment_requires_action', [ $this, 'trigger' ] );
if ( isset( $email_classes['WCS_Email_Customer_Renewal_Invoice'] ) ) {
$this->original_email = $email_classes['WCS_Email_Customer_Renewal_Invoice'];
}
// We want all the parent's methods, with none of its properties, so call its parent's constructor, rather than my parent constructor.
parent::__construct();
}
/**
* Generates the HTML for the email while keeping the `template_base` in mind.
*
* @return string
*/
public function get_content_html() {
ob_start();
wc_get_template(
$this->template_html,
[
'order' => $this->object,
'email_heading' => $this->get_heading(),
'sent_to_admin' => false,
'plain_text' => false,
'authorization_url' => $this->get_authorization_url( $this->object ),
'email' => $this,
],
'',
$this->template_base
);
return ob_get_clean();
}
/**
* Generates the plain text for the email while keeping the `template_base` in mind.
*
* @return string
*/
public function get_content_plain() {
ob_start();
wc_get_template(
$this->template_plain,
[
'order' => $this->object,
'email_heading' => $this->get_heading(),
'sent_to_admin' => false,
'plain_text' => true,
'authorization_url' => $this->get_authorization_url( $this->object ),
'email' => $this,
],
'',
$this->template_base
);
return ob_get_clean();
}
/**
* Generates the URL, which will be used to authenticate the payment.
*
* @param WC_Order $order The order whose payment needs authentication.
* @return string
*/
public function get_authorization_url( $order ) {
return add_query_arg( 'wcpay-confirmation', 1, $order->get_checkout_payment_url( false ) ); // nosemgrep: audit.php.wp.security.xss.query-arg -- server generated url is passed in.
}
/**
* Uses specific fields from `WC_Email_Customer_Invoice` for this email.
*/
public function init_form_fields() {
parent::init_form_fields();
$base_fields = $this->form_fields;
$this->form_fields = [
'enabled' => [
'title' => _x( 'Enable/disable', 'an email notification', 'woocommerce-payments' ),
'type' => 'checkbox',
'label' => __( 'Enable this email notification', 'woocommerce-payments' ),
'default' => 'yes',
],
'subject' => $base_fields['subject'],
'heading' => $base_fields['heading'],
'email_type' => $base_fields['email_type'],
];
}
/**
* Returns the default subject of the email (modifiable in settings).
*
* @return string
*/
public function get_default_subject() {
return __( 'Payment authorization needed for renewal of {site_title} order {order_number}', 'woocommerce-payments' );
}
/**
* Returns the default heading of the email (modifiable in settings).
*
* @return string
*/
public function get_default_heading() {
return __( 'Payment authorization needed for renewal of order {order_number}', 'woocommerce-payments' );
}
/**
* Triggers the email while also disconnecting the original Subscriptions email.
*
* @param WC_Order $order The order that is being paid.
*/
public function trigger( $order ) {
if ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order->get_id() ) || wcs_is_subscription( $order->get_id() ) || wcs_order_contains_renewal( $order->get_id() ) ) ) {
if ( ! $this->is_enabled() ) {
return;
}
$this->object = $order;
$this->recipient = $order->get_billing_email();
$this->find['order_date'] = '{order_date}';
$this->replace['order_date'] = wc_format_datetime( $order->get_date_created() );
$this->find['order_number'] = '{order_number}';
$this->replace['order_number'] = $order->get_order_number();
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
// Prevent the renewal email from WooCommerce Subscriptions from being sent.
if ( isset( $this->original_email ) ) {
remove_action(
'woocommerce_generated_manual_renewal_order_renewal_notification',
[
$this->original_email,
'trigger',
]
);
remove_action(
'woocommerce_order_status_failed_renewal_notification',
[
$this->original_email,
'trigger',
]
);
}
// Prevent the retry email from WooCommerce Subscriptions from being sent.
add_filter( 'wcs_get_retry_rule_raw', [ $this, 'prevent_retry_notification_email' ], 100, 3 );
// Send email to store owner indicating communication is happening with the customer to request authentication.
add_filter( 'wcs_get_retry_rule_raw', [ $this, 'set_store_owner_custom_email' ], 100, 3 );
}
}
/**
* Prevent all customer-facing retry notifications from being sent after this email.
*
* @param array $rule_array The raw details about the retry rule.
* @param int $retry_number The number of the retry.
* @param int $order_id The ID of the order that needs payment.
*
* @return array
*/
public function prevent_retry_notification_email( $rule_array, $retry_number, $order_id ) {
if ( wcs_get_objects_property( $this->object, 'id' ) === $order_id ) {
$rule_array['email_template_customer'] = '';
}
return $rule_array;
}
/**
* Send store owner a different email when the retry is related to an authentication required error.
*
* @param array $rule_array The raw details about the retry rule.
* @param int $retry_number The number of the retry.
* @param int $order_id The ID of the order that needs payment.
*
* @return array
*/
public function set_store_owner_custom_email( $rule_array, $retry_number, $order_id ) {
if (
wcs_get_objects_property( $this->object, 'id' ) === $order_id &&
'' !== $rule_array['email_template_admin'] // Only send our email if a retry admin email was already going to be sent.
) {
$rule_array['email_template_admin'] = 'WC_Payments_Email_Failed_Authentication_Retry';
}
return $rule_array;
}
}
@@ -0,0 +1,56 @@
<?php
/**
* Admin email about payment retry failed due to authentication.
*
* @package WooCommerce\Payments
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Output the email header.
*/
do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
<p>
<?php
echo esc_html(
sprintf(
// translators: %1$s: an order number, %2$s: the customer's full name, %3$s: lowercase human time diff in the form returned by wcs_get_human_time_diff(), e.g. 'in 12 hours'.
_x(
'The automatic recurring payment for order %1$s from %2$s has failed. The customer was sent an email requesting authentication of payment. If the customer does not authenticate the payment, they will be requested by email again %3$s.',
'In admin renewal failed email',
'woocommerce-payments'
),
$order->get_order_number(),
$order->get_formatted_billing_full_name(),
wcs_get_human_time_diff( $retry->get_time() )
)
);
?>
</p>
<p><?php esc_html_e( 'The renewal order is as follows:', 'woocommerce-payments' ); ?></p>
<?php
/**
* Shows the order details table.
*/
do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email );
/**
* Shows order meta data.
*/
do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email );
/**
* Shows customer details, and email address.
*/
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
/**
* Output the email footer.
*/
do_action( 'woocommerce_email_footer', $email );
@@ -0,0 +1,24 @@
<?php
/**
* Customer email about payment retry failed due to authentication.
*
* @package WooCommerce\Payments
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<?php do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
<p>
<?php
// translators: %1$s: name of the blog, %2$s: link to payment re-authentication URL, note: no full stop due to url at the end.
echo wp_kses( sprintf( _x( 'The automatic payment to renew your subscription with %1$s has failed. To reactivate the subscription, please login and authorize the renewal from your account page: %2$s', 'In failed renewal authentication email', 'woocommerce-payments' ), esc_html( get_bloginfo( 'name' ) ), '<a href="' . esc_url( $authorization_url ) . '">' . esc_html__( 'Authorize the payment &raquo;', 'woocommerce-payments' ) . '</a>' ), [ 'a' => [ 'href' => true ] ] );
?>
</p>
<?php do_action( 'woocommerce_subscriptions_email_order_details', $order, $sent_to_admin, $plain_text, $email ); ?>
<?php do_action( 'woocommerce_email_footer', $email ); ?>
@@ -0,0 +1,50 @@
<?php
/**
* Admin email about payment retry failed due to authentication.
*
* @package WooCommerce\Payments
*/
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
echo '= ' . $email_heading . " =\n\n";
printf(
// translators: %1$s: an order number, %2$s: the customer's full name, %3$s: lowercase human time diff in the form returned by wcs_get_human_time_diff(), e.g. 'in 12 hours'.
_x(
'The automatic recurring payment for order %1$s from %2$s has failed. The customer was sent an email requesting authentication of payment. If the customer does not authenticate the payment, they will be requested by email again %3$s.',
'In admin renewal failed email',
'woocommerce-payments'
),
$order->get_order_number(),
$order->get_formatted_billing_full_name(),
wcs_get_human_time_diff( $retry->get_time() )
) . "\n\n";
printf( __( 'The renewal order is as follows:', 'woocommerce-payments' ) ) . "\n\n";
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/**
* Shows the order details table.
*/
do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/**
* Shows order meta data.
*/
do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email );
/**
* Shows customer details, and email address.
*/
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) );
@@ -0,0 +1,25 @@
<?php
/**
* Customer email about payment retry failed due to authentication.
*
* @package WooCommerce\Payments
*/
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
echo $email_heading . "\n\n";
// translators: %1$s: name of the blog, %2$s: link to checkout payment url, note: no full stop due to url at the end.
printf( esc_html_x( 'The automatic payment to renew your subscription with %1$s has failed. To reactivate the subscription, please login and authorize the renewal from your account page: %2$s', 'In failed renewal authentication email', 'woocommerce-payments' ), esc_html( get_bloginfo( 'name' ) ), esc_attr( $authorization_url ) );
echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
do_action( 'woocommerce_subscriptions_email_order_details', $order, $sent_to_admin, $plain_text, $email );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) );
@@ -0,0 +1,164 @@
<?php
/**
* Trait WC_Payments_Subscriptions_Utilities
*
* @package WooCommerce\Payments
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Utility functions related to WC Subscriptions.
*/
trait WC_Payments_Subscriptions_Utilities {
/**
* Checks if subscriptions are enabled on the site.
*
* Subscriptions functionality is enabled if the WC Subscriptions plugin is active and greater than v 2.2, or the base feature is turned on.
*
* @return bool Whether subscriptions is enabled or not.
*/
public function is_subscriptions_enabled() {
if ( $this->is_subscriptions_plugin_active() ) {
return version_compare( $this->get_subscriptions_plugin_version(), '2.2.0', '>=' );
}
// TODO update this once we know how the base library feature will be enabled.
return class_exists( 'WC_Subscriptions_Core_Plugin' );
}
/**
* Returns whether this user is changing the payment method for a subscription.
*
* @return bool
*/
public function is_changing_payment_method_for_subscription() {
if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return wcs_is_subscription( wc_clean( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
}
return false;
}
/**
* Returns boolean value indicating whether payment for an order will be recurring,
* as opposed to single.
*
* @param int $order_id ID for corresponding WC_Order in process.
*
* @return bool
*/
public function is_payment_recurring( $order_id ) {
if ( ! $this->is_subscriptions_enabled() ) {
return false;
}
return $this->is_changing_payment_method_for_subscription() || wcs_order_contains_subscription( $order_id );
}
/**
* Returns a boolean value indicating whether the save payment checkbox should be
* displayed during checkout.
*
* Returns `false` if the cart currently has a subscriptions or if the request has a
* `change_payment_method` GET parameter. Returns the value in `$display` otherwise.
*
* @param bool $display Bool indicating whether to show the save payment checkbox in the absence of subscriptions.
*
* @return bool Indicates whether the save payment method checkbox should be displayed or not.
*/
public function display_save_payment_method_checkbox( $display ) {
if ( WC_Subscriptions_Cart::cart_contains_subscription() || $this->is_changing_payment_method_for_subscription() ) {
return false;
}
// Only render the "Save payment method" checkbox if there are no subscription products in the cart.
return $display;
}
/**
* Returns boolean on whether current WC_Cart or WC_Subscriptions_Cart
* contains a subscription or subscription renewal item
*
* @return bool
*/
public function is_subscription_item_in_cart() {
if ( $this->is_subscriptions_enabled() ) {
return WC_Subscriptions_Cart::cart_contains_subscription() || $this->cart_contains_renewal();
}
return false;
}
/**
* Checks the cart to see if it contains a subscription product renewal.
*
* @return mixed The cart item containing the renewal as an array, else false.
*/
public function cart_contains_renewal() {
if ( ! function_exists( 'wcs_cart_contains_renewal' ) ) {
return false;
}
return wcs_cart_contains_renewal();
}
/**
* Checks if the WC Subscriptions plugin is active.
*
* @return bool Whether the plugin is active or not.
*/
public function is_subscriptions_plugin_active() {
return class_exists( 'WC_Subscriptions' );
}
/**
* Gets the version of WooCommerce Subscriptions that is active.
*
* @return null|string The plugin version. Returns null when WC Subscriptions is not active/loaded.
*/
public function get_subscriptions_plugin_version() {
return class_exists( 'WC_Subscriptions' ) ? WC_Subscriptions::$version : null;
}
/**
* Gets the version of the subscriptions-core library.
*
* @return null|string The version number of subscriptions-core or null if not active.
*/
public function get_subscriptions_core_version() {
$subscriptions_core_instance = WC_Subscriptions_Core_Plugin::instance();
// For backwards compatibility with older versions of WC Subscriptions, we need to do an existence check.
if ( method_exists( $subscriptions_core_instance, 'get_library_version' ) ) {
return $subscriptions_core_instance->get_library_version();
}
return $subscriptions_core_instance ? $subscriptions_core_instance->get_plugin_version() : null;
}
/**
* Gets the total number of subscriptions that have already been migrated.
*
* @return int The total number of subscriptions migrated.
*/
public function get_subscription_migrated_count() {
if ( ! function_exists( 'wcs_get_orders_with_meta_query' ) ) {
return 0;
}
return count(
wcs_get_orders_with_meta_query(
[
'status' => 'any',
'return' => 'ids',
'type' => 'shop_subscription',
'limit' => -1,
'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
[
'key' => '_migrated_wcpay_subscription_id',
'compare' => 'EXISTS',
],
],
]
)
);
}
}