Check This Out
Originally Published 2015-12-28
So, I’m taking our current checkout system and building it out in NetSuite. We are currently using InfusionSoft, which has a pretty decent checkout system built in. It has some issues that I don’t like, however.
Foremost, it requires you to use a page on their server to do a checkout, so I’m sending people to xxxx.infusionsoft.com to buy our products. To an average user, this screams “Not Legitimate”. I currently get around this problem by copying the form that InfusionSoft creates into a page on my own server, and setting the action to their server, and then setting the “Thank You” page option to a page on our server. This works well in most cases, but it has a few issues.
First, the user leaves our site while credit card validation happens. If they hit any issues, they are stuck on an ugly IS error page, and not redirected back to our checkout form. This is clunky, and I wish it didn’t happen this way.
Second, to actually do something in our system, like add a membership, send out a product, or really anything, we have to use an external POST call from InfusionSoft to us. This has some security implications, as a person that knows how this works could take advantage and give out free products, or worse set up their own checkout system that funnels the money to them but adds our products! You end up having to deal with a secret key system and that gets a little messy. So my aim when creating a checkout system with NetSuite was to get away from all of this, and host all checkout processes on my server.
Checkout in NetSuite can be set up a multitude of ways. You’ll probably deal with a consultant that will give you several options, or maybe you’ll opt out of that expense and try to implement the whole thing yourself (this…is possible. It’s probably a bad idea). But it’s really up to your finance/accounting department how they want it all to work.
There are 3 Record Types that can indicate a payment in NetSuite. Those are SalesOrder, CashSale, and CustomerPayment. They are all slightly different. SalesOrders generally indicate a customer asked to purchase something. CashSales indicate that we asked the customer to pay some percentage of the total, and a CustomerPayment usually means that the customer gave us some money. It doesn’t have to work like this, but that’s sort of the idea. This should be discussed with your NetSuite customization expert and your finance department.
We set up our process to ignore CustomerPayment; A SalesOrder is created when the user submits the payment method information, and a CashSale is created when NetSuite processes the payment. If you’re set up differently, don’t worry! You can use what you learn to implement a different method.
So, here’s what we need to do: Get a list of products, price levels, and billing schedules; create an Order Form with different variations of these three things a user can select; Process the submission of an order form.
I’m focussing on Order Forms versus Shopping Carts because it’s a simpler concept. There is a lot of overlap, and we can get to the juicy parts of NetSuite integration without discussing a lot about cart sessions and the like. Also, this is how my company’s business model is set up, so that means I can talk about things I’ve actually done.
You installed This NetSuite toolkit, right?
So first thing, while you’re building your checkout page creator, you’ll need to get a list of products, their pricing, and the billing schedules. You’ll want to be able to configure different combinations of these in order to list out options on your order form. Here’s some code to get your products.
public function getProductList(){ $service = new NetSuiteService(); $service->setSearchPreferences(false, 1000); $itemSearch = new ItemSearchBasic(); $itemSearch->isInactive = new SearchBooleanField(); $itemSearch->isInactive->searchValue = false; $request = new SearchRequest(); $request->searchRecord = $itemSearch; $response = $service->search($request); if($response->searchResult->status->isSuccess){ $records = $response->searchResult->recordList->record; return $records; } return []; }
Alright lets talk about what this does. The first line instantiates the toolkit. You can also pass your configuration options into this constructor, but I’d recommend setting environment variables instead and leaving this blank. That’s all discussed in the github readme. The next line sets Search Preferences, which basically sets how much data you want back when you perform a Search request. The first parameter asks if you want “Body Fields Only”, and the second parameter is the maximum number of records per request. Body fields are the fields of a record, non body fields are connected records like transaction rows and other things. Here we want the subfields, they’re important.
The maximum records parameter can only be one of a few options. Setting it to “1” will result in an error. In general, we set this to 500 or 1000. Don’t worry, if there’s only one record in a search, it only returns 1. If there’s more, we’ll have to use some different functions that we’ll discuss in a different post.
ItemSearchBasic is the first of many Search Objects we’ll use. These Objects are basically the top level of an XML SOAP Request, in OO form. inside them you will find any sort of field that you can possibly search on. To filter on those fields, we instantiate it with a SearchField, the type of which depends on the type of the field. So a String will use SearchStringField, and in our case above, a boolean uses SearchBooleanField. In this case, we’re looking for any Item that is not inactive. We’ll get into these more later.
We then create a SearchRequest, which is just a wrapper for a search object, and then pass it into the NetSuiteService’s “search” function. This sends a message to NetSuite, and will return you a list of all your products in “response”.
So next we check the response, which is of type SearchResponse; a wrapper for your results.
$response->searchResult->status
contains an object with the outcome of your query. If it was successful, isSuccess will be set to true. Otherwise, error codes and messages will be found in this object.
The actual results are found in
$response->searchResult->recordList->record
This is a common pattern in NetSuite responses: lists are stored 2 levels deep, with xList being a wrapper for x, which is an array of type x. You get used to it. We want to return the list.
So that’s how you get any item that could be placed on a sales order. The sales order will need the item’s internalId, which is the identifier used throughout NetSuite. Everything that is stored in NetSuite has an internalId, and you can always identify an object by its RecordType and internalId. So we’ll store that field in our order form database, however you would like.
Next we need pricing for those items, which is actually stored in the record that was returned. Pricing is stored in a Matrix pattern, specifically, in
$product->pricingMatrix->pricing
This object structure is a bit confusing, but it helps deal with processing payments in multiple currencies. $product->pricingMatrix->pricing is a list of (Price Level, Currency, Prices) Tuples. The currency is self-explanatory: it’s one of the currency types your business has set up to work in. The Price Level is a name for the list of prices at a currency. The list of prices, stored in the priceList, is the value of the product at a particular price level at a particular currency. In general, there is only one price in a price level at a particular currency, but there can be more than one (I’m going to be honest with you. I haven’t figured out why this is structured this way). You’ll need all of these to put onto the Sales Order, and your checkout page is probably going to want to flatten this structure, in order to give the user an easier time. So lets make this easy.
Here’s how you’ll probably want to get all the values.
foreach($product->pricingMatrix->pricing as $price){ $product_id = $product->internalId; //same on each iteration $product_name = $product->itemId; $currency_id = $price->currency->internalId; $currency_name = $price->currency->name; //will be 'US Dollar' or 'Euro' or something $price_level_id = $price->priceLevel->internalId; $price_level_name = $price->priceLevel->name; $value = $price->priceList->price[0]->value; //save these somewhere }
Billing Schedules are not required. If you want to charge the user all at one time, all the time, you can actually ignore this section. However, if you want to set up delayed billing or payment plans, you’ll need a billing schedule set up in NetSuite. Here’s how you get those:
public function getBillingSchedules(){ $service = new NetSuiteService(); $this->service->setSearchPreferences(false, 1000); $srch = new BillingScheduleSearchBasic(); $srch->isInactive = new SearchBooleanField(); $srch->isInactive->searchValue = false; $req = new SearchRequest(); $req->searchRecord = $srch; $response = $this->service->search($req); if($response->searchResult->status->isSuccess){ return $response->searchResult->recordList->record; } return []; }
So that’s it! That’s how you’ll get all the information you need out of NetSuite to create a checkout page. Next week we’ll talk about some technical challenges with why you should probably be storing this information in your own database, and how to perform the checkout. Thanks for reading!