/**
  * 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);
 }