5

I am working with the magento api and I have run into a bit of an issue with creating an order. I have been able to get everything up to creating the order to work correctly. The issue that I am seeing is when I call the method to create the order I always get the exception: Credit card number mismatch with credit card type.

I am running Magento ver. 1.6.2.0

I have verified that the card I am testing with works via the magento frontend.

Any help with this is greatly appreciated.

This is the test code that I am using:

<?php
$proxy = new SoapClient('http://localhost/index.php/api/soap/?wsdl');
$sessionId = $proxy->login('shopapi', 'test123');

// Create a quote, get quote identifier
$shoppingCartId = $proxy->call( $sessionId, 'cart.create');

// Set customer, for example guest
$customerAsGuest = array(
    "firstname" => "testFirstname",
    "lastname" => "testLastName",
    "email" => "test@test.com",
    //"website_id" => "0",
    //"store_id" => "0",
    "mode" => "guest"
);
$resultCustomerSet = $proxy->call($sessionId, 'cart_customer.set', array( $shoppingCartId, $customerAsGuest) );

// Set customer addresses, for example guest's addresses
$arrAddresses = array(
    array(
        "mode" => "shipping",
        "firstname" => "testFirstname",
        "lastname" => "testLastname",
        "company" => "testCompany",
        "street" => "testStreet",
        "city" => "testCity",
        "region" => "CA",
        "postcode" => "90049",
        "country_id" => "US",
        "telephone" => "0123456789",
        "fax" => "0123456789",
        "is_default_shipping" => 0,
        "is_default_billing" => 0
    ),
    array(
        "mode" => "billing",
        "firstname" => "testFirstname",
        "lastname" => "testLastname",
        "company" => "testCompany",
        "street" => "testStreet",
        "city" => "testCity",
        "region" => "CA",
        "postcode" => "90049",
        "country_id" => "US",
        "telephone" => "0123456789",
        "fax" => "0123456789",
        "is_default_shipping" => 0,
        "is_default_billing" => 0
    )
);
$resultCustomerAddresses = $proxy->call($sessionId, "cart_customer.addresses", array($shoppingCartId, $arrAddresses));

// add products into shopping cart
$arrProducts = array(
    array(
        "product_id" => "1",
        "qty" => 1
    )
);
$resultCartProductAdd = $proxy->call($sessionId, "cart_product.add", array($shoppingCartId, $arrProducts));


// get list of products
$shoppingCartProducts = $proxy->call($sessionId, "cart_product.list", array($shoppingCartId));


// set payment method
$paymentMethod = array(
    "method" => "authorizenet",
    "cc_type" => 'MC',
    "cc_number" =>'5555555555554444' ,
    "cc_exp_month" => 9,
    "cc_exp_year" => 2014,
    "cc_cid" => 123     
);
$resultPaymentMethod = $proxy->call($sessionId, "cart_payment.method", array($shoppingCartId, $paymentMethod));


// create order
$resultOrderCreation = $proxy->call($sessionId,"cart.order",array($shoppingCartId));
var_dump($resultOrderCreation);
 ?>
rottmanj
  • 531
  • 1
  • 9
  • 21

5 Answers5

8

Your call to cart_payment.method is succeeding, according to your post, so the CC number is validating to be a MC card, as expected.

The problem is that Magento, due to PCI concerns, does NOT save the CC number in the database (in most cases).

So, as you are sending payment details, along with CC number and CID, in one request and then creating the order in another, the state is lost and the CC number and CID are blanked out.

When the order is created, the payment data is validated a second time and when that happens, it has an empty CC number with a type of MC, causing the fault you see.

Unfortunately, I do not see a way to make cart.order accept payment data as a work around.

You could write a module to extend the checkout APIs with a new method that does both steps in a single call and that would likely solve the problem.

Lee Saferite
  • 3,124
  • 22
  • 30
2

I ran into this issue as well. My solution was to create a custom SOAP endpoint which handled all of the order creation and submission logic at once without multiple API calls.

Even this way, I was getting card type mismatch exceptions.

The trick was once I set the payment info, collect totals, and save quote, with the getPayment->importData method, it seems to forget the card info. Probably for (not the best way to handle) PCI-DSS compliance.

To solve it, I added another getPayment->importData line after saving the quote, before submitting the order.

This is achieved without changing any of the core files.

See example:

public function customCheckout($checkoutData=false)
  {
    if(!$checkoutData){
      Mage::throwException("No checkout data received.");
    }
    if(!json_decode($checkoutData)){
      Mage::throwException("Bad checkout data received.");
    }

    $data = json_decode($checkoutData);

    $email = 'email@email.cc';

    // get the basic store info to associate with order
    $websiteId  = Mage::app()->getWebsite()->getId();
    $store      = Mage::app()->getStore();

    // begin checkout with a quote
    $quote    = Mage::getModel('sales/quote')->setStoreId($store->getId());

    // set customer by email
    $customer = Mage::getModel('customer/customer')
                  ->setWebsiteId($websiteId)
                  ->loadByEmail($email);

    // handle customer not exists by creating a new customer
    if($customer->getId() == ''){
      $customer = Mage::getModel('customer/customer');
      $customer->setWebsiteId($websiteId)
               ->setStore($store)
               ->setFirstName("Bob")
               ->setLastName("Loblaw")
               ->setEmail($email)
               ->setPassword('password');
      $customer->save();
    }

    // assign customer to SO quote
    $quote->assignCustomer($customer);

    // do we want to send a confirmation email to the customer?
    // my guess is we would handle that in a separate service.
    $quote->setSendConfirmation(0);

    // add products to quote
    foreach($data->products as $item){
      $product = Mage::getModel('catalog/product')->load($item->id);
      $quote->addProduct($product,new Varien_Object(array('qty'=>$item->qty)));
    }

    // set SO billing address
    $billingAddress = $quote->getBillingAddress()->addData(array(
      'customer_address_id' => '',
      'prefix' => '',
      'firstname' => $data->customer->firstName,
      'middlename' => '',
      'lastname' => $data->customer->lastName,
      'suffix' => '',
      'company' => '',
      'street' => array(
        '0' => 'street1',
        '1' => 'street2'
      ),
      'city'=>'city',
      'country_id'=>'US',
      'region'=>'WA',
      'postcode'=>'98101',
      'telephone' => '425-425-4254',
      'fax' => '789-789-7897',
      'vat_id' => '',
      'save_in_address_book' => 0
    ));

    // set SO shipping address, this will probably be the location of sale, on-site
    $shippingAddress = $quote->getShippingAddress()->addData(array(
      'customer_address_id' => '',
      'prefix' => '',
      'firstname' => $data->customer->firstName,
      'middlename' => 'middle',
      'lastname' => $data->customer->lastName,
      'suffix' => '',
      'company' => '',
      'street' => array(
        '0' => 'street1',
        '1' => 'street2'
      ),
      'city'=>'city',
      'country_id'=>'US',
      'region'=>'WA',
      'postcode'=>'98201',
      'telephone' => '425-425-4254',
      'fax' => '789-789-7897',
      'vat_id' => '',
      'save_in_address_book' => 0
    ));

    // set shipping method, if it's sold on site we aren't charging for delivery
    $shipMethod='freeshipping_freeshipping';

    $shippingAddress->setCollectShippingRates(true)
                    ->collectShippingRates()
                    ->setShippingMethod($shipMethod)
                    ->setPaymentMethod($data->payment->method);

    // set payment method
    $quote->getPayment()->importData(array(
      'method'      =>$data->payment->method,
      'cc_type'     =>$data->payment->type,
      'cc_number'   =>$data->payment->number,
      'cc_exp_year' =>$data->payment->expYear,
      'cc_exp_month'=>$data->payment->expMonth,
    ));

    // collect totals, save quote
    $quote->collectTotals()->save();

    // turn the quote into an order
    $service = Mage::getModel('sales/service_quote',$quote);

    // set payment method A SECOND TIME!!!!!!!!!!!!!
    $quote->getPayment()->importData(array(
      'method'      =>$data->payment->method,
      'cc_type'     =>$data->payment->type,
      'cc_number'   =>$data->payment->number,
      'cc_exp_year' =>$data->payment->expYear,
      'cc_exp_month'=>$data->payment->expMonth,
    ));

    $service->submitAll();
    $increment_id = $service->getOrder()->getRealOrderId();

    $quote = $customer = $service = null;

    $retval = new stdClass;
    $retval->orderId = $increment_id;

    return json_encode($retval);
  }
