<?php

#******************************************************************************
#* Name          : PxAccess.inc
#* Description   : The objects for PX Payment page  
#* Copyright (c) : 2004 Direct Payment solutions
#* Date          : 2003-12-24
#* Modifications : 2003-12-24 MifMessage class
#*				 : 2004-09-01 PxAccess, PxPayRequest, PxPayResponse classes
#*							  which encapsulate 3-DES to handle payment requests and
#*							  response.
#*				   2004-10-14 Implements complete transactions
#*				   2005-03-14 change unpack("H*", $enc); to unpack("H$enclen", $enc); 
#*							  due to the version 4.3.10 Php unpack function bugs
#*Version		 : 2.01.08
#******************************************************************************

# MifMessage.
# Use this class to parse a DPS PX MifMessage in XML form,
# and access the content.
class MifMessage
{
  var $xml_;
  var $xml_index_;
  var $xml_value_;

  # Constructor:
  # Create a MifMessage with the specified XML text.
  # The constructor returns a null object if there is a parsing error.
  function __construct($xml)
  {
    $p = xml_parser_create();
    xml_parser_set_option($p,XML_OPTION_CASE_FOLDING,0);
    $ok = xml_parse_into_struct($p, $xml, $value, $index);
    xml_parser_free($p);
    if ($ok)
    {
      $this->xml_ = $xml;
      $this->xml_value_ = $value;
      $this->xml_index_ = $index;
    }
    #print_r($this->xml_value_); # JH_DEBUG
  }

  # Return the value of the specified top-level attribute.
  # This method can only return attributes of the root element.
  # If the attribute is not found, return "".
  function get_attribute($attribute)
  {
    #$attribute = strtoupper($attribute);
    $attributes = $this->xml_value_[0]["attributes"];
    return $attributes[$attribute];
  }

  # Return the text of the specified element.
  # The element is given as a simplified XPath-like name.
  # For example, "Link/ServerOk" refers to the ServerOk element
  # nested in the Link element (nested in the root element).
  # If the element is not found, return "".
  function get_element_text($element)
  {
    #print_r($this->xml_value_); # JH_DEBUG
    $index = $this->get_element_index($element, 0);
    if ($index == 0)
    {
      return "";
    }
    else
    {
	## TW2004-09-24: Fixed bug when elemnt existent but empty
    #
    $elementObj = $this->xml_value_[$index];
    if (! array_key_exists("value", $elementObj))
      return "";
   
    return $this->xml_value_[$index]["value"];
    }
  }

  # (internal method)
  # Return the index of the specified element,
  # relative to some given root element index.
  #
  function get_element_index($element, $rootindex = 0)
  {
    #$element = strtoupper($element);
    $pos = strpos($element, "/");
    if ($pos !== false)
    {
      # element contains '/': find first part
      $start_path = substr($element,0,$pos);
      $remain_path = substr($element,$pos+1);
      $index = $this->get_element_index($start_path, $rootindex);
      if ($index == 0)
      {
        # couldn't find first part; give up.
        return 0;
      }
      # recursively find rest
      return $this->get_element_index($remain_path, $index);
    }
    else
    {
      # search from the parent across all its children
      # i.e. until we get the parent's close tag.
      $level = $this->xml_value_[$rootindex]["level"];
      if ($this->xml_value_[$rootindex]["type"] == "complete")
      {
        return 0;   # no children
      }
      $index = $rootindex+1;
      while ($index<count($this->xml_value_) && 
             !($this->xml_value_[$index]["level"]==$level && 
               $this->xml_value_[$index]["type"]=="close"))
      {
        # if one below parent and tag matches, bingo
        if ($this->xml_value_[$index]["level"] == $level+1 &&
#            $this->xml_value_[$index]["type"] == "complete" &&
            $this->xml_value_[$index]["tag"] == $element)
        {
          return $index;
        }
        $index++;
      }
      return 0;
    }
  }
}

class PxAccess
{
	var $Mac_Key, $Des_Key;
	var $PxAccess_Url;
	var $PxAccess_Userid;
	function __construct($Url, $UserId, $Des_Key, $Mac_Key){
		error_reporting(E_ERROR);
		$this->Mac_Key = pack("H*",$Mac_Key);
		$this->Des_Key = pack("H*", $Des_Key);
		$this->PxAccess_Url = $Url;
		$this->PxAccess_Userid = $UserId;
	}
	function makeRequest($request)
	{
		#Validate the REquest
		if($request->validData() == false) return "" ;
			
  		#$txnId=rand(1,100000);
		$txnId = uniqid("MI");  #You need to generate you own unqiue reference. JZ:2004-08-12
		$request->setTxnId($txnId);
		$request->setTs($this->getCurrentTS());
		$request->setSwVersion("2.01.01");
		$request->setAppletType("PHPPxAccess");
		
		
		$xml = $request->toXml();
		
	  if (strlen($xml)%8 != 0)
	  {
	    $xml = str_pad($xml, strlen($xml) + 8-strlen($xml)%8); # pad to multiple of 8
	  }
	  #add MAC code JZ2004-8-16
	  $mac = $this->makeMAC($xml,$this->Mac_Key );
	  $msg = $xml.$mac;
	  #$msg = $xml;
	  $enc = $this->encrypt_tripledes($msg, $this->Des_Key); #JZ2004-08-16: Include the MAC code
	  
	  $enclen = strlen($enc) * 2;
	  
	  $enc_hex = unpack("H$enclen", $enc); #JZ2005-03-14: there is a bug in the new version php unpack function 
	  #$enc_hex = @unpack("H*", $enc); #JZ2005-03-14: there is a bug in the new version php unpack function 
	  
	  #$enc_hex = $enc_hex[""]; #use this function if PHP version before 4.3.4
	   #$enc_hex = $enc_hex[1]; #use this function if PHP version after 4.3.4
	  $enc_hex = (version_compare(PHP_VERSION, "4.3.4", ">=")) ? $enc_hex[1] :$enc_hex[""];
	 
	  $PxAccess_Redirect = "$this->PxAccess_Url?userid=$this->PxAccess_Userid&request=$enc_hex";
  
		return $PxAccess_Redirect;
		
	}
		
	#******************************************************************************
	# This function ecrypts data using 3DES via libmcrypt
	#******************************************************************************
	function encrypt_tripledes($data, $key)
	{
	# deprecated libmcrypt 2.2 encryption: use this if you have libmcrypt 2.2.x
	# $result = mcrypt_ecb(MCRYPT_DES, $key, $data, MCRYPT_ENCRYPT);
	# return $result;
	#
	# otherwise use this for libmcrypt 2.4.x and above:
	  $td = mcrypt_module_open('tripledes', '', 'ecb', '');
	  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
	  mcrypt_generic_init($td, $key, $iv);
	  $result = mcrypt_generic($td, $data);
	  #mcrypt_generic_deinit($td); #Might cause problem in some PHP version
	  return $result;
	}
	
	
	#******************************************************************************
	# This function decrypts data using 3DES via libmcrypt
	#******************************************************************************
	function decrypt_tripledes($data, $key)
	{
	# deprecated libmcrypt 2.2 encryption: use this if you have libmcrypt 2.2.x
	# $result = mcrypt_ecb(MCRYPT_DES, $key, $data, MCRYPT_DECRYPT);
	# return $result;
	#
	# otherwise use this for libmcrypt 2.4.x and above:
	  $td = mcrypt_module_open('tripledes', '', 'ecb', '');
	  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
	  mcrypt_generic_init($td, $key, $iv);
	  $result = mdecrypt_generic($td, $data);
	  #mcrypt_generic_deinit($td); #Might cause problem in some PHP version
	  return $result;
	}
	
	#JZ2004-08-16
	
