├── CustomFunctions ├── bulk_operations │ ├── update_sales_person │ │ ├── updateSalesPerson.ds │ │ └── UpdateSalesPerson.gs │ └── update_custom_fields │ │ ├── UpdateCustomFields.ds │ │ └── UpdateCustomFields.gs ├── google_sheet_for_metered_billing │ ├── getCustomerReadings.gs │ ├── updateCustomerStatus.gs │ └── implement_metered_billing_with_google_sheet.ds ├── sms_for_subscription_business_using_twilio.ds ├── sms_for_subscription_business_using_textlocal.ds ├── whatsapp_click_to_chat.ds ├── sms_for_subscription_business_using_smsmagic.ds ├── charge_late_fee_for_overdue_invoices.ds ├── cancel_offline_subscription_after_expected_payment_date.ds ├── jort_form_redirect_url_customization.php ├── stateless_form_integration_with_google_cloud_function.js ├── syncPaymentModes.gs ├── ConvertNewCustomerToLead.ds ├── subscription_custom_cancellation_date.ds ├── zoho_sheet_for_metered_billing.ds ├── subscription_approval_workflow.ds ├── schedule_report_emails.ds ├── subscription_refer_and_earn_workflow.ds └── 30_days_money_back_guarantee.ds └── README.md /CustomFunctions/bulk_operations/update_sales_person/updateSalesPerson.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | * This deluge script is used to update salesperson to a subscription. 4 | * It can be used for bulk operations alongside with Zoho Sheets. 5 | 6 | */ 7 | 8 | string UPDATESALESPERSON(string subscriptionID, string salesPersonName) 9 | { 10 | //-- Paste your organizationID in the below variables. 11 | domain = "https://www.zohoapis.com/billing/v1"; 12 | organizationID = ""; 13 | data = Map(); 14 | data.put("salesperson_name",salesPersonName); 15 | url = domain + "/subscriptions/" + subscriptionID + "?organization_id=" + organizationID; 16 | response = invokeurl 17 | [ 18 | url :url 19 | type :PUT 20 | parameters:data.toString() 21 | connection:”YOUR_CONNECTION_NAME” 22 | ]; 23 | if(response.get("code") == 0) 24 | { 25 | return "Updated"; 26 | } 27 | else 28 | { 29 | return "Failed to update"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CustomFunctions/bulk_operations/update_custom_fields/UpdateCustomFields.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | * This deluge script is used to update customfields to a subscription. 4 | * It can be used for bulk operations alongside with Zoho Sheets. 5 | 6 | */ 7 | 8 | string UPDATECUSTOMFIELDS(string subscriptionID, string cfValue) 9 | { 10 | 11 | //-- Paste your organizationID in the below variables. 12 | organizationID = ""; 13 | customFieldsList = list(); 14 | customField = Map(); 15 | 16 | // -- Paste your customField ID here. 17 | customField.put("customfield_id",""); 18 | customField.put("value",cfValue); 19 | customFieldsList.add(customField); 20 | payloadMap = Map(); 21 | payloadMap.put("custom_fields",customFieldsList); 22 | 23 | domain = "https://www.zohoapis.com/billing/v1"; 24 | url = domain + "/subscriptions/" + subscriptionID + "?organization_id=" + organizationID; 25 | response = invokeurl 26 | [ 27 | url :url 28 | type :PUT 29 | parameters:payloadMap.toString() 30 | connection:"YOUR_CONNECTION_NAME" 31 | ]; 32 | if(response.get("code") == 0) 33 | { 34 | return "Updated"; 35 | } 36 | else 37 | { 38 | return "Failed to update"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CustomFunctions/bulk_operations/update_sales_person/UpdateSalesPerson.gs: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | * This google script is used to update salesperson to a subscription. 5 | * It can be used for bulk operations alongside with Google Sheets. 6 | 7 | */ 8 | 9 | function updateSalesPerson(subscriptionID, salesPersonName) 10 | { 11 | 12 | //-- Paste your accessToken and organizationID in the below variables. 13 | 14 | var accessToken = ""; 15 | var organizationID = ""; 16 | 17 | var headers = {'Authorization':'Zoho-oauthtoken '+accessToken, 'X-com-zoho-subscriptions-organizationid' : organizationID}; 18 | var data = {"salesperson_name":salesPersonName}; 19 | 20 | var url = "https://subscriptions.zoho.com/api/v1/subscriptions/"+subscriptionID; 21 | 22 | var options = { 23 | 'method' : 'put', 24 | 'contentType': 'application/json', 25 | 'payload' : JSON.stringify(data), 26 | 'headers' : headers, 27 | 'muteHttpExceptions': true 28 | }; 29 | 30 | var response = UrlFetchApp.fetch(url, options); 31 | 32 | if(response.getResponseCode() == 200) 33 | { 34 | return "Updated"; 35 | } 36 | else 37 | { 38 | return "Failed to update"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CustomFunctions/bulk_operations/update_custom_fields/UpdateCustomFields.gs: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | * This google script is used to update custom fields to a subscription. 5 | * It can be used for bulk operations alongside with Google Sheets. 6 | 7 | */ 8 | 9 | function updateCustomFields(subscriptionID, cfValue) 10 | { 11 | 12 | //-- Paste your accessToken and organizationID in the below variables. 13 | 14 | var accessToken = ""; 15 | var organizationID = ""; 16 | 17 | var headers = {'Authorization':'Zoho-oauthtoken '+accessToken, 'X-com-zoho-subscriptions-organizationid' : organizationID}; 18 | 19 | //-- Paste your customfieldID in the below jsonMap. 20 | var data = {"custom_fields":[{"value":cfValue, "customfield_id":""}]}; 21 | 22 | var url = "https://subscriptions.zoho.com/api/v1/subscriptions/"+subscriptionID+"/customfields"; 23 | 24 | var options = { 25 | 'method' : 'post', 26 | 'contentType': 'application/json', 27 | 'payload' : JSON.stringify(data), 28 | 'headers' : headers, 29 | 'muteHttpExceptions': true 30 | }; 31 | 32 | Utilities.sleep(1000); 33 | 34 | var response = UrlFetchApp.fetch(url, options); 35 | 36 | Utilities.sleep(1000); 37 | 38 | if(response.getResponseCode() == 200) 39 | { 40 | return "Updated"; 41 | } 42 | else 43 | { 44 | return "Failed to update"; 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /CustomFunctions/google_sheet_for_metered_billing/getCustomerReadings.gs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Google Script function to retrieve a row values for a particular customer. 3 | ** Pass the customer ID as a query parameter to this function's web app url. 4 | */ 5 | 6 | function doGet(e) 7 | { 8 | // Provide the spreadhsheet URL between the quotes. 9 | var spreadsheet = SpreadsheetApp.openByUrl(""); 10 | 11 | // Provide the name of the current working sheet. 12 | var sheet = spreadsheet.getSheetByName(""); 13 | 14 | return getCustomerDetails(sheet, e.parameter.id); 15 | } 16 | 17 | function getCustomerDetails(sheet, id) 18 | { 19 | var record = {}; 20 | var code = -1; // This variable will be used to identify success/failure status of this function in the json. 21 | 22 | 23 | var data = sheet.getDataRange().getValues(); 24 | 25 | // Get the headers and get the index of ID. 26 | // using the names you use in the headers 27 | var headers = data[0]; 28 | var idIndex = headers.indexOf('ID'); 29 | 30 | var sheetRow = -1; 31 | 32 | for( var i = 1 ; i < data.length; i++ ) 33 | { 34 | var dataRow = data[i]; 35 | 36 | if(dataRow[idIndex] == id) 37 | { 38 | record['id'] = dataRow[0]; 39 | record['name'] = dataRow[1]; 40 | record['last_month_reading'] = dataRow[2]; 41 | record['current_month_reading'] = dataRow[3]; 42 | code = 0; 43 | break; 44 | } 45 | } 46 | 47 | record['code'] = code; 48 | 49 | var result = JSON.stringify(record); 50 | 51 | return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /CustomFunctions/sms_for_subscription_business_using_twilio.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | Entity - Subscription 7 | 8 | Triggers - Subscription Cancelled. 9 | 10 | Work flow - When subscription is cancelled, SMS is sent to the customer. 11 | 12 | Change your country code in {{defaultCountryCode}} variable. 13 | 14 | */ 15 | 16 | // -- Provide your default country code. For example, '+91' for India. 17 | defaultCountryCode = ""; 18 | 19 | mobileNo = subscriptions.get("contactpersons").get("0").get("mobile"); 20 | length = len(mobileNo); 21 | 22 | // If mobile number is not present for the customer (or) if an invalid number is found, an email is sent to admin. 23 | 24 | if(mobileNo == "" || length < 10) 25 | { 26 | customerName = subscriptions.get("customer").get("display_name"); 27 | sendmail 28 | [ 29 | from :zoho.adminuserid 30 | to :zoho.adminuserid 31 | subject :"Invalid Mobile Number Found" 32 | message :"No mobile number (or) invalid mobile number found for the Customer " + customerName 33 | ] 34 | return; 35 | } 36 | 37 | 38 | if(length == 10) 39 | { 40 | mobileNo = defaultCountryCode + mobileNo; 41 | } 42 | 43 | planName = subscriptions.get("name"); 44 | textmessage = "Subscription has been cancelled for the Plan - " + planName; 45 | 46 | // -- Paste the twilio Mobile number from Twilio's console. 47 | twilioMobileNo = ""; 48 | 49 | sendsms 50 | [ 51 | from: twilioMobileNo 52 | to: mobileNo 53 | message: textmessage 54 | connection: "YOUR_TWILIO_CONNECTION_NAME" 55 | ]; 56 | -------------------------------------------------------------------------------- /CustomFunctions/sms_for_subscription_business_using_textlocal.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | Entity - Subscription 7 | 8 | Triggers - Subscription Cancelled. 9 | 10 | Work flow - When subscription is cancelled, SMS is sent to the customer. 11 | 12 | Change your country code in {{defaultCountryCode}} variable. 13 | 14 | */ 15 | 16 | // -- Provide your default country code. For example, '+91' for India. 17 | defaultCountryCode = ""; 18 | 19 | mobileNo = subscriptions.get("contactpersons").get("0").get("mobile"); 20 | length = len(mobileNo); 21 | 22 | // If mobile number is not present for the customer (or) if an invalid number is found, an email is sent to admin. 23 | 24 | if(mobileNo == "" || length < 10) 25 | { 26 | customerName = subscriptions.get("customer").get("display_name"); 27 | sendmail 28 | [ 29 | from :zoho.adminuserid 30 | to :zoho.adminuserid 31 | subject :"Invalid Mobile Number Found" 32 | message :"No mobile number (or) invalid mobile number found for the Customer " + customerName 33 | ] 34 | return; 35 | } 36 | 37 | 38 | if(length == 10) 39 | { 40 | mobileNo = defaultCountryCode + mobileNo; 41 | } 42 | 43 | planName = subscriptions.get("name"); 44 | textmessage = "Subscription has been cancelled for the Plan - " + planName; 45 | 46 | // -- Paste your API Key here, provided by TextLocal. 47 | apiKey = ""; 48 | 49 | infomap = Map(); 50 | infomap.put("numbers", mobileNo); 51 | infomap.put("message", textmessage); 52 | infomap.put("sender", "TXTLCL"); 53 | 54 | resp = postUrl("https://api.textlocal.in/send?" + "apiKey=" + apiKey, infomap); 55 | -------------------------------------------------------------------------------- /CustomFunctions/google_sheet_for_metered_billing/updateCustomerStatus.gs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Google Script function to update the current status for the customer. 3 | ** Pass the customer ID & current month reading as a query parameter to this function's web app url. 4 | */ 5 | 6 | function doGet(e) 7 | { 8 | // Provide the spreadhsheet URL between the quotes. 9 | var spreadSheet = SpreadsheetApp.openByUrl(""); 10 | 11 | // Provide the name of the current working sheet. 12 | var sheet = spreadSheet.getSheetByName(""); 13 | 14 | return setStatus(sheet, e.parameter.id, e.parameter.cmr); 15 | 16 | } 17 | 18 | function setStatus(sheet, id, cmr) 19 | { 20 | 21 | var json = {}; 22 | var code = -1; 23 | 24 | // Get all the data from the sheet 25 | var data = sheet.getDataRange().getValues(); 26 | 27 | // Get the headers and get the index of the ldap and the approval status 28 | // using the names you use in the headers 29 | var headers = data[0]; 30 | var idIndex = headers.indexOf('ID'); 31 | var statusIndex = headers.indexOf('Status'); 32 | var lmrIndex = headers.indexOf('Last Recorded Reading'); 33 | 34 | var sheetRow = -1; 35 | 36 | for( var i = 1 ; i < data.length; i++ ) 37 | { 38 | var row = data[i]; 39 | 40 | if(row[idIndex] == id) 41 | { 42 | sheetRow = i +1; 43 | code = 0; 44 | break; 45 | } 46 | } 47 | 48 | ++statusIndex; 49 | ++lmrIndex; 50 | 51 | //Set the value 52 | if(sheetRow != -1) 53 | { 54 | sheet.getRange(sheetRow, statusIndex).setValue('Updated'); 55 | sheet.getRange(sheetRow, lmrIndex).setValue(cmr); 56 | } 57 | 58 | json.code = code; 59 | 60 | var result = JSON.stringify(json); 61 | 62 | return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON); 63 | 64 | } 65 | -------------------------------------------------------------------------------- /CustomFunctions/whatsapp_click_to_chat.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a sample function written for automating your workflow in Zoho Subscriptions, a recurring billing and subscription 4 | management software. 5 | 6 | Entity - Invoice 7 | 8 | * This custom function is used to configure a custom button. 9 | * It will share the invoice url to the customer's mobile number on a button click. 10 | 11 | */ 12 | 13 | // Append the area & country code as prefix as per your requirements. 14 | phoneNumberPrefix = ""; 15 | contactPersons = invoice.get("contactpersons"); 16 | 17 | // Variable to store customer's phone number 18 | phoneNumber = ""; 19 | for each contact in contactPersons 20 | { 21 | if(!contact.get("phone").isBlank()) 22 | { 23 | phoneNumber = contact.get("phone"); 24 | } 25 | else if(!contact.get("mobile").isBlank()) 26 | { 27 | phoneNumber = contact.get("mobile"); 28 | } 29 | } 30 | 31 | resultMap = Map(); 32 | // 0 - Success ; Other than 0 will consider as failure 33 | 34 | if(phoneNumber.isBlank()) 35 | { 36 | resultMap.put("message","Phone number not found"); 37 | resultMap.put("code",1); 38 | } 39 | else 40 | { 41 | //Removing hyphenated values, paranthesis and leading "0"s 42 | validWaNumber = (phoneNumberPrefix + phoneNumber).replaceAll("[+()-]+","").replaceAll(" ","").replaceFirst("^0+(?!$)",""); 43 | 44 | // Customize the text as per your needs 45 | text = "Hi " + invoice.get("customer_name") + "!,\n" + "Please pay for " + invoice.get("number") + " using this link " + invoice.get("invoice_url"); 46 | 47 | url = "https://api.whatsapp.com/send?phone=" + validWaNumber + "&text=" + zoho.encryption.urlEncode(text); 48 | openUrl(url,"new window"); 49 | 50 | resultMap.put("message","Success"); 51 | resultMap.put("code",0); 52 | } 53 | return resultMap; 54 | -------------------------------------------------------------------------------- /CustomFunctions/sms_for_subscription_business_using_smsmagic.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | Entity - Subscription 7 | 8 | Triggers - Subscription Cancelled. 9 | 10 | Work flow - When subscription is cancelled, SMS is sent to the customer. 11 | 12 | Change your country code in {{defaultCountryCode}} variable. 13 | 14 | */ 15 | 16 | // -- Provide your default country code. For example, '+91' for India. 17 | defaultCountryCode = ""; 18 | 19 | mobileNo = subscriptions.get("contactpersons").get("0").get("mobile"); 20 | length = len(mobileNo); 21 | 22 | // If mobile number is not present for the customer (or) if an invalid number is found, an email is sent to admin. 23 | 24 | if(mobileNo == "" || length < 10) 25 | { 26 | customerName = subscriptions.get("customer").get("display_name"); 27 | sendmail 28 | [ 29 | from :zoho.adminuserid 30 | to :zoho.adminuserid 31 | subject :"Invalid Mobile Number Found" 32 | message :"No mobile number (or) invalid mobile number found for the Customer " + customerName 33 | ] 34 | return; 35 | } 36 | 37 | 38 | if(length == 10) 39 | { 40 | mobileNo = defaultCountryCode + mobileNo; 41 | } 42 | 43 | planName = subscriptions.get("name"); 44 | textmessage = "Subscription has been cancelled for the Plan - " + planName; 45 | 46 | // Paste your API Key here. 47 | apiKey = ""; 48 | 49 | // Sender's ID is numeric or alphanumeric string provided by SMS Magic. 50 | senderID = ""; 51 | 52 | headermap = Map(); 53 | headermap.put("apiKey",apiKey); 54 | 55 | infomap = Map(); 56 | infomap.put("mobile_number", mobileNo); 57 | infomap.put("sms_text", textmessage); 58 | infomap.put("sender_id", senderID); 59 | 60 | resp = postUrl("https://api.sms-magic.com/v1/sms/send", infomap, headermap); 61 | -------------------------------------------------------------------------------- /CustomFunctions/charge_late_fee_for_overdue_invoices.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | This custom function automatically chages Late Fee for overdue invoices based on the workflow configured. 7 | 8 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variable. 9 | 10 | Module - Invoice 11 | WorkflowType - Date Based 12 | Date of Execution - 3 day(s) after Due Date [Change as per your preference] 13 | Execution Time - 00 00 [Change as per your preference] 14 | 15 | Advanced Filter: 16 | [When Status is Overdue] 17 | 18 | */ 19 | 20 | // -- Paste your latefee Ledger Account ID in the below variable. 21 | lateFeeAccountID = ""; 22 | 23 | // -- Change the late fee amount and invoice description as per your needs. 24 | lateFeeAmount = 10; 25 | invReason = "Late Payment Fee"; 26 | invDescription = "Charges for late payment for the invoice "+invoice.get("number"); 27 | 28 | organizationID = organization.get("organization_id"); 29 | subscriptionID = invoice.get("subscriptions").get(0).get("subscription_id"); 30 | 31 | payloadMap = Map(); 32 | payloadMap.put("account_id", lateFeeAccountID); 33 | payloadMap.put("amount", lateFeeAmount); 34 | payloadMap.put("name", invReason); 35 | payloadMap.put("description", invDescription); 36 | 37 | domain = "https://www.zohoapis.com/billing/v1"; 38 | url = domain + "/subscriptions/" + subscriptionID + "/charge?organization_id=" + organizationID; 39 | request = "Charging Late Fee for "+invoice.get("number"); 40 | 41 | response = invokeUrl [ 42 | url : url 43 | type : POST 44 | parameters : payloadMap.toString() 45 | connection : "YOUR_CONNECTION_NAME" 46 | ]; 47 | 48 | // -- If the custom function fails to execute, an email will be sent to admin's email address. 49 | if(response.get("code") != 0) 50 | { 51 | errorMessage = response.get("message"); 52 | sendmail 53 | [ 54 | from :zoho.adminuserid 55 | to :zoho.adminuserid 56 | subject :"Error occured in Custom Function while " + request 57 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /CustomFunctions/cancel_offline_subscription_after_expected_payment_date.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | 7 | Module - Invoice 8 | 9 | Triggers - Subscription Cancelled. 10 | 11 | Work flow - Date Based workflow 12 | 13 | Date of Execution - 1 day(s) after Expected Payment Date 14 | 15 | Advanced Filter - When Status isn't paid. 16 | 17 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 18 | 19 | 20 | * This custom function cancels the offline subscriptions, if the renewed invoice is not paid 21 | even after the expected payment date. 22 | * Execute the custom function on unpaid invoices. 23 | * Retrieve the associated subscription with the invoice and cancel it immediately. 24 | 25 | */ 26 | 27 | organizationID = organization.get("organization_id"); 28 | domain = "https://www.zohoapis.com/billing/v1"; 29 | subscriptionList = invoice.get("subscriptions").toJSONList(); 30 | // Place the churn message ID value here, if your organization is using advanced churn settings. If not, leave it blank as such. 31 | churnMessageID = ""; 32 | params = Map(); 33 | params.put("cancel_at_end",false); 34 | params.put("reason","Payment not made even after the expected payment date"); 35 | if(!churnMessageID.isBlank()) 36 | { 37 | params.put("churn_message_id",churnMessageID); 38 | } 39 | payload = Map(); 40 | payload.put("JSONString",params); 41 | for each subscription in subscriptionList 42 | { 43 | // Get the subscription associated with the invoice and cancel it immediately. 44 | subscriptionID = subscription.get("subscription_id"); 45 | url = domain + "/subscriptions/" + subscriptionID + "/cancel?organization_id=" + organizationID; 46 | response = invokeurl 47 | [ 48 | url :url 49 | type :POST 50 | parameters:payload 51 | connection:"YOUR_CONNECTION_NAME" 52 | ]; 53 | if(response.get("code") != 0) 54 | { 55 | errorMessage = response.get("message"); 56 | sendmail 57 | [ 58 | from :zoho.adminuserid 59 | to :zoho.adminuserid 60 | subject :"Error in Custom Function Workflow" 61 | message :"Error in cancelling subscription
Error Message:" + errorMessage 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CustomFunctions/jort_form_redirect_url_customization.php: -------------------------------------------------------------------------------- 1 | '', "value" => $eye); 26 | $customFields[]=$cf1; 27 | $cf2 = array("customfield_id"=> '', "value" => $skin); 28 | $customFields[]=$cf2; 29 | $cf3 = array("customfield_id"=> '', "value" => $specialRequirement); 30 | $customFields[]=$cf3; 31 | $cf4 = array("customfield_id"=> '', "value" => $fragrance); 32 | $customFields[]=$cf4; 33 | $cf5 = array("customfield_id"=> '', "value" => $skinConcern); 34 | $customFields[]=$cf5; 35 | 36 | // Construct the header array for the API request. 37 | $headerArray = array("Content-Type: application/json;charset=UTF-8", 38 | "X-com-zoho-subscriptions-organizationid: ".$orgid, 39 | "Authorization: Zoho-authtoken ".$authToken 40 | ); 41 | 42 | // Construct the json data. 43 | $data = json_encode(array( "custom_fields"=>$customFields, 44 | "plan"=> array 45 | ( 46 | "plan_code"=> $planCode, 47 | "quantity"=> 1, 48 | "billing_cycles"=> -1, 49 | ),)); 50 | 51 | // Hit the API request. 52 | $url = "https://subscriptions.zoho.com/api/v1/hostedpages/newsubscription"; 53 | $ch = curl_init($url); 54 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 ); 55 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0 ); 56 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray); 57 | curl_setopt($ch, CURLOPT_POST, 1); 58 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 59 | 60 | $resp = curl_exec($ch); 61 | 62 | $json = json_decode($resp, true); 63 | 64 | $hpURL = $json['hostedpage'][url]; 65 | 66 | header('Location: '.$hpURL); 67 | 68 | die; 69 | 70 | ?> 71 | -------------------------------------------------------------------------------- /CustomFunctions/stateless_form_integration_with_google_cloud_function.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | 3 | /** 4 | * Responds to an HTTP request using data from the request body. 5 | * Gets the API Hosted Page URL & redirects to the same URL in the browser. 6 | * 7 | * @param {Object} req Cloud Function request context. 8 | * @param {Object} res Cloud Function response context. 9 | */ 10 | exports.retrieveHostedPageUrl = (req, res) => 11 | { 12 | 13 | /* 14 | ** Get the data for the custom fields from the req Object. 15 | */ 16 | var eyeColorJSON = JSON.parse(req.body.eyecolor); 17 | var eyeColor = eyeColorJSON.widget_metadata.value[0]["name"]; 18 | 19 | var skinColorJSON = JSON.parse(req.body.skintone); 20 | var skinColor = skinColorJSON.widget_metadata.value[0]["name"]; 21 | 22 | var fragranceJSON = JSON.parse(req.body.fragrance); 23 | var fragrance = fragranceJSON.widget_metadata.value[0]["name"]; 24 | 25 | /* 26 | ** Paste your authtoken and organizationID in the respective variables. 27 | */ 28 | 29 | var authtoken = ""; 30 | var organizationID = ""; 31 | 32 | /* 33 | * Construct the custom fields array list. Please get the customfield_id value from Zoho Subscriptions 34 | * and paste it between the quotes. 35 | */ 36 | var customFields = []; 37 | customFields.push({customfield_id: "", value: eyeColor}); 38 | customFields.push({customfield_id: "", value: skinColor}); 39 | customFields.push({customfield_id: "", value: fragrance}); 40 | customFields.push({customfield_id: "", value: req.body.specialRequirement}); 41 | customFields.push({customfield_id: "", value: req.body.skinconcern}); 42 | 43 | /* 44 | * Construct the JSON data. Paste your planCode in the plan_code key. 45 | */ 46 | var requestData = { 47 | 48 | plan: { 49 | plan_code: "", 50 | quantity: 1, 51 | billing_cycles: -1 52 | }, 53 | custom_fields: customFields 54 | }; 55 | 56 | /* 57 | ** Hit the Zoho Subscriptions API and retrieve the hosted page URL. 58 | */ 59 | var url = "https://subscriptions.zoho.com/api/v1/hostedpages/newsubscription?authtoken="+authtoken+"&organization_id="+organizationID; 60 | 61 | request(url, 62 | { method: "POST", json: true, body: requestData }, 63 | function(err, response, body) { 64 | 65 | // If statusCode is 201, the API call is successful. Else handle the error case according to your needs. 66 | if(response.statusCode == 201) 67 | { 68 | res.redirect(body.hostedpage.url); 69 | } 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /CustomFunctions/syncPaymentModes.gs: -------------------------------------------------------------------------------- 1 | /* 2 | This is google app script which needs to be used along with the Google Sheet, to check if the payment modes are 3 | already configued in Zoho Subscriptions, a recurring billing software. 4 | */ 5 | 6 | function syncPaymentModes() 7 | { 8 | 9 | //-- Paste your accessToken and organization ID in the below corresponding variables. 10 | 11 | var accessToken = ""; 12 | var organizationID = ""; 13 | 14 | var headers = {'Authorization':'Zoho-oauthtoken '+accessToken, 'X-com-zoho-subscriptions-organizationid' : organizationID}; 15 | 16 | var options = { 17 | 'method' : 'get', 18 | 'contentType': 'application/json', 19 | 'headers' : headers, 20 | 'muteHttpExceptions': true 21 | } 22 | 23 | var url = 'https://subscriptions.zoho.com/api/v1/settings/paymentmodes'; 24 | 25 | var response = UrlFetchApp.fetch(url, options); 26 | 27 | if(response.getResponseCode() == 200) 28 | { 29 | var details = JSON.parse(response.getContentText()); 30 | var paymentModes = details['payment_modes']; 31 | 32 | var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); 33 | 34 | for(i = 1; i < values.length-1; ++i) 35 | { 36 | var updateCellIndex = i + 1; 37 | var toUpdateCell = "B" + updateCellIndex; // Column B in spreadsheet, which updates the status for each payment mode. 38 | 39 | var modeName = values[i][0]; 40 | var isPresent = false; 41 | 42 | for(var j = 0; j < paymentModes.length; ++j) 43 | { 44 | var paymentModeName = paymentModes[j].name; 45 | 46 | // Check if the payment mode is present in the paymentModes array obtained from API request. 47 | if(paymentModeName == modeName) 48 | { 49 | isPresent = true; 50 | break; 51 | } 52 | } 53 | 54 | // Update the status for each payment mode in sheet. 55 | if(isPresent) 56 | { 57 | SpreadsheetApp.getActiveSheet().getRange(toUpdateCell).setValue('Yes'); 58 | } 59 | else 60 | { 61 | SpreadsheetApp.getActiveSheet().getRange(toUpdateCell).setValue('No'); 62 | } 63 | } 64 | 65 | showAlert(); 66 | } 67 | 68 | } 69 | 70 | function showAlert() 71 | { 72 | var ui = SpreadsheetApp.getUi(); 73 | 74 | var result = ui.alert( 75 | 'Message Details', 76 | 'Syncing the payment modes with server completed', 77 | ui.ButtonSet.OK); 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A sample collection of custom functions to demonstrate the automation workflow in Zoho Subscriptions, a recurring billing and subscriptions management software. 2 | 3 | Every business is unique and each of them follows a specific workflow. While managing your customers' subscriptions with Zoho, you might have some needs unique to your business. Custom Functions helps you address such needs. Here we have compiled a set of custom functions which addresses different needs. 4 | 5 | ## Custom Functions - Setup 6 | - In Zoho Subscriptions [*Web app*](https://subscriptions.zoho.com/), navigate to Settings -> Automation -> Custom Functions. 7 | - Create a new Custom function and provide a suitable name for it. 8 | - Choose the corresponding module and appropriate event to trigger the custom function. 9 | - Copy the required code depending on your use case. 10 | - Please paste your authtoken in the authtoken variable. (Refer this [*documentation*](https://www.zoho.com/subscriptions/api/v1/#authentication) to know how to generate your authtoken) 11 | - Customize the code according to your requirements (if needed). 12 | - Save the Custom function and you are good to go. 13 | 14 | ## Custom Functions 15 | 1. [*30 days money back guarantee*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/30_days_money_back_guarantee.ds) 16 | 2. [*Cancellation of Offline Subscriptions based on the Expected Payment Date*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/cancel_offline_subscription_after_expected_payment_date.ds) 17 | 3. [*SMS Reminders using Twilio*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/sms_for_subscription_business_using_twilio.ds) 18 | 4. [*SMS Reminders using SMS Magic*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/sms_for_subscription_business_using_smsmagic.ds) 19 | 5. [*SMS Reminders using TextLocal*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/sms_for_subscription_business_using_textlocal.ds) 20 | 6. [*Refer and Earn Workflow*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/subscription_refer_and_earn_workflow.ds) 21 | 7. [*Google Sheet integration for Metered Billing*](https://github.com/zoho/subscriptions-workflow-samples/tree/master/CustomFunctions/google_sheet_for_metered_billing) 22 | 8. [*WhatsApp's Click to Chat integration*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/whatsapp_click_to_chat.ds) 23 | 9. [*Charge Late Fee for overdue invoice*](https://github.com/zoho/subscriptions-workflow-samples/blob/master/CustomFunctions/charge_late_fee_for_overdue_invoices.ds) 24 | -------------------------------------------------------------------------------- /CustomFunctions/ConvertNewCustomerToLead.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This is a sample function written for automating your workflow in Zoho Subscriptions, a recurring billing and subscription 5 | management software. 6 | 7 | Module - Subscriptions. 8 | Predefined event - New Subscription. 9 | 10 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variable. 11 | 12 | * This custom function is used to convert a Lead in Zoho CRM to a Contact/Account. 13 | 14 | */ 15 | 16 | customer = subscriptions.get("customer"); 17 | customerEmail = customer.get("email"); 18 | 19 | // -- Search the lead with the email-address in Zoho CRM. 20 | searchParams = "criteria=" + "(Email:equals:" + customerEmail + ")"; 21 | url = "https://www.zohoapis.com/crm/v2/Leads/search?" + searchParams; 22 | response = invokeurl 23 | [ 24 | url :url 25 | type :GET 26 | connection:"YOUR_CONNECTION_NAME" 27 | ]; 28 | 29 | // Retrieving the details of the search result. 30 | if(response == null || response.isEmpty()) 31 | { 32 | sendmail 33 | [ 34 | from :zoho.adminuserid 35 | to :zoho.adminuserid 36 | subject :"Error occured in converting CRM Lead Custom Function" 37 | message :"No Lead found for the customer's Email address(" + customerEmail + ") in Zoho CRM. Please check manually from your end." 38 | ] 39 | } 40 | else if(response.contains("status") && response.get("status").equals("error")) 41 | { 42 | sendmail 43 | [ 44 | from :zoho.adminuserid 45 | to :zoho.adminuserid 46 | subject :"Error occured in converting CRM Lead Custom Function" 47 | message :"
Error Message
" + response.get("message") 48 | ] 49 | } 50 | else if(response.contains("data")) 51 | { 52 | leads = response.get("data"); 53 | if(leads.size() == 1) 54 | { 55 | leadID = leads.get(0).get("id"); 56 | values_map = Map(); 57 | values_map.put("overwrite",true); 58 | if(!customer.get("zcrm_account_id").isBlank()) 59 | { 60 | values_map.put("Accounts",customer.get("zcrm_account_id")); 61 | } 62 | else if(!customer.get("zcrm_contact_id").isBlank()) 63 | { 64 | values_map.put("Contacts",customer.get("zcrm_contact_id")); 65 | } 66 | response = zoho.crm.convertLead(leadID.toNumber(),values_map); 67 | if(response.contains("status") && response.get("status").equals("error")) 68 | { 69 | sendmail 70 | [ 71 | from :zoho.adminuserid 72 | to :zoho.adminuserid 73 | subject :"Error occured in converting CRM Lead Custom Function" 74 | message :"Error occured while converting the lead in Zoho CRM. Please convert manually." 75 | ] 76 | } 77 | } 78 | else 79 | { 80 | sendmail 81 | [ 82 | from :zoho.adminuserid 83 | to :zoho.adminuserid 84 | subject :"Error occured in converting CRM Lead Custom Function" 85 | message :"More than one Lead found with the same Email Address(" + customerEmail + "). Please convert the lead manually." 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CustomFunctions/subscription_custom_cancellation_date.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a sample function written for automating your workflow in Zoho Subscriptions, a recurring billing and subscription 4 | management software. 5 | 6 | Automation Module - Custom Schedulers 7 | 8 | Frequency - Daily 9 | 10 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 11 | 12 | * This custom function is used to configure a custom scheduler to cancel a subscription at a fixed (custom) date with the 13 | help of custom fields. 14 | 15 | */ 16 | 17 | organizationID = organization.get("organization_id"); 18 | 19 | // Enter the corresponding customViewID and customFieldID here. 20 | customViewID = ""; 21 | customFieldID = ""; 22 | domain = "https://www.zohoapis.com/billing/v1"; 23 | currentDate = zoho.currentdate.toString("yyyy-MM-dd"); 24 | 25 | url = domain+"/subscriptions?customview_id=" + customViewID + "&organization_id=" + organizationID; 26 | 27 | request = "Retrieving Subscriptions"; 28 | 29 | response = invokeUrl [ 30 | url : url 31 | type : GET 32 | connection : "YOUR_CONNECTION_NAME" 33 | ]; 34 | 35 | if(response.get("code") != 0) 36 | { 37 | errorMessage = response.get("message"); 38 | sendmail 39 | [ 40 | from :zoho.adminuserid 41 | to :zoho.adminuserid 42 | subject :"Error occured in Subscription Cancellation Scheduler while " + request 43 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 44 | ] 45 | return; 46 | } 47 | 48 | subscriptionList = response.get("subscriptions"); 49 | 50 | params = Map(); 51 | params.put("cancel_at_end",false); 52 | 53 | // Enter the appropriate reason for cancelling the subscription b/w the quotes. 54 | params.put("reason", ""); 55 | 56 | for each subscription in subscriptionList 57 | { 58 | if(subscription.get("status").equals("live")) 59 | { 60 | customFieldsList = subscription.get("custom_fields"); 61 | 62 | for each customField in customFieldsList 63 | { 64 | if(customField.get("customfield_id").equals(customFieldID)) 65 | { 66 | if(customField.get("value").equals(currentDate)) 67 | { 68 | subscriptionID = subscription.get("subscription_id"); 69 | 70 | cancelUrl = domain+"/subscriptions/" + subscriptionID + "/cancel?organization_id=" + organizationID; 71 | 72 | response = invokeUrl [ 73 | url : cancelUrl 74 | type : POST 75 | parameters : params.toString() 76 | connection : "YOUR_CONNECTION_NAME" 77 | ]; 78 | if(response.get("code") != 0) 79 | { 80 | errorMessage = response.get("message"); 81 | sendmail 82 | [ 83 | from :zoho.adminuserid 84 | to :zoho.adminuserid 85 | subject :"Error occured in Subscription Cancellation Scheduler" 86 | message :"Error in cancelling Subscription (Subscription ID -" + subscriptionID + ")
Error Message:" + errorMessage 87 | ] 88 | } 89 | } 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /CustomFunctions/google_sheet_for_metered_billing/implement_metered_billing_with_google_sheet.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Entity - Invoice 4 | 5 | Triggers - Invoice Created. 6 | 7 | Work flow - When a pending invoice is created, the readings value will be fetched from google script and the 8 | invoice will be updated accordingly. 9 | 10 | Place your {{authtoken}} in authtoken variable. 11 | 12 | */ 13 | 14 | 15 | invoiceStatus = invoice.get("status"); 16 | 17 | // Execute the function only for pending invoices. 18 | if(invoiceStatus.equalsIgnoreCase("pending")) 19 | { 20 | 21 | // -- Place your authtoken between the quotes. 22 | authtoken = ""; 23 | 24 | // -- Place the Google App Script urls copied from the Google App Script console. 25 | customerReadingsScriptUrl = ""; 26 | updateCustomerStatusScriptUrl = ""; 27 | 28 | orgID = organization.get("organization_id"); 29 | invID = invoice.get("invoice_id"); 30 | custID = invoice.get("customer_id"); 31 | customerName = invoice.get("customer_name"); 32 | 33 | resp = getUrl(customerReadingsScriptUrl + "?id=" + custID); 34 | 35 | if(resp.get("code") == 0) 36 | { 37 | lastMonthReading = resp.get("last_month_reading"); 38 | currentMonthReading = resp.get("closing_reading"); 39 | totalReading = currentMonthReading - lastMonthReading; 40 | 41 | /* In this example, invoice will be updated if the reading values exceed 100 units. 42 | Modify it for your needs. 43 | */ 44 | 45 | if(totalReading > 100) 46 | { 47 | invoiceItems = invoice.get("invoice_items").toJSONList(); 48 | rate = totalReading - 100; 49 | 50 | invoiceItem = Map(); 51 | invoiceItem.put("quantity","1"); 52 | invoiceItem.put("price",rate); 53 | invoiceItem.put("description","Charges based on metered billing usage."); 54 | invoiceItems.add(invoiceItem); 55 | 56 | paramsMap = Map(); 57 | paramsMap.put("invoice_items",invoiceItems); 58 | paramsMap.put("reason","Invoice line items updated based on usage."); 59 | 60 | url = "https://subscriptions.zoho.com/api/v1/invoices/" + invID + "?authtoken=" + authtoken + "&organization_id=" + orgID; 61 | request = "Updating Invoice"; 62 | resp = putUrl(url,paramsMap.tostring()); 63 | 64 | if(resp.get("code") == 0) 65 | { 66 | resp = getUrl(updateCustomerStatusScriptUrl + "?id=" + custID + "&cmr=" + currentMonthReading); 67 | } 68 | else 69 | { 70 | errorMessage = resp.get("message"); 71 | sendmail 72 | [ 73 | from :zoho.adminuserid 74 | to :zoho.adminuserid 75 | subject :"Error occured in Google sheet integration custom function while " + request 76 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 77 | ] 78 | } 79 | } 80 | } 81 | else 82 | { 83 | sendmail 84 | [ 85 | from :zoho.adminuserid 86 | to :zoho.adminuserid 87 | subject :"Error occured in Google sheet integration custom function" 88 | message :"The customer is not found in the Google Sheet." 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CustomFunctions/zoho_sheet_for_metered_billing.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Entity - Invoice 4 | 5 | Triggers - Invoice Created. 6 | 7 | Work flow - When a pending invoice is created, the readings value will be fetched from Zoho Sheet and the 8 | invoice will be updated accordingly. 9 | 10 | 11 | */ 12 | 13 | invoiceStatus = invoice.get("status"); 14 | if(!invoiceStatus.equalsIgnoreCase("pending")) 15 | { 16 | // -- Place the Zoho Sheet ID between the quotes obtained from the URL bar of the browser and also the worksheet name. 17 | sheetID = ""; 18 | worksheetName = ""; 19 | domain = "https://www.zohoapis.com/billing/v1"; 20 | orgID = organization.get("organization_id"); 21 | invID = invoice.get("invoice_id"); 22 | custID = invoice.get("customer_id"); 23 | customerName = invoice.get("customer_name"); 24 | criteria = Map(); 25 | criteria.put("criteria","\"Customer ID\"=\"" + custID + "\""); 26 | response = zoho.sheet.getRecords(sheetID,worksheetName,criteria,"YOUR_SHEETS_CONNECTION_NAME"); 27 | if(response.get("status") == "success" && response.get("records_count") > 0) 28 | { 29 | record = response.get("records").get("0"); 30 | lastMonthReading = record.get("Last Month Reading"); 31 | currentMonthReading = record.get("Closing Reading"); 32 | totalReading = currentMonthReading - lastMonthReading; 33 | /* In this example, invoice will be updated if the reading values exceed 100 units. 34 | Modify it for your needs. 35 | */ 36 | if(totalReading > 100) 37 | { 38 | invoiceItems = invoice.get("invoice_items").toJSONList(); 39 | rate = totalReading - 100; 40 | invoiceItem = Map(); 41 | invoiceItem.put("quantity","1"); 42 | invoiceItem.put("price",rate); 43 | invoiceItem.put("description","Charges based on metered billing usage."); 44 | invoiceItems.add(invoiceItem); 45 | paramsMap = Map(); 46 | paramsMap.put("invoice_items",invoiceItems); 47 | paramsMap.put("reason","Invoice line items updated based on usage."); 48 | url = domain + "/invoices/" + invID + "?organization_id=" + orgID; 49 | request = "Updating Invoice"; 50 | resp = invokeurl 51 | [ 52 | url :url 53 | type :PUT 54 | parameters:paramsMap.tostring() 55 | connection:"YOUR_BILLING_CONNECTION_NAME" 56 | ]; 57 | if(resp.get("code") == 0) 58 | { 59 | data = Map(); 60 | criteria = "\"Customer ID\"=\""+custID+"\""; 61 | data.put("Status", "Updated"); 62 | response = zoho.sheet.updateRecords(sheetID, worksheetName, criteria, data, Map(), "YOUR_SHEETS_CONNECTION_NAME"); 63 | } 64 | else 65 | { 66 | errorMessage = resp.get("message"); 67 | sendmail 68 | [ 69 | from :zoho.adminuserid 70 | to :zoho.adminuserid 71 | subject :"Error occured in Google sheet integration custom function while " + request 72 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 73 | ] 74 | } 75 | } 76 | } 77 | else 78 | { 79 | sendmail 80 | [ 81 | from :zoho.adminuserid 82 | to :zoho.adminuserid 83 | subject :"Error occured in Google sheet integration custom function" 84 | message :"The customer is not found in the Google Sheet." 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CustomFunctions/subscription_approval_workflow.ds: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 4 | a recurring billing and subscriptions management software. 5 | 6 | 7 | Module - Subscription 8 | 9 | Type - Custom Button 10 | 11 | Prequisites: 12 | 13 | 1) Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 14 | 2) Create a dropdown custom field for Invoices. Provide the label as 'Approval Status' and two dropdown values as 15 | 'Awaiting Approval' and 'Approved'. 16 | 3) Configure a custom button for Subscriptions module and paste the code below. 17 | 18 | 19 | * This custom function updates the approval status custom field. 20 | * It also postpones the billing date for the second billing cycle. 21 | * It is useful for box type subscription businesses. 22 | 23 | 24 | */ 25 | 26 | domain = "https://www.zohoapis.com/billing/v1"; 27 | 28 | organizationID = organization.get("organization_id"); 29 | subscriptionID = subscriptions.get("subscription_id"); 30 | emailID = subscriptions.get("customer").get("email"); 31 | name = subscriptions.get("name"); 32 | customFields = subscriptions.get("custom_fields").toJSONList(); 33 | cfName = "cf_approval_status"; 34 | 35 | resultMap = Map(); 36 | resultMap.put("code",1); 37 | isCFPresent = 0; 38 | 39 | for each cf in customFields 40 | { 41 | if(cf.get("api_name").equalsIgnoreCase(cfName)) 42 | { 43 | isCFPresent = 1; 44 | if(cf.get("value").equalsIgnoreCase("Approved")) 45 | { 46 | resultMap.put("message","Subscription Approved Already"); 47 | return resultMap; 48 | } 49 | else if(cf.get("value").equalsIgnoreCase("Awaiting Approval")) 50 | { 51 | cf.put("value","Approved"); 52 | cf.put("value_formatted","Approved"); 53 | } 54 | } 55 | } 56 | 57 | if(isCFPresent == 0) 58 | { 59 | resultMap.put("message","Custom Fields Not Found"); 60 | return resultMap; 61 | } 62 | params = Map(); 63 | params.put("custom_fields",customFields); 64 | updateResponse = zoho.billing.update("Subscriptions",organizationID,subscriptionID,params,"YOUR_CONNECTION_NAME"); 65 | 66 | if(updateResponse.get("code") != 0) 67 | { 68 | resultMap.put("message","Custom Fields Not Updated: " + updateResponse.get("message")); 69 | return resultMap; 70 | } 71 | 72 | // update to next billing date 73 | currentDate = zoho.currentdate.toString("yyyy-MM-dd"); 74 | nextBillingDate = addDay(currentDate,30); 75 | params = Map(); 76 | params.put("renewal_at",nextBillingDate.toString("yyyy-MM-dd")); 77 | postponeUrl = domain + "/subscriptions/" + subscriptionID + "/postpone?organization_id=" + organizationID; 78 | response = invokeurl 79 | [ 80 | url :postponeUrl 81 | type :POST 82 | parameters:params.toString() 83 | connection:"YOUR_CONNECTION_NAME" 84 | ]; 85 | 86 | if(updateResponse.get("code") != 0) 87 | { 88 | resultMap.put("message","Error Occured while updating Next Billing Date: " + updateResponse.get("message")); 89 | return resultMap; 90 | } 91 | 92 | sendmail 93 | [ 94 | from :zoho.adminuserid 95 | to :emailID 96 | subject :"Subscription Approved" 97 | message :"Your subscription has been approved and your first shipment is on its way. Thank you for the business." 98 | ]; 99 | 100 | resultMap.put("code",0); 101 | resultMap.put("message","Subscription name -" + name); 102 | return resultMap; 103 | -------------------------------------------------------------------------------- /CustomFunctions/schedule_report_emails.ds: -------------------------------------------------------------------------------- 1 | /* 2 | This is a sample function. Uncomment to execute or make changes to this function. 3 | organizationID = organization.get("organization_id"); 4 | 5 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 6 | 7 | */ 8 | 9 | 10 | currentDate = zoho.currentdate.toString("yyyy-MM-dd"); 11 | currentTime = currentDate.toTime(); 12 | 13 | // -- Obtaining the date 7 days earlier. 14 | lastWeekTime = currentTime.addWeek(-1); 15 | lastWeekDate = lastWeekTime.toString("yyyy-MM-dd"); 16 | domain = “https://www.zohoapis.com/billing/v1” 17 | linkDomain = "https://billing.zoho.com/app#"; 18 | authorizationParams = "&organization_id=" + organization.get("organization_id"); 19 | dateParams = "from_date=" + lastWeekDate + "&to_date=" + currentDate; 20 | 21 | // -- Fetching Last opportunities report. 22 | url = domain + "/reports/lost_opportunities?response_option=2&" + dateParams + authorizationParams; 23 | request = "Retrieving Lost Opportunities Report"; 24 | 25 | response = invokeurl 26 | [ 27 | url :url 28 | type :GET 29 | connection:"YOUR_CONNECTION_NAME" 30 | ]; 31 | 32 | if(response.get("code") != 0) 33 | { 34 | errorMessage = response.get("message"); 35 | sendmail 36 | [ 37 | from :zoho.adminuserid 38 | to :zoho.adminuserid 39 | subject :"Error occured in Email Notifications Scheduler while " + request 40 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 41 | ] 42 | return; 43 | } 44 | else 45 | { 46 | lastOpportunitiesCount = response.get("payments").size(); 47 | lastOpportunitiesUrl = linkDomain + "/reports/lost-opportunities?from_date=" + lastWeekDate; 48 | } 49 | 50 | // -- Getting Trial Expired Subscriptions List. 51 | url = domain + "/subscriptions?filter_by=SubscriptionStatus.TRIAL_EXPIRED_PREVIOUS_WEEK&response_option=2" + authorizationParams; 52 | request = "Retrieving Trial Expired Subscriptions"; 53 | 54 | response = invokeurl 55 | [ 56 | url :url 57 | type :GET 58 | connection:"subscription_all" 59 | ]; 60 | 61 | if(response.get("code") != 0) 62 | { 63 | errorMessage = response.get("message"); 64 | sendmail 65 | [ 66 | from :zoho.adminuserid 67 | to :zoho.adminuserid 68 | subject :"Error occured in Email Notifications Scheduler while " + request 69 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 70 | ] 71 | return; 72 | } 73 | else 74 | { 75 | pageContext = response.get("page_context"); 76 | trialExpiredSubscriptionsCount = pageContext.get("total"); 77 | trialExpiredSubscriptionsUrl = linkDomain + "/reports/activecustomers?" + dateParams; 78 | info trialExpiredSubscriptionsUrl; 79 | } 80 | 81 | // -- Getting Churned Subscriptions 82 | url = domain + "/reports/churn?response_option=2&" + dateParams + authorizationParams; 83 | 84 | request = "Retrieving Churned Subscriptions"; 85 | response = invokeurl 86 | [ 87 | url :url 88 | type :GET 89 | connection:"YOUR_CONNECTION_NAME" 90 | ]; 91 | 92 | if(response.get("code") != 0) 93 | { 94 | errorMessage = response.get("message"); 95 | sendmail 96 | [ 97 | from :zoho.adminuserid 98 | to :zoho.adminuserid 99 | subject :"Error occured in Email Notifications Scheduler while " + request 100 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 101 | ] 102 | return; 103 | } 104 | else 105 | { 106 | pageContext = response.get("page_context"); 107 | churnedSubscriptionsCount = pageContext.get("total"); 108 | churnedSubscriptionsUrl = linkDomain + "/reports/cancellation?" + dateParams; 109 | } 110 | 111 | // -- Construct email message. 112 | subjectText = "Weekly Subscription Reports"; 113 | messageText = "Last Opportunities Report
Count : " + lastOpportunitiesCount + "
URL : " + lastOpportunitiesUrl + "

Trial Expired Subscriptions Report
Count : " + trialExpiredSubscriptionsCount + "
URL : " + trialExpiredSubscriptionsUrl + "

Churned Subscriptions Report
Count : " + churnedSubscriptionsCount + "
URL : " + churnedSubscriptionsUrl; 114 | sendmail 115 | [ 116 | from :zoho.adminuserid 117 | to :zoho.adminuserid 118 | subject :subjectText 119 | message :messageText 120 | ] 121 | -------------------------------------------------------------------------------- /CustomFunctions/subscription_refer_and_earn_workflow.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This is a sample Custom Function written to be used in Workflow automation of Zoho Subscriptions, 5 | a recurring billing and subscriptions management software. 6 | 7 | 8 | Module - Subscription 9 | 10 | Predefined Event - New Subscription. 11 | 12 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 13 | 14 | 15 | * This custom function raises a credit note for the referrer with a fixed incentive amount. 16 | * Search the customer with the email address provided by the referred customer. 17 | * Raise a credit note for the searched customer. 18 | 19 | */ 20 | 21 | organizationID = organization.get("organization_id"); 22 | 23 | domain = "https://www.zohoapis.com/billing/v1"; 24 | 25 | customer = subscriptions.get("customer"); 26 | customerCustomerFields = customer.get("custom_fields"); 27 | 28 | // -- Paste the label of your Custom Field shown in Hosted Page. 29 | customFieldLabel = ""; 30 | 31 | referredCustomerName = customer.get("display_name"); 32 | 33 | for each customField in customerCustomerFields 34 | { 35 | if(customField.get("label") == customFieldLabel) 36 | { 37 | referrerEmail = customField.get("value"); 38 | break; 39 | } 40 | } 41 | 42 | if(referrerEmail != null && !isBlank(referrerEmail)) 43 | { 44 | params = "&response_option=0" + "&email_contains=" + referrerEmail + "&organization_id=" + organizationID; 45 | request = "Searching referrer Customer"; 46 | 47 | response = invokeUrl [ 48 | url : domain + "/api/v1/customers?page=1&per_page=10" + params 49 | type : GET 50 | connection : "YOUR_CONNECTION_NAME" 51 | ]; 52 | 53 | if(response.get("code") == 0) 54 | { 55 | customers = response.get("customers"); 56 | paramsMap = Map(); 57 | 58 | if(customers.size() > 0) 59 | { 60 | customer = customers.get(0); 61 | customerID = customer.get("customer_id"); 62 | 63 | // -- Fill the incentive which you would like to award, say "10.00" 64 | referrerBonus = ""; 65 | 66 | // -- Paste the Account ID, to which this transaction is to be mapped, say Discount Account ID. 67 | accountID = ""; 68 | 69 | description = "This is a incentive for referring the customer " + referredCustomerName; 70 | 71 | creditNoteDate = zoho.currentdate.toString("yyyy-MM-dd"); 72 | creditNoteItemList = list(); 73 | 74 | creditNoteItem = Map(); 75 | creditNoteItem.put("quantity","1"); 76 | creditNoteItem.put("price",referrerBonus); 77 | creditNoteItem.put("description",description); 78 | creditNoteItem.put("account_id",accountID); 79 | creditNoteItemList.add(creditNoteItem); 80 | 81 | paramsMap.put("customer_id",customerID); 82 | paramsMap.put("date",creditNoteDate); 83 | paramsMap.put("creditnote_items",creditNoteItemList); 84 | 85 | url = domain + "/api/v1/creditnotes?organization_id=" + organizationID; 86 | request = "Creating Credit Note"; 87 | 88 | response = invokeUrl [ 89 | url : url 90 | type : POST 91 | parameters : paramsMap.toString() 92 | connection : "YOUR_CONNECTION_NAME" 93 | ]; 94 | 95 | if(response.get("code") != 0) 96 | { 97 | errorMessage = response.get("message"); 98 | sendmail 99 | [ 100 | from :zoho.adminuserid 101 | to :zoho.adminuserid 102 | subject :"Error occured in Referrer custom function while " + request 103 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 104 | ] 105 | } 106 | } 107 | else 108 | { 109 | sendmail 110 | [ 111 | from :zoho.adminuserid 112 | to :zoho.adminuserid 113 | subject :"Error occured in Referrer custom function." 114 | message :"Referreral Customer with Email ID " + referrerEmail + " is not found." 115 | ] 116 | } 117 | } 118 | else 119 | { 120 | errorMessage = response.get("message"); 121 | sendmail 122 | [ 123 | from :zoho.adminuserid 124 | to :zoho.adminuserid 125 | subject :"Error occured in Referrer custom function while " + request 126 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 127 | ] 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /CustomFunctions/30_days_money_back_guarantee.ds: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Entity - subscription 4 | 5 | Triggers - Subscription Cancelled. 6 | 7 | Work flow - When subscription Cancelled with in the specified days, then amount will be refunded. 8 | 9 | Change this {{daysToConsider}} variable as per your need. 10 | 11 | Place your {{connection name}} in "YOUR_CONNECTION_NAME" variables. 12 | 13 | * This custom function refunds the payment done for a subscription, if it is cancelled within 30 days. 14 | * When a subscription is cancelled, the last sent invoice details are retrieved. 15 | * If the invoice is paid, the amount is refunded (in case of online payment) or a credit note is raised (in case of offline payment). 16 | * If the invoice is unpaid, it is marked as void. 17 | 18 | */ 19 | 20 | // Change the daysToConsider value according to your need. 21 | dayToConsider = 30; 22 | createdDate = subscriptions.get("activated_at"); 23 | cancelledDate = subscriptions.get("cancelled_at"); 24 | totalDays = days360(createdDate,cancelledDate); 25 | 26 | if(totalDays > dayToConsider) 27 | { 28 | //If the subscription is active for more than (daysToConsider) 30 days, we simnply exit the function. 29 | return; 30 | } 31 | domain = "https://www.zohoapis.com/billing/v1"; 32 | organizationID = organization.get("organization_id"); 33 | 34 | invoiceID = subscriptions.get("child_invoice_id"); 35 | if(invoiceID.isEmpty()) 36 | { 37 | return; 38 | } 39 | url = domain + "/invoices/" + invoiceID + "?organization_id=" + organizationID; 40 | request = "retriving invoice"; 41 | response = invokeUrl [ 42 | url : url 43 | type : GET 44 | connection : "YOUR_CONNECTION_NAME" 45 | ]; 46 | // Retrieving the details of the last paid invoice. 47 | if(response.get("code") != 0) 48 | { 49 | errorMessage = response.get("message"); 50 | sendmail 51 | [ 52 | from :zoho.adminuserid 53 | to :zoho.adminuserid 54 | subject :"Error occured in Auto Cancellation custom function while " + request 55 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 56 | ] 57 | return; 58 | } 59 | invoiceMap = response.get("invoice"); 60 | invoiceStatus = invoiceMap.get("status"); 61 | paramsMap = Map(); 62 | // Handling the use case for paid invoice. 63 | if(invoiceStatus == "paid") 64 | { 65 | payments = invoiceMap.get("payments").get(0); 66 | paidAmount = payments.get("amount"); 67 | isOnlinePayment = payments.get("gateway_transaction_id"); 68 | if(isOnlinePayment == "") 69 | { 70 | // For offline payment, we create a credit note for the customer. 71 | customerID = invoiceMap.get("customer_id"); 72 | invoiceItems = invoiceMap.get("invoice_items").get(0); 73 | planCode = invoiceItems.get("code"); 74 | creditNoteDate = zoho.currentdate.toString("yyyy-MM-dd"); 75 | creditNoteItemList = list(); 76 | creditNoteItem = Map(); 77 | creditNoteItem.put("code",planCode); 78 | creditNoteItem.put("quantity","1"); 79 | creditNoteItem.put("price",paidAmount); 80 | creditNoteItemList.add(creditNoteItem); 81 | paramsMap.put("customer_id",customerID); 82 | paramsMap.put("date",creditNoteDate); 83 | paramsMap.put("creditnote_items",creditNoteItemList); 84 | url = domain + "/creditnotes?organization_id=" + organizationID; 85 | request = "creating Credit Note"; 86 | response = invokeUrl [ 87 | url : url 88 | type : POST 89 | parameters : paramsMap.toString() 90 | connection : "YOUR_CONNECTION_NAME" 91 | ]; 92 | } 93 | else 94 | { 95 | // For online payment, we intiate a refund for the paid amount. 96 | paymentID = payments.get("payment_id"); 97 | refundDescription = "Subscription is cancelled and amount is auto-refunded"; 98 | paramsMap.put("amount",paidAmount); 99 | paramsMap.put("description",refundDescription); 100 | url = domain + "/payments/" + paymentID + "/refunds?organization_id=" + organizationID; 101 | request = "refunding payment"; 102 | response = invokeUrl [ 103 | url : url 104 | type : POST 105 | parameters : paramsMap.toString() 106 | connection : "YOUR_CONNECTION_NAME" 107 | ]; 108 | } 109 | } 110 | // Handling the use case for unpaid invoice, by simply voiding it. 111 | else if(invoiceStatus == "sent" || invoiceID == "overdue") 112 | { 113 | paramsMap.put("reason","subscription cancelled"); 114 | url = domain + "/invoices/" + invoiceID + "/void?organization_id=" + organizationID; 115 | request = "voiding invoice"; 116 | response = invokeUrl [ 117 | url : url 118 | type : POST 119 | parameters : paramsMap.toString() 120 | connection : "YOUR_CONNECTION_NAME" 121 | ]; 122 | } 123 | 124 | // If error occurs in any of the API requests, an email will be sent to the admin user with the request URL and it's error. 125 | if(response.get("code") != 0) 126 | { 127 | errorMessage = response.get("message"); 128 | sendmail 129 | [ 130 | from :zoho.adminuserid 131 | to :zoho.adminuserid 132 | subject :"Error occured in Auto Cancellation custom function while " + request 133 | message :"Affected url :
" + url + "
Error Message
" + errorMessage 134 | ] 135 | } 136 | --------------------------------------------------------------------------------