For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at https://ascentbusiness.co.uk/zoho-services/uk-zoho-support.  For larger projects, talk to our experts and receive dedicated support from our hands-on project consultants at https://ascentbusiness.co.uk/zoho-services/zoho-crm-implementation.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at https://ascentbusiness.co.uk.

ZohoCRM Webhook: Create ZohoInventory Records from an eBay order

What?
This is a not-so-quick article that queries an eBay order and creates the relevant ZohoInventory item, contact, sales order, package slip, shipment order, invoice, payment records...

Why?
I'm storing this here as a reference and taking the bits that I need for the various clients that request an eBay integration. This is a comprehensive snippet which does the whole lot.

How?
Using the method of "mega-functions", here is the code snippet for one function which will accept as parameter the eBay order reference and generate all the respective records in Zoho Inventory. We're using a ZohoCRM webhook, because CRM webhooks run more reliably then the ones we have found in ZohoBooks and other Zoho Apps.

Prerequisites:
  • Setup a connection called "joel_books" that has the necessary scopes to view taxes and chart of accounts
  • Setup a connection called "joel_inventory" that has the necessary scopes to do everything under the sun
  • Setup a function that generates an access token of the Trading API for the eBay store
copyraw
/*
    Function: fn_eBay_GetOrderInfoCreateUpdateZohoSO(string p_eBayOrderRef)
	
    Purpose: Queries eBay for information about an order and then creates item/contact/sales order/package slip/invoice (if not exist)
	
    Date Created:   2022-05-05 (Joel Lipman)
                    - Initial release
	Date Modified: 	2023-01-23 (Joel Lipman)
					- Revamp of code
					- Only generate invoice if payment received
					- Distinguish whether composite based on ebay item specific
					- Add stock in order to authorize payment creation
	Date Modified: 	2023-02-02 (Joel Lipman)
					- Marked Items as inclusive of VAT so VAT reports in Books will be accurate (eg. £499.95 is correctly £416.62)
	Date Modified: 	2023-02-23 (Joel Lipman)
					- Check if ebay Order ID already exists on system to determine whether to update or create.
					- Check if SalesOrder already exists on system to determine whether to update or create.
					- Check if Invoice already exists on system to determine whether to update or create.
					- Check if Package already exists on system to determine whether to update or create.
					- Check if Shipment already exists on system to determine whether to update or create.
					- Loop through eBay payments to record all the different payments received.
	Date Modified: 	2023-03-14 (Joel Lipman)
					- Resolved fix of incorrect customer (Use Email rather than StaticAlias)
					- Resolved fix of inventory adjustment level (Error:9205:Insufficient Stock)
					- Resolved fix of overpaid amount (Include Shipping Cost in SO)
	Date Modified: 	2023-03-16 (Joel Lipman)
					- Resolves issue of SO / Invoice / Package / Shipment / Payment not updating.
					- Revamp and debug line by line
					- Switched email identifier back to StaticAlias rather than member Email
					- Possibly still an issue with delayed payment (check this is not holding status)
	Date Modified: 	2023-03-17 (Joel Lipman)
					- Enhanced debug messages as function not auto-triggering on receipt of Order
					- Resolved: SO ID was not being returned when created.
					- Forced re-upload of photo for item on any modification
	Date Modified: 	2023-03-20 (Joel Lipman)
					- Check if payment has already been recorded before creating a new one.
					- Fixed reference_number being included on customer payment.
	Date Modified: 	2023-03-21 (Joel Lipman)
					- Added if conditions to not adjust stock level if item has enough quantity then that purchased
					- Adds tracking number and carrier to shipment record on either creation or modification.
					- Only creates shipment order if shipped time is specified.
					- Only marks shipment as delivered if DHL API checked and status returned is delivered
	Date Modified: 	2023-03-22 (Joel Lipman)
					- On creation of Sales Order, create map of line items and created SO (fix to invoice only generated on edit)
	Date Modified: 	2023-03-29 (Joel Lipman)
					- If an eBay order comes through as cancelled, then void the sales order, void the invoice, delete the package and shipment.
					- Added chart of accounts lookup to set purchase account for an item: now ticks purchase information and track inventory.
					- Issue on create that shipment is not created.

    More Info:
    - API Explorer Test Tool: https://developer.ebay.com/DevZone/build-test/test-tool/default.aspx?index=0&env=production&api=trading
    - GetMyeBaySelling Documentation: https://developer.ebay.com/devzone/xml/docs/reference/ebay/getmyebayselling.html
	- GetOrderTransactions Documentation: https://developer.ebay.com/devzone/xml/docs/reference/ebay/GetOrderTransactions.html
	- DHL API Developer Portal: https://developer.dhl.com/documentation
	- DHL API Developer API Reference Shipment Tracking: https://developer.dhl.com/api-reference/shipment-tracking#reference-docs-section
*/
//
// enter your own organization ID here for ZohoBooks and ZohoInventory
v_BooksOrgID = 12345678901;
//
// evaluate
v_currentDate = zoho.currentdate.toString("yyyy-MM-dd");
v_OrderDate = zoho.currentdate.toString("yyyy-MM-dd");
v_Page = 1;
v_PerPage = 10;
m_Output = Map();
l_Costs = {11,13,15,17,20};
b_DebugMode = true;
l_DebugMessages = List();
l_DebugMessages.add("eBay Order ID: " + p_eBayOrderRef);
l_DebugMessages.add("eBay Order Date: " + v_OrderDate);
info p_eBayOrderRef;
//
// get books tax rates
m_Taxes = Map();
r_Taxes = invokeurl
[
	url :"https://books.zoho.eu/api/v3/settings/taxes?organization_id=" + v_BooksOrgID
	type :GET
	connection:"joel_books"
];
if(!isnull(r_Taxes.get("taxes")))
{
	for each  r_Tax in r_Taxes.get("taxes")
	{
		m_Taxes.put(r_Tax.get("tax_percentage").toString(),r_Tax.get("tax_id"));
	}
}
l_DebugMessages.add("ZB Taxes: " + m_Taxes);
//
// set chart of accounts to use
v_Endpoint = "https://books.zoho.eu/api/v3/chartofaccounts?organization_id=" + v_BooksOrgID;
r_ChartOfAccounts = invokeurl
[
	url :v_Endpoint
	type :GET
	connection:"joel_books"
];
m_Accounts = Map();
if(!isnull(r_ChartOfAccounts.get("chartofaccounts")))
{
	for each  r_Account in r_ChartOfAccounts.get("chartofaccounts")
	{
		m_Accounts.put(r_Account.get("account_name"),r_Account.get("account_id"));
	}
}
//
// get access token 
v_AccessToken = standalone.fn_eBay_GetAccessToken();
l_DebugMessages.add("AccessToken:<br /><br />" + v_AccessToken);
//
v_TradingAPIVersion = 967;
v_Endpoint = "https://api.ebay.com/ws/api.dll";
//
// build header
m_Headers = Map();
m_Headers.put("X-EBAY-API-SITEID",3);
m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion);
v_ApiCall = "GetOrderTransactions";
m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall);
m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken);
//
// build params
m_Params = Map();
m_Params.put("WarningLevel","High");
m_Params.put("ErrorLanguage","en_GB");
m_Params.put("DetailLevel","ReturnAll");
//
// send an order array
l_OrderIDs = List();
m_OrderID = Map();
m_OrderID.put("OrderID",p_eBayOrderRef);
l_OrderIDs.add(m_OrderID);
m_Params.put("OrderIDArray",l_OrderIDs);
//
// convert to xml and replace root nodes
x_Params = m_Params.toXML();
x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">");
x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>");
//
// send the request XML as a string
r_ResponseXMLOrder = invokeurl
[
	url :v_Endpoint
	type :POST
	parameters:x_Params
	headers:m_Headers
];
info "-----------------------";
info "EBAY ORDER XML:";
if(b_DebugMode)
{
	l_DebugMessages.add("EBAY ORDER XML:<br /><br />" + r_ResponseXMLOrder.replaceAll(">",">").replaceAll("<","<"));
}
//
// ********************************************************
// loop through order array
v_ArrayNode = "OrderArray";
x_OrderArray = r_ResponseXMLOrder.subString(r_ResponseXMLOrder.indexOf("<" + v_ArrayNode),r_ResponseXMLOrder.lastIndexOf("</" + v_ArrayNode) + v_ArrayNode.length() + 3);
l_Orders = x_OrderArray.executeXPath("//Order").toXmlList();
for each  x_Order in l_Orders
{
	// initialize
	m_BooksContact = Map();
	m_BooksShipping = Map();
	m_BooksBilling = Map();
	l_InventoryLineItems = List();
	//
	v_OrderID = x_Order.executeXPath("//Order/OrderID/text()");
	v_OrderStatus = x_Order.executeXPath("//Order/OrderStatus/text()");
	b_OrderCancelled = if(v_OrderStatus.equalsIgnoreCase("Cancelled") || v_OrderStatus.equalsIgnoreCase("Inactive"),true,false);
	v_OrderAmountPaid = x_Order.executeXPath("//Order/AmountPaid/text()");
	v_OrderCurrency = x_Order.executeXPath("//Order/AmountPaid/@currencyID").executeXPath("/currencyID/text()");
	v_OrderSellerID = x_Order.executeXPath("//Order/SellerUserID/text()");
	//
	v_OrderTaxPercent = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxPercent/text()");
	v_OrderShippingIncludesTax = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/ShippingIncludedInTax/text()");
	v_OrderTaxCurrency = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxAmount/@currencyID").executeXPath("/currencyID/text()");
	v_OrderTaxAmount = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxAmount/text()");
	//
	v_BuyerShippingName = x_Order.executeXPath("//Order/ShippingAddress/Name/text()");
	v_BuyerShippingStreet1 = x_Order.executeXPath("//Order/ShippingAddress/Street1/text()");
	v_BuyerShippingStreet1 = if(v_BuyerShippingStreet1.contains(" ebay"),v_BuyerShippingStreet1.getPrefix(" ebay"),v_BuyerShippingStreet1);
	v_BuyerShippingStreet2 = x_Order.executeXPath("//Order/ShippingAddress/Street2/text()");
	v_BuyerShippingStreet2 = if(v_BuyerShippingStreet2.contains("ebay"),v_BuyerShippingStreet2.getPrefix("ebay"),v_BuyerShippingStreet2);
	v_BuyerShippingCity = x_Order.executeXPath("//Order/ShippingAddress/CityName/text()");
	v_BuyerShippingPostcode = x_Order.executeXPath("//Order/ShippingAddress/PostalCode/text()");
	v_BuyerShippingCounty = x_Order.executeXPath("//Order/ShippingAddress/StateOrProvince/text()");
	v_BuyerShippingCountryName = x_Order.executeXPath("//Order/ShippingAddress/CountryName/text()");
	v_BuyerShippingPhone = x_Order.executeXPath("//Order/ShippingAddress/Phone/text()");
	//
	// for Books: Billing Address Map
	m_BooksBilling.put("attention",v_BuyerShippingName);
	m_BooksBilling.put("address",v_BuyerShippingStreet1);
	m_BooksBilling.put("street2",v_BuyerShippingStreet2);
	m_BooksBilling.put("city",v_BuyerShippingCity);
	m_BooksBilling.put("state",v_BuyerShippingCounty);
	m_BooksBilling.put("zip",v_BuyerShippingPostcode);
	m_BooksBilling.put("country",v_BuyerShippingCountryName);
	// for Books: Shipping Address Map
	m_BooksShipping.put("attention",v_BuyerShippingName);
	m_BooksShipping.put("address",v_BuyerShippingStreet1);
	m_BooksShipping.put("street2",v_BuyerShippingStreet2);
	m_BooksShipping.put("city",v_BuyerShippingCity);
	m_BooksShipping.put("state",v_BuyerShippingCounty);
	m_BooksShipping.put("zip",v_BuyerShippingPostcode);
	m_BooksShipping.put("country",v_BuyerShippingCountryName);
	//
	v_ShippingServiceSelected = x_Order.executeXPath("//Order/ShippingServiceSelected/ShippingService/text()");
	v_ShippingServiceCost = x_Order.executeXPath("//Order/ShippingServiceSelected/ShippingServiceCost/text()");
	v_OrderSubtotal = x_Order.executeXPath("//Order/Subtotal/text()");
	v_OrderTotal = x_Order.executeXPath("//Order/Total/text()");
	//
	v_Order_DateCreated = x_Order.executeXPath("//Order/CreatedTime/text()");
	v_Order_DateCreated = if(!isnull(v_Order_DateCreated),v_Order_DateCreated.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime);
	l_DebugMessages.add("Date Order Created: " + v_Order_DateCreated);
	v_Order_DateShipped = x_Order.executeXPath("//Order/ShippedTime/text()");
	v_Order_DateShipped = if(!isnull(v_Order_DateShipped),v_Order_DateShipped.getPrefix(".").replaceFirst("T"," ",true).toTime(),null);
	l_DebugMessages.add("Date Order Shipped: " + v_Order_DateShipped);
	//
	// loop through transaction array (possibly multiple line items)
	l_Transactions = x_Order.executeXPath("//Order/TransactionArray/Transaction").toXmlList();
	for each  x_Transaction in l_Transactions
	{
		// initialize
		m_BooksItem = Map();
		m_BooksLineItem = Map();
		//
		// get buyer info
		v_BuyerUserName = x_Transaction.executeXPath("//Transaction/Buyer/UserID/text()");
		v_BuyerUserFName = x_Transaction.executeXPath("//Transaction/Buyer/UserFirstName/text()");
		v_BuyerUserSName = x_Transaction.executeXPath("//Transaction/Buyer/UserLastName/text()");
		v_BuyerUserEmail = x_Transaction.executeXPath("//Transaction/Buyer/Email/text()");
		l_DebugMessages.add("BuyerEmail: " + v_BuyerUserEmail);
		info "BuyerEmail: " + v_BuyerUserEmail;
		v_BuyerUserStaticEmail = x_Transaction.executeXPath("//Transaction/Buyer/StaticAlias/text()");
		l_DebugMessages.add("BuyerStaticEmail: " + v_BuyerUserStaticEmail);
		info "BuyerStaticEmail: " + v_BuyerUserStaticEmail;
		v_BuyerIDVerified = x_Transaction.executeXPath("//Transaction/Buyer/IDVerified/text()");
		v_BuyerSite = x_Transaction.executeXPath("//Transaction/Buyer/Site/text()");
		//
		// Update Zoho Contact Name
		m_BooksContact.put("contact_name",v_BuyerUserFName + " " + v_BuyerUserSName);
		m_BooksContact.put("billing_address",m_BooksBilling);
		m_BooksContact.put("shipping_address",m_BooksShipping);
		m_BooksContact.put("first_name",v_BuyerUserFName);
		m_BooksContact.put("last_name",v_BuyerUserSName);
		m_BooksContact.put("phone",v_BuyerShippingPhone);
		//
		v_ItemID = x_Transaction.executeXPath("//Transaction/Item/ItemID/text()").replaceAll("[^0-9]","");
		l_DebugMessages.add("Item Number: " + v_ItemID);
		info "Item Number: " + v_ItemID;
		v_ItemListingType = x_Transaction.executeXPath("//Transaction/Item/ListingType/text()");
		v_ItemSKU = x_Transaction.executeXPath("//Transaction/Item/SKU/text()");
		l_DebugMessages.add("Item SKU: " + v_ItemSKU);
		info "Item SKU: " + v_ItemSKU;
		v_ItemConditionID = x_Transaction.executeXPath("//Transaction/Item/ConditionID/text()");
		v_ItemConditionName = x_Transaction.executeXPath("//Transaction/Item/ConditionDisplayName/text()");
		v_ItemPrice = x_Transaction.executeXPath("//Transaction/Item/SellingStatus/CurrentPrice/text()");
		//
		v_TransactionID = x_Transaction.executeXPath("//Transaction/TransactionID/text()");
		v_TransactionCurrency = x_Transaction.executeXPath("//Transaction/AmountPaid/@currencyID").executeXPath("/currencyID/text()");
		v_TransactionAmountPaid = x_Transaction.executeXPath("//Transaction/AmountPaid/text()");
		v_TransactionQtyPurchased = x_Transaction.executeXPath("//Transaction/QuantityPurchased/text()");
		v_TransactionCheckoutStatus = x_Transaction.executeXPath("//Transaction/Status/CheckoutStatus/text()");
		v_TransactionCompleteStatus = x_Transaction.executeXPath("//Transaction/Status/CompleteStatus/text()");
		v_TransactionBestOffer = x_Transaction.executeXPath("//Transaction/BestOfferSale/text()");
		v_TransactionOrderRef = x_Transaction.executeXPath("//Transaction/ExtendedOrderID/text()");
		v_TransactionOrderLineItemRef = x_Transaction.executeXPath("//Transaction/OrderLineItemID/text()");
		//
		v_Transaction_DateCreated = x_Transaction.executeXPath("//Transaction/CreatedDate/text()");
		v_Transaction_DateCreated = if(!isnull(v_Transaction_DateCreated),v_Transaction_DateCreated.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime);
		v_Transaction_DateModified = x_Transaction.executeXPath("//Transaction/Status/LastTimeModified/text()");
		v_Transaction_DateModified = if(!isnull(v_Transaction_DateModified),v_Transaction_DateModified.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime);
		v_Transaction_DatePaid = x_Transaction.executeXPath("//Transaction/PaidTime/text()");
		v_Transaction_DatePaid = if(!isnull(v_Transaction_DatePaid),v_Transaction_DatePaid.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime);
		v_Transaction_DateShipped = x_Transaction.executeXPath("//Transaction/ShippedTime/text()");
		v_Transaction_DateShipped = if(!isnull(v_Transaction_DateShipped),v_Transaction_DateShipped.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime);
		//
		v_TransactionPaymentMethod = x_Transaction.executeXPath("//Transaction/Status/PaymentMethodUsed/text()");
		v_TransactionPaymentStatus = x_Transaction.executeXPath("//Transaction/Status/eBayPaymentStatus/text()");
		v_TransactionPaymentHoldStatus = x_Transaction.executeXPath("//Transaction/Status/PaymentHoldStatus/text()");
		//
		v_ShippingCarrier = x_Transaction.executeXPath("//Transaction/ShippingDetails/ShipmentTrackingDetails/ShippingCarrierUsed/text()");
		v_ShippingTrackingNumber = x_Transaction.executeXPath("//Transaction/ShippingDetails/ShipmentTrackingDetails/ShipmentTrackingNumber/text()");
		//
		// Other Zoho Contact updates
		m_BooksContact.put("currency_code",v_TransactionCurrency);
		//
		// check for the item in books and add to line items for other records
		v_BooksItemID = 0;
		v_BooksCurrentStock = 0;
		m_Criteria = Map();
		m_Criteria.put("sku",v_ItemID);
		b_Composite = false;
		//
		// search items
		r_SearchItems = zoho.inventory.getRecords("items",v_BooksOrgID,m_Criteria,"joel_inventory");
		for each  r_FoundItem in r_SearchItems.get("items")
		{
			if(!isnull(r_FoundItem.get("item_id")) && r_FoundItem.get("sku") == v_ItemID)
			{
				v_BooksItemID = r_FoundItem.get("item_id").toLong();
				v_ItemTitle = r_FoundItem.get("name");
				v_ListingDescription = r_FoundItem.get("description");
				v_BooksCurrentStock = ifnull(r_FoundItem.get("actual_available_stock"),0).toLong();
			}
		}
		// search composite items
		if(v_BooksItemID == 0)
		{
			r_SearchCompositeItems = zoho.inventory.getRecords("compositeitems",v_BooksOrgID,m_Criteria,"joel_inventory");
			for each  r_CompositeItem in r_SearchCompositeItems.get("compositeitems")
			{
				if(!isnull(r_CompositeItem.get("item_id")) && r_CompositeItem.get("sku") == v_ItemID)
				{
					v_BooksItemID = r_CompositeItem.get("item_id").toLong();
					v_ItemTitle = r_CompositeItem.get("name");
					v_ListingDescription = r_CompositeItem.get("description");
					v_BooksCurrentStock = ifnull(r_CompositeItem.get("actual_available_stock"),0).toLong();
					b_Composite = true;
				}
			}
		}
		//
		l_DebugMessages.add("ZB Item Search: " + v_BooksItemID);
		info "ZB Item Search: " + v_BooksItemID;
		// ***********************************************
		// query eBay for the item listing
		//
		// build header
		m_Headers = Map();
		m_Headers.put("X-EBAY-API-SITEID",3);
		m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion);
		v_ApiCall = "GetItem";
		m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall);
		m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken);
		//
		// build params
		m_Params = Map();
		m_Params.put("WarningLevel","High");
		m_Params.put("ErrorLanguage","en_GB");
		m_Params.put("DetailLevel","ReturnAll");
		m_Params.put("IncludeItemSpecifics",true);
		//
		// include fixed price items
		m_ActiveList = Map();
		m_ActiveList.put("Include","true");
		m_ActiveList.put("ListingType","FixedPriceItem");
		m_Pagination = Map();
		m_Pagination.put("PageNumber",v_Page);
		m_Pagination.put("EntriesPerPage",v_PerPage);
		m_ActiveList.put("Pagination",m_Pagination);
		m_Params.put("ItemID",v_ItemID);
		//
		// convert to xml and replace root nodes
		x_Params = m_Params.toXML();
		x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">");
		x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>");
		//
		// send the request XML as a string
		r_ResponseXMLItem = invokeurl
		[
			url :v_Endpoint
			type :POST
			parameters:x_Params
			headers:m_Headers
		];
		info "-----------------------";
		info "EBAY ITEM XML:";
		if(b_DebugMode)
		{
			l_DebugMessages.add("EBAY ITEM XML:<br /><br />" + r_ResponseXMLItem.replaceAll(">",">").replaceAll("<","<"));
		}
		v_Node = "Item";
		x_Item = r_ResponseXMLItem.subString(r_ResponseXMLItem.indexOf("<" + v_Node),r_ResponseXMLItem.lastIndexOf("</" + v_Node) + v_Node.length() + 3);
		//
		// parse out details of item
		v_ItemTitle = x_Item.executeXPath("//Item/Title/text()");
		l_DebugMessages.add("Item Title: " + v_ItemTitle);
		info "Item Title: " + v_ItemTitle;
		v_ItemCustomSKU = x_Item.executeXPath("//Item/SKU/text()");
		v_ItemBrand = x_Item.executeXPath("//Item/ProductListingDetails/BrandMPN/Brand/text()");
		v_ItemCategoryID = x_Item.executeXPath("//Item/PrimaryCategory/CategoryID/text()");
		v_ItemCategoryName = x_Item.executeXPath("//Item/PrimaryCategory/CategoryName/text()");
		v_ItemLocation = x_Item.executeXPath("//Item/Location/text()");
		v_ListingQuantity = x_Item.executeXPath("//Item/Quantity/text()");
		v_ListingConditionName = x_Item.executeXPath("//Item/ConditionDisplayName/text()");
		v_ListingConditionDesc = x_Item.executeXPath("//Item/ConditionDescription/text()");
		//v_ListingDescription = x_Item.executeXPath("//Item/Description/text()").replaceAll("<","<").replaceAll(">",">");
		v_ListingDescription = x_Item.executeXPath("//Item/Description/text()");
		l_ListingPictures = x_Item.executeXPath("//Item/PictureDetails/PictureURL").toXmlList();
		l_ItemSpecifics = x_Item.executeXPath("//Item/ItemSpecifics/NameValueList").toXmlList();
		v_ItemCondition = x_Item.executeXPath("//Item/ConditionID/text()");
		b_IsNew = if(v_ItemCondition == "1000",true,false);
		//
		m_BooksItem.put("name",v_ItemTitle);
		m_BooksItem.put("sku",v_ItemID);
		m_BooksItem.put("rate",v_ItemPrice.toDecimal());
		// cost?
		v_NumberOfStars = v_ItemTitle.getOccurenceCount("*") - 1;
		v_NumberOfStars = if(v_NumberOfStars < 1,0,v_NumberOfStars);
		v_NumberOfStars = if(v_NumberOfStars > 4,4,v_NumberOfStars);
		v_PurchaseRate = l_Costs.get(v_NumberOfStars);
		m_BooksItem.put("purchase_rate",v_PurchaseRate.toDecimal());
		//
		v_UnitType = "";
		if(v_ItemTitle.containsIgnoreCase("wheel") && v_ItemTitle.containsIgnoreCase("tyre"))
		{
			v_UnitType = "pcs";
		}
		else if(v_ItemTitle.containsIgnoreCase("wheel"))
		{
			v_UnitType = "Wheel";
		}
		else if(v_ItemTitle.containsIgnoreCase("tyre"))
		{
			v_UnitType = "Tyre";
		}
		m_BooksItem.put("unit",v_UnitType);
		//
		l_CustomFields = list();
		m_CustomField = Map();
		m_CustomField.put("api_name","cf_ebay_sku");
		m_CustomField.put("value",v_ItemCustomSKU);
		l_CustomFields.add(m_CustomField);
		//
		// here are the item specifics, simply specify the Item Specific label and then the Zoho Inventory API name of the field to map to.
		m_NameListMappings = Map();
		m_NameListMappings.put("Offset (ET)","cf_offset");
		m_NameListMappings.put("Centre Bore","cf_centre_bore");
		m_NameListMappings.put("Custom Bundle","cf_custom_bundle");
		m_NameListMappings.put("Manufacturer Part Number","cf_manufacturer_part_number");
		m_NameListMappings.put("Stud Diameter","cf_stud_diameter");
		m_NameListMappings.put("Wheel Material","cf_wheel_material");
		m_NameListMappings.put("Wheel Construction","cf_wheel_construction");
		m_NameListMappings.put("Reference OE/OEM Number","cf_reference_oe_oem_number");
		m_NameListMappings.put("Modified Item","cf_modified_item");
		m_NameListMappings.put("Offset","cf_offset");
		m_NameListMappings.put("Number of Studs","cf_number_of_studs");
		m_NameListMappings.put("Type","cf_type");
		m_NameListMappings.put("Wheel Diameter","cf_wheel_diameter");
		m_NameListMappings.put("Unit Quantity","cf_unit_quantity");
		m_NameListMappings.put("Finish","cf_finish");
		m_NameListMappings.put("Wheel Width","cf_wheel_width");
		//
		l_CustomFields = list();
		for each  x_ItemSpecific in l_ItemSpecifics
		{
			v_SpecificName = x_ItemSpecific.executeXPath("//NameValueList/Name/text()");
			v_SpecificValue = x_ItemSpecific.executeXPath("//NameValueList/Value/text()");
			//
			if(!isNull(m_NameListMappings.get(v_SpecificName)))
			{
				m_CustomField = Map();
				m_CustomField.put("api_name",m_NameListMappings.get(v_SpecificName));
				m_CustomField.put("value",v_SpecificValue);
				l_CustomFields.add(m_CustomField);
			}
			//
			if(v_SpecificName.containsIgnoreCase("Unit Quantity"))
			{
				if(v_SpecificValue.toLong() > 1)
				{
					b_Composite = true;
				}
			}
		}
		//
		m_BooksItem.put("custom_fields",l_CustomFields);
		//
		// do 1 picture as ZohoInventory only supports 1 image upload (at time of print)
		v_PictureURL = "";
		for each  x_Picture in l_ListingPictures
		{
			v_PictureURL = x_Picture.executeXPath("//PictureURL/text()");
			break;
		}
		//
		// update or create with purchase information
		m_BooksItem.put("inventory_account_id",m_Accounts.get("Inventory Asset"));
		m_BooksItem.put("purchase_rate",v_ItemPrice.toDecimal());
		if(v_ItemTitle.containsIgnoreCase("alloy"))
		{
			m_BooksItem.put("purchase_account_id",m_Accounts.get("Cost of Goods Sold"));
			m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong());
		}
		else
		{
			if(v_ListingConditionName.containsIgnoreCase("new"))
			{
				m_BooksItem.put("purchase_account_id",m_Accounts.get("New Tyres Purchase"));
				m_BooksItem.put("initial_stock",999);
			}
			else
			{
				m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong());
				m_BooksItem.put("purchase_account_id",m_Accounts.get("Part Worn Tyres Purchase"));
			}
		}
		m_BooksItem.put("initial_stock_rate",v_ItemPrice.toDecimal());
		//
		// send request to create item
		if(v_BooksItemID == 0)
		{
			// 			m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong());
			// 			m_BooksItem.put("initial_stock_rate",v_ItemPrice.toDecimal());
			r_CreateItem = zoho.inventory.createRecord("items",v_BooksOrgID,m_BooksItem,"joel_inventory");
			info "ITEM CREATE RESPONSE FOR " + v_ItemID;
			info r_CreateItem.get("message");
			l_DebugMessages.add("ITEM CREATE RESPONSE FOR " + v_ItemID + ": " + r_CreateItem.get("message"));
			// 
			// retrieve the generated item id for generating other records
			if(!isnull(r_CreateItem.get("item")))
			{
				if(!isnull(r_CreateItem.get("item").get("item_id")))
				{
					v_BooksItemID = r_CreateItem.get("item").get("item_id").toLong();
				}
			}
		}
		else
		{
			//
			// ENSURE THAT STOCK LEVEL IS RESTORED TO PURCHASE THIS ITEM
			if(v_BooksCurrentStock < 1)
			{
				// now build stock level adjustment on ZohoInventory for this item
				m_UpdateStock = Map();
				m_UpdateStock.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"));
				m_UpdateStock.put("reason","eBay Order");
				m_UpdateStock.put("description","An eBay Order has come in using this item.  Re-adjusting inventory level on-the-fly to include it for use in transactions.");
				m_UpdateStock.put("adjustment_type","quantity");
				//
				// need to include line items of adjustment (just this one item)
				l_LineItems = List();
				m_LineItem = Map();
				m_LineItem.put("item_id",v_BooksItemID);
				m_LineItem.put("quantity_adjusted",abs(v_TransactionQtyPurchased));
				l_LineItems.add(m_LineItem);
				m_UpdateStock.put("line_items",l_LineItems);
				//
				// create this stock level adjustment (need to use Invoke as shortcode wouldn't work)
				v_Endpoint = "https://www.zohoapis.eu/inventory/v1/inventoryadjustments?organization_id=" + v_BooksOrgID;
				r_CreateAdjustment = invokeurl
				[
					url :v_Endpoint
					type :POST
					parameters:m_UpdateStock.toString()
					connection:"joel_inventory"
				];
				info "ADJUSTED INVENTORY LEVEL";
				info r_CreateAdjustment.get("message");
				l_DebugMessages.add("ADJUSTED INVENTORY LEVEL: " + r_CreateAdjustment.get("message"));
			}
			//
			// update the item to block this function from running until modified by some other method
			m_UpdateItem = Map();
			l_CustomFields = List();
			m_CustomField = Map();
			m_CustomField.put("api_name","cf_updated_by");
			m_CustomField.put("value","eBay Order");
			l_CustomFields.add(m_CustomField);
			m_UpdateItem.put("custom_fields",l_CustomFields);
			m_UpdateItem.put("status","active");
			if(v_BooksCurrentStock < 1)
			{
				m_UpdateItem.put("initial_stock",abs(v_TransactionQtyPurchased));
				m_UpdateItem.put("initial_stock_rate",v_ItemPrice);
			}
			//info m_UpdateItem;
			v_Endpoint = "https://www.zohoapis.eu/inventory/v1/items/" + v_BooksItemID + "?organization_id=" + v_BooksOrgID;
			r_UpdateItem = invokeurl
			[
				url :v_Endpoint
				type :PUT
				parameters:m_UpdateItem.toString()
				connection:"joel_inventory"
			];
			info "UPDATED ITEM:";
			info r_UpdateItem.get("message");
			l_DebugMessages.add("UPDATED ITEM: " + r_UpdateItem.get("message"));
		}
		//
		// let's upload the picture for this (only 1 supported in inventory at this time)
		if(!isBlank(v_PictureURL))
		{
			r_DownloadedPhoto = invokeurl
			[
				url :v_PictureURL
				type :GET
			];
			//
			// set the data type
			r_DownloadedPhoto.setParamName("image");
			//
			// build up request to Zoho
			m_Params = Map();
			m_Params.put("image",r_DownloadedPhoto);
			//
			// generate endpoint
			v_Url = "https://inventory.zoho.eu/api/v1/items/" + v_BooksItemID + "/image";
			//
			// updload the photo
			r_UploadPhoto = invokeurl
			[
				url :v_Url
				type :POST
				files:r_DownloadedPhoto
				connection:"joel_inventory"
			];
			// output response to console
			info "PHOTO UPLOAD:";
			info r_UploadPhoto.get("message");
			l_DebugMessages.add("PHOTO UPLOAD OF " + v_PictureURL + ": " + r_UploadPhoto.get("message"));
		}
		//
		// ensure the item is activated
		if(v_BooksItemID != 0)
		{
			v_Endpoint = "https://www.zohoapis.eu/inventory/v1/items/" + v_BooksItemID + "/active?organization_id=" + v_BooksOrgID;
			r_ActivateItem = invokeurl
			[
				url :v_Endpoint
				type :POST
				connection:"joel_inventory"
			];
			info "ACTIVATED ITEM:";
			info r_ActivateItem.get("message");
			l_DebugMessages.add("ACTIVATED ITEM: " + r_ActivateItem.get("message"));
		}
		//
		// add to line items for sales orders or invoices
		info "BooksItemID: " + v_BooksItemID;
		m_BooksLineItem.put("item_id",v_BooksItemID);
		m_BooksLineItem.put("name",v_ItemTitle);
		v_ListingDescriptionCleaned = ifnull(v_ListingDescription,"").replaceAll("<br />","\n",true).replaceAll("<br>","\n",true).replaceAll("<(.|\n)*?>","");
		m_BooksLineItem.put("description","");
		v_OrderTaxFactor = v_OrderTaxPercent.toDecimal() / 100;
		v_OrderTaxFactor = v_OrderTaxFactor + 1;
		v_ItemPriceExclVAT = v_ItemPrice.toDecimal() / v_OrderTaxFactor;
		v_ItemPriceExclVATRounded = floor(v_ItemPriceExclVAT * 100) / 100;
		m_BooksLineItem.put("rate",v_ItemPrice);
		m_BooksLineItem.put("quantity",v_TransactionQtyPurchased.toLong());
		v_UnitType = "";
		if(v_ItemTitle.containsIgnoreCase("wheel") && v_ItemTitle.containsIgnoreCase("tyre"))
		{
			v_UnitType = "pcs";
		}
		else if(v_ItemTitle.containsIgnoreCase("wheel"))
		{
			v_UnitType = "Wheel";
		}
		else if(v_ItemTitle.containsIgnoreCase("tyre"))
		{
			v_UnitType = "Tyre";
		}
		m_BooksLineItem.put("unit",v_UnitType);
		m_BooksLineItem.put("tax_id",m_Taxes.get(v_OrderTaxPercent.toLong().toString()));
		m_BooksLineItem.put("tax_percentage",v_OrderTaxPercent.toLong());
		//m_BooksLineItem.put("item_total",v_ItemPriceExclVAT.toDecimal());
		l_InventoryLineItems.add(m_BooksLineItem);
		l_DebugMessages.add("BooksItemID: " + m_BooksLineItem);
	}
	//
	// now loop through payments for this order
	l_Payments = x_Order.executeXPath("//Order/MonetaryDetails/Payments").toXmlList();
	v_OrderPayAmountTotal = 0.0;
	l_eBayPaymentRefs = List();
	for each  x_Payment in l_Payments
	{
		m_eBayPaymentRef = Map();
		v_PaymentRef = x_Payment.executeXPath("//Payment/ReferenceID/text()");
		m_eBayPaymentRef.put("BooksItemID",v_BooksItemID);
		m_eBayPaymentRef.put("ReferenceID",v_PaymentRef);
		v_ThisPaymentStatus = x_Payment.executeXPath("//Payment/PaymentStatus/text()");
		m_eBayPaymentRef.put("Status",v_ThisPaymentStatus);
		v_ThisPaymentTime = x_Payment.executeXPath("//Payment/PaymentTime/text()");
		v_ThisPaymentTime = if(!isnull(v_ThisPaymentTime),v_ThisPaymentTime.getPrefix(".").replaceFirst("T"," ",true),zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss"));
		m_eBayPaymentRef.put("DateTime",v_ThisPaymentTime);
		v_ThisPayee = x_Payment.executeXPath("//Payment/Payee/text()");
		m_eBayPaymentRef.put("Payee",v_ThisPayee);
		v_ThisPaymentAmount = 0.0;
		if(v_ThisPaymentStatus == "Succeeded" && v_ThisPayee == v_OrderSellerID)
		{
			v_ThisPaymentAmount = x_Payment.executeXPath("//Payment/PaymentAmount/text()");
			v_OrderPayAmountTotal = v_OrderPayAmountTotal + v_ThisPaymentAmount.toDecimal();
		}
		m_eBayPaymentRef.put("Amount",v_ThisPaymentAmount);
		l_eBayPaymentRefs.add(m_eBayPaymentRef);
	}
	info "Payment(s): ";
	if(b_DebugMode)
	{
		info l_eBayPaymentRefs;
	}
	l_DebugMessages.add("PAYMENT(S): " + l_eBayPaymentRefs);
}
//
// search for this customer in Zoho Inventory
info "-----------------------";
v_BooksCustomerID = 0;
m_SearchCriteria = Map();
m_SearchCriteria.put("email",v_BuyerUserStaticEmail);
r_SearchContacts = zoho.inventory.getRecords("contacts",v_BooksOrgID,m_SearchCriteria,"joel_inventory");
for each  r_Contact in r_SearchContacts.get("contacts")
{
	if(!isnull(r_Contact.get("contact_id")))
	{
		if(!isNull(v_BuyerUserStaticEmail) && v_BuyerUserStaticEmail == r_Contact.get("email"))
		{
			v_BooksCustomerID = r_Contact.get("contact_id").toLong();
		}
	}
}
info "ZB Contact Search: " + v_BooksCustomerID;
l_DebugMessages.add("ZB Contact Search: " + v_BooksCustomerID);
if(v_BooksCustomerID == 0)
{
	// create a contact person
	m_ContactPerson = Map();
	m_ContactPerson.put("email",v_BuyerUserStaticEmail);
	m_ContactPerson.put("first_name",v_BuyerUserFName);
	m_ContactPerson.put("last_name",v_BuyerUserSName);
	m_ContactPerson.put("phone",v_BuyerShippingPhone);
	m_ContactPerson.put("is_primary_contact",true);
	l_CreateContactPerson = List();
	l_CreateContactPerson.add(m_ContactPerson);
	m_BooksContact.put("contact_persons",l_CreateContactPerson);
	//
	// other fields on creation
	m_BooksContact.put("contact_type","customer");
	m_BooksContact.put("customer_sub_type","individual");
	m_BooksContact.put("payment_terms",0);
	m_BooksContact.put("status","active");
	//
	// send request to create contact
	r_CreateContact = zoho.inventory.createRecord("contacts",v_BooksOrgID,m_BooksContact,"joel_inventory");
	info "CONTACT CREATE RESPONSE: ";
	info r_CreateContact.get("message");
	l_DebugMessages.add("CONTACT CREATE RESPONSE: " + r_CreateContact);
	// 
	// retrieve the generated contact id for generating other records
	if(!isnull(r_CreateContact.get("contact")))
	{
		if(!isnull(r_CreateContact.get("contact").get("contact_id")))
		{
			v_BooksCustomerID = r_CreateContact.get("contact").get("contact_id").toLong();
		}
	}
}
else
{
	//
	// send request to modify contact
	r_UpdateContact = zoho.inventory.updateRecord("contacts",v_BooksOrgID,v_BooksCustomerID,m_BooksContact,"joel_inventory");
	info "CONTACT UPDATE RESPONSE:";
	info r_UpdateContact.get("message");
	l_DebugMessages.add("CONTACT UPDATE RESPONSE: " + r_UpdateContact.get("message"));
}
info "CustomerID: " + v_BooksCustomerID;
l_DebugMessages.add("CustomerID: " + v_BooksCustomerID);
info "-----------------------";
//
// ********************************************************
// now we have contact ID and item ID, let's create Sales Order
if(v_BooksCustomerID != 0 && v_BooksItemID != 0)
{
	m_BooksSalesOrder = Map();
	m_BooksSalesOrder.put("customer_id",v_BooksCustomerID);
	m_BooksSalesOrder.put("date",v_Transaction_DateCreated.toString("yyyy-MM-dd","Europe/London"));
	m_BooksSalesOrder.put("reference_number",p_eBayOrderRef);
	m_BooksSalesOrder.put("line_items",l_InventoryLineItems);
	m_BooksSalesOrder.put("is_inclusive_tax",true);
	m_BooksSalesOrder.put("shipping_charge",ifnull(v_ShippingServiceCost,0.0).toDecimal());
	l_CustomFields = list();
	m_CustomField = Map();
	m_CustomField.put("api_name","cf_source");
	m_CustomField.put("value","eBay");
	l_CustomFields.add(m_CustomField);
	m_CustomField = Map();
	m_CustomField.put("api_name","cf_ebay_order_id");
	m_CustomField.put("value",p_eBayOrderRef);
	l_CustomFields.add(m_CustomField);
	m_BooksSalesOrder.put("custom_fields",l_CustomFields);
	//
	// determine if sales order already exists or to update
	v_BooksSoID = 0;
	m_SearchCriteria = Map();
	m_SearchCriteria.put("reference_number",p_eBayOrderRef);
	r_BooksSearch = zoho.inventory.getRecords("salesorders",v_BooksOrgID,m_SearchCriteria,"joel_inventory");
	for each  r_So in r_BooksSearch.get("salesorders")
	{
		if(r_So.get("reference_number") == p_eBayOrderRef)
		{
			v_BooksSoID = r_So.get("salesorder_id");
		}
	}
	info "ZB SalesOrder Search: " + v_BooksSoID;
	l_DebugMessages.add("ZB SalesOrder Search: " + v_BooksSoID);
	//info m_BooksSalesOrder;
	//
	// if sales order exists then update it
	if(v_BooksSoID != 0)
	{
		r_UpdateSO = zoho.inventory.updateRecord("salesorders",v_BooksOrgID,v_BooksSoID,m_BooksSalesOrder,"joel_inventory");
		info "SALESORDER UPDATE RESPONSE:";
		info r_UpdateSO.get("message");
		l_DebugMessages.add("SALESORDER UPDATE RESPONSE: " + r_UpdateSO.get("message") + "<br /><br />" + m_BooksSalesOrder);
		//
		r_BooksSO = zoho.inventory.getRecordsByID("salesorders",v_BooksOrgID,v_BooksSoID,"joel_inventory");
		m_BooksSO = if(!isnull(r_BooksSO.get("salesorder")),r_BooksSO.get("salesorder").toMap(),{});
		if(!isnull(m_BooksSO.get("salesorder_number")))
		{
			v_BooksSoReference = m_BooksSO.get("salesorder_number");
		}
	}
	else
	{
		r_CreateSO = zoho.inventory.createRecord("salesorders",v_BooksOrgID,m_BooksSalesOrder,"joel_inventory");
		info "SALESORDER CREATE RESPONSE:";
		info r_CreateSO.get("message");
		l_DebugMessages.add("SALESORDER CREATE RESPONSE: " + r_CreateSO.get("message") + "<br /><br />" + m_BooksSalesOrder);
		// 
		// if having created the sales order
		v_BooksSoReference = "";
		if(!isnull(r_CreateSO.get("salesorder")))
		{
			if(!isnull(r_CreateSO.get("salesorder").get("salesorder_id")))
			{
				v_BooksSoID = r_CreateSO.get("salesorder").get("salesorder_id");
				r_BooksSO = zoho.inventory.getRecordsByID("salesorders",v_BooksOrgID,v_BooksSoID,"joel_inventory");
				m_BooksSO = if(!isnull(r_BooksSO.get("salesorder")),r_BooksSO.get("salesorder").toMap(),{});
			}
			if(!isnull(r_CreateSO.get("salesorder").get("salesorder_number")))
			{
				v_BooksSoReference = r_CreateSO.get("salesorder").get("salesorder_number");
			}
		}
	}
	//
	info "SalesOrderID: " + v_BooksSoID;
	info "SalesOrderRef: " + v_BooksSoReference;
	info "-----------------------";
	l_DebugMessages.add("SalesOrderID: " + v_BooksSoID);
	l_DebugMessages.add("SalesOrderRef: " + v_BooksSoReference);
	//
	// in both cases let's confirm the sales order unless cancelled
	if(b_OrderCancelled)
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/salesorders/" + v_BooksSoID + "/status/void?organization_id=" + v_BooksOrgID;
		r_CancelSO = invokeurl
		[
			url :v_Endpoint
			type :POST
			connection:"joel_inventory"
		];
		info "SALESORDER VOID STATUS:";
		info r_CancelSO.get("message");
		l_DebugMessages.add("SALESORDER VOID RESPONSE: " + r_CancelSO.get("message"));
	}
	else
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/salesorders/" + v_BooksSoID + "/status/confirmed?organization_id=" + v_BooksOrgID;
		r_ConfirmSO = invokeurl
		[
			url :v_Endpoint
			type :POST
			connection:"joel_inventory"
		];
		info "SALESORDER CONFIRMED STATUS:";
		info r_ConfirmSO.get("message");
		l_DebugMessages.add("SALESORDER CONFIRMED RESPONSE: " + r_ConfirmSO.get("message"));
	}
	//
	// in both cases let's build up the package slip/delivery note line items
	l_PackageLineItems = List();
	l_ExistingSoLineItems = m_BooksSO.get("line_items");
	for each  r_SoLineItem in l_ExistingSoLineItems
	{
		m_PackageLineItem = Map();
		m_PackageLineItem.put("so_line_item_id",r_SoLineItem.get("line_item_id"));
		m_PackageLineItem.put("quantity",r_SoLineItem.get("quantity"));
		l_PackageLineItems.add(m_PackageLineItem);
	}
	//
	// search to see if invoice already generated
	v_BooksInvoiceID = 0;
	m_SearchCriteria = Map();
	m_SearchCriteria.put("reference_number",v_BooksSoReference);
	r_BooksSearch2 = zoho.inventory.getRecords("invoices",v_BooksOrgID,m_SearchCriteria,"joel_inventory");
	for each  r_Invoice in r_BooksSearch2.get("invoices")
	{
		if(!isNull(v_BooksSoReference) && r_Invoice.get("reference_number") == v_BooksSoReference)
		{
			v_BooksInvoiceID = r_Invoice.get("invoice_id").toLong();
			v_InvoiceID = v_BooksInvoiceID;
			v_InvoiceRef = r_Invoice.get("invoice_number");
		}
	}
	info "ZB Invoice Search: " + v_BooksInvoiceID;
	l_DebugMessages.add("ZB Invoice Search: " + v_BooksInvoiceID);
	//
	// create invoice if not exists
	if(v_BooksInvoiceID == 0 && v_BooksSoID != 0)
	{
		//
		v_InvoiceID = 0;
		v_InvoiceRef = "";
		v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/fromsalesorder?organization_id=" + v_BooksOrgID + "&salesorder_id=" + v_BooksSoID;
		r_CreateInvoice = invokeurl
		[
			url :v_Endpoint
			type :POST
			connection:"joel_inventory"
		];
		info "INVOICE FROM SO RESPONSE: ";
		info r_CreateInvoice.get("message");
		l_DebugMessages.add("INVOICE FROM SO RESPONSE: " + r_CreateInvoice.get("message"));
		//
		if(!isnull(r_CreateInvoice.get("invoice")))
		{
			if(!isnull(r_CreateInvoice.get("invoice").get("invoice_id")))
			{
				v_InvoiceID = r_CreateInvoice.get("invoice").get("invoice_id").toLong();
				v_BooksInvoiceID = v_InvoiceID;
				v_InvoiceRef = r_CreateInvoice.get("invoice").get("invoice_number");
			}
		}
	}
	else
	{
		info "INVOICE REUSED:" + v_InvoiceRef;
		l_DebugMessages.add("INVOICE REUSED:" + v_InvoiceRef);
	}
	//
	// mark invoice as sent
	if(v_BooksInvoiceID != 0)
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/" + v_BooksInvoiceID + "/status/sent?organization_id=" + v_BooksOrgID;
		r_SendInvoice = invokeurl
		[
			url :v_Endpoint
			type :POST
			connection:"joel_inventory"
		];
		info "INVOICE SENT RESPONSE:";
		info r_SendInvoice.get("message");
		l_DebugMessages.add("INVOICE SENT RESPONSE: " + r_SendInvoice.get("message"));
	}
	//
	// cancel invoice if order cancelled
	if(b_OrderCancelled && v_BooksInvoiceID != 0)
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/" + v_BooksInvoiceID + "/status/void?organization_id=" + v_BooksOrgID;
		r_CancelInvoice = invokeurl
		[
			url :v_Endpoint
			type :POST
			connection:"joel_inventory"
		];
		info "INVOICE VOID RESPONSE:";
		info r_CancelInvoice.get("message");
		l_DebugMessages.add("INVOICE VOID RESPONSE: " + r_CancelInvoice.get("message"));
	}
	info "InvoiceID: " + v_BooksInvoiceID;
	info "InvoiceRef: " + v_InvoiceRef;
	info "-----------------------";
	l_DebugMessages.add("InvoiceID: " + v_BooksInvoiceID);
	l_DebugMessages.add("InvoiceRef: " + v_InvoiceRef);
	//
	// search to see if package already generated
	v_BooksPackageID = 0;
	v_BooksShipmentID = 0;
	m_SearchCriteria = Map();
	m_SearchCriteria.put("salesorder_number",v_BooksSoReference);
	r_BooksSearch3 = zoho.inventory.getRecords("packages",v_BooksOrgID,m_SearchCriteria,"joel_inventory");
	for each  r_Package in r_BooksSearch3.get("packages")
	{
		if(r_Package.get("salesorder_number") == v_BooksSoReference)
		{
			v_BooksPackageID = r_Package.get("package_id").toLong();
			v_BooksShipmentID = if(isBlank(r_Package.get("shipment_id")),0,r_Package.get("shipment_id")).toLong();
		}
	}
	info "ZB Package Search: " + v_BooksInvoiceID;
	info "ZB Shipment Search: " + v_BooksShipmentID;
	info "Transaction Hold Status: " + v_TransactionPaymentHoldStatus;
	l_DebugMessages.add("ZB Package Search: " + v_BooksInvoiceID);
	l_DebugMessages.add("ZB Shipment Search: " + v_BooksShipmentID);
	l_DebugMessages.add("Transaction Hold Status: " + v_TransactionPaymentHoldStatus);
	//
	// create package
	v_BooksPackageNumber = "";
	if(v_BooksSoID.toLong() != 0 && v_BooksPackageID.toLong() == 0 && v_TransactionPaymentHoldStatus == "None" && !b_OrderCancelled)
	{
		//
		l_PackageIDs = List();
		m_BooksPackage = Map();
		m_BooksPackage.put("salesorder_id",v_BooksSoID);
		m_BooksPackage.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"));
		m_BooksPackage.put("line_items",l_PackageLineItems);
		r_CreatePackage = zoho.inventory.createRecord("packages",v_BooksOrgID,m_BooksPackage,"joel_inventory");
		info "PACKAGE CREATE RESPONSE:";
		info r_CreatePackage.get("message");
		l_DebugMessages.add("PACKAGE CREATE RESPONSE: " + r_CreatePackage.get("message"));
		//
		if(!isnull(r_CreatePackage.get("package")))
		{
			if(!isnull(r_CreatePackage.get("package").get("package_id")))
			{
				v_BooksPackageID = r_CreatePackage.get("package").get("package_id");
				v_BooksPackageNumber = r_CreatePackage.get("package").get("package_number");
			}
		}
	}
	//
	// delete package if exists and order cancelled
	if(b_OrderCancelled && v_BooksPackageID.toLong() != 0)
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/packages/" + v_BooksPackageID + "?organization_id=" + v_BooksOrgID;
		r_DeletePackage = invokeurl
		[
			url :v_Endpoint
			type :DELETE
			connection:"joel_inventory"
		];
		info "PACKAGE DELETE RESPONSE:";
		info r_DeletePackage.get("message");
		l_DebugMessages.add("PACKAGE DELETE RESPONSE: " + r_DeletePackage.get("message"));
	}
	info "-----------------------";
	//
	// record this payment
	//info l_eBayPaymentRefs;
	for each  r_eBayPaymentRef in l_eBayPaymentRefs
	{
		if(v_InvoiceID != 0 && v_TransactionPaymentHoldStatus == "None" && !b_OrderCancelled)
		{
			//
			// search to see if payment already recorded
			v_BooksPaymentID = 0;
			m_SearchCriteria = Map();
			m_SearchCriteria.put("reference_number",r_eBayPaymentRef.get("ReferenceID"));
			r_BooksSearch4 = zoho.inventory.getRecords("customerpayments",v_BooksOrgID,m_SearchCriteria,"joel_inventory");
			for each  r_Payment in r_BooksSearch4.get("customerpayments")
			{
				if(r_Payment.get("reference_number") == r_eBayPaymentRef.get("ReferenceID"))
				{
					v_BooksPaymentID = r_Payment.get("payment_id").toLong();
				}
			}
			info "ZB Payment Search: " + v_BooksPaymentID;
			l_DebugMessages.add("ZB Payment Search: " + v_BooksPaymentID);
			//
			// if not found, then create one
			if(v_BooksPaymentID == 0)
			{
				m_BooksPayment = Map();
				m_BooksPayment.put("customer_id",v_BooksCustomerID.toString());
				m_BooksPayment.put("payment_mode",v_TransactionPaymentMethod);
				m_BooksPayment.put("amount",r_eBayPaymentRef.get("Amount"));
				m_BooksPayment.put("date",r_eBayPaymentRef.get("DateTime").toTime().toString("yyyy-MM-dd","Europe/London"));
				m_BooksPayment.put("reference_number",r_eBayPaymentRef.get("ReferenceID"));
				l_Invoices = List();
				m_Invoice = Map();
				m_Invoice.put("invoice_id",v_BooksInvoiceID);
				m_Invoice.put("amount_applied",r_eBayPaymentRef.get("Amount"));
				m_Invoice.put("tax_amount_withheld",0);
				l_Invoices.add(m_Invoice);
				m_BooksPayment.put("invoices",l_Invoices);
				//
				// create the payment record
				r_CreatePayment = zoho.inventory.createRecord("customerpayments",v_BooksOrgID,m_BooksPayment,"joel_inventory");
				info "PAYMENT RESPONSE:" + r_eBayPaymentRef.get("ReferenceID");
				info r_CreatePayment.get("message");
				l_DebugMessages.add("PAYMENT RESPONSE:" + r_eBayPaymentRef.get("ReferenceID") + ": " + r_CreatePayment.get("message"));
			}
		}
	}
	//
	// check if invoice fully paid now
	v_CurrentInvoiceStatus = "";
	r_CurrentInvoice = zoho.inventory.getRecordsByID("invoices",v_BooksOrgID,v_InvoiceID,"joel_inventory");
	if(!isnull(r_CurrentInvoice.get("invoice")))
	{
		v_CurrentInvoiceStatus = r_CurrentInvoice.get("invoice").get("status");
	}
	//
	// create/update shipment if date shipped is specified and full payment received
	l_DebugMessages.add("SHIPPING CREATE CONDITION: " + v_BooksPackageID + "::" + v_BooksSoID + "::" + v_Order_DateShipped + "::" + v_CurrentInvoiceStatus + "::" + b_OrderCancelled);
	if(v_BooksPackageID != 0 && v_BooksSoID != 0 && !isNull(v_Order_DateShipped) && v_CurrentInvoiceStatus == "paid" && !b_OrderCancelled)
	{
		//
		v_ShipmentID = 0;
		m_BooksShipment = Map();
		m_BooksShipment.put("shipment_number","SH-" + v_BooksSoReference.getSuffix("-"));
		m_BooksShipment.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"));
		m_BooksShipment.put("reference_number",v_BooksSoReference);
		m_BooksShipment.put("delivery_method",v_ShippingCarrier);
		m_BooksShipment.put("tracking_number",v_ShippingTrackingNumber);
		l_DebugMessages.add("SHIPPING CREATE REQUEST: " + m_BooksShipment);
		//
		if(v_BooksShipmentID == 0)
		{
			v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders?organization_id=" + v_BooksOrgID + "&package_ids=" + v_BooksPackageID + "&salesorder_id=" + v_BooksSoID;
			r_CreateShipment = invokeurl
			[
				url :v_Endpoint
				type :POST
				parameters:m_BooksShipment.toString()
				connection:"joel_inventory"
			];
			info "SHIPPING CREATE RESPONSE:";
			info r_CreateShipment.get("message");
			l_DebugMessages.add("SHIPPING CREATE RESPONSE: " + r_CreateShipment.get("message"));
			//
			if(!isnull(r_CreateShipment.get("shipmentorder")))
			{
				if(!isnull(r_CreateShipment.get("shipmentorder").get("shipment_id")))
				{
					v_ShipmentID = r_CreateShipment.get("shipmentorder").get("shipment_id").toLong();
				}
			}
		}
		else
		{
			v_ShipmentID = v_BooksShipmentID;
			v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_BooksShipmentID + "?organization_id=" + v_BooksOrgID + "&package_ids=" + v_BooksPackageID + "&salesorder_id=" + v_BooksSoID;
			r_UpdateShipment = invokeurl
			[
				url :v_Endpoint
				type :PUT
				parameters:m_BooksShipment.toString()
				connection:"joel_inventory"
			];
			info "SHIPPING UPDATE RESPONSE:";
			info r_UpdateShipment.get("message");
			l_DebugMessages.add("SHIPPING UPDATE RESPONSE: " + r_UpdateShipment.get("message"));
		}
		//
		// check delivery status based on tracking number
		if(!isNull(v_ShippingTrackingNumber))
		{
			v_DeliveryStatus = standalone.fn_DHL_CheckShipmentStatus(v_ShippingTrackingNumber);
			l_DebugMessages.add("DHL API Shipment Status: " + v_DeliveryStatus);
			if(v_DeliveryStatus.equalsIgnoreCase("delivered"))
			{
				//
				// mark as delivered
				if(v_ShipmentID != 0)
				{
					v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_ShipmentID + "/status/delivered?organization_id=" + v_BooksOrgID;
					r_UpdateShipment = invokeurl
					[
						url :v_Endpoint
						type :POST
						connection:"joel_inventory"
					];
					info "SHIPPING DELIVERED RESPONSE:";
					info r_UpdateShipment.get("message");
					l_DebugMessages.add("SHIPPING DELIVERED RESPONSE: " + r_UpdateShipment.get("message"));
				}
			}
		}
	}
	//
	// delete shipment order if exists and order cancelled
	if(b_OrderCancelled && v_BooksShipmentID.toLong() != 0)
	{
		v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_BooksShipmentID + "?organization_id=" + v_BooksOrgID;
		r_DeleteShipment = invokeurl
		[
			url :v_Endpoint
			type :DELETE
			connection:"joel_inventory"
		];
		info "SHIPMENT DELETE RESPONSE:";
		info r_DeleteShipment.get("message");
		l_DebugMessages.add("SHIPMENT DELETE RESPONSE: " + r_DeleteShipment.get("message"));
	}
}
//
if(b_DebugMode)
{
	/*
	l_DebugMessages.add("That's all folks!");
	v_DebugMessages = l_DebugMessages.toString("<hr />");
	sendmail
	[
		from :zoho.adminuserid
		to :"This email address is being protected from spambots. You need JavaScript enabled to view it."
		subject :"DEBUG: eBay Order: " + p_eBayOrderRef + " :: SO Order: " + v_BooksSoReference
		message :v_DebugMessages
	]
	*/
}
return "";
  1.  /* 
  2.      Function: fn_eBay_GetOrderInfoCreateUpdateZohoSO(string p_eBayOrderRef) 
  3.   
  4.      Purpose: Queries eBay for information about an order and then creates item/contact/sales order/package slip/invoice (if not exist) 
  5.   
  6.      Date Created:   2022-05-05 (Joel Lipman) 
  7.                      - Initial release 
  8.      Date Modified:     2023-01-23 (Joel Lipman) 
  9.                      - Revamp of code 
  10.                      - Only generate invoice if payment received 
  11.                      - Distinguish whether composite based on ebay item specific 
  12.                      - Add stock in order to authorize payment creation 
  13.      Date Modified:     2023-02-02 (Joel Lipman) 
  14.                      - Marked Items as inclusive of VAT so VAT reports in Books will be accurate (eg. £499.95 is correctly £416.62) 
  15.      Date Modified:     2023-02-23 (Joel Lipman) 
  16.                      - Check if ebay Order ID already exists on system to determine whether to update or create. 
  17.                      - Check if SalesOrder already exists on system to determine whether to update or create. 
  18.                      - Check if Invoice already exists on system to determine whether to update or create. 
  19.                      - Check if Package already exists on system to determine whether to update or create. 
  20.                      - Check if Shipment already exists on system to determine whether to update or create. 
  21.                      - Loop through eBay payments to record all the different payments received. 
  22.      Date Modified:     2023-03-14 (Joel Lipman) 
  23.                      - Resolved fix of incorrect customer (Use Email rather than StaticAlias) 
  24.                      - Resolved fix of inventory adjustment level (Error:9205:Insufficient Stock) 
  25.                      - Resolved fix of overpaid amount (Include Shipping Cost in SO) 
  26.      Date Modified:     2023-03-16 (Joel Lipman) 
  27.                      - Resolves issue of SO / Invoice / Package / Shipment / Payment not updating. 
  28.                      - Revamp and debug line by line 
  29.                      - Switched email identifier back to StaticAlias rather than member Email 
  30.                      - Possibly still an issue with delayed payment (check this is not holding status) 
  31.      Date Modified:     2023-03-17 (Joel Lipman) 
  32.                      - Enhanced debug messages as function not auto-triggering on receipt of Order 
  33.                      - Resolved: SO ID was not being returned when created. 
  34.                      - Forced re-upload of photo for item on any modification 
  35.      Date Modified:     2023-03-20 (Joel Lipman) 
  36.                      - Check if payment has already been recorded before creating a new one. 
  37.                      - Fixed reference_number being included on customer payment. 
  38.      Date Modified:     2023-03-21 (Joel Lipman) 
  39.                      - Added if conditions to not adjust stock level if item has enough quantity then that purchased 
  40.                      - Adds tracking number and carrier to shipment record on either creation or modification. 
  41.                      - Only creates shipment order if shipped time is specified. 
  42.                      - Only marks shipment as delivered if DHL API checked and status returned is delivered 
  43.      Date Modified:     2023-03-22 (Joel Lipman) 
  44.                      - On creation of Sales Order, create map of line items and created SO (fix to invoice only generated on edit) 
  45.      Date Modified:     2023-03-29 (Joel Lipman) 
  46.                      - If an eBay order comes through as cancelled, then void the sales order, void the invoice, delete the package and shipment. 
  47.                      - Added chart of accounts lookup to set purchase account for an item: now ticks purchase information and track inventory. 
  48.                      - Issue on create that shipment is not created. 
  49.   
  50.      More Info: 
  51.      - API Explorer Test Tool: https://developer.ebay.com/DevZone/build-test/test-tool/default.aspx?index=0&env=production&api=trading 
  52.      - GetMyeBaySelling Documentation: https://developer.ebay.com/devzone/xml/docs/reference/ebay/getmyebayselling.html 
  53.      - GetOrderTransactions Documentation: https://developer.ebay.com/devzone/xml/docs/reference/ebay/GetOrderTransactions.html 
  54.      - DHL API Developer Portal: https://developer.dhl.com/documentation 
  55.      - DHL API Developer API Reference Shipment Tracking: https://developer.dhl.com/api-reference/shipment-tracking#reference-docs-section 
  56.  */ 
  57.  // 
  58.  // enter your own organization ID here for ZohoBooks and ZohoInventory 
  59.  v_BooksOrgID = 12345678901
  60.  // 
  61.  // evaluate 
  62.  v_currentDate = zoho.currentdate.toString("yyyy-MM-dd")
  63.  v_OrderDate = zoho.currentdate.toString("yyyy-MM-dd")
  64.  v_Page = 1
  65.  v_PerPage = 10
  66.  m_Output = Map()
  67.  l_Costs = {11,13,15,17,20}
  68.  b_DebugMode = true
  69.  l_DebugMessages = List()
  70.  l_DebugMessages.add("eBay Order ID: " + p_eBayOrderRef)
  71.  l_DebugMessages.add("eBay Order Date: " + v_OrderDate)
  72.  info p_eBayOrderRef; 
  73.  // 
  74.  // get books tax rates 
  75.  m_Taxes = Map()
  76.  r_Taxes = invokeUrl 
  77.  [ 
  78.      url :"https://books.zoho.eu/api/v3/settings/taxes?organization_id=" + v_BooksOrgID 
  79.      type :GET 
  80.      connection:"joel_books" 
  81.  ]
  82.  if(!isnull(r_Taxes.get("taxes"))) 
  83.  { 
  84.      for each  r_Tax in r_Taxes.get("taxes") 
  85.      { 
  86.          m_Taxes.put(r_Tax.get("tax_percentage").toString(),r_Tax.get("tax_id"))
  87.      } 
  88.  } 
  89.  l_DebugMessages.add("ZB Taxes: " + m_Taxes)
  90.  // 
  91.  // set chart of accounts to use 
  92.  v_Endpoint = "https://books.zoho.eu/api/v3/chartofaccounts?organization_id=" + v_BooksOrgID; 
  93.  r_ChartOfAccounts = invokeUrl 
  94.  [ 
  95.      url :v_Endpoint 
  96.      type :GET 
  97.      connection:"joel_books" 
  98.  ]
  99.  m_Accounts = Map()
  100.  if(!isnull(r_ChartOfAccounts.get("chartofaccounts"))) 
  101.  { 
  102.      for each  r_Account in r_ChartOfAccounts.get("chartofaccounts") 
  103.      { 
  104.          m_Accounts.put(r_Account.get("account_name"),r_Account.get("account_id"))
  105.      } 
  106.  } 
  107.  // 
  108.  // get access token 
  109.  v_AccessToken = standalone.fn_eBay_GetAccessToken()
  110.  l_DebugMessages.add("AccessToken:<br /><br />" + v_AccessToken)
  111.  // 
  112.  v_TradingAPIVersion = 967
  113.  v_Endpoint = "https://api.ebay.com/ws/api.dll"
  114.  // 
  115.  // build header 
  116.  m_Headers = Map()
  117.  m_Headers.put("X-EBAY-API-SITEID",3)
  118.  m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion)
  119.  v_ApiCall = "GetOrderTransactions"
  120.  m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall)
  121.  m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken)
  122.  // 
  123.  // build params 
  124.  m_Params = Map()
  125.  m_Params.put("WarningLevel","High")
  126.  m_Params.put("ErrorLanguage","en_GB")
  127.  m_Params.put("DetailLevel","ReturnAll")
  128.  // 
  129.  // send an order array 
  130.  l_OrderIDs = List()
  131.  m_OrderID = Map()
  132.  m_OrderID.put("OrderID",p_eBayOrderRef)
  133.  l_OrderIDs.add(m_OrderID)
  134.  m_Params.put("OrderIDArray",l_OrderIDs)
  135.  // 
  136.  // convert to xml and replace root nodes 
  137.  x_Params = m_Params.toXML()
  138.  x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">")
  139.  x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>")
  140.  // 
  141.  // send the request XML as a string 
  142.  r_ResponseXMLOrder = invokeUrl 
  143.  [ 
  144.      url :v_Endpoint 
  145.      type :POST 
  146.      parameters:x_Params 
  147.      headers:m_Headers 
  148.  ]
  149.  info "-----------------------"; 
  150.  info "EBAY ORDER XML:"
  151.  if(b_DebugMode) 
  152.  { 
  153.      l_DebugMessages.add("EBAY ORDER XML:<br /><br />" + r_ResponseXMLOrder.replaceAll(">",">").replaceAll("<","<"))
  154.  } 
  155.  // 
  156.  // ******************************************************** 
  157.  // loop through order array 
  158.  v_ArrayNode = "OrderArray"
  159.  x_OrderArray = r_ResponseXMLOrder.subString(r_ResponseXMLOrder.indexOf("<" + v_ArrayNode),r_ResponseXMLOrder.lastIndexOf("</" + v_ArrayNode) + v_ArrayNode.length() + 3)
  160.  l_Orders = x_OrderArray.executeXPath("//Order").toXmlList()
  161.  for each  x_Order in l_Orders 
  162.  { 
  163.      // initialize 
  164.      m_BooksContact = Map()
  165.      m_BooksShipping = Map()
  166.      m_BooksBilling = Map()
  167.      l_InventoryLineItems = List()
  168.      // 
  169.      v_OrderID = x_Order.executeXPath("//Order/OrderID/text()")
  170.      v_OrderStatus = x_Order.executeXPath("//Order/OrderStatus/text()")
  171.      b_OrderCancelled = if(v_OrderStatus.equalsIgnoreCase("Cancelled") || v_OrderStatus.equalsIgnoreCase("Inactive"),true,false)
  172.      v_OrderAmountPaid = x_Order.executeXPath("//Order/AmountPaid/text()")
  173.      v_OrderCurrency = x_Order.executeXPath("//Order/AmountPaid/@currencyID").executeXPath("/currencyID/text()")
  174.      v_OrderSellerID = x_Order.executeXPath("//Order/SellerUserID/text()")
  175.      // 
  176.      v_OrderTaxPercent = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxPercent/text()")
  177.      v_OrderShippingIncludesTax = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/ShippingIncludedInTax/text()")
  178.      v_OrderTaxCurrency = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxAmount/@currencyID").executeXPath("/currencyID/text()")
  179.      v_OrderTaxAmount = x_Order.executeXPath("//Order/ShippingDetails/SalesTax/SalesTaxAmount/text()")
  180.      // 
  181.      v_BuyerShippingName = x_Order.executeXPath("//Order/ShippingAddress/Name/text()")
  182.      v_BuyerShippingStreet1 = x_Order.executeXPath("//Order/ShippingAddress/Street1/text()")
  183.      v_BuyerShippingStreet1 = if(v_BuyerShippingStreet1.contains(" ebay"),v_BuyerShippingStreet1.getPrefix(" ebay"),v_BuyerShippingStreet1)
  184.      v_BuyerShippingStreet2 = x_Order.executeXPath("//Order/ShippingAddress/Street2/text()")
  185.      v_BuyerShippingStreet2 = if(v_BuyerShippingStreet2.contains("ebay"),v_BuyerShippingStreet2.getPrefix("ebay"),v_BuyerShippingStreet2)
  186.      v_BuyerShippingCity = x_Order.executeXPath("//Order/ShippingAddress/CityName/text()")
  187.      v_BuyerShippingPostcode = x_Order.executeXPath("//Order/ShippingAddress/PostalCode/text()")
  188.      v_BuyerShippingCounty = x_Order.executeXPath("//Order/ShippingAddress/StateOrProvince/text()")
  189.      v_BuyerShippingCountryName = x_Order.executeXPath("//Order/ShippingAddress/CountryName/text()")
  190.      v_BuyerShippingPhone = x_Order.executeXPath("//Order/ShippingAddress/Phone/text()")
  191.      // 
  192.      // for Books: Billing Address Map 
  193.      m_BooksBilling.put("attention",v_BuyerShippingName)
  194.      m_BooksBilling.put("address",v_BuyerShippingStreet1)
  195.      m_BooksBilling.put("street2",v_BuyerShippingStreet2)
  196.      m_BooksBilling.put("city",v_BuyerShippingCity)
  197.      m_BooksBilling.put("state",v_BuyerShippingCounty)
  198.      m_BooksBilling.put("zip",v_BuyerShippingPostcode)
  199.      m_BooksBilling.put("country",v_BuyerShippingCountryName)
  200.      // for Books: Shipping Address Map 
  201.      m_BooksShipping.put("attention",v_BuyerShippingName)
  202.      m_BooksShipping.put("address",v_BuyerShippingStreet1)
  203.      m_BooksShipping.put("street2",v_BuyerShippingStreet2)
  204.      m_BooksShipping.put("city",v_BuyerShippingCity)
  205.      m_BooksShipping.put("state",v_BuyerShippingCounty)
  206.      m_BooksShipping.put("zip",v_BuyerShippingPostcode)
  207.      m_BooksShipping.put("country",v_BuyerShippingCountryName)
  208.      // 
  209.      v_ShippingServiceSelected = x_Order.executeXPath("//Order/ShippingServiceSelected/ShippingService/text()")
  210.      v_ShippingServiceCost = x_Order.executeXPath("//Order/ShippingServiceSelected/ShippingServiceCost/text()")
  211.      v_OrderSubtotal = x_Order.executeXPath("//Order/Subtotal/text()")
  212.      v_OrderTotal = x_Order.executeXPath("//Order/Total/text()")
  213.      // 
  214.      v_Order_DateCreated = x_Order.executeXPath("//Order/CreatedTime/text()")
  215.      v_Order_DateCreated = if(!isnull(v_Order_DateCreated),v_Order_DateCreated.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime)
  216.      l_DebugMessages.add("Date Order Created: " + v_Order_DateCreated)
  217.      v_Order_DateShipped = x_Order.executeXPath("//Order/ShippedTime/text()")
  218.      v_Order_DateShipped = if(!isnull(v_Order_DateShipped),v_Order_DateShipped.getPrefix(".").replaceFirst("T"," ",true).toTime(),null)
  219.      l_DebugMessages.add("Date Order Shipped: " + v_Order_DateShipped)
  220.      // 
  221.      // loop through transaction array (possibly multiple line items) 
  222.      l_Transactions = x_Order.executeXPath("//Order/TransactionArray/Transaction").toXmlList()
  223.      for each  x_Transaction in l_Transactions 
  224.      { 
  225.          // initialize 
  226.          m_BooksItem = Map()
  227.          m_BooksLineItem = Map()
  228.          // 
  229.          // get buyer info 
  230.          v_BuyerUserName = x_Transaction.executeXPath("//Transaction/Buyer/UserID/text()")
  231.          v_BuyerUserFName = x_Transaction.executeXPath("//Transaction/Buyer/UserFirstName/text()")
  232.          v_BuyerUserSName = x_Transaction.executeXPath("//Transaction/Buyer/UserLastName/text()")
  233.          v_BuyerUserEmail = x_Transaction.executeXPath("//Transaction/Buyer/Email/text()")
  234.          l_DebugMessages.add("BuyerEmail: " + v_BuyerUserEmail)
  235.          info "BuyerEmail: " + v_BuyerUserEmail; 
  236.          v_BuyerUserStaticEmail = x_Transaction.executeXPath("//Transaction/Buyer/StaticAlias/text()")
  237.          l_DebugMessages.add("BuyerStaticEmail: " + v_BuyerUserStaticEmail)
  238.          info "BuyerStaticEmail: " + v_BuyerUserStaticEmail; 
  239.          v_BuyerIDVerified = x_Transaction.executeXPath("//Transaction/Buyer/IDVerified/text()")
  240.          v_BuyerSite = x_Transaction.executeXPath("//Transaction/Buyer/Site/text()")
  241.          // 
  242.          // Update Zoho Contact Name 
  243.          m_BooksContact.put("contact_name",v_BuyerUserFName + " " + v_BuyerUserSName)
  244.          m_BooksContact.put("billing_address",m_BooksBilling)
  245.          m_BooksContact.put("shipping_address",m_BooksShipping)
  246.          m_BooksContact.put("first_name",v_BuyerUserFName)
  247.          m_BooksContact.put("last_name",v_BuyerUserSName)
  248.          m_BooksContact.put("phone",v_BuyerShippingPhone)
  249.          // 
  250.          v_ItemID = x_Transaction.executeXPath("//Transaction/Item/ItemID/text()").replaceAll("[^0-9]","")
  251.          l_DebugMessages.add("Item Number: " + v_ItemID)
  252.          info "Item Number: " + v_ItemID; 
  253.          v_ItemListingType = x_Transaction.executeXPath("//Transaction/Item/ListingType/text()")
  254.          v_ItemSKU = x_Transaction.executeXPath("//Transaction/Item/SKU/text()")
  255.          l_DebugMessages.add("Item SKU: " + v_ItemSKU)
  256.          info "Item SKU: " + v_ItemSKU; 
  257.          v_ItemConditionID = x_Transaction.executeXPath("//Transaction/Item/ConditionID/text()")
  258.          v_ItemConditionName = x_Transaction.executeXPath("//Transaction/Item/ConditionDisplayName/text()")
  259.          v_ItemPrice = x_Transaction.executeXPath("//Transaction/Item/SellingStatus/CurrentPrice/text()")
  260.          // 
  261.          v_TransactionID = x_Transaction.executeXPath("//Transaction/TransactionID/text()")
  262.          v_TransactionCurrency = x_Transaction.executeXPath("//Transaction/AmountPaid/@currencyID").executeXPath("/currencyID/text()")
  263.          v_TransactionAmountPaid = x_Transaction.executeXPath("//Transaction/AmountPaid/text()")
  264.          v_TransactionQtyPurchased = x_Transaction.executeXPath("//Transaction/QuantityPurchased/text()")
  265.          v_TransactionCheckoutStatus = x_Transaction.executeXPath("//Transaction/Status/CheckoutStatus/text()")
  266.          v_TransactionCompleteStatus = x_Transaction.executeXPath("//Transaction/Status/CompleteStatus/text()")
  267.          v_TransactionBestOffer = x_Transaction.executeXPath("//Transaction/BestOfferSale/text()")
  268.          v_TransactionOrderRef = x_Transaction.executeXPath("//Transaction/ExtendedOrderID/text()")
  269.          v_TransactionOrderLineItemRef = x_Transaction.executeXPath("//Transaction/OrderLineItemID/text()")
  270.          // 
  271.          v_Transaction_DateCreated = x_Transaction.executeXPath("//Transaction/CreatedDate/text()")
  272.          v_Transaction_DateCreated = if(!isnull(v_Transaction_DateCreated),v_Transaction_DateCreated.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime)
  273.          v_Transaction_DateModified = x_Transaction.executeXPath("//Transaction/Status/LastTimeModified/text()")
  274.          v_Transaction_DateModified = if(!isnull(v_Transaction_DateModified),v_Transaction_DateModified.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime)
  275.          v_Transaction_DatePaid = x_Transaction.executeXPath("//Transaction/PaidTime/text()")
  276.          v_Transaction_DatePaid = if(!isnull(v_Transaction_DatePaid),v_Transaction_DatePaid.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime)
  277.          v_Transaction_DateShipped = x_Transaction.executeXPath("//Transaction/ShippedTime/text()")
  278.          v_Transaction_DateShipped = if(!isnull(v_Transaction_DateShipped),v_Transaction_DateShipped.getPrefix(".").replaceFirst("T"," ",true).toTime(),zoho.currenttime)
  279.          // 
  280.          v_TransactionPaymentMethod = x_Transaction.executeXPath("//Transaction/Status/PaymentMethodUsed/text()")
  281.          v_TransactionPaymentStatus = x_Transaction.executeXPath("//Transaction/Status/eBayPaymentStatus/text()")
  282.          v_TransactionPaymentHoldStatus = x_Transaction.executeXPath("//Transaction/Status/PaymentHoldStatus/text()")
  283.          // 
  284.          v_ShippingCarrier = x_Transaction.executeXPath("//Transaction/ShippingDetails/ShipmentTrackingDetails/ShippingCarrierUsed/text()")
  285.          v_ShippingTrackingNumber = x_Transaction.executeXPath("//Transaction/ShippingDetails/ShipmentTrackingDetails/ShipmentTrackingNumber/text()")
  286.          // 
  287.          // Other Zoho Contact updates 
  288.          m_BooksContact.put("currency_code",v_TransactionCurrency)
  289.          // 
  290.          // check for the item in books and add to line items for other records 
  291.          v_BooksItemID = 0
  292.          v_BooksCurrentStock = 0
  293.          m_Criteria = Map()
  294.          m_Criteria.put("sku",v_ItemID)
  295.          b_Composite = false
  296.          // 
  297.          // search items 
  298.          r_SearchItems = zoho.inventory.getRecords("items",v_BooksOrgID,m_Criteria,"joel_inventory")
  299.          for each  r_FoundItem in r_SearchItems.get("items") 
  300.          { 
  301.              if(!isnull(r_FoundItem.get("item_id")) && r_FoundItem.get("sku") == v_ItemID) 
  302.              { 
  303.                  v_BooksItemID = r_FoundItem.get("item_id").toLong()
  304.                  v_ItemTitle = r_FoundItem.get("name")
  305.                  v_ListingDescription = r_FoundItem.get("description")
  306.                  v_BooksCurrentStock = ifnull(r_FoundItem.get("actual_available_stock"),0).toLong()
  307.              } 
  308.          } 
  309.          // search composite items 
  310.          if(v_BooksItemID == 0) 
  311.          { 
  312.              r_SearchCompositeItems = zoho.inventory.getRecords("compositeitems",v_BooksOrgID,m_Criteria,"joel_inventory")
  313.              for each  r_CompositeItem in r_SearchCompositeItems.get("compositeitems") 
  314.              { 
  315.                  if(!isnull(r_CompositeItem.get("item_id")) && r_CompositeItem.get("sku") == v_ItemID) 
  316.                  { 
  317.                      v_BooksItemID = r_CompositeItem.get("item_id").toLong()
  318.                      v_ItemTitle = r_CompositeItem.get("name")
  319.                      v_ListingDescription = r_CompositeItem.get("description")
  320.                      v_BooksCurrentStock = ifnull(r_CompositeItem.get("actual_available_stock"),0).toLong()
  321.                      b_Composite = true
  322.                  } 
  323.              } 
  324.          } 
  325.          // 
  326.          l_DebugMessages.add("ZB Item Search: " + v_BooksItemID)
  327.          info "ZB Item Search: " + v_BooksItemID; 
  328.          // *********************************************** 
  329.          // query eBay for the item listing 
  330.          // 
  331.          // build header 
  332.          m_Headers = Map()
  333.          m_Headers.put("X-EBAY-API-SITEID",3)
  334.          m_Headers.put("X-EBAY-API-COMPATIBILITY-LEVEL",v_TradingAPIVersion)
  335.          v_ApiCall = "GetItem"
  336.          m_Headers.put("X-EBAY-API-CALL-NAME",v_ApiCall)
  337.          m_Headers.put("X-EBAY-API-IAF-TOKEN",v_AccessToken)
  338.          // 
  339.          // build params 
  340.          m_Params = Map()
  341.          m_Params.put("WarningLevel","High")
  342.          m_Params.put("ErrorLanguage","en_GB")
  343.          m_Params.put("DetailLevel","ReturnAll")
  344.          m_Params.put("IncludeItemSpecifics",true)
  345.          // 
  346.          // include fixed price items 
  347.          m_ActiveList = Map()
  348.          m_ActiveList.put("Include","true")
  349.          m_ActiveList.put("ListingType","FixedPriceItem")
  350.          m_Pagination = Map()
  351.          m_Pagination.put("PageNumber",v_Page)
  352.          m_Pagination.put("EntriesPerPage",v_PerPage)
  353.          m_ActiveList.put("Pagination",m_Pagination)
  354.          m_Params.put("ItemID",v_ItemID)
  355.          // 
  356.          // convert to xml and replace root nodes 
  357.          x_Params = m_Params.toXML()
  358.          x_Params = x_Params.toString().replaceFirst("<root>","<?xml version=\"1.0\" encoding=\"utf-8\"?><" + v_ApiCall + "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">")
  359.          x_Params = x_Params.toString().replaceFirst("</root>","</" + v_ApiCall + "Request>")
  360.          // 
  361.          // send the request XML as a string 
  362.          r_ResponseXMLItem = invokeUrl 
  363.          [ 
  364.              url :v_Endpoint 
  365.              type :POST 
  366.              parameters:x_Params 
  367.              headers:m_Headers 
  368.          ]
  369.          info "-----------------------"; 
  370.          info "EBAY ITEM XML:"
  371.          if(b_DebugMode) 
  372.          { 
  373.              l_DebugMessages.add("EBAY ITEM XML:<br /><br />" + r_ResponseXMLItem.replaceAll(">",">").replaceAll("<","<"))
  374.          } 
  375.          v_Node = "Item"
  376.          x_Item = r_ResponseXMLItem.subString(r_ResponseXMLItem.indexOf("<" + v_Node),r_ResponseXMLItem.lastIndexOf("</" + v_Node) + v_Node.length() + 3)
  377.          // 
  378.          // parse out details of item 
  379.          v_ItemTitle = x_Item.executeXPath("//Item/Title/text()")
  380.          l_DebugMessages.add("Item Title: " + v_ItemTitle)
  381.          info "Item Title: " + v_ItemTitle; 
  382.          v_ItemCustomSKU = x_Item.executeXPath("//Item/SKU/text()")
  383.          v_ItemBrand = x_Item.executeXPath("//Item/ProductListingDetails/BrandMPN/Brand/text()")
  384.          v_ItemCategoryID = x_Item.executeXPath("//Item/PrimaryCategory/CategoryID/text()")
  385.          v_ItemCategoryName = x_Item.executeXPath("//Item/PrimaryCategory/CategoryName/text()")
  386.          v_ItemLocation = x_Item.executeXPath("//Item/Location/text()")
  387.          v_ListingQuantity = x_Item.executeXPath("//Item/Quantity/text()")
  388.          v_ListingConditionName = x_Item.executeXPath("//Item/ConditionDisplayName/text()")
  389.          v_ListingConditionDesc = x_Item.executeXPath("//Item/ConditionDescription/text()")
  390.          //v_ListingDescription = x_Item.executeXPath("//Item/Description/text()").replaceAll("<","<").replaceAll(">",">")
  391.          v_ListingDescription = x_Item.executeXPath("//Item/Description/text()")
  392.          l_ListingPictures = x_Item.executeXPath("//Item/PictureDetails/PictureURL").toXmlList()
  393.          l_ItemSpecifics = x_Item.executeXPath("//Item/ItemSpecifics/NameValueList").toXmlList()
  394.          v_ItemCondition = x_Item.executeXPath("//Item/ConditionID/text()")
  395.          b_IsNew = if(v_ItemCondition == "1000",true,false)
  396.          // 
  397.          m_BooksItem.put("name",v_ItemTitle)
  398.          m_BooksItem.put("sku",v_ItemID)
  399.          m_BooksItem.put("rate",v_ItemPrice.toDecimal())
  400.          // cost? 
  401.          v_NumberOfStars = v_ItemTitle.getOccurenceCount("*") - 1
  402.          v_NumberOfStars = if(v_NumberOfStars < 1,0,v_NumberOfStars)
  403.          v_NumberOfStars = if(v_NumberOfStars > 4,4,v_NumberOfStars)
  404.          v_PurchaseRate = l_Costs.get(v_NumberOfStars)
  405.          m_BooksItem.put("purchase_rate",v_PurchaseRate.toDecimal())
  406.          // 
  407.          v_UnitType = ""
  408.          if(v_ItemTitle.containsIgnoreCase("wheel") && v_ItemTitle.containsIgnoreCase("tyre")) 
  409.          { 
  410.              v_UnitType = "pcs"
  411.          } 
  412.          else if(v_ItemTitle.containsIgnoreCase("wheel")) 
  413.          { 
  414.              v_UnitType = "Wheel"
  415.          } 
  416.          else if(v_ItemTitle.containsIgnoreCase("tyre")) 
  417.          { 
  418.              v_UnitType = "Tyre"
  419.          } 
  420.          m_BooksItem.put("unit",v_UnitType)
  421.          // 
  422.          l_CustomFields = list()
  423.          m_CustomField = Map()
  424.          m_CustomField.put("api_name","cf_ebay_sku")
  425.          m_CustomField.put("value",v_ItemCustomSKU)
  426.          l_CustomFields.add(m_CustomField)
  427.          // 
  428.          // here are the item specifics, simply specify the Item Specific label and then the Zoho Inventory API name of the field to map to. 
  429.          m_NameListMappings = Map()
  430.          m_NameListMappings.put("Offset (ET)","cf_offset")
  431.          m_NameListMappings.put("Centre Bore","cf_centre_bore")
  432.          m_NameListMappings.put("Custom Bundle","cf_custom_bundle")
  433.          m_NameListMappings.put("Manufacturer Part Number","cf_manufacturer_part_number")
  434.          m_NameListMappings.put("Stud Diameter","cf_stud_diameter")
  435.          m_NameListMappings.put("Wheel Material","cf_wheel_material")
  436.          m_NameListMappings.put("Wheel Construction","cf_wheel_construction")
  437.          m_NameListMappings.put("Reference OE/OEM Number","cf_reference_oe_oem_number")
  438.          m_NameListMappings.put("Modified Item","cf_modified_item")
  439.          m_NameListMappings.put("Offset","cf_offset")
  440.          m_NameListMappings.put("Number of Studs","cf_number_of_studs")
  441.          m_NameListMappings.put("Type","cf_type")
  442.          m_NameListMappings.put("Wheel Diameter","cf_wheel_diameter")
  443.          m_NameListMappings.put("Unit Quantity","cf_unit_quantity")
  444.          m_NameListMappings.put("Finish","cf_finish")
  445.          m_NameListMappings.put("Wheel Width","cf_wheel_width")
  446.          // 
  447.          l_CustomFields = list()
  448.          for each  x_ItemSpecific in l_ItemSpecifics 
  449.          { 
  450.              v_SpecificName = x_ItemSpecific.executeXPath("//NameValueList/Name/text()")
  451.              v_SpecificValue = x_ItemSpecific.executeXPath("//NameValueList/Value/text()")
  452.              // 
  453.              if(!isNull(m_NameListMappings.get(v_SpecificName))) 
  454.              { 
  455.                  m_CustomField = Map()
  456.                  m_CustomField.put("api_name",m_NameListMappings.get(v_SpecificName))
  457.                  m_CustomField.put("value",v_SpecificValue)
  458.                  l_CustomFields.add(m_CustomField)
  459.              } 
  460.              // 
  461.              if(v_SpecificName.containsIgnoreCase("Unit Quantity")) 
  462.              { 
  463.                  if(v_SpecificValue.toLong() > 1) 
  464.                  { 
  465.                      b_Composite = true
  466.                  } 
  467.              } 
  468.          } 
  469.          // 
  470.          m_BooksItem.put("custom_fields",l_CustomFields)
  471.          // 
  472.          // do 1 picture as ZohoInventory only supports 1 image upload (at time of print) 
  473.          v_PictureURL = ""
  474.          for each  x_Picture in l_ListingPictures 
  475.          { 
  476.              v_PictureURL = x_Picture.executeXPath("//PictureURL/text()")
  477.              break
  478.          } 
  479.          // 
  480.          // update or create with purchase information 
  481.          m_BooksItem.put("inventory_account_id",m_Accounts.get("Inventory Asset"))
  482.          m_BooksItem.put("purchase_rate",v_ItemPrice.toDecimal())
  483.          if(v_ItemTitle.containsIgnoreCase("alloy")) 
  484.          { 
  485.              m_BooksItem.put("purchase_account_id",m_Accounts.get("Cost of Goods Sold"))
  486.              m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong())
  487.          } 
  488.          else 
  489.          { 
  490.              if(v_ListingConditionName.containsIgnoreCase("new")) 
  491.              { 
  492.                  m_BooksItem.put("purchase_account_id",m_Accounts.get("New Tyres Purchase"))
  493.                  m_BooksItem.put("initial_stock",999)
  494.              } 
  495.              else 
  496.              { 
  497.                  m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong())
  498.                  m_BooksItem.put("purchase_account_id",m_Accounts.get("Part Worn Tyres Purchase"))
  499.              } 
  500.          } 
  501.          m_BooksItem.put("initial_stock_rate",v_ItemPrice.toDecimal())
  502.          // 
  503.          // send request to create item 
  504.          if(v_BooksItemID == 0) 
  505.          { 
  506.              //             m_BooksItem.put("initial_stock",v_TransactionQtyPurchased.toLong())
  507.              //             m_BooksItem.put("initial_stock_rate",v_ItemPrice.toDecimal())
  508.              r_CreateItem = zoho.inventory.createRecord("items",v_BooksOrgID,m_BooksItem,"joel_inventory")
  509.              info "ITEM CREATE RESPONSE FOR " + v_ItemID; 
  510.              info r_CreateItem.get("message")
  511.              l_DebugMessages.add("ITEM CREATE RESPONSE FOR " + v_ItemID + ": " + r_CreateItem.get("message"))
  512.              // 
  513.              // retrieve the generated item id for generating other records 
  514.              if(!isnull(r_CreateItem.get("item"))) 
  515.              { 
  516.                  if(!isnull(r_CreateItem.get("item").get("item_id"))) 
  517.                  { 
  518.                      v_BooksItemID = r_CreateItem.get("item").get("item_id").toLong()
  519.                  } 
  520.              } 
  521.          } 
  522.          else 
  523.          { 
  524.              // 
  525.              // ENSURE THAT STOCK LEVEL IS RESTORED TO PURCHASE THIS ITEM 
  526.              if(v_BooksCurrentStock < 1) 
  527.              { 
  528.                  // now build stock level adjustment on ZohoInventory for this item 
  529.                  m_UpdateStock = Map()
  530.                  m_UpdateStock.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"))
  531.                  m_UpdateStock.put("reason","eBay Order")
  532.                  m_UpdateStock.put("description","An eBay Order has come in using this item.  Re-adjusting inventory level on-the-fly to include it for use in transactions.")
  533.                  m_UpdateStock.put("adjustment_type","quantity")
  534.                  // 
  535.                  // need to include line items of adjustment (just this one item) 
  536.                  l_LineItems = List()
  537.                  m_LineItem = Map()
  538.                  m_LineItem.put("item_id",v_BooksItemID)
  539.                  m_LineItem.put("quantity_adjusted",abs(v_TransactionQtyPurchased))
  540.                  l_LineItems.add(m_LineItem)
  541.                  m_UpdateStock.put("line_items",l_LineItems)
  542.                  // 
  543.                  // create this stock level adjustment (need to use Invoke as shortcode wouldn't work) 
  544.                  v_Endpoint = "https://www.zohoapis.eu/inventory/v1/inventoryadjustments?organization_id=" + v_BooksOrgID; 
  545.                  r_CreateAdjustment = invokeUrl 
  546.                  [ 
  547.                      url :v_Endpoint 
  548.                      type :POST 
  549.                      parameters:m_UpdateStock.toString() 
  550.                      connection:"joel_inventory" 
  551.                  ]
  552.                  info "ADJUSTED INVENTORY LEVEL"
  553.                  info r_CreateAdjustment.get("message")
  554.                  l_DebugMessages.add("ADJUSTED INVENTORY LEVEL: " + r_CreateAdjustment.get("message"))
  555.              } 
  556.              // 
  557.              // update the item to block this function from running until modified by some other method 
  558.              m_UpdateItem = Map()
  559.              l_CustomFields = List()
  560.              m_CustomField = Map()
  561.              m_CustomField.put("api_name","cf_updated_by")
  562.              m_CustomField.put("value","eBay Order")
  563.              l_CustomFields.add(m_CustomField)
  564.              m_UpdateItem.put("custom_fields",l_CustomFields)
  565.              m_UpdateItem.put("status","active")
  566.              if(v_BooksCurrentStock < 1) 
  567.              { 
  568.                  m_UpdateItem.put("initial_stock",abs(v_TransactionQtyPurchased))
  569.                  m_UpdateItem.put("initial_stock_rate",v_ItemPrice)
  570.              } 
  571.              //info m_UpdateItem; 
  572.              v_Endpoint = "https://www.zohoapis.eu/inventory/v1/items/" + v_BooksItemID + "?organization_id=" + v_BooksOrgID; 
  573.              r_UpdateItem = invokeUrl 
  574.              [ 
  575.                  url :v_Endpoint 
  576.                  type :PUT 
  577.                  parameters:m_UpdateItem.toString() 
  578.                  connection:"joel_inventory" 
  579.              ]
  580.              info "UPDATED ITEM:"
  581.              info r_UpdateItem.get("message")
  582.              l_DebugMessages.add("UPDATED ITEM: " + r_UpdateItem.get("message"))
  583.          } 
  584.          // 
  585.          // let's upload the picture for this (only 1 supported in inventory at this time) 
  586.          if(!isBlank(v_PictureURL)) 
  587.          { 
  588.              r_DownloadedPhoto = invokeUrl 
  589.              [ 
  590.                  url :v_PictureURL 
  591.                  type :GET 
  592.              ]
  593.              // 
  594.              // set the data type 
  595.              r_DownloadedPhoto.setParamName("image")
  596.              // 
  597.              // build up request to Zoho 
  598.              m_Params = Map()
  599.              m_Params.put("image",r_DownloadedPhoto)
  600.              // 
  601.              // generate endpoint 
  602.              v_Url = "https://inventory.zoho.eu/api/v1/items/" + v_BooksItemID + "/image"
  603.              // 
  604.              // updload the photo 
  605.              r_UploadPhoto = invokeUrl 
  606.              [ 
  607.                  url :v_Url 
  608.                  type :POST 
  609.                  files:r_DownloadedPhoto 
  610.                  connection:"joel_inventory" 
  611.              ]
  612.              // output response to console 
  613.              info "PHOTO UPLOAD:"
  614.              info r_UploadPhoto.get("message")
  615.              l_DebugMessages.add("PHOTO UPLOAD OF " + v_PictureURL + ": " + r_UploadPhoto.get("message"))
  616.          } 
  617.          // 
  618.          // ensure the item is activated 
  619.          if(v_BooksItemID != 0) 
  620.          { 
  621.              v_Endpoint = "https://www.zohoapis.eu/inventory/v1/items/" + v_BooksItemID + "/active?organization_id=" + v_BooksOrgID; 
  622.              r_ActivateItem = invokeUrl 
  623.              [ 
  624.                  url :v_Endpoint 
  625.                  type :POST 
  626.                  connection:"joel_inventory" 
  627.              ]
  628.              info "ACTIVATED ITEM:"
  629.              info r_ActivateItem.get("message")
  630.              l_DebugMessages.add("ACTIVATED ITEM: " + r_ActivateItem.get("message"))
  631.          } 
  632.          // 
  633.          // add to line items for sales orders or invoices 
  634.          info "BooksItemID: " + v_BooksItemID; 
  635.          m_BooksLineItem.put("item_id",v_BooksItemID)
  636.          m_BooksLineItem.put("name",v_ItemTitle)
  637.          v_ListingDescriptionCleaned = ifnull(v_ListingDescription,"").replaceAll("<br />","\n",true).replaceAll("<br>","\n",true).replaceAll("<(.|\n)*?>","")
  638.          m_BooksLineItem.put("description","")
  639.          v_OrderTaxFactor = v_OrderTaxPercent.toDecimal() / 100
  640.          v_OrderTaxFactor = v_OrderTaxFactor + 1
  641.          v_ItemPriceExclVAT = v_ItemPrice.toDecimal() / v_OrderTaxFactor; 
  642.          v_ItemPriceExclVATRounded = floor(v_ItemPriceExclVAT * 100) / 100
  643.          m_BooksLineItem.put("rate",v_ItemPrice)
  644.          m_BooksLineItem.put("quantity",v_TransactionQtyPurchased.toLong())
  645.          v_UnitType = ""
  646.          if(v_ItemTitle.containsIgnoreCase("wheel") && v_ItemTitle.containsIgnoreCase("tyre")) 
  647.          { 
  648.              v_UnitType = "pcs"
  649.          } 
  650.          else if(v_ItemTitle.containsIgnoreCase("wheel")) 
  651.          { 
  652.              v_UnitType = "Wheel"
  653.          } 
  654.          else if(v_ItemTitle.containsIgnoreCase("tyre")) 
  655.          { 
  656.              v_UnitType = "Tyre"
  657.          } 
  658.          m_BooksLineItem.put("unit",v_UnitType)
  659.          m_BooksLineItem.put("tax_id",m_Taxes.get(v_OrderTaxPercent.toLong().toString()))
  660.          m_BooksLineItem.put("tax_percentage",v_OrderTaxPercent.toLong())
  661.          //m_BooksLineItem.put("item_total",v_ItemPriceExclVAT.toDecimal())
  662.          l_InventoryLineItems.add(m_BooksLineItem)
  663.          l_DebugMessages.add("BooksItemID: " + m_BooksLineItem)
  664.      } 
  665.      // 
  666.      // now loop through payments for this order 
  667.      l_Payments = x_Order.executeXPath("//Order/MonetaryDetails/Payments").toXmlList()
  668.      v_OrderPayAmountTotal = 0.0
  669.      l_eBayPaymentRefs = List()
  670.      for each  x_Payment in l_Payments 
  671.      { 
  672.          m_eBayPaymentRef = Map()
  673.          v_PaymentRef = x_Payment.executeXPath("//Payment/ReferenceID/text()")
  674.          m_eBayPaymentRef.put("BooksItemID",v_BooksItemID)
  675.          m_eBayPaymentRef.put("ReferenceID",v_PaymentRef)
  676.          v_ThisPaymentStatus = x_Payment.executeXPath("//Payment/PaymentStatus/text()")
  677.          m_eBayPaymentRef.put("Status",v_ThisPaymentStatus)
  678.          v_ThisPaymentTime = x_Payment.executeXPath("//Payment/PaymentTime/text()")
  679.          v_ThisPaymentTime = if(!isnull(v_ThisPaymentTime),v_ThisPaymentTime.getPrefix(".").replaceFirst("T"," ",true),zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss"))
  680.          m_eBayPaymentRef.put("DateTime",v_ThisPaymentTime)
  681.          v_ThisPayee = x_Payment.executeXPath("//Payment/Payee/text()")
  682.          m_eBayPaymentRef.put("Payee",v_ThisPayee)
  683.          v_ThisPaymentAmount = 0.0
  684.          if(v_ThisPaymentStatus == "Succeeded" && v_ThisPayee == v_OrderSellerID) 
  685.          { 
  686.              v_ThisPaymentAmount = x_Payment.executeXPath("//Payment/PaymentAmount/text()")
  687.              v_OrderPayAmountTotal = v_OrderPayAmountTotal + v_ThisPaymentAmount.toDecimal()
  688.          } 
  689.          m_eBayPaymentRef.put("Amount",v_ThisPaymentAmount)
  690.          l_eBayPaymentRefs.add(m_eBayPaymentRef)
  691.      } 
  692.      info "Payment(s): "
  693.      if(b_DebugMode) 
  694.      { 
  695.          info l_eBayPaymentRefs; 
  696.      } 
  697.      l_DebugMessages.add("PAYMENT(S): " + l_eBayPaymentRefs)
  698.  } 
  699.  // 
  700.  // search for this customer in Zoho Inventory 
  701.  info "-----------------------"; 
  702.  v_BooksCustomerID = 0
  703.  m_SearchCriteria = Map()
  704.  m_SearchCriteria.put("email",v_BuyerUserStaticEmail)
  705.  r_SearchContacts = zoho.inventory.getRecords("contacts",v_BooksOrgID,m_SearchCriteria,"joel_inventory")
  706.  for each  r_Contact in r_SearchContacts.get("contacts") 
  707.  { 
  708.      if(!isnull(r_Contact.get("contact_id"))) 
  709.      { 
  710.          if(!isNull(v_BuyerUserStaticEmail) && v_BuyerUserStaticEmail == r_Contact.get("email")) 
  711.          { 
  712.              v_BooksCustomerID = r_Contact.get("contact_id").toLong()
  713.          } 
  714.      } 
  715.  } 
  716.  info "ZB Contact Search: " + v_BooksCustomerID; 
  717.  l_DebugMessages.add("ZB Contact Search: " + v_BooksCustomerID)
  718.  if(v_BooksCustomerID == 0) 
  719.  { 
  720.      // create a contact person 
  721.      m_ContactPerson = Map()
  722.      m_ContactPerson.put("email",v_BuyerUserStaticEmail)
  723.      m_ContactPerson.put("first_name",v_BuyerUserFName)
  724.      m_ContactPerson.put("last_name",v_BuyerUserSName)
  725.      m_ContactPerson.put("phone",v_BuyerShippingPhone)
  726.      m_ContactPerson.put("is_primary_contact",true)
  727.      l_CreateContactPerson = List()
  728.      l_CreateContactPerson.add(m_ContactPerson)
  729.      m_BooksContact.put("contact_persons",l_CreateContactPerson)
  730.      // 
  731.      // other fields on creation 
  732.      m_BooksContact.put("contact_type","customer")
  733.      m_BooksContact.put("customer_sub_type","individual")
  734.      m_BooksContact.put("payment_terms",0)
  735.      m_BooksContact.put("status","active")
  736.      // 
  737.      // send request to create contact 
  738.      r_CreateContact = zoho.inventory.createRecord("contacts",v_BooksOrgID,m_BooksContact,"joel_inventory")
  739.      info "CONTACT CREATE RESPONSE: "
  740.      info r_CreateContact.get("message")
  741.      l_DebugMessages.add("CONTACT CREATE RESPONSE: " + r_CreateContact)
  742.      // 
  743.      // retrieve the generated contact id for generating other records 
  744.      if(!isnull(r_CreateContact.get("contact"))) 
  745.      { 
  746.          if(!isnull(r_CreateContact.get("contact").get("contact_id"))) 
  747.          { 
  748.              v_BooksCustomerID = r_CreateContact.get("contact").get("contact_id").toLong()
  749.          } 
  750.      } 
  751.  } 
  752.  else 
  753.  { 
  754.      // 
  755.      // send request to modify contact 
  756.      r_UpdateContact = zoho.inventory.updateRecord("contacts",v_BooksOrgID,v_BooksCustomerID,m_BooksContact,"joel_inventory")
  757.      info "CONTACT UPDATE RESPONSE:"
  758.      info r_UpdateContact.get("message")
  759.      l_DebugMessages.add("CONTACT UPDATE RESPONSE: " + r_UpdateContact.get("message"))
  760.  } 
  761.  info "CustomerID: " + v_BooksCustomerID; 
  762.  l_DebugMessages.add("CustomerID: " + v_BooksCustomerID)
  763.  info "-----------------------"; 
  764.  // 
  765.  // ******************************************************** 
  766.  // now we have contact ID and item ID, let's create Sales Order 
  767.  if(v_BooksCustomerID != 0 && v_BooksItemID != 0) 
  768.  { 
  769.      m_BooksSalesOrder = Map()
  770.      m_BooksSalesOrder.put("customer_id",v_BooksCustomerID)
  771.      m_BooksSalesOrder.put("date",v_Transaction_DateCreated.toString("yyyy-MM-dd","Europe/London"))
  772.      m_BooksSalesOrder.put("reference_number",p_eBayOrderRef)
  773.      m_BooksSalesOrder.put("line_items",l_InventoryLineItems)
  774.      m_BooksSalesOrder.put("is_inclusive_tax",true)
  775.      m_BooksSalesOrder.put("shipping_charge",ifnull(v_ShippingServiceCost,0.0).toDecimal())
  776.      l_CustomFields = list()
  777.      m_CustomField = Map()
  778.      m_CustomField.put("api_name","cf_source")
  779.      m_CustomField.put("value","eBay")
  780.      l_CustomFields.add(m_CustomField)
  781.      m_CustomField = Map()
  782.      m_CustomField.put("api_name","cf_ebay_order_id")
  783.      m_CustomField.put("value",p_eBayOrderRef)
  784.      l_CustomFields.add(m_CustomField)
  785.      m_BooksSalesOrder.put("custom_fields",l_CustomFields)
  786.      // 
  787.      // determine if sales order already exists or to update 
  788.      v_BooksSoID = 0
  789.      m_SearchCriteria = Map()
  790.      m_SearchCriteria.put("reference_number",p_eBayOrderRef)
  791.      r_BooksSearch = zoho.inventory.getRecords("salesorders",v_BooksOrgID,m_SearchCriteria,"joel_inventory")
  792.      for each  r_So in r_BooksSearch.get("salesorders") 
  793.      { 
  794.          if(r_So.get("reference_number") == p_eBayOrderRef) 
  795.          { 
  796.              v_BooksSoID = r_So.get("salesorder_id")
  797.          } 
  798.      } 
  799.      info "ZB SalesOrder Search: " + v_BooksSoID; 
  800.      l_DebugMessages.add("ZB SalesOrder Search: " + v_BooksSoID)
  801.      //info m_BooksSalesOrder; 
  802.      // 
  803.      // if sales order exists then update it 
  804.      if(v_BooksSoID != 0) 
  805.      { 
  806.          r_UpdateSO = zoho.inventory.updateRecord("salesorders",v_BooksOrgID,v_BooksSoID,m_BooksSalesOrder,"joel_inventory")
  807.          info "SALESORDER UPDATE RESPONSE:"
  808.          info r_UpdateSO.get("message")
  809.          l_DebugMessages.add("SALESORDER UPDATE RESPONSE: " + r_UpdateSO.get("message") + "<br /><br />" + m_BooksSalesOrder)
  810.          // 
  811.          r_BooksSO = zoho.inventory.getRecordsByID("salesorders",v_BooksOrgID,v_BooksSoID,"joel_inventory")
  812.          m_BooksSO = if(!isnull(r_BooksSO.get("salesorder")),r_BooksSO.get("salesorder").toMap(),{})
  813.          if(!isnull(m_BooksSO.get("salesorder_number"))) 
  814.          { 
  815.              v_BooksSoReference = m_BooksSO.get("salesorder_number")
  816.          } 
  817.      } 
  818.      else 
  819.      { 
  820.          r_CreateSO = zoho.inventory.createRecord("salesorders",v_BooksOrgID,m_BooksSalesOrder,"joel_inventory")
  821.          info "SALESORDER CREATE RESPONSE:"
  822.          info r_CreateSO.get("message")
  823.          l_DebugMessages.add("SALESORDER CREATE RESPONSE: " + r_CreateSO.get("message") + "<br /><br />" + m_BooksSalesOrder)
  824.          // 
  825.          // if having created the sales order 
  826.          v_BooksSoReference = ""
  827.          if(!isnull(r_CreateSO.get("salesorder"))) 
  828.          { 
  829.              if(!isnull(r_CreateSO.get("salesorder").get("salesorder_id"))) 
  830.              { 
  831.                  v_BooksSoID = r_CreateSO.get("salesorder").get("salesorder_id")
  832.                  r_BooksSO = zoho.inventory.getRecordsByID("salesorders",v_BooksOrgID,v_BooksSoID,"joel_inventory")
  833.                  m_BooksSO = if(!isnull(r_BooksSO.get("salesorder")),r_BooksSO.get("salesorder").toMap(),{})
  834.              } 
  835.              if(!isnull(r_CreateSO.get("salesorder").get("salesorder_number"))) 
  836.              { 
  837.                  v_BooksSoReference = r_CreateSO.get("salesorder").get("salesorder_number")
  838.              } 
  839.          } 
  840.      } 
  841.      // 
  842.      info "SalesOrderID: " + v_BooksSoID; 
  843.      info "SalesOrderRef: " + v_BooksSoReference; 
  844.      info "-----------------------"; 
  845.      l_DebugMessages.add("SalesOrderID: " + v_BooksSoID)
  846.      l_DebugMessages.add("SalesOrderRef: " + v_BooksSoReference)
  847.      // 
  848.      // in both cases let's confirm the sales order unless cancelled 
  849.      if(b_OrderCancelled) 
  850.      { 
  851.          v_Endpoint = "https://inventory.zoho.eu/api/v1/salesorders/" + v_BooksSoID + "/status/void?organization_id=" + v_BooksOrgID; 
  852.          r_CancelSO = invokeUrl 
  853.          [ 
  854.              url :v_Endpoint 
  855.              type :POST 
  856.              connection:"joel_inventory" 
  857.          ]
  858.          info "SALESORDER void STATUS:"
  859.          info r_CancelSO.get("message")
  860.          l_DebugMessages.add("SALESORDER void RESPONSE: " + r_CancelSO.get("message"))
  861.      } 
  862.      else 
  863.      { 
  864.          v_Endpoint = "https://inventory.zoho.eu/api/v1/salesorders/" + v_BooksSoID + "/status/confirmed?organization_id=" + v_BooksOrgID; 
  865.          r_ConfirmSO = invokeUrl 
  866.          [ 
  867.              url :v_Endpoint 
  868.              type :POST 
  869.              connection:"joel_inventory" 
  870.          ]
  871.          info "SALESORDER CONFIRMED STATUS:"
  872.          info r_ConfirmSO.get("message")
  873.          l_DebugMessages.add("SALESORDER CONFIRMED RESPONSE: " + r_ConfirmSO.get("message"))
  874.      } 
  875.      // 
  876.      // in both cases let's build up the package slip/delivery note line items 
  877.      l_PackageLineItems = List()
  878.      l_ExistingSoLineItems = m_BooksSO.get("line_items")
  879.      for each  r_SoLineItem in l_ExistingSoLineItems 
  880.      { 
  881.          m_PackageLineItem = Map()
  882.          m_PackageLineItem.put("so_line_item_id",r_SoLineItem.get("line_item_id"))
  883.          m_PackageLineItem.put("quantity",r_SoLineItem.get("quantity"))
  884.          l_PackageLineItems.add(m_PackageLineItem)
  885.      } 
  886.      // 
  887.      // search to see if invoice already generated 
  888.      v_BooksInvoiceID = 0
  889.      m_SearchCriteria = Map()
  890.      m_SearchCriteria.put("reference_number",v_BooksSoReference)
  891.      r_BooksSearch2 = zoho.inventory.getRecords("invoices",v_BooksOrgID,m_SearchCriteria,"joel_inventory")
  892.      for each  r_Invoice in r_BooksSearch2.get("invoices") 
  893.      { 
  894.          if(!isNull(v_BooksSoReference) && r_Invoice.get("reference_number") == v_BooksSoReference) 
  895.          { 
  896.              v_BooksInvoiceID = r_Invoice.get("invoice_id").toLong()
  897.              v_InvoiceID = v_BooksInvoiceID; 
  898.              v_InvoiceRef = r_Invoice.get("invoice_number")
  899.          } 
  900.      } 
  901.      info "ZB Invoice Search: " + v_BooksInvoiceID; 
  902.      l_DebugMessages.add("ZB Invoice Search: " + v_BooksInvoiceID)
  903.      // 
  904.      // create invoice if not exists 
  905.      if(v_BooksInvoiceID == 0 && v_BooksSoID != 0) 
  906.      { 
  907.          // 
  908.          v_InvoiceID = 0
  909.          v_InvoiceRef = ""
  910.          v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/fromsalesorder?organization_id=" + v_BooksOrgID + "&salesorder_id=" + v_BooksSoID; 
  911.          r_CreateInvoice = invokeUrl 
  912.          [ 
  913.              url :v_Endpoint 
  914.              type :POST 
  915.              connection:"joel_inventory" 
  916.          ]
  917.          info "INVOICE FROM SO RESPONSE: "
  918.          info r_CreateInvoice.get("message")
  919.          l_DebugMessages.add("INVOICE FROM SO RESPONSE: " + r_CreateInvoice.get("message"))
  920.          // 
  921.          if(!isnull(r_CreateInvoice.get("invoice"))) 
  922.          { 
  923.              if(!isnull(r_CreateInvoice.get("invoice").get("invoice_id"))) 
  924.              { 
  925.                  v_InvoiceID = r_CreateInvoice.get("invoice").get("invoice_id").toLong()
  926.                  v_BooksInvoiceID = v_InvoiceID; 
  927.                  v_InvoiceRef = r_CreateInvoice.get("invoice").get("invoice_number")
  928.              } 
  929.          } 
  930.      } 
  931.      else 
  932.      { 
  933.          info "INVOICE REUSED:" + v_InvoiceRef; 
  934.          l_DebugMessages.add("INVOICE REUSED:" + v_InvoiceRef)
  935.      } 
  936.      // 
  937.      // mark invoice as sent 
  938.      if(v_BooksInvoiceID != 0) 
  939.      { 
  940.          v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/" + v_BooksInvoiceID + "/status/sent?organization_id=" + v_BooksOrgID; 
  941.          r_SendInvoice = invokeUrl 
  942.          [ 
  943.              url :v_Endpoint 
  944.              type :POST 
  945.              connection:"joel_inventory" 
  946.          ]
  947.          info "INVOICE SENT RESPONSE:"
  948.          info r_SendInvoice.get("message")
  949.          l_DebugMessages.add("INVOICE SENT RESPONSE: " + r_SendInvoice.get("message"))
  950.      } 
  951.      // 
  952.      // cancel invoice if order cancelled 
  953.      if(b_OrderCancelled && v_BooksInvoiceID != 0) 
  954.      { 
  955.          v_Endpoint = "https://inventory.zoho.eu/api/v1/invoices/" + v_BooksInvoiceID + "/status/void?organization_id=" + v_BooksOrgID; 
  956.          r_CancelInvoice = invokeUrl 
  957.          [ 
  958.              url :v_Endpoint 
  959.              type :POST 
  960.              connection:"joel_inventory" 
  961.          ]
  962.          info "INVOICE void RESPONSE:"
  963.          info r_CancelInvoice.get("message")
  964.          l_DebugMessages.add("INVOICE void RESPONSE: " + r_CancelInvoice.get("message"))
  965.      } 
  966.      info "InvoiceID: " + v_BooksInvoiceID; 
  967.      info "InvoiceRef: " + v_InvoiceRef; 
  968.      info "-----------------------"; 
  969.      l_DebugMessages.add("InvoiceID: " + v_BooksInvoiceID)
  970.      l_DebugMessages.add("InvoiceRef: " + v_InvoiceRef)
  971.      // 
  972.      // search to see if package already generated 
  973.      v_BooksPackageID = 0
  974.      v_BooksShipmentID = 0
  975.      m_SearchCriteria = Map()
  976.      m_SearchCriteria.put("salesorder_number",v_BooksSoReference)
  977.      r_BooksSearch3 = zoho.inventory.getRecords("packages",v_BooksOrgID,m_SearchCriteria,"joel_inventory")
  978.      for each  r_Package in r_BooksSearch3.get("packages") 
  979.      { 
  980.          if(r_Package.get("salesorder_number") == v_BooksSoReference) 
  981.          { 
  982.              v_BooksPackageID = r_Package.get("package_id").toLong()
  983.              v_BooksShipmentID = if(isBlank(r_Package.get("shipment_id")),0,r_Package.get("shipment_id")).toLong()
  984.          } 
  985.      } 
  986.      info "ZB Package Search: " + v_BooksInvoiceID; 
  987.      info "ZB Shipment Search: " + v_BooksShipmentID; 
  988.      info "Transaction Hold Status: " + v_TransactionPaymentHoldStatus; 
  989.      l_DebugMessages.add("ZB Package Search: " + v_BooksInvoiceID)
  990.      l_DebugMessages.add("ZB Shipment Search: " + v_BooksShipmentID)
  991.      l_DebugMessages.add("Transaction Hold Status: " + v_TransactionPaymentHoldStatus)
  992.      // 
  993.      // create package 
  994.      v_BooksPackageNumber = ""
  995.      if(v_BooksSoID.toLong() != 0 && v_BooksPackageID.toLong() == 0 && v_TransactionPaymentHoldStatus == "None" && !b_OrderCancelled) 
  996.      { 
  997.          // 
  998.          l_PackageIDs = List()
  999.          m_BooksPackage = Map()
  1000.          m_BooksPackage.put("salesorder_id",v_BooksSoID)
  1001.          m_BooksPackage.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"))
  1002.          m_BooksPackage.put("line_items",l_PackageLineItems)
  1003.          r_CreatePackage = zoho.inventory.createRecord("packages",v_BooksOrgID,m_BooksPackage,"joel_inventory")
  1004.          info "PACKAGE CREATE RESPONSE:"
  1005.          info r_CreatePackage.get("message")
  1006.          l_DebugMessages.add("PACKAGE CREATE RESPONSE: " + r_CreatePackage.get("message"))
  1007.          // 
  1008.          if(!isnull(r_CreatePackage.get("package"))) 
  1009.          { 
  1010.              if(!isnull(r_CreatePackage.get("package").get("package_id"))) 
  1011.              { 
  1012.                  v_BooksPackageID = r_CreatePackage.get("package").get("package_id")
  1013.                  v_BooksPackageNumber = r_CreatePackage.get("package").get("package_number")
  1014.              } 
  1015.          } 
  1016.      } 
  1017.      // 
  1018.      // delete package if exists and order cancelled 
  1019.      if(b_OrderCancelled && v_BooksPackageID.toLong() != 0) 
  1020.      { 
  1021.          v_Endpoint = "https://inventory.zoho.eu/api/v1/packages/" + v_BooksPackageID + "?organization_id=" + v_BooksOrgID; 
  1022.          r_DeletePackage = invokeUrl 
  1023.          [ 
  1024.              url :v_Endpoint 
  1025.              type :DELETE 
  1026.              connection:"joel_inventory" 
  1027.          ]
  1028.          info "PACKAGE DELETE RESPONSE:"
  1029.          info r_DeletePackage.get("message")
  1030.          l_DebugMessages.add("PACKAGE DELETE RESPONSE: " + r_DeletePackage.get("message"))
  1031.      } 
  1032.      info "-----------------------"; 
  1033.      // 
  1034.      // record this payment 
  1035.      //info l_eBayPaymentRefs; 
  1036.      for each  r_eBayPaymentRef in l_eBayPaymentRefs 
  1037.      { 
  1038.          if(v_InvoiceID != 0 && v_TransactionPaymentHoldStatus == "None" && !b_OrderCancelled) 
  1039.          { 
  1040.              // 
  1041.              // search to see if payment already recorded 
  1042.              v_BooksPaymentID = 0
  1043.              m_SearchCriteria = Map()
  1044.              m_SearchCriteria.put("reference_number",r_eBayPaymentRef.get("ReferenceID"))
  1045.              r_BooksSearch4 = zoho.inventory.getRecords("customerpayments",v_BooksOrgID,m_SearchCriteria,"joel_inventory")
  1046.              for each  r_Payment in r_BooksSearch4.get("customerpayments") 
  1047.              { 
  1048.                  if(r_Payment.get("reference_number") == r_eBayPaymentRef.get("ReferenceID")) 
  1049.                  { 
  1050.                      v_BooksPaymentID = r_Payment.get("payment_id").toLong()
  1051.                  } 
  1052.              } 
  1053.              info "ZB Payment Search: " + v_BooksPaymentID; 
  1054.              l_DebugMessages.add("ZB Payment Search: " + v_BooksPaymentID)
  1055.              // 
  1056.              // if not found, then create one 
  1057.              if(v_BooksPaymentID == 0) 
  1058.              { 
  1059.                  m_BooksPayment = Map()
  1060.                  m_BooksPayment.put("customer_id",v_BooksCustomerID.toString())
  1061.                  m_BooksPayment.put("payment_mode",v_TransactionPaymentMethod)
  1062.                  m_BooksPayment.put("amount",r_eBayPaymentRef.get("Amount"))
  1063.                  m_BooksPayment.put("date",r_eBayPaymentRef.get("DateTime").toTime().toString("yyyy-MM-dd","Europe/London"))
  1064.                  m_BooksPayment.put("reference_number",r_eBayPaymentRef.get("ReferenceID"))
  1065.                  l_Invoices = List()
  1066.                  m_Invoice = Map()
  1067.                  m_Invoice.put("invoice_id",v_BooksInvoiceID)
  1068.                  m_Invoice.put("amount_applied",r_eBayPaymentRef.get("Amount"))
  1069.                  m_Invoice.put("tax_amount_withheld",0)
  1070.                  l_Invoices.add(m_Invoice)
  1071.                  m_BooksPayment.put("invoices",l_Invoices)
  1072.                  // 
  1073.                  // create the payment record 
  1074.                  r_CreatePayment = zoho.inventory.createRecord("customerpayments",v_BooksOrgID,m_BooksPayment,"joel_inventory")
  1075.                  info "PAYMENT RESPONSE:" + r_eBayPaymentRef.get("ReferenceID")
  1076.                  info r_CreatePayment.get("message")
  1077.                  l_DebugMessages.add("PAYMENT RESPONSE:" + r_eBayPaymentRef.get("ReferenceID") + ": " + r_CreatePayment.get("message"))
  1078.              } 
  1079.          } 
  1080.      } 
  1081.      // 
  1082.      // check if invoice fully paid now 
  1083.      v_CurrentInvoiceStatus = ""
  1084.      r_CurrentInvoice = zoho.inventory.getRecordsByID("invoices",v_BooksOrgID,v_InvoiceID,"joel_inventory")
  1085.      if(!isnull(r_CurrentInvoice.get("invoice"))) 
  1086.      { 
  1087.          v_CurrentInvoiceStatus = r_CurrentInvoice.get("invoice").get("status")
  1088.      } 
  1089.      // 
  1090.      // create/update shipment if date shipped is specified and full payment received 
  1091.      l_DebugMessages.add("SHIPPING CREATE CONDITION: " + v_BooksPackageID + "::" + v_BooksSoID + "::" + v_Order_DateShipped + "::" + v_CurrentInvoiceStatus + "::" + b_OrderCancelled)
  1092.      if(v_BooksPackageID != 0 && v_BooksSoID != 0 && !isNull(v_Order_DateShipped) && v_CurrentInvoiceStatus == "paid" && !b_OrderCancelled) 
  1093.      { 
  1094.          // 
  1095.          v_ShipmentID = 0
  1096.          m_BooksShipment = Map()
  1097.          m_BooksShipment.put("shipment_number","SH-" + v_BooksSoReference.getSuffix("-"))
  1098.          m_BooksShipment.put("date",v_Order_DateCreated.toString("yyyy-MM-dd"))
  1099.          m_BooksShipment.put("reference_number",v_BooksSoReference)
  1100.          m_BooksShipment.put("delivery_method",v_ShippingCarrier)
  1101.          m_BooksShipment.put("tracking_number",v_ShippingTrackingNumber)
  1102.          l_DebugMessages.add("SHIPPING CREATE REQUEST: " + m_BooksShipment)
  1103.          // 
  1104.          if(v_BooksShipmentID == 0) 
  1105.          { 
  1106.              v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders?organization_id=" + v_BooksOrgID + "&package_ids=" + v_BooksPackageID + "&salesorder_id=" + v_BooksSoID; 
  1107.              r_CreateShipment = invokeUrl 
  1108.              [ 
  1109.                  url :v_Endpoint 
  1110.                  type :POST 
  1111.                  parameters:m_BooksShipment.toString() 
  1112.                  connection:"joel_inventory" 
  1113.              ]
  1114.              info "SHIPPING CREATE RESPONSE:"
  1115.              info r_CreateShipment.get("message")
  1116.              l_DebugMessages.add("SHIPPING CREATE RESPONSE: " + r_CreateShipment.get("message"))
  1117.              // 
  1118.              if(!isnull(r_CreateShipment.get("shipmentorder"))) 
  1119.              { 
  1120.                  if(!isnull(r_CreateShipment.get("shipmentorder").get("shipment_id"))) 
  1121.                  { 
  1122.                      v_ShipmentID = r_CreateShipment.get("shipmentorder").get("shipment_id").toLong()
  1123.                  } 
  1124.              } 
  1125.          } 
  1126.          else 
  1127.          { 
  1128.              v_ShipmentID = v_BooksShipmentID; 
  1129.              v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_BooksShipmentID + "?organization_id=" + v_BooksOrgID + "&package_ids=" + v_BooksPackageID + "&salesorder_id=" + v_BooksSoID; 
  1130.              r_UpdateShipment = invokeUrl 
  1131.              [ 
  1132.                  url :v_Endpoint 
  1133.                  type :PUT 
  1134.                  parameters:m_BooksShipment.toString() 
  1135.                  connection:"joel_inventory" 
  1136.              ]
  1137.              info "SHIPPING UPDATE RESPONSE:"
  1138.              info r_UpdateShipment.get("message")
  1139.              l_DebugMessages.add("SHIPPING UPDATE RESPONSE: " + r_UpdateShipment.get("message"))
  1140.          } 
  1141.          // 
  1142.          // check delivery status based on tracking number 
  1143.          if(!isNull(v_ShippingTrackingNumber)) 
  1144.          { 
  1145.              v_DeliveryStatus = standalone.fn_DHL_CheckShipmentStatus(v_ShippingTrackingNumber)
  1146.              l_DebugMessages.add("DHL API Shipment Status: " + v_DeliveryStatus)
  1147.              if(v_DeliveryStatus.equalsIgnoreCase("delivered")) 
  1148.              { 
  1149.                  // 
  1150.                  // mark as delivered 
  1151.                  if(v_ShipmentID != 0) 
  1152.                  { 
  1153.                      v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_ShipmentID + "/status/delivered?organization_id=" + v_BooksOrgID; 
  1154.                      r_UpdateShipment = invokeUrl 
  1155.                      [ 
  1156.                          url :v_Endpoint 
  1157.                          type :POST 
  1158.                          connection:"joel_inventory" 
  1159.                      ]
  1160.                      info "SHIPPING DELIVERED RESPONSE:"
  1161.                      info r_UpdateShipment.get("message")
  1162.                      l_DebugMessages.add("SHIPPING DELIVERED RESPONSE: " + r_UpdateShipment.get("message"))
  1163.                  } 
  1164.              } 
  1165.          } 
  1166.      } 
  1167.      // 
  1168.      // delete shipment order if exists and order cancelled 
  1169.      if(b_OrderCancelled && v_BooksShipmentID.toLong() != 0) 
  1170.      { 
  1171.          v_Endpoint = "https://inventory.zoho.eu/api/v1/shipmentorders/" + v_BooksShipmentID + "?organization_id=" + v_BooksOrgID; 
  1172.          r_DeleteShipment = invokeUrl 
  1173.          [ 
  1174.              url :v_Endpoint 
  1175.              type :DELETE 
  1176.              connection:"joel_inventory" 
  1177.          ]
  1178.          info "SHIPMENT DELETE RESPONSE:"
  1179.          info r_DeleteShipment.get("message")
  1180.          l_DebugMessages.add("SHIPMENT DELETE RESPONSE: " + r_DeleteShipment.get("message"))
  1181.      } 
  1182.  } 
  1183.  // 
  1184.  if(b_DebugMode) 
  1185.  { 
  1186.      /* 
  1187.      l_DebugMessages.add("That's all folks!")
  1188.      v_DebugMessages = l_DebugMessages.toString("<hr />")
  1189.      sendmail 
  1190.      [ 
  1191.          from :zoho.adminuserid 
  1192.          to :"errors+This email address is being protected from spambots. You need JavaScript enabled to view it.
  1193.          subject :"DEBUG: eBay Order: " + p_eBayOrderRef + " :: SO Order: " + v_BooksSoReference 
  1194.          message :v_DebugMessages 
  1195.      ] 
  1196.      */ 
  1197.  } 
  1198.  return ""

Postrequisites:
  • Setup a notification on the eBay instance to notify this webhook whenever an order is created or modified.
  • Setup a scheduled function in Zoho Inventory to re-query an order as to whether it has been shipped.
  • Setup a scheduled function to query DHL (the courier in this case) to check if an item has been delivered.

Error(s) Encountered
  • Invalid value passed for package_ids
  • Invalid value passed for salesorder_id
  • {"code":57,"message":"You are not authorized to perform this operation"}
  • {"code":36023,"message":"Sorry, you are not allowed to update or delete a product which is marked as invoiced."}
  • {"code":9205,"message":"You do not have sufficient stock to create out-transactions for the item (...). Please restock the item and try again.","error_info":["1234756000001234567"]}
  • {"code":2051,"message":"Please enter a positive opening stock rate."}
For the above errors... use invokeURL more than the short-code. Ensure you control stock at the point of creating the item or updating it.
Category: Zoho :: Article: 846

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com

Related Articles

Joes Revolver Map

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF
© 2024 Joel Lipman .com. All Rights Reserved.