	#******************************************************************************
	# Generate and return a message authentication code (MAC) for a string.
	# (Uses ANSI X9.9 procedure.)
	#******************************************************************************
	function makeMAC($msg,$Mackey){
		
	 if (strlen($msg)%8 != 0)
	  {
	  	$extra = 8 - strlen($msg)%8;
	    $msg .= str_repeat(" ", $extra); # pad to multiple of 8
	  }
	  $mac = pack("C*", 0, 0, 0, 0, 0, 0, 0, 0); # start with all zeros
	  #$mac_result = unpack("C*", $mac);
	  
	   for ( $i=0; $i<strlen($msg)/8; $i++)
	  {
	    $msg8 = substr($msg, 8*$i, 8);
	    
		$mac ^= $msg8;
	    $mac = $this->encrypt_des($mac,$Mackey);
	    
	  }
		#$mac = pack("C*", $mac);
	    #$mac_result= encrypt_des($mac, $Mackey);
		
		$mac_result	= unpack("H8", $mac);
		#$mac_result	= $mac_result[""]; #use this function if PHP version before 4.3.4
		#$mac_result	= $mac_result[1]; #use this function if PHP version after 4.3.4
		$mac_result = (version_compare(PHP_VERSION, "4.3.4", ">=")) ? $mac_result[1]: $mac_result[""];

		return $mac_result;
	  
	   
	}
	 
	#******************************************************************************
	# This function ecrypts data using DES via libmcrypt
	# JZ2004-08-16
	#******************************************************************************
	function encrypt_des($data, $key)
	{
	# deprecated libmcrypt 2.2 encryption: use this if you have libmcrypt 2.2.x
	#  $result = mcrypt_ecb(MCRYPT_3DES, $key, $data, MCRYPT_ENCRYPT);
	#  return $result;
	#
	# otherwise use this for libmcrypt 2.4.x and above:
	  $td = mcrypt_module_open('des', '', 'ecb', '');
	  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
	  mcrypt_generic_init($td, $key, $iv);
	  $result = mcrypt_generic($td, $data);
	  #mcrypt_generic_deinit($td); #Might cause problem in some PHP version
	  mcrypt_module_close($td);
	
	  return $result;
	}


	#JZ2004-08-16
	function getResponse($resp_enc){
		#global $Mac_Key;
		$enc = pack("H*", $resp_enc);
		$resp = trim($this->decrypt_tripledes($enc, $this->Des_Key));
		$xml = substr($resp, 0, strlen($resp)-8);
	    $mac = substr($resp, -8);
		$checkmac = $this->makeMac($xml, $this->Mac_Key);
		if($mac != $checkmac){
			$xml = "<success>0</success><ResponseText>Response MAC Invalid</ResponseText>";
		}
		
		$pxresp = new PxPayResponse($xml);
		return $pxresp;
	
	}

	
	
	#******************************************************************************
	# Return the current time (GMT/UTC).The return time formatted YYYYMMDDHHMMSS.
	#JZ2004-08-30
	#******************************************************************************
	function getCurrentTS()
	{
	  
	  return gmstrftime("%Y%m%d%H%M%S", time());
	}
	
	

}

#******************************************************************************
# Class for PxPay request messages.
#******************************************************************************
class PxPayRequest extends PxPayMessage
{
	var $TxnId,$UrlFail,$UrlSuccess;
	var $AmountInput, $AppletVersion, $InputCurrency;
	var $EnableAddBillCard;
	var $TS;
	
	var $AppletType;
	
	#Constructor
	function __construct(){
		$this->PxPayMessage();
		
	}
	
	function setAppletType($AppletType){
		$this->AppletType = $AppletType;
	}
	
	function getAppletType(){
		return $this->AppletType;
	}
	
	
	
	function setTs($Ts){
		$this->TS = $Ts;
	}
	function setEnableAddBillCard($EnableBillAddCard){
	 $this->EnableAddBillCard = $EnableBillAddCard;
	}
	
	function getEnableAddBillCard(){
		return $this->EnableAddBillCard;
	}
	function setInputCurrency($InputCurrency){
		$this->InputCurrency = $InputCurrency;
	}
	function getInputCurrency(){
		return $this->InputCurrency;
	}
	function setTxnId( $TxnId)
	{
		$this->TxnId = $TxnId;
	}
	function getTxnId(){
		return $this->TxnId;
	}
	
