コード例 #1
0
 /**
  * Create a new recurring subscription.
  *
  * Creates a new recurring subscription and processes a charge for today.
  *
  * @param int $client_id	The Client ID
  * @param int $gateway_id The gateway ID to process this charge with
  * @param float $amount The amount to charge (e.g., "50.00")
  * @param array $credit_card The credit card information
  * @param int $credit_card['card_num'] The credit card number
  * @param int $credit_card['exp_month'] The credit card expiration month in 2 digit format (01 - 12)
  * @param int $credit_card['exp_year'] The credit card expiration year (YYYY)
  * @param string $credit_card['name'] The credit card cardholder name.  Required only is customer ID is not supplied.
  * @param int $credit_card['cvv'] The Card Verification Value.  Optional
  * @param int $customer_id The ID of the customer to link the charge to
  * @param array $customer An array of customer data to create a new customer with, if no customer_id
  * @param float $customer_ip The optional IP address of the customer
  * @param array $recur The details for a recurring charge
  * @param int $recur['plan_id'] The ID of the plan to pull recurring details from (Optional)
  * @param string $recur['start_date'] The start date of the subscription
  * @param string $recur['end_date'] The end date of the subscription
  * @param int $recur['free_trial'] The number of days to give a free trial before.  Will combine with start_date if that is also set. (Optional)
  * @param float $recur['amount'] The amount to charge every INTERVAL days.  If not there, the main $amount will be used.
  * @param int $recur['occurrences'] The total number of occurrences (Optional, if end_date doesn't exist).
  * @param string $recur['notification_url'] The URL to send POST updates to for notices re: this subscription.
  * @param string $return_url The URL for external payment processors to return the user to after payment
  * @param string $cancel_url The URL to send if the user cancels an external payment
  * @param int $renew The subscription that is being renewed, if there is one
  * @param string $coupon A coupon code
  *
  * @return mixed Array with response_code and response_text
  */
 function Recur($client_id, $gateway_id, $amount = FALSE, $credit_card = array(), $customer_id = FALSE, $customer = array(), $customer_ip = FALSE, $recur = array(), $return_url = FALSE, $cancel_url = FALSE, $renew = FALSE, $coupon = FALSE)
 {
     $CI =& get_instance();
     $CI->load->library('field_validation');
     // Clean up our url's
     if ($return_url) {
         $return_url = htmlspecialchars_decode($return_url);
     }
     if ($cancel_url) {
         $cancel_url = htmlspecialchars_decode($cancel_url);
     }
     // Get the gateway info to load the proper library
     $gateway = $this->GetGatewayDetails($client_id, $gateway_id);
     if (!$gateway or $gateway['enabled'] == '0') {
         die($this->response->Error(5017));
     }
     // load the gateway
     $gateway_name = $gateway['name'];
     $CI->load->library('payment/' . $gateway_name);
     $gateway_settings = $this->{$gateway_name}->Settings();
     $this->load->library('field_validation');
     // credit card validation has been moved after the 2nd coupon check, in case this coupon makes it free
     // are we linking this to another sub via renewal?
     if (!empty($renew)) {
         $CI->load->model('recurring_model');
         $renewed_subscription = $CI->recurring_model->GetSubscriptionDetails($client_id, $renew);
         if (!empty($renewed_subscription)) {
             $mark_as_renewed = $renewed_subscription['subscription_id'];
             /**
              * automatically set start date
              * we don't do this because Membrr does it automatically, and want to give
              * the developer more control.
              * if (strtotime($renewed_subscription['next_charge_date']) > time()) {
              * 	$recur['start_date'] = $renewed_subscription['next_charge_date'];
              * }
              * else {
              * 	$recur['start_date'] = date('Y-m-d');
              * }
              *
              * $recur['free_trial'] = 0;
              */
         } else {
             $mark_as_renewed = FALSE;
         }
     } else {
         $mark_as_renewed = FALSE;
     }
     // Get the customer details if a customer id was included
     $this->load->model('customer_model');
     if (!empty($customer_id)) {
         $customer = $CI->customer_model->GetCustomer($client_id, $customer_id);
         $customer['customer_id'] = $customer['id'];
         $created_customer = FALSE;
     } elseif (isset($customer) and !empty($customer)) {
         // look for embedded customer information
         // by Getting the customer after it's creation, we get a nice clean ISO2 code for the country
         $customer_id = $CI->customer_model->NewCustomer($client_id, $customer);
         $customer = $CI->customer_model->GetCustomer($client_id, $customer_id);
         $customer['customer_id'] = $customer_id;
         unset($customer_id);
         $created_customer = TRUE;
     } else {
         // no customer_id or customer information - is this a problem?
         // we'll check if this gateway required customer information
         if ($gateway_settings['requires_customer_information'] == 1) {
             die($this->response->Error(5018));
         }
         // no customer information was passed but this gateway is OK with that, let's just get the customer first/last name
         // from the credit card name for our records
         if (!isset($credit_card['name'])) {
             die($this->response->Error(5004));
         } else {
             $name = explode(' ', $credit_card['name']);
             $customer['first_name'] = $name[0];
             $customer['last_name'] = $name[count($name) - 1];
             $customer['customer_id'] = $CI->customer_model->SaveNewCustomer($client_id, $customer['first_name'], $customer['last_name']);
             $created_customer = TRUE;
         }
     }
     // if we have an IP, we'll populate this field
     // note, if we get an error later: the first thing we check is to see if an IP is required
     // by checking this *after* an error, we give the gateway a chance to be flexible and, if not,
     // we give the end-user the most likely error response
     if (!empty($customer_ip)) {
         // place it in $customer array
         $customer['ip_address'] = $customer_ip;
     } else {
         $customer['ip_address'] = '';
     }
     if (isset($recur['plan_id'])) {
         // we have a linked plan, let's load that information
         $CI->load->model('plan_model');
         $plan_details = $CI->plan_model->GetPlanDetails($client_id, $recur['plan_id']);
         $interval = isset($recur['interval']) ? $recur['interval'] : $plan_details->interval;
         $notification_url = isset($recur['notification_url']) ? $recur['notification_url'] : $plan_details->notification_url;
         $free_trial = isset($recur['free_trial']) ? $recur['free_trial'] : $plan_details->free_trial;
         $occurrences = isset($recur['occurrences']) ? $recur['occurrences'] : $plan_details->occurrences;
         // calculate first charge amount:
         //	  1) First charge is main $amount if given
         //	  2) If no $recur['amount'], use plan amount
         //	  3) Else use $recur['amount']
         if (isset($amount)) {
             $amount = $amount;
         } elseif (isset($recur['amount'])) {
             $amount = $recur['amount'];
         } elseif (isset($plan_details->amount)) {
             $amount = $plan_details->amount;
         }
         $amount = $this->field_validation->ValidateAmount($amount);
         if ($amount === FALSE) {
             die($this->response->Error(5009));
         }
         // store plan ID
         $plan_id = $plan_details->plan_id;
     } else {
         if (!isset($recur['interval']) or !is_numeric($recur['interval'])) {
             die($this->response->Error(5011));
         } else {
             $interval = $recur['interval'];
         }
         // Check for a notification URL
         $notification_url = isset($recur['notification_url']) ? $recur['notification_url'] : '';
         // Validate the amount
         if ($this->field_validation->ValidateAmount($amount) === FALSE) {
             die($this->response->Error(5009));
         }
         $plan_id = 0;
         $free_trial = (isset($recur['free_trial']) and is_numeric($recur['free_trial'])) ? $recur['free_trial'] : FALSE;
     }
     // Validate the start date to make sure it is in the future
     if (isset($recur['start_date'])) {
         // adjust to server time
         $recur['start_date'] = server_time($recur['start_date'], 'Y-m-d', true);
         if (!$this->field_validation->ValidateDate($recur['start_date']) or $recur['start_date'] < date('Y-m-d')) {
             die($this->response->Error(5001));
         } else {
             $start_date = date('Y-m-d', strtotime($recur['start_date']));
         }
     } else {
         $start_date = date('Y-m-d');
     }
     // coupon check
     if (!empty($coupon)) {
         $CI->load->model('coupon_model');
         $coupon = $CI->coupon_model->get_coupons($client_id, array('coupon_code' => $coupon));
         if (!empty($coupon) and $CI->coupon_model->is_eligible($coupon, isset($recur['plan_id']) ? $recur['plan_id'] : FALSE, isset($customer_id) ? $customer_id : FALSE)) {
             $coupon = $coupon[0];
             $free_trial = $CI->coupon_model->subscription_adjust_trial($free_trial, $coupon['type_id'], $coupon['trial_length']);
             $coupon_id = $coupon['id'];
         } else {
             $coupon_id = FALSE;
         }
     } else {
         $coupon_id = FALSE;
     }
     // do we have to adjust the start_date for a free trial?
     if ($free_trial) {
         $start_date = date('Y-m-d', strtotime($start_date) + $free_trial * 86400);
     }
     // get the next payment date
     if (date('Y-m-d', strtotime($start_date)) == date('Y-m-d')) {
         // deal with the same_day_every_month mod for some gateways
         if (isset($this->{$gateway_name}->same_day_every_month) and $this->{$gateway_name}->same_day_every_month == TRUE and $interval % 30 === 0) {
             $months = $interval / 30;
             $plural = $months > 1 ? 's' : '';
             $next_charge_date = date('Y-m-d', strtotime('today + ' . $months . ' month' . $plural));
         } else {
             $next_charge_date = date('Y-m-d', strtotime($start_date) + $interval * 86400);
         }
     } else {
         $next_charge_date = date('Y-m-d', strtotime($start_date));
     }
     // if an end date was passed, make sure it's valid
     if (isset($recur['end_date'])) {
         // adjust to server time
         $recur['end_date'] = !isset($end_date_set_by_server) ? server_time($recur['end_date']) : $recur['end_date'];
         if (strtotime($recur['end_date']) < time()) {
             // end_date is in the past
             die($this->response->Error(5002));
         } elseif (strtotime($recur['end_date']) < strtotime($start_date)) {
             // end_date is before start_date
             die($this->response->Error(5003));
         } else {
             // date is good
             $end_date = date('Y-m-d', strtotime($recur['end_date']));
         }
     } elseif (isset($occurrences) and !empty($occurrences)) {
         // calculate end_date from # of occurrences as defined by plan
         $end_date = date('Y-m-d', strtotime($start_date) + $interval * 86400 * $occurrences);
     } elseif (isset($recur['occurrences']) and !empty($recur['occurrences'])) {
         // calculate end_date from # of occurrences from recur node
         $end_date = date('Y-m-d', strtotime($start_date) + $interval * 86400 * $recur['occurrences']);
     } else {
         // calculate the end_date based on the max end date setting
         $end_date = date('Y-m-d', strtotime($start_date) + $this->config->item('max_recurring_days_from_today') * 86400);
     }
     if (!empty($credit_card) and isset($credit_card['exp_year']) and !empty($credit_card['exp_year'])) {
         // if the credit card expiration date is before the end date, we need to set the end date to one day before the expiration
         $check_year = $credit_card['exp_year'] > 2000 ? $credit_card['exp_year'] : '20' . $credit_card['exp_year'];
         $expiry = mktime(0, 0, 0, $credit_card['exp_month'], days_in_month($credit_card['exp_month'], $credit_card['exp_year']), $check_year);
         $date = strtotime($next_charge_date);
         while ($date < strtotime($end_date)) {
             if ($expiry < $date) {
                 $end_date = date('Y-m-d', $date);
                 break;
             }
             $date = $date + $interval * 86400;
         }
     }
     // adjust end date if it's less than next charge
     if (strtotime($end_date) <= strtotime($next_charge_date)) {
         // set end date to next charge
         $end_date = $next_charge_date;
         $total_occurrences = 1;
     }
     // figure the total number of occurrences
     $total_occurrences = round((strtotime($end_date) - strtotime($start_date)) / ($interval * 86400), 0);
     if ($total_occurrences < 1) {
         // the CC expiry date is only going to allow 1 charge
         $total_occurrences = 1;
     }
     // if they sent an $amount with their charge, this means that their first charge is different
     // so now we need to grab the true recurring amount
     if (isset($recur['amount'])) {
         $recur['amount'] = $recur['amount'];
     } elseif (isset($plan_details) and is_object($plan_details) and isset($plan_details->amount)) {
         $recur['amount'] = $plan_details->amount;
     } else {
         $recur['amount'] = $amount;
     }
     // 2nd coupon check - adjust amounts
     if (!empty($coupon_id)) {
         $CI->coupon_model->subscription_adjust_amount($amount, $recur['amount'], $coupon['type_id'], $coupon['reduction_type'], $coupon['reduction_amt']);
     }
     // validate function arguments
     if (empty($credit_card) and ((double) $recur['amount'] > 0 or (double) $amount > 0) and $gateway_settings['external'] == FALSE and $gateway_settings['no_credit_card'] == FALSE) {
         die($CI->response->Error(1004));
     }
     if (!empty($credit_card)) {
         // Validate the Credit Card number
         $credit_card['card_num'] = trim(str_replace(array(' ', '-'), '', $credit_card['card_num']));
         $credit_card['card_type'] = $this->field_validation->ValidateCreditCard($credit_card['card_num'], $gateway);
         if (!$credit_card['card_type']) {
             die($this->response->Error(5008));
         }
         if (!isset($credit_card['exp_month']) or empty($credit_card['exp_month'])) {
             die($this->response->Error(5008));
         }
         if (!isset($credit_card['exp_year']) or empty($credit_card['exp_year'])) {
             die($this->response->Error(5008));
         }
     }
     // Save the subscription info
     $CI->load->model('recurring_model');
     $card_last_four = isset($credit_card['card_num']) ? substr($credit_card['card_num'], -4, 4) : '0';
     $subscription_id = $CI->recurring_model->SaveRecurring($client_id, $gateway['gateway_id'], $customer['customer_id'], $interval, $start_date, $end_date, $next_charge_date, $total_occurrences, $notification_url, $recur['amount'], $plan_id, $card_last_four, $coupon_id);
     // get subscription
     $subscription = $CI->recurring_model->GetRecurring($client_id, $subscription_id);
     // is there a charge for today?
     $charge_today = ((double) $amount > 0 and date('Y-m-d', strtotime($subscription['date_created'])) == date('Y-m-d', strtotime($subscription['start_date']))) ? TRUE : FALSE;
     // set last_charge as today, if today was a charge
     if ($charge_today === TRUE) {
         $CI->recurring_model->SetChargeDates($subscription_id, date('Y-m-d', strtotime($subscription['date_created'])), $next_charge_date);
     }
     // if amount is greater than 0, we require a gateway to process
     if ($recur['amount'] > 0) {
         // recurring charges are not free
         $response = $CI->{$gateway_name}->Recur($client_id, $gateway, $customer, $amount, $charge_today, $start_date, $end_date, $interval, $credit_card, $subscription_id, $total_occurrences, $return_url, $cancel_url);
     } elseif ($recur['amount'] <= 0 and $amount > 0) {
         // recurring charges are free, but there is an initial charge
         // can't be an external gateway
         if ($gateway_settings['external'] == TRUE) {
             die($this->response->Error(5024));
         }
         // must have a start date of today
         if ($charge_today !== TRUE) {
             die($this->response->Error(5025));
         }
         $CI->load->model('charge_model');
         $customer['customer_id'] = isset($customer['customer_id']) ? $customer['customer_id'] : FALSE;
         $order_id = $CI->charge_model->CreateNewOrder($client_id, $gateway['gateway_id'], $amount, $credit_card, $subscription_id, $customer['customer_id'], $customer_ip);
         $response = $CI->{$gateway_name}->Charge($client_id, $order_id, $gateway, $customer, $amount, $credit_card, $return_url, $cancel_url);
         // translate response codes into proper recurring terms
         if ($response['response_code'] == 1) {
             // set order OK
             $CI->charge_model->SetStatus($order_id, 1);
             $response['response_code'] = 100;
             $response['recurring_id'] = $subscription_id;
         }
     } else {
         // this is a free subscription
         if (date('Y-m-d', strtotime($start_date)) == date('Y-m-d')) {
             // create a $0 order for today's payment
             $CI->load->model('charge_model');
             $customer['customer_id'] = isset($customer['customer_id']) ? $customer['customer_id'] : FALSE;
             $order_id = $CI->charge_model->CreateNewOrder($client_id, $gateway['gateway_id'], 0, $credit_card, $subscription_id, $customer['customer_id'], $customer_ip);
             $CI->charge_model->SetStatus($order_id, 1);
             $response_array = array('charge_id' => $order_id, 'recurring_id' => $subscription_id);
         } else {
             $response_array = array('recurring_id' => $subscription_id);
         }
         $response = $CI->response->TransactionResponse(100, $response_array);
     }
     if (isset($created_customer) and $created_customer == TRUE and $response['response_code'] != 100) {
         // charge was rejected, so let's delete the customer record we just created
         $CI->customer_model->DeleteCustomer($client_id, $customer['customer_id']);
     } elseif (isset($created_customer) and $created_customer == TRUE) {
         $response['customer_id'] = $customer['customer_id'];
     }
     if ($response['response_code'] != 100) {
         // clear it out completely
         $CI->recurring_model->DeleteRecurring($subscription_id);
     }
     if ($response['response_code'] == 100) {
         if (!empty($mark_as_renewed)) {
             $CI->recurring_model->SetRenew($mark_as_renewed, $subscription_id);
             // we used to only mark the old subscription as inactive, but now we will completely cancel it
             // this stops double recurring for PayPal-esque plugins that are initiated on their end
             $CI->recurring_model->CancelRecurring($client_id, $mark_as_renewed, TRUE);
         }
         if (!isset($response['not_completed']) or $response['not_completed'] == FALSE) {
             $CI->recurring_model->SetActive($client_id, $subscription_id);
             // delayed recurrings don't have a charge ID
             $response['charge_id'] = isset($response['charge_id']) ? $response['charge_id'] : FALSE;
             // trip it - were golden!
             TriggerTrip('new_recurring', $client_id, $response['charge_id'], $response['recurring_id']);
             // trip a recurring charge?
             if (!empty($response['charge_id'])) {
                 TriggerTrip('recurring_charge', $client_id, $response['charge_id'], $response['recurring_id']);
             }
             if (!empty($coupon_id)) {
                 // track coupon
                 $CI->coupon_model->add_usage($coupon_id, $subscription_id, $response['charge_id'], $customer_id);
             }
         } else {
             unset($response['not_completed']);
         }
     } else {
         // did we require an IP address?
         if ($gateway_settings['requires_customer_ip'] == 1 and !$customer_ip) {
             die($this->response->Error(5019));
         }
     }
     // pass back some values
     if ($charge_today === TRUE) {
         $response['amount'] = money_format("%!^i", $amount);
     } else {
         $response['amount'] = '0.00';
     }
     $response['recur_amount'] = money_format("%!^i", $recur['amount']);
     $response['free_trial'] = (int) $free_trial;
     $response['start_date'] = date('Y-m-d', strtotime($start_date));
     return $response;
 }
