SagePay.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <?php
  2. /**
  3. *
  4. * PHP Pro Bid $Id$ dewKK71K7ZWVYpZ56VQJGR/Vd+iQ6tJiFee6XhwPNyE=
  5. *
  6. * @link http://www.phpprobid.com
  7. * @copyright Copyright (c) 2014 Online Ventures Software LTD & CodeCube SRL
  8. * @license http://www.phpprobid.com/license Commercial License
  9. *
  10. * @version 7.2
  11. */
  12. /**
  13. * SagePay payment gateway model class
  14. */
  15. namespace Ppb\Model\PaymentGateway;
  16. use Cube\Controller\Request\AbstractRequest,
  17. Ppb\Service;
  18. class SagePay extends AbstractPaymentGateway
  19. {
  20. /**
  21. * payment gateway name
  22. */
  23. const NAME = 'SagePay';
  24. /**
  25. * required settings
  26. */
  27. const VENDOR = 'Vendor';
  28. const PASSWORD = 'Password';
  29. /**
  30. * form post url
  31. */
  32. const POST_URL = 'https://live.sagepay.com/gateway/service/vspform-register.vsp';
  33. /**
  34. * form post url (sandbox)
  35. */
  36. const SANDBOX_POST_URL = 'https://test.sagepay.com/gateway/service/vspform-register.vsp';
  37. /**
  38. * sagepay description
  39. */
  40. protected $_description = 'Click to pay through SagePay.';
  41. public function __construct($userId = null)
  42. {
  43. parent::__construct(self::NAME, $userId);
  44. }
  45. /**
  46. *
  47. * check if the gateway is enabled
  48. *
  49. * @return bool
  50. */
  51. public function enabled()
  52. {
  53. if (!empty($this->_data[self::VENDOR]) && !empty($this->_data[self::PASSWORD])) {
  54. return true;
  55. }
  56. return false;
  57. }
  58. /**
  59. *
  60. * get setup form elements
  61. *
  62. * @return array
  63. */
  64. public function getElements()
  65. {
  66. $translate = $this->getTranslate();
  67. return array(
  68. array(
  69. 'form_id' => 'SagePay',
  70. 'id' => self::VENDOR,
  71. 'element' => 'text',
  72. 'label' => $this->_('SagePay Vendor Name'),
  73. 'description' => $this->_('Enter your SagePay vendor name'),
  74. 'attributes' => array(
  75. 'class' => 'form-control input-medium',
  76. ),
  77. ),
  78. array(
  79. 'form_id' => 'SagePay',
  80. 'id' => self::PASSWORD,
  81. 'element' => 'text',
  82. 'label' => $this->_('SagePay Password'),
  83. 'description' => $translate->_('Enter your SagePay integration password <br>'
  84. . 'SagePay Success & Failure URL: <br>') . $this->getIpnUrl(),
  85. 'attributes' => array(
  86. 'class' => 'form-control input-medium',
  87. ),
  88. ),
  89. );
  90. }
  91. public function formElements()
  92. {
  93. return array(
  94. array(
  95. 'id' => 'VPSProtocol',
  96. 'value' => '3.00',
  97. 'element' => 'hidden',
  98. ),
  99. array(
  100. 'id' => 'TxType',
  101. 'value' => 'PAYMENT',
  102. 'element' => 'hidden',
  103. ),
  104. array(
  105. 'id' => self::VENDOR,
  106. 'value' => $this->_data[self::VENDOR],
  107. 'element' => 'hidden',
  108. ),
  109. array(
  110. 'id' => 'Crypt',
  111. 'value' => $this->_encryptAndEncode(
  112. $this->_getCryptFields()),
  113. 'element' => 'hidden',
  114. ),
  115. );
  116. }
  117. public function getPostUrl()
  118. {
  119. return self::POST_URL;
  120. }
  121. /**
  122. *
  123. * process ipn
  124. *
  125. * @param \Cube\Controller\Request\AbstractRequest $request
  126. *
  127. * @return bool
  128. */
  129. public function processIpn(AbstractRequest $request)
  130. {
  131. $response = false;
  132. $data = array();
  133. $result = explode('&', $this->_decodeAndDecrypt($request->getParam('crypt')));
  134. foreach ($result as $row) {
  135. list($key, $value) = explode('=', $row);
  136. $data[$key] = $value;
  137. }
  138. $this->setTransactionId($data['VendorTxCode'])
  139. ->setAmount($data['Amount'])
  140. ->setCurrency($data['Currency'])
  141. ->setGatewayPaymentStatus($data['Status'])
  142. ->setGatewayTransactionCode($data['VPSTxId']);
  143. if ($data['Status'] == 'OK') {
  144. $response = true;
  145. }
  146. return $response;
  147. }
  148. /**
  149. *
  150. * generate crypt form variable needed by the sagepay form
  151. *
  152. * @return string
  153. */
  154. private function _getCryptFields()
  155. {
  156. $transactionsService = new Service\Transactions();
  157. /** @var \Ppb\Db\Table\Row\User $user */
  158. $user = $transactionsService->findBy('id', $this->getTransactionId())
  159. ->findParentRow('\Ppb\Db\Table\Users');
  160. $user->setAddress();
  161. $locationsService = new Service\Table\Relational\Locations();
  162. $country = $locationsService->findBy('id', (int)$user->getData('country'));
  163. $state = null;
  164. if (strcasecmp($country['iso_code'], 'us') === 0) {
  165. $state = $user['state'];
  166. if (is_numeric($state)) {
  167. $locations = new Service\Table\Relational\Locations();
  168. $state = strtoupper(
  169. $locations->findBy('id', (int)$state)->getData('iso_code'));
  170. }
  171. }
  172. $data = array(
  173. 'VendorTxCode' => $this->getTransactionId(),
  174. 'Amount' => $this->_getAmount(),
  175. 'Currency' => $this->getCurrency(),
  176. 'Description' => $this->_shortenString($this->getName(), 100),
  177. 'SuccessURL' => $this->getSuccessUrl(),
  178. 'FailureURL' => $this->getFailureUrl(),
  179. 'BillingSurname' => $this->_shortenString($user['last_name'], 20),
  180. 'BillingFirstnames' => $this->_shortenString($user['first_name'], 20),
  181. 'BillingAddress1' => $this->_shortenString($user['address'], 100),
  182. 'BillingCity' => $this->_shortenString($user['city'], 40),
  183. 'BillingPostCode' => $this->_shortenString($user['zip_code'], 10),
  184. 'BillingCountry' => $country['iso_code'],
  185. 'BillingState' => $state,
  186. 'DeliverySurname' => $this->_shortenString($user['last_name'], 20),
  187. 'DeliveryFirstnames' => $this->_shortenString($user['first_name'], 20),
  188. 'DeliveryAddress1' => $this->_shortenString($user['address'], 100),
  189. 'DeliveryCity' => $this->_shortenString($user['city'], 40),
  190. 'DeliveryPostCode' => $this->_shortenString($user['zip_code'], 10),
  191. 'DeliveryCountry' => $country['iso_code'],
  192. 'DeliveryState' => $state,
  193. );
  194. $crypt = array();
  195. foreach ($data as $key => $value) {
  196. $crypt[] = $key . '=' . $value;
  197. }
  198. return implode('&', $crypt);
  199. }
  200. /**
  201. *
  202. * amount format required by sagepay, with commas to separate thousands
  203. *
  204. * @return string
  205. */
  206. private function _getAmount()
  207. {
  208. return number_format(
  209. $this->getAmount(), 2, '.', ',');
  210. }
  211. /**
  212. *
  213. * encrypt the crypt field
  214. *
  215. * @param $string
  216. *
  217. * @return string
  218. */
  219. private function _encryptAndEncode($string)
  220. {
  221. //** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT **
  222. //** use initialization vector (IV) set from the account password
  223. $strIV = $this->_data[self::PASSWORD];
  224. //** add PKCS5 padding to the text to be encrypted
  225. $string = $this->_addPKCS5Padding($string);
  226. //** perform encryption with PHP's MCRYPT module
  227. $strCrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->_data[self::PASSWORD], $string, MCRYPT_MODE_CBC, $strIV);
  228. //** perform hex encoding and return
  229. return "@" . bin2hex($strCrypt);
  230. }
  231. /**
  232. *
  233. * decode then decrypt based on header of the encrypted field
  234. *
  235. * @param $string
  236. *
  237. * @return string
  238. */
  239. private function _decodeAndDecrypt($string)
  240. {
  241. //** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **
  242. //** use initialization vector (IV) set from $strEncryptionPassword
  243. $strIV = $this->_data[self::PASSWORD];
  244. //** remove the first char which is @ to flag this is AES encrypted
  245. $string = substr($string, 1);
  246. //** HEX decoding
  247. $string = pack('H*', $string);
  248. //** perform decryption with PHP's MCRYPT module
  249. return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->_data[self::PASSWORD], $string, MCRYPT_MODE_CBC, $strIV);
  250. }
  251. /**
  252. *
  253. * PHP's mcrypt does not have built in PKCS5 Padding, so we use this
  254. *
  255. * @param $input
  256. *
  257. * @return string
  258. */
  259. private function _addPKCS5Padding($input)
  260. {
  261. $blockSize = 16;
  262. $padding = "";
  263. // Pad input to an even block size boundary
  264. $padLength = $blockSize - (strlen($input) % $blockSize);
  265. for ($i = 1; $i <= $padLength; $i++) {
  266. $padding .= chr($padLength);
  267. }
  268. return $input . $padding;
  269. }
  270. }