For Zoho Services only:

I'm actually part of a bigger team at Ascent Business Solutions where we have support technicians and project consultants. Support is for smaller technical fixes but this can include developments, reports or integrations; depending on the size of the task. Projects are for more time-consuming developments such as revamps of the Zoho Suite of apps or on-site training. The advantage of a team is that if I am out-of-office for a day or so, there is always someone at Ascent Business Solutions who can deal with any queries/issues you may have.

Our support rates can be found and purchased at A support bundle doesn't have an expiry date. So whether we can do what you want within the bundle and a year later need further support, if there are minutes left on the bundle then there is no additional charge.

Our project rates for bigger developments can be found at and will involve a dedicated project consultant along with developers who will hold your hand through the development process.

If you want help building a solution for one of the Zoho Apps in the Zoho Suite, contact us on 0121 392 8140 (UK) or by email at You can also visit our website at

I regularly build and specialize in 2-way API integrations for Xero, Shopify and eBay.

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

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.

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.

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:
// 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();
// 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");
	l_SearchResults = r_SearchResults.get("customerpayments");
	for each  r_Result in l_SearchResults
			// invalid searches will return 200 results non-matching, so let's double-check
				// 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"};
		// add custom field payment mode
		m_MiniCustomField = Map();
		// add custom field payment date
		m_MiniCustomField = Map();
		// add custom field last 4 digits
		if(v_Last4Digits != "")
			m_MiniCustomField = Map();
		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
	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

  • 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.

Category: Zoho :: Article: 819

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: The information on this website is provided without warranty and any content is merely the opinion of the author. Please try to test in development environments prior to adapting them to your production environments. The articles are written in good faith and, at the time of print, are working examples used 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

Related Articles

Joes Revolver Map


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

Donate & Support

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

Donate to Joel Lipman via PayPal

Donate to Joel Lipman with Bitcoin - Valid till 8 May 2022 bc1qjtp4l4ra452wzvuk9a45yfj82zkahsyy2z379y
© 2023 Joel Lipman .com. All Rights Reserved.