Zoho Books / Inventory: Get Item Rate from a Price Book/List
- Category: Zoho
- Hits: 23861
A quick article on how to get the pricebook entry using Zoho Deluge for a specific product in your ZohoBooks or ZohoInventory instance.
Why?
This took me the best part of an hour to determine by going through forum posts from 7 years to 2 years ago. The following will work in May 2024 following the API domain change.
The use-case is that the customer wants the item/product rate taken from the item record, if a pricebook is specified and the item exists within it, then it takes the rate from the pricelist. Note that when I refer to pricebook, this is also referred to as the pricelist... and vice-versa.
And in this use-case, my client has added an incredible number of price books/ price lists and then has in excess of 20k items. Previously, the function that would need to get the price book rate post saving a record in Zoho Deluge would fail because it was making too many function statements.
How?
I have this code triggered in a workflow when the sales order is created to do some further calculations based on a surcharge rate held in a custom module. The custom module could only be created in Zoho Books at time of print but Zoho Inventory is where our workflow and code is sitting right now.
ZohoBooks: Stripe Terminal Integration
- Category: Zoho
- Hits: 29398
A quick article on some code added to a button in ZohoBooks off the invoice module to initiate your Stripe terminal to take a payment.
Why?
Just to make it easy on the staff at a counter or on the phone. They bring up the invoie in ZohoBooks, click on the button, and the Stripe terminal will ask for the amount on the invoice.
Well almost. We've gone the extra step in that we added a custom field that can override the full balance, to allow partial payments such as a deposit or instalment.
How?
I won't go in to how to create a button in ZohoBooks but you simply add it to the invoice and then when it prompts for some code, you give it the snippet below.
The Magic
You would create a button for each terminal
/* ******************************************************************************* Function: Map Take_Payment( Map invoice, Map organization, Map user) Label: Take Payment Trigger: On button click Purpose: Preps stripe terminal to take payment for balance of invoice. Inputs: invoice Outputs: - Date Created: 2023-02-24 - Initial release - Reads Books Invoice and sends the amount to the reader for a payment atempt Date Modified: 2023-02-24 (Joel Lipman) - If custom field "Amount To Be Taken" is not greater than zero, then defaults to balance due of invoice. More Information: TEST PAYMENT SCENARIOS WITH PHYSICAL TEST CARD // Send in as payment endings: eg. $100.00 == payment approved 00 Payment Approved 01 Payment Declined // call issuer code 05 Payment Declined Generic 55 Payment Declined Incorrect Pin 65 Payment Declined withdrawal_count_limit_exceeded 75 Pin try exceeded ******************************************************************************* */ v_BooksOrgID = organization.get("organization_id"); // // some Stripe variables (add your own here) v_StripeTerminalID = "tmr_ABCDEFGabcdefg"; v_StripeCustomerKey = "sk_live_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; V_StripeLocationsEndpoint = " https://api.stripe.com/v1/terminal/locations"; // // set Stripe header m_Headers = Map(); m_Headers.put("Authorization","Bearer " + v_StripeCustomerKey); // // default to balance due on this Invoice v_AmountToPay = ifnull(invoice.get("balance"),0); // // Get custom Amount to Pay from Invoice l_CustomFields = invoice.get("custom_fields"); if(l_CustomFields.size() > 0) { for each m_CustomField in l_CustomFields { if(m_CustomField.get("label") == "Amount To Be Taken") { if(m_CustomField.get("value") > 0) { v_AmountToPay = m_CustomField.get("value"); } } } } // // format to Stripe amount v_AmountToPay = v_AmountToPay.truncate(2); v_StripeAmount = v_AmountToPay * 100; v_StripeAmount = v_StripeAmount.floor(); v_StripeAmount = v_StripeAmount.toNumber(); info v_StripeAmount; // // Create payment intent in Stripe v_PaymentIntentEndpoint = "https://api.stripe.com/v1/payment_intents"; m_Params = Map(); m_Params.put("amount",v_StripeAmount); m_Params.put("currency","gbp"); //m_Params.put("automatic_payment_methods[enabled]", false); m_Params.put("payment_method_types[]","card_present"); m_Params.put("capture_method","manual"); v_DescriptionString = "IN: " + invoice.get("invoice_number") + " ID: " + invoice.get("invoice_id"); m_Params.put("description",v_DescriptionString); // Later Add Code to "customer_id": "123456700000001234567", get customer id then Email v_BooksCustomerID = invoice.get("customer_id"); r_CustomerDetails = zoho.books.getRecordsByID("contacts",v_BooksOrgID,v_BooksCustomerID,"zbooks"); v_CustomerCheckCode = r_CustomerDetails.get("code"); if(v_CustomerCheckCode == 0) { m_ContactDetails = r_CustomerDetails.get("contact"); if(m_ContactDetails != null) { m_Params.put("receipt_email",m_ContactDetails.get("email")); } } r_CreatePaymentIntent = invokeurl [ url :v_PaymentIntentEndpoint type :POST parameters:m_Params headers:m_Headers ]; info "Payment Intent Create"; info r_CreatePaymentIntent; // v_CheckObject = ifnull(r_CreatePaymentIntent.get("object"),"-"); v_CheckAmount = ifnull(r_CreatePaymentIntent.get("amount"),"-"); // // Process Payment Intent if(v_CheckObject == "payment_intent") { v_PaymentIntentID = r_CreatePaymentIntent.get("id"); info "Payment Intent Created Successfuly!!! ID: "; info v_PaymentIntentID; // // Hand Off Payment Intent to Reader v_ReaderHandOffEndpoint = "https://api.stripe.com/v1/terminal/readers/" + v_StripeTerminalID + "/process_payment_intent"; m_PaymentHandOffParams = Map(); m_PaymentHandOffParams.put("payment_intent",v_PaymentIntentID); r_ReaderPaymentHandOff = invokeurl [ url :v_ReaderHandOffEndpoint type :POST parameters:m_PaymentHandOffParams headers:m_Headers ]; info r_ReaderPaymentHandOff; } return r_ReaderPaymentHandOff;
The incoming webhook
You now need to receive the Stripe webhook when it comes back into ZohoBooks to record it against the invoice. Note how we added the invoice reference and Zoho ID in the description of the payment capture in our previous bit of code:
/* ******************************************************************************* Function: Map stripe_terminal_payment( Map invoice, Map organization, Map user) Label: stripe_terminal_payment Trigger: Incoming Webhook Purpose: Listens for stripe terminal payments. OAuth URL: https://www.zohoapis.com/books/v3/settings/incomingwebhooks/iw_stripe_terminal_payment/execute?auth_type=oauth Inputs: invoice Outputs: - Date Created: 2023-03-23 - Initial release - been successfully proccessed by the physical reader Date Modified: 2024-05-21 (Joel Lipman) - Revamp of code as per best practices - Correct error: Check and update the code in line 74 as there is a Exception : Value is empty and 'get' function cannot be applied More Information: Navigate to payments, then find pending webhook response event, terminal.reader.action_succeeded, then view event details ******************************************************************************* */ // // initialize m_Blank = Map(); m_Response = Map(); v_BooksOrgID = organization.get("organization_id"); // // Stripe API Key (add your own here) v_StripeCustomerKey = "sk_live_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // // set Stripe header m_Headers = Map(); m_Headers.put("Authorization","Bearer " + v_StripeCustomerKey); // // capture response webhook m_Webhook = Map(); m_Webhook.put("data",body.get("data")); m_Webhook.put("type",body.get("type")); // // Check Type m_Data = ifnull(m_Webhook.get("data"),m_Blank); v_Type = ifnull(m_Webhook.get("type"),"-"); if(v_Type == "terminal.reader.action_succeeded") { // Get Payment Intent that needs to be Captured m_Object = m_Data.get("object"); m_Action = m_Object.get("action"); m_ProcessPaymentIntent = m_Action.get("process_payment_intent"); v_PaymentIntentID = m_ProcessPaymentIntent.get("payment_intent"); // // Retrieve details on the Payment Intent v_PaymentIntentEndpoint = "https://api.stripe.com/v1/payment_intents/" + v_PaymentIntentID; r_PaymentIntentDetails = invokeurl [ url :v_PaymentIntentEndpoint type :GET headers:m_Headers ]; // // get amount (used to capture payment intent) v_Amount = ifnull(r_PaymentIntentDetails.get("amount"),0); v_AmountReceived = ifnull(r_PaymentIntentDetails.get("amount_received"),0); v_AmountCapturable = ifnull(r_PaymentIntentDetails.get("amount_capturable"),0); v_CaptureIntentEndpoint = "https://api.stripe.com/v1/payment_intents/" + v_PaymentIntentID + "/capture"; m_CaptureParams = Map(); m_CaptureParams.put("amount_to_capture",v_AmountCapturable); r_CapturePayment = invokeurl [ url :v_CaptureIntentEndpoint type :POST parameters:m_CaptureParams headers:m_Headers ]; // // get card details (we need to store last 4 digits) v_StripeReference = ""; v_Last4Digits = ""; v_ZB_InvoiceID = 0; m_Charges = ifnull(r_PaymentIntentDetails.get("charges"),m_Blank); l_Data = ifnull(m_Charges.get("data"),{}); for each m_Data in l_Data { if(m_Data.get("id") != null) { v_StripeReference = m_Data.get("id"); } if(m_Data.get("payment_method_details") != null) { m_CardPresent = ifnull(m_Data.get("payment_method_details").get("card_present"),m_Blank); v_Last4Digits = ifnull(m_CardPresent.get("last4"),""); } if(m_Data.get("description") != null) { v_ChargeDescription = ifnull(m_Data.get("description"),""); v_ZB_InvoiceID = v_ChargeDescription.getSuffix("ID: "); v_ZB_InvoiceID = if(isNumber(v_ZB_InvoiceID),v_ZB_InvoiceID,0).toLong(); } } // // create payment record if(v_ZB_InvoiceID != 0) { // // get ZohoBooks nominal account for Stripe v_NominalAccountID = ""; r_ChartOfAccounts = invokeurl [ url :"https://www.zohoapis.com/books/v3/chartofaccounts?organization_id=" + v_BooksOrgID type :GET connection:"zbooks" ]; if(r_ChartOfAccounts.get("chartofaccounts") != null) { for each m_NomAccount in r_ChartOfAccounts.get("chartofaccounts") { if(m_NomAccount.get("account_name").equalsIgnoreCase("Stripe Clearing")) { v_NominalAccountID = m_NomAccount.get("account_id"); } } } // // retrieve invoice details from ZohoBooks r_InvoiceDetails = zoho.books.getRecordsByID("invoices",v_BooksOrgID,v_ZB_InvoiceID,"zbooks"); m_Invoice = ifnull(r_InvoiceDetails.get("invoice"),m_Blank); if(m_Invoice.get("customer_id") != null) { m_CreatePayment = Map(); m_CreatePayment.put("customer_id",m_Invoice.get("customer_id")); m_CreatePayment.put("payment_mode","In Person - Card"); m_CreatePayment.put("amount",v_AmountReceived / 100); m_CreatePayment.put("date",zoho.currentdate.toString("yyyy-MM-dd")); // l_Invoices = List(); m_ThisInvoice = Map(); m_ThisInvoice.put("invoice_id",v_ZB_InvoiceID.toString()); m_ThisInvoice.put("amount_applied",v_AmountReceived / 100); l_Invoices.add(m_ThisInvoice); m_CreatePayment.put("invoices",l_Invoices); m_CreatePayment.put("invoice_id",v_ZB_InvoiceID.toString()); m_CreatePayment.put("amount_applied",v_AmountReceived / 100); // v_PaymentRef = if(v_StripeReference=="", m_Invoice.get("invoice_number"), v_StripeReference); m_CreatePayment.put("reference_number",v_PaymentRef); m_CreatePayment.put("account_id",v_NominalAccountID); //info m_CreatePayment; // r_CreatePayment = zoho.books.createRecord("customerpayments",v_BooksOrgID,m_CreatePayment,"zbooks"); //info r_CreatePayment; if(r_CreatePayment.get("message") != null) { if(r_CreatePayment.get("message").contains("payment has been created")) { // // update the invoice (request by client to store last 4 digits on invoice) l_CustomFields = List(); m_CustomField = Map(); m_CustomField.put("api_name","cf_last_4_digits"); m_CustomField.put("value",v_Last4Digits); l_CustomFields.add(m_CustomField); m_UpdateInvoice = Map(); m_UpdateInvoice.put("custom_fields",l_CustomFields); r_UpdateInvoice = zoho.books.updateRecord("Invoices",v_BooksOrgID,v_ZB_InvoiceID,m_UpdateInvoice,"zbooks"); } } } } } m_Response.put("message",r_CreatePayment.get("message")); m_Response.put("code",0); return m_Response;
Source(s):
- Stripe Dashboard - Developers - API Keys
- Stripe Docs - Decline codes - Learn about decline codes and how to resolve them when a charge fails.
ZohoCRM: Daily Follow Up and Remind Record Owner to Convert Lead
- Category: Zoho
- Hits: 39130
An article to ensure I never spend this long on such a request again. The brief is: "Follow up when a lead is created and not converted within 1 day send an email and notification to sales person everyday for 3 days then escalate to a specified user".
Why?
Sorry Zoho! I tried using the interface to set up a workflow or any other mechanisms within ZohoCRM that allowed me to set up a daily reminder to the lead record owner but to no avail. An assignment rule wouldn't work in this case as it had to be the lead owner (there is no owner at assignment rule stage). A workflow email notification doesn't do the popup on the CRM as well. A workflow notify by email didn't give the option to remind daily... only weekly or monthly.
Spent a while trying not to use code but here we are. Code is our friend.
How?
So we'll run a workflow that triggers on the creation of a lead and runs a function which will simultaneously create a task with a reminder both on popup and email which repeats every day until an end date.
We then carry out the last bit of the brief with a separate workflow that runs exactly 4 days after the lead creation time and assigns it to the specified user (full name of user was given in the brief but removed here for privacy).
Finally, we delete any of these tasks when they get transferred to the contact record.
Zoho Creator: Render to PDF with margins and page numbers
- Category: Zoho
- Hits: 35603
There are already articles out there that document this but I use this more and more and would rather just find it on my site than going through multiple bookmarks.
Why?
This use-case is for a customer who simply wanted a quote template to be rendered for PDF or print format. I have another article for a different client who wants a pretty advanced HTML template (well it's a HTML table with rowspans and the borders need to merge cells). But here we're simply going to use ZohoCreator and its PDF rendering options.
What's wrong with just using CSS? It looks beautiful on ZohoCreator pages but then you hit the PDF button and it pretty much vomits your output back to the 90s. The code snippet below is just for a standard template with a modern design.
How?
Ok, admittedly, when first designing the template, there could have been a fair few improvements right out of the gate. Then the client comes back and says things like "can we add this?", "can it say this instead?", "can it be in a bigger font?"... without pushing back too much, it can end up looking like those old websites with HTML tables. We're going to use a HTML table again but that's because I want to send it to the PDF renderer in ZohoCreator and want all the data to stay aligned. Using only DIV layers can cause a few unexpected layouts in ZohoCreator.
Zoho Webhooks & Shopify API: Keep Disappearing
- Category: Zoho
- Hits: 42010
This is an article to show how to setup a scheduled function that checks and restores a missing webhook as well as the snippets when receiving the webhooks and storing them.
Why?
We found that Shopify would intermittently remove our webhooks. This would make the entire synchronization of the system go out of whack and some records would slip through the net while every day, the process to tidy these all up would leave the data more and more unreliable.
So Shopify want your webhooks to respond within 1 second. This is about enough time to store the record but that's it. Processing the record must be done at a later time or in a different process but sometimes it simply will not process it all and respond to Shopify within 1 second.
How?
So let's begin with the code snippets as these might answer your question to begin with and then we'll do the easy bit on how you set this up in the CRM. I'm using ZohoCRM here despite storing the record in ZohoCreator as the app and data processing is mostly done in ZohoCreator. I could have setup webhooks to go directly to Zoho Creator but I don't find Zoho Creator reliable when receiving and responding to webhooks.
Important!: For the REST API to detect what webhooks exist, it can only detect what webhooks it initialized. If you added webhooks via another app or via the Shopify Admin interface, then the following code won't be able to check and restore them.
ZohoCreator: Basic Widget with Zoho Data
- Category: Zoho
- Hits: 30566
A follow on from my article Zoho Creator: Create a Widget which uses JavaScript back from 2020 with a few adjustments now in 2024.
This widget will only work within Zoho Creator. If you want a solution which sits on an external website and which talks to Zoho, then setup an externally hosted web app with the usual JavaScript frameworks and have it send AJAX commands to a server which reads/writes information to your Zoho instance via API. No need for the Zoho Widget SDK.Why?
At time of print, I felt the documentation was a little sparse on how to connect a JS widget to the data held in Zoho Creator. Having this example for future use might save me some time. The following is the early version of a JS widget used for a customer and before I added all the layers of complexity, I want to have here an example that demonstrates a basic search on a table of data and returns one record as a result.
The use-case here is that we are enhancing a quote builder form in Zoho Creator. The JS widget (aka the Quote Builder) will need to post a new quote to the system as well as show previous orders.
How?
We're going to use the Zoho Extension SDK. You should refer to my previous linked article but the revised instructions here should also help. Then lastly, I'll post the snippet of code using in the widget to connect to Zoho based on a parameter passed in the URL that a Zoho Creator Page can receive and pass to the JS widget.
Zoho Deluge and Wordpress/WooCommerce API: Get All Products
- Category: Zoho
- Hits: 24533
A quick article on retrieving all the products from a WooCommerce instance on a client's Wordpress website.
Why?
A client of ours wants the information entered against products in their WooCommerce to display on their estimates in Zoho Books; such as product image, product name, product description.
Granted that in some cases, the design should go in the other direction, as in enter the products in Zoho Inventory and this feeds the website. More than often, however, this request comes in when the customer has already been setting their Zoho environment up and the Wordpress site has the more information.
How?
So first, this article shows you how to setup an API connection, and then we'll include the code to count the total number of products, and then finallly show the code to loop through all the pages to retrieve every product from the WooCommerce system.
Zoho Creator: Create a Widget which uses JavaScript
- Category: Zoho
- Hits: 61417
This is an article documenting how to create a Zoho Creator Widget which includes the Bootstrap and jQuery frameworks as well as other JavaScript functionality.
Why?
Quite simply that at time of print (2020-10-24), Zoho Creator does not allow you to use JavaScript anywhere in its app. HTML and CSS are mostly allowed but JavaScript automatically gets removed from any of your code.
How?
As someone who's been using Zoho services and programming in Zoho Deluge for a few years now, even I thought of Zoho Widgets as a daunting task. But after doing some, I realize this is actually very quick to do and much easier than first thought. The article below documents how to create your first widget in a MacOS environment. I aim to create a WindowsPC version of this document at some point for my colleagues. The below instructions follow a fresh install as if this is the first time this computer has created a Zoho Creator Widget.
Page 8 of 25
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
Latest Articles
Accreditation



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

bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4
0xb038962F3809b425D661EF5D22294Cf45E02FebF
Paypal:

Bitcoin:

Ethereum:
