<?php
/**
 * BoxBilling
 *
 * LICENSE
 *
 * This source file is subject to the license that is bundled
 * with this package in the file LICENSE.txt
 * It is also available through the world-wide-web at this URL:
 * http://www.boxbilling.com/LICENSE.txt
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@boxbilling.com so we can send you a copy immediately.
 *
 * @copyright Copyright (c) 2010-2012 BoxBilling (http://www.boxbilling.com)
 * @license   http://www.boxbilling.com/LICENSE.txt
 * @version   $Id$
 */
class Payment_Adapter_PayPalEmail extends Payment_AdapterAbstract
{
    public function init()
    {
        if(!function_exists('curl_exec')) {
            throw new Payment_Exception('PHP Curl extension must be enabled in order to use PayPal gateway');
        }
        
        if(!$this->getParam('email')) {
            throw new Payment_Exception('Payment gateway "PayPal" is not configured properly. Please update configuration parameter "PayPal Email address" at "Configuration -> Payments".');
        }
    }

    public static function getConfig()
    {
        return array(
            'supports_one_time_payments'   =>  true,
            'supports_subscriptions'     =>  true,
            'description'     =>  'Enter your PayPal email to start accepting payments by PayPal.',
            'form'  => array(
                'email' => array('text', array(
                            'label' => 'PayPal email address for payments',
                            'validators'=>array('EmailAddress'),
                    ),
                 ),
            ),
        );
    }

    /**
     * Return payment gateway type
     * @return string
     */
    public function getType()
    {
        return Payment_AdapterAbstract::TYPE_FORM;
    }

    /**
     * Return payment gateway type
     * @return string
     */
    public function getServiceUrl()
    {
        if($this->testMode) {
            return 'https://www.sandbox.paypal.com/cgi-bin/webscr';
        }
		return 'https://www.paypal.com/cgi-bin/webscr';
    }

    /**
     * Init call to webservice or return form params
     * @param Payment_Invoice $invoice
     */
    public function singlePayment(Payment_Invoice $invoice)
    {
        $data = array();
        $data['item_name']          = $invoice->getTitle();
        $data['item_number']        = $invoice->getNumber();
        $data['no_shipping']        = '1';
        $data['no_note']            = '1';
        $data['currency_code']      = $invoice->getCurrency();
        $data['rm']                 = '2';
        $data['return']             = $this->getParam('return_url');
        $data['cancel_return']      = $this->getParam('cancel_url');
        $data['notify_url']         = $this->getParam('notify_url');
        $data['business']           = $this->getParam('email');
        $data['cmd']                = '_xclick';
        $data['amount']             = $this->moneyFormat($invoice->getTotal(), $invoice->getCurrency());
        $data['tax']                = $this->moneyFormat($invoice->getTax(), $invoice->getCurrency());
        $data['bn']                 = "PP-BuyNowBF";
        $data['charset']            = "utf-8";

        return $data;
    }

