<?php
/** 
 * @package     VikAppointments
 * @subpackage  core
 * @author      E4J s.r.l.
 * @copyright   Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 * @link        https://vikwp.com
 */

// No direct access
defined('ABSPATH') or die('No script kiddies please!');

VAPLoader::import('libraries.mvc.controllers.admin');

/**
 * VikAppointments packages order controller.
 *
 * @since 	1.7
 */
class VikAppointmentsControllerPackagesorder extends VAPControllerAdmin
{
	/**
	 * This is the end-point used by the gateway to validate a payment transaction.
	 * It is mandatory to send the following parameters (via GET or POST) in order to
	 * retrieve the correct details of the order transaction.
	 *
	 * @param 	integer  ordnum  The order number (ID).
	 * @param 	string 	 ordkey  The order key (SID).
	 *
	 * @return 	void
	 */
	public function notifypayment()
	{
		$dispatcher = VAPFactory::getEventDispatcher();

		$app   = JFactory::getApplication();
		$input = $app->input;
			
		$oid = $input->getUint('ordnum');
		$sid = $input->getAlnum('ordkey');

		// Get order details (filter by ID and SID).
		// In case the order doesn't exist, an exception will be thrown.
		VAPLoader::import('libraries.order.factory');
		$order = VAPOrderFactory::getPackages($oid, null, array('sid' => $sid));

		/**
		 * This event is triggered every time a payment tries to validate a transaction made.
		 *
		 * DOES NOT trigger in case the order doesn't exist.
		 *
		 * @param 	mixed 	$order  The details of the purchased package.
		 *
		 * @return 	void
		 *
		 * @since 	1.6
		 */
		$dispatcher->trigger('onReceivePaymentNotification', array($order));
		
		// build return and error URL
		$return_url = "index.php?option=com_vikappointments&view=packagesorder&ordnum={$oid}&ordkey={$sid}";
		$error_url  = "index.php?option=com_vikappointments&view=packagesorder&ordnum={$oid}&ordkey={$sid}";
		
		/**
		 * If we are trying to validate an order already paid/confirmed, auto-redirect to
		 * the return URL instead of throwing an exception.
		 * 
		 * @since 1.7.1
		 */
		if ($order->statusRole == 'APPROVED')
		{
			$this->setRedirect(JRoute::rewrite($return_url, false));
			return;
		}

		// make sure the order can be paid
		if ($order->statusRole != 'PENDING')
		{
			// status not allowed
			throw new Exception('The current status of the order does not allow any payments.', 403);
		}

		if (!$order->payment)
		{
			// payment method not found
			throw new Exception('The selected payment does not exist', 404);
		}

		// reload payment details to access the parameters
		$payment = JModelVAP::getInstance('payment')->getItem($order->payment->id);

		$vik = VAPApplication::getInstance();

		$config = VAPFactory::getConfig();
			
		// fetch transaction data	
		$paymentData = array();

		/**
		 * The payment URLs are correctly routed for external usage.
		 *
		 * @since 1.6
		 */
		$return_url = $vik->routeForExternalUse($return_url, false);
		$error_url  = $vik->routeForExternalUse($error_url, false);

		/**
		 * Include the Notification URL in both the PLAIN and ROUTED formats.
		 *
		 * @since 1.7
		 */
		$notify_url = "index.php?option=com_vikappointments&task=packagesorder.notifypayment&ordnum={$oid}&ordkey={$sid}";

		// subtract amount already paid
		$total_to_pay = max(array(0, $order->totals->gross - $order->totals->paid));
	
		$paymentData['type']                 = 'packages';
		$paymentData['action']               = 'validate';
		$paymentData['oid']                  = $order->id;
		$paymentData['sid']                  = $order->sid;
		$paymentData['attempt']              = $order->payment_attempt;
		$paymentData['transaction_name']     = JText::sprintf('VAPTRANSACTIONNAMEPACK', $config->get('agencyname'));
		$paymentData['transaction_currency'] = $config->get('currencyname');
		$paymentData['currency_symb']        = $config->get('currencysymb');
		$paymentData['tax']                  = 0;
		$paymentData['return_url']           = $return_url;
		$paymentData['error_url']            = $error_url;
		$paymentData['notify_url']           = $vik->routeForExternalUse($notify_url, false);
		$paymentData['notify_url_plain']     = JUri::root() . $notify_url;
		$paymentData['total_to_pay']         = $total_to_pay;
		$paymentData['total_net_price']      = $total_to_pay;
		$paymentData['total_tax']            = 0;
		$paymentData['payment_info']         = $payment;
		$paymentData['details'] = array(
			'purchaser_nominative' => $order->purchaser_nominative,
			'purchaser_mail'       => $order->purchaser_mail,
			'purchaser_phone'      => $order->purchaser_phone,
		);

		/**
		 * Added support for customer billing details.
		 *
		 * @since 1.7
		 */
		$paymentData['billing'] = $order->billing;

		/**
		 * Trigger event to manipulate the payment details.
		 *
		 * @param 	array 	&$order   The transaction details.
		 * @param 	array 	&$params  The payment configuration array.
		 *
		 * @return 	void
		 *
		 * @since 	1.6
		 */
		$dispatcher->trigger('onInitPaymentTransaction', array(&$paymentData, &$payment->params));

		/**
		 * Instantiate the payment using the platform handler.
		 *
		 * @since 1.6.3
		 */
		$obj = $vik->getPaymentInstance($payment->file, $paymentData, $payment->params);
		
		try
		{
			// validate payment transaction
			$result = $obj->validatePayment();
		}
		catch (Exception $e)
		{
			// catch any exceptions that might have been thrown by the gateway
			$result = [];
			$result['verified'] = 0;
			$result['log']      = $e->getMessage();
		}

		// get order model
		$model = JModelVAP::getInstance('packorder');
		
		// successful response
		if ($result['verified'])
		{
			if (!empty($result['tot_paid']))
			{
				// increase total amount paid
				$order->totals->paid += (float) $result['tot_paid'];
			}

			if ($order->totals->paid >= $order->totals->gross)
			{
				// the whole amount has been paid, use the apposite PAID status
				$order->status = JHtml::fetch('vaphtml.status.paid', 'packages', 'code');
			}
			else
			{
				// a deposit have been left, use CONFIRMED status
				$order->status = JHtml::fetch('vaphtml.status.confirmed', 'packages', 'code');
			}

			// prepare data to save
			$data = array(
				'id'             => $order->id,
				'status'         => $order->status,
				'status_comment' => 'VAP_STATUS_CHANGED_FROM_PAY',
				'tot_paid'       => $order->totals->paid,
				'paid'           => $order->paid,
			);

			$model->save($data);
			
			$mailOptions = array();
			// validate e-mail rules before sending
			$mailOptions['check'] = true;

			// send e-mail notification to the customer
			$model->sendEmailNotification($order->id, $mailOptions);

			// send e-mail notification to the administrator(s)
			$mailOptions['client'] = 'packadmin';
			$model->sendEmailNotification($order->id, $mailOptions);

			// try to auto-generate the invoice
			VikAppointments::generateInvoice($order->id, 'packages');

			/**
			 * Trigger event after the validation of a successful transaction.
			 *
			 * @param 	array  $order  The transaction details.
			 * @param 	array  $args   The response array.
			 *
			 * @return 	void
			 *
			 * @since 	1.6
			 */
			$dispatcher->trigger('onSuccessPaymentTransaction', array($paymentData, $result));
		}
		// failure response
		else
		{
			// check if the payment registered any logs
			if (!empty($result['log']))
			{
				$text = array(
					'Order #' . $order->id . '-' . $order->sid . ' (Package)',
					nl2br($result['log']),
				);

				// send error logs to administrator(s)
				VikAppointments::sendAdminMailPaymentFailed($order->id, $text);

				// get current date and time
				$timeformat = preg_replace("/:i/", ':i:s', $config->get('timeformat'));
				$now = JHtml::fetch('date', 'now', $config->get('dateformat') . ' ' . $timeformat, $app->get('offset', 'UTC'));

				// build log string
				$log  = str_repeat('-', strlen($now) + 4) . "\n";
				$log .= "| $now |\n";
				$log .= str_repeat('-', strlen($now) + 4) . "\n";
				$log .= "\n" . $result['log'];

				if (!empty($order->log))
				{
					// always prepend new logs at the beginning
					$log = $log . "\n\n" . $order->log;
				}

				// prepare save data
				$data = array(
					'id'              => $order->id,
					'log'             => $log,
					'payment_attempt' => ++$order->payment_attempt,
				);

				// update order logs
				$model->save($data);
			}

			/**
			 * Trigger event after the validation of a failed transaction.
			 *
			 * @param 	array  $order  The transaction details.
			 * @param 	array  $args   The response array.
			 *
			 * @return 	void
			 *
			 * @since 	1.6
			 */
			$dispatcher->trigger('onFailPaymentTransaction', array($paymentData, $result));
		}

		// check whether the payment instance supports a method
		// to be executed after the validation
		if (method_exists($obj, 'afterValidation'))
		{
			$obj->afterValidation($result['verified'] ? 1 : 0);
		}
	}
}
