// Define LIVE constant as true if 'localhost' is not present in the host name. Configure the detecting of environment as necessary of course. defined('LIVE') || define('LIVE', strpos($_SERVER['HTTP_HOST'],'localhost')===false ? true : false); if (LIVE) { define('EPAY_URL','https://www.epay.bg/'); define('EPAY_CLIENT_EMAIL','*********'); define('EPAY_CLIENT_NUMBER','********'); define('EPAY_SECRET_KEY','********'); }else{ define('EPAY_URL','https://devep2.datamax.bg/ep2/epay2_demo/'); define('EPAY_CLIENT_EMAIL','*********'); define('EPAY_CLIENT_NUMBER','********'); define('EPAY_SECRET_KEY','********'); }
I. In the view script add the following hidden fields:
$form=$this->beginWidget('CActiveForm', array( 'id'=>'orderForm', 'htmlOptions'=>array('onsubmit'=>'return false;'), 'action'=>EPAY_URL, )); // epay.bg fields echo CHtml::hiddenField('PAGE','paylogin'); echo CHtml::hiddenField('ENCODED','',array('id'=>'epayEncoded')); echo CHtml::hiddenField('CHECKSUM','',array('id'=>'epayChecksum')); echo CHtml::hiddenField('URL_OK',Yii::app()->createAbsoluteUrl('order/success')); echo CHtml::hiddenField('URL_CANCEL',Yii::app()->createAbsoluteUrl('order/canceled'));
II. Further down in the same view file we will put JavaScript code that will send request to OrderController::actionCreate() to create the order record in the database and on success we will send another request to OrderController::actionEpayData() to generate the needed data for the hidden fields ENCODING and CHECKSUM.
$.post('<?php echo Yii::app()->createUrl('order/create'); ?>',$('#orderForm').serializeArray(),function(orderResp) { if(orderResp.error === undefined){ $.post('<?php echo url('order/epayData')?>',{orderId:orderResp.id, price:orderResp.total, productName:'<?php echo $productLang->title?>'}, function(epayResp){ $('#epayEncoded').val(epayResp.encoded); $('#epayChecksum').val(epayResp.checksum); $('#orderForm').attr({action:'<?php echo EPAY_URL?>',onsubmit:true}).submit(); },'json'); } }else{ alert(orderResp.error); },'json');
III. In the class OrderController add these methods:
public function actionEpayData(){ $epay=new EpayBg(); echo CJavaScript::jsonEncode($epay->getInitData($_POST)); Yii::app()->end(); } public function actionEpayNotify(){ $epay = new EpayBg(); $epay->notify(); }
IV. Create a file in protected/components/EpayBg.php
class EpayBg { public function getInitData($post){ $dt=new DateTime('+1 day'); $expDate=$dt->format('d.m.Y'); $min=EPAY_CLIENT_NUMBER; $data = <<<DATA MIN={$min} INVOICE={$post['orderId']} AMOUNT={$post['price']} EXP_TIME={$expDate} DESCR={$post['productName']} DATA; # XXX Packet: # (MIN or EMAIL)= REQUIRED # INVOICE= REQUIRED # AMOUNT= REQUIRED # EXP_TIME= REQUIRED # DESCR= OPTIONAL $encoded = base64_encode($data); return array( 'encoded'=>$encoded, 'checksum'=>$this->hmac('sha1', $encoded, EPAY_SECRET_KEY) ); } public function notify(){ $logCat='epay'; if(empty($_POST['encoded']) || empty($_POST['checksum'])){ Yii::log('Missing encoded or checksum POST variables', CLogger::LEVEL_INFO, $logCat); }else{ $encoded = $_POST['encoded']; $checksum = $_POST['checksum']; $hmac = $this->hmac('sha1', $encoded, EPAY_SECRET_KEY); # XXX SHA-1 algorithm REQUIRED if ($hmac == $checksum) { # XXX Check if the received CHECKSUM is OK $data = base64_decode($encoded); $lines_arr = split("\n", $data); $infoData = ''; foreach ($lines_arr as $line) { if (preg_match("/^INVOICE=(\d+):STATUS=(PAID|DENIED|EXPIRED)(:PAY_TIME=(\d+):STAN=(\d+):BCODE=([0-9a-zA-Z]+))?$/", $line, $regs)) { Yii::log($line,CLogger::LEVEL_INFO,$logCat); $invoice = $regs[1]; // order id $status = $regs[2]; $payDate = $regs[4]; # YYYYMMDDHHIISS $stan = $regs[5]; # XXX if PAID $bcode = $regs[6]; # XXX if PAID # XXX process $invoice, $status, $payDate, $stan, $bcode here # XXX if OK for this invoice $infoData .= "INVOICE=$invoice:STATUS=OK\n"; if($status==='PAID'){ $model=Order::model()->findByPk($invoice); if($model===null){ Yii::log($invoice.' order not found',CLogger::LEVEL_INFO,$logCat); }else{ $model->setAttributes(array( 'payDate'=>implode('-',array(substr($payDate,0,4),substr($payDate,4,2),substr($payDate,6,2))).' '. implode(':',array(substr($payDate,8,2),substr($payDate,10,2),substr($payDate,12,2))), 'stan'=>$stan, 'bcode'=>$bcode, 'statusId'=>Order::STATUS_PAID )); $model->save(); Product::deductQty($model); Product::sendSuccessEmails($model); } } } } echo $infoData, "\n"; } else { echo "ERR=Not valid CHECKSUM\n"; Yii::log('ERR=Not valid CHECKSUM',CLogger::LEVEL_ERROR,$logCat); } } } private function hmac($algo,$data,$passwd){ /* md5 and sha1 only */ $algo=strtolower($algo); $p=array('md5'=>'H32','sha1'=>'H40'); if(strlen($passwd)>64) $passwd=pack($p[$algo],$algo($passwd)); if(strlen($passwd)<64) $passwd=str_pad($passwd,64,chr(0)); $ipad=substr($passwd,0,64) ^ str_repeat(chr(0x36),64); $opad=substr($passwd,0,64) ^ str_repeat(chr(0x5C),64); return($algo($opad.pack($p[$algo],$algo($ipad.$data)))); } }
V. The Order model would start with:
class Order extends CActiveRecord { const STATUS_INITIATED = 1; const STATUS_CANCELED = 2; const STATUS_EXPIRED = 3; const STATUS_PAID = 4; public $statuses = array( self::STATUS_INITIATED => 'Initiated', self::STATUS_CANCELED => 'Canceled', self::STATUS_EXPIRED => 'Expired', self::STATUS_PAID => 'Paid', ); // more code of the model
The classes OrderController and EpayBg have methods for handling both initializing the payment and the notification they send about the payment status. Epay will be sending notifications until the notify script sends correct message i.e. "INVOICE=$invoice:STATUS=OK\n". Make sure there's no other output or they will continue sending notifications. Good luck.
Total 2 comments
Браво, наистина много полезно.
Много полезно четиво. Браво!!!
Leave a comment
Please login to leave your comment.