A robust Zoho Creator REST API endpoint for a health application, handling dynamic Moneris checkout tickets, Base64 photo uploads, and progressive form saving.
When building a custom web portal (e.g. Next.js, React, or standard HTML) that talks to Zoho Creator, you often need a single, secure backend endpoint to handle everything from progressive form saves to payment processing and file uploads.
The standard Zoho Creator REST API is great for simple CRUD, but it doesn't handle complex, multi-step business logic gracefully.
This Deluge script acts as a master controller for a telehealth web application. It takes a single rawInput JSON string and branches its logic based on an action parameter.
Progressive Form Saving (action: "save")
Instead of wiping out data if a user skips a field, this script conditionally updates only the fields that were actually sent (if(v != null) { rec.Field = v; }). This is crucial for multi-step forms where the user's progress is saved incrementally.
Moneris Payment Integration (action: "payment_ticket")
To prevent malicious users from modifying prices in the browser's developer tools, all pricing logic is hardcoded inside Deluge. The frontend only sends the product name; the backend verifies the price, builds the payload, and makes a secure server-to-server invokeurl call to the Moneris Gateway to generate a fresh checkout ticket.
Decoupled Photo Uploads (action: "upload_photo")
Base64 photo uploads are deliberately decoupled from the main save logic. File uploads via API are prone to timeouts or network errors. By separating the upload action, a failed photo won't crash the entire questionnaire submission, ensuring the core patient data is always captured.
health_app_api(string rawInput)
{
inputMap = rawInput.toMap();
response = Map();
action = ifnull(inputMap.get("action"), "save");
sessionId = inputMap.get("sessionId");
if(sessionId == null || sessionId == "")
{
response.put("error", "sessionId is required");
return response;
}
if(action == "get")
{
response.put("found", false);
for each rec in Health_Application[Session_ID == sessionId]
{
response.put("found", true);
response.put("ID", rec.ID);
response.put("Current_Step", rec.Current_Step);
response.put("Overall_Status", rec.Overall_Status);
response.put("Main_ED_Concern", rec.Main_ED_Concern);
response.put("Main_ED_Concern_Other", rec.Main_ED_Concern_Other);
response.put("ED_Frequency", rec.ED_Frequency);
response.put("Symptom_Onset", rec.Symptom_Onset);
response.put("First_Name", rec.First_Name);
response.put("Last_Name", rec.Last_Name);
response.put("Email", rec.Email);
response.put("Country_Code", rec.Country_Code);
response.put("Phone_Number", rec.Phone_Number);
response.put("Spontaneous_Erections", rec.Spontaneous_Erections);
response.put("Treatment_Goal", rec.Treatment_Goal);
response.put("Medical_History", rec.Medical_History);
response.put("Medical_History_Other", rec.Medical_History_Other);
response.put("Takes_Nitroglycerine", rec.Takes_Nitroglycerine);
response.put("Symptom_Triggers", rec.Symptom_Triggers);
response.put("Recent_Trauma_Surgery", rec.Recent_Trauma_Surgery);
response.put("Weight_Kg", rec.Weight_Kg);
response.put("Blood_Pressure_Reading", rec.Blood_Pressure_Reading);
response.put("Blood_Work_Recent", rec.Blood_Work_Recent);
response.put("Prior_ED_Medication", rec.Prior_ED_Medication);
response.put("Medication_Preference", rec.Medication_Preference);
response.put("Medication_Product", rec.Medication_Product);
response.put("Medication_Price", rec.Medication_Price);
response.put("Has_Allergies", rec.Has_Allergies);
response.put("Allergy_Details", rec.Allergy_Details);
response.put("Other_Medications", rec.Other_Medications);
response.put("Other_Medications_Details", rec.Other_Medications_Details);
response.put("Substance_Use", rec.Substance_Use);
response.put("Substance_Use_Details", rec.Substance_Use_Details);
response.put("Date_Of_Birth", rec.Date_Of_Birth);
response.put("Street_Address", rec.Street_Address);
response.put("City", rec.City);
response.put("State_Province", rec.State_Province);
response.put("Postal_Code", rec.Postal_Code);
response.put("Has_Health_Card", rec.Has_Health_Card);
response.put("Health_Card_Number", rec.Health_Card_Number);
}
return response;
}
if(action == "payment_ticket")
{
// ---- Moneris store credentials (LIVE) ----
store_id = "YOUR_STORE_ID";
api_token = "YOUR_API_TOKEN";
checkout_id = "YOUR_CHECKOUT_ID";
moneris_url = "https://gateway.moneris.com/chkt/request/request.php";
feeAmount = 45.00;
priceList = Map();
priceList.put("Sildenafil 100 mg (12 Tab)",149.99);
priceList.put("Sildenafil 50 mg (12 Tab)",144.70);
priceList.put("Tadalafil 20 mg (12 Tab)",197.35);
priceList.put("Tadalafil 10 mg (12 Tab)",190.87);
priceList.put("Tadalafil 5 mg (30 Tab)",148.75);
priceList.put("Vardenafil 20 mg (12 Tab)",187.19);
priceList.put("Vardenafil 10 mg (12 Tab)",167.95);
payFirst = "";
payLast = "";
payEmail = "";
payProduct = "";
payPhone = "";
payRecId = 0;
for each rec in Health_Application[Session_ID == sessionId]
{
payRecId = rec.ID;
payFirst = ifnull(rec.First_Name,"") + "";
payLast = ifnull(rec.Last_Name,"") + "";
payEmail = ifnull(rec.Email,"") + "";
payProduct = ifnull(rec.Medication_Product,"") + "";
payPhone = ifnull(rec.Phone_Number,"") + "";
}
if(payRecId == 0)
{
response.put("error","No application found for this session.");
return response;
}
patId = 0;
patIsNew = false;
try
{
if(payEmail != "")
{
for each pat in Patient[Email == payEmail]
{
patId = pat.ID;
}
if(patId == 0)
{
insert into Patient
[
Name.first_name = payFirst
Name.last_name = payLast
Email = payEmail
];
patIsNew = true;
for each pat in Patient[Email == payEmail]
{
patId = pat.ID;
}
}
response.put("patient_id",patId);
response.put("patient_created",patIsNew);
if(patIsNew && patId != 0)
{
live.send_passkey(payFirst,payLast,payEmail,patId);
response.put("passkey_email","sent");
}
}
}
catch (patErr)
{
response.put("patient_error",patErr.toString());
}
items = List();
feeItem = Map();
feeItem.put("description","Telehealth Assessment Fee");
feeItem.put("product_code","Telehealth");
feeItem.put("quantity","1");
feeItem.put("unit_cost",feeAmount.round(2).toString());
items.add(feeItem);
txnTotal = feeAmount;
if(payProduct != "")
{
if(priceList.containKey(payProduct))
{
medPrice = priceList.get(payProduct);
medItem = Map();
medItem.put("description",payProduct);
medItem.put("product_code","Medication");
medItem.put("quantity","1");
medItem.put("unit_cost",medPrice.round(2).toString());
items.add(medItem);
txnTotal = txnTotal + medPrice;
}
else
{
response.put("medication_not_priced",payProduct);
}
}
cart = Map();
cart.put("items",items);
contact = Map();
contact.put("first_name",payFirst);
contact.put("last_name",payLast);
contact.put("email",payEmail);
payload = Map();
payload.put("store_id",store_id);
payload.put("api_token",api_token);
payload.put("checkout_id",checkout_id);
payload.put("environment","prod");
payload.put("action","preload");
payload.put("language","en");
payload.put("order_no","Rumini-" + zoho.currenttime.toString("yyyyMMddHHmmss"));
payload.put("txn_total",txnTotal.round(2).toString());
payload.put("cart",cart);
payload.put("contact_details",contact);
payHeaders = Map();
payHeaders.put("Content-Type","application/json");
try
{
monResp = invokeurl
[
url: moneris_url
type: POST
parameters: payload.toString()
headers: payHeaders
];
monSuccess = monResp.get("response").get("success");
monTicket = monResp.get("response").get("ticket");
if(monSuccess == "true" || monSuccess == true)
{
response.put("ticket",monTicket);
response.put("amount",txnTotal.round(2).toString());
for each rec in Health_Application[ID == payRecId]
{
rec.Payment_Amount = txnTotal.round(2).toString();
rec.Payment_Ticket = monTicket;
}
if(patId != 0)
{
for each pat in Patient[ID == patId]
{
pat.WL_Ticket_ID = monTicket;
}
}
}
else
{
response.put("error","Moneris did not return a ticket.");
}
}
catch (payErr)
{
response.put("error","Could not reach Moneris.");
}
response.put("ID",payRecId);
return response;
}
if(action == "upload_photo")
{
response.put("uploaded", false);
fieldName = ifnull(inputMap.get("fieldName"), "") + "";
photoB64 = ifnull(inputMap.get("base64"), "") + "";
photoName = ifnull(inputMap.get("filename"), "photo.jpg") + "";
upRecId = 0;
for each rec in Health_Application[Session_ID == sessionId]
{
upRecId = rec.ID;
}
if(upRecId != 0 && fieldName != "" && photoB64 != "")
{
upUrl = "https://creator.zohocloud.ca/api/v2" + zoho.appuri + "report/Health_Application_Report/" + upRecId + "/" + fieldName + "/upload";
try
{
photoFile = zoho.encryption.base64DecodeToFile(photoB64, photoName);
photoFile.setParamName("file");
upResp = invokeurl
[
url: upUrl
type: POST
connection: "rumini_creator_upload"
files: photoFile
];
if(upResp.toString().contains("3000"))
{
response.put("uploaded", true);
}
}
catch (upErr)
{
response.put("upload_error", upErr.toString());
}
}
response.put("ID", upRecId);
return response;
}
// Default save logic for fields
stepNumber = inputMap.get("stepNumber");
isFinal = ifnull(inputMap.get("isFinalStep"), false);
fields = ifnull(inputMap.get("fields"), Map());
overallStatus = if(isFinal, "Completed", "In Progress");
found = false;
recId = 0;
for each rec in Health_Application[Session_ID == sessionId]
{
found = true;
recId = rec.ID;
rec.Current_Step = stepNumber;
rec.Last_Updated = zoho.currenttime;
rec.Overall_Status = overallStatus;
if(isFinal) { rec.Completed_Time = zoho.currenttime; }
// Incrementally save fields
if(fields.get("First_Name") != null) { rec.First_Name = fields.get("First_Name"); }
if(fields.get("Email") != null) { rec.Email = fields.get("Email"); }
}
if(!found)
{
newId = insert into Health_Application
[
Session_ID = sessionId
Current_Step = stepNumber
Overall_Status = overallStatus
Started_Time = zoho.currenttime
];
recId = newId;
response.put("status", "created");
}
else
{
response.put("status", "updated");
}
response.put("ID", recId);
return response;
}