<?php
// For compatibility with old library (Namespace Alias)
use Stripe\Customer as Stripe_Customer;
use Stripe\Invoice as Stripe_Invoice;
use Stripe\Plan as Stripe_Plan;
use Stripe\Charge as Stripe_Charge;

define( "PMPRO_STRIPE_API_VERSION", "2017-08-15" );

//include pmprogateway
require_once(dirname(__FILE__) . "/class.pmprogateway.php");

//load classes init method
add_action('init', array('PMProGateway_stripe', 'init'));

// loading plugin activation actions
add_action('activate_paid-memberships-pro', array('PMProGateway_stripe', 'pmpro_activation'));
add_action('deactivate_paid-memberships-pro', array('PMProGateway_stripe', 'pmpro_deactivation'));

/**
 * PMProGateway_stripe Class
 *
 * Handles Stripe integration.
 *
 * @since  1.4
 */
class PMProGateway_stripe extends PMProGateway
{
	/**
	 * @var bool    Is the Stripe/PHP Library loaded
	 */
	private static $is_loaded = false;
	/**
	 * Stripe Class Constructor
	 *
	 * @since 1.4
	 */
	function __construct($gateway = NULL) {
		$this->gateway = $gateway;
		$this->gateway_environment = pmpro_getOption("gateway_environment");

		if( true === $this->dependencies() ) {
			$this->loadStripeLibrary();
			Stripe\Stripe::setApiKey(pmpro_getOption("stripe_secretkey"));
			Stripe\Stripe::setAPIVersion( PMPRO_STRIPE_API_VERSION );
			self::$is_loaded = true;
		}

		return $this->gateway;
	}

	/**
	 * Warn if required extensions aren't loaded.
	 *
	 * @return bool
	 * @since 1.8.6.8.1
	 * @since 1.8.13.6 - Add json dependency
	 */
	public static function dependencies() {
		global $msg, $msgt, $pmpro_stripe_error;

		if ( version_compare( PHP_VERSION, '5.3.29', '<' )) {

			$pmpro_stripe_error = true;
			$msg = -1;
			$msgt = sprintf(__("The Stripe Gateway requires PHP 5.3.29 or greater. We recommend upgrading to PHP %s or greater. Ask your host to upgrade.", "paid-memberships-pro" ), PMPRO_PHP_MIN_VERSION );

			if ( !is_admin() ) {
				pmpro_setMessage( $msgt, "pmpro_error" );
			}

			return false;
		}

		$modules = array( 'curl', 'mbstring', 'json' );

		foreach($modules as $module){
			if(!extension_loaded($module)){
				$pmpro_stripe_error = true;
				$msg = -1;
				$msgt = sprintf(__("The %s gateway depends on the %s PHP extension. Please enable it, or ask your hosting provider to enable it.", 'paid-memberships-pro' ), 'Stripe', $module);

				//throw error on checkout page
				if(!is_admin())
					pmpro_setMessage($msgt, 'pmpro_error');

				return false;
			}
		}

		self::$is_loaded = true;
		return true;
	}

	/**
	 * Load the Stripe API library.
	 *
	 * @since 1.8
	 * Moved into a method in version 1.8 so we only load it when needed.
	 */
	function loadStripeLibrary() {
		//load Stripe library if it hasn't been loaded already (usually by another plugin using Stripe)
		if(!class_exists("Stripe\Stripe")) {
			require_once( PMPRO_DIR . "/includes/lib/Stripe/init.php" );
		}
	}

	/**
	 * Run on WP init
	 *
	 * @since 1.8
	 */
	static function init() {
		//make sure Stripe is a gateway option
		add_filter('pmpro_gateways', array('PMProGateway_stripe', 'pmpro_gateways'));

		//add fields to payment settings
		add_filter('pmpro_payment_options', array('PMProGateway_stripe', 'pmpro_payment_options'));
		add_filter('pmpro_payment_option_fields', array('PMProGateway_stripe', 'pmpro_payment_option_fields'), 10, 2);

		//add some fields to edit user page (Updates)
		add_action('pmpro_after_membership_level_profile_fields', array('PMProGateway_stripe', 'user_profile_fields'));
		add_action('profile_update', array('PMProGateway_stripe', 'user_profile_fields_save'));

		//old global RE showing billing address or not
		global $pmpro_stripe_lite;
		$pmpro_stripe_lite = apply_filters("pmpro_stripe_lite", !pmpro_getOption("stripe_billingaddress"));	//default is oposite of the stripe_billingaddress setting
		add_filter('pmpro_required_billing_fields', array('PMProGateway_stripe', 'pmpro_required_billing_fields'));

		//updates cron
		add_action('pmpro_cron_stripe_subscription_updates', array('PMProGateway_stripe', 'pmpro_cron_stripe_subscription_updates'));

		/*
			Filter pmpro_next_payment to get actual value
			via the Stripe API. This is disabled by default
			for performance reasons, but you can enable it
			by copying this line into a custom plugin or
			your active theme's functions.php and uncommenting
			it there.
		*/
		//add_filter('pmpro_next_payment', array('PMProGateway_stripe', 'pmpro_next_payment'), 10, 3);

		//code to add at checkout if Stripe is the current gateway
		$default_gateway = pmpro_getOption('gateway');
		$current_gateway = pmpro_getGateway();

		if( ($default_gateway == "stripe" || $current_gateway == "stripe") && empty($_REQUEST['review'] ) )	//$_REQUEST['review'] means the PayPal Express review page
		{
			add_action('pmpro_checkout_preheader', array('PMProGateway_stripe', 'pmpro_checkout_preheader'));
			add_action('pmpro_billing_preheader', array('PMProGateway_stripe', 'pmpro_checkout_preheader'));
			add_filter('pmpro_checkout_order', array('PMProGateway_stripe', 'pmpro_checkout_order'));
			add_filter('pmpro_billing_order', array('PMProGateway_stripe', 'pmpro_checkout_order'));
			add_filter('pmpro_include_billing_address_fields', array('PMProGateway_stripe', 'pmpro_include_billing_address_fields'));
			add_filter('pmpro_include_cardtype_field', array('PMProGateway_stripe', 'pmpro_include_billing_address_fields'));
			add_filter('pmpro_include_payment_information_fields', array('PMProGateway_stripe', 'pmpro_include_payment_information_fields'));
			
			//make sure we clean up subs we will be cancelling after checkout before processing
			add_action('pmpro_checkout_before_processing', array('PMProGateway_stripe', 'pmpro_checkout_before_processing'));
		}

		add_action( 'init', array( 'PMProGateway_stripe', 'pmpro_clear_saved_subscriptions' ) );
	}

	/**
	 * Clear any saved (preserved) subscription IDs that should have been processed and are now timed out.
	 */
	public static function pmpro_clear_saved_subscriptions() {

		if ( ! is_user_logged_in() ) {
			return;
		}

		global $current_user;
		$preserve = get_user_meta( $current_user->ID, 'pmpro_stripe_dont_cancel', true );

		// Clean up the subscription timeout values (if applicable)
		if ( !empty( $preserve ) ) {

			foreach ( $preserve as $sub_id => $timestamp ) {

				// Make sure the ID has "timed out" (more than 3 days since it was last updated/added.
				if ( intval( $timestamp ) >= ( current_time( 'timestamp' ) + ( 3 * DAY_IN_SECONDS ) ) ) {
					unset( $preserve[ $sub_id ] );
				}
			}

			update_user_meta( $current_user->ID, 'pmpro_stripe_dont_cancel', $preserve );
		}
	}

	/**
	 * Make sure Stripe is in the gateways list
	 *
	 * @since 1.8
	 */
	static function pmpro_gateways($gateways) {
		if(empty($gateways['stripe']))
			$gateways['stripe'] = __('Stripe', 'paid-memberships-pro' );

		return $gateways;
	}

	/**
	 * Get a list of payment options that the Stripe gateway needs/supports.
	 *
	 * @since 1.8
	 */
	static function getGatewayOptions() {
		$options = array(
			'sslseal',
			'nuclear_HTTPS',
			'gateway_environment',
			'stripe_secretkey',
			'stripe_publishablekey',
			'stripe_billingaddress',
			'currency',
			'use_ssl',
			'tax_state',
			'tax_rate',
			'accepted_credit_cards'
		);

		return $options;
	}

	/**
	 * Set payment options for payment settings page.
	 *
	 * @since 1.8
	 */
	static function pmpro_payment_options($options) {
		//get stripe options
		$stripe_options = self::getGatewayOptions();

		//merge with others.
		$options = array_merge($stripe_options, $options);

		return $options;
	}

	/**
	 * Display fields for Stripe options.
	 *
	 * @since 1.8
	 */
	static function pmpro_payment_option_fields($values, $gateway) {
	?>
	<tr class="pmpro_settings_divider gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<td colspan="2">
			<?php _e('Stripe Settings', 'paid-memberships-pro' ); ?>
		</td>
	</tr>
	<tr class="gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<th scope="row" valign="top">
			<label for="stripe_publishablekey"><?php _e('Publishable Key', 'paid-memberships-pro' );?>:</label>
		</th>
		<td>
			<input type="text" id="stripe_publishablekey" name="stripe_publishablekey" size="60" value="<?php echo esc_attr($values['stripe_publishablekey'])?>" />
			<?php
				$public_key_prefix = substr($values['stripe_publishablekey'] , 0, 3);
				if(!empty($values['stripe_publishablekey']) && $public_key_prefix != 'pk_') {
				?>
				<br /><small class="pmpro_message pmpro_error"><?php _e('Your Publishable Key appears incorrect.', 'paid-memberships-pro');?></small>
				<?php
				}
			?>
		</td>
	</tr>
	<tr class="gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<th scope="row" valign="top">
			<label for="stripe_secretkey"><?php _e('Secret Key', 'paid-memberships-pro' );?>:</label>
		</th>
		<td>
			<input type="text" id="stripe_secretkey" name="stripe_secretkey" size="60" value="<?php echo esc_attr($values['stripe_secretkey'])?>" />
		</td>
	</tr>
	<tr class="gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<th scope="row" valign="top">
			<label for="stripe_billingaddress"><?php _e('Show Billing Address Fields', 'paid-memberships-pro' );?>:</label>
		</th>
		<td>
			<select id="stripe_billingaddress" name="stripe_billingaddress">
				<option value="0" <?php if(empty($values['stripe_billingaddress'])) { ?>selected="selected"<?php } ?>><?php _e('No', 'paid-memberships-pro' );?></option>
				<option value="1" <?php if(!empty($values['stripe_billingaddress'])) { ?>selected="selected"<?php } ?>><?php _e('Yes', 'paid-memberships-pro' );?></option>
			</select>
			<small><?php _e("Stripe doesn't require billing address fields. Choose 'No' to hide them on the checkout page.<br /><strong>If No, make sure you disable address verification in the Stripe dashboard settings.</strong>", 'paid-memberships-pro' );?></small>
		</td>
	</tr>
	<tr class="gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<th scope="row" valign="top">
			<label><?php _e('Web Hook URL', 'paid-memberships-pro' );?>:</label>
		</th>
		<td>
			<p><?php _e('To fully integrate with Stripe, be sure to set your Web Hook URL to', 'paid-memberships-pro' );?> <pre><?php echo admin_url("admin-ajax.php") . "?action=stripe_webhook";?></pre></p>
		</td>
	</tr>

	<tr class="gateway gateway_stripe" <?php if($gateway != "stripe") { ?>style="display: none;"<?php } ?>>
		<th><?php _e( 'Stripe API Version', 'paid-memberships-pro' ); ?>:</th>
		<td><?php echo PMPRO_STRIPE_API_VERSION; ?></td>
	</tr>
	<?php
	}

	/**
	 * Code added to checkout preheader.
	 *
	 * @since 1.8
	 */
	static function pmpro_checkout_preheader() {
		global $gateway, $pmpro_level;

		$default_gateway = pmpro_getOption("gateway");

		if(($gateway == "stripe" || $default_gateway == "stripe") && !pmpro_isLevelFree($pmpro_level))
		{
			//stripe js library
			wp_enqueue_script("stripe", "https://js.stripe.com/v2/", array(), NULL);

			if ( ! function_exists( 'pmpro_stripe_javascript' ) ) {

				//stripe js code for checkout
				function pmpro_stripe_javascript()
				{
					global $pmpro_gateway, $pmpro_level, $pmpro_stripe_lite;
				?>
				<script type="text/javascript">
					<!--
					// this identifies your website in the createToken call below
					Stripe.setPublishableKey('<?php echo pmpro_getOption("stripe_publishablekey"); ?>');

					pmpro_require_billing = true;

					var tokenNum = 0;

					jQuery(document).ready(function() {
						jQuery(".pmpro_form").submit(function(event) {

						// prevent the form from submitting with the default action
						event.preventDefault();

						//double check in case a discount code made the level free
						if(pmpro_require_billing) {
							//build array for creating token
							var args = {
								number: jQuery('#AccountNumber').val(),
								exp_month: jQuery('#ExpirationMonth').val(),
								exp_year: jQuery('#ExpirationYear').val()
								<?php
									$pmpro_stripe_verify_address = apply_filters("pmpro_stripe_verify_address", pmpro_getOption('stripe_billingaddress'));
									if(!empty($pmpro_stripe_verify_address))
									{
									?>
									,address_line1: jQuery('#baddress1').val(),
									address_line2: jQuery('#baddress2').val(),
									address_city: jQuery('#bcity').val(),
									address_state: jQuery('#bstate').val(),
									address_zip: jQuery('#bzipcode').val(),
									address_country: jQuery('#bcountry').val()
								<?php
									}
								?>
							};

							//add CVC if not blank
							if(jQuery('#CVV').val().length)
								args['cvc'] = jQuery('#CVV').val();

							//add first and last name if not blank
							if (jQuery('#bfirstname').length && jQuery('#blastname').length)
								args['name'] = jQuery.trim(jQuery('#bfirstname').val() + ' ' + jQuery('#blastname').val());

							//create token(s)
							if (jQuery('#level').length) {
								var levelnums = jQuery("#level").val().split(",");
								for(var cnt = 0, len = levelnums.length; cnt < len; cnt++) {
									Stripe.createToken(args, stripeResponseHandler);
								}
							} else {
								Stripe.createToken(args, stripeResponseHandler);
							}

							// prevent the form from submitting with the default action
							return false;
						} else {
							this.submit();
							return true;	//not using Stripe anymore
						}
						});
					});

					function stripeResponseHandler(status, response) {
						if (response.error) {
							// re-enable the submit button
							jQuery('.pmpro_btn-submit-checkout,.pmpro_btn-submit').removeAttr("disabled");

							//hide processing message
							jQuery('#pmpro_processing_message').css('visibility', 'hidden');

							// show the errors on the form
							alert(response.error.message);
							jQuery(".payment-errors").text(response.error.message);
						} else {
							var form$ = jQuery("#pmpro_form, .pmpro_form");
							// token contains id, last4, and card type
							var token = response['id'];
							// insert the token into the form so it gets submitted to the server
							form$.append("<input type='hidden' name='stripeToken" + tokenNum + "' value='" + token + "'/>");
							tokenNum++;

							//console.log(response);

							//insert fields for other card fields
							if(jQuery('#CardType[name=CardType]').length)
								jQuery('#CardType').val(response['card']['brand']);
							else
								form$.append("<input type='hidden' name='CardType' value='" + response['card']['brand'] + "'/>");
							form$.append("<input type='hidden' name='AccountNumber' value='XXXXXXXXXXXX" + response['card']['last4'] + "'/>");
							form$.append("<input type='hidden' name='ExpirationMonth' value='" + ("0" + response['card']['exp_month']).slice(-2) + "'/>");
							form$.append("<input type='hidden' name='ExpirationYear' value='" + response['card']['exp_year'] + "'/>");

							// and submit
							form$.get(0).submit();
						}
					}
					-->
				</script>
				<?php
				}
				add_action("wp_head", "pmpro_stripe_javascript");
			}
		}
	}

	/**
	 * Don't require the CVV.
	 * Don't require address fields if they are set to hide.
	 */
	static function pmpro_required_billing_fields($fields) {
		global $pmpro_stripe_lite, $current_user, $bemail, $bconfirmemail;

		//CVV is not required if set that way at Stripe. The Stripe JS will require it if it is required.
		unset($fields['CVV']);

		//if using stripe lite, remove some fields from the required array
		if ($pmpro_stripe_lite) {
			//some fields to remove
			$remove = array('bfirstname', 'blastname', 'baddress1', 'bcity', 'bstate', 'bzipcode', 'bphone', 'bcountry', 'CardType');
			//if a user is logged in, don't require bemail either
			if (!empty($current_user->user_email)) {
				$remove[] = 'bemail';
				$bemail = $current_user->user_email;
				$bconfirmemail = $bemail;
			}
			//remove the fields
			foreach ($remove as $field)
				unset($fields[$field]);
		}

		return $fields;
	}

	/**
	 * Filtering orders at checkout.
	 *
	 * @since 1.8
	 */
	static function pmpro_checkout_order($morder) {
		//load up token values
		if(isset($_REQUEST['stripeToken0']))
		{
			// find the highest one still around, and use it - then remove it from $_REQUEST.
			$thetoken = "";
			$tokennum = -1;
			foreach($_REQUEST as $key => $param) {
				if(preg_match('/stripeToken(\d+)/', $key, $matches)) {
					if(intval($matches[1])>$tokennum) {
						$thetoken = sanitize_text_field($param);
						$tokennum = intval($matches[1]);
					}
				}
			}
			$morder->stripeToken = $thetoken;
			unset($_REQUEST['stripeToken'.$tokennum]);
		}

		//stripe lite code to get name from other sources if available
		global $pmpro_stripe_lite, $current_user;
		if(!empty($pmpro_stripe_lite) && empty($morder->FirstName) && empty($morder->LastName)) {
			if(!empty($current_user->ID)) {
				$morder->FirstName = get_user_meta($current_user->ID, "first_name", true);
				$morder->LastName = get_user_meta($current_user->ID, "last_name", true);
			} elseif(!empty($_REQUEST['first_name']) && !empty($_REQUEST['last_name'])) {
				$morder->FirstName = sanitize_text_field($_REQUEST['first_name']);
				$morder->LastName = sanitize_text_field($_REQUEST['last_name']);
			}
		}

		return $morder;
	}

	/**
	 * Code to run after checkout
	 *
	 * @since 1.8
	 */
	static function pmpro_after_checkout($user_id, $morder) {
		global $gateway;

		if($gateway == "stripe") {
			if(self::$is_loaded && !empty($morder) && !empty($morder->Gateway) && !empty($morder->Gateway->customer) && !empty($morder->Gateway->customer->id)) {
				update_user_meta($user_id, "pmpro_stripe_customerid", $morder->Gateway->customer->id);
			}
		}
	}

	/**
	 * Check settings if billing address should be shown.
	 * @since 1.8
	 */
	static function pmpro_include_billing_address_fields($include) {
		//check settings RE showing billing address
		if(!pmpro_getOption("stripe_billingaddress"))
			$include = false;

		return $include;
	}

	/**
	 * Use our own payment fields at checkout. (Remove the name attributes.)
	 * @since 1.8
	 */
	static function pmpro_include_payment_information_fields($include) {
		//global vars
		global $pmpro_requirebilling, $pmpro_show_discount_code, $discount_code, $CardType, $AccountNumber, $ExpirationMonth, $ExpirationYear;

		//get accepted credit cards
		$pmpro_accepted_credit_cards = pmpro_getOption("accepted_credit_cards");
		$pmpro_accepted_credit_cards = explode(",", $pmpro_accepted_credit_cards);
		$pmpro_accepted_credit_cards_string = pmpro_implodeToEnglish($pmpro_accepted_credit_cards);

		//include ours
		?>
		<div id="pmpro_payment_information_fields" class="pmpro_checkout" <?php if(!$pmpro_requirebilling || apply_filters("pmpro_hide_payment_information_fields", false) ) { ?>style="display: none;"<?php } ?>>
			<h3>
				<span class="pmpro_checkout-h3-name"><?php _e('Payment Information', 'paid-memberships-pro' );?></span>
				<span class="pmpro_checkout-h3-msg"><?php printf(__('We Accept %s', 'paid-memberships-pro' ), $pmpro_accepted_credit_cards_string);?></span>
			</h3>
			<?php $sslseal = pmpro_getOption("sslseal"); ?>
			<?php if(!empty($sslseal)) { ?>
				<div class="pmpro_checkout-fields-display-seal">
			<?php } ?>
			<div class="pmpro_checkout-fields<?php if(!empty($sslseal)) { ?> pmpro_checkout-fields-leftcol<?php } ?>">
			<?php
				$pmpro_include_cardtype_field = apply_filters('pmpro_include_cardtype_field', false);
				if($pmpro_include_cardtype_field) { ?>
					<div class="pmpro_checkout-field pmpro_payment-card-type">
						<label for="CardType"><?php _e('Card Type', 'paid-memberships-pro' );?></label>
						<select id="CardType" class=" <?php echo pmpro_getClassForField("CardType");?>">
							<?php foreach($pmpro_accepted_credit_cards as $cc) { ?>
								<option value="<?php echo $cc?>" <?php if($CardType == $cc) { ?>selected="selected"<?php } ?>><?php echo $cc?></option>
							<?php } ?>
						</select>
					</div>
				<?php } else { ?>
					<input type="hidden" id="CardType" name="CardType" value="<?php echo esc_attr($CardType);?>" />
					<script>
						<!--
						jQuery(document).ready(function() {
								jQuery('#AccountNumber').validateCreditCard(function(result) {
									var cardtypenames = {
										"amex":"American Express",
										"diners_club_carte_blanche":"Diners Club Carte Blanche",
										"diners_club_international":"Diners Club International",
										"discover":"Discover",
										"jcb":"JCB",
										"laser":"Laser",
										"maestro":"Maestro",
										"mastercard":"Mastercard",
										"visa":"Visa",
										"visa_electron":"Visa Electron"
									}

									if(result.card_type)
										jQuery('#CardType').val(cardtypenames[result.card_type.name]);
									else
										jQuery('#CardType').val('Unknown Card Type');
								});
						});
						-->
					</script>
				<?php } ?>
				<div class="pmpro_checkout-field pmpro_payment-account-number">
					<label for="AccountNumber"><?php _e('Card Number', 'paid-memberships-pro' );?></label>
					<input id="AccountNumber" class="input <?php echo pmpro_getClassForField("AccountNumber");?>" type="text" size="25" value="<?php echo esc_attr($AccountNumber)?>" autocomplete="off" />
				</div>
				<div class="pmpro_checkout-field pmpro_payment-expiration">
					<label for="ExpirationMonth"><?php _e('Expiration Date', 'paid-memberships-pro' );?></label>
					<select id="ExpirationMonth" class=" <?php echo pmpro_getClassForField("ExpirationMonth");?>">
						<option value="01" <?php if($ExpirationMonth == "01") { ?>selected="selected"<?php } ?>>01</option>
						<option value="02" <?php if($ExpirationMonth == "02") { ?>selected="selected"<?php } ?>>02</option>
						<option value="03" <?php if($ExpirationMonth == "03") { ?>selected="selected"<?php } ?>>03</option>
						<option value="04" <?php if($ExpirationMonth == "04") { ?>selected="selected"<?php } ?>>04</option>
						<option value="05" <?php if($ExpirationMonth == "05") { ?>selected="selected"<?php } ?>>05</option>
						<option value="06" <?php if($ExpirationMonth == "06") { ?>selected="selected"<?php } ?>>06</option>
						<option value="07" <?php if($ExpirationMonth == "07") { ?>selected="selected"<?php } ?>>07</option>
						<option value="08" <?php if($ExpirationMonth == "08") { ?>selected="selected"<?php } ?>>08</option>
						<option value="09" <?php if($ExpirationMonth == "09") { ?>selected="selected"<?php } ?>>09</option>
						<option value="10" <?php if($ExpirationMonth == "10") { ?>selected="selected"<?php } ?>>10</option>
						<option value="11" <?php if($ExpirationMonth == "11") { ?>selected="selected"<?php } ?>>11</option>
						<option value="12" <?php if($ExpirationMonth == "12") { ?>selected="selected"<?php } ?>>12</option>
					</select>/<select id="ExpirationYear" class=" <?php echo pmpro_getClassForField("ExpirationYear");?>">
						<?php
							for($i = date_i18n("Y"); $i < date_i18n("Y") + 10; $i++) { ?>
							<option value="<?php echo $i?>" <?php if($ExpirationYear == $i) { ?>selected="selected"<?php } ?>><?php echo $i?></option>
						<?php } ?>
					</select>
				</div>
				<?php
					$pmpro_show_cvv = apply_filters("pmpro_show_cvv", true);
					if($pmpro_show_cvv) { ?>
					<div class="pmpro_checkout-field pmpro_payment-cvv">
						<label for="CVV"><?php _e('Security Code (CVC)', 'paid-memberships-pro' );?></label>
						<input id="CVV" type="text" size="4" value="<?php if(!empty($_REQUEST['CVV'])) { echo esc_attr(sanitize_text_field($_REQUEST['CVV'])); }?>" class="input <?php echo pmpro_getClassForField("CVV");?>" />  <small>(<a href="javascript:void(0);" onclick="javascript:window.open('<?php echo pmpro_https_filter(PMPRO_URL)?>/pages/popup-cvv.html','cvv','toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, width=600, height=475');"><?php _e("what's this?", 'paid-memberships-pro' );?></a>)</small>
					</div>
				<?php } ?>
				<?php if($pmpro_show_discount_code) { ?>
					<div class="pmpro_checkout-field pmpro_payment-discount-code">
						<label for="discount_code"><?php _e('Discount Code', 'paid-memberships-pro' );?></label>
						<input class="input <?php echo pmpro_getClassForField("discount_code");?>" id="discount_code" name="discount_code" type="text" size="10" value="<?php echo esc_attr($discount_code)?>" />
						<input type="button" id="discount_code_button" name="discount_code_button" value="<?php _e('Apply', 'paid-memberships-pro' );?>" />
						<p id="discount_code_message" class="pmpro_message" style="display: none;"></p>
					</div>
				<?php } ?>
			</div> <!-- end pmpro_checkout-fields -->
			<?php if(!empty($sslseal)) { ?>
				<div class="pmpro_checkout-fields-rightcol pmpro_sslseal"><?php echo stripslashes($sslseal); ?></div>
			</div> <!-- end pmpro_checkout-fields-display-seal -->
			<?php } ?>
		</div> <!-- end pmpro_payment_information_fields -->
		<?php

		//don't include the default
		return false;
	}

	/**
	 * Fields shown on edit user page
	 *
	 * @since 1.8
	 */
	static function user_profile_fields($user) {
		global $wpdb, $current_user, $pmpro_currency_symbol;

		$cycles = array( __('Day(s)', 'paid-memberships-pro' ) => 'Day', __('Week(s)', 'paid-memberships-pro' ) => 'Week', __('Month(s)', 'paid-memberships-pro' ) => 'Month', __('Year(s)', 'paid-memberships-pro' ) => 'Year' );
		$current_year = date_i18n("Y");
		$current_month = date_i18n("m");

		//make sure the current user has privileges
		$membership_level_capability = apply_filters("pmpro_edit_member_capability", "manage_options");
		if(!current_user_can($membership_level_capability))
			return false;

		//more privelges they should have
		$show_membership_level = apply_filters("pmpro_profile_show_membership_level", true, $user);
		if(!$show_membership_level)
			return false;

		//check that user has a current subscription at Stripe
		$last_order = new MemberOrder();
		$last_order->getLastMemberOrder($user->ID);

		//assume no sub to start
		$sub = false;

		//check that gateway is Stripe
		if($last_order->gateway == "stripe" && self::$is_loaded )
		{
			//is there a customer?
			$sub = $last_order->Gateway->getSubscription($last_order);
		}

		$customer_id = $user->pmpro_stripe_customerid;

		if(empty($sub)) {
			//make sure we delete stripe updates
			update_user_meta($user->ID, "pmpro_stripe_updates", array());

			//if the last order has a sub id, let the admin know there is no sub at Stripe
			if(!empty($last_order) && $last_order->gateway == "stripe" && !empty($last_order->subscription_transaction_id) && strpos($last_order->subscription_transaction_id, "sub_") !== false)
			{
			?>
			<p><?php printf( __('%1$sNote:%2$s Subscription %3$s%4$s%5$s could not be found at Stripe. It may have been deleted.', 'paid-memberships-pro'), '<strong>', '</strong>', '<strong>', esc_attr($last_order->subscription_transaction_id), '</strong>' ); ?></p>
			<?php
			}
		} elseif ( true === self::$is_loaded ) {
		?>
		<h3><?php _e("Subscription Updates", 'paid-memberships-pro' ); ?></h3>
		<p>
			<?php
				if(empty($_REQUEST['user_id']))
					_e("Subscription updates, allow you to change the member's subscription values at predefined times. Be sure to click Update Profile after making changes.", 'paid-memberships-pro' );
				else
					_e("Subscription updates, allow you to change the member's subscription values at predefined times. Be sure to click Update User after making changes.", 'paid-memberships-pro' );
			?>
		</p>
		<table class="form-table">
			<tr>
				<th><label for="membership_level"><?php _e("Update", 'paid-memberships-pro' ); ?></label></th>
				<td id="updates_td">
					<?php
						$old_updates = $user->pmpro_stripe_updates;
						if(is_array($old_updates))
						{
							$updates = array_merge(
								array(array('template'=>true, 'when'=>'now', 'date_month'=>'', 'date_day'=>'', 'date_year'=>'', 'billing_amount'=>'', 'cycle_number'=>'', 'cycle_period'=>'Month')),
								$old_updates
							);
						}
						else
							$updates = array(array('template'=>true, 'when'=>'now', 'date_month'=>'', 'date_day'=>'', 'date_year'=>'', 'billing_amount'=>'', 'cycle_number'=>'', 'cycle_period'=>'Month'));

						foreach($updates as $update)
						{
						?>
						<div class="updates_update" <?php if(!empty($update['template'])) { ?>style="display: none;"<?php } ?>>
							<select class="updates_when" name="updates_when[]">
								<option value="now" <?php selected($update['when'], "now");?>>Now</option>
								<option value="payment" <?php selected($update['when'], "payment");?>>After Next Payment</option>
								<option value="date" <?php selected($update['when'], "date");?>>On Date</option>
							</select>
							<span class="updates_date" <?php if($update['when'] != "date") { ?>style="display: none;"<?php } ?>>
								<select name="updates_date_month[]">
									<?php
										for($i = 1; $i < 13; $i++)
										{
										?>
										<option value="<?php echo str_pad($i, 2, "0", STR_PAD_LEFT);?>" <?php if(!empty($update['date_month']) && $update['date_month'] == $i) { ?>selected="selected"<?php } ?>>
											<?php echo date_i18n("M", strtotime($i . "/1/" . $current_year));?>
										</option>
										<?php
										}
									?>
								</select>
								<input name="updates_date_day[]" type="text" size="2" value="<?php if(!empty($update['date_day'])) echo esc_attr($update['date_day']);?>" />
								<input name="updates_date_year[]" type="text" size="4" value="<?php if(!empty($update['date_year'])) echo esc_attr($update['date_year']);?>" />
							</span>
							<span class="updates_billing" <?php if($update['when'] == "now") { ?>style="display: none;"<?php } ?>>
								<?php echo $pmpro_currency_symbol?><input name="updates_billing_amount[]" type="text" size="10" value="<?php echo esc_attr($update['billing_amount']);?>" />
								<small><?php _e('per', 'paid-memberships-pro' );?></small>
								<input name="updates_cycle_number[]" type="text" size="5" value="<?php echo esc_attr($update['cycle_number']);?>" />
								<select name="updates_cycle_period[]">
								  <?php
									foreach ( $cycles as $name => $value ) {
									  echo "<option value='$value'";
									  if(!empty($update['cycle_period']) && $update['cycle_period'] == $value) echo " selected='selected'";
									  echo ">$name</option>";
									}
								  ?>
								</select>
							</span>
							<span>
								<a class="updates_remove" href="javascript:void(0);">Remove</a>
							</span>
						</div>
						<?php
						}
						?>
					<p><a id="updates_new_update" href="javascript:void(0);">+ New Update</a></p>
				</td>
			</tr>
		</table>
		<script>
			<!--
			jQuery(document).ready(function() {
				//function to update dropdowns/etc based on when field
				function updateSubscriptionUpdateFields(when)
				{
					if(jQuery(when).val() == 'date')
						jQuery(when).parent().children('.updates_date').show();
					else
						jQuery(when).parent().children('.updates_date').hide();

					if(jQuery(when).val() == 'no')
						jQuery(when).parent().children('.updates_billing').hide();
					else
						jQuery(when).parent().children('.updates_billing').show();
				}

				//and update on page load
				jQuery('.updates_when').each(function() { if(jQuery(this).parent().css('display') != 'none') updateSubscriptionUpdateFields(this); });

				//add a new update when clicking to
				var num_updates_divs = <?php echo count($updates);?>;
				jQuery('#updates_new_update').click(function() {
					//get updates
					updates = jQuery('.updates_update').toArray();

					//clone the first one
					new_div = jQuery(updates[0]).clone();

					//append
					new_div.insertBefore('#updates_new_update');

					//update events
					addUpdateEvents()

					//unhide it
					new_div.show();
					updateSubscriptionUpdateFields(new_div.children('.updates_when'));
				});

				function addUpdateEvents()
				{
					//update when when changes
					jQuery('.updates_when').change(function() {
						updateSubscriptionUpdateFields(this);
					});

					//remove updates when clicking
					jQuery('.updates_remove').click(function() {
						jQuery(this).parent().parent().remove();
					});
				}
				addUpdateEvents();
			});
		-->
		</script>
		<?php
		}
	}

	/**
	 * Process fields from the edit user page
	 *
	 * @since 1.8
	 */
	static function user_profile_fields_save($user_id) {
		global $wpdb;

		//check capabilities
		$membership_level_capability = apply_filters("pmpro_edit_member_capability", "manage_options");
		if(!current_user_can($membership_level_capability))
			return false;

		//make sure some value was passed
		if(!isset($_POST['updates_when']) || !is_array($_POST['updates_when']))
			return;

		//vars
		$updates = array();
		$next_on_date_update = "";

		//build array of updates (we skip the first because it's the template field for the JavaScript
		for($i = 1; $i < count($_POST['updates_when']); $i++)
		{
			$update = array();

			//all updates have these values
			$update['when'] = pmpro_sanitize_with_safelist($_POST['updates_when'][$i], array('now', 'payment', 'date'));
			$update['billing_amount'] = sanitize_text_field($_POST['updates_billing_amount'][$i]);
			$update['cycle_number'] = intval($_POST['updates_cycle_number'][$i]);
			$update['cycle_period'] = sanitize_text_field($_POST['updates_cycle_period'][$i]);

			//these values only for on date updates
			if($_POST['updates_when'][$i] == "date")
			{
				$update['date_month'] = str_pad(intval($_POST['updates_date_month'][$i]), 2, "0", STR_PAD_LEFT);
				$update['date_day'] = str_pad(intval($_POST['updates_date_day'][$i]), 2, "0", STR_PAD_LEFT);
				$update['date_year'] = intval($_POST['updates_date_year'][$i]);
			}

			//make sure the update is valid
			if(empty($update['cycle_number']))
				continue;

			//if when is now, update the subscription
			if($update['when'] == "now")
			{
				PMProGateway_stripe::updateSubscription($update, $user_id);

				continue;
			}
			elseif($update['when'] == 'date')
			{
				if(!empty($next_on_date_update))
					$next_on_date_update = min($next_on_date_update, $update['date_year'] . "-" . $update['date_month'] . "-" . $update['date_day']);
				else
					$next_on_date_update = $update['date_year'] . "-" . $update['date_month'] . "-" . $update['date_day'];
			}

			//add to array
			$updates[] = $update;
		}

		//save in user meta
		update_user_meta($user_id, "pmpro_stripe_updates", $updates);

		//save date of next on-date update to make it easier to query for these in cron job
		update_user_meta($user_id, "pmpro_stripe_next_on_date_update", $next_on_date_update);
	}

	/**
	 * Cron activation for subscription updates.
	 *
	 * @since 1.8
	 */
	static function pmpro_activation() {
		pmpro_maybe_schedule_event(time(), 'daily', 'pmpro_cron_stripe_subscription_updates');
	}

	/**
	 * Cron deactivation for subscription updates.
	 *
	 * @since 1.8
	 */
	static function pmpro_deactivation() {
		wp_clear_scheduled_hook('pmpro_cron_stripe_subscription_updates');
	}

	/**
	 * Cron job for subscription updates.
	 *
	 * @since 1.8
	 */
	static function pmpro_cron_stripe_subscription_updates() {
		global $wpdb;

		//get all updates for today (or before today)
		$sqlQuery = "SELECT *
					 FROM $wpdb->usermeta
					 WHERE meta_key = 'pmpro_stripe_next_on_date_update'
						AND meta_value IS NOT NULL
						AND meta_value <> ''
						AND meta_value < '" . date_i18n("Y-m-d", strtotime("+1 day", current_time('timestamp'))) . "'";
		$updates = $wpdb->get_results($sqlQuery);

		if(!empty($updates)) {
			//loop through
			foreach($updates as $update) {
				//pull values from update
				$user_id = $update->user_id;

				$user = get_userdata($user_id);

				//if user is missing, delete the update info and continue
				if(empty($user) || empty($user->ID)) {
					delete_user_meta($user_id, "pmpro_stripe_updates");
					delete_user_meta($user_id, "pmpro_stripe_next_on_date_update");

					continue;
				}

				$user_updates = $user->pmpro_stripe_updates;
				$next_on_date_update = "";

				//loop through updates looking for updates happening today or earlier
				if(!empty($user_updates)) {
					foreach($user_updates as $key => $ud) {
						if($ud['when'] == 'date' &&
						   $ud['date_year'] . "-" . $ud['date_month'] . "-" . $ud['date_day'] <= date_i18n("Y-m-d", current_time('timestamp') )
						) {
							PMProGateway_stripe::updateSubscription($ud, $user_id);

							//remove update from list
							unset($user_updates[$key]);
						} elseif($ud['when'] == 'date') {
							//this is an on date update for the future, update the next on date update
							if(!empty($next_on_date_update))
								$next_on_date_update = min($next_on_date_update, $ud['date_year'] . "-" . $ud['date_month'] . "-" . $ud['date_day']);
							else
								$next_on_date_update = $ud['date_year'] . "-" . $ud['date_month'] . "-" . $ud['date_day'];
						}
					}
				}

				//save updates in case we removed some
				update_user_meta($user_id, "pmpro_stripe_updates", $user_updates);

				//save date of next on-date update to make it easier to query for these in cron job
				update_user_meta($user_id, "pmpro_stripe_next_on_date_update", $next_on_date_update);
			}
		}
	}

	/**
	 * Before processing a checkout, check for pending invoices we want to clean up.
	 * This prevents double billing issues in cases where Stripe has pending invoices
	 * because of an expired credit card/etc and a user checks out to renew their subscription
	 * instead of updating their billing information via the billing info page.
	 */
	static function pmpro_checkout_before_processing() {
		global $wpdb, $current_user;

		// we're only worried about cases where the user is logged in
		if( ! is_user_logged_in() ) {
			return;
		}

		// make sure we're checking out with Stripe
		$current_gateway = pmpro_getGateway();
		if ( $current_gateway != 'stripe' ) {
			return;
		}

		//check the $pmpro_cancel_previous_subscriptions filter
		//this is used in add ons like Gift Memberships to stop PMPro from cancelling old memberships
		$pmpro_cancel_previous_subscriptions = true;
		$pmpro_cancel_previous_subscriptions = apply_filters( 'pmpro_cancel_previous_subscriptions', $pmpro_cancel_previous_subscriptions );        
		if( ! $pmpro_cancel_previous_subscriptions ) {
			return;
		}

		//get user and membership level
		$membership_level = pmpro_getMembershipLevelForUser($current_user->ID);

		//no level, then probably no subscription at Stripe anymore
		if(empty($membership_level))
			return;

		/**
		 * Filter which levels to cancel at the gateway.
		 * MMPU will set this to all levels that are going to be cancelled during this checkout.
		 * Others may want to display this by add_filter('pmpro_stripe_levels_to_cancel_before_checkout', __return_false);
		 */
		$levels_to_cancel = apply_filters('pmpro_stripe_levels_to_cancel_before_checkout', array($membership_level->id), $current_user);

		foreach($levels_to_cancel as $level_to_cancel) {
			//get the last order for this user/level
			$last_order = new MemberOrder();
			$last_order->getLastMemberOrder($current_user->ID, 'success', $level_to_cancel, 'stripe');

			//so let's cancel the user's susbcription
			if(!empty($last_order) && !empty($last_order->subscription_transaction_id)) {
				$subscription = $last_order->Gateway->getSubscription($last_order);
				if(!empty($subscription)) {
					$last_order->Gateway->cancelSubscriptionAtGateway($subscription, true);

					//Stripe was probably going to cancel this subscription 7 days past the payment failure (maybe just one hour, use a filter for sure)
					$memberships_users_row = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_memberships_users WHERE user_id = '" . $current_user->ID . "' AND membership_id = '" . $level_to_cancel . "' AND status = 'active' LIMIT 1");

					if(!empty($memberships_users_row) && (empty($memberships_users_row->enddate) || $memberships_users_row->enddate == '0000-00-00 00:00:00')) {
						/**
						 * Filter graced period days when canceling existing subscriptions at checkout.
						 *
						 * @since 1.9.4
						 *
						 * @param int $days Grace period defaults to 3 days
						 * @param object $membership Membership row from pmpro_memberships_users including membership_id, user_id, and enddate
						 */
						$days_grace = apply_filters('pmpro_stripe_days_grace_when_canceling_existing_subscriptions_at_checkout', 3, $memberships_users_row);
						$new_enddate = date('Y-m-d H:i:s', current_time('timestamp')+3600*24*$days_grace);
						$wpdb->update( $wpdb->pmpro_memberships_users, array('enddate'=>$new_enddate), array('user_id'=>$current_user->ID, 'membership_id'=>$level_to_cancel, 'status'=>'active'), array('%s'), array('%d', '%d', '%s') );
					}
				}
			}
		}
	}

	/**
	 * Process checkout and decide if a charge and or subscribe is needed
	 *
	 * @since 1.4
	 */
	function process(&$order) {
		//check for initial payment
		if(floatval($order->InitialPayment) == 0) {
			//just subscribe
			return $this->subscribe($order);
		} else {
			//charge then subscribe
			if($this->charge($order)) {
				if(pmpro_isLevelRecurring($order->membership_level)) {
					if($this->subscribe($order)) {
						//yay!
						return true;
					} else {
						//try to refund initial charge
						return false;
					}
				} else {
					//only a one time charge
					$order->status = "success";	//saved on checkout page
					return true;
				}
			} else {
				if(empty($order->error)) {
					if ( ! self::$is_loaded ) {

						$order->error = __( "Payment error: Please contact the webmaster (stripe-load-error)", 'paid-memberships-pro' );

					} else {

						$order->error = __( "Unknown error: Initial payment failed.", 'paid-memberships-pro' );
					}
				}

				return false;
			}
		}
	}

