GS code
// === CONFIG ===
const SPREADSHEET_ID = '1YeKz2a7Q2BugIzMljA2f1hfiZ0f0FkXrkiaDRAj0La4';
const SHEET_NAME = 'Couriers';
const TRACKING_SHEET = 'Tracking';
const STATUS_LOG_SHEET = 'Status Log';
const MERCHANT_KEY = 'oFsOGp';
const SALT = 'MriTXv7syPM4IH6zCy4KvR2nuewkHXr9';
const BLOGGER_FORM_URL = 'https://mmorder.blogspot.com/p/pay.html';
const SCRIPT_WEBAPP_URL = 'https://script.google.com/macros/s/AKfycbxHs1kka7XVA6g4c-0JPKAwMMHzr-5LyuE-JzxZrcd8tDz9bisbbjNLcjWjCYSQQJclhA/exec';
/** JSON / JSONP helper */
function _serve(data, callback) {
const payload = JSON.stringify(data);
if (callback) {
return ContentService
.createTextOutput(callback + '(' + payload + ');')
.setMimeType(ContentService.MimeType.JAVASCRIPT);
}
return ContentService
.createTextOutput(payload)
.setMimeType(ContentService.MimeType.JSON);
}
/** ===== Robust Date Parsers ===== */
/**
* Parse the sheet Timestamp (Column A) to a JS Date.
* Supports:
* - Date objects and sheet serials
* - "dd/MM/yyyy", "dd-MM-yyyy", "dd.MM.yyyy"
* - Optional time "HH:mm[:ss]"
* - ISO-like strings as fallback
*/
function _parseSheetTimestampToDate(ts) {
if (!ts) return null;
if (ts instanceof Date && !isNaN(ts)) return ts;
// Google Sheets serial date
if (typeof ts === 'number') {
var ms = Math.round((ts - 25569) * 86400 * 1000);
var d = new Date(ms);
return isNaN(d) ? null : d;
}
ts = String(ts).trim();
// dd/MM/yyyy or dd-MM-yyyy or dd.MM.yyyy with optional time (HH:mm[:ss])
var m = ts.match(/^(\d{1,2})[\/.\-](\d{1,2})[\/.\-](\d{2,4})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
if (m) {
var d = +m[1], mo = +m[2]-1, y = +m[3]; if (y < 100) y += 2000;
var hh = +(m[4]||0), mm = +(m[5]||0), ss = +(m[6]||0);
var out = new Date(y, mo, d, hh, mm, ss);
return isNaN(out) ? null : out;
}
// ISO-ish fallback
var d2 = new Date(ts);
return isNaN(d2) ? null : d2;
}
/**
* Parse Dispatch Date cell (Column J) into a Date.
* Accepts:
* - Date objects and serials
* - yyyy-MM-dd
* - dd/MM/yyyy
* - ISO-ish fallback
*/
function _parseDispatchCell(v) {
if (v === '' || v === null || v === undefined) return null;
if (v instanceof Date && !isNaN(v)) return v;
if (typeof v === 'number') {
if (v > 10000000000) { // epoch ms
var dEpoch = new Date(v);
return isNaN(dEpoch) ? null : dEpoch;
}
var ms = Math.round((v - 25569) * 86400 * 1000); // Sheets serial
var d = new Date(ms);
return isNaN(d) ? null : d;
}
var s = String(v).trim();
var m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/); // yyyy-MM-dd
if (m) {
var d1 = new Date(+m[1], +m[2]-1, +m[3], 0, 0, 0);
return isNaN(d1) ? null : d1;
}
m = s.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); // dd/MM/yyyy
if (m) {
var d2 = new Date(+m[3], +m[2]-1, +m[1], 0, 0, 0);
return isNaN(d2) ? null : d2;
}
var d3 = new Date(s); // ISO-ish
return isNaN(d3) ? null : d3;
}
function _fmtISODate(d) {
if (!d || isNaN(d)) return '';
var y = d.getFullYear();
var m = String(d.getMonth()+1); if (m.length < 2) m = '0' + m;
var day = String(d.getDate()); if (day.length < 2) day = '0' + day;
return y + '-' + m + '-' + day;
}
/** GET entrypoint */
function doGet(e) {
try {
if (e.parameter.action === 'payu') return handlePayURedirect(e);
if (e.parameter.txnid && e.parameter.status) return handlePayUResponse(e);
if (e.parameter.txnid && !e.parameter.status) return lookupLink(e);
switch (e.parameter.action) {
case 'tracking': return handleTracking(e);
case 'getTrackingMap': return handleTrackingMap(e);
case 'search': return handleSearch(e);
case 'edit': return handleEdit(e.parameter);
case 'delete': return handleDelete(e.parameter);
case 'metrics': return handleMetrics(e);
}
return ContentService.createTextOutput("No valid parameters.")
.setMimeType(ContentService.MimeType.TEXT);
} catch (err) {
return _serve({ error: 'doGet error: ' + err.message, stack: String(err.stack || '') });
}
}
/** POST entrypoint */
function doPost(e) {
try {
if (e.parameter.txnid && e.parameter.status) return handlePayUResponse(e);
var params;
try { params = JSON.parse(e.postData.contents); }
catch (parseErr) { return _serve({ error: 'Invalid JSON body: ' + parseErr.message }); }
if (params.action === 'edit') return handleEdit(params);
return handleInsert(params);
} catch (err) {
return _serve({ error: 'doPost error: ' + err.message, stack: String(err.stack || '') });
}
}
/** 0) PayU redirect */
function handlePayURedirect(e) {
var txnid = e.parameter.txnid, amount = e.parameter.amount, name = e.parameter.name,
hash = e.parameter.hash, surl = e.parameter.surl, furl = e.parameter.furl;
return HtmlService
.createHtmlOutput(
'Redirecting… ' +
''
)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
/** 1) PayU response (once) */
function handlePayUResponse(e) {
var props = PropertiesService.getScriptProperties();
var txnid = (e.parameter.txnid || '').trim();
var status = (e.parameter.status || '').toLowerCase();
var amount = (e.parameter.amount || '').trim();
var tz = Session.getScriptTimeZone();
var ts = props.getProperty(txnid);
if (!ts) {
ts = Utilities.formatDate(new Date(), tz, "dd/MM/yyyy HH:mm:ss");
props.setProperty(txnid, ts);
if (status === 'success') {
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var mainSheet = ss.getSheetByName(SHEET_NAME);
var data = mainSheet.getDataRange().getValues();
var recName = '', orderAmt = '';
for (var i = 1; i < data.length; i++) {
if ((data[i][14]||'').toString().trim() === txnid) {
mainSheet.getRange(i+1,14).setValue('Paid');
mainSheet.getRange(i+1,17).setValue('success');
recName = data[i][1];
orderAmt = data[i][10];
break;
}
}
var logSheet = ss.getSheetByName(STATUS_LOG_SHEET);
logSheet.insertRowBefore(2);
logSheet.getRange(2,1,1,5).setValues([[ ts, txnid, status, recName, orderAmt ]]);
logSheet.getRange("A2:A").setNumberFormat("@");
logSheet.getRange("E2:E").setNumberFormat("0.00");
ensureBlankRows(500);
}
}
var dest = 'https://mmorder.blogspot.com/p/success.html?txnid=' + encodeURIComponent(txnid) +
'&amount=' + encodeURIComponent(amount) + '&time=' + encodeURIComponent(ts);
var html = ' ' +
'';
return HtmlService.createHtmlOutput(html).setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
/** 2) Lookup tracking link */
function lookupLink(e) {
var txnid = (e.parameter.txnid||'').trim();
if (!txnid) return _serve({ error: 'Missing txnid' }, e.parameter.callback);
var rows = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME).getDataRange().getValues();
for (var i = 1; i < rows.length; i++) {
if ((rows[i][14]||'').toString().trim() === txnid) return _serve({ url: rows[i][15] }, e.parameter.callback);
}
return _serve({ error: 'Invalid txnid' }, e.parameter.callback);
}
/** Tracking endpoints */
function handleTracking(e) {
var s = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(TRACKING_SHEET);
var last = s.getLastRow();
var list = last<2 ? [] : s.getRange(2,1,last-1,1).getValues().join('\n').split('\n').filter(function(v){return v;});
return _serve(list, e.parameter.callback);
}
function handleTrackingMap(e) {
var s = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(TRACKING_SHEET);
var last = s.getLastRow();
if (last<2) return _serve({}, e.parameter.callback);
var map = {};
var vals = s.getRange(2,1,last-1,2).getValues();
for (var i=0;i= sd && ts <= ed)) return false;
}
return true;
});
filtered.sort(function(a,b){
function key(o){
var r = o.row;
if (sortBy === 'dispatchDate') return _parseDispatchCell(r[9]) || new Date(0);
return _parseSheetTimestampToDate(r[0]) || new Date(0); // default: timestamp
}
var ka = key(a).getTime(), kb = key(b).getTime();
return (order === 'asc') ? (ka - kb) : (kb - ka);
});
var total = filtered.length;
var page = filtered.slice(offset, offset + limit);
var tz = Session.getScriptTimeZone();
var rows = page.map(function(item){
var out = item.row.slice();
var disp = _parseDispatchCell(out[9]);
out[9] = disp ? Utilities.formatDate(disp, tz, 'yyyy-MM-dd') : ''; // normalized for HTML date input
return [item.sheetRow].concat(out);
});
return _serve({ rows: rows, total: total }, e.parameter.callback);
}
/** Insert */
function handleInsert(params) {
if (!params.recipientName || !params.orderValue) return _serve({ error:'Missing recipientName/orderValue' });
var tz = Session.getScriptTimeZone();
var tsStr = Utilities.formatDate(new Date(), tz, "dd/MM/yyyy HH:mm:ss");
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var sheet = ss.getSheetByName(SHEET_NAME);
var dispDate = null;
if (params.dispatchDate) {
var dt = _parseDispatchCell(params.dispatchDate);
if (dt) dispDate = dt;
}
var txnid='', longUrl='', status='';
if ((params.payment||'') === 'Unpaid') {
txnid = 'TXN' + Date.now();
var udf = [ '', '', '', '', '', '', '', '', '', '' ];
var hashArr = [ MERCHANT_KEY, txnid, String(params.orderValue), 'Order Payment', params.recipientName, 'test@example.com' ]
.concat(udf).concat([ SALT ]);
var hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_512, hashArr.join('|'))
.map(function(b){ return ('0'+(b&0xFF).toString(16)).slice(-2); }).join('');
var surl = SCRIPT_WEBAPP_URL + '?txnid=' + txnid + '&status=success&amount=' + encodeURIComponent(params.orderValue);
var furl = SCRIPT_WEBAPP_URL + '?txnid=' + txnid + '&status=failure&amount=' + encodeURIComponent(params.orderValue);
longUrl = BLOGGER_FORM_URL +
'?txnid=' + encodeURIComponent(txnid) +
'&amount=' + encodeURIComponent(params.orderValue) +
'&name=' + encodeURIComponent(params.recipientName) +
'&hash=' + encodeURIComponent(hash) +
'&surl=' + encodeURIComponent(surl) +
'&furl=' + encodeURIComponent(furl) +
'&service_provider=payu_paisa';
status = 'Pending';
} else {
status = 'Paid';
}
sheet.insertRowBefore(2);
var rowValues = [
tsStr, params.recipientName, params.address||'', params.primaryPhone||'',
params.secondaryPhone||'', params.preferredCourier||'', params.courieredVia||'',
params.trackingNumber||'', params.trackingURL||'', '',
params.orderValue, params.courierCharge||'', params.accountPhone||'',
params.payment||'', txnid, longUrl, status
];
sheet.getRange(2,1,1,rowValues.length).setValues([ rowValues ]);
if (dispDate) sheet.getRange(2,10).setValue(dispDate).setNumberFormat("dd/MM/yyyy");
sheet.getRange("A2:A").setNumberFormat("@");
sheet.getRange("K2:K").setNumberFormat("0.00");
ensureBlankRows(500);
return _serve({ success:true, longUrl: longUrl });
}
/** Edit */
function handleEdit(params) {
try {
if (!params.rowIndex) throw new Error('Missing rowIndex');
var row = parseInt(params.rowIndex,10);
var tz = Session.getScriptTimeZone();
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var sheet = ss.getSheetByName(SHEET_NAME);
var st = String(sheet.getRange(row,17).getValue()).toLowerCase();
if (st === 'success') { sheet.getRange(row,14).setValue('Paid'); delete params.payment; }
var map = {
courieredVia:7, trackingNumber:8, trackingURL:9, dispatchDate:10,
orderValue:11, courierCharge:12, accountPhone:13, payment:14, status:17
};
for (var f in map) {
if (params[f] !== undefined) {
var v = params[f];
if (f === 'dispatchDate') {
var d = _parseDispatchCell(v);
if (d) sheet.getRange(row, map[f]).setValue(d).setNumberFormat("dd/MM/yyyy");
else sheet.getRange(row, map[f]).setValue(v);
} else {
sheet.getRange(row, map[f]).setValue(v);
}
}
}
if (params.payment!==undefined || params.status!==undefined) {
var rd = sheet.getRange(row,1,1,17).getValues()[0];
var logSheet = ss.getSheetByName(STATUS_LOG_SHEET);
var ts2 = Utilities.formatDate(new Date(), tz, "dd/MM/yyyy HH:mm:ss");
logSheet.insertRowBefore(2);
logSheet.getRange(2,1,1,5).setValues([[ ts2, rd[14], rd[16], rd[1], rd[10] ]]);
logSheet.getRange("A2:A").setNumberFormat("@");
logSheet.getRange("E2:E").setNumberFormat("0.00");
ensureBlankRows(500);
}
sheet.getRange("A2:A").setNumberFormat("@");
ensureBlankRows(500);
return _serve({ success:true }, params.callback);
} catch(e) {
return _serve({ error:e.message }, params.callback);
}
}
/** Delete */
function handleDelete(params) {
try {
if (!params.rowIndex) throw new Error('Missing rowIndex');
var row = parseInt(params.rowIndex,10);
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
sheet.deleteRow(row);
ensureBlankRows(500);
return _serve({ success:true }, params.callback);
} catch(e) {
return _serve({ error:e.message }, params.callback);
}
}
/** METRICS — STRICTLY by Timestamp (Column A) */
function handleMetrics(e) {
var start = (e.parameter.startDate || '').trim();
var end = (e.parameter.endDate || '').trim();
if (!start || !end) return _serve({ error: 'Missing startDate or endDate' }, e.parameter.callback);
var sd = new Date(start + 'T00:00:00');
var ed = new Date(end + 'T23:59:59.999');
var rows = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME).getDataRange().getValues();
if (rows.length <= 1) {
return _serve({ totalOrders:0, paymentsReceived:0, totalOrderValue:0, totalCourierCharges:0, totalOutstanding:0 }, e.parameter.callback);
}
var totalOrders = 0, paymentsReceived = 0, totalOrderValue = 0, totalCourierCharges = 0;
for (var i=1;i= sd && ts <= ed)) continue;
totalOrders++;
var ov = parseFloat(String(r[10] || '0').replace(/[^0-9.]/g, '')) || 0; // K
var cc = parseFloat(String(r[11] || '0').replace(/[^0-9.]/g, '')) || 0; // L
var pay = String(r[13] || '').trim().toLowerCase() === 'paid';
totalOrderValue += ov;
totalCourierCharges += cc;
if (pay) paymentsReceived += ov;
}
return _serve({
totalOrders: totalOrders,
paymentsReceived: paymentsReceived,
totalOrderValue: totalOrderValue,
totalCourierCharges: totalCourierCharges,
totalOutstanding: totalOrderValue - paymentsReceived
}, e.parameter.callback);
}
/** Ensure plenty of blank rows */
function ensureBlankRows(count) {
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var sheets = ss.getSheets();
for (var i=0;i0) sheet.insertRowsAfter(max, needed);
}
}