ZohoCRM Webhook: Create ZohoInventory Records from an eBay order

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 :"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

Please publish modules in offcanvas position.