<?php
use Firebase\JWT\JWT;

/**
 * 
 * 目的： EasyPaymentLink
 * 開発者：　Anthony
 *
 */
class EasyPaymentLink extends System {
    
    /**
     * 
     * シングルトンの一般的な変数
     * 
     * @var object
     */
    private static $singleton;
    
    /**
     * 
     * @var array
     */
    private $data;
    
    /**
     * 
     * @var int
     */
    private $remarks    = NO_COUNT;
    
    /**
     * 
     * @var object
     */
    private $eplconf;
    
    /**
     * 
     * @var string
     */
    private $eplTransactionId;
    
    /**
     * 
     * @var string
     */
    private $createTime;
    
    /**
     * 
     * 変更不可能な変数
     */
    CONST DUPLICATE             = VAL_INT_1;
    CONST INVALID_TRANSACTION   = VAL_INT_2;
    CONST USER_SESSION_MISSING  = VAL_INT_3;
    CONST APN_INTERNAL_ERROR    = VAL_INT_4;
    CONST FAILED_TRANSACTION    = VAL_INT_5;
    CONST MAIL_SUCCESS          = VAL_INT_10;
    CONST MAIL_FAILED           = VAL_INT_11;
    
    /**
     * 
     * コンストラクタ
     * 
     */
    public function __construct(){
        parent::__construct();
        $this -> eplconf            = $this -> getSettingConfiguration("easy_payment_link");
        $this -> eplTransactionId   = $this -> getDataGet("transaction_id");
    }
    
    /**
     * シングルトンの機能
     * 
     * @return EasyPaymentLink
     */
    private static function getInstance() : EasyPaymentLink {
        if(!isset(self::$singleton))
            self::$singleton = new EasyPaymentLink();
        
        return self::$singleton;
    }
    
    /**
     * 
     * 公開的な確認
     * 
     * @param string $data
     * @param array $verdict
     */
    public static function validation($data, &$verdict){
        $purpose = self::getInstance();

        $purpose -> setData(json_decode($data, true));
        $purpose -> checkTransactionRequest();
        $purpose -> setVerdict($verdict);     
    }
    
    /**
     * 
     * 目的の出力
     */
    public static function action(){
        $purpose = self::getInstance();
        
        $purpose -> processTransaction();
        $purpose -> redirection();
    }
    
    /**
     * 
     * 取引の処理
     * 
     */
    private function processTransaction(){
        switch($this -> remarks){
            case self::APN_INTERNAL_ERROR:
            case self::INVALID_TRANSACTION:
            case self::FAILED_TRANSACTION:
            case self::DUPLICATE:
                return;
        }
        
        $this -> createTime = date("Y-m-d H:i:s");
        $method = VAL_STR_CCDEPOSIT_METHOD;
        
        $this -> accessModify("INSERT_WB_DEPOSIT_TRANSACTION", [
            $this -> getColumnData($this -> data, COLUMN_TRANSACTION_NUMBER),
            $this -> getColumnData($this -> data, COLUMN_USER_ACCOUNT),
            $this -> getColumnData($this -> data, COLUMN_AMOUNT),
            $this -> getColumnData($this -> data, COLUMN_CURRENCY),
            $method,
            "Credit Card Deposit",
            $this -> createTime,
            $this -> getColumnData($this -> data, COLUMN_FEE),
            NO_STRING,
            $this -> createTime,
            NO_COUNT,
            NO_COUNT,
            "APN-EPL: {$this -> eplTransactionId}",
        ], false);
        
        $this -> data["APN_EPL"] = $this -> eplTransactionId;
        $this -> logDetails(print_r($this -> data, true));
    }
    
    /**
     * 
     * リダイレクトの機能
     * 
     */
    private function redirection(){
        switch($this -> remarks){
            case self::APN_INTERNAL_ERROR:
            case self::INVALID_TRANSACTION:
            case self::FAILED_TRANSACTION:
                $this -> sendTransactionMailNotice($this -> data, self::MAIL_FAILED);
            case self::DUPLICATE:
                $this -> redirectToFailurePage();
                break;
            default:
                $this -> sendTransactionMailNotice($this -> data, self::MAIL_SUCCESS);
                $this -> redirectToSuccessPage();
                break;
        }
    }
    
    private function sendTransactionMailNotice($txn, $remarks){
        $member = $this -> getRowData($this -> getAccountCommon($txn[COLUMN_USER_ACCOUNT]));
                
        $currency        = $txn[COLUMN_CURRENCY];
        $creditedAmount  = $this -> intToCurrency($txn[COLUMN_AMOUNT] - $txn[COLUMN_FEE], $currency);
        
        $params = [
            $member[COLUMN_USER_ACCOUNT],
            $this -> getNameCommon($member[COLUMN_USER_ACCOUNT]),
            $txn[COLUMN_TRANSACTION_NUMBER],
            $this -> createTime,
            $currency,
            $this -> intToCurrency($txn[COLUMN_AMOUNT], $currency),
            $currency,
            $creditedAmount
        ];
        
        switch($remarks){
            case self::MAIL_SUCCESS:
                $template = "deposit_ccd_complete.xml";
                break;
            case self::MAIL_FAILED:
                $template = "deposit_ccd_fail.xml";
                break;
        }
        
        $this -> sendMailByTmp($template
            , $params
            , $this -> getColumnData($member, COLUMN_MAIL)
            , VAR_CS_MAIL_ADDRESS);
    }
    
    /**
     * 
     * 失敗ページへのリダイレクト
     * メールの知らせも含んでる事
     * 
     */
    private function redirectToFailurePage(){
        $language   = $this -> getColumnData($this -> data, PARAM_LANGUAGE);
        $baseUrl    = SITE_PROTOCOL."://".SITE_DOMAIN_FULL;
        
        $mergeData = ["expiration" =>  date("Y-m-d H:i:s", strtotime("+5 seconds"))];
        
        $callbackData = JWT::encode(
            $mergeData, 
            $this -> eplconf -> link_key);
        
        $requestUri = "deposit?type=".TYPE_CCD_FAIL."&ccback={$callbackData}";
        $redirectUrl = "{$baseUrl}/{$language}/{$requestUri}";
        
        header("Location: {$redirectUrl}");
    }
    
    /**
     * 
     * 成功ページへのリダイレクト
     * メールの知らせも含んでる事
     * 
     */
    private function redirectToSuccessPage(){
        $language   = $this -> getColumnData($this -> data, PARAM_LANGUAGE);
        $baseUrl    = SITE_PROTOCOL."://".SITE_DOMAIN_FULL;
        
        $mergeData = [
            "expiration" =>  date("Y-m-d H:i:s", strtotime("+8 seconds")),
            "transaction_time" => $this -> createTime,
            "credit_amount" => ($this -> data["amount"] - $this -> data["fee"])
        ];
        
        $callbackData = JWT::encode(
            array_merge($this -> data, $mergeData), 
            $this -> eplconf -> link_key);
        
        $requestUri = "deposit?type=".TYPE_CCD_SUCCESS."&ccback={$callbackData}";
        
        $redirectUrl = "{$baseUrl}/{$language}/{$requestUri}";
        
        header("Location: {$redirectUrl}");
    }
    
    /**
     * データ設定
     * 
     * @param object $data
     */
    private function setData($data){
        $this -> data = $data;
    }
    
    /**
     * 
     * リクエストや取引番号などの確認
     * 
     */
    private function checkTransactionRequest(){
        $eplTransStatus = $this -> fetchEPLTransactionStatus();
        
        if(is_array($eplTransStatus) && isset($eplTransStatus["status"])){
            switch($eplTransStatus["status"]){
                 case "200":
                     if($eplTransStatus["data"]["status_code"] == "OK.00.00"){                
                         if($this -> isDepositTransaction())
                             $this -> remarks = self::DUPLICATE;                  
                     }else
                         $this -> remarks = self::FAILED_TRANSACTION;
                     break;
                 case "500":
                     $this -> remarks = self::APN_INTERNAL_ERROR;
                     break;
                 default:
                     $this -> remarks = self::INVALID_TRANSACTION;
                     break;
            }
        }else
            $this -> remarks = self::APN_INTERNAL_ERROR;
            
        $this -> data["status"]                 = $eplTransStatus["status"];
        $this -> data["trans_response_code"]    = $eplTransStatus["data"]["status_code"];
    }
    
