For Zoho services only


I'm currently part of a wider delivery team at Ascent Business Solutions, recognised as a leading Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions support organisations with everything from targeted technical fixes through to full Zoho CRM implementations and long-term platform adoption. Working as a team rather than a one-person consultancy allows projects to move forward consistently, with access to the right skills at each stage.

The team I manage specialises in API integrations between Zoho and third-party finance and commerce platforms such as Xero, Shopify, WooCommerce, and eBay. Much of our work involves solving integration challenges that fall outside standard documentation, supporting new ideas, new sectors, and evolving business models.

Success is measured through practical outcomes and return on investment, ranging from scaling small operations into high-turnover businesses to delivering rapid gains through online payments, automation, and streamlined digital workflows.

If you are looking for structured Zoho expertise backed by an established consultancy, you can contact Ascent Business Solutions on 0121 392 8140 (UK), email info@ascentbusiness.co.uk, or visit https://www.ascentbusiness.co.uk.
Zoho Books/Inventory: Trigger a workflow when an invoice has been paid

Zoho Books/Inventory: Trigger a workflow when an invoice has been paid

What?
An article on something that has taken me several days to get working: Get Zoho Books or Zoho Inventory that when an invoice is marked as paid, update 2 custom fields with the Payment Method, and the Payment Date.

Why?
This was requested by a customer and the problem happened in that the workflow would simply not trigger when the invoice was paid.  The customer had added 3 custom fields: Payment Method, Payment Date, and Last Four Digits.  They needed these because sometimes their customers needed to know what card they had paid with (if paid by card).  The customer wanted payment method and payment date to appear on the invoice.

How?
This sounds rather straightforward, write a function that given an invoice, checks the customer payments and updates the custom fields.  Executing the function this worked as expected.  Now put it on a workflow where the settings were:

  • When an invoice is "Created or Edited"
  • Execute the workflow when "When any field is updated"
  • Filter the triggers when Status is "Paid"
  • Just once or every time is "Everytime"
  • Actions is a custom function called "fn_invoice_updatepaymentmethod"
But this wouldn't work despite being as per the documentation (see sources below).

