Gloriafood integration customer address filleds help [SOLVED]

I need help please.
When i call the order i get the customer address filleds all in one line using customer _address string.
I need to poll the customer address every filled separately (street, floor, city) using the customer _address _parts object (see gloriafood documentation pic) from here integration_docs/accepted_orders at master · GlobalFood/integration_docs · GitHub
here is the script im using…

var express = require('express');
var request = require('request');
var querystring = require('querystring');
var libphonenumber = require('libphonenumber-js');
var app = express();

var messageServer = 'localhost';
var messageServerPort = 9000;
var gloriaFoodKey = 'XXXXXXX';
var serverKey = 'XXXX';
var timeout = 30000;
var customerEntityType = 'Customers';
var itemTagName = 'Gloria Name';
var ticketType = 'Ticket';
var departmentName = 'pizza';
var userName = 'איציק';
var terminalName = 'Server';
var printJobName = 'Print Bill';
var additionalPrintJobs = [];  // array of additional print job names
var miscProductName = 'Misc';
var deliveryFeeCalculation = 'Delivery Service';
var tipCalculation = 'Tip';
var accessToken = undefined;
var accessTokenExpires = '';

var formatPhoneNumber = true;
var formatPhoneNumberCountry = 'IL'; // set to your ISO country code
var formatPhoneNumberFormat = 'National'; // format type, this should suffice
var formatPhoneNumberHyphen = false;
var formatPhoneNumberNoSpaces = true;


function Authorize(callback) {
    accessToken = undefined;
    var form = { grant_type: 'client_credentials', client_secret: serverKey, client_id: 'gloria' };
    var formData = querystring.stringify(form);
    var contentLength = formData.length;

    request({
        headers: {
            'Content-Length': contentLength,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/Token',
        body: formData,
        method: 'POST'
    }, function (err, res, body) {
        if (err) {
            console.log('Error while trying to authorize >', err.message);
            if (callback) callback();
        }
        else if (res.statusCode === 400) {
            console.log(body);
            if (callback) callback();
        }
        else {
            var result = JSON.parse(body);
            accessToken = result.access_token;
            accessTokenExpires = new Date(result['.expires']);
            if (callback) callback();
        }
    });
}

function gql(query, callback) {
    if (!accessToken) {
        console.log('Valid access Token is needed to execute GQL calls.')
        return;
    }
    var data = JSON.stringify({ query: query });
    request({
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Bearer ' + accessToken
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/api/graphql',
        body: data,
        method: 'POST'
    }, function (err, res, body) {
        if (res.statusCode === 401) {
            console.log('Should Authorize...');
            Authorize(() => gql(query, callback));
        }
        else {
            var data = JSON.parse(body).data;
            if (callback) callback(data);
        }
    });
}

function readTickets(callback) {
    request({
        method: 'POST',
        uri: 'https://pos.gloriafood.com/pos/order/pop',
        headers: {
            'Authorization': gloriaFoodKey,
            'Accept': 'application/json',
            'Glf-Api-Version': '2'
        }
    }, function (err, res, body) {
        if (err) {
            console.log(`problem with request: ${err.message}`);
        } else {
            console.log(body);
            callback(JSON.parse(body));
        }
    });
}

app.get('/', function (req, res) {
    res.send(`Welcome to Gloria Food integration application.
    <br/>
    <ul>
        <li><a href="/gqltest">Click here to retrieve product list</a></li>
        <li><a href="/test">Click here to create a test ticket</a></li>
    </ul>`);
});

app.get('/gqltest', function (req, res) {
    gql('{getProducts{id,name,price}}', (data) => {
        data.getProducts.forEach(x => res.write(`<div>${x.name} $${x.price}</div>`))
        res.end();
    });
});

app.get('/ctest', function (req, res) {
    loadCustomer({ phone: "222-344 1123" }, data => {
        res.send(JSON.stringify(data));
    })
});

app.get('/test', function (req, res) {
    processTickets(JSON.parse(getTestData()));
    res.send("Ticket Created! See log for details");
});

app.listen(3000, function () {
    console.log('Gloria Food integration app listening on port 3000!');
    Authorize(() => loop());
});

function loop() {
    if (!accessToken) {
        console.log('There is no valid access token. Skipping...')
        Authorize();
    }
    else if (accessTokenExpires < new Date()) {
        console.log('Access Token Expired. Reauthenticating...');
        Authorize(() => loop());
        return;
    }
    else {
        console.log('Reading Tickets...');
        readTickets((tickets) => processTickets(tickets));
    }
    setTimeout(loop, timeout);
}

function processTickets(tickets) {
    if (tickets.count == 0) return;
    tickets.orders.forEach((order) => processOrder(order));
}

function processOrder(order) {
	
	// Format phone number
	if (formatPhoneNumber) {
		order.client_phone = libphonenumber.formatNumber({ country: formatPhoneNumberCountry, phone: order.client_phone }, formatPhoneNumberFormat);

		if (formatPhoneNumberHyphen) {
			order.client_phone = order.client_phone.replace(/ +/g, '-');
		}
		else {
			order.client_phone = order.client_phone.replace(/-+/g, '');
		}
		
		if (formatPhoneNumberNoSpaces) { order.client_phone = order.client_phone.replace(/ +/g, ''); }
	}
	
    var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        address: order.client_address,
        newCustomer: false
    }
    loadCustomer(customer, customer => {
        var services = order.items
            .filter(x => x.type === 'tip' || x.type === 'delivery_fee')
            .map(x => { return { name: getCalculationName(x.type), amount: x.price }; })
            .filter(x => x.name);
        loadItems(order.items.map(x => processItem(x)), items => {
            createTicket(customer, items, order.instructions, order.fulfill_at, services, ticketId => {
                gql('mutation m {postTicketRefreshMessage(id:0){id}}', () => {
                    console.log(`Ticket ${ticketId} created...`);
                });
            });
        });
    });
}

function getCalculationName(name) {
    if (name === 'tip') return tipCalculation;
    if (name === 'delivery_fee') return deliveryFeeCalculation;
    return undefined;
}

function loadItems(items, callback) {
    var script = getLoadItemsScript(items);
    gql(script, data => {
        callback(items.filter(x => x.type === 'item').map(item => {
            return {
                id: item.id,
                name: item.name,
                type: item.type,
                sambaName: data[`i${item.id}`][0] ? data[`i${item.id}`][0].name : miscProductName,
                price: item.price,
                quantity: item.quantity,
                instructions: item.instructions,
                options: item.options
            }
        }));
    });
}

function isNewCustomer(customer) {
    if (customer.states && customer.states.find(x => x.stateName === 'CStatus')) {
        return customer.states.find(x => x.stateName === 'CStatus').state === 'Unconfirmed';
    }
    return false;
}

function createTicket(customer, items, instructions, fulfill_at, services, callback) {
    var newCustomer = isNewCustomer(customer);
    gql(getAddTicketScript(items, customer.name, newCustomer, instructions, fulfill_at, services), data => {
        if (newCustomer)
            callback(data.addTicket.id);
        else printTicketToKitchen(data.addTicket.id, () => callback(data.addTicket.id));
    });
}

function printTicketToKitchen(ticketId, callback) {
    gql(getKitchenPrintScript(ticketId), (data) => {
        if (additionalPrintJobs && additionalPrintJobs.length > 0) {
            var scripts = additionalPrintJobs.map((x) => getAdditionalPrintScript(x, ticketId)).join('\r\n');
            gql(scripts, callback);
        } else callback(data);
    });
}

function loadCustomer(customer, callback) {
    gql(getIsEntityExistsScript(customer), (data) => {
        if (!data.isEntityExists) {
            createCustomer(customer, callback);
        } else getCustomer(customer.phone, callback);
    });
}

function createCustomer(customer, callback) {
    gql(getAddCustomerScript(customer), (data) => {
        gql(getNewCustomerStateScript(customer), () => {
            getCustomer(data.addEntity.name, callback);
        })
    });
}

function getCustomer(customerName, callback) {
    gql(getCustomerScript(customerName), (data) => {
        callback(data.getEntity);
    });
}

function getLoadItemsScript(items) {
    var part = items.map(item => `i${item.id}: getProducts(itemTag:{name:"${itemTagName}",value:"${item.name}"}){name} `);
    return `{${part}}`;
}

function getCustomerScript(name) {
    return `{getEntity(type:"${customerEntityType}",name:"${name}"){name,customData{name,value},states{stateName,state}}}`;
}

function getIsEntityExistsScript(customer) {
    return `{isEntityExists(type:"${customerEntityType}",name:"${customer.phone}")}`;
}

function getAddCustomerScript(customer) {
    return `
    mutation m{addEntity(entity:{
        entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
            {name:"First Name",value:"${customer.firstName}"},
            {name:"Last Name",value:"${customer.lastName}"},
            {name:"כתובת",value:"${customer.address}"},
            {name:"EMail",value:"${customer.email}"}
        ]})
        {name}
    }`;
}

function getNewCustomerStateScript(customer) {
    return `mutation m{updateEntityState(entityTypeName:"${customerEntityType}",entityName:"${customer.phone}",state:"Unconfirmed",stateName:"CStatus"){name}}`;
}

function getKitchenPrintScript(ticketId) {
    return `mutation m {
                executePrintJob(name: "${printJobName}", ticketId: ${ticketId}, 
                    orderStateFilters: [{stateName: "Status", state: "New"}],
                    nextOrderStates:[{stateName:"Status",currentState:"New",state:"Submitted"}]) 
                {name}
            }`;
}

function getAdditionalPrintScript(name, ticketId) {
    var mName = name.split(' ').join('_');
    return `mutation m_${mName} {
                n_${mName}:executePrintJob(name: "${name}", ticketId: ${ticketId}) 
                {name}
            }`;
}

function GetOrderTags(order) {
    if (order.options) {
        var options = order.options.map(x => `{tagName:"Default",tag:"${x.name}",price:${x.price},quantity:${x.quantity}}`);
        if (order.instructions && order.instructions !== '') {
            options.push(`{tagName:"Default",tag:"Instructions",note:"${order.instructions}"}`);
        }
        var result = options.join();
        return `tags:[${result}],`
    }
    return "";
}

function GetOrderPrice(order) {
    if (order.price > 0)
        return `price:${order.price},`;
    return "";
}

function getAddTicketScript(orders, customerName, newCustomer, instructions, fulfill_at, services) {
    var orderLines = orders.map(order => {
        return `{
            name:"${order.sambaName ? order.sambaName : order.name}",
            menuItemName:"${order.sambaName === miscProductName ? order.name : ''}",
            quantity:${order.quantity > 0 ? order.quantity : 1},
            ${GetOrderPrice(order)}
            ${GetOrderTags(order)}
            states:[
                {stateName:"Status",state:"New"}
            ]
        }`;
    });

    var entityPart = customerName
        ? `entities:[{entityType:"${customerEntityType}",name:"${customerName}"}],`
        : '';
    var calculationsPart = services
        ? `calculations:[${services.map(x => `{name:"${x.name}",amount:${x.amount}}`).join()}],`
        : '';

    var notePart = instructions && instructions !== ''
        ? `note:"${instructions}",`
        : '';
    var result = `
        mutation m{addTicket(
            ticket:{type:"${ticketType}",
                department:"${departmentName}",
                user:"${userName}",
                terminal:"${terminalName}",
                ${notePart}
                ${entityPart}
                states:[
                    {stateName:"Status",state:"Unpaid"},
                    {stateName:"Source",state:"Gloria"},
                    {stateName:"Delivery",state:"${newCustomer ? 'Unconfirmed' : 'Waiting'}"}
                ],
                tags:[{tagName:"Delivery Minutes",tag:"${Math.ceil(Math.abs(new Date(fulfill_at) - Date.now()) / 60000)}"}],
                ${calculationsPart}
                orders:[${orderLines.join()}]
            }){id}}`;
    return result;
}

function processItem(item) {
    var result = {
        id: item.id,
        name: item.name,
        type: item.type,
        price: item.price,
        quantity: item.quantity,
        instructions: item.instructions,
        options: item.options.map(x => { return { name: x.name, quantity: x.quantity, price: x.price } })
    };
    return result;
}

var getTestData = () => `{
    "count": 1,
    "orders": [
        {
            "coupons": [],
            "id": 776113,
            "restaurant_id": 4172,
            "client_id": 188995,
            "type": "delivery",
            "source": "website",
            "sub_total_price": 47.88,
            "tax_value": 4.13,
            "total_price": 62.41,
            "client_first_name": "John",
            "client_last_name": "Pink",
            "client_email": "john.brown@sambapos.com",
            "client_phone": "${Math.floor((Math.random() * 300) + 200)}-456 6699",
            "pin_skipped": 0,
            "restaurant_name": "John's Excellent Pizza",
            "restaurant_phone": "+15558964567",
            "restaurant_country": "United States of America",
            "restaurant_state": "California",
            "restaurant_city": "San Francisco",
            "restaurant_street": "10 Market Street",
            "restaurant_zipcode": "1234678",
            "restaurant_latitude": "37.7944872589999",
            "restaurant_longitude": "-122.395311999999",
            "instructions": "Deliver ASAP",
            "currency": "USD",
            "latitude": "37.79448725889753",
            "longitude": "-122.395311680426",
            "tax_type": "NET",
            "tax_name": "Sales Tax",
            "fulfill_at": "${ new Date(new Date().getTime() + 25 * 60000).toISOString()}",
            "pos_system_id": 1,
            "restaurant_key": "8yCPCvb3dDo1k",
            "api_version": 2,
            "payment": "ONLINE",
            "client_address": "21 Market Street, San Francisco",
            "items": [
                {
                    "id": 1678316,
                    "name": "DELIVERY_FEE",
                    "total_item_price": 5,
                    "price": 5,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": null,
                    "type": "delivery_fee",
                    "tax_rate": 0.1,
                    "tax_value": 0.5,
                    "parent_id": null,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678317,
                    "name": "TIP",
                    "total_item_price": 5.67,
                    "price": 5.67,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": null,
                    "type": "tip",
                    "tax_rate": 0.05,
                    "tax_value": 0.2702,
                    "parent_id": null,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "GROSS",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678322,
                    "name": "Pizza Margherita",
                    "total_item_price": 8.2,
                    "price": 7,
                    "quantity": 1,
                    "instructions": "",
                    "type_id": 58424,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 8.2,
                    "options": [
                        {
                            "id": 1771325,
                            "name": "Small",
                            "price": 0,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771326,
                            "name": "Crispy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771327,
                            "name": "Extra mozzarella",
                            "price": 1.2,
                            "group_name": "Extra Toppings (Small)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678324,
                    "name": "Pizza Prosciutto",
                    "total_item_price": 11.7,
                    "price": 8,
                    "quantity": 1,
                    "instructions": "User may enter a very long description for the pizza. For example he may want to explain what kind of sauce he wants or how dough should be cooked. So we should handle that case properly.",
                    "type_id": 58425,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.819,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": [
                        {
                            "id": 1771331,
                            "name": "Large",
                            "price": 2,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771332,
                            "name": "Crispy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771333,
                            "name": "Extra mozzarella",
                            "price": 1.7,
                            "group_name": "Extra Toppings (Large)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678331,
                    "name": "Pizza Prosciutto",
                    "total_item_price": 8.7,
                    "price": 8,
                    "quantity": 2,
                    "instructions": "no salt",
                    "type_id": 58425,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.609,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": [
                        {
                            "id": 1771343,
                            "name": "Small",
                            "price": 0,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771344,
                            "name": "Fluffy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771345,
                            "name": "Corn",
                            "price": 0.7,
                            "group_name": "Extra Toppings (Small)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678332,
                    "name": "2 + 1 Pizza Special",
                    "total_item_price": 28.6,
                    "price": 0,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": 251,
                    "type": "promo_item",
                    "tax_rate": 0.07,
                    "tax_value": 1.3566,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": 1.02,
                    "tax_type": "NET",
                    "item_discount": 8.2,
                    "options": []
                },
                {
                    "id": 1678334,
                    "name": "Spaghetti Bolognese",
                    "total_item_price": 18,
                    "price": 9,
                    "quantity": 2,
                    "instructions": "",
                    "type_id": 58426,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 1.197,
                    "parent_id": null, 
                    "cart_discount_rate": 0.05,
                    "cart_discount": 0.9,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678335,
                    "name": "Spaghetti Frutti di Mare",
                    "total_item_price": 12,
                    "price": 12,
                    "quantity": 1,
                    "instructions": "",
                    "type_id": 58427,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.798,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": 0.6,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678336,
                    "name": "5% off total larger than 40$",
                    "total_item_price": 0,
                    "price": 0,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": 250,
                    "type": "promo_cart",
                    "tax_rate": 0.07,
                    "tax_value": 0,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": -2.52,
                    "tax_type": "NET",
                    "item_discount": 2.52,
                    "options": []
                }
            ]
        }
    ]
}`;

bf406d40d6046e08ada92c8410c3722070942d9c_2_690x377

I’m no wiz with javascript but here would be my suggestions:

this code block (lines 181-188):

var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        address: order.client_address,
        newCustomer: false
    }

you’ll want to add what fields you want to use.

Because client_address_parts can be null you’ll want to check for that before assigning values to variables.

something like this for the above code block (untested):

var street = null;
var bloc = null;
var floor = null;
var apartment = null;
var intercom = null;
var moreAddress = null;
var zipcode = null;
var city = null;

if (order.client_address_parts != null){
    street = order.client_address_parts.street;
    bloc = order.client_address_parts.bloc;
    floor = order.client_address_parts.floor;
    apartment = order.client_address_parts.apartment;
    intercom = order.client_address_parts.intercom;
    moreAddress = order.client_address_parts.more_address;
    zipcode = order.client_address_parts.zipcode;
    city = order.client_address_parts.city;
}

var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        street: street,
        city: city,
        bloc: bloc,
        floor: floor,
        apartment: apartment,
        intercom: intercom,
        moreAddress: moreAddress,
        zipcode: zipcode,
        newCustomer: false
    }

then for this block (lines 288-299):

function getAddCustomerScript(customer) {
    return `
    mutation m{addEntity(entity:{
        entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
            {name:"First Name",value:"${customer.firstName}"},
            {name:"Last Name",value:"${customer.lastName}"},
            {name:"?????",value:"${customer.address}"},
            {name:"EMail",value:"${customer.email}"}
        ]})
        {name}
    }`;
}

add what fields you need from the customer object. I’m not sure why the customer address field name has “???” but you should be able to explicitly declare it, or, well, remove that line as you’ll be specifying the parts of the address you want to use.

add this to the array for each field you want passed (remember the list is comma separated):

{name:"CustomFieldName",value:"${customer.FieldName}"}

where “CustomFieldName” is your entity custom field name and “FieldName” is the field you want to reference from the customer object.

1 Like

thanks i will test this…

@Memo
i get this error.
ReferenceError: getCustomer is not defined
at C:\Program Files\SambaPOS5\gloria\script.js:284:16
at Request._callback (C:\Program Files\SambaPOS5\gloria\script.js:87:27)
at Request.self.callback (C:\Program Files\SambaPOS5\gloria\node_modules\request\request.js:185:22)
at Request.emit (node:events:369:20)
at Request. (C:\Program Files\SambaPOS5\gloria\node_modules\request\request.js:1154:10)
at Request.emit (node:events:369:20)
at IncomingMessage. (C:\Program Files\SambaPOS5\gloria\node_modules\request\request.js:1076:12)
at Object.onceWrapper (node:events:475:28)
at IncomingMessage.emit (node:events:381:22)
at endReadableNT (node:internal/streams/readable:1307:12)

