sagepay_server.php (21305B)
1 <?php 2 class ModelExtensionPaymentSagePayServer extends Model { 3 public function getMethod($address, $total) { 4 $this->load->language('extension/payment/sagepay_server'); 5 6 $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "zone_to_geo_zone` WHERE geo_zone_id = '" . (int)$this->config->get('payment_sagepay_server_geo_zone_id') . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')"); 7 8 if ($this->config->get('payment_sagepay_server_total') > 0 && $this->config->get('payment_sagepay_server_total') > $total) { 9 $status = false; 10 } elseif (!$this->config->get('payment_sagepay_server_geo_zone_id')) { 11 $status = true; 12 } elseif ($query->num_rows) { 13 $status = true; 14 } else { 15 $status = false; 16 } 17 18 $method_data = array(); 19 20 if ($status) { 21 $method_data = array( 22 'code' => 'sagepay_server', 23 'title' => $this->language->get('text_title'), 24 'terms' => '', 25 'sort_order' => $this->config->get('payment_sagepay_server_sort_order') 26 ); 27 } 28 29 return $method_data; 30 } 31 32 public function getCards($customer_id) { 33 34 $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "sagepay_server_card` WHERE customer_id = '" . (int)$customer_id . "'"); 35 36 $card_data = array(); 37 38 $this->load->model('account/address'); 39 40 foreach ($query->rows as $row) { 41 42 $card_data[] = array( 43 'card_id' => $row['card_id'], 44 'customer_id' => $row['customer_id'], 45 'token' => $row['token'], 46 'digits' => '**** ' . $row['digits'], 47 'expiry' => $row['expiry'], 48 'type' => $row['type'], 49 ); 50 } 51 return $card_data; 52 } 53 54 public function getCard($card_id, $token) { 55 $qry = $this->db->query("SELECT * FROM " . DB_PREFIX . "sagepay_server_card WHERE (card_id = '" . $this->db->escape($card_id) . "' OR token = '" . $this->db->escape($token) . "') AND customer_id = '" . (int)$this->customer->getId() . "'"); 56 57 if ($qry->num_rows) { 58 return $qry->row; 59 } else { 60 return false; 61 } 62 } 63 64 public function addCard($data) { 65 $this->db->query("INSERT into `" . DB_PREFIX . "sagepay_server_card` SET customer_id = '" . $this->db->escape($data['customer_id']) . "', token = '" . $this->db->escape($data['Token']) . "', digits = '" . $this->db->escape($data['Last4Digits']) . "', expiry = '" . $this->db->escape($data['ExpiryDate']) . "', type = '" . $this->db->escape($data['CardType']) . "'"); 66 } 67 68 public function deleteCard($card_id) { 69 $this->db->query("DELETE FROM " . DB_PREFIX . "sagepay_server_card WHERE card_id = '" . (int)$card_id . "'"); 70 } 71 72 public function addOrder($order_info) { 73 $this->db->query("DELETE FROM `" . DB_PREFIX . "sagepay_server_order` WHERE `order_id` = '" . (int)$order_info['order_id'] . "'"); 74 75 $this->db->query("INSERT INTO `" . DB_PREFIX . "sagepay_server_order` SET `order_id` = '" . (int)$order_info['order_id'] . "', `customer_id` = '" . (int)$this->customer->getId() . "', `VPSTxId` = '" . $this->db->escape($order_info['VPSTxId']) . "', `VendorTxCode` = '" . $this->db->escape($order_info['VendorTxCode']) . "', `SecurityKey` = '" . $this->db->escape($order_info['SecurityKey']) . "', `date_added` = now(), `date_modified` = now(), `currency_code` = '" . $this->db->escape($order_info['currency_code']) . "', `total` = '" . $this->currency->format($order_info['total'], $order_info['currency_code'], false, false) . "'"); 76 } 77 78 public function getOrder($order_id, $vpstx_id = null) { 79 $qry = $this->db->query("SELECT * FROM `" . DB_PREFIX . "sagepay_server_order` WHERE `order_id` = '" . (int)$order_id . "' OR `VPSTxId` = '" . $this->db->escape($vpstx_id) . "' LIMIT 1"); 80 81 if ($qry->num_rows) { 82 $order = $qry->row; 83 $order['transactions'] = $this->getTransactions($order['sagepay_server_order_id']); 84 85 return $order; 86 } else { 87 return false; 88 } 89 } 90 91 public function updateOrder($order_info, $vps_txn_id, $tx_auth_no) { 92 $this->db->query("UPDATE `" . DB_PREFIX . "sagepay_server_order` SET `VPSTxId` = '" . $this->db->escape($vps_txn_id) . "', `TxAuthNo` = '" . $this->db->escape($tx_auth_no) . "' WHERE `order_id` = '" . (int)$order_info['order_id'] . "'"); 93 } 94 95 public function deleteOrder($order_id) { 96 $this->db->query("DELETE FROM `" . DB_PREFIX . "sagepay_server_order` WHERE order_id = '" . (int)$order_id . "'"); 97 $this->db->query("DELETE FROM `" . DB_PREFIX . "order_recurring` WHERE order_id = '" . (int)$order_id . "'"); 98 } 99 100 public function addTransaction($sagepay_server_order_id, $type, $order_info) { 101 $this->db->query("INSERT INTO `" . DB_PREFIX . "sagepay_server_order_transaction` SET `sagepay_server_order_id` = '" . (int)$sagepay_server_order_id . "', `date_added` = now(), `type` = '" . $this->db->escape($type) . "', `amount` = '" . $this->currency->format($order_info['total'], $order_info['currency_code'], false, false) . "'"); 102 } 103 104 private function getTransactions($sagepay_server_order_id) { 105 $qry = $this->db->query("SELECT * FROM `" . DB_PREFIX . "sagepay_server_order_transaction` WHERE `sagepay_server_order_id` = '" . (int)$sagepay_server_order_id . "'"); 106 107 if ($qry->num_rows) { 108 return $qry->rows; 109 } else { 110 return false; 111 } 112 } 113 114 public function getRecurringOrders($order_id) { 115 $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "order_recurring` WHERE order_id = '" . (int)$order_id . "'"); 116 return $query->rows; 117 } 118 119 public function addRecurringPayment($item, $vendor_tx_code) { 120 121 $this->load->model('checkout/recurring'); 122 $this->load->language('extension/payment/sagepay_server'); 123 124 //trial information 125 if ($item['recurring']['trial'] == 1) { 126 $trial_amt = $this->currency->format($this->tax->calculate($item['recurring']['trial_price'], $item['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency'], false, false) * $item['quantity'] . ' ' . $this->session->data['currency']; 127 $trial_text = sprintf($this->language->get('text_trial'), $trial_amt, $item['recurring']['trial_cycle'], $item['recurring']['trial_frequency'], $item['recurring']['trial_duration']); 128 } else { 129 $trial_text = ''; 130 } 131 132 $recurring_amt = $this->currency->format($this->tax->calculate($item['recurring']['price'], $item['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency'], false, false) * $item['quantity'] . ' ' . $this->session->data['currency']; 133 $recurring_description = $trial_text . sprintf($this->language->get('text_recurring'), $recurring_amt, $item['recurring']['cycle'], $item['recurring']['frequency']); 134 135 if ($item['recurring']['duration'] > 0) { 136 $recurring_description .= sprintf($this->language->get('text_length'), $item['recurring']['duration']); 137 } 138 139 //create new recurring and set to pending status as no payment has been made yet. 140 $recurring_id = $this->model_checkout_recurring->addRecurring($this->session->data['order_id'], $recurring_description, $item['recurring']); 141 142 $this->model_checkout_recurring->editReference($recurring_id, $vendor_tx_code); 143 } 144 145 public function updateRecurringPayment($item, $order_details) { 146 147 $this->load->model('checkout/recurring'); 148 149 $order_info = $this->model_checkout_order->getOrder($order_details['order_id']); 150 151 //trial information 152 if ($item['trial'] == 1) { 153 $price = $this->currency->format($item['trial_price'], $this->session->data['currency'], false, false); 154 } else { 155 $price = $this->currency->format($item['recurring_price'], $this->session->data['currency'], false, false); 156 } 157 158 $response_data = $this->setPaymentData($order_info, $order_details, $price, $item['order_recurring_id'], $item['recurring_name']); 159 160 $next_payment = new DateTime('now'); 161 $trial_end = new DateTime('now'); 162 $subscription_end = new DateTime('now'); 163 164 if ($item['trial'] == 1 && $item['trial_duration'] != 0) { 165 $next_payment = $this->calculateSchedule($item['trial_frequency'], $next_payment, $item['trial_cycle']); 166 $trial_end = $this->calculateSchedule($item['trial_frequency'], $trial_end, $item['trial_cycle'] * $item['trial_duration']); 167 } elseif ($item['trial'] == 1) { 168 $next_payment = $this->calculateSchedule($item['trial_frequency'], $next_payment, $item['trial_cycle']); 169 $trial_end = new DateTime('0000-00-00'); 170 } 171 172 if ($trial_end > $subscription_end && $item['recurring_duration'] != 0) { 173 $subscription_end = new DateTime(date_format($trial_end, 'Y-m-d H:i:s')); 174 $subscription_end = $this->calculateSchedule($item['recurring_frequency'], $subscription_end, $item['recurring_cycle'] * $item['recurring_duration']); 175 } elseif ($trial_end == $subscription_end && $item['recurring_duration'] != 0) { 176 $next_payment = $this->calculateSchedule($item['recurring_frequency'], $next_payment, $item['recurring_cycle']); 177 $subscription_end = $this->calculateSchedule($item['recurring_frequency'], $subscription_end, $item['recurring_cycle'] * $item['recurring_duration']); 178 } elseif ($trial_end > $subscription_end && $item['recurring_duration'] == 0) { 179 $subscription_end = new DateTime('0000-00-00'); 180 } elseif ($trial_end == $subscription_end && $item['recurring_duration'] == 0) { 181 $next_payment = $this->calculateSchedule($item['recurring_frequency'], $next_payment, $item['recurring_cycle']); 182 $subscription_end = new DateTime('0000-00-00'); 183 } 184 185 $this->addRecurringOrder($order_details['order_id'], $response_data, $item['order_recurring_id'], date_format($trial_end, 'Y-m-d H:i:s'), date_format($subscription_end, 'Y-m-d H:i:s')); 186 187 if ($response_data['Status'] == 'OK') { 188 $this->updateRecurringOrder($item['order_recurring_id'], date_format($next_payment, 'Y-m-d H:i:s')); 189 190 $this->addRecurringTransaction($item['order_recurring_id'], $response_data, 1); 191 } else { 192 $this->addRecurringTransaction($item['order_recurring_id'], $response_data, 4); 193 } 194 } 195 196 private function setPaymentData($order_info, $sagepay_order_info, $price, $order_recurring_id, $recurring_name, $i = null) { 197 if ($this->config->get('payment_sagepay_server_test') == 'live') { 198 $url = 'https://live.sagepay.com/gateway/service/repeat.vsp'; 199 $payment_data['VPSProtocol'] = '3.00'; 200 } elseif ($this->config->get('payment_sagepay_server_test') == 'test') { 201 $url = 'https://test.sagepay.com/gateway/service/repeat.vsp'; 202 $payment_data['VPSProtocol'] = '3.00'; 203 } elseif ($this->config->get('payment_sagepay_server_test') == 'sim') { 204 $url = 'https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorRepeatTx'; 205 $payment_data['VPSProtocol'] = '2.23'; 206 } 207 208 $payment_data['TxType'] = 'REPEAT'; 209 $payment_data['Vendor'] = $this->config->get('payment_sagepay_server_vendor'); 210 $payment_data['VendorTxCode'] = $order_recurring_id . 'RSD' . strftime("%Y%m%d%H%M%S") . mt_rand(1, 999); 211 $payment_data['Amount'] = $this->currency->format($price, $this->session->data['currency'], false, false); 212 $payment_data['Currency'] = $this->session->data['currency']; 213 $payment_data['Description'] = substr($recurring_name, 0, 100); 214 $payment_data['RelatedVPSTxId'] = trim($sagepay_order_info['VPSTxId'], '{}'); 215 $payment_data['RelatedVendorTxCode'] = $sagepay_order_info['VendorTxCode']; 216 $payment_data['RelatedSecurityKey'] = $sagepay_order_info['SecurityKey']; 217 $payment_data['RelatedTxAuthNo'] = $sagepay_order_info['TxAuthNo']; 218 219 if (!empty($order_info['shipping_lastname'])) { 220 $payment_data['DeliverySurname'] = substr($order_info['shipping_lastname'], 0, 20); 221 $payment_data['DeliveryFirstnames'] = substr($order_info['shipping_firstname'], 0, 20); 222 $payment_data['DeliveryAddress1'] = substr($order_info['shipping_address_1'], 0, 100); 223 224 if ($order_info['shipping_address_2']) { 225 $payment_data['DeliveryAddress2'] = $order_info['shipping_address_2']; 226 } 227 228 $payment_data['DeliveryCity'] = substr($order_info['shipping_city'], 0, 40); 229 $payment_data['DeliveryPostCode'] = substr($order_info['shipping_postcode'], 0, 10); 230 $payment_data['DeliveryCountry'] = $order_info['shipping_iso_code_2']; 231 232 if ($order_info['shipping_iso_code_2'] == 'US') { 233 $payment_data['DeliveryState'] = $order_info['shipping_zone_code']; 234 } 235 236 $payment_data['CustomerName'] = substr($order_info['firstname'] . ' ' . $order_info['lastname'], 0, 100); 237 $payment_data['DeliveryPhone'] = substr($order_info['telephone'], 0, 20); 238 } else { 239 $payment_data['DeliveryFirstnames'] = $order_info['payment_firstname']; 240 $payment_data['DeliverySurname'] = $order_info['payment_lastname']; 241 $payment_data['DeliveryAddress1'] = $order_info['payment_address_1']; 242 243 if ($order_info['payment_address_2']) { 244 $payment_data['DeliveryAddress2'] = $order_info['payment_address_2']; 245 } 246 247 $payment_data['DeliveryCity'] = $order_info['payment_city']; 248 $payment_data['DeliveryPostCode'] = $order_info['payment_postcode']; 249 $payment_data['DeliveryCountry'] = $order_info['payment_iso_code_2']; 250 251 if ($order_info['payment_iso_code_2'] == 'US') { 252 $payment_data['DeliveryState'] = $order_info['payment_zone_code']; 253 } 254 255 $payment_data['DeliveryPhone'] = $order_info['telephone']; 256 } 257 $response_data = $this->sendCurl($url, $payment_data, $i); 258 $response_data['VendorTxCode'] = $payment_data['VendorTxCode']; 259 $response_data['Amount'] = $payment_data['Amount']; 260 $response_data['Currency'] = $payment_data['Currency']; 261 262 return $response_data; 263 } 264 265 public function cronPayment() { 266 267 $this->load->model('account/order'); 268 $recurrings = $this->getProfiles(); 269 $cron_data = array(); 270 $i = 0; 271 272 foreach ($recurrings as $recurring) { 273 274 $recurring_order = $this->getRecurringOrder($recurring['order_recurring_id']); 275 276 $today = new DateTime('now'); 277 $unlimited = new DateTime('0000-00-00'); 278 $next_payment = new DateTime($recurring_order['next_payment']); 279 $trial_end = new DateTime($recurring_order['trial_end']); 280 $subscription_end = new DateTime($recurring_order['subscription_end']); 281 282 $order_info = $this->model_account_order->getOrder($recurring['order_id']); 283 284 if (($today > $next_payment) && ($trial_end > $today || $trial_end == $unlimited)) { 285 $price = $this->currency->format($recurring['trial_price'], $order_info['currency_code'], false, false); 286 $frequency = $recurring['trial_frequency']; 287 $cycle = $recurring['trial_cycle']; 288 } elseif (($today > $next_payment) && ($subscription_end > $today || $subscription_end == $unlimited)) { 289 $price = $this->currency->format($recurring['recurring_price'], $order_info['currency_code'], false, false); 290 $frequency = $recurring['recurring_frequency']; 291 $cycle = $recurring['recurring_cycle']; 292 } else { 293 continue; 294 } 295 296 $sagepay_order_info = $this->getOrder($recurring['order_id']); 297 298 $response_data = $this->setPaymentData($order_info, $sagepay_order_info, $price, $recurring['order_recurring_id'], $recurring['recurring_name'], $i); 299 300 $cron_data[] = $response_data; 301 302 if ($response_data['RepeatResponseData_' . $i++]['Status'] == 'OK') { 303 $this->addRecurringTransaction($recurring['order_recurring_id'], $response_data, 1); 304 $next_payment = $this->calculateSchedule($frequency, $next_payment, $cycle); 305 $next_payment = date_format($next_payment, 'Y-m-d H:i:s'); 306 $this->updateRecurringOrder($recurring['order_recurring_id'], $next_payment); 307 } else { 308 $this->addRecurringTransaction($recurring['order_recurring_id'], $response_data, 4); 309 } 310 } 311 $log = new Log('sagepay_server_recurring_orders.log'); 312 $log->write(print_r($cron_data, 1)); 313 return $cron_data; 314 } 315 316 private function calculateSchedule($frequency, $next_payment, $cycle) { 317 if ($frequency == 'semi_month') { 318 $day = date_format($next_payment, 'd'); 319 $value = 15 - $day; 320 $is_even = false; 321 if ($cycle % 2 == 0) { 322 $is_even = true; 323 } 324 325 $odd = ($cycle + 1) / 2; 326 $plus_even = ($cycle / 2) + 1; 327 $minus_even = $cycle / 2; 328 329 if ($day == 1) { 330 $odd = $odd - 1; 331 $plus_even = $plus_even - 1; 332 $day = 16; 333 } 334 335 if ($day <= 15 && $is_even) { 336 $next_payment->modify('+' . $value . ' day'); 337 $next_payment->modify('+' . $minus_even . ' month'); 338 } elseif ($day <= 15) { 339 $next_payment->modify('first day of this month'); 340 $next_payment->modify('+' . $odd . ' month'); 341 } elseif ($day > 15 && $is_even) { 342 $next_payment->modify('first day of this month'); 343 $next_payment->modify('+' . $plus_even . ' month'); 344 } elseif ($day > 15) { 345 $next_payment->modify('+' . $value . ' day'); 346 $next_payment->modify('+' . $odd . ' month'); 347 } 348 } else { 349 $next_payment->modify('+' . $cycle . ' ' . $frequency); 350 } 351 return $next_payment; 352 } 353 354 private function addRecurringOrder($order_id, $response_data, $order_recurring_id, $trial_end, $subscription_end) { 355 $this->db->query("INSERT INTO `" . DB_PREFIX . "sagepay_server_order_recurring` SET `order_id` = '" . (int)$order_id . "', `order_recurring_id` = '" . (int)$order_recurring_id . "', `VPSTxId` = '" . $this->db->escape($response_data['VPSTxId']) . "', `VendorTxCode` = '" . $this->db->escape($response_data['VendorTxCode']) . "', `SecurityKey` = '" . $this->db->escape($response_data['SecurityKey']) . "', `TxAuthNo` = '" . $this->db->escape($response_data['TxAuthNo']) . "', `date_added` = now(), `date_modified` = now(), `next_payment` = now(), `trial_end` = '" . $trial_end . "', `subscription_end` = '" . $subscription_end . "', `currency_code` = '" . $this->db->escape($response_data['Currency']) . "', `total` = '" . $this->currency->format($response_data['Amount'], $response_data['Currency'], false, false) . "'"); 356 } 357 358 private function updateRecurringOrder($order_recurring_id, $next_payment) { 359 $this->db->query("UPDATE `" . DB_PREFIX . "sagepay_server_order_recurring` SET `next_payment` = '" . $next_payment . "', `date_modified` = now() WHERE `order_recurring_id` = '" . (int)$order_recurring_id . "'"); 360 } 361 362 private function getRecurringOrder($order_recurring_id) { 363 $qry = $this->db->query("SELECT * FROM " . DB_PREFIX . "sagepay_server_order_recurring WHERE order_recurring_id = '" . (int)$order_recurring_id . "'"); 364 return $qry->row; 365 } 366 367 private function addRecurringTransaction($order_recurring_id, $response_data, $type) { 368 $this->db->query("INSERT INTO `" . DB_PREFIX . "order_recurring_transaction` SET `order_recurring_id` = '" . (int)$order_recurring_id . "', `date_added` = NOW(), `amount` = '" . (float)$response_data['Amount'] . "', `type` = '" . (int)$type . "', `reference` = '" . $this->db->escape($response_data['VendorTxCode']) . "'"); 369 } 370 371 private function getProfiles() { 372 373 $sql = " 374 SELECT `or`.order_recurring_id 375 FROM `" . DB_PREFIX . "order_recurring` `or` 376 JOIN `" . DB_PREFIX . "order` `o` USING(`order_id`) 377 WHERE o.payment_code = 'sagepay_server'"; 378 379 $qry = $this->db->query($sql); 380 381 $order_recurring = array(); 382 383 foreach ($qry->rows as $recurring) { 384 $order_recurring[] = $this->getProfile($recurring['order_recurring_id']); 385 } 386 return $order_recurring; 387 } 388 389 private function getProfile($order_recurring_id) { 390 $qry = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_recurring WHERE order_recurring_id = " . (int)$order_recurring_id); 391 return $qry->row; 392 } 393 394 public function updateCronJobRunTime() { 395 $this->db->query("DELETE FROM `" . DB_PREFIX . "setting` WHERE `code` = 'sagepay_server' AND `key` = 'payment_sagepay_server_last_cron_job_run'"); 396 $this->db->query("INSERT INTO `" . DB_PREFIX . "setting` (`store_id`, `code`, `key`, `value`, `serialized`) VALUES (0, 'sagepay_server', 'payment_sagepay_server_last_cron_job_run', NOW(), 0)"); 397 } 398 399 public function sendCurl($url, $payment_data, $i = null) { 400 $curl = curl_init($url); 401 402 curl_setopt($curl, CURLOPT_PORT, 443); 403 curl_setopt($curl, CURLOPT_HEADER, 0); 404 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 405 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 406 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); 407 curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); 408 curl_setopt($curl, CURLOPT_FRESH_CONNECT, 1); 409 curl_setopt($curl, CURLOPT_POST, 1); 410 curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($payment_data)); 411 412 $response = curl_exec($curl); 413 414 curl_close($curl); 415 416 $response_info = explode(chr(10), $response); 417 418 foreach ($response_info as $string) { 419 if (strpos($string, '=') && isset($i)) { 420 $parts = explode('=', $string, 2); 421 $data['RepeatResponseData_' . $i][trim($parts[0])] = trim($parts[1]); 422 } elseif (strpos($string, '=')) { 423 $parts = explode('=', $string, 2); 424 $data[trim($parts[0])] = trim($parts[1]); 425 } 426 } 427 return $data; 428 } 429 430 public function logger($title, $data) { 431 if ($this->config->get('payment_sagepay_server_debug')) { 432 $log = new Log('sagepay_server.log'); 433 $backtrace = debug_backtrace(); 434 $log->write($backtrace[6]['class'] . '::' . $backtrace[6]['function'] . ' - ' . $title . ': ' . print_r($data, 1)); 435 } 436 } 437 438 public function recurringPayments() { 439 /* 440 * Used by the checkout to state the module 441 * supports recurring recurrings. 442 */ 443 return true; 444 } 445 }