Here's the code for our function; note it is within ZohoBooks and not ZohoInventory (moved) and the connection "zbooks" has a fullaccess scope:
copyraw
//
// initialize
v_PaymentMode = "";
v_PaymentDate = null;
v_Last4Digits = "";
r_UpdateInvoice = Map();
//
// evaluate
v_InvoiceID = invoice.get("invoice_id");
v_CustomerID = invoice.get("customer_id");
v_BooksOrgID = organization.get("organization_id");
v_InvoiceRef = invoice.get("invoice_number");
v_BalanceDue = ifnull(invoice.get("balance"),0.0);
//
// search payments
m_SearchCriteria = Map();
m_SearchCriteria.put("customer_id",v_CustomerID);
m_SearchCriteria.put("sort_order","A");
m_SearchCriteria.put("sort_by","payment_number");
//
// fetch all payments against this customer in order of payment reference (oldest to most recent)
r_SearchResults = zoho.books.getRecords("customerpayments",v_BooksOrgID,m_SearchCriteria,"zbooks");
if(!isnull(r_SearchResults.get("customerpayments")))
{
	l_SearchResults = r_SearchResults.get("customerpayments");
	for each  r_Result in l_SearchResults
	{
		if(!isnull(r_Result.get("payment_id")))
		{
			// 
			// invalid searches will return 200 results non-matching, so let's double-check
			if(r_Result.get("invoice_numbers").containsIgnoreCase(v_InvoiceRef))
			{
				// retrieve payment mode, amount and date
				v_PaymentMode = ifnull(r_Result.get("payment_mode_formatted"),"");
				v_PaymentDate = r_Result.get("date");
				v_Last4Digits = ifnull(r_Result.get("last_four_digits"),"");
			}
		}
	}
	//
	// if fully paid and payment date is not null
	if(v_BalanceDue.toDecimal() == 0.0 && v_PaymentDate != null)
	{
		m_UpdateInvoice = Map();
		//
		// copy existing custom fields (not required but just in case)
		l_CustomFields = invoice.get("custom_fields");
		l_NewCustomFields = List();
		for each  m_CustomField in l_CustomFields
		{
			// exclude existing custom field payment mode and date
			l_DontCopyFields = {"cf_payment_method","cf_payment_date","cf_last_4_digits"};
			if(!l_DontCopyFields.contains(m_CustomField.get("api_name")))
			{
				l_NewCustomFields.add(m_CustomField);
			}
		}
		// add custom field payment mode
		m_MiniCustomField = Map();
		m_MiniCustomField.put("api_name","cf_payment_method");
		m_MiniCustomField.put("value",v_PaymentMode);
		l_NewCustomFields.add(m_MiniCustomField);
		//
		// add custom field payment date
		m_MiniCustomField = Map();
		m_MiniCustomField.put("api_name","cf_payment_date");
		m_MiniCustomField.put("value",v_PaymentDate);
		l_NewCustomFields.add(m_MiniCustomField);
		//
		// add custom field last 4 digits
		if(v_Last4Digits != "")
		{
			m_MiniCustomField = Map();
			m_MiniCustomField.put("api_name","cf_last_4_digits");
			m_MiniCustomField.put("value",v_Last4Digits);
			l_NewCustomFields.add(m_MiniCustomField);
		}
		//
		m_UpdateInvoice.put("custom_fields",l_NewCustomFields);
		r_UpdateInvoice = zoho.books.updateRecord("invoices",v_BooksOrgID,v_InvoiceID,m_UpdateInvoice,"zbooks");
		info r_UpdateInvoice;
	}
}
//
// send ourselves an email as evidence that the workflow was triggered and ran this function
sendmail
[
	from :zoho.adminuserid
	to :"This email address is being protected from spambots. You need JavaScript enabled to view it."
	subject :"Client Test: ZB: Payment Received for Invoice " + v_InvoiceRef
	message :"Workflow was executed and function ran:Balance: " + v_BalanceDue + "Customer Payments: " + r_SearchResults + "Invoice Update" + r_UpdateInvoice
]
  1.  // 
  2.  // initialize 
  3.  v_PaymentMode = ""
  4.  v_PaymentDate = null
  5.  v_Last4Digits = ""
  6.  r_UpdateInvoice = Map()
  7.  // 
  8.  // evaluate 
  9.  v_InvoiceID = invoice.get("invoice_id")
  10.  v_CustomerID = invoice.get("customer_id")
  11.  v_BooksOrgID = organization.get("organization_id")
  12.  v_InvoiceRef = invoice.get("invoice_number")
  13.  v_BalanceDue = ifnull(invoice.get("balance"),0.0)
  14.  // 
  15.  // search payments 
  16.  m_SearchCriteria = Map()
  17.  m_SearchCriteria.put("customer_id",v_CustomerID)
  18.  m_SearchCriteria.put("sort_order","A")
  19.  m_SearchCriteria.put("sort_by","payment_number")
  20.  // 
  21.  // fetch all payments against this customer in order of payment reference (oldest to most recent) 
  22.  r_SearchResults = zoho.books.getRecords("customerpayments",v_BooksOrgID,m_SearchCriteria,"zbooks")
  23.  if(!isnull(r_SearchResults.get("customerpayments"))) 
  24.  { 
  25.      l_SearchResults = r_SearchResults.get("customerpayments")
  26.      for each  r_Result in l_SearchResults 
  27.      { 
  28.          if(!isnull(r_Result.get("payment_id"))) 
  29.          { 
  30.              // 
  31.              // invalid searches will return 200 results non-matching, so let's double-check 
  32.              if(r_Result.get("invoice_numbers").containsIgnoreCase(v_InvoiceRef)) 
  33.              { 
  34.                  // retrieve payment mode, amount and date 
  35.                  v_PaymentMode = ifnull(r_Result.get("payment_mode_formatted"),"")
  36.                  v_PaymentDate = r_Result.get("date")
  37.                  v_Last4Digits = ifnull(r_Result.get("last_four_digits"),"")
  38.              } 
  39.          } 
  40.      } 
  41.      // 
  42.      // if fully paid and payment date is not null 
  43.      if(v_BalanceDue.toDecimal() == 0.0 && v_PaymentDate != null) 
  44.      { 
  45.          m_UpdateInvoice = Map()
  46.          // 
  47.          // copy existing custom fields (not required but just in case) 
  48.          l_CustomFields = invoice.get("custom_fields")
  49.          l_NewCustomFields = List()
  50.          for each  m_CustomField in l_CustomFields 
  51.          { 
  52.              // exclude existing custom field payment mode and date 
  53.              l_DontCopyFields = {"cf_payment_method","cf_payment_date","cf_last_4_digits"}
  54.              if(!l_DontCopyFields.contains(m_CustomField.get("api_name"))) 
  55.              { 
  56.                  l_NewCustomFields.add(m_CustomField)
  57.              } 
  58.          } 
  59.          // add custom field payment mode 
  60.          m_MiniCustomField = Map()
  61.          m_MiniCustomField.put("api_name","cf_payment_method")
  62.          m_MiniCustomField.put("value",v_PaymentMode)
  63.          l_NewCustomFields.add(m_MiniCustomField)
  64.          // 
  65.          // add custom field payment date 
  66.          m_MiniCustomField = Map()
  67.          m_MiniCustomField.put("api_name","cf_payment_date")
  68.          m_MiniCustomField.put("value",v_PaymentDate)
  69.          l_NewCustomFields.add(m_MiniCustomField)
  70.          // 
  71.          // add custom field last 4 digits 
  72.          if(v_Last4Digits != "") 
  73.          { 
  74.              m_MiniCustomField = Map()
  75.              m_MiniCustomField.put("api_name","cf_last_4_digits")
  76.              m_MiniCustomField.put("value",v_Last4Digits)
  77.              l_NewCustomFields.add(m_MiniCustomField)
  78.          } 
  79.          // 
  80.          m_UpdateInvoice.put("custom_fields",l_NewCustomFields)
  81.          r_UpdateInvoice = zoho.books.updateRecord("invoices",v_BooksOrgID,v_InvoiceID,m_UpdateInvoice,"zbooks")
  82.          info r_UpdateInvoice; 
  83.      } 
  84.  } 
  85.  // 
  86.  // send ourselves an email as evidence that the workflow was triggered and ran this function 
  87.  sendmail 
  88.  [ 
  89.      from :zoho.adminuserid 
  90.      to :"This email address is being protected from spambots. You need JavaScript enabled to view it." 
  91.      subject :"Client Test: ZB: Payment Received for Invoice " + v_InvoiceRef 
  92.      message :"Workflow was executed and function ran:Balance: " + v_BalanceDue + "Customer Payments: " + r_SearchResults + "Invoice Update" + r_UpdateInvoice 
  93.  ] 

The trick is all in the workflow settings. This worked for us but may not work for you or may be already working for you without this issue. By changing the workflow to execute not "When any field is upddated" but "When any selected field is updated" and then selecting the fields "Status", "Balance", and "Notes". (not sure which one fixed it but I now add all three):
Zoho Books - Trigger Workflow When An Invoice Is Paid - Filter Triggers

Error(s):
  • Workflow did not trigger and did not receive even the email included in my custom function.

Debug Method(s):
  • Go to Settings > Automations > Custom Functions > Select the function > Execute the function against a paid invoice - check the fields updated.
  • Go to Sales > Invoices > Select an invoice > Click on "Comments & History"
  • Go into Settings > Automations > Workflow Logs > Custom Functions: check if/when they occurred and in relation to what invoice.

Source(s):
Category: Zoho Books :: Article: 351

Credit where Credit is Due:


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

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

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

Kind Regards,

Joel Lipman
www.joellipman.com