    /**
     * Perform recurent payment
     * @param Payment_Invoice $invoice
     * @see https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_Appx_websitestandard_htmlvariables#id08A6HI00JQU
     */
    public function recurrentPayment(Payment_Invoice $invoice)
    {
        $recurrenceInfo = $invoice->getSubscription();
        if(!$recurrenceInfo instanceof Payment_Invoice_Subscription) {
            throw new Exception('Subscription info is not present for payment invoice');
        }
        
        $data = array();
        $data['item_name']          = $invoice->getTitle();
        $data['item_number']        = $invoice->getNumber();
        $data['no_shipping']        = '1';
        $data['no_note']            = '1'; // Do not prompt payers to include a note with their payments. Allowable values for Subscribe buttons:
        $data['currency_code']      = $invoice->getCurrency();
        $data['return']             = $this->getParam('return_url');
        $data['cancel_return']      = $this->getParam('cancel_url');
        $data['notify_url']         = $this->getParam('notify_url');
        $data['business']           = $this->getParam('email');

        $data['cmd']                = '_xclick-subscriptions';
        $data['rm']                 = '2';

        $data['invoice_id']         = $invoice->getNumber();

        // 1 period Trial data
        //$data['a1']                 = $this->moneyFormat($invoice->totalAmount()); // Trial period 1 price. For a free trial period, specify 0.
        //$data['p1']                 = $invoice->firstPeriod(); // Trial period 1 duration. Required if you specify a1. Specify an integer value in the allowable range for the units of duration that you specify with t1.
        //$data['t1']                 = $invoice->firstPaymentTerm();

        // Recurrence info
        $data['a3']                 = $this->moneyFormat($recurrenceInfo->getAmount(), $invoice->getCurrency()); // Regular subscription price.
        $data['p3']                 = $this->_getCycle($invoice); //Subscription duration. Specify an integer value in the allowable range for the units of duration that you specify with t3.

        /**
         * t3: Regular subscription units of duration. Allowable values:
         *  D – for days; allowable range for p3 is 1 to 90
         *  W – for weeks; allowable range for p3 is 1 to 52
         *  M – for months; allowable range for p3 is 1 to 24
         *  Y – for years; allowable range for p3 is 1 to 5
         */
        $data['t3']                 = $this->_getUnits($invoice);

        $data['src']                = 1; //Recurring payments. Subscription payments recur unless subscribers cancel their subscriptions before the end of the current billing cycle or you limit the number of times that payments recur with the value that you specify for srt.
//        $data['srt']                = 1; //Recurring times. Number of times that subscription payments recur. Specify an integer above 1. Valid only if you specify src="1".
        $data['sra']                = 1; //Reattempt on failure. If a recurring payment fails, PayPal attempts to collect the payment two more times before canceling the subscription.
        $data['charset']			= 'UTF-8'; //Sets the character encoding for the billing information/log-in page, for the information you send to PayPal in your HTML button code, and for the information that PayPal returns to you as a result of checkout processes initiated by the payment button. The default is based on the character encoding settings in your account profile.

        //client data
        $buyer = $invoice->getBuyer();
        $data['address1']			= $buyer->getAddress();
        $data['city']				= $buyer->getCity();
        $data['email']				= $buyer->getEmail();
        $data['first_name']			= $buyer->getFirstName();
        $data['last_name']			= $buyer->getLastName();
        $data['zip']				= $buyer->getZip();
        $data['state']				= $buyer->getState();
        $data['charset']            = "utf-8";
        return $data;
    }

    /**
     * Handle IPN and return response object
     * @return Payment_Transaction
     */
    public function getTransaction($data, Payment_Invoice $invoice)
    {
        $ipn = $data['post'];
        $response = new Payment_Transaction();
        $type = isset($ipn['txn_type']) ? $ipn['txn_type'] : null;
        
        switch ($type)
        {
            case 'cart':
            case 'express_checkout':
            case 'subscr_payment':
            case 'virtual_terminal':
            case 'send_money':
            case 'web_accept':
                $response->setType(Payment_Transaction::TXTYPE_PAYMENT);
                break;
            
            case 'subscr_signup':
                $response->setType(Payment_Transaction::TXTYPE_SUBSCR_CREATE);
                $response->setSubscriptionId($ipn['subscr_id']);
                if(isset($ipn['ipn_track_id'])) {
                    $response->setId($ipn['ipn_track_id']);
                }
                if(isset($ipn['mc_amount3'])) {
                    $response->setAmount($ipn['mc_amount3']);
                }
                break;

            case 'subscr_failed':
            case 'subscr_eot':
            case 'subscr_cancel':
                $response->setType(Payment_Transaction::TXTYPE_SUBSCR_CANCEL);
                $response->setSubscriptionId($ipn['subscr_id']);
                if(isset($ipn['ipn_track_id'])) {
                    $response->setId($ipn['ipn_track_id']);
                }
                break;

            default:
                $response->setType(Payment_Transaction::TXTYPE_UNKNOWN);
                break;
        }

        if(isset($ipn['txn_id'])) {
            $response->setId($ipn['txn_id']);
        }

        if(isset($ipn['mc_gross'])) {
            $response->setAmount($ipn['mc_gross']);
        }

        if(isset($ipn['mc_currency'])) {
            $response->setCurrency($ipn['mc_currency']);
        }
        
        if (isset($ipn['payment_status']) ) {
            if($ipn["payment_status"] == "Refunded" || $ipn["payment_status"] == "Reversed") {
                $response->setType(Payment_Transaction::TXTYPE_REFUND);
                $response->setStatus(Payment_Transaction::STATUS_COMPLETE);
            }

            if($ipn['payment_status'] == 'Pending') {
                $response->setStatus(Payment_Transaction::STATUS_PENDING);
            }

            if($ipn['payment_status'] == 'Completed') {
                $response->setStatus(Payment_Transaction::STATUS_COMPLETE);
            }
        }

        return $response;
    }

