/** * Completely whipe all Membership data from Database. * * Note: This function is not used currently... * * @since 1.0.0 */ private static function cleanup_db() { global $wpdb; $sql = array(); $trash_ids = array(); // Delete membership meta-data from users. $users = MS_Model_Member::get_members(); foreach ($users as $user) { $user->delete_all_membership_usermeta(); $user->save(); } // Determine IDs of Membership Pages. $page_types = MS_Model_Pages::get_page_types(); foreach ($page_types as $type => $name) { $page_id = MS_Model_Pages::get_setting($type); $trash_ids[] = $page_id; } /** * Delete all plugin settings. * Settings are saved by classes that extend MS_Model_option */ foreach (MS_Model_Gateway::get_gateways() as $option) { $option->delete(); } MS_Factory::load('MS_Model_Addon')->delete(); MS_Factory::load('MS_Model_Pages')->delete(); MS_Factory::load('MS_Model_Settings')->delete(); /** * Delete transient data * Transient data is saved by classed that extend MS_Model_Transient */ MS_Factory::load('MS_Model_Simulate')->delete(); /** * Delete all plugin content. * Content is saved by classes that extend MS_Model_CustomPostType */ $ms_posttypes = array(MS_Model_Communication::get_post_type(), MS_Model_Event::get_post_type(), MS_Model_Invoice::get_post_type(), MS_Model_Transactionlog::get_post_type(), MS_Model_Membership::get_post_type(), MS_Model_Relationship::get_post_type(), MS_Addon_Coupon_Model::get_post_type(), MS_Addon_Invitation_Model::get_post_type()); foreach ($ms_posttypes as $type) { $sql[] = $wpdb->prepare("DELETE FROM {$wpdb->posts} WHERE post_type = %s;", $type); } // Remove orphaned post-metadata. $sql[] = "\n\t\tDELETE FROM {$wpdb->postmeta}\n\t\tWHERE NOT EXISTS (\n\t\t\tSELECT 1 FROM {$wpdb->posts} tmp WHERE tmp.ID = post_id\n\t\t);\n\t\t"; // Clear all WP transient cache. $sql[] = "\n\t\tDELETE FROM {$wpdb->options}\n\t\tWHERE option_name LIKE '_transient_%';\n\t\t"; foreach ($sql as $s) { $wpdb->query($s); } // Move Membership pages to trash. foreach ($trash_ids as $id) { wp_delete_post($id, true); } // Clear all data from WP Object cache. wp_cache_flush(); // Redirect to the main page. wp_safe_redirect(MS_Controller_Plugin::get_admin_url()); exit; }
/** * Defines predefines filters for this list table. * * @since 1.0.0 * @return array */ public function get_views() { $views = array(); $base_url = remove_query_arg(array('state', 'id', 'invoice')); $views['all'] = array('label' => __('All', 'membership2'), 'url' => $base_url, 'count' => MS_Model_Transactionlog::get_item_count()); $views['ok'] = array('label' => __('Successful', 'membership2'), 'url' => add_query_arg('state', 'ok', $base_url), 'count' => MS_Model_Transactionlog::get_item_count(array('state' => 'ok'))); $views['err'] = array('label' => __('Failed', 'membership2'), 'url' => add_query_arg('state', 'err', $base_url), 'count' => MS_Model_Transactionlog::get_item_count(array('state' => 'err'))); $views['ignore'] = array('label' => __('Ignored', 'membership2'), 'url' => add_query_arg('state', 'ignore', $base_url), 'count' => MS_Model_Transactionlog::get_item_count(array('state' => 'ignore'))); return apply_filters('ms_helper_listtable_transactionlog_views', $views); }
/** * Processes gateway IPN return. * * @since 1.0.0 * @param MS_Model_Transactionlog $log Optional. A transaction log item * that will be updated instead of creating a new log entry. */ public function handle_return($log = false) { $success = false; $exit = false; $redirect = false; $notes = ''; $status = null; $external_id = null; $invoice_id = 0; $subscription_id = 0; $amount = 0; if (!empty($_POST['vendor_order_id']) && !empty($_POST['md5_hash'])) { $invoice_id = intval($_POST['vendor_order_id']); $invoice = MS_Factory::load('MS_Model_Invoice', $invoice_id); $raw_hash = $_POST['sale_id'] . $this->seller_id . $_POST['invoice_id'] . $this->secret_word; $md5_hash = strtoupper(md5($raw_hash)); if ($md5_hash == $_POST['md5_hash'] && !empty($_POST['message_type']) && ($invoice->id = $invoice_id)) { $subscription = $invoice->get_subscription(); $membership = $subscription->get_membership(); $member = $subscription->get_member(); $subscription_id = $subscription->id; $external_id = $_POST['invoice_id']; $amount = (double) $_POST['invoice_list_amount']; switch ($_POST['message_type']) { case 'RECURRING_INSTALLMENT_SUCCESS': $notes = 'Payment received'; // Not sure if the invoice was already paid via the // INVOICE_STATUS_CHANGED message if (!$invoice->is_paid()) { $success = true; } break; case 'INVOICE_STATUS_CHANGED': $notes = sprintf('Invoice was %s', $_POST['invoice_status']); switch ($_POST['invoice_status']) { case 'deposited': // Not sure if invoice was already paid via the // RECURRING_INSTALLMENT_SUCCESS message. if (!$invoice->is_paid()) { $success = true; } break; case 'declied': $status = MS_Model_Invoice::STATUS_DENIED; break; } break; case 'RECURRING_STOPPED': $notes = 'Recurring payments stopped manually'; $member->cancel_membership($membership->id); $member->save(); break; case 'FRAUD_STATUS_CHANGED': $notes = 'Ignored: Users Fraud-status was checked'; $success = null; break; case 'ORDER_CREATED': $notes = 'Ignored: 2Checkout created a new order'; $success = null; break; case 'RECURRING_RESTARTED': $notes = 'Ignored: Recurring payments started'; $success = null; break; case 'RECURRING_COMPLETE': $notes = 'Ignored: Recurring complete'; $success = null; break; case 'RECURRING_INSTALLMENT_FAILED': $notes = 'Ignored: Recurring payment failed'; $success = null; $status = MS_Model_Invoice::STATUS_PENDING; break; default: $notes = sprintf('Warning: Unclear command "%s"', $_POST['message_type']); break; } $invoice->add_notes($notes); $invoice->save(); if ($success) { $invoice->pay_it($this->id, $external_id); } elseif (!empty($status)) { $invoice->status = $status; $invoice->save(); $invoice->changed(); } do_action('ms_gateway_2checkout_payment_processed_' . $status, $invoice, $subscription); } else { $reason = 'Unexpected transaction response'; switch (true) { case $md5_hash != $_POST['md5_hash']: $reason = 'MD5 Hash invalid'; break; case empty($_POST['message_type']): $reason = 'Message type is empty'; break; case $invoice->id != $invoice_id: $reason = sprintf('Expected invoice_id "%s" but got "%s"', $invoice->id, $invoice_id); break; } $notes = 'Response Error: ' . $reason; MS_Helper_Debug::log($notes); MS_Helper_Debug::log($response); $exit = true; } } else { // Did not find expected POST variables. Possible access attempt from a non PayPal site. $notes = 'Error: Missing POST variables. Identification is not possible.'; MS_Helper_Debug::log($notes); $redirect = MS_Helper_Utility::home_url('/'); $exit = true; } if (!$log) { do_action('ms_gateway_transaction_log', self::ID, 'handle', $success, $subscription_id, $invoice_id, $amount, $notes, $external_id); if ($redirect) { wp_safe_redirect($redirect); exit; } if ($exit) { exit; } } else { $log->invoice_id = $invoice_id; $log->subscription_id = $subscription_id; $log->amount = $amount; $log->description = $notes; $log->external_id = $external_id; if ($success) { $log->manual_state('ok'); } $log->save(); } do_action('ms_gateway_2checkout_handle_return_after', $this); if ($log) { return $log; } }
/** * Processes gateway IPN return. * * @since 1.0.0 * @param MS_Model_Transactionlog $log Optional. A transaction log item * that will be updated instead of creating a new log entry. */ public function handle_return($log = false) { $success = false; $exit = false; $redirect = false; $notes = ''; $status = null; $invoice_id = 0; $subscription_id = 0; $amount = 0; do_action('ms_gateway_paypalsingle_handle_return_before', $this); lib3()->array->strip_slashes($_POST, 'pending_reason'); if ((isset($_POST['payment_status']) || isset($_POST['txn_type'])) && !empty($_POST['invoice'])) { if ($this->is_live_mode()) { $domain = 'https://www.paypal.com'; } else { $domain = 'https://www.sandbox.paypal.com'; } // Ask PayPal to validate our $_POST data. $ipn_data = (array) stripslashes_deep($_POST); $ipn_data['cmd'] = '_notify-validate'; $response = wp_remote_post($domain . '/cgi-bin/webscr', array('timeout' => 60, 'sslverify' => false, 'httpversion' => '1.1', 'body' => $ipn_data)); $invoice_id = intval($_POST['invoice']); $external_id = $_POST['txn_id']; $amount = (double) $_POST['mc_gross']; $currency = $_POST['mc_currency']; $invoice = MS_Factory::load('MS_Model_Invoice', $invoice_id); if (!is_wp_error($response) && !MS_Model_Transactionlog::was_processed(self::ID, $external_id) && 200 == $response['response']['code'] && !empty($response['body']) && 'VERIFIED' == $response['body'] && $invoice->id == $invoice_id) { $new_status = false; $subscription = $invoice->get_subscription(); $membership = $subscription->get_membership(); $member = $subscription->get_member(); $subscription_id = $subscription->id; // Process PayPal response switch ($_POST['payment_status']) { // Successful payment case 'Completed': case 'Processed': $success = true; if ($amount == $invoice->total) { $notes .= __('Payment successful', 'membership2'); } else { $notes .= __('Payment registered, though amount differs from invoice.', 'membership2'); } break; case 'Reversed': $notes = __('Last transaction has been reversed. Reason: Payment has been reversed (charge back). ', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Refunded': $notes = __('Last transaction has been reversed. Reason: Payment has been refunded', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Denied': $notes = __('Last transaction has been reversed. Reason: Payment Denied', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Pending': $pending_str = array('address' => __('Customer did not include a confirmed shipping address', 'membership2'), 'authorization' => __('Funds not captured yet', 'membership2'), 'echeck' => __('eCheck that has not cleared yet', 'membership2'), 'intl' => __('Payment waiting for aproval by service provider', 'membership2'), 'multi-currency' => __('Payment waiting for service provider to handle multi-currency process', 'membership2'), 'unilateral' => __('Customer did not register or confirm his/her email yet', 'membership2'), 'upgrade' => __('Waiting for service provider to upgrade the PayPal account', 'membership2'), 'verify' => __('Waiting for service provider to verify his/her PayPal account', 'membership2'), '*' => ''); $reason = $_POST['pending_reason']; $notes = __('Last transaction is pending. Reason: ', 'membership2') . (isset($pending_str[$reason]) ? $pending_str[$reason] : $pending_str['*']); $status = MS_Model_Invoice::STATUS_PENDING; break; default: case 'Partially-Refunded': case 'In-Progress': $success = null; break; } if ('new_case' == $_POST['txn_type'] && 'dispute' == $_POST['case_type']) { // Status: Dispute $status = MS_Model_Invoice::STATUS_DENIED; $notes = __('Dispute about this payment', 'membership2'); } if (!empty($notes)) { $invoice->add_notes($notes); } $invoice->save(); if ($success) { $invoice->pay_it($this->id, $external_id); } elseif (!empty($status)) { $invoice->status = $status; $invoice->save(); $invoice->changed(); } do_action('ms_gateway_paypalsingle_payment_processed_' . $status, $invoice, $subscription); } else { $reason = 'Unexpected transaction response'; switch (true) { case is_wp_error($response): $reason = 'Response is error'; break; case 200 != $response['response']['code']: $reason = 'Response code is ' . $response['response']['code']; break; case empty($response['body']): $reason = 'Response is empty'; break; case 'VERIFIED' != $response['body']: $reason = sprintf('Expected response "%s" but got "%s"', 'VERIFIED', (string) $response['body']); break; case $invoice->id != $invoice_id: $reason = sprintf('Expected invoice_id "%s" but got "%s"', $invoice->id, $invoice_id); break; case MS_Model_Transactionlog::was_processed(self::ID, $external_id): $reason = 'Duplicate: Already processed that transaction.'; break; } $notes = 'Response Error: ' . $reason; $exit = true; } } else { // Did not find expected POST variables. Possible access attempt from a non PayPal site. $u_agent = $_SERVER['HTTP_USER_AGENT']; if (false === strpos($u_agent, 'PayPal')) { // Very likely someone tried to open the URL manually. Redirect to home page $notes = 'Error: Missing POST variables. Redirect user to Home-URL.'; $redirect = MS_Helper_Utility::home_url('/'); } else { $notes = 'Error: Missing POST variables. Identification is not possible.'; } $exit = true; } if (!$log) { do_action('ms_gateway_transaction_log', self::ID, 'handle', $success, $subscription_id, $invoice_id, $amount, $notes, $external_id); if ($redirect) { wp_safe_redirect($redirect); exit; } if ($exit) { exit; } } else { $log->invoice_id = $invoice_id; $log->subscription_id = $subscription_id; $log->amount = $amount; $log->description = $notes; $log->external_id = $external_id; if ($success) { $log->manual_state('ok'); } $log->save(); } do_action('ms_gateway_paypalsingle_handle_return_after', $this, $log); if ($log) { return $log; } }
/** * Loads the items that are displayed on the current list page. * * @since 1.0.1.2 */ public function prepare_items() { $this->_column_headers = array($this->get_columns(), $this->get_hidden_columns(), $this->get_sortable_columns()); $current_page = $this->get_pagenum(); $args = array('posts_per_page' => -1, 'offset' => 0); if (!empty($_GET['source']) && !empty($_GET['source_id'])) { $this->matching_type = $_GET['source']; $this->matching_type_id = $_GET['source_id']; $args['state'] = array('err', 'ignore'); $args['source'] = array($this->matching_type_id, $this->matching_type); $total_items = MS_Model_Transactionlog::get_item_count($args); $this->items = apply_filters('ms_helper_listtable_transactionmatching_items', MS_Model_Transactionlog::get_items($args)); $this->set_pagination_args(array('total_items' => $total_items, 'per_page' => $per_page)); } }
/** * Create view output. * * @since 1.0.0 * * @return string */ public function to_html() { $this->check_simulation(); $buttons = array(); // Count invalid transactions. $args = array('state' => 'err'); $error_count = MS_Model_Transactionlog::get_item_count($args); if ($error_count && (empty($_GET['state']) || 'err' != $_GET['state'])) { if (1 == $error_count) { $message = __('One transaction failed. Please %2$sreview the logs%3$s and decide if you want to ignore the transaction or manually assign it to an invoice.', MS_TEXT_DOMAIN); } else { $message = __('%1$s transactions failed. Please %2$sreview the logs%3$s and decide if you want to ignore the transaction or manually assign it to an invoice.', MS_TEXT_DOMAIN); } $review_url = MS_Controller_Plugin::get_admin_url('billing', array('show' => 'logs', 'state' => 'err')); lib2()->ui->admin_message(sprintf($message, $error_count, '<a href="' . $review_url . '">', '</a>'), 'err'); } if (isset($_GET['show']) && 'logs' == $_GET['show']) { $title = __('Transaction Logs', MS_TEXT_DOMAIN); $listview = MS_Factory::create('MS_Helper_ListTable_TransactionLog'); $listview->prepare_items(); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing'), 'value' => __('Show Invoices', MS_TEXT_DOMAIN), 'class' => 'button'); } else { $title = __('Billing', MS_TEXT_DOMAIN); $listview = MS_Factory::create('MS_Helper_ListTable_Billing'); $listview->prepare_items(); $buttons[] = array('id' => 'add_new', 'type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('action' => MS_Controller_Billing::ACTION_EDIT, 'invoice_id' => 0)), 'value' => __('Create new Invoice', MS_TEXT_DOMAIN), 'class' => 'button'); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('show' => 'logs')), 'value' => __('Show Transaction Logs', MS_TEXT_DOMAIN), 'class' => 'button'); if (!empty($_GET['gateway_id'])) { $gateway = MS_Model_Gateway::factory($_GET['gateway_id']); if ($gateway->name) { $title .= ' - ' . $gateway->name; } } } ob_start(); ?> <div class="wrap ms-wrap ms-billing"> <?php MS_Helper_Html::settings_header(array('title' => $title, 'title_icon_class' => 'wpmui-fa wpmui-fa-credit-card')); ?> <div> <?php foreach ($buttons as $button) { MS_Helper_Html::html_element($button); } ?> </div> <?php $listview->views(); $listview->search_box(__('User', MS_TEXT_DOMAIN), 'search'); ?> <form action="" method="post"> <?php $listview->display(); ?> </form> </div> <?php $html = ob_get_clean(); return apply_filters('ms_view_billing_list', $html, $this); }
/** * Loads the items that are displayed on the current list page. * * @since 1.0.0 */ public function prepare_items() { $this->_column_headers = array($this->get_columns(), $this->get_hidden_columns(), $this->get_sortable_columns()); $per_page = $this->get_items_per_page('transactionlog_per_page', self::DEFAULT_PAGE_SIZE); $current_page = $this->get_pagenum(); $args = array('posts_per_page' => $per_page, 'offset' => ($current_page - 1) * $per_page); if (!empty($_GET['state'])) { $args['state'] = $_GET['state']; } if (!empty($_GET['gateway_id'])) { $args['meta_query'] = array('gateway_id' => array('key' => '_gateway_id', 'value' => $_GET['gateway_id'])); } $total_items = MS_Model_Transactionlog::get_item_count($args); $this->items = apply_filters('ms_helper_listtable_transactionlog_items', MS_Model_Transactionlog::get_items($args)); $this->set_pagination_args(array('total_items' => $total_items, 'per_page' => $per_page)); }
/** * Create view output. * * @since 1.0.0 * * @return string */ public function to_html() { $this->check_simulation(); $buttons = array(); $module = 'billing'; if (isset($_GET['show'])) { $module = $_GET['show']; } if (!$module) { // Show a message if there are error-state transactions. $args = array('state' => 'err'); $error_count = MS_Model_Transactionlog::get_item_count($args); if ($error_count) { if (1 == $error_count) { $message = __('One transaction failed. Please %2$sreview the logs%3$s and decide if you want to ignore the transaction or manually assign it to an invoice.', 'membership2'); } else { $message = __('%1$s transactions failed. Please %2$sreview the logs%3$s and decide if you want to ignore the transaction or manually assign it to an invoice.', 'membership2'); } $review_url = MS_Controller_Plugin::get_admin_url('billing', array('show' => 'logs', 'state' => 'err')); lib3()->ui->admin_message(sprintf($message, $error_count, '<a href="' . $review_url . '">', '</a>'), 'err'); } } // Decide which list to display in the Billings page. switch ($module) { // Transaction logs. case 'logs': $title = __('Transaction Logs', 'membership2'); $listview = MS_Factory::create('MS_Helper_ListTable_TransactionLog'); $listview->prepare_items(); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing'), 'value' => __('Show Invoices', 'membership2'), 'class' => 'button'); break; // M1 Migration matching. // M1 Migration matching. case 'matching': $title = __('Automatic Transaction Matching', 'membership2'); $listview = MS_Factory::create('MS_Helper_ListTable_TransactionMatching'); $listview->prepare_items(); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing'), 'value' => __('Show Invoices', 'membership2'), 'class' => 'button'); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('show' => 'logs')), 'value' => __('Show Transaction Logs', 'membership2'), 'class' => 'button'); break; // Default billings list. // Default billings list. case 'billing': default: $title = __('Billing', 'membership2'); $listview = MS_Factory::create('MS_Helper_ListTable_Billing'); $listview->prepare_items(); $buttons[] = array('id' => 'add_new', 'type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('action' => MS_Controller_Billing::ACTION_EDIT, 'invoice_id' => 0)), 'value' => __('Create new Invoice', 'membership2'), 'class' => 'button'); $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('show' => 'logs')), 'value' => __('Show Transaction Logs', 'membership2'), 'class' => 'button'); if (!empty($_GET['gateway_id'])) { $gateway = MS_Model_Gateway::factory($_GET['gateway_id']); if ($gateway->name) { $title .= ' - ' . $gateway->name; } } break; } if ('matching' != $module) { if (MS_Model_Import::can_match()) { $btn_label = __('Setup automatic matching', 'membership2'); $btn_class = 'button'; } else { $btn_label = '(' . __('Setup automatic matching', 'membership2') . ')'; $btn_class = 'button button-link'; } $buttons[] = array('type' => MS_Helper_Html::TYPE_HTML_LINK, 'url' => MS_Controller_Plugin::get_admin_url('billing', array('show' => 'matching')), 'value' => $btn_label, 'class' => $btn_class); } // Default list view part - dislay prepared values from above. ob_start(); ?> <div class="wrap ms-wrap ms-billing"> <?php MS_Helper_Html::settings_header(array('title' => $title, 'title_icon_class' => 'wpmui-fa wpmui-fa-credit-card')); ?> <div> <?php foreach ($buttons as $button) { MS_Helper_Html::html_element($button); } ?> </div> <?php $listview->views(); $listview->search_box(__('User', 'membership2'), 'search'); ?> <form action="" method="post"> <?php $listview->display(); ?> </form> </div> <?php $html = ob_get_clean(); return apply_filters('ms_view_billing_list', $html, $this); }
/** * Processes gateway IPN return. * * @since 1.0.0 * @param MS_Model_Transactionlog $log Optional. A transaction log item * that will be updated instead of creating a new log entry. */ public function handle_return($log = false) { $success = false; $ignore = false; $exit = false; $redirect = false; $notes = ''; $status = null; $notes_pay = ''; $notes_txn = ''; $external_id = null; $invoice_id = 0; $subscription_id = 0; $amount = 0; $transaction_type = ''; $payment_status = ''; $ext_type = false; if (!empty($_POST['txn_type'])) { $transaction_type = strtolower($_POST['txn_type']); } if (isset($_POST['mc_gross'])) { $amount = (double) $_POST['mc_gross']; } elseif (isset($_POST['mc_amount3'])) { // mc_amount1 and mc_amount2 are for trial period prices. $amount = (double) $_POST['mc_amount3']; } if (!empty($_POST['payment_status'])) { $payment_status = strtolower($_POST['payment_status']); } if (!empty($_POST['txn_id'])) { $external_id = $_POST['txn_id']; } if (!empty($_POST['mc_currency'])) { $currency = $_POST['mc_currency']; } // Step 1: Find the invoice_id and determine if payment is M2 or M1. if ($payment_status || $transaction_type) { if (!empty($_POST['invoice'])) { // BEST CASE: // 'invoice' is set in all regular M2 subscriptions! $invoice_id = intval($_POST['invoice']); /* * PayPal only knows the first invoice of the subscription. * So we need to check: If the invoice is already paid then the * payment is for a follow-up invoice. */ $invoice = MS_Factory::load('MS_Model_Invoice', $invoice_id); if ($invoice->is_paid()) { $subscription = $invoice->get_subscription(); $invoice_id = $subscription->first_unpaid_invoice(); } } elseif (!empty($_POST['custom'])) { // FALLBACK A: // Maybe it's an imported M1 subscription. $infos = explode(':', $_POST['custom']); if (count($infos) > 2) { // $infos should contain [timestamp, user_id, sub_id, key] $m1_user_id = intval($infos[1]); $m1_sub_id = intval($infos[2]); // Roughtly equals M2 membership->id. // M1 payments use the following type/status values. $pay_types = array('subscr_signup', 'subscr_payment'); $pay_stati = array('completed', 'processed'); if ($m1_user_id > 0 && $m1_sub_id > 0) { if (in_array($transaction_type, $pay_types)) { $ext_type = 'm1'; } elseif (in_array($payment_status, $pay_stati)) { $ext_type = 'm1'; } } if ('m1' == $ext_type) { $is_linked = false; // Seems to be a valid M1 payment: // Find the associated imported subscription! $subscription = MS_Model_Import::find_subscription($m1_user_id, $m1_sub_id, 'source', self::ID); if (!$subscription) { $membership = MS_Model_Import::membership_by_source($m1_sub_id); if ($membership) { $is_linked = true; $notes = sprintf('Error: User is not subscribed to Membership %s.', $membership->id); } } $invoice_id = $subscription->first_unpaid_invoice(); if (!$is_linked && !$invoice_id) { MS_Model_Import::need_matching($m1_sub_id, 'm1'); } } // end if: 'm1' == $ext_type } } elseif (!empty($_POST['btn_id']) && !empty($_POST['payer_email'])) { // FALLBACK B: // Payment was made by a custom PayPal Payment button. $user = get_user_by('email', $_POST['payer_email']); if ($user && $user->ID) { $ext_type = 'pay_btn'; $is_linked = false; $subscription = MS_Model_Import::find_subscription($user->ID, $_POST['btn_id'], 'pay_btn', self::ID); if (!$subscription) { $membership = MS_Model_Import::membership_by_matching('pay_btn', $_POST['btn_id']); if ($membership) { $is_linked = true; $notes = sprintf('Error: User is not subscribed to Membership %s.', $membership->id); } } $invoice_id = $subscription->first_unpaid_invoice(); if (!$is_linked && !$invoice_id) { MS_Model_Import::need_matching($_POST['btn_id'], 'pay_btn'); } } else { $notes = sprintf('Error: Could not find user "%s".', $_POST['payer_email']); } // end if: 'pay_btn' == $ext_type } } // Step 2a: Check if the txn_id was already processed by M2. if (MS_Model_Transactionlog::was_processed(self::ID, $external_id)) { $notes = 'Duplicate: Already processed that transaction.'; $success = false; $ignore = true; } elseif ($invoice_id) { if ($this->is_live_mode()) { $domain = 'https://www.paypal.com'; } else { $domain = 'https://www.sandbox.paypal.com'; } // PayPal post authenticity verification. $ipn_data = (array) stripslashes_deep($_POST); $ipn_data['cmd'] = '_notify-validate'; $response = wp_remote_post($domain . '/cgi-bin/webscr', array('timeout' => 60, 'sslverify' => false, 'httpversion' => '1.1', 'body' => $ipn_data)); $invoice = MS_Factory::load('MS_Model_Invoice', $invoice_id); if (!is_wp_error($response) && 200 == $response['response']['code'] && !empty($response['body']) && 'VERIFIED' == $response['body'] && $invoice->id == $invoice_id) { $subscription = $invoice->get_subscription(); $membership = $subscription->get_membership(); $member = $subscription->get_member(); $subscription_id = $subscription->id; // Process PayPal payment status if ($payment_status) { switch ($payment_status) { // Successful payment case 'completed': case 'processed': $success = true; if ($amount == $invoice->total) { $notes .= __('Payment successful', 'membership2'); } else { $notes .= __('Payment registered, though amount differs from invoice.', 'membership2'); } break; case 'reversed': $notes_pay = __('Last transaction has been reversed. Reason: Payment has been reversed (charge back).', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'refunded': $notes_pay = __('Last transaction has been reversed. Reason: Payment has been refunded.', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'denied': $notes_pay = __('Last transaction has been reversed. Reason: Payment Denied.', 'membership2'); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'pending': lib3()->array->strip_slashes($_POST, 'pending_reason'); $notes_pay = __('Last transaction is pending.', 'membership2') . ' '; switch ($_POST['pending_reason']) { case 'address': $notes_pay .= __('Customer did not include a confirmed shipping address', 'membership2'); break; case 'authorization': $notes_pay .= __('Funds not captured yet', 'membership2'); break; case 'echeck': $notes_pay .= __('The eCheck has not cleared yet', 'membership2'); break; case 'intl': $notes_pay .= __('Payment waiting for approval by service provider', 'membership2'); break; case 'multi-currency': $notes_pay .= __('Payment waiting for service provider to handle multi-currency process', 'membership2'); break; case 'unilateral': $notes_pay .= __('Customer did not register or confirm his/her email yet', 'membership2'); break; case 'upgrade': $notes_pay .= __('Waiting for service provider to upgrade the PayPal account', 'membership2'); break; case 'verify': $notes_pay .= __('Waiting for service provider to verify his/her PayPal account', 'membership2'); break; default: $notes_pay .= __('Unknown reason', 'membership2'); break; } $status = MS_Model_Invoice::STATUS_PENDING; $ignore = true; break; default: case 'partially-refunded': case 'in-progress': $notes_pay = sprintf(__('Not handling payment_status: %s', 'membership2'), $payment_status); $ignore = true; break; } } // Check for subscription details if ($transaction_type) { switch ($transaction_type) { case 'subscr_signup': case 'subscr_payment': // Payment was received $notes_txn = __('PayPal Subscripton has been created.', 'membership2'); if (0 == $invoice->total) { $success = true; } else { $ignore = true; } break; case 'subscr_modify': // Payment profile was modified $notes_txn = __('PayPal Subscription has been modified.', 'membership2'); $ignore = true; break; case 'recurring_payment_profile_canceled': case 'subscr_cancel': // Subscription was manually cancelled. $notes_txn = __('PayPal Subscription has been canceled.', 'membership2'); $member->cancel_membership($membership->id); $member->save(); $ignore = true; break; case 'recurring_payment_suspended': // Recurring subscription was manually suspended. $notes_txn = __('PayPal Subscription has been suspended.', 'membership2'); $member->cancel_membership($membership->id); $member->save(); $ignore = true; break; case 'recurring_payment_suspended_due_to_max_failed_payment': // Recurring subscription was automatically suspended. $notes_txn = __('PayPal Subscription has failed.', 'membership2'); $member->cancel_membership($membership->id); $member->save(); $ignore = true; break; case 'new_case': // New Dispute was filed for a payment. $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'subscr_eot': /* * Meaning: Subscription expired. * * - after a one-time payment was made * - after last transaction in a recurring subscription * - payment failed * - ... * * We do not handle this event... * * One time payment sends 3 messages: * 1. subscr_start (new subscription starts) * 2. subscr_payment (payment confirmed) * 3. subscr_eot (subscription ends) */ $notes_txn = __('No more payments will be made for this subscription.', 'membership2'); $ignore = true; break; default: // Other event that we do not have a case for... $notes_txn = sprintf(__('Not handling txn_type: %s', 'membership2'), $transaction_type); $ignore = true; break; } } if (!empty($notes_pay)) { $invoice->add_notes($notes_pay); } if (!empty($notes_txn)) { $invoice->add_notes($notes_txn); } if ($notes_pay) { $notes .= ($notes ? ' | ' : '') . $notes_pay; } if ($notes_txn) { $notes .= ($notes ? ' | ' : '') . $notes_txn; } $invoice->save(); if ($success) { $invoice->pay_it($this->id, $external_id); } elseif (!empty($status)) { $invoice->status = $status; $invoice->save(); $invoice->changed(); } do_action('ms_gateway_paypalstandard_payment_processed_' . $status, $invoice, $subscription); } else { $reason = 'Unexpected transaction response'; switch (true) { case is_wp_error($response): $reason = 'PayPal did not verify this transaction: Unknown error'; break; case 200 != $response['response']['code']: $reason = sprintf('PayPal did not verify the transaction: Code %s', $response['response']['code']); break; case empty($response['body']): $reason = 'PayPal did not verify this transaction: Empty response'; break; case 'VERIFIED' != $response['body']: $reason = sprintf('PayPal did not verify this transaction: "%s"', $response['body']); break; case !$invoice->id: $reason = sprintf('Specified invoice does not exist: "%s"', $invoice_id); break; } $notes = 'Response Error: ' . $reason; $exit = true; } } else { // Did not find expected POST variables. Possible access attempt from a non PayPal site. $u_agent = $_SERVER['HTTP_USER_AGENT']; if (!$log && false === strpos($u_agent, 'PayPal')) { // Very likely someone tried to open the URL manually. Redirect to home page if (!$notes) { $notes = 'Ignored: Missing POST variables. Redirect to Home-URL.'; } $redirect = MS_Helper_Utility::home_url('/'); $ignore = true; $success = false; } elseif ('m1' == $ext_type) { /* * The payment belongs to an imported M1 subscription and could * not be auto-matched. * Do not return an error code, but also do not modify any * invoice/subscription. */ $notes = 'M1 Payment detected. Manual matching required.'; $ignore = false; $success = false; } elseif ('pay_btn' == $ext_type) { /* * The payment was made by a PayPal Payment button that was * created in the PayPal account and not by M1/M2. */ $notes = 'PayPal Payment button detected. Manual matching required.'; $ignore = false; $success = false; } else { // PayPal sent us a IPN notice about a non-Membership payment: // Ignore it, but add it to the logs. if (!empty($notes)) { // We already have an error message, do nothing. } elseif (!$payment_status || !$transaction_type) { $notes = 'Ignored: Payment_status or txn_type not specified. Cannot process.'; } elseif (empty($_POST['invoice']) && empty($_POST['custom'])) { $notes = 'Ignored: No invoice or custom data specified.'; } else { $notes = 'Ignored: Missing POST variables. Identification is not possible.'; } $ignore = true; $success = false; } $exit = true; } if ($ignore && !$success) { $success = null; $notes .= ' [Irrelevant IPN call]'; } if (!$log) { do_action('ms_gateway_transaction_log', self::ID, 'handle', $success, $subscription_id, $invoice_id, $amount, $notes, $external_id); if ($redirect) { wp_safe_redirect($redirect); exit; } if ($exit) { exit; } } else { $log->invoice_id = $invoice_id; $log->subscription_id = $subscription_id; $log->amount = $amount; $log->description = $notes; $log->external_id = $external_id; if ($success) { $log->manual_state('ok'); } elseif ($ignore) { $log->manual_state('ignore'); } $log->save(); } do_action('ms_gateway_paypalstandard_handle_return_after', $this); if ($log) { return $log; } }