init
This commit is contained in:
+176
@@ -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;
|
||||
}
|
||||
}
|
||||
+186
@@ -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
|
||||
}
|
||||
}
|
||||
+76
@@ -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 );
|
||||
}
|
||||
}
|
||||
+138
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+231
@@ -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;
|
||||
}
|
||||
}
|
||||
+56
@@ -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 );
|
||||
+24
@@ -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 »', '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 ); ?>
|
||||
+50
@@ -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' ) );
|
||||
+25
@@ -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' ) );
|
||||
+1024
File diff suppressed because it is too large
Load Diff
+164
@@ -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',
|
||||
],
|
||||
],
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user