automatewoo/includes/Triggers/Customer_Before_Saved_Card_Expiry.php
fei-source 47d3c9a8b6 Update to v6.2.2
Source: GrootMade/Festinger Vault
2026-03-15 08:31:15 +08:00

177 lines
4.6 KiB
PHP

<?php
namespace AutomateWoo;
use AutomateWoo\Exceptions\InvalidArgument;
use AutomateWoo\Triggers\AbstractBatchedDailyTrigger;
use RuntimeException;
use WC_Payment_Tokens;
defined( 'ABSPATH' ) || exit;
/**
* Trigger_Customer_Before_Saved_Card_Expiry class.
*
* @since 3.7.0
*/
class Trigger_Customer_Before_Saved_Card_Expiry extends AbstractBatchedDailyTrigger {
/**
* @var string[]
*/
public $supplied_data_items = [ 'customer', 'card' ];
/**
* Load admin details.
*/
public function load_admin_details() {
$this->title = __( 'Customer Before Saved Card Expiry', 'automatewoo' );
$this->description = sprintf(
/* translators: %1$s docs URL */
__(
'This trigger runs a set number of days before a customer\'s saved card expires. Cards expire on the last calendar day of their expiry month. <a href="%1$s" target="_blank">Some payment gateways are not supported.</a>',
'automatewoo'
),
Admin::get_docs_link( 'triggers/saved-card-expiry-notifications/' )
);
$this->description .= ' ' . $this->get_description_text_workflow_not_immediate();
$this->group = __( 'Customers', 'automatewoo' );
}
/**
* Load fields.
*/
public function load_fields() {
$days_before = ( new Fields\Positive_Number() )
->set_name( 'days_before_expiry' )
->set_title( __( 'Days before expiry', 'automatewoo' ) )
->set_required();
$this->add_field( $days_before );
$this->add_field( $this->get_field_time_of_day() );
}
/**
* Get credit cards based on the specified days before expiry field.
*
* Uses cursor-based pagination (token_id > $after_id) to prevent skipping items
* when the dataset changes between async batch runs.
*
* @since 6.2.3 Updated to use cursor-based pagination.
*
* @param Workflow $workflow
* @param int $limit
* @param int $after_id Return tokens with ID greater than this value.
*
* @return array
*/
protected function get_cards_by_expiry( $workflow, $limit, $after_id ) {
global $wpdb;
$days_before = absint( $workflow->get_trigger_option( 'days_before_expiry' ) );
if ( ! $days_before ) {
return [];
}
$date = new DateTime();
Time_Helper::convert_from_gmt( $date ); // get cards based on the sites timezone
$date->modify( "{$days_before} days" );
$day_to_run = (int) $date->format( 'j' );
$days_in_month = (int) $date->format( 't' );
if ( $days_in_month !== $day_to_run ) {
return [];
}
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT token_id FROM {$wpdb->prefix}woocommerce_payment_tokens as tokens
LEFT JOIN {$wpdb->payment_tokenmeta} AS m1 ON tokens.token_id = m1.payment_token_id
LEFT JOIN {$wpdb->payment_tokenmeta} AS m2 ON tokens.token_id = m2.payment_token_id
WHERE type = 'CC'
AND m1.meta_key = 'expiry_year'
AND m1.meta_value = %s
AND m2.meta_key = 'expiry_month'
AND m2.meta_value = %s
AND tokens.token_id > %d
ORDER BY tokens.token_id ASC
LIMIT %d
",
[
$date->format( 'Y' ),
$date->format( 'm' ),
$after_id,
$limit,
]
),
OBJECT_K
);
return array_keys( $results );
}
/**
* Get a batch of items to process for given workflow.
*
* @param Workflow $workflow
* @param int $after_id Items with ID greater than this value (cursor for pagination).
* @param int $limit The max items for the query.
*
* @return array[] Array of items in array format. Items will be stored in the database so they should be IDs not objects.
*/
public function get_batch_for_workflow( Workflow $workflow, int $after_id, int $limit ): array {
$items = [];
foreach ( $this->get_cards_by_expiry( $workflow, $limit, $after_id ) as $token_id ) {
$items[] = [
'token' => $token_id,
];
}
return $items;
}
/**
* Process a single item for a workflow to process.
*
* @param Workflow $workflow
* @param array $item
*
* @throws InvalidArgument If token is not set.
* @throws RuntimeException If token is not found.
*/
public function process_item_for_workflow( Workflow $workflow, array $item ) {
if ( ! isset( $item['token'] ) ) {
throw InvalidArgument::missing_required( 'token' );
}
$token = WC_Payment_Tokens::get( $item['token'] );
if ( ! $token ) {
throw new RuntimeException( 'Token was not found.' );
}
$workflow->maybe_run(
[
'customer' => Customer_Factory::get_by_user_id( $token->get_user_id() ),
'card' => $token,
]
);
}
/**
* @param Workflow $workflow
*
* @return bool
*/
public function validate_workflow( $workflow ) {
// workflow should only run once for each card
if ( $workflow->has_run_for_data_item( 'card' ) ) {
return false;
}
return true;
}
}