    /**
     * Check if Ipn is valid
     */
    public function isIpnValid($data, Payment_Invoice $invoice)
    {
        // use http_raw_post_data instead of post due to encoding
        parse_str($data['http_raw_post_data'], $post);
		$req = 'cmd=_notify-validate';
		foreach ($post as $key => $value) {
			$value = urlencode(stripslashes($value));
			$req .= "&$key=$value";
		}
		$ret = $this->download($this->getServiceURL(), $req);
		return $ret == 'VERIFIED';
    }

    /**
     * Converts duration units to paypal format
     * @param Payment_Invoice $invoice
     * @throws Payment_Exception
     * @return string
     */
    private function _getUnits(Payment_Invoice $invoice) {
    	$unit = $invoice->getSubscription()->getUnit();
        $unit = strtolower($unit);
    	switch($unit) {
    		case 'd': 	return 'D'; 	break;
    		case 'm':	return 'M';		break;
    		case 'q':	return 'M';		break;
    		case 'b':	return 'M';		break;
    		case 'y':	return 'Y';		break;
    		case 'a':	return 'Y';		break;
    		case 'bia': return 'Y';		break;
    		case 'tria':return 'Y';		break;
    		default:
    			throw new Payment_Exception('Paypal accepts only these units of subscription duration: day, month, year');
    		break;
    	}
    }

    /**
     *
     * Checks if subscription duration is in allowed boundaries
     * @param Payment_Invoice $invoice
     * @throws Payment_Exception
     * @return integer
     */
    private function _getCycle(Payment_Invoice $invoice) {
    	if ($c = $invoice->getSubscription()->getCycle()) {
    		return $c;
    	}
    	$unit = $invoice->getSubscription()->getUnit();
    	switch ($unit) {
    		case 'm':	return 1;	break;
    		case 'q':	return 3;	break;
    		case 'b':	return 6;	break;
    		case 'y':	return 1;	break;
    		case 'a':	return 1;	break;
    		case 'bia': return 2;	break;
    		case 'tria':return 3;	break;
    	}

    	return 0;
    }
    
    public function moneyFormat($amount, $currency = null)
    {
        //HUF currency do not accept decimal values
        if($currency == 'HUF') {
            return number_format($amount, 0);
        }
        return number_format($amount, 2, '.', '');
    }
    
	/**
	 * Post variables to url and download result
	 * @param string $url
	 * @param mixed $post_vars
	 * @return string $data
	*/
	private function download($url, $post_vars = false, $phd = array(), $contentType = 'application/x-www-form-urlencoded')
    {
		$post_contents = '';
		if ($post_vars) {
			if (is_array($post_vars)) {
				foreach($post_vars as $key => $val) {
					$post_contents .= ($post_contents ? '&' : '').urlencode($key).'='.urlencode($val);
				}
			} else {
				$post_contents = $post_vars;
			}
		}

		$uinf = parse_url($url);
		$host = $uinf['host'];
		$path = $uinf['path'];
		$path .= (isset($uinf['query']) && $uinf['query']) ? ('?'.$uinf['query']) : '';

		$headers = Array(
			($post_contents ? 'POST' : 'GET')." $path HTTP/1.1",
			"Host: $host",
		);
		if ($phd) {
			if (!is_array($phd)) {
				$headers[count($headers)] = $phd;
			} else {
				$next = count($headers);
				$count = count($phd);
				for ($i = 0; $i < $count; $i++) { $headers[$next + $i] = $phd[$i]; }
			}
		}
		if ($post_contents) {
			$headers[] = "Content-Type: $contentType";
			$headers[] = 'Content-Length: '.strlen($post_contents);
		}

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
		curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_TIMEOUT, 600);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

		// Apply the XML to our curl call
		if ($post_contents) {
			curl_setopt($ch, CURLOPT_POST, 1);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $post_contents);
		}

		$data = curl_exec($ch);
		if (curl_errno($ch)) return false;
		curl_close($ch);

		return $data;
	}
}
