Skip to content

Instantly share code, notes, and snippets.

@stracker-phil
Created October 24, 2015 19:31
Show Gist options
  • Save stracker-phil/04984c3424993526ec66 to your computer and use it in GitHub Desktop.
Save stracker-phil/04984c3424993526ec66 to your computer and use it in GitHub Desktop.
M2 helper plugin: Match PayPal IPN Messages when user deleted the original invoice
<?php
/**
* Plugin Name: M2 PayPal Invoice Matching
* Description: Creates missing invoices for M2 PayPal subscriptions. This plugin extends the page "Membership 2 > Billing > Transaction Logs"
* Author: Philipp Stracker (WPMU DEV)
* Created: 21.10.2015
* Version: 1.0.0
*
* Addresses issue of this thread:
* http://premium.wpmudev.org/forums/topic/-of-my-members-now-have-expired-accounts-in-membership-pro-2
* ----------------------------------------------------------------------------
*/
class M2_PP_Inv_Matching {
protected $match_result = '';
static public function init() {
static $Inst = null;
if ( null === $Inst ) {
$Inst = new M2_PP_Inv_Matching();
}
}
protected function __construct() {
add_action(
'load-membership-2_page_membership2-billing',
array( $this, 'billings_page' )
);
add_filter(
'ms_helper_listtable_transactionlog-column_invoice',
array( $this, 'logs_invoice_column' ),
10, 2
);
}
public function billings_page() {
if ( isset( $_GET['gateway_id'] ) || 'paypalstandard' == $_GET['gateway_id'] ) {
$this->process_action();
} else {
$this->show_notice();
}
}
protected function process_action() {
if ( empty( $_REQUEST['action'] ) ) {
return;
}
$action = $_REQUEST['action'];
switch ( $action ) {
case 'm2_pp_inv':
if ( ! empty( $_REQUEST['log'] ) ) {
$log_id = $_REQUEST['log'];
MS_Model_Import::retry_to_process( $log_id );
add_filter(
'ms_view_render',
array( $this, 'show_inv_form' ),
10, 2
);
}
break;
case 'm2_pp_match':
$this->create_matching();
add_filter(
'ms_view_render',
array( $this, 'show_matching_result' ),
10, 2
);
break;
}
}
protected function show_notice() {
$url = MS_Controller_Plugin::get_admin_url(
'billing',
array(
'show' => 'logs',
'state' => 'err',
'gateway_id' => 'paypalstandard',
)
);
lib3()->ui->admin_message(
'<b>M2 PayPal Invoice Matching</b>: Get a list of <a href="' . esc_url( $url ) . '">all PayPal transaction errors</a>'
);
}
public function show_inv_form( $html, $view ) {
if ( empty( $_REQUEST['log'] ) ) { return; }
$log_id = $_REQUEST['log'];
$log = MS_Factory::load( 'MS_Model_Transactionlog', $log_id );
if ( 'err' != $log->state ) {
echo '<div class="updated notice is-dismissible"><p>This is an irrelevant transaction. Matching not possible.</p></div>';
return $html;
}
$pp = $log->post;
lib3()->array->equip(
$pp,
'invoice',
'first_name',
'last_name',
'payer_email',
'item_name',
'item_number'
);
$inv_id = intval( $pp['invoice'] );
$user_id = 0;
$mem_id = intval( $pp['item_number'] );
$def_user = get_user_by( 'email', $pp['payer_email'] );
if ( $def_user ) { $user_id = $def_user->ID; }
$meta_user_id = intval( get_post_meta( $inv_id, 'user_id', true ) );
$meta_mem_id = intval( get_post_meta( $inv_id, 'membership_id', true ) );
$meta_user = get_user_by( 'id', $meta_user_id );
$meta_mem = MS_Factory::load( 'MS_Model_Membership', $meta_mem_id );
if ( $meta_user_id ) { $user_id = $meta_user_id; }
if ( $meta_mem_id ) { $mem_id = $meta_mem_id; }
// Get user options.
$args = array( 'fields' => 'all', 'number' => 0 );
$wp_user_search = new WP_User_Query( $args );
$items = $wp_user_search->get_results();
$user_options = array( '<option value="0">- Unknown -</option>');
foreach ( $items as $user ) {
$user_options[] = sprintf(
'<option value="%s" %s>%s: %s (%s)</option>',
$user->ID,
selected( $user->ID, $user_id, false ),
$user->ID,
$user->first_name . ' ' . $user->last_name,
$user->user_email
);
}
// Get membership options.
$membership_options = array( '<option value="0">- Unknown -</option>' );
$args = array( 'include_guest' => 0 );
$items = MS_Model_Membership::get_membership_names( $args );
foreach ( $items as $id => $name ) {
$membership_options[] = sprintf(
'<option value="%s" %s>%s: %s</option>',
$id,
selected( $id, $mem_id, false ),
$id,
$name
);
}
$name_match = strtolower( $meta_user->first_name ) == strtolower( $pp['first_name'] )
&& strtolower( $meta_user->last_name ) == strtolower( $pp['last_name'] );
$email_match = strtolower( $meta_user->user_email ) == strtolower( $pp['payer_email'] );
$mem_match = $meta_mem_id == intval( $pp['item_number'] );
$url = remove_query_arg( array( 'action', 'log' ) );
?>
<form method="POST" action="<?php echo esc_url( $url ); ?>" class="matching-form">
<input type="hidden" name="action" value="m2_pp_match" />
<input type="hidden" name="log_id" value="<?php echo $log_id; ?>" />
<h2>Transaction #<?php echo $log->id; ?></h2>
<h3>Details From PayPal</h3>
<p>
PayPal Invoice ID: <strong><?php echo $pp['invoice']; ?></strong><br>
Payer First/Lastname: <strong><?php echo $pp['first_name']; ?> <?php echo $pp['last_name']; ?></strong><br>
Payer Email: <strong><?php echo $pp['payer_email']; ?></strong><br>
Purchased Item ID: <strong><?php echo $pp['item_number']; ?></strong><br>
Purchased Item Name: <strong><?php echo $pp['item_name']; ?></strong><br>
</p>
<h3>Reconstructed From DB</h3>
<p>
User ID: <strong><?php echo $meta_user_id; ?></strong><br>
User Name: <strong><?php echo $meta_user->first_name; ?> <?php echo $meta_user->last_name; ?></strong>
<?php if ( $name_match ) : ?>
<i class="dashicons dashicons-yes" style="color:#080;"></i>
<?php else : ?>
<i class="dashicons dashicons-no" style="color:#C00;"></i>
<?php endif; ?>
<br>
User Email: <strong><?php echo $meta_user->user_email; ?></strong>
<?php if ( $email_match ) : ?>
<i class="dashicons dashicons-yes" style="color:#080;"></i>
<?php else : ?>
<i class="dashicons dashicons-no" style="color:#C00;"></i>
<?php endif; ?>
<br>
Membership ID: <strong><?php echo $meta_mem_id; ?> - <?php echo $meta_mem->name; ?></strong>
<?php if ( $mem_match ) : ?>
<i class="dashicons dashicons-yes" style="color:#080;"></i>
<?php else : ?>
<i class="dashicons dashicons-no" style="color:#C00;"></i>
<?php endif; ?>
<br>
</p>
<h3>M2 Matching Details</h3>
<p>
WordPress User:
<?php if ( $email_match && $name_match ) : ?>
<input type="hidden" name="user_id" value="<?php echo esc_attr( $meta_user_id ); ?>" />
<strong><?php echo $meta_user_id; ?> - <?php echo $meta_user->first_name; ?> <?php echo $meta_user->last_name; ?></strong>
<?php else : ?>
<select name="user_id"><?php echo implode( '', $user_options ); ?></select>
<?php endif; ?>
<br>
Membership ID:
<?php if ( $mem_match ) : ?>
<input type="hidden" name="membership_id" value="<?php echo esc_attr( $meta_mem_id ); ?>" />
<strong><?php echo $meta_mem_id; ?> - <?php echo $meta_mem->name; ?></strong>
<?php else : ?>
<select name="membership_id"><?php echo implode( '', $membership_options ); ?></select>
<?php endif; ?>
<br>
PayPal Invoice ID:
<?php if ( $inv_id ) : ?>
<input type="hidden" name="inv_id" value="<?php echo esc_attr( $inv_id ); ?>" />
<strong><?php echo $inv_id; ?></strong>
<?php else : ?>
<input type="text" name="inv_id" value="" />
<?php endif; ?><br>
</p>
<p>
<a href="<?php echo esc_url( $url ); ?>" class="button">Cancel</a>
<button class="button button-primary">Create missing invoice</button>
</p>
</form>
<script>jQuery(function(){ jQuery('.matching-form select').wpmuiSelect(); })</script>
<?php
return '';
}
public function show_matching_result( $code, $log ) {
echo '<div class="updated notice is-dismissible"><p>' . $this->match_result . '</p></div>';
return $code;
}
protected function create_matching() {
global $wpdb;
if ( empty( $_POST['inv_id'] ) || empty( $_POST['user_id'] ) || empty( $_POST['membership_id'] ) || empty( $_POST['log_id'] ) ) {
// Missing or invalid input data.
$this->match_result = 'Required data missing, submit the form again';
return;
}
$inv_id = intval( $_POST['inv_id'] );
$user_id = intval( $_POST['user_id'] );
$membership_id = intval( $_POST['membership_id'] );
$log_id = intval( $_POST['log_id'] );
$sql = "SELECT 1 FROM {$wpdb->posts} WHERE ID=%s;";
$sql = $wpdb->prepare( $sql, $inv_id );
$res = $wpdb->get_var( $sql );
if ( $res ) {
// This post ID already exists, do not overwrite it.
$this->match_result = 'This Invoice ID is already used by some other post/page/etc';
return;
}
// Create entry in the POST table - needed to specify the POST ID!
$data = array(
'ID' => $inv_id,
'post_author' => $user_id,
'post_date' => date( 'Y-m-d' ),
'post_date_gmt' => gmdate( 'Y-m-d' ),
'post_title' => 'Invoice',
'post_name' => 'invoice-' . $inv_id,
'post_status' => 'private',
'post_parent' => 0,
'post_type' => MS_Model_Invoice::get_post_type(),
);
$wpdb->insert(
$wpdb->posts,
$data
);
// Load and re-save the whole invoice object to fill in missing fields.
$member = MS_Factory::load( 'MS_Model_Member', $user_id );
$sub = $member->get_subscription( $membership_id );
if ( $sub ) {
$member->add_membership( $membership_id );
$sub = $member->get_subscription( $membership_id );
}
$inv = MS_Factory::load( 'MS_Model_Invoice', $inv_id );
$inv->gateway_id = 'paypalstandard';
$inv->user_id = $user_id;
$inv->membership_id = $membership_id;
$inv->ms_relationship_id = $sub->id;
$inv->add_notes( 'Created by M2 PayPal Matching plugin' );
$inv->save();
// Re-Process the transaction.
if ( MS_Model_Import::retry_to_process( $log_id ) ) {
$this->match_result = 'Matching created and payment was processed correctly.';
} else {
$this->match_result = 'Matching created but could not process payment.';
}
}
public function logs_invoice_column( $code, $log ) {
if ( 'err' != $log->state ) { return $code; }
if ( 'paypalstandard' != $log->gateway_id ) { return $code; }
lib3()->array->equip( $log->post, 'invoice' );
$inv_id = intval( $log->post['invoice'] );
if ( ! $inv_id ) {
$code .= '<div>N/A</div>';
return $code;
}
$meta_user = intval( get_post_meta( $inv_id, 'user_id', true ) );
$user = get_user_by( 'id', $meta_user );
if ( $user && $user->ID ) {
$label = 'Match';
$class = 'button button-primary';
} else {
$label = 'Check';
$class = 'button';
}
$url = add_query_arg(
array(
'action' => 'm2_pp_inv',
'log' => $log->id
)
);
$code .= sprintf(
'<div><a href="%s" class="%s" style="min-width:0;width:100%%">%s</a></div>',
esc_url( $url ),
esc_attr( $class ),
esc_html( $label )
);
return $code;
}
}; // End of the M2_PP_Inv_Matching class
add_action( 'plugins_loaded', array( 'M2_PP_Inv_Matching', 'init' ) );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment