Meditrack Subscription
We have added a separate ‘Subscription’ tab in MyAccount where customers can subscribe to ‘Meditrack’. We can navigate to the subscription section from the header menu profile.
To subscribe, customers must set up a default shipping address, billing address, and credit card. Otherwise, a validation message will be displayed.
Upon successful subscription to Meditrack, a custom record will be created, and the start date and status will be displayed on the website.
Newley created custom record in NetSuite:
To navigate to the custom record, log in to your NetSuite account and go to Customization → Lists, Records, & Fields → Record Types.
Then, on the subscription record, select the List button to see all the subscriptions created.
On a daily basis, a scheduled (Map/Reduce) script will run. If the next billing date is empty or matches today’s date, the script will check whether the default shipping address, billing address, and credit card are set. If any of these defaults are not set, the subscription status will be changed to ‘Cancelled,’ and the customer will need to contact the admin to re-enable the subscription. This will involve either making the record in NetSuite inactive or deleting it.
If all the default requirements are met, the scheduled script will run without any errors, and a new Sales Order will be created with the service item.
The activation date will be the date the first Sales Order is created. The last billing date will be the date of the most recent Sales Order creation, and the next billing date will be 30 days from the last billing date. The subscription status will be changed to ‘Active.’ For testing purposes, we have created a service item that will be added to the custom record. We need to provide the item ID statically in the scheduled script for future updates if the service item needs to be changed then in the schedule script, we need to change the item’s ID.
If the customer needs to cancel the subscription, they can do so at any time. There will be an unsubscribe button, allowing the customer to cancel the subscription by providing a reason and clicking the unsubscribe button.

Once the subscription is cancelled by the customer, an email will be sent to the admin with the customer’s name and reason for cancellation. The admin must then either make the record in NetSuite inactive or delete it to allow the customer to subscribe again.
Email:
The content on the Meditrack subscription section can be configured- Refer page No:6 for accessing configuration record.
Go to the “Extensions” subtab.
Select Registration Form.
You can configure certain fields, such as the sender ID, which will be the employee ID from whom the email will be sent, and the recipient email ID, which will be the admin who receives the email when any customer cancels their subscription. Additionally, you can set the subscription validation message for when a customer hasn’t set up their default billing, shipping address, and credit card.
Extension Files:
- Configuration file:

We need to create two service files as one for subscriptiona and another for unsubscribe.





subscription.View.js
define('JJ.subscription.subscription.View'
, [
'jj_subscription_subscription.tpl',
'Backbone',
'SC.Configuration',
'Profile.Model',
'JJ.subscription.subscription.Model',
'underscore',
'JJ.subscription.unsubscribe.Model',
'Backbone.FormView',
'Backbone.CompositeView'
]
, function subscriptionView(
jj_subscription_subscription_tpl,
Backbone,
Configuration,
ProfileModel,
subscriptionModel,
_,
unsubscribeModel,
BackboneFormView,
BackboneCompositeView
) {
'use strict';
return Backbone.View.extend({
template: jj_subscription_subscription_tpl,
title: _(Configuration.get('subscription.pageTitle')).translate(),
page_header: _(Configuration.get('subscription.pageHeader')).translate(),
getBreadcrumbPages: function () {
return {
text: this.title,
href: '/subscription'
};
},
getSelectedMenu: function getSelectedMenu() {
return 'subscription';
},
initialize: function (options) {
this.message = '';
BackboneCompositeView.add(this);
this.application = options.application;
this.UnsubscribeModel = new unsubscribeModel();
this.model = new subscriptionModel();
BackboneFormView.add(this);
},
events: {
'click [data-action="subscribe"]': 'Subscribe',
'click [data-action="unsubscribe"]': 'UnSubscribe'
},
bindings: {
'[name="unsubscribe_reason"]': 'unsubscribe_reason'
},
UnSubscribe: function (e) {
try {
let unsubscribe_reason = this.$('[name="unsubscribe_reason"]').val().trim();
let validationMessage = document.getElementById('unsubscribe-validation-message');
if (!unsubscribe_reason) {
validationMessage.style.display = 'block';
return;
} else {
validationMessage.style.display = 'none';
}
this.UnsubscribeModel.set('unsubscribe_reason', unsubscribe_reason);
this.UnsubscribeModel.save()
.done(function () {
location.reload();
})
.fail(function () {
location.reload();
});
} catch (e) {
console.error('Error in UnSubscribe function:', e);
}
},
Subscribe: function (e) {
try {
let profileModel = ProfileModel.getInstance();
let addresses = profileModel.attributes.addresses.models;
let failureMessage = document.getElementById('failure-validation-message');
let submitButton = document.querySelector('.subscription-submit');
let hasDefaultBilling = false;
let hasDefaultShipping = false;
let hasDefaultPayment = false;
// Iterate through all addresses to check for default billing and shipping
for (let i = 0; i < addresses.length; i++) {
let address = addresses[i];
if (address.attributes.defaultbilling === "T") {
hasDefaultBilling = true;
}
if (address.attributes.defaultshipping === "T") {
hasDefaultShipping = true;
}
}
// Check for default payment method
if (profileModel.attributes.defaultCreditCard && profileModel.attributes.defaultCreditCard.attributes.savecard === "T") {
hasDefaultPayment = true;
}
// Conditions to enable the submit button or display failure message
if (hasDefaultBilling && hasDefaultShipping && hasDefaultPayment) {
submitButton.disabled = false;
submitButton.classList.remove('disabled');
this.model.save()
location.reload();
} else {
failureMessage.innerText = Configuration.get('subscription.failureMessage');
failureMessage.style.display = "block";
submitButton.disabled = true;
submitButton.classList.add('disabled');
}
} catch (e) {
console.error('Error in Subscribe function:', e);
alert('An error occurred. Please try again.');
}
},
getContext: function getContext() {
try {
let profile_data = ProfileModel.getInstance();
let cust_id = profile_data.get("internalid");
return this.model.fetch({ data: { function: "getContext", cust_id: cust_id } })
.then(result => {
if (!result) {
console.error("Empty response received from the backend");
return {};
}
let {
IsInActive, SubscriptionStatus, StartDate, ActivateDate, BillingDate, NextBillingDate, HoldReason,
SubscribedItem } = result;
let SubscriptionCancelled = SubscriptionStatus === 'Cancelled';
let isInactiveFlag = IsInActive === 'F';
if (SubscriptionCancelled || isInactiveFlag) {
return {
SubscriptionCancelled,
IsInActive: isInactiveFlag,
StartDate,
ActivateDate,
BillingDate,
NextBillingDate,
SubscriptionStatus,
HoldReason,
SubscribedItem
};
}
return {};
})
.catch(e => {
console.error("Error fetching context:", e);
return {};
});
} catch (e) {
console.error("Error in the try block:", e);
return {};
}
},
updateContextAndRender: function () {
this.getContext()
.then(context => {
this._render(context);
this.initializeDocumentReadyFunctions();
})
.catch(e => {
console.error("Error updating context and rendering:", e);
});
},
render: function () {
this.updateContextAndRender();
return this;
},
_render: function (context) {
this.$el.html(this.template(context));
return this;
},
initializeDocumentReadyFunctions: function () {
$(document).ready(function () {
let checkBox = $('#subscription');
let $submitButton = $('.subscription-submit');
function toggleSubmitButton() {
$submitButton.prop('disabled', !checkBox.is(':checked'));
}
toggleSubmitButton();
checkBox.change(toggleSubmitButton);
});
}
});
});


JJ.subscription.subscription.js
define('JJ.subscription.subscription'
, [
'SC.Model',
'Configuration',
'Utils'
], function (
SCModel,
Configuration,
Utils
) {
'use strict';
return SCModel.extend({
name: 'JJ.subscription.subscription',
unSubscribeSubscription: function (data) {
try {
var currentUser = nlapiGetUser();
var customerRecord = nlapiLoadRecord('customer', currentUser);
var customerName = customerRecord.getFieldValue('entityid');
var unsubscribe_reason = Utils.sanitizeString(data.unsubscribe_reason);
// Search for active subscriptions
var customrecord_meditrack_subscriptionSearchh = nlapiSearchRecord("customrecord_meditrack_subscription", null,
[
["custrecord_subscription_customer", "anyof", currentUser], "AND",
["isinactive", "is", "F"]
],
[
new nlobjSearchColumn("internalid")
]
);
var action = 'not_found';
var recordId = null;
if (customrecord_meditrack_subscriptionSearchh && customrecord_meditrack_subscriptionSearchh.length > 0) {
var searchResult = customrecord_meditrack_subscriptionSearchh[0];
var subscriptionId = searchResult.getValue("internalid");
var record = nlapiLoadRecord('customrecord_meditrack_subscription', subscriptionId);
record.setFieldValue('custrecord_status', '4');
record.setFieldValue('custrecord_hold_reason', unsubscribe_reason);
recordId = nlapiSubmitRecord(record, true);
nlapiLogExecution('DEBUG', 'Subscription Unsubscribed', 'Record ID: ' + recordId);
action = 'unsubscribed';
}
// Send email notification
var emailId = Configuration.get('subscription.emailId');
var employeeId = Configuration.get('subscription.employeeId');
var emailSubject = Configuration.get('subscription.emailSubject');
var emailContent = Configuration.get('subscription.emailContent');
var admin = employeeId;
var recipient = emailId;
var subject = emailSubject;
var content = "Hi,<br>";
content += emailContent + "<br>";
content += "Customer Details.<br>";
content += "Name : " + customerName + "<br>";
content += "Reason for cancellation : " + unsubscribe_reason;
content += "<br>";
content += "Thank You ";
nlapiSendEmail(admin, recipient, subject, content);
return { success: true, action: action, recordId: recordId };
} catch (e) {
nlapiLogExecution('ERROR', 'Error in unSubscribeSubscription function', e.toString());
return { success: false, error: e.toString() };
}
},
searchSubscription: function (data) {
try {
var currentUser = nlapiGetUser();
var customrecord_meditrack_subscriptionSearch = nlapiSearchRecord("customrecord_meditrack_subscription", null,
[
["custrecord_subscription_customer", "anyof", currentUser]
],
[
new nlobjSearchColumn("custrecord_subscription_start_date"),
new nlobjSearchColumn("custrecord_activation_date"),
new nlobjSearchColumn("custrecord_last_billing_date"),
new nlobjSearchColumn("custrecord_next_billing_date"),
new nlobjSearchColumn("custrecord_status"),
new nlobjSearchColumn("custrecord_hold_reason"),
new nlobjSearchColumn("custrecord_subscription_item"),
new nlobjSearchColumn("isinactive")
]
);
if (customrecord_meditrack_subscriptionSearch) {
for (var j = 0; j < customrecord_meditrack_subscriptionSearch.length; j++) {
var searchResult = customrecord_meditrack_subscriptionSearch[j];
var startDate = searchResult.getValue("custrecord_subscription_start_date");
var activateDate = searchResult.getValue("custrecord_activation_date");
var billingDate = searchResult.getValue("custrecord_last_billing_date");
var nextBillingDate = searchResult.getValue("custrecord_next_billing_date");
var subscriptionStatus = searchResult.getText("custrecord_status");
var holdReason = searchResult.getValue("custrecord_hold_reason");
var subscribedItem = searchResult.getText("custrecord_subscription_item");
var isInActive = searchResult.getValue("isinactive");
}
}
} catch (e) {
nlapiLogExecution('ERROR', 'Error in search', e.toString());
}
return {
IsInActive: isInActive,
StartDate: startDate,
ActivateDate: activateDate,
BillingDate: billingDate,
NextBillingDate: nextBillingDate,
SubscriptionStatus: subscriptionStatus,
HoldReason: holdReason,
SubscribedItem: subscribedItem
};
},
createSubscriptionRecord: function (data) {
try {
var currentUser = nlapiGetUser();
var newRecord = nlapiCreateRecord('customrecord_meditrack_subscription');
newRecord.setFieldValue('custrecord_subscription_customer', currentUser);
newRecord.setFieldValue('custrecord_subscription_start_date', nlapiDateToString(new Date()));
newRecord.setFieldValue('custrecord_status', '3');
var recordId = nlapiSubmitRecord(newRecord, true);
nlapiLogExecution('DEBUG', 'Record Created', 'Record ID: ' + recordId);
} catch (e) {
nlapiLogExecution('ERROR', 'Error Creating Record', e.toString());
}
}
});
});
The template file:
<section class="subscription-info-card">
<span class="subscription-info-card-content">
{{#if IsInActive}}
<h2><b>Meditrack Subscription Status</b></h2>
<table class="subscription-table-desktop">
<tr>
<th>Start Date</th>
<th>Activated On</th>
<th>Last Billing</th>
<th>Next Billing</th>
<th>Status</th>
<th>Hold Reason</th>
</tr>
<tr>
<td>{{StartDate}}</td>
<td>{{ActivateDate}}</td>
<td>{{BillingDate}}</td>
<td>{{NextBillingDate}}</td>
<td>{{SubscriptionStatus}}</td>
<td>{{HoldReason}}</td>
</tr>
</table>
<table class="subscription-table-mobile">
<tr>
<td>Start Date</td>
<td>{{StartDate}}</td>
</tr>
<tr>
<td>Activated On</td>
<td>{{ActivateDate}}</td>
</tr>
<tr>
<td>Last Billing</td>
<td>{{BillingDate}}</td>
</tr>
<tr>
<td>Next Billing</td>
<td>{{NextBillingDate}}</td>
</tr>
<tr>
<td>Status</td>
<td>{{SubscriptionStatus}}</td>
</tr>
<tr>
<td>Hold Reason</td>
<td>{{HoldReason}}</td>
</tr>
</table>
{{#if SubscriptionCancelled}}
<p class="subscription-unsubscribe-status">Your Meditrack subscription has been cancelled.</p>
{{else}}
<div class="subscription-unsubscribe-section">
<p class="subscription-unsubscribe-heading">Please provide a reason and click the button below to unsubscribe from
the Meditrack subscription.</p>
<div class="subscription-controls-group" data-input="unsubscribe_reason" data-validation="control-group">
<label class="subscription-label" for="unsubscribe_reason">
{{translate 'Reason <small class="subscription-required">*</small>'}}
</label>
<div class="subscription-group-controls" data-validation="control">
<textarea name="unsubscribe_reason" id="unsubscribe_reason" class="subscription-input"></textarea>
</div>
<div id="unsubscribe-validation-message" class="unsubscribe-validation-message">Please provide a
reason.
</div>
</div>
<button type="submit" class="subscription-unsubscribe-button" data-action="unsubscribe">
Unsubscribe
</button>
</div>
{{/if}}
<div class="subscription-contact-us">Please <a href="/contact-us" data-hashtag="#/contact-us" data-touchpoint="home"
class="subscription-contact-us-link">Contact Us</a> for your queries and to subscribe again if your subscription
got cancelled.</div>
{{else}}
<h2><b>Subscribe to Meditrack Subscription.</b></h2>
<div id="subscription-Subscribe">
<div class="subscription-controls-group">
<div class="subscription-controls">
<label class="subscription-label">
<input type="checkbox" id="subscription" data-type="subscription-checkbox" value="T"
data-unchecked-value="F">
Yes, I would like to Subscribe for Meditrack.
</label>
</div>
<div class="subscription-controls-submit">
<button type="submit" data-action="subscribe" class="subscription-submit" disabled>Subscribe</button>
</div>
<div class="failure-validation-message" id="failure-validation-message"></div>
</div>
</div>
{{/if}}
</span>
</section>
For automation we have made it through Map/Reduce script so for that refer the KB Articel : https://jjknowledgebase.com/post/70208