	/**
	 * Make a one-time charge with Stripe
	 *
	 * @since 1.4
	 */
	function charge(&$order) {
		global $pmpro_currency, $pmpro_currencies;
		$currency_unit_multiplier = 100; //ie 100 cents per USD

		//account for zero-decimal currencies like the Japanese Yen
		if(is_array($pmpro_currencies[$pmpro_currency]) && isset($pmpro_currencies[$pmpro_currency]['decimals']) && $pmpro_currencies[$pmpro_currency]['decimals'] == 0) {
			$currency_unit_multiplier = 1;
		}			

		//create a code for the order
		if(empty($order->code)) {
			$order->code = $order->getRandomCode();	
		}			

		//what amount to charge?
		$amount = $order->InitialPayment;

		//tax
		$order->subtotal = $amount;
		$tax = $order->getTax(true);
		$amount = pmpro_round_price((float)$order->subtotal + (float)$tax);

		//create a customer
		$result = $this->getCustomer($order);

		if(empty($result)) {
			//failed to create customer
			return false;
		}

		//charge
		try {
			$response = Stripe_Charge::create(array(
			  "amount" => $amount * $currency_unit_multiplier, # amount in cents, again
			  "currency" => strtolower($pmpro_currency),
			  "customer" => $this->customer->id,
			  "description" => apply_filters('pmpro_stripe_order_description', "Order #" . $order->code . ", " . trim($order->FirstName . " " . $order->LastName) . " (" . $order->Email . ")", $order)
			  )
			);
		} catch (Exception $e) {
			//$order->status = "error";
			$order->errorcode = true;
			$order->error = "Error: " . $e->getMessage();
			$order->shorterror = $order->error;
			return false;
		}

		if(empty($response["failure_message"])) {
			//successful charge
			$order->payment_transaction_id = $response["id"];
			$order->updateStatus("success");
			$order->saveOrder();
			return true;
		} else {
			//$order->status = "error";
			$order->errorcode = true;
			$order->error = $response['failure_message'];
			$order->shorterror = $response['failure_message'];
			return false;
		}
	}

	/**
	 * Get a Stripe customer object.
	 *
	 * If $this->customer is set, it returns it.
	 * It first checks if the order has a subscription_transaction_id. If so, that's the customer id.
	 * If not, it checks for a user_id on the order and searches for a customer id in the user meta.
	 * If a customer id is found, it checks for a customer through the Stripe API.
	 * If a customer is found and there is a stripeToken on the order passed, it will update the customer.
	 * If no customer is found and there is a stripeToken on the order passed, it will create a customer.
	 *
	 * @since 1.4
	 * @return Stripe_Customer|false
	 */
	function getCustomer(&$order = false, $force = false) {
		global $current_user;

		//already have it?
		if(!empty($this->customer) && !$force) {
			return $this->customer;
		}			

		//figure out user_id and user
		if(!empty($order->user_id)) {
			$user_id = $order->user_id;	
		}			

		//if no id passed, check the current user
		if(empty($user_id) && !empty($current_user->ID)) {
			$user_id = $current_user->ID;	
		}			

		if(!empty($user_id)) {
			$user = get_userdata($user_id);
		} else {
			$user = NULL;	
		}			

		//transaction id?
		if(!empty($order->subscription_transaction_id) && strpos($order->subscription_transaction_id, "cus_") !== false) {
			$customer_id = $order->subscription_transaction_id;
		} else {
			//try based on user id
			if(!empty($user_id)) {
				$customer_id = get_user_meta($user_id, "pmpro_stripe_customerid", true);
			}

			//look up by transaction id
			if(empty($customer_id) && !empty($user_id)) {
				//user id from this order or the user's last stripe order
				if(!empty($order->payment_transaction_id)) {
					$payment_transaction_id = $order->payment_transaction_id;
				} else {
					//find the user's last stripe order
					$last_order = new MemberOrder();
					$last_order->getLastMemberOrder($user_id, array('success', 'cancelled'), NULL, 'stripe', $order->Gateway->gateway_environment);
					if(!empty($last_order->payment_transaction_id))
						$payment_transaction_id = $last_order->payment_transaction_id;
				}

				//we have a transaction id to look up
				if(!empty($payment_transaction_id)) {
					if(strpos($payment_transaction_id, "ch_") !== false) {
						//charge, look it up
						try {
							$charge = Stripe_Charge::retrieve($payment_transaction_id);
						} catch( \Exception $exception ) {
							$order->error = sprintf( __( 'Error: %s', 'paid-memberships-pro' ), $exception->getMessage() );
							return false;
						}

						if(!empty($charge) && !empty($charge->customer))
							$customer_id = $charge->customer;
					} else if(strpos($payment_transaction_id, "in_") !== false) {
						//invoice look it up
						try {
							$invoice = Stripe_Invoice::retrieve($payment_transaction_id);
						} catch( \Exception $exception ) {
							$order->error = sprintf( __( 'Error: %s', 'paid-memberships-pro' ), $exception->getMessage() );
							return false;
						}

						if(!empty($invoice) && !empty($invoice->customer))
							$customer_id = $invoice->customer;
					}
				}

				//if we found it, save to user meta for future reference
				if(!empty($customer_id)) {
					update_user_meta($user_id, "pmpro_stripe_customerid", $customer_id);
				}					
			}
		}

		//get name and email values from order in case we update
		if(!empty($order->FirstName) && !empty($order->LastName)) {
			$name = trim($order->FirstName . " " . $order->LastName);
		} elseif(!empty($order->FirstName)) {
			$name = $order->FirstName;
		} elseif(!empty($order->LastName)) {
			$name = $order->LastName;
		}
		
		if(empty($name) && !empty($user->ID)) {
			$name = trim($user->first_name . " " . $user->last_name);

			//still empty?
			if(empty($name))
				$name = $user->user_login;
		} elseif(empty($name)) {
			$name = "No Name";
		}
		
		if(!empty($order->Email)) {
			$email = $order->Email;
		} else {
			$email = "";
		}
			
		if(empty($email) && !empty($user->ID) && !empty($user->user_email)) {
			$email = $user->user_email;
		} elseif(empty($email)) {
			$email = "No Email";
		}			

		//check for an existing stripe customer
		if(!empty($customer_id)) {
			try {
				$this->customer = Stripe_Customer::retrieve($customer_id);

				//update the customer description and card
				if(!empty($order->stripeToken)) {
					$this->customer->description = $name . " (" . $email . ")";
					$this->customer->email = $email;
					$this->customer->card = $order->stripeToken;
					$this->customer->save();
				}

				return $this->customer;
			} catch (Exception $e) {
				//assume no customer found
			}
		}

		//no customer id, create one
		if(!empty($order->stripeToken)) {
			try {
				$this->customer = Stripe_Customer::create(array(
						  "description" => $name . " (" . $email . ")",
						  "email" => $order->Email,
						  "card" => $order->stripeToken
						));
			} catch (Exception $e) {
				$order->error = __("Error creating customer record with Stripe:", 'paid-memberships-pro' ) . " " . $e->getMessage();
				$order->shorterror = $order->error;
				return false;
			}

			if(!empty($user_id)) {
				//user logged in/etc
				update_user_meta($user_id, "pmpro_stripe_customerid", $this->customer->id);
			} else {
				//user not registered yet, queue it up
				global $pmpro_stripe_customer_id;
				$pmpro_stripe_customer_id = $this->customer->id;
				if(! function_exists('pmpro_user_register_stripe_customerid')) {
					function pmpro_user_register_stripe_customerid($user_id) {
						global $pmpro_stripe_customer_id;
						update_user_meta($user_id, "pmpro_stripe_customerid", $pmpro_stripe_customer_id);
					}
					add_action("user_register", "pmpro_user_register_stripe_customerid");
				}
			}

			return apply_filters('pmpro_stripe_create_customer', $this->customer);
		}

		return false;
	}

	/**
	 * Get a Stripe subscription from a PMPro order
	 *
	 * @since 1.8
	 */
	function getSubscription(&$order) {
		global $wpdb;

		//no order?
		if(empty($order) || empty($order->code)) {
			return false;
		}

		$result = $this->getCustomer($order, true);	//force so we don't get a cached sub for someone else

		//no customer?
		if(empty($result)) {
			return false;
		}			

		//is there a subscription transaction id pointing to a sub?
		if(!empty($order->subscription_transaction_id) && strpos($order->subscription_transaction_id, "sub_") !== false) {
			try {
				$sub = $this->customer->subscriptions->retrieve($order->subscription_transaction_id);
			} catch (Exception $e) {
				$order->error = __("Error getting subscription with Stripe:", 'paid-memberships-pro' ) . $e->getMessage();
				$order->shorterror = $order->error;
				return false;
			}

			return $sub;
		}

		//no subscriptions object in customer
		if(empty($this->customer->subscriptions)) {
			return false;
		}

		//find subscription based on customer id and order/plan id
		$subscriptions = $this->customer->subscriptions->all();

		//no subscriptions
		if(empty($subscriptions) || empty($subscriptions->data)) {
			return false;
		}

		//we really want to test against the order codes of all orders with the same subscription_transaction_id (customer id)
		$codes = $wpdb->get_col("SELECT code FROM $wpdb->pmpro_membership_orders WHERE user_id = '" . $order->user_id . "' AND subscription_transaction_id = '" . $order->subscription_transaction_id . "' AND status NOT IN('refunded', 'review', 'token', 'error')");

		//find the one for this order
		foreach($subscriptions->data as $sub) {
			if(in_array($sub->plan->id, $codes)) {
				return $sub;
			}
		}

		//didn't find anything yet
		return false;
	}

	/**
	 * Create a new subscription with Stripe
	 *
	 * @since 1.4
	 */
	function subscribe(&$order, $checkout = true) {
		global $pmpro_currency, $pmpro_currencies;

		$currency_unit_multiplier = 100; //ie 100 cents per USD

		//account for zero-decimal currencies like the Japanese Yen
		if(is_array($pmpro_currencies[$pmpro_currency]) && isset($pmpro_currencies[$pmpro_currency]['decimals']) && $pmpro_currencies[$pmpro_currency]['decimals'] == 0)
			$currency_unit_multiplier = 1;

		//create a code for the order
		if(empty($order->code))
			$order->code = $order->getRandomCode();

		//filter order before subscription. use with care.
		$order = apply_filters("pmpro_subscribe_order", $order, $this);

		//figure out the user
		if(!empty($order->user_id)) {
			$user_id = $order->user_id;
		} else {
			global $current_user;
			$user_id = $current_user->ID;
		}

		//set up customer
		$result = $this->getCustomer($order);
		if(empty($result)) {
			return false;	//error retrieving customer
		}

		//set subscription id to custom id
		$order->subscription_transaction_id = $this->customer['id'];	//transaction id is the customer id, we save it in user meta later too

		//figure out the amounts
		$amount = $order->PaymentAmount;
		$amount_tax = $order->getTaxForPrice($amount);
		$amount = pmpro_round_price((float)$amount + (float)$amount_tax);

		/*
			There are two parts to the trial. Part 1 is simply the delay until the first payment
			since we are doing the first payment as a separate transaction.
			The second part is the actual "trial" set by the admin.

			Stripe only supports Year or Month for billing periods, but we account for Days and Weeks just in case.
		*/
		//figure out the trial length (first payment handled by initial charge)
		if($order->BillingPeriod == "Year") {
			$trial_period_days = $order->BillingFrequency * 365;	//annual
		} elseif($order->BillingPeriod == "Day") {
			$trial_period_days = $order->BillingFrequency * 1;		//daily
		} elseif($order->BillingPeriod == "Week") {
			$trial_period_days = $order->BillingFrequency * 7;		//weekly
		} else {
			$trial_period_days = $order->BillingFrequency * 30;	//assume monthly
		}			

		//convert to a profile start date
		$order->ProfileStartDate = date_i18n("Y-m-d", strtotime("+ " . $trial_period_days . " Day", current_time("timestamp"))) . "T0:0:0";

		//filter the start date
		$order->ProfileStartDate = apply_filters("pmpro_profile_start_date", $order->ProfileStartDate, $order);

		//convert back to days
		$trial_period_days = ceil(abs(strtotime(date_i18n("Y-m-d"), current_time("timestamp")) - strtotime($order->ProfileStartDate, current_time("timestamp"))) / 86400);

		//for free trials, just push the start date of the subscription back
		if(!empty($order->TrialBillingCycles) && $order->TrialAmount == 0) {
			$trialOccurrences = (int)$order->TrialBillingCycles;
			if($order->BillingPeriod == "Year") {
				$trial_period_days = $trial_period_days + (365 * $order->BillingFrequency * $trialOccurrences);	//annual
			} elseif($order->BillingPeriod == "Day") {
				$trial_period_days = $trial_period_days + (1 * $order->BillingFrequency * $trialOccurrences);		//daily
			} elseif($order->BillingPeriod == "Week") {
				$trial_period_days = $trial_period_days + (7 * $order->BillingFrequency * $trialOccurrences);	//weekly
			} else {
				$trial_period_days = $trial_period_days + (30 * $order->BillingFrequency * $trialOccurrences);	//assume monthly
			}
		} elseif(!empty($order->TrialBillingCycles)) {
			/*
				Let's set the subscription to the trial and give the user an "update" to change the sub later to full price (since v2.0)

				This will force TrialBillingCycles > 1 to act as if they were 1
			*/
			$new_user_updates = array();
			$new_user_updates[] = array(
				'when' => 'payment',
				'billing_amount' => $order->PaymentAmount,
				'cycle_period' => $order->BillingPeriod,
				'cycle_number' => $order->BillingFrequency
			);

			//now amount to equal the trial #s
			$amount = $order->TrialAmount;
			$amount_tax = $order->getTaxForPrice($amount);
			$amount = pmpro_round_price((float)$amount + (float)$amount_tax);
		}

		//create a plan
		try {
			$plan = array(
				"amount" => $amount * $currency_unit_multiplier,
				"interval_count" => $order->BillingFrequency,
				"interval" => strtolower($order->BillingPeriod),
				"trial_period_days" => $trial_period_days,
				"name" => $order->membership_name . " for order " . $order->code,
				"currency" => strtolower($pmpro_currency),
				"id" => $order->code
			);

			$plan = Stripe_Plan::create(apply_filters('pmpro_stripe_create_plan_array', $plan));
		} catch (Exception $e) {
			$order->error = __("Error creating plan with Stripe:", 'paid-memberships-pro' ) . $e->getMessage();
			$order->shorterror = $order->error;
			return false;
		}

		//before subscribing, let's clear out the updates so we don't trigger any during sub
		if(!empty($user_id)) {
			$old_user_updates = get_user_meta($user_id, "pmpro_stripe_updates", true);
			update_user_meta($user_id, "pmpro_stripe_updates", array());
		}

		if(empty($order->subscription_transaction_id) && !empty($this->customer['id'])) {
			$order->subscription_transaction_id = $this->customer['id'];
		}

		//subscribe to the plan
		try {
			$subscription = array("plan" => $order->code);
			$result = $this->customer->subscriptions->create(apply_filters('pmpro_stripe_create_subscription_array', $subscription));
		} catch (Exception $e) {
			//try to delete the plan
			$plan->delete();

			//give the user any old updates back
			if(!empty($user_id)) {
				update_user_meta($user_id, "pmpro_stripe_updates", $old_user_updates);
			}

			//return error
			$order->error = __("Error subscribing customer to plan with Stripe:", 'paid-memberships-pro' ) . $e->getMessage();
			$order->shorterror = $order->error;
			return false;
		}

		//delete the plan
		$plan = Stripe_Plan::retrieve($order->code);
		$plan->delete();

		//if we got this far, we're all good
		$order->status = "success";
		$order->subscription_transaction_id = $result['id'];

		//save new updates if this is at checkout
		if($checkout) {
			//empty out updates unless set above
			if(empty($new_user_updates)) {
				$new_user_updates = array();
			}

			//update user meta
			if(!empty($user_id)) {
				update_user_meta($user_id, "pmpro_stripe_updates", $new_user_updates);
			} else {
				//need to remember the user updates to save later
				global $pmpro_stripe_updates;
				$pmpro_stripe_updates = $new_user_updates;
				function pmpro_user_register_stripe_updates($user_id) {
					global $pmpro_stripe_updates;
					update_user_meta($user_id, "pmpro_stripe_updates", $pmpro_stripe_updates);
				}
				add_action("user_register", "pmpro_user_register_stripe_updates");
			}
		} else {
			//give them their old updates back
			update_user_meta($user_id, "pmpro_stripe_updates", $old_user_updates);
		}

		return true;
	}

	/**
	 * Helper method to save the subscription ID to make sure the membership doesn't get cancelled by the webhook
	 */
	static function ignoreCancelWebhookForThisSubscription($subscription_id, $user_id = NULL) {
		if(empty($user_id)) {
			global $current_user;
			$user_id = $current_user->ID;
		}

		$preserve = get_user_meta( $user_id, 'pmpro_stripe_dont_cancel', true );

		// No previous values found, init the array
		if ( empty( $preserve ) ) {
			$preserve = array();
		}

		// Store or update the subscription ID timestamp (for cleanup)
		$preserve[$subscription_id] = current_time( 'timestamp' );

		update_user_meta( $user_id, 'pmpro_stripe_dont_cancel', $preserve );
	}

	/**
	 * Helper method to process a Stripe subscription update
	 */
	static function updateSubscription($update, $user_id) {
		global $wpdb;

		//get level for user
		$user_level = pmpro_getMembershipLevelForUser($user_id);

		//get current plan at Stripe to get payment date
		$last_order = new MemberOrder();
		$last_order->getLastMemberOrder($user_id);
		$last_order->setGateway('stripe');
		$last_order->Gateway->getCustomer($last_order);

		$subscription = $last_order->Gateway->getSubscription($last_order);

		if(!empty($subscription)) {
			$end_timestamp = $subscription->current_period_end;

			//cancel the old subscription
			if(!$last_order->Gateway->cancelSubscriptionAtGateway($subscription, true)) {
				//throw error and halt save
				if ( !function_exists( 'pmpro_stripe_user_profile_fields_save_error' )) {
					//throw error and halt save
					function pmpro_stripe_user_profile_fields_save_error( $errors, $update, $user ) {
						$errors->add( 'pmpro_stripe_updates', __( 'Could not cancel the old subscription. Updates have not been processed.', 'paid-memberships-pro' ) );
					}

					add_filter( 'user_profile_update_errors', 'pmpro_stripe_user_profile_fields_save_error', 10, 3 );
				}

				//stop processing updates
				return;
			}
		}

		//if we didn't get an end date, let's set one one cycle out
		if(empty($end_timestamp)) {
			$end_timestamp = strtotime("+" . $update['cycle_number'] . " " . $update['cycle_period'], current_time('timestamp'));
		}

		//build order object
		$update_order = new MemberOrder();
		$update_order->setGateway('stripe');
		$update_order->user_id = $user_id;
		$update_order->membership_id = $user_level->id;
		$update_order->membership_name = $user_level->name;
		$update_order->InitialPayment = 0;
		$update_order->PaymentAmount = $update['billing_amount'];
		$update_order->ProfileStartDate = date_i18n("Y-m-d", $end_timestamp);
		$update_order->BillingPeriod = $update['cycle_period'];
		$update_order->BillingFrequency = $update['cycle_number'];

		//need filter to reset ProfileStartDate
		add_filter('pmpro_profile_start_date', function( $startdate, $order ) use ( $update_order ) {
			return "{$update_order->ProfileStartDate}T0:0:0";
		}, 10, 2);

		//update subscription
		$update_order->Gateway->subscribe($update_order, false);

		//update membership
		$sqlQuery = "UPDATE $wpdb->pmpro_memberships_users
						SET billing_amount = '" . esc_sql($update['billing_amount']) . "',
							cycle_number = '" . esc_sql($update['cycle_number']) . "',
							cycle_period = '" . esc_sql($update['cycle_period']) . "',
							trial_amount = '',
							trial_limit = ''
						WHERE user_id = '" . esc_sql($user_id) . "'
							AND membership_id = '" . esc_sql($last_order->membership_id) . "'
							AND status = 'active'
						LIMIT 1";

		$wpdb->query($sqlQuery);

		//save order so we know which plan to look for at stripe (order code = plan id)
		$update_order->status = "success";
		$update_order->saveOrder();
	}

	/**
	 * Helper method to update the customer info via getCustomer
	 *
	 * @since 1.4
	 */
	function update(&$order) {
		//we just have to run getCustomer which will look for the customer and update it with the new token
		$result = $this->getCustomer($order);

		if(!empty($result)) {
			return true;
		} else {
			return false;	//couldn't find the customer
		}
	}

	/**
	 * Cancel a subscription at Stripe
	 *
	 * @since 1.4
	 */
	function cancel(&$order, $update_status = true) {
		global $pmpro_stripe_event;

		//no matter what happens below, we're going to cancel the order in our system
		if($update_status) {
			$order->updateStatus("cancelled");
		}

		//require a subscription id
		if(empty($order->subscription_transaction_id)) {
			return false;
		}	

		//find the customer
		$result = $this->getCustomer($order);

		if(!empty($result)) {
			//find subscription with this order code
			$subscription = $this->getSubscription($order);

			if(!empty($subscription) 
				&& ( empty( $pmpro_stripe_event ) || empty( $pmpro_stripe_event->type ) || $pmpro_stripe_event->type != 'customer.subscription.deleted' ) ) {
				if($this->cancelSubscriptionAtGateway($subscription)) {
					//we're okay, going to return true later
				} else {
					$order->error = __("Could not cancel old subscription.", 'paid-memberships-pro' );
					$order->shorterror = $order->error;

					return false;
				}
			}

			/*
				Clear updates for this user. (But not if checking out, we would have already done that.)
			*/
			if(empty($_REQUEST['submit-checkout'])) {
				update_user_meta($order->user_id, "pmpro_stripe_updates", array());
			}

			return true;
		} else {
			$order->error = __("Could not find the customer.", 'paid-memberships-pro' );
			$order->shorterror = $order->error;
			return false;	//no customer found
		}
	}

	/**
	 * Helper method to cancel a subscription at Stripe and also clear up any upaid invoices.
	 *
	 * @since 1.8
	 */
	function cancelSubscriptionAtGateway($subscription, $preserve_local_membership = false) {
		// Check if a valid sub.
		if( empty( $subscription) || empty( $subscription->id ) ) {
			return false;
		}

		// If this is already cancelled, return true.
		if( !empty( $subscription->canceled_at ) ) {
			return true;
		}

		// Make sure we get the customer for this subscription.
		$order = new MemberOrder();
		$order->getLastMemberOrderBySubscriptionTransactionID($subscription->id);

		// No order?
		if(empty($order)) {
			//lets cancel anyway, but this is suspicious
			$r = $subscription->cancel();

			return true;
		}

		// Okay have an order, so get customer so we can cancel invoices too
		$this->getCustomer($order);

		// Get open invoices.
		$invoices = $this->customer->invoices();
		$invoices = $invoices->all();

		// Found it, cancel it.
		try {
			// Find any open invoices for this subscription and forgive them.
			if(!empty($invoices)) {
				foreach($invoices->data as $invoice) {
					if(!$invoice->closed && $invoice->subscription == $subscription->id) {
						$invoice->closed = true;
						$invoice->save();
					}
				}
			}

			// Sometimes we don't want to cancel the local membership when Stripe sends its webhook.
			if($preserve_local_membership) {
				PMProGateway_stripe::ignoreCancelWebhookForThisSubscription($subscription->id, $order->user_id);
			}

			// Cancel
			$r = $subscription->cancel();

			return true;
		} catch(Exception $e) {
			return false;
		}
	}

	/**
	 * Filter pmpro_next_payment to get date via API if possible
	 *
	 * @since 1.8.6
	*/
	static function pmpro_next_payment($timestamp, $user_id, $order_status) {
		//find the last order for this user
		if(!empty($user_id)) {
			//get last order
			$order = new MemberOrder();
			$order->getLastMemberOrder($user_id, $order_status);

			//check if this is a Stripe order with a subscription transaction id
			if(!empty($order->id) && !empty($order->subscription_transaction_id) && $order->gateway == "stripe") {
				//get the subscription and return the current_period end or false
				$subscription = $order->Gateway->getSubscription($order);

				if( !empty( $subscription ) ) {
					$customer = $order->Gateway->getCustomer();
					if( ! $customer->delinquent && ! empty ( $subscription->current_period_end ) ) {
						return $subscription->current_period_end;
					} elseif ( $customer->delinquent && ! empty( $subscription->current_period_start ) ) {
						return $subscription->current_period_start;
					} else {
						return $false;  // shouldn't really get here
					}
				}
			}
		}

		return $timestamp;
	}

	/**
	 * Refund a payment or invoice
	 * @param  object &$order           Related PMPro order object.
	 * @param  string $transaction_id   Payment or Invoice id to void.
	 * @return bool                     True or false if the void worked
	 */
	function void(&$order, $transaction_id = null) {
		//stripe doesn't differentiate between voids and refunds, so let's just pass on to the refund function
		return $this->refund($order, $transaction_id);
	}

	/**
	 * Refund a payment or invoice
	 * @param  object &$order         Related PMPro order object.
	 * @param  string $transaction_id Payment or invoice id to void.
	 * @return bool                   True or false if the refund worked.
	 */
	function refund(&$order, $transaction_id = NULL) {
		//default to using the payment id from the order
		if(empty($transaction_id) && !empty($order->payment_transaction_id)) {
			$transaction_id = $order->payment_transaction_id;
		}

		//need a transaction id
		if(empty($transaction_id)) {
			return false;
		}

		//if an invoice ID is passed, get the charge/payment id
		if(strpos($transaction_id, "in_") !== false) {
			$invoice = Stripe_Invoice::retrieve($transaction_id);

			if(!empty($invoice) && !empty($invoice->charge)) {
				$transaction_id = $invoice->charge;
			}
		}

		//get the charge
		try {
			$charge = Stripe_Charge::retrieve($transaction_id);
		} catch (Exception $e) {
			$charge = false;
		}

		//can't find the charge?
		if(empty($charge)) {
			$order->status = "error";
			$order->errorcode = "";
			$order->error = "";
			$order->shorterror = "";

			return false;
		}

		//attempt refund
		try {
			$refund = $charge->refund();
		} catch (Exception $e) {
			//$order->status = "error";
			$order->errorcode = true;
			$order->error = __("Error: ", 'paid-memberships-pro' ) . $e->getMessage();
			$order->shorterror = $order->error;
			return false;
		}

		if($refund->status == "succeeded") {
			$order->status = "refunded";
			$order->saveOrder();

			return true;
		} else  {
			$order->status = "error";
			$order->errorcode = true;
			$order->error = sprintf(__("Error: Unkown error while refunding charge #%s", 'paid-memberships-pro' ), $transaction_id);
			$order->shorterror = $order->error;

			return false;
		}
	}
}