diff --git a/plugin/lgxpay/lgdacom/XPayClient.php b/plugin/lgxpay/lgdacom/XPayClient.php index 07543e50a..c83c74702 100644 --- a/plugin/lgxpay/lgdacom/XPayClient.php +++ b/plugin/lgxpay/lgdacom/XPayClient.php @@ -1,235 +1,234 @@ - */ - -/** - * Curl based HTTP Client - * Simple but effective OOP wrapper around Curl php lib. - * Contains common methods needed - * for getting data from url, setting referrer, credentials, - * sending post data, managing cookies, etc. - * - * Samle usage: - * $curl = &new Curl_HTTP_Client(); - * $useragent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"; - * $curl->set_user_agent($useragent); - * $curl->store_cookies("/tmp/cookies.txt"); - * $post_data = array('login' => 'pera', 'password' => 'joe'); - * $html_data = $curl->send_post_data(http://www.foo.com/login.php, $post_data); - */ class XPayClient { - /** - * Curl handler - * @access private - * @var resource - */ var $ch; - - /** - * set debug to true in order to get usefull output - * @access private - * @var string - */ var $debug = false; var $bTest = false; - - /** - * Contain last error message if error occured - * @access private - * @var string - */ var $error_msg; - var $home_dir; var $mode; var $TX_ID; var $MID; var $Auth_Code; - var $config; - var $Post = array(); var $response_json; var $response_array; var $response_code; var $response_msg; - - var $log_file; - - var $err_label = array("FATAL", "ERROR", "WARN ", "INFO ", "DEBUG"); - - - /** - * Curl_HTTP_Client constructor - * @param boolean debug - * @access public - */ - function XPayClient($home_dir, $mode="real") + var $err_label = array("FATAL","ERROR","WARN ","INFO ","DEBUG"); + var $INFO = array("LGD_TXID","LGD_AUTHCODE","LGD_MID","LGD_OID","LGD_TXNAME","LGD_PAYKEY","LGD_RESPCODE","LGD_RESPMSG"); + var $DEBUG = array("LGD_TXID","LGD_AUTHCODE","LGD_MID","LGD_TID","LGD_OID","LGD_PAYTYPE","LGD_PAYDATE","LGD_TXNAME","LGD_PAYKEY","LGD_RESPCODE","LGD_RESPMSG"); + function IsAcceptLog($ParamName,$LogLevel) { - // for php4 JSON - if ( !function_exists('json_decode') ){ - function json_decode($content, $assoc=false){ - require_once 'JSON.php'; - if ( $assoc ){ - $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); - } else { - $json = new Services_JSON; - } - return $json->decode($content); - } + if(LGD_LOG_DEBUG == $LogLevel) + { + if (in_array($ParamName,$this->DEBUG,true)) return true; + } + else if(LGD_LOG_INFO == $LogLevel) + { + if (in_array($ParamName,$this->INFO,true)) return true; + } + return false; + } + function XPayClient($home_dir,$mode="real") + { + if(!function_exists('json_decode')) + { + function json_decode($content,$assoc=false) + { + require_once 'JSON.php'; + if ( $assoc ) + { + $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + } + else + { + $json = new Services_JSON; + } + return $json->decode($content); + } + } + define("LGD_USER_AGENT","XPayClient (4.0.0.0/PHP)"); + define("LGD_LOG_FATAL",0); + define("LGD_LOG_ERROR",1); + define("LGD_LOG_WARN",2); + define("LGD_LOG_INFO",3); + define("LGD_LOG_DEBUG",4); + define("LGD_ERR_NO_HOME_DIR","10001"); + define("LGD_ERR_NO_MALL_CONFIG","10002"); + define("LGD_ERR_NO_LGDACOM_CONFIG","10003"); + define("LGD_ERR_NO_MID","10004"); + define("LGD_ERR_OUT_OF_MEMORY","10005"); + define("LGD_ERR_NO_SECURE_PROTOCOLS","10007"); + define("LGD_ERR_HTTP_URL","20001"); + define("LGD_ERR_RESOLVE_HOST","20002"); + define("LGD_ERR_RESOLVE_PROXY","20003"); + define("LGD_ERR_CONNECT","20004"); + define("LGD_ERR_WRITE","20005"); + define("LGD_ERR_READ","20006"); + define("LGD_ERR_SEND","20007"); + define("LGD_ERR_RECV","20008"); + define("LGD_ERR_TIMEDOUT","20009"); + define("LGD_ERR_SSL","20101"); + define("LGD_ERR_CURL","20201"); + define("LGD_ERR_JSON_DECODE","40001"); + if(!isset($_SESSION)) + { + session_start(); } - - define("LGD_USER_AGENT", "XPayClient (1.1/PHP)"); - - define("LGD_LOG_FATAL", 0); - define("LGD_LOG_ERROR", 1); - define("LGD_LOG_WARN", 2); - define("LGD_LOG_INFO", 3); - define("LGD_LOG_DEBUG", 4); - - define("LGD_ERR_NO_HOME_DIR", "10001"); - define("LGD_ERR_NO_MALL_CONFIG", "10002"); - define("LGD_ERR_NO_LGDACOM_CONFIG", "10003"); - define("LGD_ERR_NO_MID", "10004"); - define("LGD_ERR_OUT_OF_MEMORY", "10005"); - - define("LGD_ERR_HTTP_URL", "20001"); - define("LGD_ERR_RESOLVE_HOST", "20002"); - define("LGD_ERR_RESOLVE_PROXY", "20003"); - define("LGD_ERR_CONNECT", "20004"); - define("LGD_ERR_WRITE", "20005"); - define("LGD_ERR_READ", "20006"); - define("LGD_ERR_SEND", "20007"); - define("LGD_ERR_RECV", "20008"); - define("LGD_ERR_TIMEDOUT", "20009"); - - define("LGD_ERR_SSL", "20101"); - - define("LGD_ERR_CURL", "20201"); - - define("LGD_ERR_JSON_DECODE", "40001"); - - session_start(); - $this->home_dir = $home_dir; - - // check directory and config files - if (!file_exists($home_dir)) { + if (!file_exists($home_dir)) + { $this->response_code = LGD_ERR_NO_HOME_DIR; $this->response_msg = "home_dir [".$home_dir."] does not exist"; - trigger_error($this->response_msg, E_USER_ERROR); + trigger_error($this->response_msg, E_USER_ERROR); } - else if (!file_exists($home_dir."/conf/mall.conf")) { + else if (!file_exists($home_dir."/conf/mall.conf")) + { $this->response_code = LGD_ERR_NO_MALL_CONFIG; $this->response_msg = "config file [".$home_dir."/conf/mall.conf] does not exist"; - trigger_error($this->response_msg, E_USER_ERROR); + trigger_error($this->response_msg, E_USER_ERROR); } - else if (!file_exists($home_dir."/conf/lgdacom.conf")) { + else if (!file_exists($home_dir."/conf/lgdacom.conf")) + { $this->response_code = LGD_ERR_NO_LGDACOM_CONFIG; $this->response_msg = "config file [".$home_dir."/conf/lgdacom.conf] does not exist"; - trigger_error($this->response_msg, E_USER_ERROR); + trigger_error($this->response_msg, E_USER_ERROR); } - $array1 = parse_ini_file($home_dir . "/conf/mall.conf"); + foreach($array1 as $name => $value) + { + $tempValue = $name; + if( strpos($tempValue,"MID_") !== FALSE ) + { + $tempValue = substr($tempValue,4,strlen($tempValue)); + $name = $tempValue; + $temparray = array($name => $value); + $array1 += $temparray; + break; + } + } $array2 = parse_ini_file($home_dir . "/conf/lgdacom.conf"); $this->config = $array1 + $array2; // log_dir 재설정 $this->config["log_dir"] = $home_dir."/log"; + $random_str = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'].$_SERVER['DOCUMENT_ROOT'].date("Ymd") : $_SERVER['DOCUMENT_ROOT'].date("Ymd"); - $this->log_file = $this->config["log_dir"] . "/log_" . date("Ymd") . '_' . substr(md5(mt_rand()), 0, 12) . ".log"; - // make log directory if does not exist - if (!file_exists($this->config["log_dir"])) { + $this->log_file = $this->config["log_dir"] . "/log_" . date("Ymd") . '_' . substr(md5($random_str), 0, 12) . ".log"; + if (!file_exists($this->config["log_dir"])) + { mkdir($this->config["log_dir"], "0777", true); } - $this->log("XPayClient initialize [".$home_dir."] [".$mode."]", LGD_LOG_INFO); - foreach($this->config as $name => $value) - $this->log("Config [".$name."] = [".$value."]", LGD_LOG_DEBUG); - if (strtolower($mode) == "test") { + if (strtolower($mode) == "test") + { $this->bTest = true; $this->debug = false; } $this->init(); } - - /** - * Init Curl session - * @access public - */ + function array_push_associative(&$arr) + { + $args = func_get_args(); + foreach ($args as $arg) + { + if (is_array($arg)) + { + foreach ($arg as $key => $value) + { + $arr[$key] = $value; + $ret++; + } + } + else + { + $arr[$arg] = ""; + } + } + return $ret; + } function init() { - // initialize curl handle $this->ch = curl_init(); - - //set various options - - // set user agent string for sending client version string curl_setopt($this->ch, CURLOPT_USERAGENT, LGD_USER_AGENT); - - //set error in case http return code bigger than 300 - //curl_setopt($this->ch, CURLOPT_FAILONERROR, true); - - // allow redirects curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true); - - // use gzip if possible curl_setopt($this->ch, CURLOPT_ENCODING , 'gzip, deflate'); - - // do not veryfy ssl - // this is important for windows - // as well for being able to access pages with non valid cert - if ($this->config["verify_cert"] == 0) { + if ($this->config["verify_cert"] == 0) + { curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); $this->log("Do not verify server Certificate", LGD_LOG_WARN); } - - // do not verify host name - // - if ($this->config["verify_host"] == 0) { + if ($this->config["verify_host"] == 0) + { curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); $this->log("Do not verify host Domain", LGD_LOG_WARN); } + // TLS 설정 + //; 512 (TLS1.1) , 2048 (TLS1.2) , 2560 (TLS1.0) + //CURL_SSLVERSION_DEFAULT (0), CURL_SSLVERSION_TLSv1 (1), CURL_SSLVERSION_SSLv2 (2), + //CURL_SSLVERSION_SSLv3 (3), CURL_SSLVERSION_TLSv1_0 (4), CURL_SSLVERSION_TLSv1_1 (5) or CURL_SSLVERSION_TLSv1_2 (6). + if( $this->config['default_secure_protocols'] == "512" ) + { + curl_setopt($this->ch, CURLOPT_SSLVERSION , 5 ); + //echo "default_secure_protocols = " . $this->config['default_secure_protocols'] . "
"; + } + else if ( $this->config['default_secure_protocols'] == "2048" ) + { + curl_setopt($this->ch, CURLOPT_SSLVERSION , 6 ); + //echo "default_secure_protocols = " . $this->config['default_secure_protocols'] . "
"; + } + else if ( $this->config['default_secure_protocols'] == "2560" ) + { + curl_setopt($this->ch, CURLOPT_SSLVERSION , 4 ); + //echo "default_secure_protocols = " . $this->config['default_secure_protocols'] . "
"; + } + else + { + curl_setopt($this->ch, CURLOPT_SSLVERSION , 0); + //echo "default_secure_protocols = " . $this->config['default_secure_protocols'] . "
"; + } curl_setopt($this->ch, CURLOPT_CAINFO, $this->home_dir."/conf/ca-bundle.crt"); - } - function Init_TX($MID) { - if ($this->config[$MID] == null) { + if ($this->config[$MID] == null) + { $this->response_code = LGD_ERR_NO_MID; $this->response_msg = "Key for MID [".$MID."] does not exist in mall.conf"; $this->log($this->response_msg, LGD_LOG_FATAL); return false; } - $this->TX_ID = $this->Gen_TX_ID($MID); $this->MID = $MID; $this->Auth_Code = $this->Gen_Auth_Code($this->TX_ID, $MID); - - $this->Post = array("LGD_TXID" => $this->TX_ID, - "LGD_AUTHCODE" => $this->Auth_Code, - "LGD_MID" => $MID); - + $this->Post = array("LGD_TXID" => $this->TX_ID,"LGD_AUTHCODE" => $this->Auth_Code,"LGD_MID" => $MID); return true; } - + function GenerateGUID() + { + if (function_exists('com_create_guid')) + { + return com_create_guid(); + } + else + { + mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45); + //$uuid = chr(123).substr($charid, 0, 8).$hyphen.substr($charid, 8, 4).$hyphen.substr($charid,12, 4).$hyphen.substr($charid,16, 4).$hyphen.substr($charid,20,12).chr(125); + $uuid = $charid; + return $uuid; + } + } function Get_Unique() { - if(isset($_SESSION['tx_counter'])) - $_SESSION['tx_counter'] = $_SESSION['tx_counter'] + 1; - else - $_SESSION['tx_counter'] = 1; -// $this->log("session id = ".session_id().$_SESSION['tx_counter'], LGD_LOG_FATAL); - return session_id().$_SESSION['tx_counter']; + if(isset($_SESSION['tx_counter'])) $_SESSION['tx_counter'] = $_SESSION['tx_counter'] + 1; + else $_SESSION['tx_counter'] = 1; + return session_id().$_SESSION['tx_counter'].$this->GenerateGUID(); } - function Gen_TX_ID($MID) { $now = date("YmdHis"); @@ -237,294 +236,297 @@ class XPayClient $tx_id = $header . sha1($header.$this->Get_Unique()); return $tx_id; } - function Gen_Auth_Code($tx_id, $MID) { $auth_code = sha1($tx_id . $this->config[$MID]); return $auth_code; } - function Set($name, $value) { $this->Post[$name] = $value; } - function Check_Auto_Rollback($errno) { - if ($errno == 28) //CURLE_OPERATION_TIMEDOUT - return true; - - return false; + //CURLE_OPERATION_TIMEDOUT + if ($errno == 28) return true; + else return false; } - function set_curl_error() { - switch (curl_errno($this->ch)) { - case 1: // CURLE_UNSUPPORTED_PROTOCOL: - case 3: // CURLE_URL_MALFORMAT: - $this->response_code = LGD_ERR_HTTP_URL; - $this->response_msg = "URL error"; - break; - - case 6: // CURLE_COULDNT_RESOLVE_HOST: - $this->response_code = LGD_ERR_RESOLVE_HOST; - $this->response_msg = "Resolve host error"; - break; - - case 5: // CURLE_COULDNT_RESOLVE_PROXY: - $this->response_code = LGD_ERR_RESOLVE_PROXY; - $this->response_msg = "Resolve proxy error"; - break; - - case 7: // CURLE_COULDNT_CONNECT: - $this->response_code = LGD_ERR_CONNECT; - $this->response_msg = "Could not connect error"; - break; - - case 23: // CURLE_WRITE_ERROR: - $this->response_code = LGD_ERR_WRITE; - $this->response_msg = "Write error"; - break; - - case 26: // CURLE_READ_ERROR: - $this->response_code = LGD_ERR_READ; - $this->response_msg = "Read error"; - break; - - case 55: // CURLE_SEND_ERROR: - $this->response_code = LGD_ERR_SEND; - $this->response_msg = "Send error"; - break; - - case 56: // CURLE_RECV_ERROR: - $this->response_code = LGD_ERR_RECV; - $this->response_msg = "Recv error"; - break; - - case 27: // CURLE_OUT_OF_MEMORY: - $this->response_code = LGD_ERR_OUT_OF_MEMORY; - $this->response_msg = "Out of memory error"; - break; - - case 28: // CURLE_OPERATION_TIMEDOUT : - $this->response_code = LGD_ERR_TIMEDOUT; - $this->response_msg = "Timeout error"; - break; - - case 35: // CURLE_SSL_CONNECT_ERROR: - case 51: // CURLE_PEER_FAILED_VERIFICATION: - case 53: // CURLE_SSL_ENGINE_NOTFOUND: - case 54: // CURLE_SSL_ENGINE_SETFAILED: - case 58: // CURLE_SSL_CERTPROBLEM: - case 59: // CURLE_SSL_CIPHER: - case 60: // CURLE_SSL_CACERT: - case 64: // CURLE_USE_SSL_FAILED: - case 66: // CURLE_SSL_ENGINE_INITFAILED: - case 77: // CURLE_SSL_CACERT_BADFILE: - case 80: // CURLE_SSL_SHUTDOWN_FAILED: - case 82: // CURLE_SSL_CRL_BADFILE: - case 83: // CURLE_SSL_ISSUER_ERROR: - $this->response_code = LGD_ERR_SSL; - $this->response_msg = "SSL error"; - break; - - default: - $this->response_code = LGD_ERR_CURL; - $this->response_msg = "CURL error"; - break; + switch (curl_errno($this->ch)) + { + case 1: // CURLE_UNSUPPORTED_PROTOCOL: + case 3: // CURLE_URL_MALFORMAT: + $this->response_code = LGD_ERR_HTTP_URL; + $this->response_msg = "URL error"; + break; + case 6: // CURLE_COULDNT_RESOLVE_HOST: + $this->response_code = LGD_ERR_RESOLVE_HOST; + $this->response_msg = "Resolve host error"; + break; + case 5: // CURLE_COULDNT_RESOLVE_PROXY: + $this->response_code = LGD_ERR_RESOLVE_PROXY; + $this->response_msg = "Resolve proxy error"; + break; + case 7: // CURLE_COULDNT_CONNECT: + $this->response_code = LGD_ERR_CONNECT; + $this->response_msg = "Could not connect error"; + break; + case 23: // CURLE_WRITE_ERROR: + $this->response_code = LGD_ERR_WRITE; + $this->response_msg = "Write error"; + break; + case 26: // CURLE_READ_ERROR: + $this->response_code = LGD_ERR_READ; + $this->response_msg = "Read error"; + break; + case 55: // CURLE_SEND_ERROR: + $this->response_code = LGD_ERR_SEND; + $this->response_msg = "Send error"; + break; + case 56: // CURLE_RECV_ERROR: + $this->response_code = LGD_ERR_RECV; + $this->response_msg = "Recv error"; + break; + case 27: // CURLE_OUT_OF_MEMORY: + $this->response_code = LGD_ERR_OUT_OF_MEMORY; + $this->response_msg = "Out of memory error"; + break; + case 28: // CURLE_OPERATION_TIMEDOUT : + $this->response_code = LGD_ERR_TIMEDOUT; + $this->response_msg = "Timeout error"; + break; + case 35: // CURLE_SSL_CONNECT_ERROR: + case 51: // CURLE_PEER_FAILED_VERIFICATION: + case 53: // CURLE_SSL_ENGINE_NOTFOUND: + case 54: // CURLE_SSL_ENGINE_SETFAILED: + case 58: // CURLE_SSL_CERTPROBLEM: + case 59: // CURLE_SSL_CIPHER: + case 60: // CURLE_SSL_CACERT: + case 64: // CURLE_USE_SSL_FAILED: + case 66: // CURLE_SSL_ENGINE_INITFAILED: + case 77: // CURLE_SSL_CACERT_BADFILE: + case 80: // CURLE_SSL_SHUTDOWN_FAILED: + case 82: // CURLE_SSL_CRL_BADFILE: + case 83: // CURLE_SSL_ISSUER_ERROR: + $this->response_code = LGD_ERR_SSL; + $this->response_msg = "SSL error"; + break; + default: + $this->response_code = LGD_ERR_CURL; + $this->response_msg = "CURL error"; + break; } - $this->response_msg .= "; cURL error code = ".curl_errno($this->ch)." msg = ".curl_error($this->ch); } - function TX($bRollbackOnError = true) { $bTX = true; $bRollback = false; $strRollbackReason = ""; $bReporting = false; + $bCheckURL = false; $strReportStatus = ""; $strReportMsg = ""; - - if ($bRollbackOnError) { - if ($this->config['auto_rollback'] == 0) - $bRollbackOnError = false; + if ($bRollbackOnError) + { + if ($this->config['auto_rollback'] == 0) $bRollbackOnError = false; + } + + if ($this->bTest) $url = $this->config['test_url']; + else $url = $this->config['url']; + + $Protocol = parse_url($url, PHP_URL_SCHEME); + if($Protocol == "") + { + $url = "https://" . $url; + $bCheckURL = true; + } + else if($Protocol == "http") + { + $bCheckURL = false; + } + else if($Protocol == "https") + { + $bCheckURL = true; + } + if($bCheckURL == true) + { + $result = $this->send_post_data($url, $this->Post, null, $this->config['timeout']); } - - if ($this->bTest) - $url = $this->config['test_url']; else - $url = $this->config['url']; - - $result = $this->send_post_data($url, $this->Post, null, $this->config['timeout']); - if ($result == false) { - $bTX = false; - $this->set_curl_error(); - - $this->log("TX failed: res code = ".$this->response_code."; msg = ". $this->response_msg, LGD_LOG_FATAL); - - if ($bRollbackOnError && $this->Check_Auto_Rollback(curl_errno($this->ch))) { - $bRollback = true; - $strRollbackReason = "Timeout"; - } + { + $result = false; + $bRollback = false; + $bReporting = false; + $this->response_code = LGD_ERR_HTTP_URL; + $this->response_msg = "http protocol not supported."; + $this->log("TX failed: res code = ".$this->response_code."; msg = ". $this->response_msg, LGD_LOG_FATAL); } - else { + if ($result == false) + { + if($bCheckURL == true) + { + $bTX = false; + $this->set_curl_error(); + $this->log("TX failed: res code = ".$this->response_code."; msg = ". $this->response_msg, LGD_LOG_FATAL); + if ($bRollbackOnError && $this->Check_Auto_Rollback(curl_errno($this->ch))) + { + $bRollback = true; + $strRollbackReason = "Timeout"; + } + } + else + { + $bTX = false; + } + } + else + { $http_res_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); - if ($http_res_code < 200 || $http_res_code >= 300) { + if ($http_res_code < 200 || $http_res_code >= 300) + { // http response error $bTX = false; $this->response_code = "".(30000 + $http_res_code); $this->response_msg = "HTTP response code = ".$http_res_code; $this->log("TX failed: res code = ".$this->response_code."; msg = ". $this->response_msg, LGD_LOG_FATAL); - // report $bReporting = true; $strReportStatus = "HTTP response ".$http_res_code; $strReportMsg = $result; - if ($bRollbackOnError && $http_res_code >= 500) { + if ($bRollbackOnError && $http_res_code >= 500) + { // rollback $bRollback = true; $strRollbackReason = "HTTP ".$http_res_code; } - } - else { - $this->log("Result = [".$result."]", LGD_LOG_DEBUG); + else + { $this->response_json = $result; - $this->response_array = json_decode($this->response_json, true); - - if (($this->response_array == false) || (strlen($this->response_array["LGD_RESPCODE"]) == 0)) { + if (($this->response_array == false) || (strlen($this->response_array["LGD_RESPCODE"]) == 0)) + { // JSON decode failed $bTX = false; - $bReporting = true; $strReportStatus = "JSON decode fail"; $strReportMsg = $result; - - if ($bRollbackOnError) { + if ($bRollbackOnError) + { $bRollback = true; $strRollbackReason = "JSON decode fail"; } $this->log("JSON Decode failed", LGD_LOG_ERROR); - $this->response_array = array(); $this->response_code = LGD_ERR_JSON_DECODE; $this->response_msg = "JSON Decode Failed"; } - else { + else + { $this->response_code = $this->response_array["LGD_RESPCODE"]; - if ($this->config['output_UTF8'] == 1) - $this->response_msg = $this->response_array["LGD_RESPMSG"]; - else - $this->response_msg = iconv("utf-8", "euc-kr", $this->response_array["LGD_RESPMSG"]); - + if ($this->config['output_UTF8'] == 1) $this->response_msg = $this->response_array["LGD_RESPMSG"]; + else $this->response_msg = iconv("utf-8", "euc-kr", $this->response_array["LGD_RESPMSG"]); $this->log("Response Code=[".$this->response_code."], Msg=[".iconv("utf-8", "euc-kr", $this->response_array["LGD_RESPMSG"])."], Count=".$this->Response_Count(), LGD_LOG_INFO); $keys = $this->Response_Names(); - for ($i = 0; $i < $this->Response_Count(); $i++) { - foreach($keys as $name) { - $this->log("Response (".$name.", ".$i.") = ".$this->Response($name, $i), LGD_LOG_DEBUG); + for ($i = 0; $i < $this->Response_Count(); $i++) + { + foreach($keys as $name) + { + if($this->IsAcceptLog($name,LGD_LOG_DEBUG)) + { + $this->log("Response (".$name.", ".$i.") = ".$this->Response($name, $i), LGD_LOG_DEBUG); + } } } } } } - - if ($bRollback) { + if ($bRollback) + { // try auto-rollback $tx_id = $this->TX_ID; $code = $this->response_code; $msg = $this->response_msg; $this->init(); $this->Rollback($strRollbackReason); - // restore previous info $this->TX_ID = $tx_id; $this->response_code = $code; $this->response_msg = $msg; } - - if ($bReporting) { + if ($bReporting) + { $this->Report($strReportStatus, $strReportMsg); } return $bTX; } - function Report_TX() { $url = $this->config['aux_url']; - $result = $this->send_post_data($url, $this->Post, null, $this->config['timeout']); - if ($result == false) { - + if ($result == false) + { set_curl_error(); $this->log("Reporting failed: res code = ".$this->response_code."; msg = ". $this->response_msg, LGD_LOG_ERROR); - return false; } - $http_res_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); - if ($http_res_code < 200 || $http_res_code >= 300) { + if ($http_res_code < 200 || $http_res_code >= 300) + { // http response error $this->log("Reporting failed: HTTP response code = ".$http_res_code, LGD_LOG_ERROR); return false; } - - - $this->log("Reporting result = [".$result."]", LGD_LOG_DEBUG); - $response_array = json_decode($result, true); - - if ($response_array == false) { + if ($response_array == false) + { // JSON decode failed $this->log("Report JSON Decode failed", LGD_LOG_ERROR); return false; } - $response_code = $response_array["LGD_RESPCODE"]; $response_msg = iconv("utf-8", "euc-kr", $response_array["LGD_RESPMSG"]); - $this->log("Report Response Code=[".$response_code."], Msg=[".$response_msg."]", LGD_LOG_INFO); - return true; } - function Patch_TX($filename) { $url = $this->config['aux_url']; - $tmp_file = tempnam($this->home_dir."/conf", "tmp"); $fp = fopen($tmp_file, "w"); $result = $this->post_into_file($url, $this->Post, $fp, null, $this->config['timeout']); fclose($fp); - - if ($result == false) { + if ($result == false) + { unlink($tmp_file); $this->log("Patch failed: file = ".$filename, LGD_LOG_ERROR); - return false; } - else { + else + { $this->log("Patch success: file = ".$filename, LGD_LOG_INFO); $type = curl_getinfo($this->ch, CURLINFO_CONTENT_TYPE); - if (strncasecmp($type, "text/plain", 10) == 0) { + if (strncasecmp($type, "text/plain", 10) == 0) + { $this->log("Patch success: Content-Type = ".$type, LGD_LOG_INFO); copy($tmp_file, $this->home_dir."/conf/".$filename); unlink($tmp_file); - return true; + return true; } - else { + else + { // error $fp = fopen($tmp_file, "r"); $content = fread($fp, 512); $this->Report("Patch error : ".$filename, $content); fclose($fp); - unlink($tmp_file); + unlink($tmp_file); } return false; } } - function Rollback($reason) { $RBTX = $this->TX_ID; @@ -532,163 +534,86 @@ class XPayClient $this->Set("LGD_TXNAME", "Rollback"); $this->Set("LGD_RB_TXID", $RBTX); $this->Set("LGD_RB_REASON", $reason); - if ($this->TX(false) == false) - return false; - - if ($this->response_code == "0000") - return true; + if ($this->TX(false) == false) return false; + if ($this->response_code == "0000") return true; return false; } - function Report($status, $msg) { - if ($this->config['report_error'] != 1) - return false; - + if ($this->config['report_error'] != 1) return false; $this->Init_TX($this->MID); $this->Set("LGD_TXNAME", "Report"); $this->Set("LGD_STATUS", $status); $this->Set("LGD_MSG", $msg); - return $this->Report_TX(); } - function Patch($filename) { $this->Init_TX($this->MID); $this->Set("LGD_TXNAME", "Patch"); $this->Set("LGD_FILE", $filename); - return $this->Patch_TX($filename); } - - - function Response_Json() { return ($this->response_json); } - function Response_Count() { - if ($this->response_array["LGD_RESPONSE"] == null) - return 0; + if ($this->response_array["LGD_RESPONSE"] == null) return 0; return count($this->response_array["LGD_RESPONSE"]); } - function Response_Code() { return ($this->response_code); } - function Response_Msg() { return ($this->response_msg); } - function Response_Names() { - if ($this->Response_Count() == 0) - return null; + if ($this->Response_Count() == 0) return null; return array_keys($this->response_array["LGD_RESPONSE"][0]); } - function Response($name, $index=0) { - if ($this->config['output_UTF8'] == 1) - return ($this->response_array["LGD_RESPONSE"][$index][$name]); - else - return (iconv("utf-8", "euc-kr", $this->response_array["LGD_RESPONSE"][$index][$name])); + if ($this->config['output_UTF8'] == 1) return ($this->response_array["LGD_RESPONSE"][$index][$name]); + else return (iconv("utf-8", "euc-kr", $this->response_array["LGD_RESPONSE"][$index][$name])); } - - function Log($msg, $level=LGD_LOG_FATAL) { if( !(defined('LGD_LOG_SAVE') && LGD_LOG_SAVE) ){ return; } - - if ($level > $this->config["log_level"]) - return; + if ($level > $this->config["log_level"]) return; $err_msg = date("Y-m-d H:i:s")." [".$this->err_label[$level]."] [".$this->TX_ID."] ".$msg."\n"; error_log($err_msg, 3, $this->log_file); } - - /** - * Set username/pass for basic http auth - * @param string user - * @param string pass - * @access public - */ function set_credentials($username,$password) { curl_setopt($this->ch, CURLOPT_USERPWD, "$username:$password"); } - - /** - * Set referrer - * @param string referrer url - * @access public - */ function set_referrer($referrer_url) { curl_setopt($this->ch, CURLOPT_REFERER, $referrer_url); } - - /** - * Set client's useragent - * @param string user agent - * @access public - */ function set_user_agent($useragent) { curl_setopt($this->ch, CURLOPT_USERAGENT, $useragent); } - - /** - * Set to receive output headers in all output functions - * @param boolean true to include all response headers with output, false otherwise - * @access public - */ function include_response_headers($value) { curl_setopt($this->ch, CURLOPT_HEADER, $value); } - - - /** - * Set proxy to use for each curl request - * @param string proxy - * @access public - */ function set_proxy($proxy) { curl_setopt($this->ch, CURLOPT_PROXY, $proxy); } - - - - /** - * Send post data to target URL - * return data returned from url or false if error occured - * @param string url - * @param mixed post data (assoc array ie. $foo['post_var_name'] = $value or as string like var=val1&var2=val2) - * @param string ip address to bind (default null) - * @param int timeout in sec for complete curl operation (default 10) - * @return string data - * @access public - */ function send_post_data($url, $postdata, $ip=null, $timeout=10) { - //set various curl options first - - // set url to post to curl_setopt($this->ch, CURLOPT_URL, $url); - - // return into a variable rather than displaying it curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); - - //bind to specific ip address if it is sent trough arguments if ($ip) { if($this->debug) @@ -697,44 +622,27 @@ class XPayClient } curl_setopt($this->ch, CURLOPT_INTERFACE, $ip); } - - //set curl function timeout to $timeout curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout); - - //set method to post curl_setopt($this->ch, CURLOPT_POST, true); - - - //generate post string $post_array = array(); if(is_array($postdata)) - { + { foreach($postdata as $key=>$value) { $post_array[] = urlencode($key) . "=" . urlencode($value); - $this->log("Post [".$key."] = [".$value."]", LGD_LOG_DEBUG); + if($this->IsAcceptLog($key,LGD_LOG_DEBUG)) + { + $this->log("Post [".$key."] = [".$value."]", LGD_LOG_DEBUG); + } } - $post_string = implode("&",$post_array); - - if($this->debug) - { - echo "Url: $url\nPost String: $post_string\n"; - } } - else + else { $post_string = $postdata; } - - $this->log("post_string = ".$post_string, LGD_LOG_DEBUG); - // set post string curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post_string); - - - //and finally send curl request $result = curl_exec($this->ch); - if(curl_errno($this->ch)) { if($this->debug) @@ -743,7 +651,6 @@ class XPayClient echo "Error number: " .curl_errno($this->ch) ."\n"; echo "Error message: " .curl_error($this->ch)."\n"; } - return false; } else @@ -751,28 +658,11 @@ class XPayClient return $result; } } - - /** - * fetch data from target URL - * return data returned from url or false if error occured - * @param string url - * @param string ip address to bind (default null) - * @param int timeout in sec for complete curl operation (default 5) - * @return string data - * @access public - */ function fetch_url($url, $ip=null, $timeout=5) { - // set url to post to curl_setopt($this->ch, CURLOPT_URL,$url); - - //set method to get curl_setopt($this->ch, CURLOPT_HTTPGET,true); - - // return into a variable rather than displaying it curl_setopt($this->ch, CURLOPT_RETURNTRANSFER,true); - - //bind to specific ip address if it is sent trough arguments if($ip) { if($this->debug) @@ -781,13 +671,8 @@ class XPayClient } curl_setopt($this->ch,CURLOPT_INTERFACE,$ip); } - - //set curl function timeout to $timeout curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout); - - //and finally send curl request $result = curl_exec($this->ch); - if(curl_errno($this->ch)) { if($this->debug) @@ -796,7 +681,6 @@ class XPayClient echo "Error number: " .curl_errno($this->ch) ."\n"; echo "Error message: " .curl_error($this->ch)."\n"; } - return false; } else @@ -804,29 +688,11 @@ class XPayClient return $result; } } - - /** - * Fetch data from target URL - * and store it directly to file - * @param string url - * @param resource value stream resource(ie. fopen) - * @param string ip address to bind (default null) - * @param int timeout in sec for complete curl operation (default 5) - * @return boolean true on success false othervise - * @access public - */ function fetch_into_file($url, $fp, $ip=null, $timeout=5) { - // set url to post to curl_setopt($this->ch, CURLOPT_URL, $url); - - //set method to get curl_setopt($this->ch, CURLOPT_HTTPGET, true); - - // store data into file rather than displaying it curl_setopt($this->ch, CURLOPT_FILE, $fp); - - //bind to specific ip address if it is sent trough arguments if($ip) { if($this->debug) @@ -835,13 +701,8 @@ class XPayClient } curl_setopt($this->ch, CURLOPT_INTERFACE, $ip); } - - //set curl function timeout to $timeout curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout); - - //and finally send curl request $result = curl_exec($this->ch); - if(curl_errno($this->ch)) { if($this->debug) @@ -850,7 +711,6 @@ class XPayClient echo "Error number: " .curl_errno($this->ch) ."\n"; echo "Error message: " .curl_error($this->ch)."\n"; } - return false; } else @@ -858,28 +718,10 @@ class XPayClient return true; } } - /** - * Send post data to target URL - * return data returned from url or false if error occured - * @param string url - * @param mixed post data (assoc array ie. $foo['post_var_name'] = $value or as string like var=val1&var2=val2) - * @param string ip address to bind (default null) - * @param int timeout in sec for complete curl operation (default 10) - * @return string data - * @access public - */ function post_into_file($url, $postdata, $fp, $ip=null, $timeout=10) { - //set various curl options first - - // set url to post to curl_setopt($this->ch, CURLOPT_URL, $url); - - // store data into file rather than displaying it curl_setopt($this->ch, CURLOPT_FILE, $fp); - - - //bind to specific ip address if it is sent trough arguments if ($ip) { if($this->debug) @@ -888,43 +730,31 @@ class XPayClient } curl_setopt($this->ch, CURLOPT_INTERFACE, $ip); } - - //set curl function timeout to $timeout curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout); - - //set method to post curl_setopt($this->ch, CURLOPT_POST, true); - - - //generate post string $post_array = array(); if(is_array($postdata)) - { + { foreach($postdata as $key=>$value) { $post_array[] = urlencode($key) . "=" . urlencode($value); - $this->log("Post [".$key."] = [".$value."]", LGD_LOG_DEBUG); + if(IsAcceptLog($key,LGD_LOG_DEBUG)) + { + $this->log("Post [".$key."] = [".$value."]", LGD_LOG_DEBUG); + } } - $post_string = implode("&",$post_array); - if($this->debug) { echo "Url: $url\nPost String: $post_string\n"; } } - else + else { $post_string = $postdata; } - - // set post string curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post_string); - - - //and finally send curl request $result = curl_exec($this->ch); - if(curl_errno($this->ch)) { if($this->debug) @@ -933,7 +763,6 @@ class XPayClient echo "Error number: " .curl_errno($this->ch) ."\n"; echo "Error message: " .curl_error($this->ch)."\n"; } - return false; } else @@ -941,30 +770,10 @@ class XPayClient return $result; } } - - /** - * Send multipart post data to the target URL - * return data returned from url or false if error occured - * (contribution by vule nikolic, vule@dinke.net) - * @param string url - * @param array assoc post data array ie. $foo['post_var_name'] = $value - * @param array assoc $file_field_array, contains file_field name = value - path pairs - * @param string ip address to bind (default null) - * @param int timeout in sec for complete curl operation (default 30 sec) - * @return string data - * @access public - */ function send_multipart_post_data($url, $postdata, $file_field_array=array(), $ip=null, $timeout=30) { - //set various curl options first - - // set url to post to curl_setopt($this->ch, CURLOPT_URL, $url); - - // return into a variable rather than displaying it curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); - - //bind to specific ip address if it is sent trough arguments if($ip) { if($this->debug) @@ -973,48 +782,27 @@ class XPayClient } curl_setopt($this->ch,CURLOPT_INTERFACE,$ip); } - - //set curl function timeout to $timeout curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout); - - //set method to post curl_setopt($this->ch, CURLOPT_POST, true); - - // disable Expect header - // hack to make it working $headers = array("Expect: "); curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); - - // initialize result post array $result_post = array(); - - //generate post string $post_array = array(); $post_string_array = array(); if(!is_array($postdata)) { return false; } - foreach($postdata as $key=>$value) { $post_array[$key] = $value; $post_string_array[] = urlencode($key)."=".urlencode($value); } - $post_string = implode("&",$post_string_array); - - if($this->debug) { echo "Post String: $post_string\n"; } - - // set post string - //curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post_string); - - - // set multipart form data - file array field-value pairs if(!empty($file_field_array)) { foreach($file_field_array as $var_name => $var_value) @@ -1023,15 +811,9 @@ class XPayClient $file_field_array[$var_name] = "@".$var_value; } } - - // set post data $result_post = array_merge($post_array, $file_field_array); curl_setopt($this->ch, CURLOPT_POSTFIELDS, $result_post); - - - //and finally send curl request $result = curl_exec($this->ch); - if(curl_errno($this->ch)) { if($this->debug) @@ -1040,7 +822,6 @@ class XPayClient echo "Error number: " .curl_errno($this->ch) ."\n"; echo "Error message: " .curl_error($this->ch)."\n"; } - return false; } else @@ -1048,73 +829,177 @@ class XPayClient return $result; } } - - /** - * Set file location where cookie data will be stored and send on each new request - * @param string absolute path to cookie file (must be in writable dir) - * @access public - */ function store_cookies($cookie_file) { - // use cookies on each request (cookies stored in $cookie_file) curl_setopt ($this->ch, CURLOPT_COOKIEJAR, $cookie_file); curl_setopt ($this->ch, CURLOPT_COOKIEFILE, $cookie_file); } - - /** - * Set custom cookie - * @param string cookie - * @access public - */ function set_cookie($cookie) - { + { curl_setopt ($this->ch, CURLOPT_COOKIE, $cookie); } - - /** - * Get last URL info - * usefull when original url was redirected to other location - * @access public - * @return string url - */ function get_effective_url() { return curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL); } - - /** - * Get http response code - * @access public - * @return int - */ function get_http_response_code() { return curl_getinfo($this->ch, CURLINFO_HTTP_CODE); } - - /** - * Return last error message and error number - * @return string error msg - * @access public - */ function get_error_msg() { $err = "Error number: " .curl_errno($this->ch) ."\n"; $err .="Error message: " .curl_error($this->ch)."\n"; - return $err; } + function close() + { + curl_close($this->ch); + } + function GetTimeStamp() + { + $Result = ""; + $Result = date("YmdHis"); + return $Result; + } + + function GetHashData($LGD_MID,$LGD_OID,$LGD_AMOUNT,$LGD_TIMESTAMP) + { + $LGD_HASHDATA = ""; + $LGD_HASHDATA = md5($LGD_MID.$LGD_OID.$LGD_AMOUNT.$LGD_TIMESTAMP.$this->config[$LGD_MID]); + return $LGD_HASHDATA; + } + + function GetHashDataOpenpay($LGD_VENDERNO,$LGD_MID,$LGD_OPENPAY_MER_UID,$LGD_OPENPAY_TOKEN,$LGD_TIMESTAMP) + { + $LGD_HASHDATA = ""; + $InputData = ""; + $InputData = $LGD_MID . $LGD_VENDERNO . $LGD_OPENPAY_MER_UID; + if(empty($LGD_OPENPAY_TOKEN)) $InputData = $InputData . $LGD_TIMESTAMP .$this->config[$LGD_MID]; + else $InputData = $InputData . $LGD_OPENPAY_TOKEN . $LGD_TIMESTAMP .$this->config[$LGD_MID]; + $LGD_HASHDATA = hash('sha512',$InputData); + return $LGD_HASHDATA; + } + + function GetHashDataCas($LGD_MID,$LGD_OID,$LGD_AMOUNT,$LGD_RESPCODE,$LGD_TIMESTAMP) + { + $LGD_HASHDATA = ""; + $LGD_HASHDATA = md5($LGD_MID.$LGD_OID.$LGD_AMOUNT.$LGD_RESPCODE.$LGD_TIMESTAMP.$this->config[$LGD_MID]); + return $LGD_HASHDATA; + } + + /** + @brief MertKey Bytes 변환 + @param MertKey MertKey + @date 2014.04.03 + @author SangmanPark (sangman.park@gmail.com) + @remark MertKey 를 Bytes 로 변환한다. + */ + function StringToHex($MertKey) + { + $szKey; + $szMertKey = str_split($MertKey,2); + for ($i = 0 ; $i < 16 ; $i++) + { + $szKey[$i] = Hex2Bin ($szMertKey[$i]); + } + $szKeyString = implode("", $szKey); + + //for ($i = 0 ; $i < 16 ; $i++) + //{ + // echo Bin2Hex($szKeyString[$i])."
"; + //} + + return $szKeyString; + } /** - * Close curl session and free resource - * Usually no need to call this function directly - * in case you do you have to call init() to recreate curl - * @access public - */ - function close() + @brief Micro time + @param MertKey MertKey + @date 2014.04.03 + @author SangmanPark (sangman.park@gmail.com) + @remark Micro Time 을 구한다. + */ + function getMicrotime() { - //close curl session and free up resources - curl_close($this->ch); + if (version_compare(PHP_VERSION, '5.0.0', '<')) + { + return array_sum(explode(' ', microtime())); + } + + return microtime(true); + } + + /** + @brief Encrypt And Encode + @param MertKey MertKey + @param PlainBuffer 원문 + @date 2014.04.03 + @author SangmanPark (sangman.park@gmail.com) + @remark AES128/ECB/PKCS#5 로 암호화 한후 Base64 Encoding 한다. + */ + function EncryptAndEncode($MertKey,$PlainBuffer) + { + echo "EncryptAndEncode Start
"; + $microsecond = $this->getMicrotime(); + $PlainBufferTemp = sprintf("%lf", $microsecond); + $PlainBufferTemp = substr($PlainBufferTemp,-3); + $LgdValue = ":LGD:".$PlainBufferTemp; + $PlainBuffer .= $LgdValue; + $key = $this->StringToHex($MertKey); + $EncrytBuffer = mcrypt_ecb(MCRYPT_RIJNDAEL_128,$key,$PlainBuffer,MCRYPT_ENCRYPT); + $EncryptAndEncodeBuffer = base64_encode($EncrytBuffer); + $EncryptReturnValue = $EncryptAndEncodeBuffer; + echo "EncryptReturnValue = " . $EncryptReturnValue . "
"; + echo "EncryptAndEncode Complete
"; + return $EncryptReturnValue; + } + + /** + @brief Decode And Decrypt + @param MertKey MertKey + @param EncryptAndEncodeBuffer Encrypt And Encode String + @date 2014.04.03 + @author SangmanPark (sangman.park@gmail.com) + @remark Base64 Decoding 한후 AES128/ECB/PKCS#5 로 복호화 한다. + */ + function DecodeAndDecrypt($MertKey,$EncryptAndEncodeBuffer) + { + echo "DecodeAndDecrypt Start
"; + $key = $this->StringToHex($MertKey); + $DecodeBuffer = base64_decode($EncryptAndEncodeBuffer); + $DecryptBuffer = mcrypt_ecb(MCRYPT_RIJNDAEL_128,$key,$DecodeBuffer,MCRYPT_DECRYPT); + $UnPaddString = $this->pkcs5_unpad($DecryptBuffer); + echo "UnPaddString : ".$UnPaddString . "
"; + echo "DecodeAndDecrypt Complete
"; + $ConvertCharset = iconv("euc-kr", "utf-8", $UnPaddString); + return $ConvertCharset; + + } + + /** + * PKCS5 패딩추가 + * @param string $text + * @param int $blocksize + * @return string + */ + function pkcs5_pad($text, $blocksize) + { + $pad = $blocksize - (strlen($text) % $blocksize); + return $text . str_repeat(chr($pad), $pad); + } + + /** + * PKCS5 패딩제거 + * @param string $text + * @return boolean|string + */ + function pkcs5_unpad($text) + { + $pad = ord($text{strlen($text)-1}); + if ($pad > strlen($text)) return false; + if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; + return substr($text, 0, -1 * $pad); } } ?> \ No newline at end of file diff --git a/plugin/lgxpay/lgdacom/conf/mall.conf b/plugin/lgxpay/lgdacom/conf/mall.conf index 2629bbb18..fc8d5eaf2 100644 --- a/plugin/lgxpay/lgdacom/conf/mall.conf +++ b/plugin/lgxpay/lgdacom/conf/mall.conf @@ -4,8 +4,8 @@ server_id = 01 ;timeout API û timeout ð (:) timeout = 60 -;log_level 0: FATAL; 1: ERROR; 2: WARNING; 3: INFO; 4: DEBUG -log_level = 4 +;log_level 3: INFO; 4: DEBUG +log_level = 3 ;verify_cert 1: ; 0: verify_cert = 1 @@ -24,11 +24,14 @@ auto_rollback = 1 ;log_dir log directory full path (α ġ log ݵ αװ ) -log_dir = c:\lgdacom\log +log_dir = C:\lgdacom\log + +; 512 (TLS1.1) , 2048 (TLS1.2) , 2560 (TLS1.0) java / php +default_secure_protocols=2048 ;>>>>>>>>>>>>>>>>>>>> ݵ Է ּ <<<<<<<<<<<<<<<<<<<< -; ID LGڷ ߱޹ ̵ Էϼ. (߱޹ ̵ տ "t" ̽ø ׽Ʈ̵ Դϴ.) +; ID LG U+ ߱޹ ̵ Էϼ. (߱޹ ̵ տ "t" ̽ø ׽Ʈ̵ Դϴ.) ;MertKey -> -> --> ýۿ ȮϽǼ ֽϴ. ; ID = MertKey ( ׽Ʈ, 2 ̵ Էּ. Է½ ּǥ(;) ø ȵ˴ϴ.)