コード例 #2
0
ファイル: gateway_model.php プロジェクト: josev814/hero
 /**
  * Create a new recurring subscription.
  *
  * Creates a new recurring subscription and processes a charge for today.
  *
  * @param int $gateway_id The gateway ID to process this charge with
  * @param float $amount The amount to charge (e.g., "50.00")
  * @param array $credit_card The credit card information
  * @param int $credit_card['card_num'] The credit card number
  * @param int $credit_card['exp_month'] The credit card expiration month in 2 digit format (01 - 12)
  * @param int $credit_card['exp_year'] The credit card expiration year (YYYY)
  * @param string $credit_card['name'] The credit card cardholder name.  Required only is customer ID is not supplied.
  * @param int $credit_card['cvv'] The Card Verification Value.  Optional
  * @param int $customer_id The ID of the customer to link the charge to
  * @param array $customer An array of customer data to create a new customer with, if no customer_id
  * @param float $customer_ip The optional IP address of the customer
  * @param array $recur The details for a recurring charge
  * @param int $recur['plan_id'] The ID of the plan to pull recurring details from (Optional)
  * @param string $recur['start_date'] The start date of the subscription
  * @param string $recur['end_date'] The end date of the subscription
  * @param int $recur['free_trial'] The number of days to give a free trial before.  Will combine with start_date if that is also set. (Optional)
  * @param float $recur['amount'] The amount to charge every INTERVAL days.  If not there, the main $amount will be used.
  * @param int $recur['occurrences'] The total number of occurrences (Optional, if end_date doesn't exist).
  * @param string $recur['notification_url'] The URL to send POST updates to for notices re: this subscription.
  * @param string $return_url The URL for external payment processors to return the user to after payment
  * @param string $cancel_url The URL to send if the user cancels an external payment
  * @param int $renew The subscription that is being renewed, if there is one
  * @param int $coupon_id A potential coupon_id
  *
  * @return mixed Array with response_code and response_text
  */
 function Recur($gateway_id, $amount = FALSE, $credit_card = array(), $customer_id = FALSE, $customer = array(), $customer_ip = FALSE, $recur = array(), $return_url = FALSE, $cancel_url = FALSE, $renew = FALSE, $coupon_id = 0)
 {
     $this->CI->load->library('field_validation');
     // Get the gateway info to load the proper library
     $gateway = $this->GetGatewayDetails($gateway_id);
     if (!$gateway or $gateway['enabled'] == '0') {
         die($this->response->Error(5017));
     }
     // load the gateway
     $gateway_name = $gateway['name'];
     $this->CI->load->library('billing/payment/' . $gateway_name);
     $gateway_settings = $this->{$gateway_name}->Settings();
     $amount = (double) $amount;
     // validate function arguments
     if (!empty($amount) and empty($credit_card) and $gateway_settings['external'] == FALSE and $gateway_settings['no_credit_card'] == FALSE) {
         die($this->CI->response->Error(1004));
     }
     $this->load->library('field_validation');
     if (!empty($credit_card)) {
         // Validate the Credit Card number
         $credit_card['card_num'] = trim(str_replace(array(' ', '-'), '', $credit_card['card_num']));
         $credit_card['card_type'] = $this->field_validation->ValidateCreditCard($credit_card['card_num'], $gateway);
         if (!$credit_card['card_type']) {
             die($this->response->Error(5008));
         }
         if (!isset($credit_card['exp_month']) or empty($credit_card['exp_month'])) {
             die($this->response->Error(5008));
         }
         if (!isset($credit_card['exp_year']) or empty($credit_card['exp_year'])) {
             die($this->response->Error(5008));
         }
     }
     // are we linking this to another sub via renewal?
     if (!empty($renew)) {
         $this->CI->load->model('billing/recurring_model');
         $renewed_subscription = $this->CI->recurring_model->GetSubscriptionDetails($renew);
         if (!empty($renewed_subscription)) {
             $mark_as_renewed = $renewed_subscription['subscription_id'];
             /**
             				* this would automate the renewal subscription starting
             				* after the renewed subscription
             				*
             				if (strtotime($renewed_subscription['next_charge_date']) > time()) {
             					$recur['start_date'] = $renewed_subscription['next_charge_date'];
             				}
             				else {
             					$recur['start_date'] = date('Y-m-d');
             				}
             				
             				$recur['free_trial'] = 0;*/
         } else {
             $mark_as_renewed = FALSE;
         }
     } else {
         $mark_as_renewed = FALSE;
     }
     // Get the customer details if a customer id was included
     $this->load->model('billing/customer_model');
     if (!empty($customer_id)) {
         $customer = $this->CI->customer_model->GetCustomer($customer_id);
         $customer['customer_id'] = $customer['id'];
         $created_customer = FALSE;
     } elseif (isset($customer) and !empty($customer)) {
         // look for embedded customer information
         // by Getting the customer after it's creation, we get a nice clean ISO2 code for the country
         $customer_id = $this->CI->customer_model->NewCustomer($customer);
         $customer = $this->CI->customer_model->GetCustomer($customer_id);
         $customer['customer_id'] = $customer_id;
         unset($customer_id);
         $created_customer = TRUE;
     } else {
         // no customer_id or customer information - is this a problem?
         // we'll check if this gateway required customer information
         if ($gateway_settings['requires_customer_information'] == 1) {
             die($this->response->Error(5018));
         }
         // no customer information was passed but this gateway is OK with that, let's just get the customer first/last name
         // from the credit card name for our records
         if (!isset($credit_card['name'])) {
             die($this->response->Error(5004));
         } else {
             $name = explode(' ', $credit_card['name']);
             $customer['first_name'] = $name[0];
             $customer['last_name'] = $name[count($name) - 1];
             $customer['customer_id'] = $this->CI->customer_model->SaveNewCustomer($customer['first_name'], $customer['last_name']);
             $created_customer = TRUE;
         }
     }
     // if we have an IP, we'll populate this field
     // note, if we get an error later: the first thing we check is to see if an IP is required
     // by checking this *after* an error, we give the gateway a chance to be flexible and, if not,
     // we give the end-user the most likely error response
     if (!empty($customer_ip)) {
         // place it in $customer array
         $customer['ip_address'] = $customer_ip;
     } else {
         $customer['ip_address'] = '';
     }
     if (isset($recur['plan_id'])) {
         // we have a linked plan, let's load that information
         $this->CI->load->model('billing/plan_model');
         $plan_details = $this->CI->plan_model->GetPlanDetails($recur['plan_id']);
         $interval = isset($recur['interval']) ? $recur['interval'] : $plan_details->interval;
         $notification_url = isset($recur['notification_url']) ? $recur['notification_url'] : $plan_details->notification_url;
         $free_trial = isset($recur['free_trial']) ? $recur['free_trial'] : $plan_details->free_trial;
         $occurrences = isset($recur['occurrences']) ? $recur['occurrences'] : $plan_details->occurrences;
         // calculate first charge amount:
         //	  1) First charge is main $amount if given
         //	  2) If no $recur['amount'], use plan amount
         //	  3) Else use $recur['amount']
         if (isset($amount)) {
             $amount = $amount;
         } elseif (isset($recur['amount'])) {
             $amount = $recur['amount'];
         } elseif (isset($plan_details->amount)) {
             $amount = $plan_details->amount;
         }
         $amount = $this->field_validation->ValidateAmount($amount);
         if ($amount === FALSE) {
             die($this->response->Error(5009));
         }
         // store plan ID
         $plan_id = $plan_details->plan_id;
     } else {
         if (!isset($recur['interval']) or !is_numeric($recur['interval'])) {
             die($this->response->Error(5011));
         } else {
             $interval = $recur['interval'];
         }
         // Check for a notification URL
         $notification_url = isset($recur['notification_url']) ? $recur['notification_url'] : '';
         // Validate the amount
         if ($this->field_validation->ValidateAmount($amount) === FALSE) {
             die($this->response->Error(5009));
         }
         $plan_id = 0;
         $free_trial = (isset($recur['free_trial']) and is_numeric($recur['free_trial'])) ? $recur['free_trial'] : FALSE;
     }
     // Validate the start date to make sure it is in the future
     if (isset($recur['start_date'])) {
         // adjust to server time
         $recur['start_date'] = server_time($recur['start_date'], 'Y-m-d', true);
         if (!$this->field_validation->ValidateDate($recur['start_date']) or $recur['start_date'] < date('Y-m-d')) {
             die($this->response->Error(5001));
         } else {
             $start_date = date('Y-m-d', strtotime($recur['start_date']));
         }
     } else {
         $start_date = date('Y-m-d');
     }
     // do we have to adjust the start_date for a free trial?
     if ($free_trial) {
         $start_date = date('Y-m-d', strtotime($start_date) + $free_trial * 86400);
     }
     // get the next payment date
     if (date('Y-m-d', strtotime($start_date)) == date('Y-m-d')) {
         $next_charge_date = date('Y-m-d', strtotime($start_date) + $interval * 86400);
     } else {
         $next_charge_date = date('Y-m-d', strtotime($start_date));
     }
     // if an end date was passed, make sure it's valid
     if (isset($recur['end_date'])) {
         // adjust to server time
         $recur['end_date'] = !isset($end_date_set_by_server) ? server_time($recur['end_date']) : $recur['end_date'];
         if (strtotime($recur['end_date']) < time()) {
             // end_date is in the past
             die($this->response->Error(5002));
         } elseif (strtotime($recur['end_date']) < strtotime($start_date)) {
             // end_date is before start_date
             die($this->response->Error(5003));
         } else {
             // date is good
             $end_date = date('Y-m-d', strtotime($recur['end_date']));
         }
     } elseif (isset($occurrences) and !empty($occurrences)) {
         // calculate end_date from # of occurrences as defined by plan
         $end_date = date('Y-m-d', strtotime($start_date) + $interval * 86400 * $occurrences);
     } elseif (isset($recur['occurrences']) and !empty($recur['occurrences'])) {
         // calculate end_date from # of occurrences from recur node
         $end_date = date('Y-m-d', strtotime($start_date) + $interval * 86400 * $recur['occurrences']);
     } else {
         // calculate the end_date based on the max end date setting
         $end_date = date('Y-m-d', strtotime($start_date) + $this->config->item('max_recurring_days_from_today') * 86400);
     }
     if (!empty($credit_card) and isset($credit_card['exp_year']) and !empty($credit_card['exp_year'])) {
         // if the credit card expiration date is before the end date, we need to set the end date to one day before the expiration
         $check_year = $credit_card['exp_year'] > 2000 ? $credit_card['exp_year'] : '20' . $credit_card['exp_year'];
         $expiry = mktime(0, 0, 0, $credit_card['exp_month'], days_in_month($credit_card['exp_month'], $credit_card['exp_year']), $check_year);
         $date = strtotime($next_charge_date);
         while ($date < strtotime($end_date)) {
             if ($expiry < $date) {
                 $end_date = date('Y-m-d', $date);
                 break;
             }
             $date = $date + $interval * 86400;
         }
     }
     // adjust end date if it's less than next charge
     if (strtotime($end_date) <= strtotime($next_charge_date)) {
         // set end date to next charge
         $end_date = $next_charge_date;
         $total_occurrences = 1;
     }
     // figure the total number of occurrences
     $total_occurrences = round((strtotime($end_date) - strtotime($start_date)) / ($interval * 86400), 0);
     if ($total_occurrences < 1) {
         // the CC expiry date is only going to allow 1 charge
         $total_occurrences = 1;
     }
     // if they sent an $amount with their charge, this means that their first charge is different
     // so now we need to grab the true recurring amount, unless they overrode it
     if (isset($recur['amount'])) {
         $recur['amount'] = $recur['amount'];
     } elseif (is_object($plan_details) and isset($plan_details->amount)) {
         $recur['amount'] = $plan_details->amount;
     } else {
         $recur['amount'] = $amount;
     }
     // Save the subscription info
     $this->CI->load->model('billing/recurring_model');
     $card_last_four = isset($credit_card['card_num']) ? substr($credit_card['card_num'], -4, 4) : '0';
     $subscription_id = $this->CI->recurring_model->SaveRecurring($gateway['gateway_id'], $customer['customer_id'], $interval, $start_date, $end_date, $next_charge_date, $total_occurrences, $notification_url, $recur['amount'], $plan_id, $card_last_four, $coupon_id);
     // get subscription
     $subscription = $this->CI->recurring_model->GetRecurring($subscription_id);
     // is there a charge for today?
     $charge_today = date('Y-m-d', strtotime($subscription['date_created'])) == date('Y-m-d', strtotime($subscription['start_date'])) ? TRUE : FALSE;
     // set last_charge as today, if today was a charge
     if ($charge_today === TRUE) {
         $this->CI->recurring_model->SetChargeDates($subscription_id, date('Y-m-d'), $next_charge_date);
     }
     // if amount is greater than 0, we require a gateway to process
     if ($recur['amount'] > 0) {
         // recurring charges are not free
         $response = $this->CI->{$gateway_name}->Recur($gateway, $customer, $amount, $charge_today, $start_date, $end_date, $interval, $credit_card, $subscription_id, $total_occurrences, $return_url, $cancel_url);
     } elseif ($recur['amount'] <= 0 and $amount > 0) {
         // recurring charges are free, but there is an initial charge
         // can't be an external gateway
         if ($gateway_settings['external'] == TRUE) {
             die($this->response->Error(5024));
         }
         // must have a start date of today
         if ($charge_today !== TRUE) {
             die($this->response->Error(5025));
         }
         $this->CI->load->model('billing/charge_model');
         $customer['customer_id'] = isset($customer['customer_id']) ? $customer['customer_id'] : FALSE;
         $order_id = $this->CI->charge_model->CreateNewOrder($gateway['gateway_id'], $amount, $credit_card, $subscription_id, $customer['customer_id'], $customer_ip);
         $response = $this->CI->{$gateway_name}->Charge($order_id, $gateway, $customer, $amount, $credit_card, $return_url, $cancel_url);
         // translate response codes into proper recurring terms
         if ($response['response_code'] == 1) {
             // set order OK
             $this->CI->charge_model->SetStatus($order_id, 1);
             $response['response_code'] = 100;
             $response['recurring_id'] = $subscription_id;
         }
     } else {
         // this is a free subscription
         if ($charge_today === TRUE) {
             // create a $0 order for today's payment
             $this->CI->load->model('billing/charge_model');
             $customer['customer_id'] = isset($customer['customer_id']) ? $customer['customer_id'] : FALSE;
             $order_id = $this->CI->charge_model->CreateNewOrder($gateway['gateway_id'], 0, $credit_card, $subscription_id, $customer['customer_id'], $customer_ip);
             $this->CI->charge_model->SetStatus($order_id, 1);
             $response_array = array('charge_id' => $order_id, 'recurring_id' => $subscription_id);
         } else {
             $response_array = array('recurring_id' => $subscription_id);
         }
         $response = $this->CI->response->TransactionResponse(100, $response_array);
     }
     if (isset($created_customer) and $created_customer == TRUE and $response['response_code'] != 100) {
         // charge was rejected, so let's delete the customer record we just created
         $this->CI->customer_model->DeleteCustomer($customer['customer_id']);
     } elseif (isset($created_customer) and $created_customer == TRUE) {
         $response['customer_id'] = $customer['customer_id'];
     }
     if ($response['response_code'] != 100) {
         // clear it out completely
         $this->CI->recurring_model->DeleteRecurring($subscription_id);
     }
     if ($response['response_code'] == 100) {
         // save the "mark_as_renewed" subscription as charge data so we can do this maintenance later
         // we no longer do this maintenance here because for external gateways, we need to wait
         // for the user to complete their payment
         if (!empty($mark_as_renewed)) {
             $this->CI->load->model('billing/charge_data_model');
             $this->CI->charge_data_model->Save('r' . $subscription_id, 'mark_as_renewed', $mark_as_renewed);
         }
         if (!isset($response['not_completed']) or $response['not_completed'] == FALSE) {
             $this->CI->recurring_model->SetActive($subscription_id);
             // delayed recurrings don't have a charge ID
             $response['charge_id'] = isset($response['charge_id']) ? $response['charge_id'] : FALSE;
             // hook call
             $this->app_hooks->data('subscription', $response['recurring_id']);
             if (!empty($mark_as_renewed)) {
                 $this->app_hooks->trigger('subscription_renew', $response['recurring_id']);
             } else {
                 $this->app_hooks->trigger('subscription_new', $response['recurring_id']);
             }
             // trip a recurring charge?
             if (!empty($response['charge_id'])) {
                 // hook
                 $this->app_hooks->data('invoice', $response['charge_id']);
                 $this->app_hooks->trigger('subscription_charge', $response['charge_id'], $response['recurring_id']);
             }
         } else {
             unset($response['not_completed']);
         }
     } else {
         // did we require an IP address?
         if ($gateway_settings['requires_customer_ip'] == 1 and !$customer_ip) {
             die($this->response->Error(5019));
         }
     }
     $this->app_hooks->reset();
     return $response;
 }