	function setUrlFail($UrlFail){
		$this->UrlFail = $UrlFail;
	}
	function getUrlFail(){
		return $this->UrlFail;
	}
	function setUrlSuccess($UrlSuccess){
		$this->UrlSuccess = $UrlSuccess;
	}
	function setAmountInput($AmountInput){
		$this->AmountInput = sprintf("%9.2f",$AmountInput); 
	}
	
	function getAmountInput(){
		return $this->AmountInput;
	}
	function setSwVersion($SwVersion){
		$this->AppletVersion = $SwVersion;
	}
	
	function getSwVersion(){
		return $this->AppletVersion;
	}
	#******************************************************************
	#Data validation 
	#******************************************************************
	function validData(){
		$msg = "";
		if($this->TxnType != "Purchase")
			if($this->TxnType != "Auth")
				if($this->TxnType != "GetCurrRate")
					if($this->TxnType != "Refund")
						if($this->TxnType != "Complete")
							if($this->TxnType != "Order1")
								$msg = "Invalid TxnType[$this->TxnType]<br>";
		
		if(strlen($this->MerchantReference) > 64)
			$msg = "Invalid MerchantReference [$this->MerchantReference]<br>";
		
		if(strlen($this->TxnId) > 16)
			$msg = "Invalid TxnId [$this->TxnId]<br>";
		if(strlen($this->TxnData1) > 255)
			$msg = "Invalid TxnData1 [$this->TxnData1]<br>";
		if(strlen($this->TxnData2) > 255)
			$msg = "Invalid TxnData2 [$this->TxnData2]<br>";
		if(strlen($this->TxnData3) > 255)
			$msg = "Invalid TxnData3 [$this->TxnData3]<br>";
			
		if(strlen($this->EmailAddress) > 255)
			$msg = "Invalid EmailAddress [$this->EmailAddress]<br>";
			
		if(strlen($this->UrlFail) > 255)
			$msg = "Invalid UrlFail [$this->UrlFail]<br>";
		if(strlen($this->UrlSuccess) > 255)
			$msg = "Invalid UrlSuccess [$this->UrlSuccess]<br>";
		if(strlen($this->BillingId) > 32)
			$msg = "Invalid BillingId [$this->BillingId]<br>";
		if(strlen($this->DpsBillingId) > 16)
			$msg = "Invalid DpsBillingId [$this->DpsBillingId]<br>";
			
		if ($msg != "") {
		    trigger_error($msg,E_USER_ERROR);
			return false;
		}
		return true;
	}

}

#******************************************************************************
# Abstract base class for PxPay messages.
# These are messages with certain defined elements,  which can be serialized to XML.

#******************************************************************************
class PxPayMessage {
	var $TxnType;
  	var $TxnData1;
  	var $TxnData2;
  	var $TxnData3;
  	var $MerchantReference;
  	var $EmailAddress;
  	var $BillingId;
  	var $DpsBillingId;
	var $DpsTxnRef;
	
	function __construct(){
	
	}
	function setDpsTxnRef($DpsTxnRef){
		$this->DpsTxnRef = $DpsTxnRef;
	}
	
	function getDpsTxnRef(){
		return $this->DpsTxnRef;
	}
	
	function setDpsBillingId($DpsBillingId){
		$this->DpsBillingId = $DpsBillingId;
	}
	
	function getDpsBillingId(){
		return $this->DpsBillingId;
	}
	function setBillingId($BillingId){
		$this->BillingId = $BillingId;
	}
	
	function getBillingId(){
		return $this->BillingId;
	}
	function setTxnType($TxnType){
		$this->TxnType = $TxnType;
	}
	function getTxnType(){
		return $this->TxnType;
	}
	function setMerchantReference($MerchantReference){
		$this->MerchantReference = $MerchantReference;
	}
	
	function getMerchantReference(){
		return $this->MerchantReference;
	}
	function setEmailAddress($EmailAddress){
		$this->EmailAddress = $EmailAddress;
		
	}
	
	function getEmailAddress(){
		return $this->EmailAddress;
	}
	
	function setTxnData1($TxnData1){
		$this->TxnData1 = $TxnData1;
		
	}
	function getTxnData1(){
		return $this->TxnData1;
	}
	function setTxnData2($TxnData2){
		$this->TxnData2 = $TxnData2;
		
	}
	function getTxnData2(){
		return $this->TxnData2;
	}
	
