Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 40 additions & 26 deletions public_html/wp-content/plugins/camptix/addons/payment-stripe.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public function camptix_init() {
);

add_action( 'template_redirect', array( $this, 'template_redirect' ) );
add_action( 'camptix_pre_attendee_timeout', array( $this, 'pre_attendee_timeout' ) );
}

/**
Expand Down Expand Up @@ -523,7 +522,9 @@ public function payment_checkout( $payment_token ) {
)
);

update_post_meta( $order['attendee_id'], '_stripe_checkout_session_id', wp_slash( $session['id'] ) );
foreach ( $order['attendees'] as $attendee ) {
update_post_meta( $attendee->ID, '_stripe_checkout_session_id', wp_slash( $session['id'] ) );
}

wp_redirect( esc_url_raw( $session['url'] ) );
die();
Expand Down Expand Up @@ -632,44 +633,57 @@ public function send_refund_request( $payment_token ) {
}

/**
* Check if a stripe session timed out.
* Get payment details from Stripe.
*
* @param string $payment_token
* @return array|false See parent method.
*/
public function pre_attendee_timeout( $attendee_id ) {
/** @var CampTix_Plugin $camptix */
global $camptix;

// precheck the attendee is in draft.
if ( 'draft' !== get_post_field( 'post_status', $attendee_id ) ) {
return;
public function get_payment( $payment_token ) {
$order = $this->get_order( $payment_token );
if ( ! $order ) {
return false;
}

$stripe_session_id = get_post_meta( $attendee_id, '_stripe_checkout_session_id', true );
$payment_token = get_post_meta( $attendee_id, 'tix_payment_token', true );
if ( ! $stripe_session_id || ! $payment_token ) {
$stripe_session_id = false;
foreach ( $order['attendees'] as $attendee ) {
$stripe_session_id = get_post_meta( $attendee->ID, '_stripe_checkout_session_id', true );
if ( $stripe_session_id ) {
break;
}
}
if ( ! $stripe_session_id ) {
return;
}

$stripe = new CampTix_Stripe_API_Client( $payment_token, $this->get_api_credentials()['api_secret_key'] );
$session = $stripe->get_session( $stripe_session_id );

if ( is_wp_error( $session ) || empty( $session['status'] ) ) {
return;
return false;
}

// Uh oh, we've hit timeout on a ticket, but the linked checkout session succeeded.
if ( 'complete' === $session['status'] && 'paid' === $session['payment_status'] ) {
$camptix->log( 'Stripe checkout timed out, but order succeeded.', $attendee_id, $session );

$transaction_id = $session['payment_intent']['latest_charge'] ?? '';
$payment_data = array(
'transaction_id' => $transaction_id,
'transaction_details' => array(
'raw' => $session,
),
if ( 'complete' === $session['status'] ) {
return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_COMPLETED,
'transaction_id' => $session['payment_intent']['latest_charge'] ?? '',
'transaction_details' => $session,
);
} elseif ( 'open' === $session['status'] ) {
return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_PENDING,
'transaction_details' => $session,
);
} elseif ( 'expired' === $session['status'] ) {
return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_TIMEOUT,
'transaction_details' => $session,
);

$camptix->payment_result( $payment_token, CampTix_Plugin::PAYMENT_STATUS_COMPLETED, $payment_data, false /* non-interactive */ );
}

// TODO: Handle the payment having been refunded.

// Unknown.
return false;
}
}

Expand Down
83 changes: 73 additions & 10 deletions public_html/wp-content/plugins/camptix/camptix.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,27 @@ function schedule_events() {
add_action( 'tix_scheduled_every_ten_minutes', array( $this, 'send_emails_batch' ) );
add_action( 'tix_scheduled_every_ten_minutes', array( $this, 'process_refund_all' ) );

add_action( 'tix_scheduled_daily', array( $this, 'review_timeout_payments' ) );
add_action( 'tix_scheduled_two_hours', array( $this, 'check_payment_status_for_draft_tickets' ) );

add_action( 'tix_scheduled_daily', array( $this, 'cron_camptix_stats_ticket_validation' ) );

if ( ! wp_next_scheduled( 'tix_scheduled_every_ten_minutes' ) )
wp_schedule_event( time(), '10-mins', 'tix_scheduled_every_ten_minutes' );
// No need for ticketing schedules on an archived event.
if ( $this->options['archived'] ) {
wp_clear_scheduled_hook( 'tix_scheduled_every_ten_minutes' );
wp_clear_scheduled_hook( 'tix_scheduled_two_hours' );
wp_clear_scheduled_hook( 'tix_scheduled_daily' );
return;
}

// wp_clear_scheduled_hook( 'tix_scheduled_hourly' );
if ( ! wp_next_scheduled( 'tix_scheduled_daily' ) )
if ( ! wp_next_scheduled( 'tix_scheduled_every_ten_minutes' ) ) {
wp_schedule_event( time(), '10-mins', 'tix_scheduled_every_ten_minutes' );
}
if ( ! wp_next_scheduled( 'tix_scheduled_daily' ) ) {
wp_schedule_event( time(), 'daily', 'tix_scheduled_daily' );
}
if ( ! wp_next_scheduled( 'tix_scheduled_two_hours' ) ) {
wp_schedule_event( time(), 'two-hours', 'tix_scheduled_two_hours' );
}
}

/**
Expand All @@ -208,6 +220,10 @@ function cron_schedules( $schedules ) {
'interval' => 60 * 10,
'display' => __( 'Once every 10 minutes', 'wordcamporg' ),
);
$schedules['two-hours'] = array(
'interval' => 2 * HOUR_IN_SECONDS,
'display' => 'Every 2 hours',
);
return $schedules;
}

Expand Down Expand Up @@ -7203,12 +7219,13 @@ function get_used_coupons_count( $coupon_id ) {
}

/**
* Review Timeout Payments
* Review Draft attendee payments.
*
* This routine looks up old draft attendee posts and puts
* their status into Timeout.
* This routine looks up old draft attendee posts and either:
* - Marks the attendee as paid.
* - Puts their ticket into timeout status.
*/
function review_timeout_payments() {
function check_payment_status_for_draft_tickets() {

// Nothing to do for archived sites.
if ( $this->options['archived'] )
Expand All @@ -7217,18 +7234,20 @@ function review_timeout_payments() {
$processed = 0;
$current_loop = 1;
$max_loops = 500;
$seen_ids = [];

while ( $attendees = get_posts( array(
'fields' => 'ids',
'post_type' => 'tix_attendee',
'post_status' => 'draft',
'posts_per_page' => 100,
'post__not_in' => $seen_ids,
'cache_results' => false,
'meta_query' => array(
array(
'key' => 'tix_timestamp',
'compare' => '<',
'value' => time() - 60 * 60 * 24, // 24 hours ago
'value' => time() - ( 2 * HOUR_IN_SECONDS ),
'type' => 'NUMERIC',
),
array(
Expand All @@ -7241,6 +7260,40 @@ function review_timeout_payments() {
) ) ) {

foreach ( $attendees as $attendee_id ) {
$seen_ids[] = $attendee_id;

// If the payment method supports fetching payment status, check that before we do anything.
$payment_method = get_post_meta( $attendee_id, 'tix_payment_method', true );
$payment_method_obj = $this->get_payment_method_by_id( $payment_method );
$tix_payment_token = get_post_meta( $attendee_id, 'tix_payment_token', true );
if ( $payment_method_obj && $tix_payment_token ) {
$payment = $payment_method_obj->get_payment( $tix_payment_token );

if ( $payment && CampTix_Plugin::PAYMENT_STATUS_COMPLETED === $payment['status'] ) {
// Mark as published instead of timing out.
$this->payment_result(
$tix_payment_token,
$payment['status'],
$payment,
false /* not interactive, do not die/redirect. */
);

continue;
} elseif ( $payment && CampTix_Plugin::PAYMENT_STATUS_PENDING === $payment['status'] ) {
// Leave as draft, still pending.
continue;
}
} else {
// If no payment method or token, minimum of 24hrs must pass..
$order_date = get_post_meta( $attendee_id, 'tix_timestamp', true );
if ( ( time() - $order_date ) < DAY_IN_SECONDS ) {
continue;
}
}

/**
* Allow plugins to hook in before an attendee is timed out.
*/
do_action( 'camptix_pre_attendee_timeout', $attendee_id );

// Check the post_status again, incase a filter has caused the post to change.
Expand Down Expand Up @@ -7748,6 +7801,16 @@ function payment_result( $payment_token, $result, $data = array(), $interactive
if ( empty( $payment_token ) )
die( 'Do not call payment_result without a payment token.' );

// Back-compat for some payment gateways.
if ( is_scalar( $data ) ) {
_doing_it_wrong( 'Camptix::payment_result', 'Passing a scalar as $data is deprecated. Please pass an array with at least the transaction_id key.', '20251101' );
$data = array(
'transaction_id' => $data,
'transaction_details' => $_REQUEST,
);
unset( $data['transaction_details']['tix_action'], $data['transaction_details']['tix_payment_token'], $data['transaction_details']['tix_payment_method'] );
}

$attendees = get_posts( array(
'posts_per_page' => -1,
'post_type' => 'tix_attendee',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ abstract class CampTix_Payment_Method extends CampTix_Addon {
);
public $camptix_options;

/**
* The period of time (in seconds) after which a pending payment is considered timed out.
*
* @var int
*/
public $payment_timeout_period = 86400; // 24 hours in seconds.

/**
* Constructor
*/
Expand Down Expand Up @@ -216,6 +223,47 @@ function validate_options( $input ) {
*/
abstract function payment_checkout( $payment_token );

/**
* Provide the current status of a payment.
*
* @param string $payment_token
*
* @return array {
* @type int $status A payment status, e.g., PAYMENT_STATUS_CANCELLED, PAYMENT_STATUS_COMPLETED
* @type ?string $transaction_id The transaction ID from the payment gateway, or null if not available
* @type ?array $transaction_details Additional details about the transaction from the payment gateway, or null if not available
* }
*/
function get_payment( $payment_token ) {
_doing_it_wrong( __METHOD__, 'get_payment() not implemented in payment module.', '20251101' );
$order = $this->get_order( $payment_token );
if ( ! $order ) {
// If we can't find the order, return FAILED.
return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_FAILED,
'transaction_id' => null,
'transaction_details' => null
);
}

// For back-compat, we'll return TIMEOUT or PENDING based on the order date and the defined timeout period (Default of 24 hours).
$order_date = strtotime( $order['attendees'][0]->tix_timestamp );
$time_elapsed = time() - strtotime( $order_date );
if ( $time_elapsed > $this->payment_timeout_period ) {
return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_TIMEOUT,
'transaction_id' => null,
'transaction_details' => null
);
}

return array(
'status' => CampTix_Plugin::PAYMENT_STATUS_PENDING,
'transaction_id' => null,
'transaction_details' => null
);
}

/**
* Handle the refund process
*
Expand All @@ -227,6 +275,8 @@ function payment_refund( $payment_token ) {
/** @var $camptix Camptix_Plugin */
global $camptix;

_doing_it_wrong( __METHOD__, 'payment_refund() not implemented in payment module.', '20251101' );

$refund_data = array();
$camptix->log( __FUNCTION__ . ' not implemented in payment module.', 0, null, 'refund' );

Expand Down Expand Up @@ -292,7 +342,7 @@ function get_order( $payment_token = false ) {
}

$attendees = get_posts( array(
'posts_per_page' => 1,
'posts_per_page' => -1,
'post_type' => 'tix_attendee',
'post_status' => 'any',
'meta_query' => array(
Expand All @@ -304,27 +354,26 @@ function get_order( $payment_token = false ) {
),
),
) );

if ( ! $attendees ) {
return array();
}

return $this->get_order_by_attendee_id( $attendees[0]->ID );
}
$order = false;
foreach ( $attendees as $attendee ) {
$order = (array) get_post_meta( $attendee->ID, 'tix_order', true );
if ( $order ) {
break;
}
}
if ( ! $order ) {
return array();
}

/**
* Get the order for the given attendee
*
* @param int $attendee_id
*
* @return array
*/
function get_order_by_attendee_id( $attendee_id ) {
$order = (array) get_post_meta( $attendee_id, 'tix_order', true );
// "last" attendee added, ie. not always the purchaser.
$order['attendee_id'] = $attendees[0]->ID;

if ( $order ) {
$order['attendee_id'] = $attendee_id;
}
// All attendees.
$order['attendees'] = $attendees;

return $order;
}
Expand Down
Loading