/** * Fires only at user creation from admin * Create even to send notification email. * * @since 1.0.2.6 * * @var int $user_id */ public function save_user_create_event($user_id) { if (is_admin()) { $member = MS_Factory::load('MS_Model_Member', $user_id); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_REGISTERED, $member); } }
/** * Check for card expiration date. * * Save event for card expire soon. * * @since 1.0.0 * * @access protected * @param MS_Model_Relationship $subscription The membership relationship. */ public function check_card_expiration($subscription) { do_action('ms_gateway_check_card_expiration_before', $this); $member = MS_Factory::load('MS_Model_Member', $subscription->user_id); $card_exp = $member->get_gateway_profile($this->id, 'card_exp'); if (!empty($card_exp)) { $comm = MS_Model_Communication::get_communication(MS_Model_Communication::COMM_TYPE_CREDIT_CARD_EXPIRE); $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); $card_expire_days = MS_Helper_Period::subtract_dates($card_exp, MS_Helper_Period::current_date(), DAY_IN_SECONDS, true); if ($card_expire_days < 0 || $days == $card_expire_days) { MS_Model_Event::save_event(MS_Model_Event::TYPE_CREDIT_CARD_EXPIRE, $subscription); } } do_action('ms_gateway_check_card_expiration_after', $this, $subscription); }
/** * Handles register user submit. * * On validation errors, step back to register form. * * @since 1.0.0 */ public function register_user() { do_action('ms_controller_frontend_register_user_before', $this); if (!$this->verify_nonce()) { return; } try { $user = MS_Factory::create('MS_Model_Member'); // Default WP registration filter $fields = apply_filters('signup_user_init', $_REQUEST); foreach ($fields as $field => $value) { $user->{$field} = $value; } $user->save(); // Default WP action hook do_action('signup_finished'); $user->signon_user(); if (!MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_REGISTERED, $user)) { wp_new_user_notification($user->id, $user->password); } do_action('ms_controller_frontend_register_user_complete', $user); // Go to membership signup payment form. if (empty($_REQUEST['membership_id'])) { $redirect = esc_url_raw(add_query_arg(array('step' => self::STEP_CHOOSE_MEMBERSHIP))); } else { $redirect = esc_url_raw(add_query_arg(array('step' => self::STEP_PAYMENT_TABLE, 'membership_id' => absint($_REQUEST['membership_id'])))); } wp_safe_redirect($redirect); exit; } catch (Exception $e) { $this->register_errors = $e->getMessage(); // step back $this->add_action('the_content', 'register_form', 1); do_action('ms_controller_frontend_register_user_error', $this->register_errors); } }
/** * Move a membership. * * @since 1.0.0 * @api * * @param int $old_membership_id The membership id to move from. * @param int $mew_membership_id The membership id to move to. */ public function move_membership($old_membership_id, $mew_membership_id) { $old_subscription = $this->get_subscription($old_membership_id); if ($old_subscription) { $new_subscription = MS_Model_Relationship::create_ms_relationship($mew_membership_id, $this->id, $old_subscription->gateway_id, $old_membership_id); $this->cancel_membership($old_membership_id); $this->subscriptions[] = $new_subscription; MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_MOVED, $new_subscription); } do_action('ms_model_membership_move_membership', $old_membership_id, $mew_membership_id, $this); }
/** * Check membership status. * * Execute actions when time/period condition are met. * E.g. change membership status, add communication to queue, create invoices. * * This check is called via a cron job. * * @since 1.0.0 * @internal Used by Cron * @see MS_Model_Plugin::check_membership_status() */ public function check_membership_status() { do_action('ms_model_relationship_check_membership_status_before', $this); /** * Use `define( 'MS_LOCK_SUBSCRIPTIONS', true );` in wp-config.php to prevent * Membership2 from sending *any* emails to users. * Also any currently enqueued message is removed from the queue * * @since 1.0.0 */ if (MS_Plugin::get_modifier('MS_LOCK_SUBSCRIPTIONS')) { return false; } $membership = $this->get_membership(); $remaining_days = $this->get_remaining_period(); $remaining_trial_days = $this->get_remaining_trial_period(); $comms = MS_Model_Communication::get_communications($membership); $invoice_before_days = 5; //@todo create a setting to configure this period. $deactivate_expired_after_days = 30; //@todo create a setting to configure this period. $deactivate_pending_after_days = 30; //@todo create a setting to configure this period. $deactivate_trial_expired_after_days = 5; //@todo create a setting to configure this period. //@todo: Add a flag to subscriptions with sent communications. Then improve the conditions below to prevent multiple emails. do_action('ms_check_membership_status-' . $this->status, $this, $remaining_days, $remaining_trial_days); // Update the Subscription status. $next_status = $this->calculate_status(null); switch ($next_status) { case self::STATUS_TRIAL: if (MS_Model_Addon::is_enabled(MS_Model_Addon::ADDON_TRIAL) && MS_Model_Addon::is_enabled(MS_Model_Addon::ADDON_AUTO_MSGS_PLUS)) { // Send trial end communication. $comm = $comms[MS_Model_Communication::COMM_TYPE_BEFORE_TRIAL_FINISHES]; if ($comm->enabled) { $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); //@todo: This will send out the reminder multiple times on the reminder-day (4 times or more often) if ($days == $remaining_trial_days) { $comm->add_to_queue($this->id); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_BEFORE_TRIAL_FINISHES, $this); } } } // Check for card expiration $gateway = $this->get_gateway(); $gateway->check_card_expiration($this); break; case self::STATUS_TRIAL_EXPIRED: if (MS_Model_Addon::is_enabled(MS_Model_Addon::ADDON_TRIAL)) { // Mark the trial period as completed. $this->save() is below. $this->trial_period_completed = true; // Request payment to the gateway (for gateways that allows it). $gateway = $this->get_gateway(); /* * The subscription will be either automatically activated * or set to pending. * * Important: Set trial_period_completed=true before calling * request_payment()! */ if ($gateway->request_payment($this)) { $next_status = self::STATUS_ACTIVE; $this->status = $next_status; $this->config_period(); // Needed because of status change. } // Check for card expiration $gateway->check_card_expiration($this); // Deactivate expired memberships after a period of time. if ($deactivate_trial_expired_after_days < -$remaining_trial_days) { $this->deactivate_membership(); } } break; case self::STATUS_ACTIVE: case self::STATUS_EXPIRED: case self::STATUS_CANCELED: /* * Make sure the expire date has a correct value, in case the user * changed the payment_type of the parent membership after this * subscription was created. */ if ($this->payment_type != $membership->payment_type) { $this->payment_type = $membership->payment_type; switch ($this->payment_type) { case MS_Model_Membership::PAYMENT_TYPE_PERMANENT: $this->expire_date = false; break; default: // Either keep the current expire date (if valid) or // calculate a new expire date, based on current date. if (!$this->expire_date) { $this->expire_date = $this->calc_expire_date(MS_Helper_Period::current_date()); } break; } // Recalculate the days until the subscription expires. $remaining_days = $this->get_remaining_period(); // Recalculate the new Subscription status. $next_status = $this->calculate_status(); } /* * Only "Recurring" memberships will ever try to automatically * renew the subscription. All other types will expire when the * end date is reached. */ $auto_renew = $membership->payment_type == MS_Model_Membership::PAYMENT_TYPE_RECURRING; $deactivate = false; $invoice = null; if ($auto_renew && $membership->pay_cycle_repetitions > 0) { /* * The membership has a payment-repetition limit. * When this limit is reached then we do not auto-renew the * subscription but expire it. */ $payments = $this->get_payments(); if (count($payments) >= $membership->pay_cycle_repetitions) { $auto_renew = false; } } if ($auto_renew) { if ($remaining_days < $invoice_before_days) { // Create a new invoice. $invoice = $this->get_next_invoice(); } else { $invoice = $this->get_current_invoice(); } } else { $invoice = $this->get_current_invoice(); } // Advanced communications Add-on. if (MS_Model_Addon::is_enabled(MS_Model_Addon::ADDON_AUTO_MSGS_PLUS)) { // Before finishes communication. $comm = $comms[MS_Model_Communication::COMM_TYPE_BEFORE_FINISHES]; $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); if ($days == $remaining_days) { $comm->add_to_queue($this->id); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_BEFORE_FINISHES, $this); } // After finishes communication. $comm = $comms[MS_Model_Communication::COMM_TYPE_AFTER_FINISHES]; $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); if ($remaining_days < 0 && $days == abs($remaining_days)) { $comm->add_to_queue($this->id); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_AFTER_FINISHES, $this); } // Before payment due. $comm = $comms[MS_Model_Communication::COMM_TYPE_BEFORE_PAYMENT_DUE]; $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); $invoice_days = MS_Helper_Period::subtract_dates($invoice->due_date, MS_Helper_Period::current_date()); if (MS_Model_Invoice::STATUS_BILLED == $invoice->status && $days == $invoice_days) { $comm->add_to_queue($this->id); MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_BEFORE_DUE, $this); } // After payment due event $comm = $comms[MS_Model_Communication::COMM_TYPE_AFTER_PAYMENT_DUE]; $days = MS_Helper_Period::get_period_in_days($comm->period['period_unit'], $comm->period['period_type']); $invoice_days = MS_Helper_Period::subtract_dates($invoice->due_date, MS_Helper_Period::current_date()); if (MS_Model_Invoice::STATUS_BILLED == $invoice->status && $days == $invoice_days) { $comm->add_to_queue($this->id); MS_Model_Event::save_event(MS_Model_Event::TYPE_PAYMENT_AFTER_DUE, $this); } } // -- End of advanced communications Add-on // Subscription ended. See if we can renew it. if ($remaining_days <= 0) { if ($auto_renew) { /* * The membership can be renewed. Try to renew it * automatically by requesting the next payment from the * payment gateway (only works if gateway supports this) */ $gateway = $this->get_gateway(); $gateway->check_card_expiration($this); $gateway->request_payment($this); // Check if the payment was successful. $remaining_days = $this->get_remaining_period(); } /* * User did not renew the membership. Give him some time to * react before restricting his access. */ if ($deactivate_expired_after_days < -$remaining_days) { $deactivate = true; } } $next_status = $this->calculate_status(null); /* * When the subscription expires the first time then create a * new event that triggers the "Expired" email. */ if (self::STATUS_EXPIRED == $next_status && $next_status != $this->status) { MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_EXPIRED, $this); } elseif ($deactivate) { $this->deactivate_membership(); $next_status = $this->status; // Move membership to configured membership. $membership = $this->get_membership(); $new_membership = MS_Factory::load('MS_Model_Membership', $membership->on_end_membership_id); if ($new_membership->is_valid()) { $member = MS_Factory::load('MS_Model_Member', $this->user_id); $new_subscription = $member->add_membership($membership->on_end_membership_id, $this->gateway_id); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_MOVED, $new_subscription); /* * If the new membership is paid we want that the user * confirms the payment in his account. So we set it * to "Pending" first. */ if (!$new_membership->is_free()) { $new_subscription->status = self::STATUS_PENDING; } } } break; case self::STATUS_DEACTIVATED: /* * A subscription was finally deactivated. * Lets check if the member has any other active subscriptions, * or (if not) his account should be deactivated. * * First get a list of all subscriptions that do not have status * Pending / Deactivated. */ $subscriptions = self::get_subscriptions(array('user_id' => $this->user_id)); // Check if there is a subscription that keeps the user active. $deactivate = true; foreach ($subscriptions as $item) { if ($item->id == $this->id) { continue; } $deactivate = false; } if ($deactivate) { $member = $this->get_member(); $member->is_member = false; $member->save(); } break; case self::STATUS_PENDING: default: // Do nothing. break; } // Save the new status. $this->status = $next_status; $this->save(); // Save the changed email queue. foreach ($comms as $comm) { $comm->save(); } do_action('ms_model_relationship_check_membership_status_after', $this); }
/** * 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); }
/** * 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; }
/** * Handle update credit card information in gateway. * * Used to change credit card info in account's page. * * Related action hooks: * - template_redirect * * @since 1.0.0 */ public function update_card() { if (!empty($_POST['gateway'])) { $gateway = MS_Model_Gateway::factory($_POST['gateway']); $member = MS_Model_Member::get_current_member(); switch ($gateway->id) { case MS_Gateway_Stripe::ID: if (!empty($_POST['stripeToken']) && $this->verify_nonce()) { lib2()->array->strip_slashes($_POST, 'stripeToken'); $gateway->add_card($member, $_POST['stripeToken']); if (!empty($_POST['ms_relationship_id'])) { $ms_relationship = MS_Factory::load('MS_Model_Relationship', $_POST['ms_relationship_id']); MS_Model_Event::save_event(MS_Model_Event::TYPE_UPDATED_INFO, $ms_relationship); } wp_safe_redirect(esc_url_raw(add_query_arg(array('msg' => 1)))); exit; } break; case MS_Gateway_Authorize::ID: if ($this->verify_nonce()) { do_action('ms_controller_frontend_signup_gateway_form', $this); } elseif (!empty($_POST['ms_relationship_id']) && $this->verify_nonce($_POST['gateway'] . '_' . $_POST['ms_relationship_id'])) { $gateway->update_cim_profile($member); $gateway->save_card_info($member); if (!empty($_POST['ms_relationship_id'])) { $ms_relationship = MS_Factory::load('MS_Model_Relationship', $_POST['ms_relationship_id']); MS_Model_Event::save_event(MS_Model_Event::TYPE_UPDATED_INFO, $ms_relationship); } wp_safe_redirect(esc_url_raw(add_query_arg(array('msg' => 1)))); exit; } break; default: break; } } do_action('ms_controller_gateway_update_card', $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; }
/** * Ajax handler. Used by shortcode `ms-membership-login` to recover password * * @since 1.0.0 * @internal */ public function ajax_lostpass() { $resp = array(); // First check the nonce, if it fails the function will break check_ajax_referer('ms-ajax-lostpass'); // Nonce is checked, get the POST data and sign user on $errors = new WP_Error(); if (empty($_POST['user_login'])) { $resp['error'] = __('Enter a username or e-mail address.', 'membership2'); } else { if (strpos($_POST['user_login'], '@')) { $user_data = get_user_by('email', trim($_POST['user_login'])); if (empty($user_data)) { $resp['error'] = __('There is no user registered with that email address.', 'membership2'); } } else { $login = trim($_POST['user_login']); $user_data = get_user_by('login', $login); } } do_action('lostpassword_post'); if (!empty($resp['error'])) { $this->respond($resp); } if (!$user_data) { $resp['error'] = __('Invalid username or e-mail.', 'membership2'); $this->respond($resp); } // Redefining user_login ensures we return the right case in the email. $user_login = $user_data->user_login; $user_email = $user_data->user_email; do_action('retreive_password', $user_login); // Legacy (misspelled) do_action('retrieve_password', $user_login); $allow = apply_filters('allow_password_reset', true, $user_data->ID); if (!$allow) { $resp['error'] = __('Password reset is not allowed for this user', 'membership2'); $this->respond($resp); } elseif (is_wp_error($allow)) { return $allow; } // Save an event about the password reset; also send the email template. $member = MS_Factory::load('MS_Model_Member', $user_data->ID); MS_Model_Event::save_event(MS_Model_Event::TYPE_MS_RESETPASSWORD, $member); // Send our default email if the user does not have a custom email template in place. if (!apply_filters('ms_sent_reset_password_email', false)) { // Get a new reset-key. $reset = $member->new_password_reset_key(); $schema = is_ssl() ? 'https' : 'http'; $message = sprintf(__('Someone requested that the password be reset for the following account: %sIf this was a mistake, just ignore this email and nothing will happen.%s', 'membership2'), "\r\n\r\n" . network_home_url('/', $schema) . "\r\n" . sprintf(__('Your username: %s', 'membership2'), $user_login) . "\r\n\r\n", "\r\n\r\n" . $reset->url . "\r\n"); if (is_multisite()) { $blogname = $GLOBALS['current_site']->site_name; } else { $blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES); } $title = sprintf(__('[%s] Password Reset'), $blogname); $title = apply_filters('retrieve_password_title', $title); $message = apply_filters('retrieve_password_message', $message, $reset->key, $reset->url); if ($message && !wp_mail($user_email, wp_specialchars_decode($title), $message)) { $resp['error'] = __('The e-mail could not be sent.') . '<br />' . __('Possible reason: your host may have disabled the mail() function.'); } else { $resp['success'] = __('Check your e-mail for the confirmation link.', 'membership2'); } } else { $resp['success'] = __('Check your e-mail for the confirmation link.', 'membership2'); } $this->respond($resp); }