From dee4f6a3a5e3cc37a300f5f013eae584d3aac8f3 Mon Sep 17 00:00:00 2001 From: Semenov Date: Fri, 8 Apr 2016 17:06:42 +0300 Subject: [PATCH] Added payPal --- CHANGELOG.md | 3 +- components/PaySystemHandlerComponent.php | 10 ++ controllers/OrderController.php | 17 ++- controllers/PayPalController.php | 97 +++++++++++++ controllers/RobokassaController.php | 17 ++- paySystems/PayPalPaySystem.php | 174 ++++++++++++++++++++++- paySystems/RobokassaPaySystem.php | 26 +++- paySystems/robokassa/Merchant.php | 11 +- views/order/pay-pal.php | 10 +- 9 files changed, 335 insertions(+), 30 deletions(-) create mode 100644 controllers/PayPalController.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be944b9..0b9cce21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Changelog 1.0.0-alpha6.pre ----------------- - * PayPal + * Rewrite robokassa + * Added payPal 1.0.0-alpha5 ----------------- diff --git a/components/PaySystemHandlerComponent.php b/components/PaySystemHandlerComponent.php index 7a7571dc..3dfa35f6 100644 --- a/components/PaySystemHandlerComponent.php +++ b/components/PaySystemHandlerComponent.php @@ -22,4 +22,14 @@ class PaySystemHandlerComponent extends Model implements ConfigFormInterface public function renderConfigForm(ActiveForm $activeForm) {} + + static public function logError($message, $group = "") + { + \Yii::error($message, static::className() . "::" . $group); + } + + static public function logInfo($message, $group = "") + { + \Yii::info($message, static::className() . "::" . $group); + } } \ No newline at end of file diff --git a/controllers/OrderController.php b/controllers/OrderController.php index 480b4c4a..65e090a5 100644 --- a/controllers/OrderController.php +++ b/controllers/OrderController.php @@ -81,6 +81,16 @@ public function behaviors() ]); } + public function beforeAction($action) + { + if ($action->id == 'view') + { + $this->enableCsrfValidation = false; + } + + return parent::beforeAction($action); + } + /** * @return string */ @@ -122,11 +132,4 @@ public function actionPayPal() ]); } - public function actionPayPalNotify() - { - \Yii::error(serialize(\Yii::$app->request->get()), 'paypal'); - \Yii::error(serialize(\Yii::$app->request->post()), 'paypal'); - return ""; - } - } \ No newline at end of file diff --git a/controllers/PayPalController.php b/controllers/PayPalController.php new file mode 100644 index 00000000..c665b44f --- /dev/null +++ b/controllers/PayPalController.php @@ -0,0 +1,97 @@ + + * @link http://skeeks.com/ + * @copyright 2010 SkeekS (СкикС) + * @date 21.09.2015 + */ +namespace skeeks\cms\shop\controllers; + +use skeeks\cms\base\Controller; +use skeeks\cms\components\Cms; +use skeeks\cms\filters\CmsAccessControl; +use skeeks\cms\helpers\RequestResponse; +use skeeks\cms\shop\models\ShopBasket; +use skeeks\cms\shop\models\ShopBuyer; +use skeeks\cms\shop\models\ShopFuser; +use skeeks\cms\shop\models\ShopOrder; +use skeeks\cms\shop\models\ShopPersonType; +use skeeks\cms\shop\models\ShopPersonTypeProperty; +use skeeks\cms\shop\models\ShopProduct; +use skeeks\cms\shop\paySystems\PayPalPaySystem; +use skeeks\cms\shop\paySystems\robokassa\Merchant; +use skeeks\cms\shop\paySystems\RobokassaPaySystem; +use yii\base\Exception; +use yii\filters\AccessControl; +use yii\filters\VerbFilter; +use yii\helpers\ArrayHelper; +use yii\helpers\Json; +use yii\helpers\Url; +use yii\web\BadRequestHttpException; + +/** + * Class RobocassaController + * @package skeeks\cms\shop\controllers + */ +class PayPalController extends Controller +{ + /** + * @inheritdoc + */ + public $enableCsrfValidation = false; + + public function actionIpn() + { + $custom = (int) \Yii::$app->request->post('custom'); + \Yii::info('Ipn post data: ' . serialize(\Yii::$app->request->post()), 'paypal'); + \Yii::info('Ipn custom: ' . $custom, 'paypal'); + + /*if (!$custom) + { + $custom = (int) \Yii::$app->request->get('custom'); + }*/ + + if (!$custom) + { + \Yii::error('Order id not found', 'paypal'); + return false; + } + + $shopOrder = ShopOrder::findOne($custom); + \Yii::info('Ordder id: ' . $shopOrder->id); + + if (!$shopOrder) + { + \Yii::error('Ordder not found: ' . $custom, 'paypal'); + } + + /** + * @var $payPal PayPalPaySystem + * @var $shopOrder ShopOrder + */ + $payPal = $shopOrder->paySystem->paySystemHandler; + if (!$payPal instanceof PayPalPaySystem) + { + \Yii::error('Order handler not paypal: ', 'paypal'); + } + + if ($payPal->initIpn()) + { + if ($shopOrder->payed != "Y") + { + \Yii::info('Order processNotePayment', 'paypal'); + $shopOrder->processNotePayment(); + } + + $shopOrder->ps_status = "STATUS_SUCCESS"; + $shopOrder->payed = "Y"; + $shopOrder->save(); + + } else + { + \Yii::error('Ipn false: ', 'paypal'); + } + + return ""; + } +} \ No newline at end of file diff --git a/controllers/RobokassaController.php b/controllers/RobokassaController.php index 5e70f8b4..270842de 100644 --- a/controllers/RobokassaController.php +++ b/controllers/RobokassaController.php @@ -55,9 +55,11 @@ class RobokassaController extends Controller public function actionSuccess() { + RobokassaPaySystem::logInfo('success request'); if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'], $_REQUEST['SignatureValue'])) { + RobokassaPaySystem::logError('Not found params'); throw new BadRequestHttpException('Not found params'); } @@ -65,7 +67,6 @@ public function actionSuccess() $merchant = $this->getMerchant($order); $shp = $this->getShp(); - if ($merchant->checkSignature($_REQUEST['SignatureValue'], $_REQUEST['OutSum'], $_REQUEST['InvId'], $merchant->sMerchantPass1, $shp)) { $order->ps_status = "STATUS_ACCEPTED"; @@ -73,14 +74,18 @@ public function actionSuccess() return $this->redirect(Url::to(['/shop/order/view', 'id' => $order->id])); } + RobokassaPaySystem::logError('bad signature'); throw new BadRequestHttpException('bad signature'); } public function actionResult() { + RobokassaPaySystem::logInfo('result request'); + if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'], $_REQUEST['SignatureValue'])) { + RobokassaPaySystem::logError('Not found params'); throw new BadRequestHttpException('Not found params'); } @@ -90,8 +95,7 @@ public function actionResult() if ($merchant->checkSignature($_REQUEST['SignatureValue'], $_REQUEST['OutSum'], $_REQUEST['InvId'], $merchant->sMerchantPass2, $shp)) { - - + RobokassaPaySystem::logInfo('result signature OK'); if ($order->payed != "Y") { $order->processNotePayment(); @@ -104,13 +108,18 @@ public function actionResult() return 'Ok'; } + RobokassaPaySystem::logError('bad signature'); + throw new BadRequestHttpException; } public function actionFail() { + RobokassaPaySystem::logInfo('fail request'); + if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'])) { + RobokassaPaySystem::logError('Not found params'); throw new BadRequestHttpException; } @@ -190,6 +199,7 @@ protected function getMerchant(ShopOrder $order) $paySystemHandler = $order->paySystem->paySystemHandler; if (!$paySystemHandler || !$paySystemHandler instanceof RobokassaPaySystem) { + RobokassaPaySystem::logError('Not found pay system'); throw new BadRequestHttpException('Not found pay system'); } @@ -197,6 +207,7 @@ protected function getMerchant(ShopOrder $order) if (!$merchant instanceof Merchant) { + RobokassaPaySystem::logError('Not found merchant'); throw new BadRequestHttpException('Not found merchant'); } diff --git a/paySystems/PayPalPaySystem.php b/paySystems/PayPalPaySystem.php index fe8fe17f..95032088 100644 --- a/paySystems/PayPalPaySystem.php +++ b/paySystems/PayPalPaySystem.php @@ -13,15 +13,23 @@ use yii\widgets\ActiveForm; /** + * @property string $payPalUrl + * * Class PayPalPaySystem * @package skeeks\cms\shop\paySystems */ class PayPalPaySystem extends PaySystemHandlerComponent { - public $payNowButtonUrl = 'https://www.sandbox.paypal.com/cgi-bin/websc'; //https://auth.robokassa.ru/Merchant/Index.aspx public $receiverEmail = 'semenov-facilitator@skeeks.com'; - public $sMerchantPass1 = ''; - public $sMerchantPass2 = ''; + public $isLive = true; + + /** + * @return string + */ + public function getPayPalUrl() + { + return $this->isLive ? 'https://www.paypal.com/cgi-bin/websc' : 'https://www.sandbox.paypal.com/cgi-bin/websc'; + } /** * Можно задать название и описание компонента @@ -37,8 +45,7 @@ static public function descriptorConfig() public function rules() { return ArrayHelper::merge(parent::rules(), [ - [['payNowButtonUrl'], 'string'], - [['payNowButtonUrl'], 'url'], + [['isLive'], 'boolean'], [['receiverEmail'], 'string'], ]); } @@ -46,8 +53,15 @@ public function rules() public function attributeLabels() { return ArrayHelper::merge(parent::attributeLabels(), [ - 'payNowButtonUrl' => \skeeks\cms\shop\Module::t('app', 'Road to the api service paypal'), 'receiverEmail' => 'PayPal account email', + 'isLive' => 'Is live', + ]); + } + + public function attributeHints() + { + return ArrayHelper::merge(parent::attributeHints(), [ + 'isLive' => 'If is live used url: https://www.paypal.com/cgi-bin/websc else https://www.sandbox.paypal.com/cgi-bin/websc', ]); } @@ -82,7 +96,153 @@ public function paymentResponse(ShopOrder $shopOrder) public function renderConfigForm(ActiveForm $activeForm) { - echo $activeForm->field($this, 'payNowButtonUrl')->textInput(); + echo $activeForm->field($this, 'isLive')->checkbox(); echo $activeForm->field($this, 'receiverEmail')->textInput(); } + + private $debug = true; + private $service; + private $projectName = 'test'; + + /** + * @throws Exception + */ + public function initIpn(){ + $postData = file_get_contents('php://input'); + $transactionType = $this->getPaymentType($postData); + + //$config = Config::get(); + + // в зависимости от типа платежа выбираем клас + if($transactionType == "web_accept"){ + //$this->service = new PaypalSinglePayment(); + } + /*elseif($transactionType == PaypalTransactionType::TRANSACTION_TYPE_SUBSCRIPTION){ + $this->service = new PaypalSubscription($config); + }*/ + else{ + \Yii::error('Wrong payment type', 'paypal'); + //throw new Exception('Wrong payment type'); + } + + $raw_post_data = file_get_contents('php://input'); + + $raw_post_array = explode('&', $raw_post_data); + $myPost = array(); + foreach ($raw_post_array as $keyval) { + $keyval = explode ('=', $keyval); + if (count($keyval) == 2) + $myPost[$keyval[0]] = urldecode($keyval[1]); + } + + $customData = $customData = json_decode($myPost['custom'],true); + $userId = $customData['user_id']; + + // read the post from PayPal system and add 'cmd' + $req = 'cmd=_notify-validate'; + if(function_exists('get_magic_quotes_gpc')) { + $get_magic_quotes_exists = true; + } + else{ + $get_magic_quotes_exists = false; + } + + + foreach ($myPost as $key => $value) { + if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) { + $value = urlencode(stripslashes($value)); + } else { + $value = urlencode($value); + } + $req .= "&$key=$value"; + } + + $myPost['customData'] = $customData; + + $paypal_url = $this->payPalUrl; + //$paypal_url = 'https://www.paypal.com/cgi-bin/websc'; + + // проверка подлинности IPN запроса + $res = $this->sendRequest($paypal_url,$req); + + // Inspect IPN validation result and act accordingly + // Split response headers and payload, a better way for strcmp + $tokens = explode("\r\n\r\n", trim($res)); + $res = trim(end($tokens)); + + /**/ + if (strcmp ($res, "VERIFIED") == 0) { + \Yii::info("VERIFIED", 'paypal'); + // продолжаем обраюотку запроса + //$this->service->processPayment($myPost); + return true; + } else if (strcmp ($res, "INVALID") == 0) { + // запрос не прощел проверку + \Yii::error("Invalid IPN: $req", 'paypal'); + /*self::log([ + 'message' => "Invalid IPN: $req" . PHP_EOL, + 'level' => self::LOG_LEVEL_ERROR + ], $myPost);*/ + } + /**/ + } + + private function sendRequest($paypal_url,$req){ + $debug = $this->debug; + + $ch = curl_init($paypal_url); + if ($ch == FALSE) { + return FALSE; + } + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $req); + + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); + if($debug == true) { + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLINFO_HEADER_OUT, 1); + } + + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + + //передаем заголовок, указываем User-Agent - название нашего приложения. Необходимо для работы в live режиме + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close', 'User-Agent: ' . $this->projectName)); + + $res = curl_exec($ch); + curl_close($ch); + + return $res; + } + + public function getPaymentType($rawPostData){ + $post = $this->getPostFromRawData($rawPostData); + + if(isset($post['subscr_id'])){ + return "subscr_payment"; + } + else{ + return "web_accept"; + } + } + + /** + * @param $raw_post_data + * @return array + */ + public function getPostFromRawData($raw_post_data){ + $raw_post_array = explode('&', $raw_post_data); + $myPost = array(); + foreach ($raw_post_array as $keyval) { + $keyval = explode ('=', $keyval); + if(count($keyval) == 2) + $myPost[$keyval[0]] = urldecode($keyval[1]); + } + + return $myPost; + } } \ No newline at end of file diff --git a/paySystems/RobokassaPaySystem.php b/paySystems/RobokassaPaySystem.php index 5f7536ba..38a4802f 100644 --- a/paySystems/RobokassaPaySystem.php +++ b/paySystems/RobokassaPaySystem.php @@ -13,12 +13,13 @@ use yii\widgets\ActiveForm; /** + * @property string $baseUrl * Class RobokassaPaySystem * @package skeeks\cms\shop\paySystems */ class RobokassaPaySystem extends PaySystemHandlerComponent { - public $baseUrl = 'http://test.robokassa.ru/Index.aspx'; //https://auth.robokassa.ru/Merchant/Index.aspx + public $isLive = true; //https://auth.robokassa.ru/Merchant/Index.aspx public $sMerchantLogin = ''; public $sMerchantPass1 = ''; public $sMerchantPass2 = ''; @@ -34,27 +35,38 @@ static public function descriptorConfig() ]); } + public function getBaseUrl() + { + return "https://auth.robokassa.ru/Merchant/Index.aspx"; + } + public function rules() { return ArrayHelper::merge(parent::rules(), [ - [['baseUrl'], 'string'], - [['baseUrl'], 'url'], [['sMerchantLogin'], 'string'], [['sMerchantPass1'], 'string'], [['sMerchantPass2'], 'string'], + [['isLive'], 'boolean'], ]); } public function attributeLabels() { return ArrayHelper::merge(parent::attributeLabels(), [ - 'baseUrl' => \skeeks\cms\shop\Module::t('app', 'Road to the api service robocassa'), + 'isLive' => 'Is live', 'sMerchantLogin' => 'sMerchantLogin', 'sMerchantPass1' => 'sMerchantPass1', 'sMerchantPass2' => 'sMerchantPass2', ]); } + public function attributeHints() + { + return ArrayHelper::merge(parent::attributeHints(), [ + 'isLive' => 'If is live used: https://auth.robokassa.ru/Merchant/Index.aspx else http://test.robokassa.ru/Index.aspx', + ]); + } + /** * @return \skeeks\cms\shop\paySystems\robokassa\Merchant * @throws \yii\base\InvalidConfigException @@ -64,8 +76,10 @@ public function getMerchant() /** * @var \skeeks\cms\shop\paySystems\robokassa\Merchant $merchant */ - $merchant = \Yii::createObject(ArrayHelper::merge($this->toArray(['baseUrl', 'sMerchantLogin', 'sMerchantPass1', 'sMerchantPass2']), [ + $merchant = \Yii::createObject(ArrayHelper::merge($this->toArray(['sMerchantLogin', 'sMerchantPass1', 'sMerchantPass2']), [ 'class' => '\skeeks\cms\shop\paySystems\robokassa\Merchant', + 'baseUrl' => $this->baseUrl, + 'isLive' => (bool) $this->isLive, ])); return $merchant; @@ -82,7 +96,7 @@ public function paymentResponse(ShopOrder $shopOrder) public function renderConfigForm(ActiveForm $activeForm) { - echo $activeForm->field($this, 'baseUrl')->textInput(); + echo $activeForm->field($this, 'isLive')->checkbox(); echo $activeForm->field($this, 'sMerchantLogin')->textInput(); echo $activeForm->field($this, 'sMerchantPass1')->textInput(); echo $activeForm->field($this, 'sMerchantPass2')->textInput(); diff --git a/paySystems/robokassa/Merchant.php b/paySystems/robokassa/Merchant.php index 9c055fba..6d9ffaaf 100644 --- a/paySystems/robokassa/Merchant.php +++ b/paySystems/robokassa/Merchant.php @@ -12,6 +12,8 @@ class Merchant extends Object public $sMerchantPass1; public $sMerchantPass2; + public $isLive = true; + public $baseUrl = 'https://auth.robokassa.ru/Merchant/Index.aspx'; public function payment($nOutSum, $nInvId, $sInvDesc = null, $sIncCurrLabel=null, $sEmail = null, $sCulture = null, $shp = []) @@ -33,16 +35,21 @@ public function payment($nOutSum, $nInvId, $sInvDesc = null, $sIncCurrLabel=null 'SignatureValue' => $sSignatureValue, 'IncCurrLabel' => $sIncCurrLabel, 'Email' => $sEmail, - 'Culture' => $sCulture, + 'Culture' => $sCulture ]; + if (!$this->isLive) + { + $data['isTest'] = 1; + } + $url .= '?' . http_build_query($data); if (!empty($shp) && ($query = http_build_query($shp)) !== '') { $url .= '&' . $query; } - Yii::$app->user->setReturnUrl(Yii::$app->request->getUrl()); + \Yii::$app->user->setReturnUrl(Yii::$app->request->getUrl()); return Yii::$app->response->redirect($url); } diff --git a/views/order/pay-pal.php b/views/order/pay-pal.php index 245e72fb..ac06c83c 100644 --- a/views/order/pay-pal.php +++ b/views/order/pay-pal.php @@ -12,11 +12,13 @@ $payPal = $model->paySystem->paySystemHandler; $returnUrl = \yii\helpers\Url::to(['/shop/order/view', 'id' => $model->id], true); -$notifyUrl = \yii\helpers\Url::to(['/shop/order/pay-pal-notify'], true); +$notifyUrl = \yii\helpers\Url::to(['/shop/pay-pal/ipn'], true); + +/*$data = 'a:33:{s:8:"mc_gross";s:5:"68.52";s:22:"protection_eligibility";s:10:"Ineligible";s:8:"payer_id";s:13:"GB94ESEPPPB42";s:3:"tax";s:4:"0.00";s:12:"payment_date";s:25:"00:37:35 Apr 08, 2016 PDT";s:14:"payment_status";s:9:"Completed";s:7:"charset";s:6:"KOI8_R";s:10:"first_name";s:9:"Alexander";s:6:"mc_fee";s:5:"12.67";s:14:"notify_version";s:3:"3.8";s:6:"custom";s:1:"{";s:12:"payer_status";s:8:"verified";s:8:"business";s:30:"semenov-facilitator@skeeks.com";s:8:"quantity";s:1:"1";s:11:"verify_sign";s:56:"An5ns1Kso7MWUdW4ErQKJJJ4qi4-ALfkkgv3i0h-cHWSy3tgwVF8d7UG";s:11:"payer_email";s:23:"semenov-test@skeeks.com";s:6:"txn_id";s:17:"2T609443LF1147527";s:12:"payment_type";s:7:"instant";s:9:"last_name";s:7:"Semenov";s:14:"receiver_email";s:30:"semenov-facilitator@skeeks.com";s:11:"payment_fee";s:0:"";s:11:"receiver_id";s:13:"MSPKLDZ5MLELE";s:8:"txn_type";s:10:"web_accept";s:9:"item_name";s:9:"Order #19";s:11:"mc_currency";s:3:"RUB";s:11:"item_number";s:0:"";s:17:"residence_country";s:2:"RU";s:8:"test_ipn";s:1:"1";s:15:"handling_amount";s:4:"0.00";s:19:"transaction_subject";s:0:"";s:13:"payment_gross";s:0:"";s:8:"shipping";s:4:"0.00";s:12:"ipn_track_id";s:13:"e008e0a49d8a1";}'; +print_r(unserialize($data));die;*/ $money = $model->money->convertToCurrency("RUB"); -$customData = ['product_id' => $model->id]; $this->registerJs(<<
-
+ @@ -40,7 +42,7 @@ - +