├── 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 |
--------------------------------------------------------------------------------