<?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 $apiBase = "https://api.stripe.com/v1/";

	protected static $apiVersion = "2022-08-01";

	/**
	 * Currencies Stripe expects in their base unit (not multiplied by 100).
	 * @link 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',
	);

	protected static function toMinorUnits($amount, $currency)
	{
		if (in_array(strtolower($currency), self::$zeroDecimal))
		{
			return (int) round($amount);
		}

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

	/**
	 * Thin Stripe REST call built on top of the framework's pjHttp component
	 * (same transport pjPaypal uses for the PayPal REST API).
	 */
	private function stripeRequest($secret_key, $method, $path, $params=null)
	{
		$http = new pjHttp();

		$headers = array(
			"Authorization: Bearer " . $secret_key,
			"Stripe-Version: " . self::$apiVersion,
		);
		if (strtoupper($method) === 'POST')
		{
			$headers[] = "Content-Type: application/x-www-form-urlencoded";
		}

		$http->setHeaders($headers);
		$http->setMethod($method);
		if (strtoupper($method) === 'POST' && is_array($params))
		{
			$http->setData($params); // array -> http_build_query (nested keys supported)
		}

		$resp = $http->request(self::$apiBase . $path);

		return json_decode($resp->getResponse(), true);
	}

	/**
	 * Creates a hosted Checkout Session and returns its id/url to the browser,
	 * which then redirects the customer to Stripe.
	 */
	public function pjActionCreateOrder()
	{
		$this->setAjax(true);
		$this->setLayout('pjActionEmpty');

		$options = $this->option_arr;

		$input = file_get_contents('php://input');
		$post = json_decode($input, true);
		$cart = isset($post['cart']) ? $post['cart'] : array();

		$reference_id = isset($cart['reference_id']) ? $cart['reference_id'] : '';
		$currency = isset($cart['currency']) ? $cart['currency'] : $options['o_currency'];
		$amount = isset($cart['amount']) ? $cart['amount'] : 0;
		$description = isset($cart['description']) && $cart['description'] !== '' ? $cart['description'] : 'Payment';

		// The confirm handler is supplied per surface (listing vs booking) via notify_url.
		$notify_url = isset($cart['notify_url']) && $cart['notify_url'] !== ''
			? $cart['notify_url']
			: PJ_INSTALL_URL . "index.php?controller=pjListings&action=pjActionConfirmStripe";
		$glue = strpos($notify_url, '?') === FALSE ? '?' : '&';
		$success_url = $notify_url . $glue . "custom=" . rawurlencode($reference_id) . "&session_id={CHECKOUT_SESSION_ID}";
		$cancel_url = isset($cart['failure_url']) && $cart['failure_url'] !== ''
			? $cart['failure_url']
			: (!empty($options['o_stripe_cancel_url'])
				? $options['o_stripe_cancel_url']
				: PJ_INSTALL_URL . "index.php?controller=pjAdminListings&action=pjActionIndex");

		$params = array(
			'mode' => 'payment',
			'client_reference_id' => $reference_id,
			'success_url' => $success_url,
			'cancel_url' => $cancel_url,
			'line_items' => array(
				array(
					'quantity' => 1,
					'price_data' => array(
						'currency' => strtolower($currency),
						'unit_amount' => self::toMinorUnits($amount, $currency),
						'product_data' => array(
							'name' => $description,
						),
					),
				),
			),
		);

		$result = $this->stripeRequest($options['o_stripe_secret_key'], 'POST', 'checkout/sessions', $params);

		if (isset($result['error']))
		{
			$this->log(self::$logPrefix . "createOrder error: " . @$result['error']['message']);
			pjAppController::jsonResponse(array('error' => @$result['error']['message']));
		}

		pjAppController::jsonResponse(array(
			'id' => @$result['id'],
			'url' => @$result['url'],
		));
	}

	/**
	 * Verifies that a Checkout Session was paid, records it and returns the txn id.
	 * Mirrors the return contract of pjPaypal::pjActionConfirm.
	 */
	public function pjActionConfirm()
	{
		$this->setAjax(true);
		$this->setLayout('pjActionEmpty');

		$params = $this->getParams();
		$request = $params['request'];

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

		if (!isset($params['key']) || $params['key'] != md5($this->option_arr['private_key'] . PJ_SALT))
		{
			$this->log(self::$logPrefix . "Missing or invalid 'key' parameter.");
			return $response;
		}
		if (empty($request['session_id']))
		{
			$this->log(self::$logPrefix . "Missing or invalid 'session_id' parameter.");
			return $response;
		}
		if (isset($request['cancel_hash']) && isset($params['cancel_hash']) && $request['cancel_hash'] == $params['cancel_hash'])
		{
			$this->log(self::$logPrefix . "Payment was cancelled.");
			$response['status'] = 'CANCEL';
			return $response;
		}

		$session = $this->stripeRequest($this->option_arr['o_stripe_secret_key'], 'GET', 'checkout/sessions/' . $request['session_id']);

		if (!is_array($session) || isset($session['error']) || !isset($session['id']))
		{
			$this->log(self::$logPrefix . "Unable to retrieve session. " . @$session['error']['message']);
			$response['status'] = 'UNKNOW';
			return $response;
		}

		if (@$session['payment_status'] != 'paid')
		{
			$this->log(self::$logPrefix . "Session not paid. Status: " . @$session['payment_status']);
			$response['status'] = @$session['payment_status'];
			return $response;
		}

		$txn_id = @$session['payment_intent'];

		$expected = self::toMinorUnits($params['deposit'], $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)
		{
			$this->log(self::$logPrefix . "TXN ID: {$txn_id}<br>AMOUNT didn't match (expected {$expected}, got {$paid}).");
			return $response;
		}
		if (!is_null($paidCurrency) && $paidCurrency != strtolower($params['currency']))
		{
			$this->log(self::$logPrefix . "TXN ID: {$txn_id}<br>CURRENCY didn't match.");
			return $response;
		}

		$this->pjActionSaveIpn(@$params['foreign_id'], array(
			'session_id'  => $session['id'],
			'txn_id'      => $txn_id,
			'amount'      => $params['deposit'],
			'currency'    => strtoupper($params['currency']),
			'payer_email' => @$session['customer_details']['email'],
		));

		$this->log(self::$logPrefix . "Payment was successful. TXN ID: {$txn_id}.");

		$response['status'] = 'OK';
		$response['txn_id'] = $txn_id;
		$response['transaction_id'] = $txn_id;

		return $response;
	}

	public function pjActionSave($foreign_id, $data=array())
	{
		$this->setLayout('pjActionEmpty');

		$params = $this->getParams();
		if (!isset($params['key']) || $params['key'] != md5($this->option_arr['private_key'] . PJ_SALT))
		{
			return FALSE;
		}

		return $this->pjActionSaveIpn($params['foreign_id'], $params['data']);
	}

	private function pjActionSaveIpn($foreign_id, $data)
	{
		return pjStripeModel::factory()
			->setAttributes(array(
				'foreign_id'  => $foreign_id,
				'session_id'  => @$data['session_id'],
				'txn_id'      => @$data['txn_id'],
				'amount'      => @$data['amount'],
				'currency'    => @$data['currency'],
				'payer_email' => @$data['payer_email'],
				'dt'          => date("Y-m-d H:i:s"),
			))
			->insert()
			->getInsertId();
	}

	public function pjActionForm()
	{
		$this->setLayout('pjActionEmpty');
		$this->setAjax(true);
		// KEYS:
		//-------------
		//name
		//id
		//public_key
		//item_name
		//custom
		//amount
		//currency_code
		//return
		//failure_url
		$this->set('arr', $this->getParams());
	}

	public function pjActionGetDetails()
	{
		$this->setAjax(true);

		if ($this->isXHR() && $this->isLoged() && $this->isAdmin())
		{
			if (isset($_GET['id']) && (int) $_GET['id'] > 0)
			{
				$this->set('arr', pjStripeModel::factory()->find($_GET['id'])->getData());
			}
		}
	}

	public function pjActionGetStripe()
	{
		$this->setAjax(true);

		if ($this->isXHR() && $this->isLoged() && $this->isAdmin())
		{
			$pjStripeModel = pjStripeModel::factory();

			if (isset($_GET['q']) && !empty($_GET['q']))
			{
				$q = $pjStripeModel->escapeStr($_GET['q']);
				$q = str_replace(array('%', '_'), array('\%', '\_'), $q);
				$pjStripeModel->where('t1.txn_id LIKE', "%$q%");
			}

			$column = 'dt';
			$direction = 'DESC';
			if (isset($_GET['direction']) && isset($_GET['column']) && in_array(strtoupper($_GET['direction']), array('ASC', 'DESC')))
			{
				$column = $_GET['column'];
				$direction = strtoupper($_GET['direction']);
			}

			$total = $pjStripeModel->findCount()->getData();
			$rowCount = isset($_GET['rowCount']) && (int) $_GET['rowCount'] > 0 ? (int) $_GET['rowCount'] : 10;
			$pages = ceil($total / $rowCount);
			$page = isset($_GET['page']) && (int) $_GET['page'] > 0 ? intval($_GET['page']) : 1;
			$offset = ((int) $page - 1) * $rowCount;
			if ($page > $pages)
			{
				$page = $pages;
			}

			$data = $pjStripeModel->select('t1.*')
				->orderBy("`$column` $direction")->limit($rowCount, $offset)->findAll()->getData();

			pjAppController::jsonResponse(compact('data', 'total', 'pages', 'page', 'rowCount', 'column', 'direction'));
		}
		exit;
	}

	public function pjActionIndex()
	{
		$this->checkLogin();

		if ($this->isAdmin())
		{
			$this->appendJs('jquery.datagrid.js', PJ_FRAMEWORK_LIBS_PATH . 'pj/js/');
			$this->appendJs('pjStripe.js', $this->getConst('PLUGIN_JS_PATH'));
			$this->appendJs('index.php?controller=pjAdmin&action=pjActionMessages', PJ_INSTALL_URL, true);
		} else {
			$this->set('status', 2);
		}
	}
}
?>
