<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * This class provides PAYPAL support
 *
 * @package    Checkout
 * @category   Plugins
 * @author     Deon George
 * @copyright  (c) 2009-2013 Open Source Billing
 * @license    http://dev.osbill.net/license.html
 */
abstract class Checkout_Plugin_Paypal extends Checkout_Plugin {
	protected $url_prod = 'www.paypal.com';
	protected $url_test = 'www.sandbox.paypal.com';
	private $ipn_test = '173.0.82.126';
	protected $email_prod = ''; // @todo This should be in the DB
	protected $email_test = ''; // @todo This should be in the DB
	protected $test_mode = FALSE;

	protected $curlopts = array(
		CURLOPT_CONNECTTIMEOUT => 60,
		CURLOPT_FAILONERROR => TRUE,
		CURLOPT_FOLLOWLOCATION => FALSE,
		CURLOPT_HEADER => FALSE,
		CURLOPT_HTTPPROXYTUNNEL => FALSE,
		CURLOPT_RETURNTRANSFER => TRUE,
		CURLOPT_TIMEOUT => 30,
		CURLOPT_SSL_VERIFYHOST => FALSE,
		CURLOPT_SSL_VERIFYPEER => FALSE,
		CURLOPT_VERBOSE => FALSE,
	);

	/**
	 * User return from Paypal after payment
	 */
	public function after(Cart $co) {
		SystemMessage::factory()
			->title(_('Payment Processing'))
			->type('info')
			->body(_('Thank you for your payment with paypal. It will be processed and applied to your cart items automatically in due course.'));

		HTTP::redirect(URL::link('user','welcome'));
	}

	/**
	 * User cancelled from Paypal and returned
	 */
	public function cancel(Cart $co) {
		SystemMessage::add(array(
			'title'=>_('Payment Cancelled'),
			'type'=>'info',
			'body'=>sprintf('Payment with Paypal was cancelled at your request.'),
		));

		HTTP::redirect('cart');
	}

	/**
	 * Paypal payment notification and verification
	 */
	public function notify(Model_Checkout_Notify $cno) {
		$debug_mode = Kohana::$config->load('debug')->checkout_notify;

		// If testing
		if (! $cno->status OR $cno->processed OR ($debug_mode AND Request::$client_ip == $this->ipn_test))
			return _('Thank you');

		$co = Cart::instance(isset($cno->data['custom']) ? $cno->data['custom'] : '');

		if (! $co->contents())
			return _('Thank you!');

		if (! $debug_mode) {
			$request = Request::factory(sprintf('https://%s/cgi-bin/webscr',isset($cno->data['test_ipn']) ? $this->url_test : $this->url_prod))
				->method('POST');

			$request->client()->options(Arr::merge($this->curlopts,array(
				CURLOPT_POSTFIELDS => Arr::merge(array('cmd'=>'_notify-validate'),$cno->data),
			)));

			$response = $request->execute();
		}

		switch ($debug_mode ? 'VERIFIED' : $response->body()) {
			case 'VERIFIED':
				// Verify that the IPN is for us.
				// @todo This should be in the DB.
				if ($cno->data['business'] == ($this->test_mode ? $this->email_test : $this->email_prod)) {

					switch ($cno->data['payment_status']) {
						case 'Completed':
							// Our cart items total.
							$total = $co->total();
							$po = ORM::factory('Payment');

							// Does the payment cover the cart total?
							if ($this->co->fee_passon AND $cno->data['mc_gross'] === (string)($total+$this->co->fee($total))) {
								// Store the amounts in an array, so we can pro-rata the fee to each item.
								$amts = array();

								// Add the fee to each item (pro-rated)
								for ($c=1;$c<=$cno->data['num_cart_items'];$c++) {
									// The payment fee - there should only be 1 of these, and it should be the last item.
									// We assume fees are added to $po->items() which are invoices.
									if (preg_match('/^0:/',$cno->data['item_number'.$c])) {
										$i = $j = 0;
										foreach ($po->subitems() as $pio) {
											$io = ORM::factory('Invoice',$pio->invoice_id);

											$iio = ORM::factory('Invoice_Item');
											$iio->quantity = 1;
											$iio->module_id = $cno->mid()->id;
											$iio->module_ref = $cno->id;
											$iio->item_type = 125; // Payment Fee
											$iio->price_base = (++$j==count($amts)) ? $cno->data['mc_gross_'.$c]-$i : Currency::round($pio->alloc_amt/array_sum($amts)*$cno->data['mc_gross_'.$c]);
											$iio->date_start = $iio->date_stop = time();

											$io->subitem_add($iio,$io->account->country,TRUE);

											// @todo Validate Save
											$io->save();

											$pio->alloc_amt = ($pio->alloc_amt+$iio->total() > $pio->invoice->total()) ? $pio->alloc_amt : $pio->alloc_amt+$iio->total();
											$i += $iio->price_base;
										}

									} elseif (is_numeric($cno->data['item_number'.$c])) {
										array_push($amts,$cno->data['mc_gross_'.$c]);
										$cio = ORM::factory('cart',$cno->data['item_number'.$c]);

										if ($cio->loaded())
											switch ($cio->motype()) {
												case 'invoice':
													// Validate we are all the same account
													// @todo Need to handle if the cart has more than 1 account.
													if (! $po->account_id AND $cio->mo()->account_id) {
														$po->account_id = $cio->mo()->account_id;

													} elseif ($po->account_id != $cio->mo()->account_id) {
														throw new Kohana_Exception('Unable to handle payments for multiple accounts');

													}

													$pio = $po->payment_item;
													$pio->alloc_amt = $cno->data['mc_gross_'.$c];
													$pio->invoice_id = $cio->module_item;
													$po->add_item($pio);

													break;

												default:
													throw new Kohana_Exception('Dont know how to handle :item',array(':item',$cio->motype()));
											}

									// Dont know how to handle this item.
									} else {
										// @todo
									}
								}

								// @todo Validate Save
								$po->account_id = $cio->mo()->account_id;
								$po->fees_amt = $cno->data['mc_fee'];
								$po->total_amt = $cno->data['mc_gross'];
								$po->date_payment = strtotime($cno->data['payment_date']);
								$po->checkout_id = $this->co->id;
								$po->notes = $cno->data['txn_id'];

								if (! $po->save())
									$cno->result = array('msg'=>'Failed to save PO?','po'=>$po);

								// Clear the cart
								if (! $debug_mode)
									$co->delete();

							} elseif (! $this->co->fee_passon AND $cno->data['mc_gross']-$cno->data['mc_fee'] == $total) {
								// Ignore the fee

							} else {
								$cno->result = array(
									'msg'=>'IPN doesnt match cart total',
									't'=>$total,
									'tt'=>(string)($total+$this->co->fee($total)),
									'g'=>$cno->data['mc_gross'],
									'fpo'=>$this->co->fee_passon,
									't1'=>($cno->data['mc_gross'] === (string)($total+$this->co->fee($total))),
								);

								// If there is more than 1 item in the cart, we'll leave it to an admin to process.
								if ($cno->data['num_cart_items'] == 1) {
								} else {
									// @todo - add the payment, with no payment items
								}
							}

							break;

						case 'Refunded':
						default:
							throw new Kohana_Exception('Unable to handle payments of type :type',array(':type'=>$cno->data['payment_status']));
					}

				} else {
					$cno->status = FALSE;
				}

				break;

			case 'INVALID':
			default:
				$cno->status = FALSE;
		}

		if (! $debug_mode)
			$cno->processed = TRUE;

		$cno->save();

		return _('Processed, thank you!');
	}
}
?>
