_data[self::VENDOR]) && !empty($this->_data[self::PASSWORD])) { return true; } return false; } /** * * get setup form elements * * @return array */ public function getElements() { $translate = $this->getTranslate(); return array( array( 'form_id' => 'SagePay', 'id' => self::VENDOR, 'element' => 'text', 'label' => $this->_('SagePay Vendor Name'), 'description' => $this->_('Enter your SagePay vendor name'), 'attributes' => array( 'class' => 'form-control input-medium', ), ), array( 'form_id' => 'SagePay', 'id' => self::PASSWORD, 'element' => 'text', 'label' => $this->_('SagePay Password'), 'description' => $translate->_('Enter your SagePay integration password
' . 'SagePay Success & Failure URL:
') . $this->getIpnUrl(), 'attributes' => array( 'class' => 'form-control input-medium', ), ), ); } public function formElements() { return array( array( 'id' => 'VPSProtocol', 'value' => '3.00', 'element' => 'hidden', ), array( 'id' => 'TxType', 'value' => 'PAYMENT', 'element' => 'hidden', ), array( 'id' => self::VENDOR, 'value' => $this->_data[self::VENDOR], 'element' => 'hidden', ), array( 'id' => 'Crypt', 'value' => $this->_encryptAndEncode( $this->_getCryptFields()), 'element' => 'hidden', ), ); } public function getPostUrl() { return self::POST_URL; } /** * * process ipn * * @param \Cube\Controller\Request\AbstractRequest $request * * @return bool */ public function processIpn(AbstractRequest $request) { $response = false; $data = array(); $result = explode('&', $this->_decodeAndDecrypt($request->getParam('crypt'))); foreach ($result as $row) { list($key, $value) = explode('=', $row); $data[$key] = $value; } $this->setTransactionId($data['VendorTxCode']) ->setAmount($data['Amount']) ->setCurrency($data['Currency']) ->setGatewayPaymentStatus($data['Status']) ->setGatewayTransactionCode($data['VPSTxId']); if ($data['Status'] == 'OK') { $response = true; } return $response; } /** * * generate crypt form variable needed by the sagepay form * * @return string */ private function _getCryptFields() { $transactionsService = new Service\Transactions(); /** @var \Ppb\Db\Table\Row\User $user */ $user = $transactionsService->findBy('id', $this->getTransactionId()) ->findParentRow('\Ppb\Db\Table\Users'); $user->setAddress(); $locationsService = new Service\Table\Relational\Locations(); $country = $locationsService->findBy('id', (int)$user->getData('country')); $state = null; if (strcasecmp($country['iso_code'], 'us') === 0) { $state = $user['state']; if (is_numeric($state)) { $locations = new Service\Table\Relational\Locations(); $state = strtoupper( $locations->findBy('id', (int)$state)->getData('iso_code')); } } $data = array( 'VendorTxCode' => $this->getTransactionId(), 'Amount' => $this->_getAmount(), 'Currency' => $this->getCurrency(), 'Description' => $this->_shortenString($this->getName(), 100), 'SuccessURL' => $this->getSuccessUrl(), 'FailureURL' => $this->getFailureUrl(), 'BillingSurname' => $this->_shortenString($user['last_name'], 20), 'BillingFirstnames' => $this->_shortenString($user['first_name'], 20), 'BillingAddress1' => $this->_shortenString($user['address'], 100), 'BillingCity' => $this->_shortenString($user['city'], 40), 'BillingPostCode' => $this->_shortenString($user['zip_code'], 10), 'BillingCountry' => $country['iso_code'], 'BillingState' => $state, 'DeliverySurname' => $this->_shortenString($user['last_name'], 20), 'DeliveryFirstnames' => $this->_shortenString($user['first_name'], 20), 'DeliveryAddress1' => $this->_shortenString($user['address'], 100), 'DeliveryCity' => $this->_shortenString($user['city'], 40), 'DeliveryPostCode' => $this->_shortenString($user['zip_code'], 10), 'DeliveryCountry' => $country['iso_code'], 'DeliveryState' => $state, ); $crypt = array(); foreach ($data as $key => $value) { $crypt[] = $key . '=' . $value; } return implode('&', $crypt); } /** * * amount format required by sagepay, with commas to separate thousands * * @return string */ private function _getAmount() { return number_format( $this->getAmount(), 2, '.', ','); } /** * * encrypt the crypt field * * @param $string * * @return string */ private function _encryptAndEncode($string) { //** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT ** //** use initialization vector (IV) set from the account password $strIV = $this->_data[self::PASSWORD]; //** add PKCS5 padding to the text to be encrypted $string = $this->_addPKCS5Padding($string); //** perform encryption with PHP's MCRYPT module $strCrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->_data[self::PASSWORD], $string, MCRYPT_MODE_CBC, $strIV); //** perform hex encoding and return return "@" . bin2hex($strCrypt); } /** * * decode then decrypt based on header of the encrypted field * * @param $string * * @return string */ private function _decodeAndDecrypt($string) { //** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT ** //** use initialization vector (IV) set from $strEncryptionPassword $strIV = $this->_data[self::PASSWORD]; //** remove the first char which is @ to flag this is AES encrypted $string = substr($string, 1); //** HEX decoding $string = pack('H*', $string); //** perform decryption with PHP's MCRYPT module return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->_data[self::PASSWORD], $string, MCRYPT_MODE_CBC, $strIV); } /** * * PHP's mcrypt does not have built in PKCS5 Padding, so we use this * * @param $input * * @return string */ private function _addPKCS5Padding($input) { $blockSize = 16; $padding = ""; // Pad input to an even block size boundary $padLength = $blockSize - (strlen($input) % $blockSize); for ($i = 1; $i <= $padLength; $i++) { $padding .= chr($padLength); } return $input . $padding; } }