Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gracefully handle unrefundable ticket refund attempts #1063

Open
wants to merge 3 commits into
base: production
Choose a base branch
from
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
25 changes: 25 additions & 0 deletions public_html/wp-content/plugins/camptix/addons/payment-paypal.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class CampTix_Payment_Method_PayPal extends CampTix_Payment_Method {
public $id = 'paypal';
public $name = 'PayPal';
public $description = 'PayPal Express Checkout';
protected $refund_expiry = 90; // days.

public $supported_currencies = array( 'AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'USD', 'NZD', 'CHF', 'HKD', 'SGD', 'SEK',
'DKK', 'PLN', 'NOK', 'HUF', 'CZK', 'ILS', 'MXN', 'BRL', 'MYR', 'PHP', 'TWD', 'THB', 'TRY',
Expand Down Expand Up @@ -738,6 +739,30 @@ function fill_payload_with_order( &$payload, $order ) {
return $payload;
}

/**
* Check if the transaction is refundable.
*
* @param string $payment_token
*
* @return bool|obj WP_Error or boolean false if not refundable
*/
public function transaction_is_refundable( $payment_token ) {
/** @var CampTix_Plugin $camptix */
global $camptix;

if ( empty( $this->refund_expiry ) ) {
return false;
}

$txn_details = $camptix->get_post_meta_from_payment_token( $payment_token, 'tix_transaction_details' );

if ( $txn_details['raw']['raw']['TIMESTAMP'] + ( $this->refund_expiry * DAY_IN_SECONDS ) < time() ) {
return new WP_Error('refund-expired', sprintf( __( 'PayPal only allows refund within %d days of payment.', 'camptix' ), $this->refund_expiry ) );
}

return true;
}

/**
* Submits a single, user-initiated refund request to PayPal and returns the result
*
Expand Down
31 changes: 28 additions & 3 deletions public_html/wp-content/plugins/camptix/addons/payment-stripe.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

class CampTix_Payment_Method_Stripe extends CampTix_Payment_Method {
public $id = 'stripe';
public $name = 'Credit Card (Stripe)';
public $description = 'Credit card processing, powered by Stripe.';
public $id = 'stripe';
public $name = 'Credit Card (Stripe)';
public $description = 'Credit card processing, powered by Stripe.';
protected $refund_expiry = 90; // days.

/**
* See https://support.stripe.com/questions/which-currencies-does-stripe-support.
Expand Down Expand Up @@ -504,6 +505,30 @@ public function camptix_payment_result( $payment_token, $result, $data ) {
}
}

/**
* Check if the transaction is refundable.
*
* @param string $payment_token
*
* @return bool|obj WP_Error or boolean false if not refundable
*/
public function transaction_is_refundable( $payment_token ) {
/** @var CampTix_Plugin $camptix */
global $camptix;

if ( empty( $this->refund_expiry ) ) {
return false;
}

$txn_details = $camptix->get_post_meta_from_payment_token( $payment_token, 'tix_transaction_details' );

if ( $txn_details['raw']['charge']['created'] + ( $this->refund_expiry * DAY_IN_SECONDS ) < time() ) {
return new WP_Error('refund-expired', sprintf( __( 'Stripe only allows refund within %d days of payment.', 'camptix' ), $this->refund_expiry ) );
}

return true;
}

/**
* Submits a single, user-initiated refund request to Stripe and returns the result.
*
Expand Down
72 changes: 42 additions & 30 deletions public_html/wp-content/plugins/camptix/camptix.php
Original file line number Diff line number Diff line change
Expand Up @@ -6076,9 +6076,7 @@ function form_access_tickets() {
$edit_link = $this->get_edit_attendee_link( $attendee->ID, $edit_token );
$first_name = get_post_meta( $attendee->ID, 'tix_first_name', true );
$last_name = get_post_meta( $attendee->ID, 'tix_last_name', true );

if ( $this->is_refundable( $attendee->ID ) )
$is_refundable = true;
$is_refundable = $this->is_refundable( $attendee->ID )
?>
<tr>
<td>
Expand Down Expand Up @@ -6111,8 +6109,12 @@ function form_access_tickets() {

</tbody>
</table>
<?php if ( $is_refundable ) : ?>
<p><?php printf( __( "Change of plans? Made a mistake? Don't worry, you can %s.", 'wordcamporg' ), '<a href="' . esc_url( $this->get_refund_tickets_link( $access_token ) ) . '">' . __( 'request a refund', 'wordcamporg' ) . '</a>' ); ?></p>
<?php if ( $is_refundable ) :
if ( is_wp_error( $is_refundable ) ) : ?>
<p><?php echo esc_html( sprintf( __( 'Sorry, refund is not possible. %s', 'wordcamporg' ), $is_refundable->get_error_message() ) ) ?></p>
<?php else : ?>
<p><?php printf( __( "Change of plans? Made a mistake? Don't worry, you can %s.", 'wordcamporg' ), '<a href="' . esc_url( $this->get_refund_tickets_link( $access_token ) ) . '">' . __( 'request a refund', 'wordcamporg' ) . '</a>' ); ?></p>
<?php endif; ?>
<?php endif; ?>
</div><!-- #tix -->
<?php
Expand Down Expand Up @@ -6358,20 +6360,6 @@ function form_refund_request() {
// Clean things up before and after the shortcode.
$post->post_content = apply_filters( 'camptix_post_content_override', $this->shortcode_str, $post->post_content, $_GET['tix_action'] );

if ( ! $this->options['refunds_enabled'] || ! isset( $_REQUEST['tix_access_token'] ) || ! ctype_alnum( $_REQUEST['tix_access_token'] ) ) {
$this->error_flags['invalid_access_token'] = true;
$this->redirect_with_error_flags();
die();
}

$today = date( 'Y-m-d' );
$refunds_until = $this->options['refunds_date_end'];
if ( ! strtotime( $refunds_until ) || strtotime( $refunds_until ) < strtotime( $today ) ) {
$this->error_flags['cannot_refund'] = true;
$this->redirect_with_error_flags();
die();
}

$access_token = $_REQUEST['tix_access_token'];

// Let's get one attendee
Expand Down Expand Up @@ -6401,6 +6389,14 @@ function form_refund_request() {
$tickets = array();

foreach ( $attendees as $attendee ) {
$is_refundable = $this->is_refundable( $attendee->ID );
if ( is_wp_error( $is_refundable ) || ! $is_refundable ) {
$this->log( 'Tried refund, but transaction is not refundable', $attendee->ID );
$this->error_flags['cannot_refund'] = true;
$this->redirect_with_error_flags();
die();
}

$txn_id = get_post_meta( $attendee->ID, 'tix_transaction_id', true );
if ( $txn_id ) {
$transactions[ $txn_id ] = get_post_meta( $attendee->ID, 'tix_transaction_details', true );
Expand All @@ -6419,13 +6415,15 @@ function form_refund_request() {
}

if ( count( $transactions ) != 1 || $transactions[ $txn_id ]['payment_amount'] <= 0 ) {
$this->log( 'Tried refund, but tno ransaction or payment amount 0', $attendee->ID );
$this->error_flags['cannot_refund'] = true;
$this->redirect_with_error_flags();
die();
}

$transaction = array_shift( $transactions );
if ( ! $transaction['receipt_email'] || ! $transaction['transaction_id'] || ! $transaction['payment_amount'] ) {
$this->log( 'Tried refund, but problems with original transaction', $attendee->ID );
$this->error_flags['cannot_refund'] = true;
$this->redirect_with_error_flags();
die();
Expand All @@ -6445,6 +6443,7 @@ function form_refund_request() {

// Bail if a payment method does not exist.
if ( ! $payment_method_obj ) {
$this->log( 'Tried refund, but payment method does not exist', $attendee->ID );
$this->error_flags['cannot_refund'] = true;
$this->redirect_with_error_flags();
die();
Expand Down Expand Up @@ -6548,26 +6547,39 @@ function form_refund_success() {
* Return true if an attendee_id is refundable.
*/
function is_refundable( $attendee_id ) {
if ( ! $this->options['refunds_enabled'] )
return false;

$payment_method = get_post_meta( $attendee_id, 'tix_payment_method', true );
$payment_method_obj = $this->get_payment_method_by_id( $payment_method );
if ( ! $payment_method_obj || ! $payment_method_obj->supports_feature( 'refund-single' ) )
if ( ! $this->options['refunds_enabled'] ) {
return false;
}

$today = date( 'Y-m-d' );
$today = date( 'Y-m-d' );
$refunds_until = $this->options['refunds_date_end'];

if ( ! strtotime( $refunds_until ) )
if ( ! strtotime( $refunds_until ) ) {
return false;
}

if ( strtotime( $refunds_until ) < strtotime( $today ) )
if ( strtotime( $refunds_until ) < strtotime( $today ) ) {
return false;
}

$attendee = get_post( $attendee_id );
if ( $attendee->post_status == 'publish' && (float) get_post_meta( $attendee->ID, 'tix_order_total', true ) > 0 && get_post_meta( $attendee->ID, 'tix_transaction_id', true ) )
$payment_method = get_post_meta( $attendee_id, 'tix_payment_method', true );
$payment_method_obj = $this->get_payment_method_by_id( $payment_method );

if ( ! $payment_method_obj || ! $payment_method_obj->supports_feature( 'refund-single' ) ) {
return false;
}

$attendee = get_post( $attendee_id );
$payment_token = get_post_meta( $attendee->ID, 'tix_payment_token', true );
$is_refundable = $payment_method_obj->transaction_is_refundable( $payment_token );

if ( is_wp_error( $is_refundable ) ) {
return $is_refundable;
}

if ( $attendee->post_status == 'publish' && (float) get_post_meta( $attendee->ID, 'tix_order_total', true ) > 0 && get_post_meta( $attendee->ID, 'tix_transaction_id', true ) ) {
return true;
}

return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ function validate_options( $input ) {
*/
abstract function payment_checkout( $payment_token );

/**
* Check if the transaction is refundable.
*
* @param string $payment_token
*
* @return bool|obj WP_Error or boolean false if not refundable
*/
public function transaction_is_refundable( $payment_token ) {
return true; // To maintain backwards compatibility, default to refundable.
}

/**
* Handle the refund process
*
Expand Down
Loading