Brad Fellows
  • 171
  • 8
  • This solution works for me. Whether it will lead any problem related to PCI complaint? @brad – Karthi Skb May 30 '17 at 16:07
  • It should not lead to any PCI problems. The way my example is written, it doesn't persist the card information past sending it to the payment method. If the payment method you use persists the card data, then you already have PCI exposure. – Brad Fellows May 31 '17 at 17:46
  • i'm using out of the box authorize.net module and saveCC is already disabled in module level. So it will not lead to any issue right? – Karthi Skb Jun 01 '17 at 06:28
  • I hope not but I would verify that if you have concerns. Check logs and/or analyze the database to look for card information. – Brad Fellows Jun 02 '17 at 17:50
1

I have never done successful API transactions to the payment method code authorizenet. This only always succeeds via frontend, not sure why.. nor have I had time to investigate why. Although what I have always done instead is to use authorizenet_direct post instead. My reasoning behind this / how I stumbled upon this is that the same also applies to the Paypal payment methods. API seems to only work with paypal_direct or paypaluk_direct. Try it! it should work. At the very least, this works for me.

$paymentMethod = array(
    "method" => "authorizenet_directpost",
    "cc_type" => 'MC',
    "cc_number" =>'5555555555554444' ,
    "cc_exp_month" => 9,
    "cc_exp_year" => 2014,
    "cc_cid" => 123     
);
Seth Malaki
  • 4,436
  • 23
  • 48
  • The issue with this is it leaves the order in a state of pending payment and not in a state of processing. I have set it to authorize and capture but it never seems to capture. – rottmanj May 20 '12 at 04:27
  • It's likely not capturing because it doesn't have a valid transaction to reference for the capture. The AuthorizeNet DirectPost method doesn't seem compatible with the Checkout API unless perhaps you do the payment detail post manually and get back a valid transaction. Not sure if that's even possible. – Lee Saferite May 23 '12 at 08:36
1

Actually I just discovered a way to take authorize.net payments via the API.

First in /app/code/core/Mage/Payment/Model/Method/Cc.php, uncomment the line about saving the cid, so the function look like this on line 65

public function prepareSave()
{
    $info = $this->getInfoInstance();
    if ($this->_canSaveCc) {
        $info->setCcNumberEnc($info->encrypt($info->getCcNumber()));
    }
    $info->setCcCidEnc($info->encrypt($info->getCcCid()));
    $info->setCcNumber(null)
        ->setCcCid(null);
    return $this;
}

And then in the main model file of your payment method, make sure the variable is set to:

    protected $_canSaveCc = true;

I've tested 2 payment methods using this and both work fine via API now.

no_chocobo
  • 19
  • 1
  • I have the same issue and tried your solution but it's not working.. I am supposed to do something else as well which i might be missing... Thank you – Satish Aug 21 '15 at 06:46
  • Please don't do it this way. You are allowing Magento to save the cardholder data, which is a significant PCI-DSS exposure that you are better off avoiding if at all possible. And it's usually not a good idea to hack Magento core; that is a good way to maybe or maybe not lose your changes if you decide to apply an update. – Brad Fellows Oct 12 '17 at 03:55
1

this is my solution: goto /app/code/core/Mage/Payment/Model/Method/Cc.php and see function :

public function prepareSave()
{
    $info = $this->getInfoInstance();
    if ($this->_canSaveCc) {
        $info->setCcNumberEnc($info->encrypt($info->getCcNumber()));
    }
    //$info->setCcCidEnc($info->encrypt($info->getCcCid()));
    $info->setCcNumber(null)
        ->setCcCid(null);
    return $this;
}

only comment line :

$info->setCcNumber(null)
 ->setCcCid(null);
Vu Anh
  • 209
  • 2
  • 7
  • I have the same issue and tried your solution but it's not working.. I am supposed to do something else as well which i might be missing... Thank you – Satish Aug 21 '15 at 10:40