this is the updated script did i updated it ok??
var express = require(‘express’);
var request = require(‘request’);
var querystring = require(‘querystring’);
var libphonenumber = require(‘libphonenumber-js’);
var app = express();

    var messageServer = 'localhost';
    var messageServerPort = 9000;
    var gloriaFoodKey = 'XXXXXXX';
    var serverKey = '553181';
    var timeout = 30000;
    var customerEntityType = 'Customers';
    var itemTagName = 'Gloria Name';
    var ticketType = 'Ticket';
    var departmentName = 'pizza';
    var userName = 'איציק';
    var terminalName = 'Server';
    var printJobName = 'Print Bill';
    var additionalPrintJobs = [];  // array of additional print job names
    var miscProductName = 'Misc';
    var deliveryFeeCalculation = 'Delivery Service';
    var tipCalculation = 'Tip';
    var accessToken = undefined;
    var accessTokenExpires = '';

    var formatPhoneNumber = true;
    var formatPhoneNumberCountry = 'IL'; // set to your ISO country code
    var formatPhoneNumberFormat = 'National'; // format type, this should suffice
    var formatPhoneNumberHyphen = false;
    var formatPhoneNumberNoSpaces = true;


    function Authorize(callback) {
        accessToken = undefined;
        var form = { grant_type: 'client_credentials', client_secret: serverKey, client_id: 'gloria' };
        var formData = querystring.stringify(form);
        var contentLength = formData.length;

        request({
            headers: {
                'Content-Length': contentLength,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            uri: 'http://' + messageServer + ':' + messageServerPort + '/Token',
            body: formData,
            method: 'POST'
        }, function (err, res, body) {
            if (err) {
                console.log('Error while trying to authorize >', err.message);
                if (callback) callback();
            }
            else if (res.statusCode === 400) {
                console.log(body);
                if (callback) callback();
            }
            else {
                var result = JSON.parse(body);
                accessToken = result.access_token;
                accessTokenExpires = new Date(result['.expires']);
                if (callback) callback();
            }
        });
    }

    function gql(query, callback) {
        if (!accessToken) {
            console.log('Valid access Token is needed to execute GQL calls.')
            return;
        }
        var data = JSON.stringify({ query: query });
        request({
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Authorization': 'Bearer ' + accessToken
            },
            uri: 'http://' + messageServer + ':' + messageServerPort + '/api/graphql',
            body: data,
            method: 'POST'
        }, function (err, res, body) {
            if (res.statusCode === 401) {
                console.log('Should Authorize...');
                Authorize(() => gql(query, callback));
            }
            else {
                var data = JSON.parse(body).data;
                if (callback) callback(data);
            }
        });
    }

    function readTickets(callback) {
        request({
            method: 'POST',
            uri: 'https://pos.gloriafood.com/pos/order/pop',
            headers: {
                'Authorization': gloriaFoodKey,
                'Accept': 'application/json',
                'Glf-Api-Version': '2'
            }
        }, function (err, res, body) {
            if (err) {
                console.log(`problem with request: ${err.message}`);
            } else {
                console.log(body);
                callback(JSON.parse(body));
            }
        });
    }

    app.get('/', function (req, res) {
        res.send(`Welcome to Gloria Food integration application.
        <br/>
        <ul>
            <li><a href="/gqltest">Click here to retrieve product list</a></li>
            <li><a href="/test">Click here to create a test ticket</a></li>
        </ul>`);
    });

    app.get('/gqltest', function (req, res) {
        gql('{getProducts{id,name,price}}', (data) => {
            data.getProducts.forEach(x => res.write(`<div>${x.name} $${x.price}</div>`))
            res.end();
        });
    });

    app.get('/ctest', function (req, res) {
        loadCustomer({ phone: "222-344 1123" }, data => {
            res.send(JSON.stringify(data));
        })
    });

    app.get('/test', function (req, res) {
        processTickets(JSON.parse(getTestData()));
        res.send("Ticket Created! See log for details");
    });

    app.listen(3000, function () {
        console.log('Gloria Food integration app listening on port 3000!');
        Authorize(() => loop());
    });

    function loop() {
        if (!accessToken) {
            console.log('There is no valid access token. Skipping...')
            Authorize();
        }
        else if (accessTokenExpires < new Date()) {
            console.log('Access Token Expired. Reauthenticating...');
            Authorize(() => loop());
            return;
        }
        else {
            console.log('Reading Tickets...');
            readTickets((tickets) => processTickets(tickets));
        }
        setTimeout(loop, timeout);
    }

    function processTickets(tickets) {
        if (tickets.count == 0) return;
        tickets.orders.forEach((order) => processOrder(order));
    }

    function processOrder(order) {
    	
    	// Format phone number
    	if (formatPhoneNumber) {
    		order.client_phone = libphonenumber.formatNumber({ country: formatPhoneNumberCountry, phone: order.client_phone }, formatPhoneNumberFormat);

    		if (formatPhoneNumberHyphen) {
    			order.client_phone = order.client_phone.replace(/ +/g, '-');
    		}
    		else {
    			order.client_phone = order.client_phone.replace(/-+/g, '');
    		}
    		
    		if (formatPhoneNumberNoSpaces) { order.client_phone = order.client_phone.replace(/ +/g, ''); }
    	}
    	
    var street = null;
    var bloc = null;
    var floor = null;
    var apartment = null;
    var intercom = null;
    var moreAddress = null;
    var zipcode = null;
    var city = null;

    if (order.client_address_parts != null){
        street = order.client_address_parts.street;
        bloc = order.client_address_parts.bloc;
        floor = order.client_address_parts.floor;
        apartment = order.client_address_parts.apartment;
        intercom = order.client_address_parts.intercom;
        moreAddress = order.client_address_parts.more_address;
        zipcode = order.client_address_parts.zipcode;
        city = order.client_address_parts.city;
    }

    var customer = {
            firstName: order.client_first_name,
            lastName: order.client_last_name,
            email: order.client_email,
            phone: order.client_phone,
            street: street,
            city: city,
            bloc: bloc,
            floor: floor,
            apartment: apartment,
            intercom: intercom,
            moreAddress: moreAddress,
            zipcode: zipcode,
            newCustomer: false
        }
        loadCustomer(customer, customer => {
            var services = order.items
                .filter(x => x.type === 'tip' || x.type === 'delivery_fee')
                .map(x => { return { name: getCalculationName(x.type), amount: x.price }; })
                .filter(x => x.name);
            loadItems(order.items.map(x => processItem(x)), items => {
                createTicket(customer, items, order.instructions, order.fulfill_at, services, ticketId => {
                    gql('mutation m {postTicketRefreshMessage(id:0){id}}', () => {
                        console.log(`Ticket ${ticketId} created...`);
                    });
                });
            });
        });
    }

    function getCalculationName(name) {
        if (name === 'tip') return tipCalculation;
        if (name === 'delivery_fee') return deliveryFeeCalculation;
        return undefined;
    }

    function loadItems(items, callback) {
        var script = getLoadItemsScript(items);
        gql(script, data => {
            callback(items.filter(x => x.type === 'item').map(item => {
                return {
                    id: item.id,
                    name: item.name,
                    type: item.type,
                    sambaName: data[`i${item.id}`][0] ? data[`i${item.id}`][0].name : miscProductName,
                    price: item.price,
                    quantity: item.quantity,
                    instructions: item.instructions,
                    options: item.options
                }
            }));
        });
    }

    function isNewCustomer(customer) {
        if (customer.states && customer.states.find(x => x.stateName === 'CStatus')) {
            return customer.states.find(x => x.stateName === 'CStatus').state === 'Unconfirmed';
        }
        return false;
    }

    function createTicket(customer, items, instructions, fulfill_at, services, callback) {
        var newCustomer = isNewCustomer(customer);
        gql(getAddTicketScript(items, customer.name, newCustomer, instructions, fulfill_at, services), data => {
            if (newCustomer)
                callback(data.addTicket.id);
            else printTicketToKitchen(data.addTicket.id, () => callback(data.addTicket.id));
        });
    }

    function printTicketToKitchen(ticketId, callback) {
        gql(getKitchenPrintScript(ticketId), (data) => {
            if (additionalPrintJobs && additionalPrintJobs.length > 0) {
                var scripts = additionalPrintJobs.map((x) => getAdditionalPrintScript(x, ticketId)).join('\r\n');
                gql(scripts, callback);
            } else callback(data);
        });
    }

    function loadCustomer(customer, callback) {
        gql(getIsEntityExistsScript(customer), (data) => {
            if (!data.isEntityExists) {
                createCustomer(customer, callback);
            } else getCustomer(customer.phone, callback);
        });
    }

    function getAddCustomerScript(customer) {
        return `
        mutation m{addEntity(entity:{
            entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
                {name:"First Name",value:"${customer.firstName}"},
                {name:"Last Name",value:"${customer.lastName}"},
                {name:"address",value:"${customer.address}"},
                {name:"EMail",value:"${customer.email}"},
                {name:"street",value:"${customer.street}"}
            ]})
            {name}
        }`;
    }

    function getLoadItemsScript(items) {
        var part = items.map(item => `i${item.id}: getProducts(itemTag:{name:"${itemTagName}",value:"${item.name}"}){name} `);
        return `{${part}}`;
    }

    function getCustomerScript(name) {
        return `{getEntity(type:"${customerEntityType}",name:"${name}"){name,customData{name,value},states{stateName,state}}}`;
    }

    function getIsEntityExistsScript(customer) {
        return `{isEntityExists(type:"${customerEntityType}",name:"${customer.phone}")}`;
    }

    function getAddCustomerScript(customer) {
        return `
        mutation m{addEntity(entity:{
            entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
                {name:"First Name",value:"${customer.firstName}"},
                {name:"Last Name",value:"${customer.lastName}"},
                {name:"address",value:"${customer.address}"},
                {name:"EMail",value:"${customer.email}"}
            ]})
            {name}
        }`;
    }

    function getNewCustomerStateScript(customer) {
        return `mutation m{updateEntityState(entityTypeName:"${customerEntityType}",entityName:"${customer.phone}",state:"Unconfirmed",stateName:"CStatus"){name}}`;
    }

    function getKitchenPrintScript(ticketId) {
        return `mutation m {
                    executePrintJob(name: "${printJobName}", ticketId: ${ticketId}, 
                        orderStateFilters: [{stateName: "Status", state: "New"}],
                        nextOrderStates:[{stateName:"Status",currentState:"New",state:"Submitted"}]) 
                    {name}
                }`;
    }

    function getAdditionalPrintScript(name, ticketId) {
        var mName = name.split(' ').join('_');
        return `mutation m_${mName} {
                    n_${mName}:executePrintJob(name: "${name}", ticketId: ${ticketId}) 
                    {name}
                }`;
    }

    function GetOrderTags(order) {
        if (order.options) {
            var options = order.options.map(x => `{tagName:"Default",tag:"${x.name}",price:${x.price},quantity:${x.quantity}}`);
            if (order.instructions && order.instructions !== '') {
                options.push(`{tagName:"Default",tag:"Instructions",note:"${order.instructions}"}`);
            }
            var result = options.join();
            return `tags:[${result}],`
        }
        return "";
    }

    function GetOrderPrice(order) {
        if (order.price > 0)
            return `price:${order.price},`;
        return "";
    }

    function getAddTicketScript(orders, customerName, newCustomer, instructions, fulfill_at, services) {
        var orderLines = orders.map(order => {
            return `{
                name:"${order.sambaName ? order.sambaName : order.name}",
                menuItemName:"${order.sambaName === miscProductName ? order.name : ''}",
                quantity:${order.quantity > 0 ? order.quantity : 1},
                ${GetOrderPrice(order)}
                ${GetOrderTags(order)}
                states:[
                    {stateName:"Status",state:"New"}
                ]
            }`;
        });

        var entityPart = customerName
            ? `entities:[{entityType:"${customerEntityType}",name:"${customerName}"}],`
            : '';
        var calculationsPart = services
            ? `calculations:[${services.map(x => `{name:"${x.name}",amount:${x.amount}}`).join()}],`
            : '';

        var notePart = instructions && instructions !== ''
            ? `note:"${instructions}",`
            : '';
        var result = `
            mutation m{addTicket(
                ticket:{type:"${ticketType}",
                    department:"${departmentName}",
                    user:"${userName}",
                    terminal:"${terminalName}",
                    ${notePart}
                    ${entityPart}
                    states:[
                        {stateName:"Status",state:"Unpaid"},
                        {stateName:"Source",state:"Gloria"},
                        {stateName:"Delivery",state:"${newCustomer ? 'Unconfirmed' : 'Waiting'}"}
                    ],
                    tags:[{tagName:"Delivery Minutes",tag:"${Math.ceil(Math.abs(new Date(fulfill_at) - Date.now()) / 60000)}"}],
                    ${calculationsPart}
                    orders:[${orderLines.join()}]
                }){id}}`;
        return result;
    }

    function processItem(item) {
        var result = {
            id: item.id,
            name: item.name,
            type: item.type,
            price: item.price,
            quantity: item.quantity,
            instructions: item.instructions,
            options: item.options.map(x => { return { name: x.name, quantity: x.quantity, price: x.price } })
        };
        return result;
    }

    var getTestData = () => `{
        "count": 1,
        "orders": [
            {
                "coupons": [],
                "id": 776113,
                "restaurant_id": 4172,
                "client_id": 188995,
                "type": "delivery",
                "source": "website",
                "sub_total_price": 47.88,
                "tax_value": 4.13,
                "total_price": 62.41,
                "client_first_name": "John",
                "client_last_name": "Pink",
                "client_email": "john.brown@sambapos.com",
                "client_phone": "${Math.floor((Math.random() * 300) + 200)}-456 6699",
                "pin_skipped": 0,
                "restaurant_name": "John's Excellent Pizza",
                "restaurant_phone": "+15558964567",
                "restaurant_country": "United States of America",
                "restaurant_state": "California",
                "restaurant_city": "San Francisco",
                "restaurant_street": "10 Market Street",
                "restaurant_zipcode": "1234678",
                "restaurant_latitude": "37.7944872589999",
                "restaurant_longitude": "-122.395311999999",
                "instructions": "Deliver ASAP",
                "currency": "USD",
                "latitude": "37.79448725889753",
                "longitude": "-122.395311680426",
                "tax_type": "NET",
                "tax_name": "Sales Tax",
                "fulfill_at": "${ new Date(new Date().getTime() + 25 * 60000).toISOString()}",
                "pos_system_id": 1,
                "restaurant_key": "8yCPCvb3dDo1k",
                "api_version": 2,
                "payment": "ONLINE",
                "client_address": "21 Market Street, San Francisco",
                "items": [
                    {
                        "id": 1678316,
                        "name": "DELIVERY_FEE",
                        "total_item_price": 5,
                        "price": 5,
                        "quantity": 1,
                        "instructions": null,
                        "type_id": null,
                        "type": "delivery_fee",
                        "tax_rate": 0.1,
                        "tax_value": 0.5,
                        "parent_id": null,
                        "cart_discount_rate": 0,
                        "cart_discount": 0,
                        "tax_type": "NET",
                        "item_discount": 0,
                        "options": []
                    },
                    {
                        "id": 1678317,
                        "name": "TIP",
                        "total_item_price": 5.67,
                        "price": 5.67,
                        "quantity": 1,
                        "instructions": null,
                        "type_id": null,
                        "type": "tip",
                        "tax_rate": 0.05,
                        "tax_value": 0.2702,
                        "parent_id": null,
                        "cart_discount_rate": 0,
                        "cart_discount": 0,
                        "tax_type": "GROSS",
                        "item_discount": 0,
                        "options": []
                    },
                    {
                        "id": 1678322,
                        "name": "Pizza Margherita",
                        "total_item_price": 8.2,
                        "price": 7,
                        "quantity": 1,
                        "instructions": "",
                        "type_id": 58424,
                        "type": "item",
                        "tax_rate": 0.07,
                        "tax_value": 0,
                        "parent_id": 1678332,
                        "cart_discount_rate": 0,
                        "cart_discount": 0,
                        "tax_type": "NET",
                        "item_discount": 8.2,
                        "options": [
                            {
                                "id": 1771325,
                                "name": "Small",
                                "price": 0,
                                "group_name": "Size",
                                "quantity": 1,
                                "type": "size"
                            },
                            {
                                "id": 1771326,
                                "name": "Crispy",
                                "price": 0,
                                "group_name": "Crust",
                                "quantity": 1,
                                "type": "option"
                            },
                            {
                                "id": 1771327,
                                "name": "Extra mozzarella",
                                "price": 1.2,
                                "group_name": "Extra Toppings (Small)",
                                "quantity": 1,
                                "type": "option"
                            }
                        ]
                    },
                    {
                        "id": 1678324,
                        "name": "Pizza Prosciutto",
                        "total_item_price": 11.7,
                        "price": 8,
                        "quantity": 1,
                        "instructions": "User may enter a very long description for the pizza. For example he may want to explain what kind of sauce he wants or how dough should be cooked. So we should handle that case properly.",
                        "type_id": 58425,
                        "type": "item",
                        "tax_rate": 0.07,
                        "tax_value": 0.819,
                        "parent_id": 1678332,
                        "cart_discount_rate": 0,
                        "cart_discount": 0,
                        "tax_type": "NET",
                        "item_discount": 0,
                        "options": [
                            {
                                "id": 1771331,
                                "name": "Large",
                                "price": 2,
                                "group_name": "Size",
                                "quantity": 1,
                                "type": "size"
                            },
                            {
                                "id": 1771332,
                                "name": "Crispy",
                                "price": 0,
                                "group_name": "Crust",
                                "quantity": 1,
                                "type": "option"
                            },
                            {
                                "id": 1771333,
                                "name": "Extra mozzarella",
                                "price": 1.7,
                                "group_name": "Extra Toppings (Large)",
                                "quantity": 1,
                                "type": "option"
                            }
                        ]
                    },
                    {
                        "id": 1678331,
                        "name": "Pizza Prosciutto",
                        "total_item_price": 8.7,
                        "price": 8,
                        "quantity": 2,
                        "instructions": "no salt",
                        "type_id": 58425,
                        "type": "item",
                        "tax_rate": 0.07,
                        "tax_value": 0.609,
                        "parent_id": 1678332,
                        "cart_discount_rate": 0,
                        "cart_discount": 0,
                        "tax_type": "NET",
                        "item_discount": 0,
                        "options": [
                            {
                                "id": 1771343,
                                "name": "Small",
                                "price": 0,
                                "group_name": "Size",
                                "quantity": 1,
                                "type": "size"
                            },
                            {
                                "id": 1771344,
                                "name": "Fluffy",
                                "price": 0,
                                "group_name": "Crust",
                                "quantity": 1,
                                "type": "option"
                            },
                            {
                                "id": 1771345,
                                "name": "Corn",
                                "price": 0.7,
                                "group_name": "Extra Toppings (Small)",
                                "quantity": 1,
                                "type": "option"
                            }
                        ]
                    },
                    {
                        "id": 1678332,
                        "name": "2 + 1 Pizza Special",
                        "total_item_price": 28.6,
                        "price": 0,
                        "quantity": 1,
                        "instructions": null,
                        "type_id": 251,
                        "type": "promo_item",
                        "tax_rate": 0.07,
                        "tax_value": 1.3566,
                        "parent_id": null,
                        "cart_discount_rate": 0.05,
                        "cart_discount": 1.02,
                        "tax_type": "NET",
                        "item_discount": 8.2,
                        "options": []
                    },
                    {
                        "id": 1678334,
                        "name": "Spaghetti Bolognese",
                        "total_item_price": 18,
                        "price": 9,
                        "quantity": 2,
                        "instructions": "",
                        "type_id": 58426,
                        "type": "item",
                        "tax_rate": 0.07,
                        "tax_value": 1.197,
                        "parent_id": null, 
                        "cart_discount_rate": 0.05,
                        "cart_discount": 0.9,
                        "tax_type": "NET",
                        "item_discount": 0,
                        "options": []
                    },
                    {
                        "id": 1678335,
                        "name": "Spaghetti Frutti di Mare",
                        "total_item_price": 12,
                        "price": 12,
                        "quantity": 1,
                        "instructions": "",
                        "type_id": 58427,
                        "type": "item",
                        "tax_rate": 0.07,
                        "tax_value": 0.798,
                        "parent_id": null,
                        "cart_discount_rate": 0.05,
                        "cart_discount": 0.6,
                        "tax_type": "NET",
                        "item_discount": 0,
                        "options": []
                    },
                    {
                        "id": 1678336,
                        "name": "5% off total larger than 40$",
                        "total_item_price": 0,
                        "price": 0,
                        "quantity": 1,
                        "instructions": null,
                        "type_id": 250,
                        "type": "promo_cart",
                        "tax_rate": 0.07,
                        "tax_value": 0,
                        "parent_id": null,
                        "cart_discount_rate": 0.05,
                        "cart_discount": -2.52,
                        "tax_type": "NET",
                        "item_discount": 2.52,
                        "options": []
                    }
                ]
            }
        ]
    }`;

CAN YOU PLEASE Update my acript as it shuld be?

Everything you changed seems syntactically correct. But comparing your script to others posted you’re missing functions: getCustomer, createCustomer, etc.

Using a script Jesse posted a few years ago, I think I’ve merged your changes and the number formatting over. You’ll have to test. Also, check the config at the top of the script. I think I copied everything over but please make sure.

Source @ pastebin

var express = require('express');
var request = require('request');
var querystring = require('querystring');
var libphonenumber = require('libphonenumber - js');
var app = express();
var messageServer = 'localhost';
var messageServerPort = 9000;
var gloriaFoodKey = 'XXXXXXX';
var serverKey = '553181';
var timeout = 30000;
var customerEntityType = 'Customers';
var itemTagName = 'Gloria Name';
var ticketType = 'Ticket';
var departmentName = 'pizza';
var userName = 'Administrator';
var terminalName = 'Server';
var printJobName = 'Print Bill';
var additionalPrintJobs = []; // array of additional print job names
var miscProductName = 'Misc';
var deliveryFeeCalculation = 'Delivery Service';
var tipCalculation = 'Tip';
var accessToken = undefined;
var accessTokenExpires = '';

var formatPhoneNumber = true;
var formatPhoneNumberCountry = 'IL'; // set to your ISO country code
var formatPhoneNumberFormat = 'National'; // format type, this should suffice
var formatPhoneNumberHyphen = false;
var formatPhoneNumberNoSpaces = true;

Authorize(loop());