	function getTxnData3(){
		return $this->TxnData3;
	}
	function setTxnData3($TxnData3){
		$this->TxnData3 = $TxnData3;
		
	}
	function toXml(){
		$arr = get_object_vars($this);
		$root = get_class($this);
		if($root == "PxPayRequest")
			$root = "Request";
		elseif ($root == "PxPayResponse")
			$root = "Response";
		else
			$root ="Request";
			
		$xml  = "<$root>";
    	while (list($prop, $val) = each($arr))
        	$xml .= "<$prop>$val</$prop>" ;

		$xml .= "</$root>";
		return $xml;
	}
	
	
}

#******************************************************************************
# Class for PxPay response messages.
#******************************************************************************

class PxPayResponse extends PxPayMessage
{
	var $Success;
  var $StatusRequired;
  var $Retry;
  var $AuthCode;
  var $AmountSettlement;
  var $CurrencySettlement;
  var $CardName;
  var $CurrencyInput;
  var $UserId;
  var $ResponseText;
  #var $DpsTxnRef;
  var $MerchantTxnId;
  var $TS;
  
	function __construct($xml){
		$msg = new MifMessage($xml);
		$this->PxPayMessage();
		
		$TS = $msg->get_element_text("TS");
		$expiryTS = $this->getExpiredTS();
		if(strcmp($TS, $expiryTS) < 0 ){
			$this->Success = "0";
			$this->ResponseText = "Response TS out of range";
			return;
		}
		
		$this->setBillingId($msg->get_element_text("BillingId"));
		$this->setDpsBillingId($msg->get_element_text("DpsBillingId"));
		$this->setEmailAddress($msg->get_element_text("EmailAddress"));
		$this->setMerchantReference($msg->get_element_text("MerchantReference"));
		$this->setTxnData1($msg->get_element_text("TxnData1"));
		$this->setTxnData2($msg->get_element_text("TxnData2"));
		$this->setTxnData3($msg->get_element_text("TxnData3"));
		$this->setTxnType($msg->get_element_text("TxnType"));
		
		$this->Success = $msg->get_element_text("Success");
		$this->StatusRequired = $msg->get_element_text("StatusRequired");
		$this->Retry = $msg->get_element_text("Retry");
		$this->AuthCode = $msg->get_element_text("AuthCode");
		$this->AmountSettlement = $msg->get_element_text("AmountSettlement");
		$this->CurrencySettlement = $msg->get_element_text("CurrencySettlement");
		$this->CardName = $msg->get_element_text("CardName");
		$this->CurrencyInput = $msg->get_element_text("CurrencyInput");
		$this->UserId = $msg->get_element_text("UserId");
		$this->ResponseText = $msg->get_element_text("ResponseText");
		$this->DpsTxnRef = $msg->get_element_text("DpsTxnRef");
		$this->MerchantTxnId = $msg->get_element_text("MerchantTxnId");
		$this->TS = $msg->get_element_text("TS");
	}
	function getTS(){
		return $this->TS;
	}
	function getMerchantTxnId(){
		return $this->MerchantTxnId;
	}
	
	function getResponseText(){
		return $this->ResponseText;
	}
	function getUserId(){
		return $this->UserId;
	}
	function getCurrencyInput(){
		return $this->CurrencyInput;
	}
	function getCardName(){
		return $this->CardName;
	}
	function getCurrencySettlement(){
		$this->CurrencySettlement;
	}
	function getAmountSettlement(){
		return $this->AmountSettlement;
	}
	function getSuccess(){
		return $this->Success;
	}
	function getStatusRequired(){
		return $this->StatusRequired;
	}
	function getRetry(){
		return $this->Retry;
	}
	function getAuthCode(){
		return $this->AuthCode;
	}
	#******************************************************************************
	# Return the expired time, i.e. 2 days ago (GMT/UTC).
	#JZ2004-08-30
	#******************************************************************************
	function  getExpiredTS()
	{
	  
	  return gmstrftime("%Y%m%d%H%M%S", time()- 2 * 24 * 60 * 60);
	}
	
}

?>
