klarna_account.php (21743B)
1 <?php 2 class ControllerExtensionPaymentKlarnaAccount 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_account'); 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 $totals = array(); 40 $taxes = $this->cart->getTaxes(); 41 $total = 0; 42 43 // Because __call can not keep var references so we put them into an array. 44 $total_data = array( 45 'totals' => &$totals, 46 'taxes' => &$taxes, 47 'total' => &$total 48 ); 49 50 $this->load->model('setting/extension'); 51 52 $sort_order = array(); 53 54 $results = $this->model_setting_extension->getExtensions('total'); 55 56 foreach ($results as $key => $value) { 57 $sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order'); 58 } 59 60 array_multisort($sort_order, SORT_ASC, $results); 61 62 $klarna_tax = array(); 63 64 foreach ($results as $result) { 65 if ($this->config->get('total_' . $result['code'] . '_status')) { 66 $this->load->model('extension/total/' . $result['code']); 67 68 $taxes = array(); 69 70 // We have to put the totals in an array so that they pass by reference. 71 $this->{'model_extension_total_' . $result['code']}->getTotal($total_data); 72 73 $amount = 0; 74 75 foreach ($taxes as $tax_id => $value) { 76 $amount += $value; 77 } 78 79 $klarna_tax[$result['code']] = $amount; 80 } 81 } 82 83 foreach ($totals as $key => $value) { 84 $sort_order[$key] = $value['sort_order']; 85 86 if (isset($klarna_tax[$value['code']])) { 87 if ($klarna_tax[$value['code']]) { 88 $totals[$key]['tax_rate'] = abs($klarna_tax[$value['code']] / $value['value'] * 100); 89 } else { 90 $totals[$key]['tax_rate'] = 0; 91 } 92 } else { 93 $totals[$key]['tax_rate'] = '0'; 94 } 95 } 96 97 $this->session->data['klarna'][$this->session->data['order_id']] = $totals; 98 99 // Order must have identical shipping and billing address or have no shipping address at all 100 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'])) { 101 $data['error_warning'] = $this->language->get('error_address_match'); 102 } else { 103 $data['error_warning'] = ''; 104 } 105 106 $klarna_account = $this->config->get('payment_klarna_account'); 107 108 $data['merchant'] = $klarna_account[$order_info['payment_iso_code_3']]['merchant']; 109 $data['phone_number'] = $order_info['telephone']; 110 111 $country_to_currency = array( 112 'NOR' => 'NOK', 113 'SWE' => 'SEK', 114 'FIN' => 'EUR', 115 'DNK' => 'DKK', 116 'DEU' => 'EUR', 117 'NLD' => 'EUR' 118 ); 119 120 if ($order_info['payment_iso_code_3'] == 'DEU' || $order_info['payment_iso_code_3'] == 'NLD') { 121 $address = $this->splitAddress($order_info['payment_address_1']); 122 123 $data['street'] = $address[0]; 124 $data['street_number'] = $address[1]; 125 $data['street_extension'] = $address[2]; 126 127 if ($order_info['payment_iso_code_3'] == 'DEU') { 128 $data['street_number'] = trim($address[1] . ' ' . $address[2]); 129 } 130 } else { 131 $data['street'] = ''; 132 $data['street_number'] = ''; 133 $data['street_extension'] = ''; 134 } 135 136 $data['company'] = $order_info['payment_company']; 137 $data['iso_code_2'] = $order_info['payment_iso_code_2']; 138 $data['iso_code_3'] = $order_info['payment_iso_code_3']; 139 140 $payment_option = array(); 141 142 $total = $this->currency->format($order_info['total'], $country_to_currency[$order_info['payment_iso_code_3']], '', false); 143 144 $pclasses = $this->config->get('klarna_account_pclasses'); 145 146 if (isset($pclasses[$order_info['payment_iso_code_3']])) { 147 $pclasses = $pclasses[$order_info['payment_iso_code_3']]; 148 } else { 149 $pclasses = array(); 150 } 151 152 foreach ($pclasses as $pclass) { 153 // 0 - Campaign 154 // 1 - Account 155 // 2 - Special 156 // 3 - Fixed 157 if (!in_array($pclass['type'], array(0, 1, 3))) { 158 continue; 159 } 160 161 if ($pclass['type'] == 2) { 162 $monthly_cost = -1; 163 } else { 164 if ($total < $pclass['minamount']) { 165 continue; 166 } 167 168 if ($pclass['type'] == 3) { 169 continue; 170 } else { 171 $sum = $total; 172 173 $lowest_payment = $this->getLowestPaymentAccount($order_info['payment_iso_code_3']); 174 $monthly_cost = 0; 175 176 $monthly_fee = $pclass['invoicefee']; 177 $start_fee = $pclass['startfee']; 178 179 $sum += $start_fee; 180 181 $base = ($pclass['type'] == 1); 182 183 $minimum_payment = ($pclass['type'] === 1) ? $this->getLowestPaymentAccount($order_info['payment_iso_code_3']) : 0; 184 185 if ($pclass['months'] == 0) { 186 $payment = $sum; 187 } elseif ($pclass['interestrate'] == 0) { 188 $payment = $sum / $pclass['months']; 189 } else { 190 $interest = $pclass['interestrate'] / (100.0 * 12); 191 $payment = $sum * $interest / (1 - pow((1 + $interest), -$pclass['months'])); 192 } 193 194 $payment += $monthly_fee; 195 196 $balance = $sum; 197 $pay_data = array(); 198 199 $months = $pclass['months']; 200 201 while (($months != 0) && ($balance > 0.01)) { 202 $interest = $balance * $pclass['interestrate'] / (100.0 * 12); 203 $new_balance = $balance + $interest + $monthly_fee; 204 205 if ($minimum_payment >= $new_balance || $payment >= $new_balance) { 206 $pay_data[] = $new_balance; 207 break; 208 } 209 210 $new_payment = max($payment, $minimum_payment); 211 212 if ($base) { 213 $new_payment = max($new_payment, $balance / 24.0 + $monthly_fee + $interest); 214 } 215 216 $balance = $new_balance - $new_payment; 217 218 $pay_data[] = $new_payment; 219 220 $months -= 1; 221 } 222 223 $monthly_cost = round(isset($pay_data[0]) ? ($pay_data[0]) : 0, 2); 224 225 if ($monthly_cost < 0.01) { 226 continue; 227 } 228 229 if ($pclass['type'] == 1 && $monthly_cost < $lowest_payment) { 230 $monthly_cost = $lowest_payment; 231 } 232 233 if ($pclass['type'] == 0 && $monthly_cost < $lowest_payment) { 234 continue; 235 } 236 } 237 } 238 239 $payment_option[$pclass['id']]['pclass_id'] = $pclass['id']; 240 $payment_option[$pclass['id']]['title'] = $pclass['description']; 241 $payment_option[$pclass['id']]['months'] = $pclass['months']; 242 $payment_option[$pclass['id']]['monthly_cost'] = $monthly_cost; 243 } 244 245 $sort_order = array(); 246 247 foreach ($payment_option as $key => $value) { 248 $sort_order[$key] = $value['pclass_id']; 249 } 250 251 array_multisort($sort_order, SORT_ASC, $payment_option); 252 253 $data['payment_options'] = array(); 254 255 foreach ($payment_option as $payment_option) { 256 $data['payment_options'][] = array( 257 'code' => $payment_option['pclass_id'], 258 'title' => sprintf($this->language->get('text_monthly_payment'), $payment_option['title'], $this->currency->format($this->currency->convert($payment_option['monthly_cost'], $country_to_currency[$order_info['payment_iso_code_3']], $this->session->data['currency']), $this->session->data['currency'], 1)) 259 ); 260 } 261 262 return $this->load->view('extension/payment/klarna_account', $data); 263 } 264 } 265 266 public function send() { 267 $this->load->language('extension/payment/klarna_account'); 268 269 $json = array(); 270 271 $this->load->model('checkout/order'); 272 273 $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']); 274 275 // Order must have identical shipping and billing address or have no shipping address at all 276 if ($order_info) { 277 if ($order_info['payment_iso_code_3'] == 'DEU' && empty($this->request->post['deu_terms'])) { 278 $json['error'] = $this->language->get('error_deu_terms'); 279 } 280 281 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'])) { 282 $json['error'] = $this->language->get('error_address_match'); 283 } 284 285 if (!$json) { 286 $klarna_account = $this->config->get('payment_klarna_account'); 287 288 if ($klarna_account[$order_info['payment_iso_code_3']]['server'] == 'live') { 289 $url = 'https://payment.klarna.com/'; 290 } else { 291 $url = 'https://payment.testdrive.klarna.com/'; 292 } 293 294 $country_to_currency = array( 295 'NOR' => 'NOK', 296 'SWE' => 'SEK', 297 'FIN' => 'EUR', 298 'DNK' => 'DKK', 299 'DEU' => 'EUR', 300 'NLD' => 'EUR' 301 ); 302 303 switch ($order_info['payment_iso_code_3']) { 304 // Sweden 305 case 'SWE': 306 $country = 209; 307 $language = 138; 308 $encoding = 2; 309 $currency = 0; 310 break; 311 // Finland 312 case 'FIN': 313 $country = 73; 314 $language = 37; 315 $encoding = 4; 316 $currency = 2; 317 break; 318 // Denmark 319 case 'DNK': 320 $country = 59; 321 $language = 27; 322 $encoding = 5; 323 $currency = 3; 324 break; 325 // Norway 326 case 'NOR': 327 $country = 164; 328 $language = 97; 329 $encoding = 3; 330 $currency = 1; 331 break; 332 // Germany 333 case 'DEU': 334 $country = 81; 335 $language = 28; 336 $encoding = 6; 337 $currency = 2; 338 break; 339 // Netherlands 340 case 'NLD': 341 $country = 154; 342 $language = 101; 343 $encoding = 7; 344 $currency = 2; 345 break; 346 } 347 348 if (isset($this->request->post['street'])) { 349 $street = $this->request->post['street']; 350 } else { 351 $street = $order_info['payment_address_1']; 352 } 353 354 if (isset($this->request->post['house_no'])) { 355 $house_no = $this->request->post['house_no']; 356 } else { 357 $house_no = ''; 358 } 359 360 if (isset($this->request->post['house_ext'])) { 361 $house_ext = $this->request->post['house_ext']; 362 } else { 363 $house_ext = ''; 364 } 365 366 $address = array( 367 'email' => $order_info['email'], 368 'telno' => $this->request->post['phone_no'], 369 'cellno' => '', 370 'fname' => $order_info['payment_firstname'], 371 'lname' => $order_info['payment_lastname'], 372 'company' => $order_info['payment_company'], 373 'careof' => '', 374 'street' => $street, 375 'house_number' => $house_no, 376 'house_extension' => $house_ext, 377 'zip' => $order_info['payment_postcode'], 378 'city' => $order_info['payment_city'], 379 'country' => $country 380 ); 381 382 $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']); 383 384 foreach ($product_query->rows as $product) { 385 $goods_list[] = array( 386 'qty' => (int)$product['quantity'], 387 'goods' => array( 388 'artno' => $product['model'], 389 'title' => $product['name'], 390 'price' => (int)str_replace('.', '', $this->currency->format($product['price'], $country_to_currency[$order_info['payment_iso_code_3']], '', false)), 391 'vat' => (float)$product['tax_rate'], 392 'discount' => 0.0, 393 'flags' => 0 394 ) 395 ); 396 } 397 398 if (isset($this->session->data['klarna'][$this->session->data['order_id']])) { 399 $totals = $this->session->data['klarna'][$this->session->data['order_id']]; 400 } else { 401 $totals = array(); 402 } 403 404 foreach ($totals as $total) { 405 if ($total['code'] != 'sub_total' && $total['code'] != 'tax' && $total['code'] != 'total') { 406 $goods_list[] = array( 407 'qty' => 1, 408 'goods' => array( 409 'artno' => '', 410 'title' => $total['title'], 411 'price' => (int)str_replace('.', '', $this->currency->format($total['value'], $country_to_currency[$order_info['payment_iso_code_3']], '', false)), 412 'vat' => (float)$total['tax_rate'], 413 'discount' => 0.0, 414 'flags' => 0 415 ) 416 ); 417 } 418 } 419 420 $digest = ''; 421 422 foreach ($goods_list as $goods) { 423 $digest .= utf8_decode(htmlspecialchars(html_entity_decode($goods['goods']['title'], ENT_COMPAT, 'UTF-8'))) . ':'; 424 } 425 426 $digest = base64_encode(pack('H*', hash('sha256', $digest . $klarna_account[$order_info['payment_iso_code_3']]['secret']))); 427 428 if (isset($this->request->post['pno'])) { 429 $pno = $this->request->post['pno']; 430 } else { 431 $pno = sprintf('%02d', (int)$this->request->post['pno_day']) . sprintf('%02d', (int)$this->request->post['pno_month']) . (int)$this->request->post['pno_year']; 432 } 433 434 if (isset($this->request->post['code'])) { 435 $pclass = (int)$this->request->post['code']; 436 } else { 437 $pclass = ''; 438 } 439 440 if (isset($this->request->post['gender']) && ($order_info['payment_iso_code_3'] == 'DEU' || $order_info['payment_iso_code_3'] == 'NLD')) { 441 $gender = (int)$this->request->post['gender']; 442 } else { 443 $gender = ''; 444 } 445 446 $transaction = array( 447 '4.1', 448 'API:OPENCART:' . VERSION, 449 $pno, 450 $gender, 451 '', 452 '', 453 (string)$order_info['order_id'], 454 '', 455 $address, 456 $address, 457 $order_info['ip'], 458 0, 459 $currency, 460 $country, 461 $language, 462 (int)$klarna_account[$order_info['payment_iso_code_3']]['merchant'], 463 $digest, 464 $encoding, 465 $pclass, 466 $goods_list, 467 $order_info['comment'], 468 array('delay_adjust' => 1), 469 array(), 470 array(), 471 array(), 472 array(), 473 array(), 474 ); 475 476 $xml = '<methodCall>'; 477 $xml .= ' <methodName>add_invoice</methodName>'; 478 $xml .= ' <params>'; 479 480 foreach ($transaction as $parameter) { 481 $xml .= ' <param><value>' . $this->constructXmlrpc($parameter) . '</value></param>'; 482 } 483 484 $xml .= ' </params>'; 485 $xml .= '</methodCall>'; 486 487 $header = array(); 488 489 $header[] = 'Content-Type: text/xml'; 490 $header[] = 'Content-Length: ' . strlen($xml); 491 492 $curl = curl_init(); 493 494 curl_setopt($curl, CURLOPT_URL, $url); 495 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 496 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); 497 curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); 498 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 499 curl_setopt($curl, CURLOPT_HTTPHEADER, $header); 500 curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); 501 502 $response = curl_exec($curl); 503 504 if (curl_errno($curl)) { 505 $log = new Log('klarna_account.log'); 506 $log->write('HTTP Error for order #' . $order_info['order_id'] . '. Code: ' . curl_errno($curl) . ' message: ' . curl_error($curl)); 507 508 $json['error'] = $this->language->get('error_network'); 509 } else { 510 preg_match('/<member><name>faultString<\/name><value><string>(.+)<\/string><\/value><\/member>/', $response, $match); 511 512 if (isset($match[1])) { 513 preg_match('/<member><name>faultCode<\/name><value><int>([0-9]+)<\/int><\/value><\/member>/', $response, $match2); 514 515 $log = new Log('klarna_account.log'); 516 $log->write('Failed to create an invoice for order #' . $order_info['order_id'] . '. Message: ' . utf8_encode($match[1]) . ' Code: ' . $match2[1]); 517 518 $json['error'] = utf8_encode($match[1]); 519 } else { 520 $xml = new DOMDocument(); 521 $xml->loadXML($response); 522 523 $invoice_number = $xml->getElementsByTagName('string')->item(0)->nodeValue; 524 $klarna_order_status = $xml->getElementsByTagName('int')->item(0)->nodeValue; 525 526 if ($klarna_order_status == '1') { 527 $order_status = $klarna_account[$order_info['payment_iso_code_3']]['accepted_status_id']; 528 } elseif ($klarna_order_status == '2') { 529 $order_status = $klarna_account[$order_info['payment_iso_code_3']]['pending_status_id']; 530 } else { 531 $order_status = $this->config->get('config_order_status_id'); 532 } 533 534 $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']])); 535 536 $this->model_checkout_order->addOrderHistory($this->session->data['order_id'], $order_status, $comment, 1); 537 538 $json['redirect'] = $this->url->link('checkout/success'); 539 } 540 } 541 542 curl_close($curl); 543 } 544 } 545 546 $this->response->addHeader('Content-Type: application/json'); 547 $this->response->setOutput(json_encode($json)); 548 } 549 550 private function constructXmlrpc($data) { 551 $type = gettype($data); 552 553 switch ($type) { 554 case 'boolean': 555 if ($data == true) { 556 $value = 1; 557 } else { 558 $value = false; 559 } 560 561 $xml = '<boolean>' . $value . '</boolean>'; 562 break; 563 case 'integer': 564 $xml = '<int>' . (int)$data . '</int>'; 565 break; 566 case 'double': 567 $xml = '<double>' . (float)$data . '</double>'; 568 break; 569 case 'string': 570 $xml = '<string>' . htmlspecialchars($data) . '</string>'; 571 break; 572 case 'array': 573 if ($data === array_values($data)) { 574 $xml = '<array><data>'; 575 576 foreach ($data as $value) { 577 $xml .= '<value>' . $this->constructXmlrpc($value) . '</value>'; 578 } 579 580 $xml .= '</data></array>'; 581 } else { 582 $xml = '<struct>'; 583 584 foreach ($data as $key => $value) { 585 $xml .= '<member>'; 586 $xml .= ' <name>' . htmlspecialchars($key) . '</name>'; 587 $xml .= ' <value>' . $this->constructXmlrpc($value) . '</value>'; 588 $xml .= '</member>'; 589 } 590 591 $xml .= '</struct>'; 592 } 593 break; 594 default: 595 $xml = '<nil/>'; 596 break; 597 } 598 599 return $xml; 600 } 601 602 private function getLowestPaymentAccount($country) { 603 switch ($country) { 604 case 'SWE': 605 $amount = 50.0; 606 break; 607 case 'NOR': 608 $amount = 95.0; 609 break; 610 case 'FIN': 611 $amount = 8.95; 612 break; 613 case 'DNK': 614 $amount = 89.0; 615 break; 616 case 'DEU': 617 case 'NLD': 618 $amount = 5.00; 619 break; 620 621 default: 622 $log = new Log('klarna.log'); 623 $log->write('Unknown country ' . $country); 624 625 $amount = null; 626 break; 627 } 628 629 return $amount; 630 } 631 632 private function splitAddress($address) { 633 $numbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); 634 635 $characters = array('-', '/', ' ', '#', '.', 'a', 'b', 'c', 'd', 'e', 636 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 637 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 638 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 639 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 640 'X', 'Y', 'Z'); 641 642 $specialchars = array('-', '/', ' ', '#', '.'); 643 644 $num_pos = $this->strposArr($address, $numbers, 2); 645 646 $street_name = substr($address, 0, $num_pos); 647 648 $street_name = trim($street_name); 649 650 $number_part = substr($address, $num_pos); 651 652 $number_part = trim($number_part); 653 654 $ext_pos = $this->strposArr($number_part, $characters, 0); 655 656 if ($ext_pos != '') { 657 $house_number = substr($number_part, 0, $ext_pos); 658 659 $house_extension = substr($number_part, $ext_pos); 660 661 $house_extension = str_replace($specialchars, '', $house_extension); 662 } else { 663 $house_number = $number_part; 664 $house_extension = ''; 665 } 666 667 return array($street_name, $house_number, $house_extension); 668 } 669 670 private function strposArr($haystack, $needle, $where) { 671 $defpos = 10000; 672 673 if (!is_array($needle)) { 674 $needle = array($needle); 675 } 676 677 foreach ($needle as $what) { 678 if (($pos = strpos($haystack, $what, $where)) !== false) { 679 if ($pos < $defpos) { 680 $defpos = $pos; 681 } 682 } 683 } 684 685 return $defpos; 686 } 687 }