function Authorize(callback) {
    accessToken = undefined;
    var form = {
        grant_type: 'client_credentials',
        client_secret: serverKey,
        client_id: 'gloria'
    };
    var formData = querystring.stringify(form);
    var contentLength = formData.length;

    request({
        headers: {
            'Content-Length': contentLength,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/Token',
        body: formData,
        method: 'POST'
    }, function (err, res, body) {
        if (err) {
            console.log('Error while trying to authorize >', err.message);
        } else if (res.statusCode === 400) {
            console.log(body);
            if (callback) callback();
        } else {
            var result = JSON.parse(body);
            accessToken = result.access_token;
            accessTokenExpires = new Date(result['.expires']);
            if (callback) callback();
        }
    });
}

function gql(query, callback) {
    if (!accessToken) {
        console.log('Valid access Token is needed to execute GQL calls.')
        return;
    }
    var data = JSON.stringify({
        query: query
    });
    request({
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Bearer ' + accessToken
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/api/graphql',
        body: data,
        method: 'POST'
    }, function (err, res, body) {
        if (res.statusCode === 401) {
            console.log('Should Authorize...');
            Authorize(() => gql(query, callback));
        } else {
            var data = JSON.parse(body).data;
            if (callback) callback(data);
        }
    });
}

function readTickets(callback) {
    request({
        method: 'POST',
        uri: 'https://pos.gloriafood.com/pos/order/pop',
        headers: {
            'Authorization': gloriaFoodKey,
            'Accept': 'application/json',
            'Glf-Api-Version': '2'
        }
    }, function (err, res, body) {
        if (err) {
            console.log(`problem with request: ${err.message}`);
        } else {
            callback(JSON.parse(body));
        }
    });
}

function loop() {
    if (!accessToken) {
        console.log('There is no valid access token. Skipping...')
        Authorize();
    } else if (accessTokenExpires < new Date()) {
        console.log('Access Token Expired. Reauthenticating...');
        Authorize(() => loop());
        return;
    } else {
        console.log('Reading Tickets...');
        readTickets((tickets) => processTickets(tickets));
    }
    setTimeout(loop, timeout);
}

function processTickets(tickets) {
    if (tickets.count == 0) return;
    tickets.orders.forEach((order) => processOrder(order));
}

function processOrder(order) {
    // Format phone number
    if (formatPhoneNumber) {
        order.client_phone = libphonenumber.formatNumber({
            country: formatPhoneNumberCountry,
            phone: order.client_phone
        }, formatPhoneNumberFormat);

        if (formatPhoneNumberHyphen) {
            order.client_phone = order.client_phone.replace(/ +/g, '-');
        } else {
            order.client_phone = order.client_phone.replace(/-+/g, '');
        }

        if (formatPhoneNumberNoSpaces) {
            order.client_phone = order.client_phone.replace(/ +/g, '');
        }
    }

    var street = null;
    var bloc = null;
    var floor = null;
    var apartment = null;
    var intercom = null;
    var moreAddress = null;
    var zipcode = null;
    var city = null;

    if (order.client_address_parts != null) {
        street = order.client_address_parts.street;
        bloc = order.client_address_parts.bloc;
        floor = order.client_address_parts.floor;
        apartment = order.client_address_parts.apartment;
        intercom = order.client_address_parts.intercom;
        moreAddress = order.client_address_parts.more_address;
        zipcode = order.client_address_parts.zipcode;
        city = order.client_address_parts.city;
    }

    var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        street: street,
        city: city,
        bloc: bloc,
        floor: floor,
        apartment: apartment,
        intercom: intercom,
        moreAddress: moreAddress,
        zipcode: zipcode,
        newCustomer: false
    }

    loadCustomer(customer, customer => {
        var services = order.items
            .filter(x => x.type === 'tip' || x.type === 'delivery_fee' || x.type === 'promo_cart')
            .map(x => {
                return {
                    name: getCalculationName(x.type),
                    amount: Math.abs((x.cart_discount_rate) * 100) || x.price
                };
            })
            .filter(x => x.name);
        loadItems(order.items.map(x => processItem(x)), items => {
            createTicket(customer, items, order.instructions, order.fulfill_at, services, order.payment, ticketId => {
                gql('mutation m {postTicketRefreshMessage(id:0){id}}', () => {
                    console.log(`Ticket ${ticketId} created...`);
                });
            });
        });
    });
}

function getCalculationName(name) {
    if (name === 'promo_cart') return promotionDiscount;
    if (name === 'tip') return tipCalculation;
    if (name === 'delivery_fee') return deliveryFeeCalculation;
    return undefined;
}

function loadItems(items, callback) {
    var script = getLoadItemsScript(items);
    gql(script, data => {
        callback(items.filter(x => x.type === 'item').map(item => {
            return {
                id: item.id,
                name: item.name,
                type: item.type,
                sambaName: data[`i${item.id}`][0] ? data[`i${item.id}`][0].name : miscProductName,
                price: item.price,
                quantity: item.quantity,
                instructions: item.instructions,
                options: item.options,
                portions: item.portions
            }
        }));
    });
}

function isNewCustomer(customer) {
    if (customer.states && customer.states.find(x => x.stateName === 'CStatus')) {
        return customer.states.find(x => x.stateName === 'CStatus').state === 'Unconfirmed';
    }
    return false;
}

function createTicket(customer, items, instructions, fulfill_at, services, payment, callback) {
    var newCustomer = isNewCustomer(customer);
    gql(getAddTicketScript(items, customer.name, newCustomer, instructions, fulfill_at, services, payment), data => {
        if (newCustomer)
            callback(data.addTicket.id);
        else printTicketToKitchen(data.addTicket.id, () => callback(data.addTicket.id));
    });
}

function printTicketToKitchen(ticketId, callback) {
    gql(getKitchenPrintScript(ticketId), callback);
}

function loadCustomer(customer, callback) {
    gql(getIsEntityExistsScript(customer), (data) => {
        if (!data.isEntityExists) {
            createCustomer(customer, callback);
        } else getCustomer(customer.phone, callback);
    });
}

function createCustomer(customer, callback) {
    gql(getAddCustomerScript(customer), (data) => {
        gql(getNewCustomerStateScript(customer), () => {
            getCustomer(data.addEntity.name, callback);
        })
    });
}

function getCustomer(customerName, callback) {
    gql(getCustomerScript(customerName), (data) => {
        callback(data.getEntity);
    });
}

function getLoadItemsScript(items) {
    var part = items.map(item => `i${item.id}: getProducts(itemTag:{name:"${itemTagName}",value:"${item.name}"}){name} `);
    return `{${part}}`;
}

function getCustomerScript(name) {
    return `{getEntity(type:"${customerEntityType}",name:"${name}"){name,customData{name,value},states{stateName,state}}}`;
}

function getIsEntityExistsScript(customer) {
    return `{isEntityExists(type:"${customerEntityType}",name:"${customer.phone}")}`;
}

function getAddCustomerScript(customer) {
    return `
    mutation m{addEntity(entity:{
        entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
            {name:"First Name",value:"${customer.firstName}"},
            {name:"Last Name",value:"${customer.lastName}"},
            {name:"Address",value:"${customer.address}"},
            {name:"EMail",value:"${customer.email}"},
            {name:"street",value:"${customer.street}"}
        ]})
        {name}
     }`;
}

function getNewCustomerStateScript(customer) {
    return `mutation m{updateEntityState(entityTypeName:"${customerEntityType}",entityName:"${customer.phone}",state:"Unconfirmed",stateName:"CStatus"){name}}`;
}

function getKitchenPrintScript(ticketId) {
    return `mutation m {
                executePrintJob(name: "${printJobName}", ticketId: ${ticketId}, 
                    orderStateFilters: [{stateName: "Status", state: "New"}],
                    nextOrderStates:[{stateName:"Status",currentState:"New",state:"Submitted"}]) 
                {name}
            }`;
}

function GetOrderTags(order) {
    if (order.options) {
        var options = order.options.map(x => `{tagName:"${x.group_name}",tag:"${x.name}",price:${x.price},quantity:${x.quantity}}`);
        if (order.instructions) {
            options.push(`{tagName:"Default",tag:"Instructions",note:"${order.instructions}"}`);
        }
        var result = options.join();
        return `tags:[${result}],`
    }
    return "";
}

function GetPortions(order) {
    if (order.portions) {
        var portions = order.portions.map(x => `portion:"${x.name}",`);
        var result = portions.join();
        return `${result}`
    }
    return "";
}

function GetOrderPrice(order) {
    if (order.portions) {
        var price = order.portions.map(x => `price:${Math.abs((x.price) + (order.price))},`);
        var result = price.join();
        return `${result}`
    }
    if (order.price > 0)
        return `price:${order.price},`;
    return "";

}

function getAddTicketScript(orders, customerName, newCustomer, instructions, fulfill_at, services, payment) {
    var orderLines = orders.map(order => {
        return `{
            name:"${order.sambaName ? order.sambaName : order.name}",
            menuItemName:"${order.sambaName === miscProductName ? order.name : ''}",
            quantity:${order.quantity > 0 ? order.quantity : 1},
            ${GetPortions(order)}
            ${GetOrderPrice(order)}
            ${GetOrderTags(order)}
            states:[
                {stateName:"Status",state:"Submitted"}
            ]
        }`;
    });

    var entityPart = customerName ?
        `entities:[{entityType:"${customerEntityType}",name:"${customerName}"}],` :
        '';
    var calculationsPart = services ?
        `calculations:[${services.map(x => `{name:"${x.name}",amount:${x.amount}}`).join()}],` :
        '';

    var result = `
        mutation m{addTicket(
            ticket:{type:"${ticketType}",
                department:"${departmentName}",
                user:"${userName}",
                terminal:"${terminalName}",
                note:"${instructions !== null ? instructions : ''}",
                ${entityPart}
                states:[
                    {stateName:"Status",state:"Unpaid"},
                    {stateName:"Source",state:"Gloria"},
                    {stateName:"Payment",state:"${payment}"}
                ],
                tags:[{tagName:"Cook Time Minutes",tag:"${Math.ceil(Math.abs(new Date(fulfill_at) - Date.now()) / 60000)}"}],
                ${calculationsPart}
                orders:[${orderLines.join()}]
            }){id}}`;
    return result;
}

function processItem(item) {
    var result = {
        id: item.id,
        name: item.name,
        type: item.type,
        price: item.price,
        quantity: item.quantity,
        instructions: item.instructions,
        options: item.options.filter(x => x.type === 'option').map(x => {
            return {
                group_name: x.group_name,
                name: x.name,
                quantity: x.quantity,
                price: x.price
            }
        }),
        portions: item.options.filter(x => x.type === 'size').map(x => {
            return {
                name: x.name,
                price: x.price
            }
        })
    };
    return result;
}

@Memo
Thank you so much for your support.
It’s still not working for me.
I checked everything and can’t find why.

SyntaxError: Unexpected end of input
←[90m    at Object.compileFunction (node:vm:355:18)←[39m
←[90m    at wrapSafe (node:internal/modules/cjs/loader:1022:15)←[39m
←[90m    at Module._compile (node:internal/modules/cjs/loader:1056:27)←[39m
←[90m    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1121:10)←[39m
←[90m    at Module.load (node:internal/modules/cjs/loader:972:32)←[39m
←[90m    at Function.Module._load (node:internal/modules/cjs/loader:813:14)←[39m
←[90m    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)←[39m
←[90m    at node:internal/main/run_main_module:17:47←[39m

Go to SambaPOS and take a screen shot of:
Settings > Entities > Entity Types > Customers > Custom Fields

Sorry to keep you hanging - I had to get a node.js dev enviornment setup.

I am able to run the script.

2021-04-29_11;11_1619716263_Console

The require for libphone number has an error - change it to

var libphonenumber = require('libphonenumber-js');

So, a couple of things to check:

make sure your packages are all installed:

  • libphonenumber-js
  • express
  • request (deprecated, but still installs via npm)

be sure you have the script client configured for API access in SambaPOS

1 Like

Ok i will check this and let you know.
Thanks

I just tested in a fresh W10 VM as I didn’t know if visual studio installer might have installed some dependencies that made things work and what not.

Using node.js 14.16.1 LTS (I figure this may be the best given the age of the script and its dependencies - reduces the chances of breaking changes).

One thing I noticed when installing packages via npm was a warning about no packages.json file present. So I copied the one from the solution folder. I removed the dependencies field and left just this:

{
  "name": "GloriaFoodIntegration",
  "version": "0.0.0",
  "description": "Online ordering integration for SambaPOS",
  "main": "app.js",
  "author": {
    "name": ""
  }
}

then executed the following commands:

npm i libphonenumber-js
npm i express
npm i request

node app.js

and it fired right up

afterwards your package.json should look something like this:

{
  "name": "GloriaFoodIntegration",
  "version": "0.0.0",
  "description": "Online ordering integration for SambaPOS",
  "main": "app.js",
  "author": {
    "name": ""
  },
  "dependencies": {
    "express": "^4.17.1",
    "libphonenumber-js": "^1.9.16",
    "request": "^2.88.2"
  }
}

@Memo
Ok so i tried everything you said but it didn’t work for me.
Any ideas??
Here is my script.js and package.json
I also installed all the modules as you said.


script.js

var express = require('express');
var request = require('request');
var querystring = require('querystring');
var libphonenumber = require('libphonenumber-js');
var app = express();
var messageServer = 'localhost';
var messageServerPort = 9000;
var gloriaFoodKey = 'XXXXXXXX';
var serverKey = '553181';
var timeout = 30000;
var customerEntityType = 'Customers';
var itemTagName = 'Gloria Name';
var ticketType = 'Ticket';
var departmentName = 'pizza';
var userName = 'Administrator';
var terminalName = 'Server';
var printJobName = 'Print Bill';
var additionalPrintJobs = []; // array of additional print job names
var miscProductName = 'Misc';
var deliveryFeeCalculation = 'Delivery Service';
var tipCalculation = 'Tip';
var accessToken = undefined;
var accessTokenExpires = '';

var formatPhoneNumber = true;
var formatPhoneNumberCountry = 'IL'; // set to your ISO country code
var formatPhoneNumberFormat = 'National'; // format type, this should suffice
var formatPhoneNumberHyphen = false;
var formatPhoneNumberNoSpaces = true;

Authorize(loop());

function Authorize(callback) {
    accessToken = undefined;
    var form = {
        grant_type: 'client_credentials',
        client_secret: serverKey,
        client_id: 'gloria'
    };
    var formData = querystring.stringify(form);
    var contentLength = formData.length;

    request({
        headers: {
            'Content-Length': contentLength,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/Token',
        body: formData,
        method: 'POST'
    }, function (err, res, body) {
        if (err) {
            console.log('Error while trying to authorize >', err.message);
        } else if (res.statusCode === 400) {
            console.log(body);
            if (callback) callback();
        } else {
            var result = JSON.parse(body);
            accessToken = result.access_token;
            accessTokenExpires = new Date(result['.expires']);
            if (callback) callback();
        }
    });
}

function gql(query, callback) {
    if (!accessToken) {
        console.log('Valid access Token is needed to execute GQL calls.')
        return;
    }
    var data = JSON.stringify({
        query: query
    });
    request({
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Bearer ' + accessToken
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/api/graphql',
        body: data,
        method: 'POST'
    }, function (err, res, body) {
        if (res.statusCode === 401) {
            console.log('Should Authorize...');
            Authorize(() => gql(query, callback));
        } else {
            var data = JSON.parse(body).data;
            if (callback) callback(data);
        }
    });
}

function readTickets(callback) {
    request({
        method: 'POST',
        uri: 'https://pos.gloriafood.com/pos/order/pop',
        headers: {
            'Authorization': gloriaFoodKey,
            'Accept': 'application/json',
            'Glf-Api-Version': '2'
        }
    }, function (err, res, body) {
        if (err) {
            console.log(`problem with request: ${err.message}`);
        } else {
            callback(JSON.parse(body));
        }
    });
}

function loop() {
    if (!accessToken) {
        console.log('There is no valid access token. Skipping...')
        Authorize();
    } else if (accessTokenExpires < new Date()) {
        console.log('Access Token Expired. Reauthenticating...');
        Authorize(() => loop());
        return;
    } else {
        console.log('Reading Tickets...');
        readTickets((tickets) => processTickets(tickets));
    }
    setTimeout(loop, timeout);
}

function processTickets(tickets) {
    if (tickets.count == 0) return;
    tickets.orders.forEach((order) => processOrder(order));
}

function processOrder(order) {
    // Format phone number
    if (formatPhoneNumber) {
        order.client_phone = libphonenumber.formatNumber({
            country: formatPhoneNumberCountry,
            phone: order.client_phone
        }, formatPhoneNumberFormat);

        if (formatPhoneNumberHyphen) {
            order.client_phone = order.client_phone.replace(/ +/g, '-');
        } else {
            order.client_phone = order.client_phone.replace(/-+/g, '');
        }

        if (formatPhoneNumberNoSpaces) {
            order.client_phone = order.client_phone.replace(/ +/g, '');
        }
    }

    var street = null;
    var bloc = null;
    var floor = null;
    var apartment = null;
    var intercom = null;
    var moreAddress = null;
    var zipcode = null;
    var city = null;

    if (order.client_address_parts != null) {
        street = order.client_address_parts.street;
        bloc = order.client_address_parts.bloc;
        floor = order.client_address_parts.floor;
        apartment = order.client_address_parts.apartment;
        intercom = order.client_address_parts.intercom;
        moreAddress = order.client_address_parts.more_address;
        zipcode = order.client_address_parts.zipcode;
        city = order.client_address_parts.city;
    }

    var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        street: street,
        city: city,
        bloc: bloc,
        floor: floor,
        apartment: apartment,
        intercom: intercom,
        moreAddress: moreAddress,
        zipcode: zipcode,
        newCustomer: false
    }

    loadCustomer(customer, customer => {
        var services = order.items
            .filter(x => x.type === 'tip' || x.type === 'delivery_fee' || x.type === 'promo_cart')
            .map(x => {
                return {
                    name: getCalculationName(x.type),
                    amount: Math.abs((x.cart_discount_rate) * 100) || x.price
                };
            })
            .filter(x => x.name);
        loadItems(order.items.map(x => processItem(x)), items => {
            createTicket(customer, items, order.instructions, order.fulfill_at, services, order.payment, ticketId => {
                gql('mutation m {postTicketRefreshMessage(id:0){id}}', () => {
                    console.log(`Ticket ${ticketId} created...`);
                });
            });
        });
    });
}

function getCalculationName(name) {
    if (name === 'promo_cart') return promotionDiscount;
    if (name === 'tip') return tipCalculation;
    if (name === 'delivery_fee') return deliveryFeeCalculation;
    return undefined;
}

function loadItems(items, callback) {
    var script = getLoadItemsScript(items);
    gql(script, data => {
        callback(items.filter(x => x.type === 'item').map(item => {
            return {
                id: item.id,
                name: item.name,
                type: item.type,
                sambaName: data[`i${item.id}`][0] ? data[`i${item.id}`][0].name : miscProductName,
                price: item.price,
                quantity: item.quantity,
                instructions: item.instructions,
                options: item.options,
                portions: item.portions
            }
        }));
    });
}

function isNewCustomer(customer) {
    if (customer.states && customer.states.find(x => x.stateName === 'CStatus')) {
        return customer.states.find(x => x.stateName === 'CStatus').state === 'Unconfirmed';
    }
    return false;
}

function createTicket(customer, items, instructions, fulfill_at, services, payment, callback) {
    var newCustomer = isNewCustomer(customer);
    gql(getAddTicketScript(items, customer.name, newCustomer, instructions, fulfill_at, services, payment), data => {
        if (newCustomer)
            callback(data.addTicket.id);
        else printTicketToKitchen(data.addTicket.id, () => callback(data.addTicket.id));
    });
}

function printTicketToKitchen(ticketId, callback) {
    gql(getKitchenPrintScript(ticketId), callback);
}

function loadCustomer(customer, callback) {
    gql(getIsEntityExistsScript(customer), (data) => {
        if (!data.isEntityExists) {
            createCustomer(customer, callback);
        } else getCustomer(customer.phone, callback);
    });
}

function createCustomer(customer, callback) {
    gql(getAddCustomerScript(customer), (data) => {
        gql(getNewCustomerStateScript(customer), () => {
            getCustomer(data.addEntity.name, callback);
        })
    });
}

function getCustomer(customerName, callback) {
    gql(getCustomerScript(customerName), (data) => {
        callback(data.getEntity);
    });
}

function getLoadItemsScript(items) {
    var part = items.map(item => `i${item.id}: getProducts(itemTag:{name:"${itemTagName}",value:"${item.name}"}){name} `);
    return `{${part}}`;
}

function getCustomerScript(name) {
    return `{getEntity(type:"${customerEntityType}",name:"${name}"){name,customData{name,value},states{stateName,state}}}`;
}

function getIsEntityExistsScript(customer) {
    return `{isEntityExists(type:"${customerEntityType}",name:"${customer.phone}")}`;
}

function getAddCustomerScript(customer) {
    return `
    mutation m{addEntity(entity:{
        entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
            {name:"First Name",value:"${customer.firstName}"},
            {name:"Last Name",value:"${customer.lastName}"},
            {name:"Address",value:"${customer.address}"},
            {name:"EMail",value:"${customer.email}"},
            {name:"street",value:"${customer.street}"}
        ]})
        {name}
     }`;
}

function getNewCustomerStateScript(customer) {
    return `mutation m{updateEntityState(entityTypeName:"${customerEntityType}",entityName:"${customer.phone}",state:"Unconfirmed",stateName:"CStatus"){name}}`;
}

function getKitchenPrintScript(ticketId) {
    return `mutation m {
                executePrintJob(name: "${printJobName}", ticketId: ${ticketId}, 
                    orderStateFilters: [{stateName: "Status", state: "New"}],
                    nextOrderStates:[{stateName:"Status",currentState:"New",state:"Submitted"}]) 
                {name}
            }`;
}

function GetOrderTags(order) {
    if (order.options) {
        var options = order.options.map(x => `{tagName:"${x.group_name}",tag:"${x.name}",price:${x.price},quantity:${x.quantity}}`);
        if (order.instructions) {
            options.push(`{tagName:"Default",tag:"Instructions",note:"${order.instructions}"}`);
        }
        var result = options.join();
        return `tags:[${result}],`
    }
    return "";
}

function GetPortions(order) {
    if (order.portions) {
        var portions = order.portions.map(x => `portion:"${x.name}",`);
        var result = portions.join();
        return `${result}`
    }
    return "";
}

function GetOrderPrice(order) {
    if (order.portions) {
        var price = order.portions.map(x => `price:${Math.abs((x.price) + (order.price))},`);
        var result = price.join();
        return `${result}`
    }
    if (order.price > 0)
        return `price:${order.price},`;
    return "";

}

function getAddTicketScript(orders, customerName, newCustomer, instructions, fulfill_at, services, payment) {
    var orderLines = orders.map(order => {
        return `{
            name:"${order.sambaName ? order.sambaName : order.name}",
            menuItemName:"${order.sambaName === miscProductName ? order.name : ''}",
            quantity:${order.quantity > 0 ? order.quantity : 1},
            ${GetPortions(order)}
            ${GetOrderPrice(order)}
            ${GetOrderTags(order)}
            states:[
                {stateName:"Status",state:"Submitted"}
            ]
        }`;
    });

    var entityPart = customerName ?
        `entities:[{entityType:"${customerEntityType}",name:"${customerName}"}],` :
        '';
    var calculationsPart = services ?
        `calculations:[${services.map(x => `{name:"${x.name}",amount:${x.amount}}`).join()}],` :
        '';

    var result = `
        mutation m{addTicket(
            ticket:{type:"${ticketType}",
                department:"${departmentName}",
                user:"${userName}",
                terminal:"${terminalName}",
                note:"${instructions !== null ? instructions : ''}",
                ${entityPart}
                states:[
                    {stateName:"Status",state:"Unpaid"},
                    {stateName:"Source",state:"Gloria"},
                    {stateName:"Payment",state:"${payment}"}
                ],
                tags:[{tagName:"Cook Time Minutes",tag:"${Math.ceil(Math.abs(new Date(fulfill_at) - Date.now()) / 60000)}"}],
                ${calculationsPart}
                orders:[${orderLines.join()}]
            }){id}}`;
    return result;
}

function processItem(item) {
    var result = {
        id: item.id,
        name: item.name,
        type: item.type,
        price: item.price,
        quantity: item.quantity,
        instructions: item.instructions,
        options: item.options.filter(x => x.type === 'option').map(x => {
            return {
                group_name: x.group_name,
                name: x.name,
                quantity: x.quantity,
                price: x.price
            }
        }),
        portions: item.options.filter(x => x.type === 'size').map(x => {
            return {
                name: x.name,
                price: x.price
            }
        })
    };
    return result;
}

package.json

{
  "name": "gloria",
  "version": "0.0.0",
  "description": "Online ordering integration for SambaPOS",
  "main": "script.js",
  "author": {
    "name": ""
  },
  "dependencies": {
    "express": "^4.17.1",
    "libphonenumber-js": "^1.9.16",
    "request": "^2.88.2"
  }
}

I spun up another VM. I copied your latest script as posted and just changed the restaurant key. I configured the application “gloria” in SambaPOS. I installed the required packages. It runs just fine.

Just let the script run - it takes a few seconds to get the access token from the message server (40s+ in this case)

2021-04-29_15;48_1619732913_%pn

Unless you’re rolling your own integration, I would suggest using the official integration application. Sure it doesn’t run as a service, but the difficult stuff has been done, works with automation, and it’s reasonably-priced.

The js implementation from years ago does not execute any automation on the ticket, when it’s created, when an order’s added, etc.

4 Likes

thank you for your help.

1 Like

@Memo
Finally I found the script error and made it work.
Thank you so much for your time and effort.

1 Like

Great to hear! For others who may encounter the same issue, would you mind posting your solution? Thanks!

2 Likes

Of course.
Here is the working script.js and package. Json

Script. Js

var express = require('express');
var request = require('request');
var querystring = require('querystring');
var libphonenumber = require('libphonenumber-js');
var app = express();

var messageServer = 'localhost';
var messageServerPort = 9000;
var gloriaFoodKey = 'XXXXXXXXX';
var serverKey = 'XXXX';
var timeout = 30000;
var customerEntityType = 'Customers';
var itemTagName = 'Gloria Name';
var ticketType = 'Ticket';
var departmentName = 'pizza';
var userName = 'איציק';
var terminalName = 'Server';
var printJobName = 'Print Bill';
var additionalPrintJobs = [];  // array of additional print job names
var miscProductName = 'Misc';
var deliveryFeeCalculation = 'Delivery Service';
var tipCalculation = 'Tip';
var accessToken = undefined;
var accessTokenExpires = '';

var formatPhoneNumber = true;
var formatPhoneNumberCountry = 'IL'; // set to your ISO country code
var formatPhoneNumberFormat = 'National'; // format type, this should suffice
var formatPhoneNumberHyphen = false;
var formatPhoneNumberNoSpaces = true;


function Authorize(callback) {
    accessToken = undefined;
    var form = { grant_type: 'client_credentials', client_secret: serverKey, client_id: 'gloria' };
    var formData = querystring.stringify(form);
    var contentLength = formData.length;

    request({
        headers: {
            'Content-Length': contentLength,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/Token',
        body: formData,
        method: 'POST'
    }, function (err, res, body) {
        if (err) {
            console.log('Error while trying to authorize >', err.message);
            if (callback) callback();
        }
        else if (res.statusCode === 400) {
            console.log(body);
            if (callback) callback();
        }
        else {
            var result = JSON.parse(body);
            accessToken = result.access_token;
            accessTokenExpires = new Date(result['.expires']);
            if (callback) callback();
        }
    });
}

function gql(query, callback) {
    if (!accessToken) {
        console.log('Valid access Token is needed to execute GQL calls.')
        return;
    }
    var data = JSON.stringify({ query: query });
    request({
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Bearer ' + accessToken
        },
        uri: 'http://' + messageServer + ':' + messageServerPort + '/api/graphql',
        body: data,
        method: 'POST'
    }, function (err, res, body) {
        if (res.statusCode === 401) {
            console.log('Should Authorize...');
            Authorize(() => gql(query, callback));
        }
        else {
            var data = JSON.parse(body).data;
            if (callback) callback(data);
        }
    });
}

function readTickets(callback) {
    request({
        method: 'POST',
        uri: 'https://pos.gloriafood.com/pos/order/pop',
        headers: {
            'Authorization': gloriaFoodKey,
            'Accept': 'application/json',
            'Glf-Api-Version': '2'
        }
    }, function (err, res, body) {
        if (err) {
            console.log(`problem with request: ${err.message}`);
        } else {
            console.log(body);
            callback(JSON.parse(body));
        }
    });
}

app.get('/', function (req, res) {
    res.send(`Welcome to Gloria Food integration application.
    <br/>
    <ul>
        <li><a href="/gqltest">Click here to retrieve product list</a></li>
        <li><a href="/test">Click here to create a test ticket</a></li>
    </ul>`);
});

app.get('/gqltest', function (req, res) {
    gql('{getProducts{id,name,price}}', (data) => {
        data.getProducts.forEach(x => res.write(`<div>${x.name} $${x.price}</div>`))
        res.end();
    });
});

app.get('/ctest', function (req, res) {
    loadCustomer({ phone: "222-344 1123" }, data => {
        res.send(JSON.stringify(data));
    })
});

app.get('/test', function (req, res) {
    processTickets(JSON.parse(getTestData()));
    res.send("Ticket Created! See log for details");
});

app.listen(3000, function () {
    console.log('Gloria Food integration app listening on port 3000!');
    Authorize(() => loop());
});

function loop() {
    if (!accessToken) {
        console.log('There is no valid access token. Skipping...')
        Authorize();
    }
    else if (accessTokenExpires < new Date()) {
        console.log('Access Token Expired. Reauthenticating...');
        Authorize(() => loop());
        return;
    }
    else {
        console.log('Reading Tickets...');
        readTickets((tickets) => processTickets(tickets));
    }
    setTimeout(loop, timeout);
}

function processTickets(tickets) {
    if (tickets.count == 0) return;
    tickets.orders.forEach((order) => processOrder(order));
}

function processOrder(order) {
	
	// Format phone number
	if (formatPhoneNumber) {
		order.client_phone = libphonenumber.formatNumber({ country: formatPhoneNumberCountry, phone: order.client_phone }, formatPhoneNumberFormat);

		if (formatPhoneNumberHyphen) {
			order.client_phone = order.client_phone.replace(/ +/g, '-');
		}
		else {
			order.client_phone = order.client_phone.replace(/-+/g, '');
		}
		
		if (formatPhoneNumberNoSpaces) { order.client_phone = order.client_phone.replace(/ +/g, ''); }
	}

var street = null;
var bloc = null;
var floor = null;
var apartment = null;
var intercom = null;
var moreAddress = null;
var zipcode = null;
var city = null;

if (order.client_address_parts != null){
    street = order.client_address_parts.street;
    bloc = order.client_address_parts.bloc;
    floor = order.client_address_parts.floor;
    apartment = order.client_address_parts.apartment;
    intercom = order.client_address_parts.intercom;
    moreAddress = order.client_address_parts.more_address;
    zipcode = order.client_address_parts.zipcode;
    city = order.client_address_parts.city;
}
	
    var customer = {
        firstName: order.client_first_name,
        lastName: order.client_last_name,
        email: order.client_email,
        phone: order.client_phone,
        address: order.client_address,
        street: street,
        city: city,
        floor: floor,
        newCustomer: false
    }
    loadCustomer(customer, customer => {
        var services = order.items
            .filter(x => x.type === 'tip' || x.type === 'delivery_fee')
            .map(x => { return { name: getCalculationName(x.type), amount: x.price }; })
            .filter(x => x.name);
        loadItems(order.items.map(x => processItem(x)), items => {
            createTicket(customer, items, order.instructions, order.fulfill_at, services, ticketId => {
                gql('mutation m {postTicketRefreshMessage(id:0){id}}', () => {
                    console.log(`Ticket ${ticketId} created...`);
                });
            });
        });
    });
}

function getCalculationName(name) {
    if (name === 'tip') return tipCalculation;
    if (name === 'delivery_fee') return deliveryFeeCalculation;
    return undefined;
}

function loadItems(items, callback) {
    var script = getLoadItemsScript(items);
    gql(script, data => {
        callback(items.filter(x => x.type === 'item').map(item => {
            return {
                id: item.id,
                name: item.name,
                type: item.type,
                sambaName: data[`i${item.id}`][0] ? data[`i${item.id}`][0].name : miscProductName,
                price: item.price,
                quantity: item.quantity,
                instructions: item.instructions,
                options: item.options
            }
        }));
    });
}

function isNewCustomer(customer) {
    if (customer.states && customer.states.find(x => x.stateName === 'CStatus')) {
        return customer.states.find(x => x.stateName === 'CStatus').state === 'Unconfirmed';
    }
    return false;
}

function createTicket(customer, items, instructions, fulfill_at, services, callback) {
    var newCustomer = isNewCustomer(customer);
    gql(getAddTicketScript(items, customer.name, newCustomer, instructions, fulfill_at, services), data => {
        if (newCustomer)
            callback(data.addTicket.id);
        else printTicketToKitchen(data.addTicket.id, () => callback(data.addTicket.id));
    });
}

function printTicketToKitchen(ticketId, callback) {
    gql(getKitchenPrintScript(ticketId), (data) => {
        if (additionalPrintJobs && additionalPrintJobs.length > 0) {
            var scripts = additionalPrintJobs.map((x) => getAdditionalPrintScript(x, ticketId)).join('\r\n');
            gql(scripts, callback);
        } else callback(data);
    });
}

function loadCustomer(customer, callback) {
    gql(getIsEntityExistsScript(customer), (data) => {
        if (!data.isEntityExists) {
            createCustomer(customer, callback);
        } else getCustomer(customer.phone, callback);
    });
}

function createCustomer(customer, callback) {
    gql(getAddCustomerScript(customer), (data) => {
        gql(getNewCustomerStateScript(customer), () => {
            getCustomer(data.addEntity.name, callback);
        })
    });
}

function getCustomer(customerName, callback) {
    gql(getCustomerScript(customerName), (data) => {
        callback(data.getEntity);
    });
}

function getLoadItemsScript(items) {
    var part = items.map(item => `i${item.id}: getProducts(itemTag:{name:"${itemTagName}",value:"${item.name}"}){name} `);
    return `{${part}}`;
}

function getCustomerScript(name) {
    return `{getEntity(type:"${customerEntityType}",name:"${name}"){name,customData{name,value},states{stateName,state}}}`;
}

function getIsEntityExistsScript(customer) {
    return `{isEntityExists(type:"${customerEntityType}",name:"${customer.phone}")}`;
}

function getAddCustomerScript(customer) {
    return `
    mutation m{addEntity(entity:{
        entityType:"${customerEntityType}",name:"${customer.phone}",customData:[
            {name:"First Name",value:"${customer.firstName}"},
            {name:"Last Name",value:"${customer.lastName}"},
            {name:"address",value:"${customer.address}"},
            {name:"EMail",value:"${customer.email}"},
            {name:"street",value:"${customer.street}"},
            {name:"city",value:"${customer.city}"}
            {name:"floor",value:"${customer.floor}"}
        ]})
        {name}
    }`;
}

function getNewCustomerStateScript(customer) {
    return `mutation m{updateEntityState(entityTypeName:"${customerEntityType}",entityName:"${customer.phone}",state:"Unconfirmed",stateName:"CStatus"){name}}`;
}

function getKitchenPrintScript(ticketId) {
    return `mutation m {
                executePrintJob(name: "${printJobName}", ticketId: ${ticketId}, 
                    orderStateFilters: [{stateName: "Status", state: "New"}],
                    nextOrderStates:[{stateName:"Status",currentState:"New",state:"Submitted"}]) 
                {name}
            }`;
}

function getAdditionalPrintScript(name, ticketId) {
    var mName = name.split(' ').join('_');
    return `mutation m_${mName} {
                n_${mName}:executePrintJob(name: "${name}", ticketId: ${ticketId}) 
                {name}
            }`;
}

function GetOrderTags(order) {
    if (order.options) {
        var options = order.options.map(x => `{tagName:"Default",tag:"${x.name}",price:${x.price},quantity:${x.quantity}}`);
        if (order.instructions && order.instructions !== '') {
            options.push(`{tagName:"Default",tag:"Instructions",note:"${order.instructions}"}`);
        }
        var result = options.join();
        return `tags:[${result}],`
    }
    return "";
}

function GetOrderPrice(order) {
    if (order.price > 0)
        return `price:${order.price},`;
    return "";
}

function getAddTicketScript(orders, customerName, newCustomer, instructions, fulfill_at, services) {
    var orderLines = orders.map(order => {
        return `{
            name:"${order.sambaName ? order.sambaName : order.name}",
            menuItemName:"${order.sambaName === miscProductName ? order.name : ''}",
            quantity:${order.quantity > 0 ? order.quantity : 1},
            ${GetOrderPrice(order)}
            ${GetOrderTags(order)}
            states:[
                {stateName:"Status",state:"New"}
            ]
        }`;
    });

    var entityPart = customerName
        ? `entities:[{entityType:"${customerEntityType}",name:"${customerName}"}],`
        : '';
    var calculationsPart = services
        ? `calculations:[${services.map(x => `{name:"${x.name}",amount:${x.amount}}`).join()}],`
        : '';

    var notePart = instructions && instructions !== ''
        ? `note:"${instructions}",`
        : '';
    var result = `
        mutation m{addTicket(
            ticket:{type:"${ticketType}",
                department:"${departmentName}",
                user:"${userName}",
                terminal:"${terminalName}",
                ${notePart}
                ${entityPart}
                states:[
                    {stateName:"Status",state:"Unpaid"},
                    {stateName:"Source",state:"Gloria"},
                    {stateName:"Delivery",state:"${newCustomer ? 'Unconfirmed' : 'Waiting'}"}
                ],
                tags:[{tagName:"Delivery Minutes",tag:"${Math.ceil(Math.abs(new Date(fulfill_at) - Date.now()) / 60000)}"}],
                ${calculationsPart}
                orders:[${orderLines.join()}]
            }){id}}`;
    return result;
}

function processItem(item) {
    var result = {
        id: item.id,
        name: item.name,
        type: item.type,
        price: item.price,
        quantity: item.quantity,
        instructions: item.instructions,
        options: item.options.map(x => { return { name: x.name, quantity: x.quantity, price: x.price } })
    };
    return result;
}

var getTestData = () => `{
    "count": 1,
    "orders": [
        {
            "coupons": [],
            "id": 776113,
            "restaurant_id": 4172,
            "client_id": 188995,
            "type": "delivery",
            "source": "website",
            "sub_total_price": 47.88,
            "tax_value": 4.13,
            "total_price": 62.41,
            "client_first_name": "John",
            "client_last_name": "Pink",
            "client_email": "john.brown@sambapos.com",
            "client_phone": "${Math.floor((Math.random() * 300) + 200)}-456 6699",
            "pin_skipped": 0,
            "restaurant_name": "John's Excellent Pizza",
            "restaurant_phone": "+15558964567",
            "restaurant_country": "United States of America",
            "restaurant_state": "California",
            "restaurant_city": "San Francisco",
            "restaurant_street": "10 Market Street",
            "restaurant_zipcode": "1234678",
            "restaurant_latitude": "37.7944872589999",
            "restaurant_longitude": "-122.395311999999",
            "instructions": "Deliver ASAP",
            "currency": "USD",
            "latitude": "37.79448725889753",
            "longitude": "-122.395311680426",
            "tax_type": "NET",
            "tax_name": "Sales Tax",
            "fulfill_at": "${ new Date(new Date().getTime() + 25 * 60000).toISOString()}",
            "pos_system_id": 1,
            "restaurant_key": "8yCPCvb3dDo1k",
            "api_version": 2,
            "payment": "ONLINE",
            "client_address": "21 Market Street, San Francisco",
            "items": [
                {
                    "id": 1678316,
                    "name": "DELIVERY_FEE",
                    "total_item_price": 5,
                    "price": 5,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": null,
                    "type": "delivery_fee",
                    "tax_rate": 0.1,
                    "tax_value": 0.5,
                    "parent_id": null,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678317,
                    "name": "TIP",
                    "total_item_price": 5.67,
                    "price": 5.67,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": null,
                    "type": "tip",
                    "tax_rate": 0.05,
                    "tax_value": 0.2702,
                    "parent_id": null,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "GROSS",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678322,
                    "name": "Pizza Margherita",
                    "total_item_price": 8.2,
                    "price": 7,
                    "quantity": 1,
                    "instructions": "",
                    "type_id": 58424,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 8.2,
                    "options": [
                        {
                            "id": 1771325,
                            "name": "Small",
                            "price": 0,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771326,
                            "name": "Crispy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771327,
                            "name": "Extra mozzarella",
                            "price": 1.2,
                            "group_name": "Extra Toppings (Small)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678324,
                    "name": "Pizza Prosciutto",
                    "total_item_price": 11.7,
                    "price": 8,
                    "quantity": 1,
                    "instructions": "User may enter a very long description for the pizza. For example he may want to explain what kind of sauce he wants or how dough should be cooked. So we should handle that case properly.",
                    "type_id": 58425,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.819,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": [
                        {
                            "id": 1771331,
                            "name": "Large",
                            "price": 2,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771332,
                            "name": "Crispy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771333,
                            "name": "Extra mozzarella",
                            "price": 1.7,
                            "group_name": "Extra Toppings (Large)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678331,
                    "name": "Pizza Prosciutto",
                    "total_item_price": 8.7,
                    "price": 8,
                    "quantity": 2,
                    "instructions": "no salt",
                    "type_id": 58425,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.609,
                    "parent_id": 1678332,
                    "cart_discount_rate": 0,
                    "cart_discount": 0,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": [
                        {
                            "id": 1771343,
                            "name": "Small",
                            "price": 0,
                            "group_name": "Size",
                            "quantity": 1,
                            "type": "size"
                        },
                        {
                            "id": 1771344,
                            "name": "Fluffy",
                            "price": 0,
                            "group_name": "Crust",
                            "quantity": 1,
                            "type": "option"
                        },
                        {
                            "id": 1771345,
                            "name": "Corn",
                            "price": 0.7,
                            "group_name": "Extra Toppings (Small)",
                            "quantity": 1,
                            "type": "option"
                        }
                    ]
                },
                {
                    "id": 1678332,
                    "name": "2 + 1 Pizza Special",
                    "total_item_price": 28.6,
                    "price": 0,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": 251,
                    "type": "promo_item",
                    "tax_rate": 0.07,
                    "tax_value": 1.3566,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": 1.02,
                    "tax_type": "NET",
                    "item_discount": 8.2,
                    "options": []
                },
                {
                    "id": 1678334,
                    "name": "Spaghetti Bolognese",
                    "total_item_price": 18,
                    "price": 9,
                    "quantity": 2,
                    "instructions": "",
                    "type_id": 58426,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 1.197,
                    "parent_id": null, 
                    "cart_discount_rate": 0.05,
                    "cart_discount": 0.9,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678335,
                    "name": "Spaghetti Frutti di Mare",
                    "total_item_price": 12,
                    "price": 12,
                    "quantity": 1,
                    "instructions": "",
                    "type_id": 58427,
                    "type": "item",
                    "tax_rate": 0.07,
                    "tax_value": 0.798,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": 0.6,
                    "tax_type": "NET",
                    "item_discount": 0,
                    "options": []
                },
                {
                    "id": 1678336,
                    "name": "5% off total larger than 40$",
                    "total_item_price": 0,
                    "price": 0,
                    "quantity": 1,
                    "instructions": null,
                    "type_id": 250,
                    "type": "promo_cart",
                    "tax_rate": 0.07,
                    "tax_value": 0,
                    "parent_id": null,
                    "cart_discount_rate": 0.05,
                    "cart_discount": -2.52,
                    "tax_type": "NET",
                    "item_discount": 2.52,
                    "options": []
                }
            ]
        }
    ]
}`;

package.json

  "name": "gloria",
  "version": "1.0.0",
  "main": "script.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "libphonenumber-js": ">=1.2.20 <=1.5.2",
    "querystring": "^0.2.0",
    "request": "^2.88.2"
  },
  "description": ""
}
5 Likes