klarna_invoice.php (17516B)
1 <?php 2 class ControllerExtensionPaymentKlarnaInvoice extends Controller { 3 public function index() { 4 $this->load->model('checkout/order'); 5 6 $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']); 7 8 if ($order_info) { 9 $this->load->language('extension/payment/klarna_invoice'); 10 11 $data['days'] = array(); 12 13 for ($i = 1; $i <= 31; $i++) { 14 $data['days'][] = array( 15 'text' => sprintf('%02d', $i), 16 'value' => $i 17 ); 18 } 19 20 $data['months'] = array(); 21 22 for ($i = 1; $i <= 12; $i++) { 23 $data['months'][] = array( 24 'text' => sprintf('%02d', $i), 25 'value' => $i 26 ); 27 } 28 29 $data['years'] = array(); 30 31 for ($i = date('Y'); $i >= 1900; $i--) { 32 $data['years'][] = array( 33 'text' => $i, 34 'value' => $i 35 ); 36 } 37 38 // Store Taxes to send to Klarna 39 $total_data = array(); 40 $total = 0; 41 42 $this->load->model('setting/extension'); 43 44 $sort_order = array(); 45 46 $results = $this->model_setting_extension->getExtensions('total'); 47 48 foreach ($results as $key => $value) { 49 $sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order'); 50 } 51 52 array_multisort($sort_order, SORT_ASC, $results); 53 54 $klarna_tax = array(); 55 56 foreach ($results as $result) { 57 if ($this->config->get($result['code'] . '_status')) { 58 $this->load->model('extension/total/' . $result['code']); 59 60 $taxes = array(); 61 62 // We have to put the totals in an array so that they pass by reference. 63 $this->{'model_extension_total_' . $result['code']}->getTotal(array("totals"=>$total_data, "total"=>$total, "taxes"=>$taxes)); 64 65 $amount = 0; 66 67 foreach ($taxes as $tax_id => $value) { 68 $amount += $value; 69 } 70 71 $klarna_tax[$result['code']] = $amount; 72 } 73 } 74 75 foreach ($total_data as $key => $value) { 76 $sort_order[$key] = $value['sort_order']; 77 78 if (isset($klarna_tax[$value['code']])) { 79 if ($klarna_tax[$value['code']]) { 80 $total_data[$key]['tax_rate'] = abs($klarna_tax[$value['code']] / $value['value'] * 100); 81 } else { 82 $total_data[$key]['tax_rate'] = 0; 83 } 84 } else { 85 $total_data[$key]['tax_rate'] = '0'; 86 } 87 } 88 89 $this->session->data['klarna'][$this->session->data['order_id']] = $total_data; 90 91 // Order must have identical shipping and billing address or have no shipping address at all 92 if ($this->cart->hasShipping() && !($order_info['payment_firstname'] == $order_info['shipping_firstname'] && $order_info['payment_lastname'] == $order_info['shipping_lastname'] && $order_info['payment_address_1'] == $order_info['shipping_address_1'] && $order_info['payment_address_2'] == $order_info['shipping_address_2'] && $order_info['payment_postcode'] == $order_info['shipping_postcode'] && $order_info['payment_city'] == $order_info['shipping_city'] && $order_info['payment_zone_id'] == $order_info['shipping_zone_id'] && $order_info['payment_zone_code'] == $order_info['shipping_zone_code'] && $order_info['payment_country_id'] == $order_info['shipping_country_id'] && $order_info['payment_country'] == $order_info['shipping_country'] && $order_info['payment_iso_code_3'] == $order_info['shipping_iso_code_3'])) { 93 $data['error_warning'] = $this->language->get('error_address_match'); 94 } else { 95 $data['error_warning'] = ''; 96 } 97 98 $klarna_invoice = $this->config->get('payment_klarna_invoice'); 99 100 $data['merchant'] = $klarna_invoice[$order_info['payment_iso_code_3']]['merchant']; 101 $data['phone_number'] = $order_info['telephone']; 102 103 if ($order_info['payment_iso_code_3'] == 'DEU' || $order_info['payment_iso_code_3'] == 'NLD') { 104 $address = $this->splitAddress($order_info['payment_address_1']); 105 106 $data['street'] = $address[0]; 107 $data['street_number'] = $address[1]; 108 $data['street_extension'] = $address[2]; 109 110 if ($order_info['payment_iso_code_3'] == 'DEU') { 111 $data['street_number'] = trim($address[1] . ' ' . $address[2]); 112 } 113 } else { 114 $data['street'] = ''; 115 $data['street_number'] = ''; 116 $data['street_extension'] = ''; 117 } 118 119 $data['company'] = $order_info['payment_company']; 120 $data['iso_code_2'] = $order_info['payment_iso_code_2']; 121 $data['iso_code_3'] = $order_info['payment_iso_code_3']; 122 123 // Get the invoice fee 124 $query = $this->db->query("SELECT `value` FROM `" . DB_PREFIX . "order_total` WHERE `order_id` = " . (int)$order_info['order_id'] . " AND `code` = 'klarna_fee'"); 125 126 if ($query->num_rows && !$query->row['value']) { 127 $data['klarna_fee'] = $query->row['value']; 128 } else { 129 $data['klarna_fee'] = ''; 130 } 131 132 return $this->load->view('extension/payment/klarna_invoice', $data); 133 } 134 } 135 136 public function send() { 137 $this->load->language('extension/payment/klarna_invoice'); 138 139 $json = array(); 140 141 $this->load->model('checkout/order'); 142 143 $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']); 144 145 // Order must have identical shipping and billing address or have no shipping address at all 146 if ($order_info) { 147 if ($order_info['payment_iso_code_3'] == 'DEU' && empty($this->request->post['deu_terms'])) { 148 $json['error'] = $this->language->get('error_deu_terms'); 149 } 150 151 if ($this->cart->hasShipping() && !($order_info['payment_firstname'] == $order_info['shipping_firstname'] && $order_info['payment_lastname'] == $order_info['shipping_lastname'] && $order_info['payment_address_1'] == $order_info['shipping_address_1'] && $order_info['payment_address_2'] == $order_info['shipping_address_2'] && $order_info['payment_postcode'] == $order_info['shipping_postcode'] && $order_info['payment_city'] == $order_info['shipping_city'] && $order_info['payment_zone_id'] == $order_info['shipping_zone_id'] && $order_info['payment_zone_code'] == $order_info['shipping_zone_code'] && $order_info['payment_country_id'] == $order_info['shipping_country_id'] && $order_info['payment_country'] == $order_info['shipping_country'] && $order_info['payment_iso_code_3'] == $order_info['shipping_iso_code_3'])) { 152 $json['error'] = $this->language->get('error_address_match'); 153 } 154 155 if (!$json) { 156 $klarna_invoice = $this->config->get('payment_klarna_invoice'); 157 158 if ($klarna_invoice[$order_info['payment_iso_code_3']]['server'] == 'live') { 159 $url = 'https://payment.klarna.com/'; 160 } else { 161 $url = 'https://payment.testdrive.klarna.com/'; 162 } 163 164 $country_to_currency = array( 165 'NOR' => 'NOK', 166 'SWE' => 'SEK', 167 'FIN' => 'EUR', 168 'DNK' => 'DKK', 169 'DEU' => 'EUR', 170 'NLD' => 'EUR' 171 ); 172 173 switch ($order_info['payment_iso_code_3']) { 174 // Sweden 175 case 'SWE': 176 $country = 209; 177 $language = 138; 178 $encoding = 2; 179 $currency = 0; 180 break; 181 // Finland 182 case 'FIN': 183 $country = 73; 184 $language = 37; 185 $encoding = 4; 186 $currency = 2; 187 break; 188 // Denmark 189 case 'DNK': 190 $country = 59; 191 $language = 27; 192 $encoding = 5; 193 $currency = 3; 194 break; 195 // Norway 196 case 'NOR': 197 $country = 164; 198 $language = 97; 199 $encoding = 3; 200 $currency = 1; 201 break; 202 // Germany 203 case 'DEU': 204 $country = 81; 205 $language = 28; 206 $encoding = 6; 207 $currency = 2; 208 break; 209 // Netherlands 210 case 'NLD': 211 $country = 154; 212 $language = 101; 213 $encoding = 7; 214 $currency = 2; 215 break; 216 } 217 218 if (isset($this->request->post['street'])) { 219 $street = $this->request->post['street']; 220 } else { 221 $street = $order_info['payment_address_1']; 222 } 223 224 if (isset($this->request->post['house_no'])) { 225 $house_no = $this->request->post['house_no']; 226 } else { 227 $house_no = ''; 228 } 229 230 if (isset($this->request->post['house_ext'])) { 231 $house_ext = $this->request->post['house_ext']; 232 } else { 233 $house_ext = ''; 234 } 235 236 $address = array( 237 'email' => $order_info['email'], 238 'telno' => $this->request->post['phone_no'], 239 'cellno' => '', 240 'fname' => $order_info['payment_firstname'], 241 'lname' => $order_info['payment_lastname'], 242 'company' => $order_info['payment_company'], 243 'careof' => '', 244 'street' => $street, 245 'house_number' => $house_no, 246 'house_extension' => $house_ext, 247 'zip' => $order_info['payment_postcode'], 248 'city' => $order_info['payment_city'], 249 'country' => $country, 250 ); 251 252 $product_query = $this->db->query("SELECT `name`, `model`, `price`, `quantity`, `tax` / `price` * 100 AS 'tax_rate' FROM `" . DB_PREFIX . "order_product` WHERE `order_id` = " . (int)$order_info['order_id'] . " UNION ALL SELECT '', `code`, `amount`, '1', 0.00 FROM `" . DB_PREFIX . "order_voucher` WHERE `order_id` = " . (int)$order_info['order_id']); 253 254 foreach ($product_query->rows as $product) { 255 $goods_list[] = array( 256 'qty' => (int)$product['quantity'], 257 'goods' => array( 258 'artno' => $product['model'], 259 'title' => $product['name'], 260 'price' => (int)str_replace('.', '', $this->currency->format($product['price'], $country_to_currency[$order_info['payment_iso_code_3']], '', false)), 261 'vat' => (float)$product['tax_rate'], 262 'discount' => 0.0, 263 'flags' => 0 264 ) 265 ); 266 } 267 268 if (isset($this->session->data['klarna'][$this->session->data['order_id']])) { 269 $totals = $this->session->data['klarna'][$this->session->data['order_id']]; 270 } else { 271 $totals = array(); 272 } 273 274 foreach ($totals as $total) { 275 if ($total['code'] != 'sub_total' && $total['code'] != 'tax' && $total['code'] != 'total') { 276 $goods_list[] = array( 277 'qty' => 1, 278 'goods' => array( 279 'artno' => '', 280 'title' => $total['title'], 281 'price' => (int)str_replace('.', '', $this->currency->format($total['value'], $country_to_currency[$order_info['payment_iso_code_3']], '', false)), 282 'vat' => (float)$total['tax_rate'], 283 'discount' => 0.0, 284 'flags' => 0 285 ) 286 ); 287 } 288 } 289 290 $digest = ''; 291 292 foreach ($goods_list as $goods) { 293 $digest .= utf8_decode(htmlspecialchars(html_entity_decode($goods['goods']['title'], ENT_COMPAT, 'UTF-8'))) . ':'; 294 } 295 296 $digest = base64_encode(pack('H*', hash('sha256', $digest . $klarna_invoice[$order_info['payment_iso_code_3']]['secret']))); 297 298 if (isset($this->request->post['pno'])) { 299 $pno = $this->request->post['pno']; 300 } else { 301 $pno = sprintf('%02d', (int)$this->request->post['pno_day']) . sprintf('%02d', (int)$this->request->post['pno_month']) . (int)$this->request->post['pno_year']; 302 } 303 304 $pclass = -1; 305 306 if (isset($this->request->post['gender']) && ($order_info['payment_iso_code_3'] == 'DEU' || $order_info['payment_iso_code_3'] == 'NLD')) { 307 $gender = (int)$this->request->post['gender']; 308 } else { 309 $gender = ''; 310 } 311 312 $transaction = array( 313 '4.1', 314 'API:OPENCART:' . VERSION, 315 $pno, 316 $gender, 317 '', 318 '', 319 (string)$order_info['order_id'], 320 '', 321 $address, 322 $address, 323 $order_info['ip'], 324 0, 325 $currency, 326 $country, 327 $language, 328 (int)$klarna_invoice[$order_info['payment_iso_code_3']]['merchant'], 329 $digest, 330 $encoding, 331 $pclass, 332 $goods_list, 333 $order_info['comment'], 334 array('delay_adjust' => 1), 335 array(), 336 array(), 337 array(), 338 array(), 339 array() 340 ); 341 342 $xml = '<methodCall>'; 343 $xml .= ' <methodName>add_invoice</methodName>'; 344 $xml .= ' <params>'; 345 346 foreach ($transaction as $parameter) { 347 $xml .= ' <param><value>' . $this->constructXmlrpc($parameter) . '</value></param>'; 348 } 349 350 $xml .= ' </params>'; 351 $xml .= '</methodCall>'; 352 353 $header = array(); 354 355 $header[] = 'Content-Type: text/xml'; 356 $header[] = 'Content-Length: ' . strlen($xml); 357 358 $curl = curl_init(); 359 360 curl_setopt($curl, CURLOPT_URL, $url); 361 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 362 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); 363 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); 364 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 365 curl_setopt($curl, CURLOPT_HTTPHEADER, $header); 366 curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); 367 368 $response = curl_exec($curl); 369 370 if (curl_errno($curl)) { 371 $log = new Log('klarna_invoice.log'); 372 $log->write('HTTP Error for order #' . $order_info['order_id'] . '. Code: ' . curl_errno($curl) . ' message: ' . curl_error($curl)); 373 374 $json['error'] = $this->language->get('error_network'); 375 } else { 376 preg_match('/<member><name>faultString<\/name><value><string>(.+)<\/string><\/value><\/member>/', $response, $match); 377 378 if (isset($match[1])) { 379 preg_match('/<member><name>faultCode<\/name><value><int>([0-9]+)<\/int><\/value><\/member>/', $response, $match2); 380 381 $log = new Log('klarna_invoice.log'); 382 $log->write('Failed to create an invoice for order #' . $order_info['order_id'] . '. Message: ' . utf8_encode($match[1]) . ' Code: ' . $match2[1]); 383 384 $json['error'] = utf8_encode($match[1]); 385 } else { 386 $xml = new DOMDocument(); 387 $xml->loadXML($response); 388 389 $invoice_number = $xml->getElementsByTagName('string')->item(0)->nodeValue; 390 $klarna_order_status = $xml->getElementsByTagName('int')->item(0)->nodeValue; 391 392 if ($klarna_order_status == '1') { 393 $order_status = $klarna_invoice[$order_info['payment_iso_code_3']]['accepted_status_id']; 394 } elseif ($klarna_order_status == '2') { 395 $order_status = $klarna_invoice[$order_info['payment_iso_code_3']]['pending_status_id']; 396 } else { 397 $order_status = $this->config->get('config_order_status_id'); 398 } 399 400 $comment = sprintf($this->language->get('text_comment'), $invoice_number, $this->config->get('config_currency'), $country_to_currency[$order_info['payment_iso_code_3']], $this->currency->getValue($country_to_currency[$order_info['payment_iso_code_3']])); 401 402 $this->model_checkout_order->addOrderHistory($this->session->data['order_id'], $order_status, $comment, 1); 403 404 $json['redirect'] = $this->url->link('checkout/success'); 405 } 406 } 407 408 curl_close($curl); 409 } 410 } 411 412 $this->response->addHeader('Content-Type: application/json'); 413 $this->response->setOutput(json_encode($json)); 414 } 415 416 private function constructXmlrpc($data) { 417 $type = gettype($data); 418 419 switch ($type) { 420 case 'boolean': 421 if ($data == true) { 422 $value = 1; 423 } else { 424 $value = false; 425 } 426 427 $xml = '<boolean>' . $value . '</boolean>'; 428 break; 429 case 'integer': 430 $xml = '<int>' . (int)$data . '</int>'; 431 break; 432 case 'double': 433 $xml = '<double>' . (float)$data . '</double>'; 434 break; 435 case 'string': 436 $xml = '<string>' . htmlspecialchars($data) . '</string>'; 437 break; 438 case 'array': 439 // is numeric ? 440 if ($data === array_values($data)) { 441 $xml = '<array><data>'; 442 443 foreach ($data as $value) { 444 $xml .= '<value>' . $this->constructXmlrpc($value) . '</value>'; 445 } 446 447 $xml .= '</data></array>'; 448 449 } else { 450 // array is associative 451 $xml = '<struct>'; 452 453 foreach ($data as $key => $value) { 454 $xml .= '<member>'; 455 $xml .= ' <name>' . htmlspecialchars($key) . '</name>'; 456 $xml .= ' <value>' . $this->constructXmlrpc($value) . '</value>'; 457 $xml .= '</member>'; 458 } 459 460 $xml .= '</struct>'; 461 } 462 463 break; 464 default: 465 $xml = '<nil/>'; 466 break; 467 } 468 469 return $xml; 470 } 471 472 private function splitAddress($address) { 473 $numbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); 474 475 $characters = array('-', '/', ' ', '#', '.', 'a', 'b', 'c', 'd', 'e', 476 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 477 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 478 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 479 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 480 'X', 'Y', 'Z'); 481 482 $specialchars = array('-', '/', ' ', '#', '.'); 483 484 $num_pos = $this->strposArr($address, $numbers, 2); 485 486 $street_name = substr($address, 0, $num_pos); 487 488 $street_name = trim($street_name); 489 490 $number_part = substr($address, $num_pos); 491 492 $number_part = trim($number_part); 493 494 $ext_pos = $this->strposArr($number_part, $characters, 0); 495 496 if ($ext_pos != '') { 497 $house_number = substr($number_part, 0, $ext_pos); 498 499 $house_extension = substr($number_part, $ext_pos); 500 501 $house_extension = str_replace($specialchars, '', $house_extension); 502 } else { 503 $house_number = $number_part; 504 $house_extension = ''; 505 } 506 507 return array($street_name, $house_number, $house_extension); 508 } 509 510 private function strposArr($haystack, $needle, $where) { 511 $defpos = 10000; 512 513 if (!is_array($needle)) { 514 $needle = array($needle); 515 } 516 517 foreach ($needle as $what) { 518 if (($pos = strpos($haystack, $what, $where)) !== false) { 519 if ($pos < $defpos) { 520 $defpos = $pos; 521 } 522 } 523 } 524 525 return $defpos; 526 } 527 }