init
This commit is contained in:
+376
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Task_Disputes
|
||||
*
|
||||
* @package WooCommerce\Payments\Tasks
|
||||
*/
|
||||
|
||||
namespace WooCommerce\Payments\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use WCPay\Database_Cache;
|
||||
use WC_Payments_Utils;
|
||||
use WC_Payments_API_Client;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC Onboarding Task displayed if disputes awaiting response.
|
||||
*
|
||||
* Note: this task is separate to the Payments → Overview disputes task, which is defined in client/overview/task-list/tasks.js.
|
||||
*/
|
||||
class WC_Payments_Task_Disputes extends Task {
|
||||
/**
|
||||
* Client for making requests to the WooCommerce Payments API
|
||||
*
|
||||
* @var WC_Payments_API_Client
|
||||
*/
|
||||
private $api_client;
|
||||
|
||||
/**
|
||||
* Database_Cache instance.
|
||||
*
|
||||
* @var Database_Cache
|
||||
*/
|
||||
private $database_cache;
|
||||
|
||||
|
||||
/**
|
||||
* Disputes due within 7 days.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_due_within_7d;
|
||||
|
||||
/**
|
||||
* Disputes due within 1 day.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_due_within_1d;
|
||||
|
||||
/**
|
||||
* A memory cache of all disputes needing response.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $disputes_needing_response = null;
|
||||
|
||||
/**
|
||||
* WC_Payments_Task_Disputes constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->api_client = \WC_Payments::get_payments_api_client();
|
||||
$this->database_cache = \WC_Payments::get_database_cache();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the task.
|
||||
*/
|
||||
private function fetch_relevant_disputes() {
|
||||
$this->disputes_due_within_7d = $this->get_disputes_needing_response_within_days( 7 );
|
||||
$this->disputes_due_within_1d = $this->get_disputes_needing_response_within_days( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'woocommerce_payments_disputes_task';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( null === $this->disputes_needing_response ) {
|
||||
$this->fetch_relevant_disputes();
|
||||
}
|
||||
if ( count( (array) $this->disputes_due_within_7d ) === 1 ) {
|
||||
$dispute = $this->disputes_due_within_7d[0];
|
||||
$amount = WC_Payments_Utils::interpret_stripe_amount( $dispute['amount'], $dispute['currency'] );
|
||||
$amount_formatted = WC_Payments_Utils::format_currency( $amount, $dispute['currency'] );
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %s is a currency formatted amount */
|
||||
__( 'Respond to a dispute for %s – Last day', 'woocommerce-payments' ),
|
||||
$amount_formatted
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
/* translators: %s is a currency formatted amount */
|
||||
__( 'Respond to a dispute for %s', 'woocommerce-payments' ),
|
||||
$amount_formatted
|
||||
);
|
||||
}
|
||||
|
||||
$active_disputes = $this->get_disputes_needing_response();
|
||||
if ( ! is_array( $active_disputes ) || count( $active_disputes ) === 0 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dispute_currencies = array_unique( array_column( $active_disputes, 'currency' ) );
|
||||
|
||||
// If multiple currencies, use simple task title without total amounts.
|
||||
if ( count( $dispute_currencies ) > 1 ) {
|
||||
return sprintf(
|
||||
// translators: %d is a number greater than 1.
|
||||
__( 'Respond to %d active disputes', 'woocommerce-payments' ),
|
||||
count( $active_disputes )
|
||||
);
|
||||
}
|
||||
|
||||
// If single currency, calculate total amount and include in task title.
|
||||
$dispute_total = array_reduce(
|
||||
$active_disputes,
|
||||
function ( $total, $dispute ) {
|
||||
return $total + ( $dispute['amount'] ?? 0 );
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
$dispute_total_formatted = WC_Payments_Utils::format_currency(
|
||||
WC_Payments_Utils::interpret_stripe_amount( $dispute_total, $dispute_currencies[0] ),
|
||||
$dispute_currencies[0]
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d is a number greater than 1. %s is a formatted amount, eg: $10.00 */
|
||||
__( 'Respond to %1$d active disputes for a total of %2$s', 'woocommerce-payments' ),
|
||||
count( $active_disputes ),
|
||||
$dispute_total_formatted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent list ID.
|
||||
*
|
||||
* This function prior to WC 6.4.0 was abstract and so is needed for backwards compatibility.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_parent_id() {
|
||||
// WC 6.4.0 compatibility.
|
||||
if ( is_callable( 'parent::get_parent_id' ) ) {
|
||||
return parent::get_parent_id();
|
||||
}
|
||||
|
||||
return 'extended';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task subtitle.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_additional_info() {
|
||||
if ( count( (array) $this->disputes_due_within_7d ) === 1 ) {
|
||||
$local_timezone = new \DateTimeZone( wp_timezone_string() );
|
||||
$dispute = $this->disputes_due_within_7d[0];
|
||||
$due_by_local_time = ( new \DateTime( $dispute['due_by'] ) )->setTimezone( $local_timezone );
|
||||
// Sum of Unix timestamp and timezone offset in seconds.
|
||||
$due_by_ts = $due_by_local_time->getTimestamp() + $due_by_local_time->getOffset();
|
||||
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %s is time, eg: 11:59 PM */
|
||||
__( 'Respond today by %s', 'woocommerce-payments' ),
|
||||
date_i18n( wc_time_format(), $due_by_ts )
|
||||
);
|
||||
}
|
||||
|
||||
$now = new \DateTime( 'now', $local_timezone );
|
||||
$diff = $now->diff( $due_by_local_time );
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s is a date, eg: Jan 1, 2021. %2$s is the number of days left, eg: 2 days. */
|
||||
__( 'By %1$s – %2$s left to respond', 'woocommerce-payments' ),
|
||||
date_i18n( wc_date_format(), $due_by_ts ),
|
||||
/* translators: %s is the number of days left, e.g. 1 day. */
|
||||
sprintf( _n( '%d day', '%d days', $diff->days, 'woocommerce-payments' ), $diff->days )
|
||||
);
|
||||
}
|
||||
|
||||
if ( count( (array) $this->disputes_due_within_1d ) > 0 ) {
|
||||
return sprintf(
|
||||
/* translators: %d is the number of disputes. */
|
||||
__(
|
||||
'Final day to respond to %d of the disputes',
|
||||
'woocommerce-payments'
|
||||
),
|
||||
count( (array) $this->disputes_due_within_1d )
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %d is the number of disputes. */
|
||||
__(
|
||||
'Last week to respond to %d of the disputes',
|
||||
'woocommerce-payments'
|
||||
),
|
||||
count( (array) $this->disputes_due_within_7d )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task's action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
$disputes = $this->disputes_due_within_7d;
|
||||
if ( count( (array) $disputes ) === 1 ) {
|
||||
$dispute = $disputes[0];
|
||||
return admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-admin',
|
||||
'path' => '%2Fpayments%2Ftransactions%2Fdetails',
|
||||
'id' => $dispute['charge_id'],
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'page' => 'wc-admin',
|
||||
'path' => '%2Fpayments%2Fdisputes',
|
||||
'filter' => 'awaiting_response',
|
||||
],
|
||||
'admin.php'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time to complete the task.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the task is completed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the task is visible.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
if ( null === $this->disputes_needing_response ) {
|
||||
$this->fetch_relevant_disputes();
|
||||
}
|
||||
return count( (array) $this->disputes_due_within_7d ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disputes needing response within the given number of days.
|
||||
*
|
||||
* @param int $num_days Number of days in the future to check for disputes needing response.
|
||||
*
|
||||
* @return array Disputes needing response within the given number of days.
|
||||
*/
|
||||
private function get_disputes_needing_response_within_days( $num_days ) {
|
||||
$to_return = [];
|
||||
|
||||
$active_disputes = $this->get_disputes_needing_response();
|
||||
if ( ! is_array( $active_disputes ) ) {
|
||||
return $to_return;
|
||||
}
|
||||
|
||||
foreach ( $active_disputes as $dispute ) {
|
||||
if ( ! $dispute['due_by'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare UTC times.
|
||||
$now_utc = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
|
||||
$due_by_utc = new \DateTime( $dispute['due_by'], new \DateTimeZone( 'UTC' ) );
|
||||
|
||||
if ( $now_utc > $due_by_utc ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $now_utc->diff( $due_by_utc );
|
||||
// If the dispute is due within the given number of days, add it to the list.
|
||||
if ( $diff->days <= $num_days ) {
|
||||
$to_return[] = $dispute;
|
||||
}
|
||||
}
|
||||
|
||||
return $to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets disputes awaiting a response. ie have a 'needs_response' or 'warning_needs_response' status.
|
||||
*
|
||||
* @return array|null Array of disputes awaiting a response. Null on failure.
|
||||
*/
|
||||
private function get_disputes_needing_response() {
|
||||
if ( null !== $this->disputes_needing_response ) {
|
||||
return $this->disputes_needing_response;
|
||||
}
|
||||
|
||||
$this->disputes_needing_response = $this->database_cache->get_or_add(
|
||||
Database_Cache::ACTIVE_DISPUTES_KEY,
|
||||
function () {
|
||||
try {
|
||||
$response = $this->api_client->get_disputes(
|
||||
[
|
||||
'pagesize' => 50,
|
||||
'search' => [ 'warning_needs_response', 'needs_response' ],
|
||||
]
|
||||
);
|
||||
} catch ( \Exception $e ) {
|
||||
// Ensure an array is always returned, even if the API call fails.
|
||||
return [];
|
||||
}
|
||||
|
||||
$active_disputes = $response['data'] ?? [];
|
||||
|
||||
// sort by due_by date ascending.
|
||||
usort(
|
||||
$active_disputes,
|
||||
function ( $a, $b ) {
|
||||
$a_due_by = new \DateTime( $a['due_by'] );
|
||||
$b_due_by = new \DateTime( $b['due_by'] );
|
||||
|
||||
return $a_due_by <=> $b_due_by;
|
||||
}
|
||||
);
|
||||
|
||||
return $active_disputes;
|
||||
},
|
||||
'is_array'
|
||||
);
|
||||
|
||||
return $this->disputes_needing_response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user