    /**
     * 
     * 入金取引番号の確認
     * 
     * @return boolean
     */
    private function isDepositTransaction(){
        $deposit = $this -> getRowData($this -> accessSelect("SELECT_DEPOSIT_BY_TRANSACTION_NUMBER", 
            [$this -> getColumnData($this -> data, PARAM_TRANSACTION_NUMBER)]));
        
        return $this -> isLoopData($deposit);
    }
    
    /**
     * 
     * EPLの取引番号を取り出す
     * 
     * @return mixed|unknown
     */
    private function fetchEPLTransactionStatus(){
        if($this -> eplTransactionId == NO_STRING){
            return [
                "status" => "404",
                "message" => "No EPL Transaction ID specified."       
            ];
        }
        
        $handle = curl_init();

        curl_setopt_array($handle, [
            CURLOPT_URL => $this -> eplconf -> status_check_link,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_ENCODING => "utf8",
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => json_encode([
                "transaction_id" => $this -> eplTransactionId,
                "notify_user" => false
            ]),
            CURLOPT_HTTPHEADER => [
                "Content-type: application/json",
                "Authorization: {$this -> eplconf -> link_key}"
            ]
        ]);
        
        $output = curl_exec($handle);
        
        if($this -> checkJSONString($output)){
            $output = json_decode($output, true);
        }
        
        if($err = curl_error($handle))
            $output = $err;
        
        curl_close($handle);
        return $output;
    }
    
    /**
     * 
     * 確認の結果設定
     * 
     * @param unknown $verdict
     */
    private function setVerdict(&$verdict){
        switch($this -> remarks){
            case NO_COUNT:
                $verdict = [
                    "validation" => true,
                    "description" => NO_STRING
                ];
                break;
            case self::DUPLICATE:
                $transactionNumber = $this -> getColumnData($this -> data, PARAM_TRANSACTION_NUMBER);
                $verdict = [
                    "validation" => true,
                    "description" => "Duplicate Transaction Number {$transactionNumber}"
                ];
                break;
            case self::INVALID_TRANSACTION:                    
                $verdict = [
                    "validation" => true,
                    "description" => "Invalid Transaction"
                ];
                break;
            case self::FAILED_TRANSACTION:
                $verdict = [
                    "validation" => true,
                    "description" => "Failed Transaction"
                    ];
                break;
            case self::USER_SESSION_MISSING:
                $verdict = [
                    "validation" => false,
                    "description" => "Session not found"
                ];
                break;
            case self::APN_INTERNAL_ERROR:                
                $verdict = [
                    "validation" => true,
                    "description" => "Internal Issue Occured. Please try again"
                ];
                break;
        }
        
        // 評決を詳細
        $this -> logDetails(print_r(array_merge($verdict, 
            [
                "responseCode" => $this -> data["status"],
                "failedAPNTransactionID" => 
                    $this -> remarks != NO_COUNT && isset($this -> eplTransactionId) 
                    ? $this -> eplTransactionId : NO_STRING
            ]), true));
    }

    /**
     * 
     * 詳細をログ
     * 
     * @param unknown $content
     */
    private function logDetails($content){
        $timestamp = date("Y-m-d H:i:s");
        
        $logDir = SITE_ROOT."api/Logs/APN";
        
        if(!@file_exists($logDir))
            mkdir($logDir, 0777, true);
            
        $logFile = "{$logDir}/Log_EZ".date("Y-m-d").".log";
        error_log("[{$timestamp}]\n\n{$content}\n\n", VAL_INT_3, $logFile);
    }
}