Saturday, January 10, 2009

PayPal Zend Framework Validator

I was presented with a project where the client wanted a form that would accept credit card payments via paypal.  I could either write it in PHP or Java. While I am a big fan of both languages I felt in this instance that PHP was the most time efficient route.   However, PayPal doesn't offer an API for PHP. Only Java and .NET.  As to not be discouraged I decided to spend a little time trying to come with a custom validator that would handle the validation of the credit card validation.

::Warning::
My use case was very specific.  I only needed to authorize credit card sales.  Basically their simpliest form of transations.

Since all the applications that I write now are us the Zend Framework I was able to reduce "inline-code" by creating a custom validator that I assign to my preValidation method inside of the model.  :: I worked on this concept with Jeremy Kendall ::

There is definitely room for improvement, but this is might be helpful to others.  For example, the paypal options should really not be declared as Zend_Config instance, but checked for an instance of Zend_config and then set as an array with the toArray method.


// Validator

/**
* @see Zend_Validate_Abstract
*/
require_once 'Zend/Validate/Abstract.php';

/**
* @see Zend_Http_Client
*/
require_once 'Zend/Http/Client.php';

class SJCRH_Validate_PayFlow extends Zend_Validate_Regex {

const CHARGE_FAILED = "ppChargeFailed";

const CHARGE_EXCEPTED_CODE = "0";

/**
* Error message to display to the user if the credit card transaction fails
*
* @access protected
* @var array
*/
protected $_messageTemplates = array(
self::CHARGE_FAILED => "There was an error processing your card. Error code: '%code%' Error message: '%msg%'"
);

/**
* @var array
*/
protected $_messageVariables = array(
'code' => '_code',
'msg' => '_msg'
);

/**
* Error Code value
*
* @var mixed
*/
protected $_code;

/**
* Error Message
*
* @param Zend_Config $options
*/
protected $_msg;


/**
* CC validation using PayFlow
*
* @param Zend_Config $options
* @param string $ccnum
* @param string $exp
* @param array $billing
* @param string $amount
* @param string $country
*/
public function __construct(Zend_Config $paypaloptions, $ccnum, $amount, $exp, $billing = array(), $country = 'US') {

if (!$paypaloptions instanceof Zend_Config) {
throw new Exception("Options must be an instance of Zend Config");
} else {
$options = $paypaloptions->toArray();
}

/**
* Random string used to error checking with payflow - DUPLICATION TRANSACTIONS
*/
$requestId = md5(date('YmdGis'));

$zfClient = new Zend_Http_Client($options['url']);

$zfClient->setHeaders(array('X-VPS-Request-ID' => $requestId));

/**
* Recommended from their documentation to change the timeout from 30 seconds (default)
* to 45
*/
$zfClient->setConfig(array('timeout' => 45));

$zfClient->setMethod(Zend_Http_Client::POST);

$zfClient->setParameterPost(array(
'USER' => $options['username'],
'VENDOR' => $options['vendor'],
'PARTNER' => $options['partner'],
'PWD' => $options['password'],
'FIRSTNAME' => $billing['firstname'],
'LASTNAME' => $billing['lastname'],
'STREET' => $billing['street'],
'ZIP' => $billing['zip'],
'TENDER' => 'C',
'TRXTYPE' => 'S',
'ACCT' => $ccnum,
'EXPDATE' => $exp,
'AMT' => $amount,
'CURRENCY' => 'USD',
'COUNTRY' => $country,
'CLIENTIP' => $_SERVER['REMOTE_ADDR'],
'VERBOSITY' => 'MEDIUM'
));

/**
* The response key/value pairs are seperated by ampersands.
*
* I extract the RESULT and RESPMSG
*
* If anything but a RESULT=0 is found then the code and error message are
* set to the validator's variables to display to the form user.
*/

$payFlowResponse = explode("&", $zfClient->request()->getBody());
$regex = "/(\w*)=(.*)/i";

@preg_match($regex, $payFlowResponse[0], $matches);

$this->setCode($matches[2]);

if ($this->getCode() !== self::CHARGE_EXCEPTED_CODE) {
@preg_match($regex, $payFlowResponse[2], $matches);
$this->setMsg($matches[2]);
}
}

public function isValid($value) {

$this->_setValue($value);

if ($this->getCode() !== self::CHARGE_EXCEPTED_CODE) {
$this->_error();
return false;
}

return true;
}

public function setCode($code) {
$this->_code = $code;
}

public function setMsg($msg) {
$this->_msg = $msg;
}

public function getCode() {
return $this->_code;
}

public function getMsg() {
return $this->_msg;
}
}

No comments: