<?php
if (!defined("ROOT_PATH"))
{
	header("HTTP/1.1 403 Forbidden");
	exit;
}
class pjStripe extends pjStripeAppController
{
	protected static $logPrefix = "Payments | pjStripe plugin<br>";

	protected static $paymentMethod = 'stripe';

	/**
	 * Currencies Stripe expects in their base unit (i.e. NOT multiplied by 100).
	 * See https://stripe.com/docs/currencies#zero-decimal
	 */
	protected static $zeroDecimal = array(
		'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga',
		'pyg', 'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf',
	);

	/**
	 * Convert a human amount (e.g. 19.90) into the smallest currency unit Stripe expects.
	 */
	protected static function toMinorUnits($amount, $currency)
	{
		if (in_array(strtolower($currency), self::$zeroDecimal))
		{
			return (int) round($amount);
		}

		return (int) round($amount * 100);
	}

	public static function getPaymentLocale($localeId = null)
	{
		$locale = 'en'; // English (default)

		if ($localeId && $locale_arr = pjLocaleModel::factory()->select('language_iso')->find($localeId)->getData())
		{
			$lang = strtok($locale_arr['language_iso'], '-');
			if (strpos($locale_arr['language_iso'], '-FI'))
			{
				$lang = 'fi';
			}
			elseif (strpos($locale_arr['language_iso'], '-NO') || in_array($locale_arr['language_iso'], array('nb', 'nn')))
			{
				$lang = 'no';
			}
			elseif (strpos($locale_arr['language_iso'], '-SE'))
			{
				$lang = 'sv';
			}

			$locales = array(
				'zh' => 'zh', // Simplified Chinese
				'da' => 'da', // Danish
				'nl' => 'nl', // Dutch
				'fi' => 'fi', // Finnish
				'fr' => 'fr', // French
				'de' => 'de', // German
				'it' => 'it', // Italian
				'ja' => 'ja', // Japanese
				'no' => 'no', // Norwegian
				'es' => 'es', // Spanish
				'sv' => 'sv', // Swedish
			);

			if (array_key_exists($lang, $locales))
			{
				$locale = $locales[$lang];
			}
		}

		return $locale;
	}

	/**
	 * Creates a hosted Stripe Checkout Session and hands the id to the view,
	 * which redirects the customer to Stripe.
	 */
	public function pjActionForm()
	{
		$this->setLayout('pjActionEmpty');

		$params = $this->getParams();

		$cancel_url = "{$params['notify_url']}&cancel_hash={$params['cancel_hash']}";
		$item_name = pjSanitize::html(@$params['item_name']);
		if ($item_name === '' || $item_name === null)
		{
			$item_name = 'Booking';
		}

		$data = array(
			'payment_method_types' => array('card'),
			'line_items' => array(
				array(
					'price_data' => array(
						'currency' => strtolower($params['currency_code']),
						'product_data' => array(
							'name' => $item_name,
							'description' => $item_name,
						),
						'unit_amount' => self::toMinorUnits($params['amount'], $params['currency_code']),
					),
					'quantity' => 1,
				)
			),
			'mode' => 'payment',
			'locale' => self::getPaymentLocale(@$params['locale_id']),
			'success_url' => @$params['notify_url'] . '&id=' . @$params['custom'] . '&stripesessid={CHECKOUT_SESSION_ID}',
			'cancel_url' => $cancel_url . '&id=' . @$params['custom'] . '&stripesessid={CHECKOUT_SESSION_ID}',
		);

		$sdk = new pjStripeSDK(null, $params['public_key'], $params['private_key']);

		try {
			$result = $sdk->createSession($data);

			if (isset($result['id']))
			{
				$this->set('session_id', $result['id']);
			}
		} catch (Exception $e) {
			$this->log(self::$logPrefix . $e->getMessage());
		}

		$this->set('arr', $params);
	}

	/**
	 * Entry point called from pjFront::pjActionConfirmStripe() after the customer
	 * returns from Stripe. Validates the request and delegates verification.
	 */
	public function pjActionConfirm()
	{
		$params = $this->getParams();
		$request = $params['request'];

		if (!isset($params['key']) || $params['key'] != md5($this->option_arr['private_key'] . PJ_SALT))
		{
			$this->log(self::$logPrefix . "Missing or invalid 'key' parameter.");
			return FALSE;
		}

		$response = array('status' => 'FAIL', 'redirect' => true);

		if (isset($request['cancel_hash']) && $request['cancel_hash'] == $params['cancel_hash'])
		{
			$this->log(self::$logPrefix . "Payment was cancelled.");
			$response['status'] = 'CANCEL';
			return $response;
		}

		$options = $this->option_arr;

		$_response = $this->requestAction(array(
			'controller' => 'pjStripe',
			'action' => 'pjActionProcess',
			'params' => array(
				'key' => md5($this->option_arr['private_key'] . PJ_SALT),
				'public_key' => $options['o_stripe_public_key'],
				'private_key' => $options['o_stripe_private_key'],
				'amount' => $params['amount'],
				'currency' => $this->option_arr['o_currency'],
				'description' => pjSanitize::html(@$params['uuid']),
				'foreign_id' => $params['order_id'],
				'session_id' => $request['stripesessid'], // Stripe Checkout Session id
			)
		), array('return'));

		if ($_response['status'] == 'OK')
		{
			$response['status'] = 'OK';
			$response['txn_id'] = @$_response['txn_id'];
		}

		return $response;
	}

	/**
	 * Verifies that the Checkout Session was actually paid and records the payment.
	 * Primary check: retrieve the session directly. Fallback: scan today's events.
	 */
	public function pjActionProcess()
	{
		$params = $this->getParams();

		if (!isset($params['key']) || $params['key'] != md5($this->option_arr['private_key'] . PJ_SALT))
		{
			$this->log(self::$logPrefix . "Missing or invalid key parameter.");
			return array('status' => 'ERR', 'code' => 100, 'text' => 'Missing or invalid key parameter');
		}

		if (!(isset($params['session_id'], $params['private_key'], $params['amount'], $params['currency'])))
		{
			$this->log(self::$logPrefix . "Missing, empty or invalid parameters.");
			return array('status' => 'ERR', 'code' => 101, 'text' => 'Missing, empty or invalid parameters.');
		}

		$sdk = new pjStripeSDK(null, $params['public_key'], $params['private_key']);

		$session = NULL;
		$txn_id = NULL;
		$stripe_id = NULL;
		$created = NULL;

		// --- Primary: retrieve the session and check it is paid ---------------
		try {
			$session = $sdk->retrieveSession($params['session_id']);
		} catch (Exception $e) {
			$this->log(self::$logPrefix . $e->getMessage());
			$session = NULL;
		}

		if (is_array($session) && @$session['id'] == $params['session_id'] && @$session['payment_status'] == 'paid')
		{
			$txn_id = @$session['payment_intent'];
			$stripe_id = $session['id'];
			$created = @$session['created'];
		}
		else
		{
			// --- Fallback: look the session up in today's completed events ----
			try {
				$events = $sdk->getEvents();
			} catch (Exception $e) {
				$this->log(self::$logPrefix . $e->getMessage());
				return array('status' => 'ERR', 'code' => 102, 'text' => $e->getMessage());
			}

			foreach ($events['data'] as $item)
			{
				if ($item['data']['object']['id'] == $params['session_id']
					&& $item['data']['object']['object'] == 'checkout.session'
					&& $item['type'] == 'checkout.session.completed')
				{
					$session = $item['data']['object'];
					$txn_id = $item['data']['object']['payment_intent'];
					$stripe_id = $item['id'];
					$created = $item['created'];
					break;
				}
			}
		}

		if (is_null($stripe_id))
		{
			$this->log(self::$logPrefix . "Payment not found or not completed.");
			return array('status' => 'ERR', 'code' => 104, 'text' => 'Payment not found or not completed.');
		}

		// --- Verify the amount / currency actually charged --------------------
		$expected = self::toMinorUnits($params['amount'], $params['currency']);
		$paid = isset($session['amount_total']) ? (int) $session['amount_total'] : NULL;
		$paidCurrency = isset($session['currency']) ? strtolower($session['currency']) : NULL;

		if (!is_null($paid) && ($paid != $expected || $paidCurrency != strtolower($params['currency'])))
		{
			$this->log(self::$logPrefix . "Amount/currency mismatch. Expected {$expected} {$params['currency']}, got {$paid} {$paidCurrency}.");
			return array('status' => 'ERR', 'code' => 106, 'text' => 'Amount/currency mismatch.');
		}

		if (isset($params['foreign_id']))
		{
			self::pjActionSavePayment($params['foreign_id'], array_merge($params, array(
				'stripe_id' => $stripe_id,
				'txn_id' => $txn_id,
				'created' => $created,
			)));
		}

		$this->log(self::$logPrefix . "Payment completed. Stripe ID: {$stripe_id}. Payment ID: {$txn_id}");

		return array('status' => 'OK', 'code' => 200, 'text' => 'Payment completed.', 'txn_id' => $txn_id);
	}

	private static function pjActionSavePayment($foreign_id, $data)
	{
		return pjStripeModel::factory()
			->setAttributes(array(
				'foreign_id' => $foreign_id,
				'stripe_id' => @$data['stripe_id'],
				'txn_id' => @$data['txn_id'],
				'amount' => @$data['amount'],
				'currency' => @$data['currency'],
				'description' => @$data['description'],
				'dt' => preg_match('|^\d{10}$|', @$data['created']) ? sprintf(':FROM_UNIXTIME(%u)', @$data['created']) : ':NOW()'
			))
			->insert()
			->getInsertId();
	}
}
?>
