/** * Implements a multi-dimensional array_intersect_assoc. * * The standard array_intersect_assoc does the intersection based on string * values of the keys. * We need to find a way to recursively check multi-dimensional arrays. * * Note that we are not passing values here but references. * * @since 1.0.0 * * @param mixed $arr1 First array to intersect. * @param mixed $arr2 Second array to intersect. * @return array Combined array. */ public static function array_intersect_assoc_deep(&$arr1, &$arr2) { // If not arrays, at least associate the strings. // If 1 argument is an array and the other not throw error. if (!is_array($arr1) && !is_array($arr2)) { $arr1 = (string) $arr1; $arr2 = (string) $arr2; return $arr1 == $arr2 ? $arr1 : false; } elseif (is_array($arr1) !== is_array($arr2)) { MS_Helper_Debug::log('WARNING: MS_Helper_Utility::array_intersect_assoc_deep() - ' . 'Both params need to be of same type (array or string).', true); return false; } $intersections = array_intersect(array_keys($arr1), array_keys($arr2)); $assoc_array = array(); // Time to recursively run through the arrays foreach ($intersections as $key) { $result = MS_Helper_Utility::array_intersect_assoc_deep($arr1[$key], $arr2[$key]); if ($result) { $assoc_array[$key] = $result; } } return apply_filters('ms_helper_utility_array_intersect_assoc_deep', $assoc_array, $arr1, $arr2); }
/** * Route page request to handling method. * * @since 1.0.0 */ public function admin_page_router() { $this->wizard_tracker(); $step = $this->get_step(); if (self::is_valid_step($step)) { $method = 'page_' . $step; if (method_exists($this, $method)) { $callback = apply_filters('ms_controller_membership_admin_page_router_callback', array($this, $method), $this); call_user_func($callback); } else { do_action('ms_controller_membership_admin_page_router_' . $step, $this); MS_Helper_Debug::log("Method {$method} not found for step {$step}"); } } else { MS_Helper_Debug::log("Invalid step: {$step}"); } do_action('ms_controller_membership_admin_page_router', $step, $this); }
/** * Render membership payment information. * * Related Filter Hooks: * - the_content * * @since 1.0.0 * * @param string $content The page content to filter. * @return string The filtered content. */ public function payment_table($content) { $data = array(); $subscription = null; $member = MS_Model_Member::get_current_member(); $membership_id = 0; lib2()->array->equip_request('membership_id', 'move_from_id', 'ms_relationship_id'); if (!empty($_POST['ms_relationship_id'])) { // Error path, showing payment table again with error msg $subscription = MS_Factory::load('MS_Model_Relationship', absint(intval($_POST['ms_relationship_id']))); $membership = $subscription->get_membership(); $membership_id = $membership->id; if (!empty($_POST['error'])) { lib2()->array->strip_slashes($_POST, 'error'); $data['error'] = $_POST['error']; } } elseif (!empty($_REQUEST['membership_id'])) { // First time loading $membership_id = intval($_REQUEST['membership_id']); $membership = MS_Factory::load('MS_Model_Membership', $membership_id); $move_from_id = absint($_REQUEST['move_from_id']); $subscription = MS_Model_Relationship::create_ms_relationship($membership_id, $member->id, '', $move_from_id); } else { MS_Helper_Debug::log('Error: missing POST params'); MS_Helper_Debug::log($_POST); return $content; } $invoice = $subscription->get_current_invoice(); /** * Notify Add-ons that we are preparing payment details for a membership * subscription. * * E.g. Coupon discount is applied by this hook. * * @since 1.0.0 */ $invoice = apply_filters('ms_signup_payment_details', $invoice, $subscription, $membership); $invoice->save(); $data['invoice'] = $invoice; $data['membership'] = $membership; $data['member'] = $member; $data['ms_relationship'] = $subscription; $view = MS_Factory::load('MS_View_Frontend_Payment'); $view->data = apply_filters('ms_view_frontend_payment_data', $data, $membership_id, $subscription, $member, $this); return apply_filters('ms_controller_frontend_payment_table', $view->to_html(), $this); }
$msg .= "\nIn " . $callee['file'] . ' on line ' . $callee['line']; } error_log($msg); } public static function debug_trace($return = false) { if (!WP_DEBUG && !WDEV_DEBUG) { return; } $traces = debug_backtrace(); $fields = array('file', 'line', 'function', 'class'); $log = array('---------------------------- Trace start ----------------------------'); foreach ($traces as $i => $trace) { $line = array(); foreach ($fields as $field) { if (!empty($trace[$field])) { $line[] = "{$field}: {$trace[$field]}"; } } $log[] = " [{$i}]" . implode('; ', $line); } if ($return) { return implode("\n", $log); } else { error_log(implode("\n", $log)); } } } MS_Helper_Debug::log('**************************** REQUEST START ****************************'); MS_Helper_Debug::log('***** URL: ' . lib3()->net->current_url());
/** * Merge Membership2 rules. * * Merge every rule model with Membership2/visitor membership rules. * This ensure rules are consistent with Membership2 rules. * * @since 1.0.0 * @internal */ public function merge_protection_rules() { if ($this->is_base()) { // This is the visitor membership, no need to merge anything. return; } $base_rules = self::get_base()->_rules; foreach ($base_rules as $key => $base_rule) { try { // Key could be "type" of "site:type" format. $rule_type = MS_Rule::rule_type($key); $rule = $this->get_rule($rule_type); $rule->protect_undefined_items($base_rule, true); $this->set_rule($rule_type, $rule); } catch (Exception $e) { MS_Helper_Debug::log($e); } } $this->_rules = apply_filters('ms_model_membership_merge_protection_rules', $this->_rules, $this); }
/** * Request automatic payment to the gateway. * * @since 1.0.0 * @api * * @param MS_Model_Relationship $subscription The related membership relationship. * @return bool True on success. */ public function request_payment($subscription) { $was_paid = false; $note = ''; $external_id = ''; do_action('ms_gateway_stripe_request_payment_before', $subscription, $this); $this->_api->set_gateway($this); $member = $subscription->get_member(); $invoice = $subscription->get_current_invoice(); if (!$invoice->is_paid()) { try { $customer = $this->_api->find_customer($member); if (!empty($customer)) { if (0 == $invoice->total) { $invoice->changed(); $success = true; $note = __('No payment for free membership', 'membership2'); } else { $charge = $this->_api->charge($customer, $invoice->total, $invoice->currency, $invoice->name); $external_id = $charge->id; if (true == $charge->paid) { $was_paid = true; $invoice->pay_it($this->id, $external_id); $note = __('Payment successful', 'membership2'); } else { $note = __('Stripe payment failed', 'membership2'); } } } else { $note = "Stripe customer is empty for user {$member->username}"; MS_Helper_Debug::log($note); } } catch (Exception $e) { $note = 'Stripe error: ' . $e->getMessage(); MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_FAILED, $subscription); MS_Helper_Debug::log($note); } } else { // Invoice was already paid earlier. $was_paid = true; $note = __('Invoice already paid', 'membership2'); } do_action('ms_gateway_transaction_log', self::ID, 'request', $was_paid, $subscription->id, $invoice->id, $invoice->total, $note, $external_id); do_action('ms_gateway_stripe_request_payment_after', $subscription, $was_paid, $this); return $was_paid; }
/** * Returns property. * * @since 1.0.0 * @internal * * @param string $property The name of a property. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. */ public function __get($property) { $value = null; switch ($property) { case 'status': $value = $this->get_status(); break; default: if (!property_exists($this, $property)) { MS_Helper_Debug::log('Property does not exist: ' . $property); } else { $value = $this->{$property}; } break; } return apply_filters('ms_model_relationship__get', $value, $property, $this); }
/** * Processes gateway IPN return. * * @since 1.0.0 */ public function handle_return() { $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 = ''; $is_m1 = false; $fields_set = false; if (!empty($_POST['txn_type'])) { $transaction_type = strtolower($_POST['txn_type']); } if (!empty($_POST['payment_status'])) { $payment_status = strtolower($_POST['payment_status']); } if ($payment_status || $transaction_type) { if (!empty($_POST['invoice'])) { // 'invoice' is set in all regular M2 subscriptions. $fields_set = true; } elseif (!empty($_POST['custom'])) { // First: We cannot process this payment. $fields_set = false; // But let's check if it is an M1 payment. $infos = explode(':', $_POST['custom']); if (count($infos) > 2) { // $infos should contain [timestamp, user_id, sub_id, key] $pay_types = array('subscr_signup', 'subscr_payment'); $pay_stati = array('completed', 'processed'); if (in_array($transaction_type, $pay_types)) { $is_m1 = true; } elseif (in_array($payment_status, $pay_stati)) { $is_m1 = true; } } } } if ($fields_set) { 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_id = intval($_POST['invoice']); $currency = $_POST['mc_currency']; $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) { $amount = (double) $_POST['mc_gross']; $external_id = $_POST['txn_id']; switch ($payment_status) { // Successful payment case 'completed': case 'processed': if ($amount == $invoice->total) { $success = true; $notes .= __('Payment successful', MS_TEXT_DOMAIN); } else { $notes_pay = __('Payment amount differs from invoice total.', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; } break; case 'reversed': $notes_pay = __('Last transaction has been reversed. Reason: Payment has been reversed (charge back).', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'refunded': $notes_pay = __('Last transaction has been reversed. Reason: Payment has been refunded.', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'denied': $notes_pay = __('Last transaction has been reversed. Reason: Payment Denied.', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; $ignore = true; break; case 'pending': lib2()->array->strip_slashes($_POST, 'pending_reason'); $notes_pay = __('Last transaction is pending.', MS_TEXT_DOMAIN) . ' '; switch ($_POST['pending_reason']) { case 'address': $notes_pay .= __('Customer did not include a confirmed shipping address', MS_TEXT_DOMAIN); break; case 'authorization': $notes_pay .= __('Funds not captured yet', MS_TEXT_DOMAIN); break; case 'echeck': $notes_pay .= __('The eCheck has not cleared yet', MS_TEXT_DOMAIN); break; case 'intl': $notes_pay .= __('Payment waiting for approval by service provider', MS_TEXT_DOMAIN); break; case 'multi-currency': $notes_pay .= __('Payment waiting for service provider to handle multi-currency process', MS_TEXT_DOMAIN); break; case 'unilateral': $notes_pay .= __('Customer did not register or confirm his/her email yet', MS_TEXT_DOMAIN); break; case 'upgrade': $notes_pay .= __('Waiting for service provider to upgrade the PayPal account', MS_TEXT_DOMAIN); break; case 'verify': $notes_pay .= __('Waiting for service provider to verify his/her PayPal account', MS_TEXT_DOMAIN); break; default: $notes_pay .= __('Unknown reason', MS_TEXT_DOMAIN); 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', MS_TEXT_DOMAIN), $payment_status); MS_Helper_Debug::log($notes_pay); $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 profile has been created.', MS_TEXT_DOMAIN); if (0 == $invoice->total) { $success = true; } else { $ignore = true; } break; case 'subscr_modify': // Payment profile was modified $notes_txn = __('Paypal subscription profile has been modified.', MS_TEXT_DOMAIN); $ignore = true; break; case 'recurring_payment_profile_canceled': case 'subscr_cancel': // Subscription was manually cancelled. $notes_txn = __('Paypal subscription profile has been canceled.', MS_TEXT_DOMAIN); $member->cancel_membership($membership->id); $member->save(); $ignore = true; break; case 'recurring_payment_suspended': // Recurring subscription was manually suspended. $notes_txn = __('Paypal subscription profile has been suspended.', MS_TEXT_DOMAIN); $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 profile has failed.', MS_TEXT_DOMAIN); $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 madeafter last * - 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.', MS_TEXT_DOMAIN); $ignore = true; break; default: // Other event that we do not have a case for... $notes_txn = sprintf(__('Not handling txn_type: %s', MS_TEXT_DOMAIN), $transaction_type); MS_Helper_Debug::log($notes_txn); $ignore = true; break; } } if (!empty($notes_pay)) { $invoice->add_notes($notes_pay); } if (!empty($notes_txn)) { $invoice->add_notes($notes_txn); } $notes .= $notes_pay . ' | ' . $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 != $invoice_id: $reason = sprintf('PayPal gave us an invalid invoice_id: "%s"', $invoice_id); break; } $notes = 'Response Error: ' . $reason; MS_Helper_Debug::log($notes); MS_Helper_Debug::log($response); MS_Helper_Debug::log($_POST); $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.'; MS_Helper_Debug::log($notes); $redirect = home_url(); } elseif ($is_m1) { /* * 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; } else { if (!$payment_status && !$transaction_type) { $notes = 'Error: payment_status and txn_type not specified. Cannot process.'; } elseif (empty($_POST['invoice']) && empty($_POST['custom'])) { $notes = 'Error: No invoice or custom data specified.'; } else { $notes = 'Error: Missing POST variables. Identification is not possible.'; } // PayPal did provide invalid details... status_header(404); MS_Helper_Debug::log($notes); } $exit = true; } if ($ignore && !$success) { $success = null; } do_action('ms_gateway_transaction_log', self::ID, 'handle', $success, $subscription_id, $invoice_id, $amount, $notes); if ($redirect) { wp_safe_redirect($redirect); exit; } if ($exit) { exit; } do_action('ms_gateway_paypalstandard_handle_return_after', $this); }
/** * Check if the subscription is still active. * * @since 1.0.0 * @param MS_Model_Relationship $subscription The related membership relationship. * @return bool True on success. */ public function request_payment($subscription) { $was_paid = false; $note = ''; do_action('ms_gateway_stripeplan_request_payment_before', $subscription, $this); $this->_api->set_gateway($this); $member = $subscription->get_member(); $invoice = $subscription->get_current_invoice(); if (!$invoice->is_paid()) { try { $customer = $this->_api->find_customer($member); if (!empty($customer)) { if (0 == $invoice->total) { $invoice->changed(); $success = true; $note = __('No payment for free membership', MS_TEXT_DOMAIN); } else { // Get or create the subscription. $stripe_sub = $this->_api->subscribe($customer, $invoice); if ('active' == $stripe_sub->status) { $was_paid = true; $invoice->pay_it($this->id, $stripe_sub->id); $note = __('Payment successful', MS_TEXT_DOMAIN); $this->cancel_if_done($subscription, $stripe_sub); } else { $note = __('Stripe payment failed', MS_TEXT_DOMAIN); } } } else { MS_Helper_Debug::log("Stripe customer is empty for user {$member->username}"); } } catch (Exception $e) { $note = 'Stripe error: ' . $e->getMessage(); MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_FAILED, $subscription); MS_Helper_Debug::log($note); } } else { // Invoice was already paid earlier. $was_paid = true; } do_action('ms_gateway_stripeplan_request_payment_after', $subscription, $was_paid, $this); do_action('ms_gateway_transaction_log', self::ID, 'request', $was_paid, $subscription->id, $invoice->id, $invoice->total, $note); return $was_paid; }
/** * Update the subscription details after the invoice has changed. * * Process transaction status change related to this membership relationship. * Change status accordinly to transaction status. * * @since 1.0.0 * @param MS_Model_Invoice $invoice The invoice to process. * @return MS_Model_Invoice The processed invoice. */ public function changed() { do_action('ms_model_invoice_changed_before', $this); if (!$this->ms_relationship_id) { MS_Helper_Debug::log('Cannot process transaction: No relationship defined (inv #' . $this->id . ')'); } else { $subscription = $this->get_subscription(); $member = MS_Factory::load('MS_Model_Member', $this->user_id); $membership = $subscription->get_membership(); switch ($this->status) { case self::STATUS_NEW: case self::STATUS_BILLED: break; case self::STATUS_PAID: if ($this->total > 0) { MS_Model_Event::save_event(MS_Model_Event::TYPE_PAID, $subscription); } do_action('ms_model_invoice_changed-paid', $this, $member); // Check for moving memberships if ($subscription->move_from_id) { $ids = explode(',', $subscription->move_from_id); foreach ($ids as $id) { $move_from = MS_Model_Relationship::get_subscription($subscription->user_id, $id); if ($move_from->is_valid()) { $move_from->cancel_membership(); } } $subscription->cancelled_memberships = $subscription->move_from_id; $subscription->move_from_id = ''; } /* * Memberships with those payment types can have multiple * invoices for a single subscription. */ $multi_invoice = array(MS_Model_Membership::PAYMENT_TYPE_RECURRING, MS_Model_Membership::PAYMENT_TYPE_FINITE); if (in_array($membership->payment_type, $multi_invoice)) { // Update the current_invoice_number counter. $subscription->current_invoice_number = max($subscription->current_invoice_number, $this->invoice_number + 1); } if (MS_Gateway_Manual::ID == $this->gateway_id) { $this->pay_it($this->gateway_id); } break; case self::STATUS_DENIED: MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_DENIED, $subscription); break; case self::STATUS_PENDING: MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_PENDING, $subscription); break; default: do_action('ms_model_invoice_changed-unknown', $this); break; } $member->save(); $subscription->gateway_id = $this->gateway_id; $subscription->save(); $this->gateway_id = $this->gateway_id; $this->save(); } return apply_filters('ms_model_invoice_changed', $this, $this); }
/** * Process purchase using gateway. * * Related Action Hooks: * - ms_controller_frontend_signup_process_purchase * * @since 1.0.0 */ public function process_purchase() { $fields = array('gateway', 'ms_relationship_id'); lib2()->array->equip_request('gateway', 'ms_relationship_id'); $valid = true; $nonce_name = $_REQUEST['gateway'] . '_' . $_REQUEST['ms_relationship_id']; if (!self::validate_required($fields, 'any')) { $valid = false; $err = 'GAT-01 (invalid fields)'; } elseif (!MS_Model_Gateway::is_valid_gateway($_REQUEST['gateway'])) { $valid = false; $err = 'GAT-02 (invalid gateway)'; } elseif (!$this->verify_nonce($nonce_name, 'any')) { $valid = false; $err = 'GAT-03 (invalid nonce)'; } if ($valid) { $subscription = MS_Factory::load('MS_Model_Relationship', $_REQUEST['ms_relationship_id']); $gateway_id = $_REQUEST['gateway']; $gateway = MS_Model_Gateway::factory($gateway_id); try { $invoice = $gateway->process_purchase($subscription); // If invoice is successfully paid, redirect to welcome page. if ($invoice->is_paid() || $invoice->uses_trial && MS_Model_Invoice::STATUS_BILLED == $invoice->status) { // Make sure to respect the single-membership rule $this->validate_membership_states($subscription); // Redirect user to the Payment-Completed page. if (!defined('IS_UNIT_TEST')) { MS_Model_Pages::redirect_to(MS_Model_Pages::MS_PAGE_REG_COMPLETE, array('ms_relationship_id' => $subscription->id)); } } elseif (MS_Gateway_Manual::ID == $gateway_id) { // For manual gateway payments. $this->add_action('the_content', 'purchase_info_content'); } else { // Something went wrong, the payment was not successful. $this->add_action('the_content', 'purchase_error_content'); } } catch (Exception $e) { MS_Helper_Debug::log($e->getMessage()); switch ($gateway_id) { case MS_Gateway_Authorize::ID: $_POST['auth_error'] = $e->getMessage(); // call action to step back do_action('ms_controller_frontend_signup_gateway_form'); break; case MS_Gateway_Stripe::ID: $_POST['error'] = sprintf(__('Error: %s', MS_TEXT_DOMAIN), $e->getMessage()); // Hack to send the error message back to the payment_table. MS_Plugin::instance()->controller->controllers['frontend']->add_action('the_content', 'payment_table', 1); break; default: do_action('ms_controller_gateway_form_error', $e); $this->add_action('the_content', 'purchase_error_content'); break; } } } else { MS_Helper_Debug::log('Error Code ' . $err); $this->add_action('the_content', 'purchase_error_content'); } // Hack to show signup page in case of errors $ms_page = MS_Model_Pages::get_page(MS_Model_Pages::MS_PAGE_REGISTER); if ($ms_page) { // During unit-testing the $ms_page object might be empty. global $wp_query; $wp_query->query_vars['page_id'] = $ms_page->ID; $wp_query->query_vars['post_type'] = 'page'; } do_action('ms_controller_gateway_process_purchase_after', $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; $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; } }
/** * Creates Authorize.net CIM profile for current user. * * @since 1.0.0 * @param MS_Model_Member $member The member to create CIM profile to. */ protected function create_cim_profile($member) { do_action('ms_gateway_authorize_create_cim_profile_before', $member, $this); $this->load_authorize_lib(); $customer = new M2_AuthorizeNetCustomer(); $customer->merchantCustomerId = $member->id; $customer->email = $member->email; $customer->paymentProfiles[] = $this->create_cim_payment_profile(); $response = $this->get_cim()->createCustomerProfile($customer); if ($response->isError()) { MS_Helper_Debug::log($response); // Duplicate record, delete the old one. if ('E00039' == $response->xml->messages->message->code) { $cim_profile_id = str_replace('A duplicate record with ID ', '', $response->xml->messages->message->text); $cim_profile_id = (int) str_replace(' already exists.', '', $cim_profile_id); $this->get_cim()->deleteCustomerProfile($cim_profile_id); // Try again $this->create_cim_profile($member); return; } else { throw new Exception(__('Payment failed due to CIM profile not created: ', 'membership2') . $response->getMessageText()); } } $cim_profile_id = $response->getCustomerProfileId(); $cim_payment_profile_id = $response->getCustomerPaymentProfileIds(); $this->save_cim_profile($member, $cim_profile_id, $cim_payment_profile_id); }
/** * Verify required fields. * * To be overridden in children classes. * * @since 1.0.0 * * @return boolean */ public function is_configured() { MS_Helper_Debug::log(sprintf(__('Override the is_configured method of the %s-gateway', 'membership2'), $this->id)); return false; }
/** * Processes gateway IPN return. * * @since 1.0.0 */ public function handle_return() { $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); lib2()->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) && 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': if ($amount == $invoice->total) { $success = true; $notes .= __('Payment successful', MS_TEXT_DOMAIN); } else { $notes = __('Payment amount differs from invoice total.', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; } break; case 'Reversed': $notes = __('Last transaction has been reversed. Reason: Payment has been reversed (charge back). ', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Refunded': $notes = __('Last transaction has been reversed. Reason: Payment has been refunded', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Denied': $notes = __('Last transaction has been reversed. Reason: Payment Denied', MS_TEXT_DOMAIN); $status = MS_Model_Invoice::STATUS_DENIED; break; case 'Pending': $pending_str = array('address' => __('Customer did not include a confirmed shipping address', MS_TEXT_DOMAIN), 'authorization' => __('Funds not captured yet', MS_TEXT_DOMAIN), 'echeck' => __('eCheck that has not cleared yet', MS_TEXT_DOMAIN), 'intl' => __('Payment waiting for aproval by service provider', MS_TEXT_DOMAIN), 'multi-currency' => __('Payment waiting for service provider to handle multi-currency process', MS_TEXT_DOMAIN), 'unilateral' => __('Customer did not register or confirm his/her email yet', MS_TEXT_DOMAIN), 'upgrade' => __('Waiting for service provider to upgrade the PayPal account', MS_TEXT_DOMAIN), 'verify' => __('Waiting for service provider to verify his/her PayPal account', MS_TEXT_DOMAIN), '*' => ''); $reason = $_POST['pending_reason']; $notes = __('Last transaction is pending. Reason: ', MS_TEXT_DOMAIN) . (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', MS_TEXT_DOMAIN); } 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; } $notes = 'Response Error: ' . $reason; MS_Helper_Debug::log($notes); MS_Helper_Debug::log($response); MS_Helper_Debug::log($_POST); $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.'; MS_Helper_Debug::log($notes); $redirect = home_url(); } else { status_header(404); $notes = 'Error: Missing POST variables. Identification is not possible.'; MS_Helper_Debug::log($notes); } $exit = true; } do_action('ms_gateway_transaction_log', self::ID, 'handle', $success, $subscription_id, $invoice_id, $amount, $notes); if ($redirect) { wp_safe_redirect($redirect); exit; } if ($exit) { exit; } do_action('ms_gateway_paypalsingle_handle_return_after', $this); }
/** * Determines the users country based on his IP address. * * @since 1.0.0 * @internal * * @param string $mode [declared|vat|card|auto] Either the country from user * settings or the auto-detected country. */ protected static function fetch_country($mode = 'declared') { $member = MS_Model_Member::get_current_member(); $country = false; $store_it = false; $non_auto_countries = array('declared', 'vat', 'card'); if (!in_array($mode, $non_auto_countries)) { $mode = 'auto'; } $auto_detect = 'auto' == $mode; $key = 'tax_' . $mode . '_country'; // If no country is stored use the API to determine it. if ($auto_detect) { try { $ip_info = lib3()->net->current_ip(); $data = (object) (array) self::taxamo()->locateGivenIP($ip_info->ip); $country = (object) array('code' => $data->country_code); // Store result in Session, not in DB. $store_it = true; $member = null; } catch (Exception $ex) { MS_Helper_Debug::log('Taxamo error: ' . $ex->getMessage()); } } else { // Try to get the stored country from user-meta or session (for guest) $country = self::get_tax_profile_value($member, $key); } // API did not return a valid resonse, use a dummy value. if (!$country) { $country = (object) array('code' => ''); } // Store result in user-deta or session. if ($store_it && self::set_tax_profile_value($member, $key, $country)) { $member->save(); } $country_names = self::get_country_codes('name'); if ($country->code && isset($country_names[$country->code])) { $country->name = $country_names[$country->code]; } else { $country->name = $country_names['XX']; } return $country; }
/** * Check if a user is subscribed in the list * * @param string $user_email * @param string $list_id * @return bool True if the user is subscribed already to the list */ public static function is_user_subscribed($user_email, $list_id) { $subscribed = false; if (is_email($user_email) && self::get_api_status()) { $emails = array(array('email' => $user_email)); $results = self::$mailchimp_api->lists->memberInfo($list_id, $emails); if (is_wp_error($results)) { MS_Helper_Debug::log($results); } elseif (!empty($results['success_count']) && !empty($results['data'][0]['status']) && 'subscribed' == $results['data'][0]['status']) { $subscribed = true; } } return $subscribed; }
/** * Get specific MS Page. * * @since 1.0.0 * * @param string $page_type The page type to retrieve the page. * @return WP_Post The page model object. */ public static function get_page($page_type) { $result = null; if (self::is_valid_type($page_type)) { // Get a list of all WP_Post items. $pages = self::get_pages(); if (!empty($pages[$page_type])) { $result = $pages[$page_type]; } } else { MS_Helper_Debug::log('ms_model_pages_get_page error: invalid page type: ' . $page_type); } return apply_filters('ms_model_pages_get_page', $result); }
/** * Create and Save event. * * Default search arguments for this custom post_type. * * @since 1.0.0 * * @param string $type The event type. * @return mixed $data The additional data to create an event. */ public static function save_event($type, $data) { $event = null; if (self::is_valid_type($type)) { $event = MS_Factory::create('MS_Model_Event'); $event->type = $type; $event->topic = self::get_topic($type); $description = ''; switch ($event->topic) { case self::TOPIC_PAYMENT: case self::TOPIC_WARNING: case self::TOPIC_MEMBERSHIP: $subscription = $data; if ($subscription->id > 0) { $membership = $subscription->get_membership(); $member = MS_Factory::load('MS_Model_Member', $subscription->user_id); $event->user_id = $subscription->user_id; $event->membership_id = $subscription->membership_id; $event->ms_relationship_id = $subscription->id; $event->name = sprintf('user: %s, membership: %s, type: %s', $member->name, $membership->name, $type); if (self::TOPIC_PAYMENT == $event->topic) { $invoice = $subscription->get_current_invoice(false); $description = sprintf(self::get_description($type), $membership->name, $invoice ? $invoice->id : '-'); } else { $description = sprintf(self::get_description($type), $membership->name); } } else { // The subscription has no ID. // Possibly it was not saved yet... } break; case self::TOPIC_USER: if ($data instanceof MS_Model_Member) { $member = $data; $event->user_id = $member->id; $event->name = sprintf('user: %s, type: %s', $member->name, $type); } elseif ($data instanceof MS_Model_Relationship) { $subscription = $data; $membership = $subscription->get_membership(); $member = MS_Factory::load('MS_Model_Member', $subscription->user_id); $event->user_id = $subscription->user_id; $event->membership_id = $subscription->membership_id; $event->ms_relationship_id = $subscription->id; $event->name = sprintf('user: %s, membership: %s, type: %s', $member->name, $membership->name, $type); } $description = self::get_description($type); break; default: MS_Helper_Debug::log("Event topic not implemented: '{$event->topic}'"); break; } $event->description = apply_filters('ms_model_event_description', $description, $type, $data); $event->date = MS_Helper_Period::current_date(); $event = apply_filters('ms_model_news_record_user_signup_object', $event); if (!self::is_duplicate($event, $data)) { $event->save(); // Hook to these actions to handle event notifications. // e.g. auto communication. do_action('ms_model_event', $event, $data); do_action('ms_model_event_' . $type, $event, $data); } else { $event = null; } } return apply_filters('ms_model_event_save_event', $event, $type, $data); }