<?php
/**
 * ePayments library for PHP
 * 
 * @package    Proxim
 * @author     Davison Pro <davisonpro.coder@gmail.com | https://davisonpro.dev>
 * @copyright  2019 Proxim
 * @version    1.0.0
 * @since      File available since Release 1.0.0
 */

namespace Proxim;

class ePayments {
  /**
   * Link to the payment system. By default in test mode
   */
  private $url = 'https://sb-ms.epayments.com';
  /**
   * Test mode. Enabled by default!
   */
  private $sandbox = true;
  /**
   * shopId  issued upon registration in the system
   */
  private $shopId;
  /**
   * secretKey issued upon registration in the system
   */
  private $secretKey;
  /**
   * UserName issued upon registration in the system
   */
  private $UserName;
  /**
   * Password issued upon registration in the system
   */
  private $Password;
  /**
   * Token for authorization via api
   */
  private $token;
  /**
   * Order Name
   */
  private $orderName;
  /**
   * Order number in the online store
   */
  private $orderNumber;
  /**
   * Order price
   */
  private $orderSumAmount;
  /**
   * Currency
   */
  private $orderSumCurrency = 'USD';
  /**
   * Redirect to the payment system
   */
  private $link;
  /**
   *  Language in the payment system
   */
  private $language;
  /**
   * Payment type
   */
  private $gatewayId;
  /**
   * Refund link if replenishment is not successful
   */
  private $shopFailUrl;
  /**
   *  Refund link upon successful deposit
   */
  private $shopSuccessUrl;
  /**
   * Return to Store Return Link
   */
  private $shopDefaultUrl;
  /**
   * Invoice link lifetime
   */
  private $operationLifeTime;

  /**
   * Sets test or combat mode
   */
  public function setTestMode(bool $bool = true): ?bool{
    $this->url = $bool ? 'https://sb-ms.epayments.com' : 'https://ms.epayments.com';
    return $this->sandbox = $bool;
  }

  /**
   * The identifier of the online store that is issued upon registration
   */
  public function setShopId(int $value): ?int {
    return $this->shopId = $value;
  }

  /**
   * The secret key
   */
  public function setSecretKey(string $value): ?string {
    return $this->secretKey = $value;
  }

  /**
   * UserName - issued upon registration in the merchant
   */
  public function setUserName(string $value): ?string {
    return $this->UserName = $value;
  }

  /**
   * Password - issued upon registration in the merchant
   */
  public function setPassword(string $value): ?string {
    return $this->Password = $value;
  }

  /**
   * Unique account number in the online store.
   * If a payment with such an order number has already been successfully completed, then repeated payment attempts will be rejected.
   * All characters are allowed. Length from 1 to 64 characters
   */
  public function setOrderNumber(string $value): ?string {
    return $this->orderNumber = $value;
  }

  /**
   * Amount of payment.
   * Separator - period.
   * The number of characters after the point depends on the payment currency (orderSumCurrency) and its bit requirements (in accordance with ISO-4217).
   * For example, to create an invoice for 10 USD, you must transfer:
   * • orderSumAmount = 10.00
   * • orderSumCurrency = USD
   * When transmitting incorrect bit depth for the selected currency, the error 2002019 will be returned in the response
   */
  public function setOrderSumAmount(float $value): ?float {
    return $this->orderSumAmount = $value;
  }

  /**
   * Three-letter ISO code of the currency of payment
   */
  public function setOrderSumCurrency(string $value): ?string {
    return $this->orderSumCurrency = $value;
  }

  /**
   * Payment Description
   */
  public function setOrderName(string $value): ?string {
    return $this->orderName = $value;
  }

  /**
   * Payment Method Id:
   * • 1 – bank card;
   * • 2 – purse ePayments.
   * If the parameter is not passed, the buyer will see the payment method selection page
   */
  public function setGatewayId(int $value): ?int {
    return $this->gatewayId = $value;
  }

  /**
   * Page to which the buyer will be redirected in case of successful payment.
   */
  public function setShopSuccessUrl(string $value): ?string {
    return $this->shopSuccessUrl = $value;
  }

  /**
   * The page to which the buyer will be redirected in case of an error when making a payment.
   */
  public function setShopFailUrl(string $value): ?string {
    return $this->shopFailUrl = $value;
  }

  /**
   * The page that the buyer will return to when clicking the "Back to the store" button
   * before receiving the result (i.e., the buyer on his own initiative refused to pay).
   */
  public function setShopDefaultUrl(string $value): ?string {
    return $this->shopDefaultUrl = $value;
  }

  /**
   * Language of the payment page. Possible Values:
   * • En (default) - English;
   * • Ru – Russian;
   * • ES – Spanish
   */
  public function setLanguage(string $value): ?string {
    return $this->language = $value;
  }

