ErrorMsgs['credit_card_validation_error'] = $this->Application->Phrase('lu_cc_validation_error'); $this->ErrorMsgs['credit_card_expired'] = $this->Application->Phrase('lu_cc_expired'); } /** * Return error message for field * * @param string $field * @return string * @access public */ public function GetErrorMsg($field, $force_escape = null) { if( $field != 'OrderNumber' ) return parent::GetErrorMsg($field, $force_escape); $number['error'] = parent::GetErrorMsg('Number', $force_escape); $number['pseudo'] = $this->GetErrorPseudo('Number'); $subnumber['error'] = parent::GetErrorMsg('SubNumber', $force_escape); $subnumber['pseudo'] = $this->GetErrorPseudo('SubNumber'); // if pseudo match & not empty -> return 1st // if one of pseudos not empty -> return it // if we got one pseudo "bad_type" and other pseudo "required", then return "bad_type" error message if( $number['pseudo'] && ($number['pseudo'] == $subnumber['pseudo']) ) { return $number['error']; } if( $number['pseudo'] && !$subnumber['pseudo'] ) { return $number['error']; } if( !$number['pseudo'] && $subnumber['pseudo'] ) { return $subnumber['error']; } if( $number['pseudo'] == 'bad_type' ) { return $number['error']; } if( $subnumber['pseudo'] == 'bad_type' ) { return $subnumber['error']; } // $msg = '['.$number_error.'('.$number_pseudo.')] ['.$subnumber_error.'] ('.$subnumber_pseudo.')'; // // return $msg; } function SetFieldsFromHash($hash, $set_fields=null) { parent::SetFieldsFromHash($hash, $set_fields); $options = $this->GetFieldOptions('PaymentCCExpDate'); if( $this->GetDirtyField($options['month_field']) || $this->GetDirtyField($options['year_field']) ) { $this->SetDirtyField('PaymentCCExpDate', 0); $this->SetField('PaymentCCExpDate', 0); } } /** * Returns gateway data based on payment type used in order * * @return Array */ function getGatewayData($pt_id=null) { // get Gateway fields if (!isset($pt_id) || !$pt_id) { $pt_id = $this->GetDBField('PaymentType'); } $pt_table = $this->Application->getUnitOption('pt','TableName'); $sql = 'SELECT GatewayId FROM %s WHERE PaymentTypeId = %s'; $gw_id = $this->Conn->GetOne( sprintf($sql, $pt_table, $pt_id) ); $sql = 'SELECT * FROM %s WHERE GatewayId = %s'; $ret = $this->Conn->GetRow( sprintf($sql, TABLE_PREFIX.'Gateways', $gw_id) ); // get Gateway parameters based on payment type $gwf_table = $this->Application->getUnitOption('gwf','TableName'); $gwfv_table = $this->Application->getUnitOption('gwfv','TableName'); $sql = 'SELECT gwfv.Value, gwf.SystemFieldName FROM %s gwf LEFT JOIN %s gwfv ON gwf.GWConfigFieldId = gwfv.GWConfigFieldId WHERE gwfv.PaymentTypeId = %s AND gwf.GatewayId = %s'; $ret['gw_params'] = $this->Conn->GetCol( sprintf($sql, $gwf_table, $gwfv_table, $pt_id, $gw_id), 'SystemFieldName' ); $ret['gw_params']['gateway_id'] = $gw_id; if ($this->GetDBField('IsRecurringBilling') && $this->Application->ConfigValue('Comm_AutoProcessRecurringOrders')) { if (isset($ret['gw_params']['shipping_control'])) { $ret['gw_params']['shipping_control'] = SHIPPING_CONTROL_DIRECT; } } return $ret; } /** * Checks if tangible items are present in order * * @return bool */ function HasTangibleItems() { $sql = 'SELECT COUNT(*) FROM '.TABLE_PREFIX.'OrderItems orditems LEFT JOIN '.TABLE_PREFIX.'Products p ON p.ProductId = orditems.ProductId WHERE (orditems.OrderId = '.$this->GetID().') AND (p.Type = '.PRODUCT_TYPE_TANGIBLE.')'; return $this->Conn->GetOne($sql) ? true : false; } /** * Calculates tax value of order items based on billing & shipping country specified * * @return double */ function getTaxPercent() { $cs_helper =& $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $shipping_country_id = $cs_helper->getCountryStateId($this->GetDBField('ShippingCountry'), DESTINATION_TYPE_COUNTRY); $shipping_state_id = $cs_helper->getCountryStateId($this->GetDBField('ShippingState'), DESTINATION_TYPE_STATE); $shipping_zip = (string) $this->GetDBField('ShippingZip'); $billing_country_id = $cs_helper->getCountryStateId($this->GetDBField('BillingCountry'), DESTINATION_TYPE_COUNTRY); $billing_state_id = $cs_helper->getCountryStateId($this->GetDBField('BillingState'), DESTINATION_TYPE_STATE); $billing_zip = (string) $this->GetDBField('BillingZip'); /* $dest_ids = array_diff( array_unique( Array( $shipping_country_id, $shipping_state_id, $billing_country_id, $billing_state_id ) ), Array(0) ); $dest_values = array_diff( array_unique( Array( $this->Conn->qstr($shipping_zip), $this->Conn->qstr($billing_zip) ) ), Array('\'\'') ); */ $tax = false; $sql = 'SELECT tx.* FROM '.$this->Application->getUnitOption('tax', 'TableName').' tx LEFT JOIN '.$this->Application->getUnitOption('taxdst', 'TableName').' txd ON tx.TaxZoneId = txd.TaxZoneId WHERE ( txd.StdDestId IN ('.$shipping_country_id.','.$shipping_state_id.') AND ( (txd.DestValue = "" OR txd.DestValue IS NULL) OR txd.DestValue = '.$this->Conn->qstr($shipping_zip).' ) ) OR ( txd.StdDestId IN ('.$billing_country_id.','.$billing_state_id.') AND ( (txd.DestValue = "" OR txd.DestValue IS NULL) OR txd.DestValue = '.$this->Conn->qstr($billing_zip).' ) ) ORDER BY tx.TaxValue DESC'; $tax = $this->Conn->GetRow($sql); if ($tax == false) { $tax['TaxValue'] = 0; $tax['ApplyToShipping'] = 0; $tax['ApplyToProcessing'] = 0; } return $tax; } function RecalculateTax() { $tax = $this->getTaxPercent(); $this->SetDBField( 'VATPercent', $tax['TaxValue'] ); $this->SetDBField( 'ShippingTaxable', $tax['ApplyToShipping']); $this->SetDBField( 'ProcessingTaxable', $tax['ApplyToProcessing']); $this->UpdateTotals(); $subtotal = $this->GetDBField('AmountWithoutVAT'); $query = 'SELECT SUM(Quantity * Price) FROM '.TABLE_PREFIX.'OrderItems AS oi LEFT JOIN '.TABLE_PREFIX.'Products AS p ON p.ProductId = oi.ProductId WHERE p.Type = 6 AND oi.OrderId = '.$this->GetDBField('OrderId'); $tax_exempt = $this->Conn->GetOne($query); if ($tax_exempt) $subtotal -= $tax_exempt; $this->SetDBField( 'VAT', round($subtotal * $tax['TaxValue'] / 100, 2) ); $this->UpdateTotals(); } function UpdateTotals() { $total = 0; $total += $this->GetDBField('SubTotal'); if ($this->GetDBField('ShippingTaxable')) $total += $this->GetDBField('ShippingCost'); if ($this->GetDBField('ProcessingTaxable')) $total += $this->GetDBField('ProcessingFee'); $this->SetDBField('AmountWithoutVAT', $total); $total += $this->GetDBField('VAT'); if (!$this->GetDBField('ShippingTaxable')) $total += $this->GetDBField('ShippingCost'); if (!$this->GetDBField('ProcessingTaxable')) $total += $this->GetDBField('ProcessingFee'); $total += $this->GetDBField('InsuranceFee'); $this->SetDBField('TotalAmount', $total); } function getTotalAmount() { return $this->GetDBField('SubTotal') + $this->GetDBField('ShippingCost') + $this->GetDBField('VAT') + $this->GetDBField('ProcessingFee') + $this->GetDBField('InsuranceFee') - $this->GetDBField('GiftCertificateDiscount'); } /** * Check field value by user-defined alghoritm * * @param string $field field name * @param Array $params field options from config * @return bool */ function CustomValidation($field, $params) { $res = true; $res = $res && $this->ValidateCCNumber($field, $params); $res = $res && $this->ValidateCCExpiration($field, $params); return $res; } function requireCreditCard() { $pt_table = $this->Application->getUnitOption('pt', 'TableName'); $sql = 'SELECT RequireCCFields FROM '.$pt_table.' pt LEFT JOIN '.TABLE_PREFIX.'Gateways gw ON gw.GatewayId = pt.GatewayId WHERE pt.PaymentTypeId = '.$this->GetDBField('PaymentType'); return $this->Conn->GetOne($sql); } /** * Check if field value is valid credit card number against credit card type specified * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateCCNumber($field, $params) { $cardtype_field = getArrayValue($params, 'cardtype_field'); $value = $this->GetDBField($field); if( !$cardtype_field || !$value || !$this->requireCreditCard() ) return true; if ($this->Application->ConfigValue('Comm_MaskProcessedCreditCards')) { $mask_found = strpos($value, str_repeat('X', 4)) !== false; if ($this->Application->isAdminUser && $mask_found) { // masked card numbers always appear valid in admin return true; } } if (defined('DEBUG_MODE') && kUtil::constOn('DBG_PAYMENT_GW')) { $gw_data = $this->getGatewayData(); $this->Application->registerClass( $gw_data['ClassName'], GW_CLASS_PATH.'/'.$gw_data['ClassFile'] ); $gateway_object =& $this->Application->recallObject( $gw_data['ClassName'] ); $test_numbers = $gateway_object->GetTestCCNumbers(); if (in_array($value, $test_numbers)) return true; } $error_field = isset($params['error_field']) ? $params['error_field'] : $field; // '1' => 'Visa','2' => 'Mastercard', '3' => 'Amex', '4' => 'Discover', 5 => 'Diners Club', 6 => 'JBC' // Innocent until proven guilty $cc_valid = true; // Get rid of any non-digits $value = preg_replace('/[^\d]/', '', $value); // Perform card-specific checks, if applicable switch( $this->GetDBField($cardtype_field) ) { case 2: // MasterCard $cc_valid = preg_match('/^5[1-5].{14}$/', $value); break; case 1: // Visa $cc_valid = preg_match('/^4.{15}$|^4.{12}$/', $value); break; case 3: // American Express $cc_valid = preg_match('/^3[47].{13}$/', $value); break; case 4: // Discover $cc_valid = preg_match('/^6011.{12}$/', $value); break; case 5: // Diners Club $cc_valid = preg_match('/^30[0-5].{11}$|^3[68].{12}$/', $value); break; case 6: // JBC $cc_valid = preg_match('/^3.{15}$|^2131|1800.{11}$/', $value); break; default: $this->SetError($error_field, 'credit_card_validation_error'); return false; break; } // The Luhn formula works right to left, so reverse the number. $value = strrev($value); $total = 0; for($x = 0; $x < strlen($value); $x++) { $digit = substr($value, $x, 1); // If it's an odd digit, double it if( $x / 2 != floor($x/2) ) { $digit *= 2; // If the result is two digits, add them if( strlen($digit) == 2 ) { $digit = substr($digit, 0, 1) + substr($digit, 1, 1); } } // Add the current digit, doubled and added if applicable, to the Total $total += $digit; } // If it passed (or bypassed) the card-specific check and the Total is // evenly divisible by 10, it's cool! if ($cc_valid && $total % 10 == 0) { return true; } else { $this->SetError($error_field, 'credit_card_validation_error'); return false; } } /** * Check if field value is non-expired credit card expiration date * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateCCExpiration($field, $params) { $formatter = getArrayValue($params, 'formatter'); if( ($formatter != 'kCCDateFormatter') || !$this->requireCreditCard() ) return true; if(!$this->Application->isAdminUser) { // validate expiration date only for front if (preg_match('/([\d]{2})\/([\d]{2})/', $this->GetDBField($field), $rets)) { $month = $rets[1]; $year = $rets[2]; $now_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y') ); $day_count = adodb_date('t', adodb_mktime(0, 0, 0, $month, 1, $year) ); $cc_date = adodb_mktime(23, 59, 59, $month, $day_count, $year); if ($cc_date < $now_date) { $error_field = isset($params['error_field']) ? $params['error_field'] : $field; $this->SetError($error_field, 'credit_card_expired'); return false; } } } return true; } function getNextSubNumber() { $table = $this->Application->GetLiveName($this->TableName); $sql = 'SELECT MAX(SubNumber) FROM '.$table.' WHERE Number = '.$this->GetDBField('Number'); return $this->Conn->GetOne($sql) + 1; } function ResetAddress($prefix) { $fields = Array('To','Company','Phone','Fax','Email','Address1','Address2','City','State','Zip','Country'); foreach($fields as $field) { $this->SetDBField($prefix.$field, $this->Fields[$prefix.$field]['default']); } } function IsProfileAddress($address_type) { return $this->Application->GetVar($this->Prefix.'_IsProfileAddress'); } // ===== Gift Certificates Related ===== function RecalculateGift(&$event) { $gc_id = $this->GetDBField('GiftCertificateId'); if ($gc_id < 1) { return; } $gc =& $this->Application->recallObject('gc', null, Array('skip_autoload' => true)); /* @var $gc kDBItem */ $gc->Load($gc_id); if ($gc->GetDBField('Status') == gcDISABLED) { // disabled GC $this->SetDBField('GiftCertificateId', 0); $this->SetDBField('GiftCertificateDiscount', 0); // disabled return; } $debit = $gc->GetDBField('Debit') + $this->GetDBField('GiftCertificateDiscount'); $this->UpdateTotals(); $total = $this->GetDBField('TotalAmount'); $gift_certificate_discount = $debit >= $total ? $total : $debit; $this->SetDBField('TotalAmount', $total - $gift_certificate_discount); $this->GetDBField('GiftCertificateDiscount', $gift_certificate_discount); $debit -= $gift_certificate_discount; $gc->SetDBField('Debit', $debit); $gc->SetDBField('Status', $debit > 0 ? gcENABLED : gcUSED); $gc->Update(); if ($gift_certificate_discount == 0) { $this->RemoveGiftCertificate($object); $event->SetRedirectParam('checkout_error', 108); } $this->SetDBField('GiftCertificateDiscount', $gift_certificate_discount); } function RemoveGiftCertificate() { $gc_id = $this->GetDBField('GiftCertificateId'); $gc =& $this->Application->recallObject('gc', null, Array('skip_autoload' => true)); /* @var $gc kDBItem */ $gc->Load($gc_id); $debit = $gc->GetDBField('Debit') + $this->GetDBField('GiftCertificateDiscount'); if ($gc->isLoaded() && ($debit > 0)) { $gc->SetDBField('Debit', $debit); $gc->SetDBField('Status', gcENABLED); $gc->Update(); } $this->SetDBField('GiftCertificateId', 0); $this->SetDBField('GiftCertificateDiscount', 0); } }