Check This Out: Part 2

Originally Published 2016-01-13

Alright, so we got the data we need out of NetSuite to create an order form. Those pieces of data are Items, Price Levels, and Billing Schedules. Throw a couple radio buttons down, ask for a Credit Card number, and we’re done! Well, so we thought.

First, you’ll notice some interesting behavior when getting Items out of NetSuite. It turns out that ItemSearch will return any sort of thing that can show up on a SalesOrder. That includes Inventory Items and Non-Inventory Items, which are only really different in the fact that one, well, has an inventory to keep track of. Those are pretty obvious, but a little weirder is that it also returns Discounts. Discounts in NetSuite are just items to add to an invoice, and they are a little different than the other items. If you use the code from last week, you’ll notice the main difference is that they don’t have price levels, which may cause your code to throw an error.

We can filter for those in the ItemSearchBasic object with the field Type. Type is a SearchEnumMultiSelectField, and setting it will look something like this:

$search->type = new SearchEnumMultiSelectField();
$search->type->operator = SearchEnumMultiSelectFieldOperator::anyOf;
$search->type->value=[RecordType::_inventoryItem];

So we can see here kind of what this does, and how type searches are used. Record Type is an Enum in the system, and their values can be looked up using the static class. This also works with Countries, as we’ll get to in a second.

So discounts can either be avoided or retrieved this way. On my order form, I made them a property of the item being purchased, and I just end up adding both the item and the discount to the SalesOrder when this item is selected on my order form.

Alright, we got that out of the way, now we can make an order form. We can split an order form into 3 sets of information to collect, which you can get as little or as much of as you’d like.

  1. Customer Information
  2. Billing Information
  3. Purchase Information

Customer Information lists the customer’s identity, and tells you where to send the product. For just about any sort of product, you’ll want to collect an email address. This gives you a reasonably unique identifier of a customer. If you’re selling physical products, you probably also want their shipping address, first name, last name, and maybe phone number.

Billing Information lists how the customer plans to pay you. For Credit Card Payments, which we’ll focus on, you’ll at least need the Card Number and Expiration Date. For more advanced security you’ll want the CVC (also known as CVV, or a few other names. It’s the number on the back of the card), and parts or all of the billing address. For certain types of businesses, ACH payments might also be accepted here.

Purchase Information is what the customer is buying. That’s all that stuff we collected last week. You could get this via a shopping cart, or just select a combination from  a basic order form. It’s really up to you how you collect all this information, and I’m not going to get too caught up in the details here. I’m assuming you’re building this custom because it needs to be custom.

Alright, order form is up, customer fills out your form and clicks “BUY NOW!”. What needs to happen for you to get money? Here’s the steps I took, the first time through.

  1. Look up customer information to find current customer
  2. Add new customer or update current customer information
  3. Create Sales Order for customer with billing information and products
  4. Check Sales Order status to see if credit card was authorized.
  5. Do whatever your process is to get the customer the product they just bought.

You may notice I said “the first time through”. This actually ends up being a really bad experience from the customer end, due to some limitations of NetSuite. However, it is a good way to show off how NetSuite does things, and the order of operations in creating things, so I’ll go over that.

First step, look up customer information. You’re going to want to use CustomerSearchBasic. It works pretty much the same as ItemSearchBasic; you could look up a customer by email address just like this.

$search = new CustomerSearchBasic();
$search->email = new SearchStringField();
$search->email->operator = SearchStringFieldOperator::is;
$search->email->value= $request->input('email');

$req = new SearchRequest();
$req->record =$search;
$service->search($req);

I just threw in a little Laravel there for the email value. You could also use $_POST[’email’] or whatever you wanted. I don’t believe any sort of injection is possible here, but sanitizing your inputs is never a bad idea. You can’t be too careful!

But hey, the user might be logged in! What if we saved their NetSuite Customer internalId in the session? Then we could look up a customer without using the search function, like this!

$request = new GetRequest();
$request->baseRef = new RecordRef();
$request->baseRef->internalId = $internalId;
$request->baseRef->type = RecordType::customer;
$resp = $this->service->get($request);
if ($resp->readResponse->status->isSuccess) {
    return $resp->readResponse->record;
}

So that’s pretty simple.  A GetRequest looks up any record by internalId and type. You can use this for any record in the whole system. And depending on your SearchPreferences, it doesn’t just retrieve the customer, but can also the customer’s address records, billing method records, and other sub lists all at once.

Next step is to add or update the customer record. We want to do this to keep updated information, add new billing methods, addresses, or anything else. This gets a little more involved.

Important points here: One, many properties cannot be updated directly. These can change depending on the type of record, or whether you’re adding or updating a record. Some of these are obvious: timestamp fields and derived properties. Others you can find in the documentation, or just get ready to receive an error back from the web server. Two, the objects you’re editing are creating XML SOAP Requests. So any set property is going to be pushed to the server, and NetSuite is going to try to save that piece of data. What this really means is you can’t really do something like this:

$request = new GetRequest();
$request->baseRef = new RecordRef();
$request->baseRef->internalId = $internalId;
$request->baseRef->type = RecordType::customer;
$resp = $this->service->get($request);
if ($resp->readResponse->status->isSuccess) {
    $record = $resp->readResponse->record;
    $record->firstName = "Thwiv";
    save($record);
}

While I gloss over how that save method works for the moment, that seems like it would update the customer’s first name. What this actually does is send the entire customer record you’ve retrieved, including subrecords if you’re SearchPreferences are set, to NetSuite to be updated. This includes the properties that cannot be set, which will cause an error, and you will get frustrated and yell obscenities for a few hours while you try to figure out what’s wrong.  Don’t do that.

What you really want to do is create a new object to be updated. Sort of like this method here.

function save($old_customer = null, $newFirstName){

$add = true;
    $customer = new Customer();
    if(!empty($old_customer)){
        $add = false;
        $customer->internalId = $old_customer->internalId;
    }
    if($newFirstName != $oldCustomer->firstName){
        $customer->firstName = $newFirstName;
    }
    if($add){
        $req = new AddRequest();
        $req->record = $customer;
        $resp = $this->service->add($req);
    } else {
        $req = new UpdateRequest();
        $req->record = $customer;
        $resp = $this->service->update($req);
    }

}

As you can see, we set the internalId of the new customer object to that of the one we pulled from NetSuite. The internalId is the unique key for all objects in NetSuite, so setting that on an object tells it what to update. If it’s not set, an update won’t work, and if it is set and you try to do an Add, you will get an error. The Add and Update Requests are fairly straightforward, and you pass the same information.

And to save time, you can add or update several related records at the same time. Check this out:

$address = new CustomerAddressbook();
$address->addressbookAddress = new Address();

$address->addressbookAddress->attention = $address->first . ' ' . $address->last;
$address->addressbookAddress->addr1 = $address->line1;
$address->addressbookAddress->city = $address->city;
$address->addressbookAddress->zip = $address->zip;
$address->addressbookAddress->state = $address->state;
$address->addressbookAddress->country = $my_country;

$book = new CustomerAddressbookList();
$book->addressbook = array($address);
$book->replaceAll = false;

$customer->addressbookList = $book;

 

Adding some code like this to your customer object will also add a new address record to the customer object. A few things to notice:

  1. An Address is a single record of an Addressbook, which is in a list contained in AddressbookList. This is true of all “Contact” types (Contacts, Customers, Employees, Partners). Why? Addressbook allows you to name the address. This is, to me, a little overengineering on NetSuite’s part, but it’s the way things are, and I’d say it’s better to live with it than to make a fuss.
  2. replaceAll on the CustomerAddressbookList does what it sounds like. False makes it append the new record(s), true makes it delete the old and replace them. This is a feature in most of the other subrecord field lists as well. I am not a fan of this feature, though I understand the point. I usually consider deleting records to be a “bad thing”, and making it this easy seems a bit…wrong. However if you were to say, grab the addressbookList from your old_customer, and then pass the whole thing into your new customer record and set replaceAll to true, this might be a simpler operation.
  3. You’ll see that country is not just a String like the other address fields. Country is always pulled from the NetSuite list of countries which have their own ID. These are not ISO Compliant, and they are generally the country name in camel case english, preceded by an underscore (ex: _unitedStates). You can use the Country facade to look up specific countries, (ex: Country::unitedStates) but It’s probably in your best interest to save a dictionary between ISO standard and NetSuite somewhere.

You can look up the fields you can update in the API. Another you might want to consider updating are the customer’s saved payment methods, which would look like this.

$cc = new CustomerCreditCards();
$cc->ccDefault = true;
$expDate = date('Y-m-d\Th:i:s.i-h:i', strtotime($ccObj->year.'-'.$ccObj->month.'-01'));
$cc->ccExpireDate = $expDate; 

$cc->ccNumber = $ccObj->cc;
$cc->ccName = $ccObj->first . ' ' . $ccObj->last;
$cc->paymentMethod = new RecordRef();
$cc->paymentMethod->type = RecordType::paymentMethod;
$cc->paymentMethod->internalId = getCreditCardType($ccObj->card);

$ccList = new CustomerCreditCardsList();


$ccList->creditCards = array($cc);
$ccList->replaceAll = false;

$customer->creditCardsList = $ccList;
  1. First thing you see here, the expiration date has a format, despite that usually you only get a month and year
  2. Payment Methods are in a list in a NetSuite table. You’ll usually see this as the Credit Card Type, which is Visa, MasterCard, Discover, American Express in the US. You can grab the credit card type from the user like many sites do, but if you wanted, you can use the first digit of the card number to determine the Card Type in most cases. 3 is American Express, 4 is Visa, 5 is MasterCard, and 6 is Discover.

Alright, that’s the basics of updating a customer. I was going to keep going, but this post is getting like, holy crap super long, and I’m already late. I’m not writing the Game of Thrones novels, so I’ll just publish and continue next time. I’ll show you how to create a SalesOrder, and maybe talk more about why this isn’t the best order of operations.

Add a Comment

Your email address will not be published. Required fields are marked *