  /**
   * The duration of the link until the transition to the payment page. Indicated in minutes.
   * Valid value is from 1 to 4320.
   */
  public function setOperationLifeTime(int $value): ?int {
    return $this->operationLifeTime = $value;
  }

  /**
   * Hash Function Result SHA512.
   * The line is formed on the side of the online store using the following attributes of the payment form:
   * shopId;secretKey;orderNumber;orderSumAmount;orderSumCurrency
   * Attribute Separator - Semicolon «;».
   * Spaces between attributes are not allowed
   */
  public function getSha512(): ?string{
    $parts = [
      $this->getShopId(),
      $this->getSecretKey(),
      $this->getOrderNumber(),
      $this->getOrderSumAmount(),
      $this->getOrderSumCurrency(),
    ];

    return hash('sha512', implode(';', $parts));
  }

  public function getShopId(): ?int {
    return $this->shopId;
  }

  public function getSecretKey(): ?string {
    return $this->secretKey;
  }

  public function getUserName(): ?string {
    return $this->UserName;
  }

  public function getPassword(): ?string {
    return $this->Password;
  }

  public function getOrderNumber(): ?string {
    return $this->orderNumber;
  }

  public function getOrderSumAmount(): ?string {
    return number_format((float) $this->orderSumAmount, 2, '.', '');
  }

  public function getOrderSumCurrency(): ?string {
    return $this->orderSumCurrency;
  }

  public function getOrderName(): ?string {
    return $this->orderName;
  }

  public function getGatewayId(): ?int {
    return $this->gatewayId;
  }

  public function getShopSuccessUrl(): ?string {
    return $this->shopSuccessUrl;
  }

  public function getShopFailUrl(): ?string {
    return $this->shopFailUrl;
  }

  public function getShopDefaultUrl(): ?string {
    return $this->shopDefaultUrl;
  }

  public function getLanguage(): ?string {
    return $this->language;
  }

  public function getOperationLifeTime(): ?int {
    return $this->operationLifeTime;
  }

  /**
   * Authorization (receiving a token)
   */
  public function getToken() {

    $fields = http_build_query([
      'grant_type' => 'password',
      'username' => $this->getUserName(),
      'password' => $this->getPassword(),
    ]);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $this->url . '/api/v1/oauth/token');
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
    $result = json_decode(curl_exec($ch));
    curl_close($ch);

    if (isset($result->access_token)) {
      return $this->token = $result->access_token;
    }
    return false;
  }

  public function getUrl() {

    $query = http_build_query([
      'shopId' => $this->getShopId(),
      'orderNumber' => $this->getOrderNumber(),
      'orderSumAmount' => $this->getOrderSumAmount(),
      'orderSumCurrency' => $this->getOrderSumCurrency(),
      'orderName' => $this->getOrderName(),
      'sha512' => $this->getSha512(),
      'gatewayId' => $this->getGatewayId(),
      'shopSuccessUrl' => $this->getShopSuccessUrl(),
      'shopFailUrl' => $this->getShopFailUrl(),
      'shopDefaultUrl' => $this->getShopDefaultUrl(),
      'language' => $this->getLanguage(),
      'operationLifeTime' => $this->getOperationLifeTime()
    ]);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $this->url . '/api/v1/public/paymentpage?' . $query);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $result = json_decode(curl_exec($ch));
    curl_close($ch);

    if (empty($result)) {
      throw new \Exception("Error Processing Request");
    }

    if (empty($result->result->urlToRedirect)) {
      throw new \Exception("Сould not get a link to pay");
    }

    $this->link = $result->result->urlToRedirect;

    return $result;
  }

  /**
   * Transaction Verification
   * @param int $id transaction id
   * @param string $orderNumber
   */
  public function check(int $id, string $orderNumber): ?bool{

    $result = $this->getOperation($id);

    if (isset($result->error)) {
      return false;
    }

    if ($result->result->operations[0]->id !== $id
      || $result->result->operations[0]->state !== 'Done'
      || $result->result->operations[0]->orderNumber !== $orderNumber
    ) {
      return false;
    }
    return true;
  }

  /**
   * Getting operation by id
   */
  public function getOperation(int $id) {
    $query = http_build_query([
      'operationId' => $id,
    ]);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $this->url . '/api/v1/operation?' . $query);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
      'Content-Type: application/json',
      'Authorization: Bearer ' . $this->getToken(),
    ));
    $result = json_decode(curl_exec($ch));
    curl_close($ch);

    return $result;
  }

  /**
   * Redirect to a payment system or error output
   */
  public function send(): void {

    $link = $this->link ?: $this->getUrl();

    if (isset($link->error)) {
      throw new \Exception($link->error->messages[0]);
    }

    if (isset($link->result->urlToRedirect)) {
      header('Location: ' . $link->result->urlToRedirect);
      exit;
    }
  }
}