var sdk = require("remote-pay-cloud-api");
var DelegateCloverConnectorListener = require("./DelegateCloverConnectorListener.js");
var DebugCloverConnectorListener = require("./DebugCloverConnectorListener.js");
var MethodToMessage = require("./MethodToMessage");
var CardEntryMethods = require("./CardEntryMethods.js");
var WebSocketDevice = require("./WebSocketDevice.js");
var RemoteMessageBuilder = require("./RemoteMessageBuilder.js");
var RemoteMessageParser = require("./RemoteMessageParser.js");
var XmlHttpSupport = require("./xmlHttpSupport.js");
var Endpoints = require("./Endpoints.js");
var CloverOAuth2 = require("./CloverOAuth2.js");
var DeviceContactInfo = require("./DeviceContactInfo.js");
var CloverError = require("./CloverError.js");
var ImageUtil = require("./ImageUtil.js");
var CloverID = require("./CloverID.js");
var EndPointConfig = require("./EndpointConfig.js");
var MessageBundle = require("./MessageBundle.js");
var CloudMethod = require("./CloudMethod.js");
var Logger = require('./Logger.js');
var LanMethod = require('./LanMethod.js');
// !!NOTE!! The following is automatically updated to reflect the npm version.
// See the package.json postversion script, which maps to scripts/postversion.sh
// Do not change this or the versioning may not reflect the npm version correctly.
CLOVER_CLOUD_SDK_VERSION = "1.1.0";
/**
* Interface to the Clover remote-pay API.
*
* Defines the interface used to interact with remote pay
* adapters.
*/
/**
*
* @param {Object.<string, string>} configuration - the configuration for the connector
* @constructor
*/
var CloverConnectorImpl = function(configuration) {
sdk.remotepay.ICloverConnector.call(this);
this.log = Logger.create();
this.cloverConnectorListeners = [];
if(configuration){
try {
// Make sure we do not change the passed object, make a copy.
this.configuration = JSON.parse(JSON.stringify(configuration));
} catch(e) {
this.log.error("Could not load configuration", e);
throw e;
}
} else {
this.configuration = {};
}
// This flag will be set by the discovery response.
this.deviceSupportsAckMessages = false;
this.debugConfiguration = {};
if(this.configuration.debugConfiguration) {
this.debugConfiguration = this.configuration.debugConfiguration
} else {
this.debugConfiguration = {};
}
this.device = new WebSocketDevice(this.configuration.allowOvertakeConnection,
this.configuration[CloverConnectorImpl.KEY_FriendlyId] === null ?
CloverID.getNewId() : this.configuration[CloverConnectorImpl.KEY_FriendlyId]
);
var builderPackageConf = this.configuration[CloverConnectorImpl.KEY_Package] ?
this.configuration[CloverConnectorImpl.KEY_Package] : CloverConnectorImpl.WebSocketPackage;
this.configuration[CloverConnectorImpl.KEY_Package] = builderPackageConf;
if(!this.configuration["clientId"]) {
throw new CloverError(CloverError.INCOMPLETE_CONFIGURATION, "'clientId' must be included in the configuration.");
}
if(!this.configuration["remoteApplicationId"]) {
throw new CloverError(CloverError.INCOMPLETE_CONFIGURATION, "'remoteApplicationId' must be included in the configuration.");
}
var messageCount = isNaN(parseInt(this.configuration["messageCount"])) ?
0 : parseInt(this.configuration["messageCount"]);
this.remoteMessageParser = new RemoteMessageParser(builderPackageConf);
this.messageBuilder = new RemoteMessageBuilder(builderPackageConf,
CloverConnectorImpl.RemoteSourceSDK + ":" + CLOVER_CLOUD_SDK_VERSION,
this.configuration["remoteApplicationId"], messageCount
);
this.device.messageBuilder = this.messageBuilder;
this.device.echoAllMessages = false;
this.pauseBetweenDiscovery = 3000;
this.numberOfDiscoveryMessagesToSend = 10;
this.discoveryResponseReceived = false;
this.isReady = false;
// MerchantInfo
this.merchantInfo = null;
this.configuration.autoVerifySignature =
Boolean(this.configuration.autoVerifySignature);
this.configuration.disableRestartTransactionWhenFailed =
Boolean(this.configuration.disableRestartTransactionWhenFailed);
this.configuration.remotePrint =
Boolean(this.configuration.remotePrint);
this.configuration.allowOvertakeConnection =
Boolean(this.configuration.allowOvertakeConnection);
this.configuration.allowOfflinePayment =
Boolean(this.configuration.allowOfflinePayment);
this.configuration.approveOfflinePaymentWithoutPrompt =
Boolean(this.configuration.approveOfflinePaymentWithoutPrompt);
this.configuration.defaultEmployeeId =
this.configuration["defaultEmployeeId"]===undefined ? "DFLTEMPLOYEE" :
this.configuration.defaultEmployeeId;
this.configuration.cardEntryMethods =
this.configuration["cardEntryMethods"]===undefined ? CardEntryMethods.DEFAULT :
this.configuration.cardEntryMethods;
// This listener just sends messages on to the other listeners
this.delegateCloverConnectorListener = new DelegateCloverConnectorListener(this);
// Following is an internal structure used for specialized acknowledgement message handling.
this.acknowledgementHooks = {};
// We need to hold on to this because the finishOK does not provide enough information
this.refundPaymentResponse = null; // RefundPaymentResponse;
this.setupMappingOfProtocolMessages();
//****************************************
// Very useful for debugging
//****************************************
if (this.debugConfiguration[WebSocketDevice.ALL_MESSAGES]) {
this.device.on(WebSocketDevice.ALL_MESSAGES,
function (message) {
// Do not log ping or pong
if ((message['type'] != 'PONG') && (message['type'] != 'PING')) {
this.log.debug(message);
}
}.bind(this)
);
}
};
CloverConnectorImpl.prototype = Object.create(sdk.remotepay.ICloverConnector.prototype);
CloverConnectorImpl.prototype.constructor = CloverConnectorImpl;
/**
* @param {Object} message - the message to send
* @param {boolean} handshaking - are we currently handshaking
* @private
*/
CloverConnectorImpl.prototype.sendMessage = function(message, handshaking) {
try {
this.log.debug("sendMessage", message);
// If the device is null OR
// we are NOT handshaking, and we are not ready, then error.
// We need this because when we need to send the discoveryRequest even
// though we are not 'ready'. The isReady flag is set using the discoveryResponse.
if(this.device == null || (!handshaking && !this.isReady)) {
var notConnectedErrorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
notConnectedErrorEvent.setMessage("Device is not connected");
notConnectedErrorEvent.setCode(sdk.remotepay.DeviceErrorEventCode.NotConnected);
notConnectedErrorEvent.setType(sdk.remotepay.ErrorType.COMMUNICATION);
this.delegateCloverConnectorListener.onDeviceError(notConnectedErrorEvent);
} else {
this.device.sendMessage(message);
}
} catch(e) {
var errorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
errorEvent.setType(sdk.remotepay.ErrorType.COMMUNICATION);
errorEvent.setCode(sdk.remotepay.DeviceErrorEventCode.UnknownError);
try {
if (e && e.message) {
errorEvent.setMessage("Could not send message : " + JSON.stringify(message) + "," + e.message);
} else {
errorEvent.setMessage("Could not send message : " + JSON.stringify(message) + "," + JSON.stringify(e));
}
}catch(e) {
// no idea what to do now
this.log.error(e);
}
this.delegateCloverConnectorListener.onDeviceError(errorEvent);
}
};
/**
* Define how the low level protocol messages get translated into
* the API calls.
*
* @private
*/
CloverConnectorImpl.prototype.setupMappingOfProtocolMessages = function() {
this.mapAck();
this.mapDeviceEvents();
this.mapConnectionEvents();
this.mapDiscoveryResponse();
this.mapVerifySignature();
this.mapConfirmPayment();
this.mapUIEvents();
this.mapTipAdjustResponse();
this.mapCapturePreauthResponse();
this.mapCloseoutResponse();
this.mapRefundResponse();
this.mapPaymentVoided();
this.mapVaultCardResponse();
this.mapLastMessageResponse();
this.mapFinishOk();
this.mapFinishCancel();
this.mapTxStartResponse();
this.mapRetrievePendingPayments();
this.mapCardDataResponse();
this.mapShutdown();
this.mapReset();
};
/**
* This is a special method only used in Cloud.
*
* @private
*/
CloverConnectorImpl.prototype.mapShutdown = function() {
this.device.on(LanMethod.SHUTDOWN,
function (message) {
this.dispose();
}.bind(this));
};
/**
* This is a special method only used in Cloud.
*
* @private
*/
CloverConnectorImpl.prototype.mapReset = function() {
this.device.on(LanMethod.RESET,
function (message) {
this.onResetRequest();
}.bind(this));
};
/**
* When the server requests a connection reset, this will be called.
*
* The default implementation is to immediately drop the connection and reconnect. To allow
* for current work to be completed, this can be overridden. If a reset request is sent, but ignored,
* the server will forcably drop the connection after a period of time.
*/
CloverConnectorImpl.prototype.onResetRequest = function() {
this.reconnect();
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapRetrievePendingPayments = function() {
this.device.on(sdk.remotemessage.Method.RETRIEVE_PENDING_PAYMENTS_RESPONSE,
function (message) {
this.processRetrievePendingPayments(message);
}.bind(this));
};
/**
* This is a work in progress to generically extract a RemoteMessage from a json
* in a generic way. It seems to work for the cases where the mapping is correct
* in MethodToMessage.
*
* @private
*
* @param remoteMessageJson
* @return {sdk.remotemessage.RemoteMessage} the remote message object type that is mapped from the
* string returned from the remoteMessageJson.getMethod() call.
*/
CloverConnectorImpl.prototype.extractPayloadFromRemoteMessageJson = function(remoteMessageJson) {
// Get the sdk.remotemessage.Message type for this message
var responseMessageType = MethodToMessage[remoteMessageJson.getMethod()];
// Create an instance of the message
var remotemessageMessage = new responseMessageType;
// Populate the message using the remoteMessageJson, which is a json object that is a
// sdk.remotemessage.RemoteMessage
this.remoteMessageParser.parseMessage(message, remotemessageMessage);
// remotemessageMessage is a sdk.remotemessage.Message that is populated.
return remotemessageMessage;
};
/**
* currently unused
* @private
*/
CloverConnectorImpl.prototype.mapTipAdded = function() {
this.device.on(sdk.remotemessage.Method.TIP_ADDED, function (remoteMessageJson) {
var protocolMessage = this.extractPayloadFromRemoteMessageJson(remoteMessageJson);
//Do something with it
}.bind(this));
};
/**
* currently unused
* @private
*/
CloverConnectorImpl.prototype.mapPartialAuth = function() {
this.device.on(sdk.remotemessage.Method.PARTIAL_AUTH, function (remoteMessageJson) {
var protocolMessage = this.extractPayloadFromRemoteMessageJson(remoteMessageJson);
//Do something with it
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapAck = function () {
// Setup special acknowledgement message handling
this.device.on(CloverConnectorImpl.ACK,
function (message) {
// var remoteMessage = new sdk.remotemessage.RemoteMessage();
// Note that the following does not work for The cloud specific messages:
// SHUTDOWN, ERROR; because they are not members of the Method enumeration.
// this.remoteMessageParser.transfertoObject(message, remoteMessage);
// See if there is a registered callback for the message
var ackMessage = new sdk.remotemessage.AcknowledgementMessage();
this.remoteMessageParser.parseMessage(message, ackMessage);
if(!ackMessage.getSourceMessageId()) {
// backwards compatibility hack.
ackMessage.setSourceMessageId(message.id);
}
var callback = this.acknowledgementHooks[ackMessage.getSourceMessageId()];
if (callback) {
try {
// These are one time hooks. Remove the callback
delete this.acknowledgementHooks[ackMessage.getSourceMessageId()];
// Call the registered callback
callback();
} catch (e) {
this.log.warn(e);
}
}
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapDeviceEvents = function() {
this.device.on(WebSocketDevice.DEVICE_OPEN, function (event) {
// The connection to the device is open, but we do not yet know if there is anyone at the other end.
// Send discovery request message.
this.log.debug(event);
this.sendMessage(this.messageBuilder.buildDiscoveryRequestObject(), true);
this.isReady = false;
this.delegateCloverConnectorListener.onConnected();
}.bind(this));
this.device.on(WebSocketDevice.DEVICE_CLOSE, function (event) {
this.log.debug(event);
this.isReady = false;
this.delegateCloverConnectorListener.onDisconnected();
}.bind(this));
this.device.on(WebSocketDevice.DEVICE_ERROR, function (event) {
// todo: Will figure out error codes later
this.log.debug(event);
var deviceErrorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
deviceErrorEvent.setType(sdk.remotepay.ErrorType.COMMUNICATION);
//deviceErrorEvent.setCode(DeviceErrorEventCode.AccessDenied);
// this.isReady = false; For this connector, this may not be true yet.
this.delegateCloverConnectorListener.onDeviceError(deviceErrorEvent);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapConnectionEvents = function() {
this.device.on(CloverConnectorImpl.ERROR, function (event) {
// Will figure out error codes later
this.log.debug(event);
var deviceErrorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
deviceErrorEvent.setType(sdk.remotepay.ErrorType.EXCEPTION);
//deviceErrorEvent.setCode(DeviceErrorEventCode.AccessDenied);
this.delegateCloverConnectorListener.onDeviceError(deviceErrorEvent);
}.bind(this));
this.device.on(WebSocketDevice.CONNECTION_ERROR, function (message) {
// Will figure out error codes later
this.log.debug(message);
var deviceErrorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
deviceErrorEvent.setType(sdk.remotepay.ErrorType.COMMUNICATION);
//deviceErrorEvent.setCode(DeviceErrorEventCode.AccessDenied);
deviceErrorEvent.setMessage(message);
this.delegateCloverConnectorListener.onDeviceError(deviceErrorEvent);
}.bind(this));
this.device.on(WebSocketDevice.CONNECTION_STOLEN, function (message) {
this.log.debug(message);
// How do we handle this? Message is the friendly id of the
// other terminal that stole the connection.
this.isReady = false;
this.delegateCloverConnectorListener.onDisconnected();
}.bind(this));
this.device.on(WebSocketDevice.CONNECTION_DENIED, function (message) {
this.log.debug(message);
// Will figure out error codes later
var deviceErrorEvent = new sdk.remotepay.CloverDeviceErrorEvent();
deviceErrorEvent.setMessage(message);
deviceErrorEvent.setCode(sdk.remotepay.DeviceErrorEventCode.AccessDenied);
deviceErrorEvent.setType(sdk.remotepay.ErrorType.COMMUNICATION);
this.delegateCloverConnectorListener.onDeviceError(deviceErrorEvent);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapDiscoveryResponse = function() {
this.device.on(sdk.remotemessage.Method.DISCOVERY_RESPONSE, function (message) {
var discoveryResponse = new sdk.remotemessage.DiscoveryResponseMessage();
this.remoteMessageParser.parseMessage(message, discoveryResponse);
/** This goes in a deserialization class **/
this.merchantInfo = new sdk.remotepay.MerchantInfo();
this.merchantInfo.setMerchantID(discoveryResponse.getMerchantId());
this.merchantInfo.setMerchantName(discoveryResponse.getMerchantName());
this.merchantInfo.setMerchantMID(discoveryResponse.getMerchantMId());
this.merchantInfo.setSupportsTipAdjust(discoveryResponse.getSupportsTipAdjust());
this.merchantInfo.setSupportsManualRefunds(discoveryResponse.getSupportsManualRefund());
var deviceInfo = new sdk.remotepay.DeviceInfo();
deviceInfo.setName( discoveryResponse.getName() );
deviceInfo.setSerial(discoveryResponse.getSerial());
deviceInfo.setModel(discoveryResponse.getModel());
this.merchantInfo.setDeviceInfo(deviceInfo);
this.deviceSupportsAckMessages = discoveryResponse.supportsAcknowledgement;
this.isReady = discoveryResponse.ready;
if(discoveryResponse.ready) {
this.delegateCloverConnectorListener.onReady(this.merchantInfo);
} else {
// if this is called, the implication is that the device is NOT ready, even if it WAS ready before.
this.delegateCloverConnectorListener.onConnected();
}
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapVerifySignature = function() {
this.device.on(sdk.remotemessage.Method.VERIFY_SIGNATURE, function (message) {
this.processVerifySignature(message);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapConfirmPayment = function() {
this.device.on(sdk.remotemessage.Method.CONFIRM_PAYMENT_MESSAGE, function (message) {
this.processConfirmPayment(message);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapUIEvents = function() {
this.device.on(sdk.remotemessage.Method.UI_STATE, function (message) {
var uiMessage = new sdk.remotemessage.UiStateMessage();
this.remoteMessageParser.parseMessage(message, uiMessage);
var deviceEvent = new sdk.remotepay.CloverDeviceEvent();
deviceEvent.setMessage(uiMessage.getUiText());
// Following maps exactly, but want insulation from
// remotemessage <-> remotepay
//noinspection JSCheckFunctionSignatures
deviceEvent.setEventState(uiMessage.getUiState());
deviceEvent.setInputOptions(uiMessage.getInputOptions());
if(uiMessage.getUiDirection() === sdk.remotemessage.UiDirection.ENTER) {
this.delegateCloverConnectorListener.onDeviceActivityStart(deviceEvent);
} else {
this.delegateCloverConnectorListener.onDeviceActivityEnd(deviceEvent);
}
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapTipAdjustResponse = function() {
this.device.on(sdk.remotemessage.Method.TIP_ADJUST_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.TipAdjustResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.TipAdjustAuthResponse();
apiMessage.setSuccess(message.getSuccess());
apiMessage.setTipAmount(message.getAmount());
apiMessage.setPaymentId(message.getPaymentId());
this.delegateCloverConnectorListener.onTipAdjustAuthResponse(apiMessage);
}.bind(this));
};
/**
* Newer sdk.remotemessage response objects contain a more standard pattern, but they do not share a
* baseclass. This results in the unfortunate need for a convention that is not enforced. Some
* subclasses will have these functions, others may not.
*
* The convention is the presence of the getStatus() and getReason() functions.
*
* @param {remotepay.BaseResponse} apiMessage
* @param {remotemessage.Message} message
*/
CloverConnectorImpl.prototype.populateGeneric = function(apiMessage, message) {
try {
//noinspection JSUnresolvedFunction
apiMessage.setSuccess(message.getStatus() == sdk.remotepay.ResultStatus.SUCCESS);
}catch(e){
this.log.error("Attempt to set success failed!");
}
try {
//noinspection JSUnresolvedFunction
apiMessage.setResult(message.getStatus() == sdk.remotepay.ResultStatus.SUCCESS ?
sdk.remotepay.ResponseCode.SUCCESS : message.getCode() == sdk.remotepay.ResultStatus.FAIL ?
sdk.remotepay.ResponseCode.FAIL : sdk.remotepay.ResponseCode.ERROR );
}catch(e){
this.log.error("Attempt to set result failed!");
}
try {
//noinspection JSUnresolvedFunction
apiMessage.setReason(message.getReason());
}catch(e){
this.log.warn("Attempt to set reason failed!");
}
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapCapturePreauthResponse = function() {
this.device.on(sdk.remotemessage.Method.CAPTURE_PREAUTH_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.CapturePreAuthResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.CapturePreAuthResponse();
this.populateGeneric(apiMessage, message);
apiMessage.setPaymentId(message.getPaymentId());
apiMessage.setAmount(message.getAmount());
apiMessage.setTipAmount(message.getTipAmount());
this.delegateCloverConnectorListener.onCapturePreAuthResponse(apiMessage);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapCloseoutResponse = function() {
this.device.on(sdk.remotemessage.Method.CLOSEOUT_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.CloseoutResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.CloseoutResponse();
this.populateGeneric(apiMessage, message);
apiMessage.setBatch(message.getBatch());
this.delegateCloverConnectorListener.onCloseoutResponse(apiMessage);
}.bind(this));
};
/**
* This message has additional information about failures, so we build
* a response based on this information - if the result is a failure.
* This will then be used in the "finishCancel" processing.
*
*
* @private
*/
CloverConnectorImpl.prototype.mapRefundResponse = function() {
this.device.on(sdk.remotemessage.Method.REFUND_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.RefundResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
if(message.getCode() != sdk.remotemessage.TxState.SUCCESS) {
this.refundPaymentResponse = new sdk.remotepay.RefundPaymentResponse();
this.refundPaymentResponse.setSuccess(message.getCode() == sdk.remotemessage.TxState.SUCCESS);
this.refundPaymentResponse.setResult(message.getCode() == sdk.remotemessage.TxState.SUCCESS ?
sdk.remotepay.ResponseCode.SUCCESS : message.getCode() == sdk.remotemessage.TxState.FAIL ?
sdk.remotepay.ResponseCode.FAIL : sdk.remotepay.ResponseCode.ERROR);
this.refundPaymentResponse.setReason(message.getReason());
this.refundPaymentResponse.setMessage(message.getMessage());
this.refundPaymentResponse.setRefund(message.getRefund());
}
// This is now called from finishOK mapping
// this.delegateCloverConnectorListener.onRefundPaymentResponse(apiMessage);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapPaymentVoided = function() {
this.device.on(sdk.remotemessage.Method.PAYMENT_VOIDED, function (remoteMessage) {
this.log.debug(remoteMessage);
var message = new sdk.remotemessage.PaymentVoidedMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
this.sendVoidPaymentResponse(message.getPayment());
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapVaultCardResponse = function() {
this.device.on(sdk.remotemessage.Method.VAULT_CARD_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.VaultCardResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.VaultCardResponse();
this.populateGeneric(apiMessage, message);
apiMessage.setCard(message.getCard());
var endOfOperationCallback = function() {
this.delegateCloverConnectorListener.onVaultCardResponse(apiMessage);
}.bind(this);
if(apiMessage.getSuccess()) {
this.endOfOperationOK(endOfOperationCallback);
} else {
this.endOfOperationCancel(endOfOperationCallback);
}
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapCardDataResponse = function() {
this.device.on(sdk.remotemessage.Method.CARD_DATA_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.CardDataResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.ReadCardDataResponse();
this.populateGeneric(apiMessage, message);
apiMessage.setCardData(message.getCardData());
var endOfOperationCallback = function() {
this.delegateCloverConnectorListener.onReadCardDataResponse(apiMessage);
}.bind(this);
if(apiMessage.getSuccess()) {
this.endOfOperationOK(endOfOperationCallback);
} else {
this.endOfOperationCancel(endOfOperationCallback);
}
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapLastMessageResponse = function() {
this.device.on(sdk.remotemessage.Method.LAST_MSG_RESPONSE, function (remoteMessage) {
var message = new sdk.remotemessage.LastMessageResponseMessage();
// Pass the flag to attach unknown properties to the object.
this.remoteMessageParser.parseMessage(remoteMessage, message);
var requestMessageType = MethodToMessage[message.getRequest().getMethod()];
var requestMessageInstance = new requestMessageType;
this.remoteMessageParser.transfertoObject(message.getRequest(), requestMessageInstance);
message.setRequest(requestMessageInstance);
var responseMessageType = MethodToMessage[message.getResponse().getMethod()];
var responseMessageInstance = new responseMessageType;
this.remoteMessageParser.transfertoObject(message.getResponse(), responseMessageInstance);
message.setResponse(responseMessageInstance);
var apiMessage = new sdk.remotepay.BaseResponse();
apiMessage.setCode(sdk.remotepay.ResponseCode.SUCCESS);
apiMessage["request"] = requestMessageInstance;
apiMessage["request"] = responseMessageInstance;
this.delegateCloverConnectorListener.onLastTransactionResponse(apiMessage);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapTxStartResponse = function () {
this.device.on(sdk.remotemessage.Method.TX_START_RESPONSE, function (message) {
this.log.debug(message);
var txStartResponseMessage = new sdk.remotemessage.TxStartResponseMessage();
this.remoteMessageParser.parseMessage(message, txStartResponseMessage);
if (!txStartResponseMessage.getSuccess()) {
var theLastRequest = this.lastRequest;
this.lastRequest = null;
var endOfOperationCallback = null;
if (this.matchsLastRequest(theLastRequest, sdk.remotepay.PreAuthRequest)) {
var preAuthResponse = new sdk.remotepay.PreAuthResponse();
this.populateTxStartResponseToBaseResponse(txStartResponseMessage, preAuthResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onPreAuthResponse(preAuthResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.AuthRequest)) {
var authResponse = new sdk.remotepay.AuthResponse();
this.populateTxStartResponseToBaseResponse(txStartResponseMessage, authResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onAuthResponse(authResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.SaleRequest)) {
var saleResponse = new sdk.remotepay.SaleResponse();
this.populateTxStartResponseToBaseResponse(txStartResponseMessage, saleResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onSaleResponse(saleResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.ManualRefundRequest)) {
var manualRefundResponse = new sdk.remotepay.ManualRefundResponse();
this.populateTxStartResponseToBaseResponse(txStartResponseMessage, manualRefundResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onManualRefundResponse(manualRefundResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.RefundRequestMessage)) {
var apiMessage = new sdk.remotepay.RefundPaymentResponse();
this.populateTxStartResponseToBaseResponse(txStartResponseMessage, apiMessage);
this.refundPaymentResponse = null;
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onRefundPaymentResponse(apiMessage);
}.bind(this);
}
this.endOfOperationCancel(endOfOperationCallback);
}
}.bind(this));
};
CloverConnectorImpl.prototype.matchsLastRequest = function (lastRequest, theType) {
var result = false;
if(lastRequest != null) {
var lastReqStr = '' + lastRequest._class_;
var theTypeStr = '' + theType;
result = (lastReqStr == theTypeStr);
}
return result;
};
/**
* @private
* @param {remotemessage.TxStartResponseMessage} txStartResponseMessage
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateTxStartResponseToBaseResponse = function(txStartResponseMessage, response) {
response.setSuccess(txStartResponseMessage.getSuccess());
var result = sdk.remotepay.ResponseCode[txStartResponseMessage.getResult()];
if(!result)result = sdk.remotepay.ResponseCode.FAIL;
response.setResult(result);
response.setReason(txStartResponseMessage.getExternalPaymentId());
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapFinishOk = function () {
this.device.on(sdk.remotemessage.Method.FINISH_OK, function (message) {
this.log.debug(message);
var finishOk = new sdk.remotemessage.FinishOkMessage();
this.remoteMessageParser.parseMessage(message, finishOk);
var theLastRequest = this.lastRequest;
this.lastRequest = null;
var endOfOperationCallback = null;
if (finishOk.getPayment() !== undefined) {
if (this.matchsLastRequest(theLastRequest, sdk.remotepay.PreAuthRequest)) {
var preAuthResponse = new sdk.remotepay.PreAuthResponse();
this.populateOkPaymentResponse(preAuthResponse, finishOk.getPayment(), finishOk.getSignature());
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onPreAuthResponse(preAuthResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.AuthRequest)) {
var authResponse = new sdk.remotepay.AuthResponse();
this.populateOkPaymentResponse(authResponse, finishOk.getPayment(), finishOk.getSignature());
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onAuthResponse(authResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.SaleRequest)) {
var saleResponse = new sdk.remotepay.SaleResponse();
this.populateOkPaymentResponse(saleResponse, finishOk.getPayment(), finishOk.getSignature());
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onSaleResponse(saleResponse);
}.bind(this);
} else if (theLastRequest === null) {
this.showWelcomeScreen();
return; // skip the end of operation
} else {
this.resetDevice();
throw new CloverError(CloverError.INVALID_DATA,
"Failed to pair this response. " + finishOk.getPayment());
}
} else if (finishOk.getCredit()) {
var manualRefundResponse = new sdk.remotepay.ManualRefundResponse();
manualRefundResponse.setSuccess(true);
manualRefundResponse.setCredit(finishOk.getCredit());
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onManualRefundResponse(manualRefundResponse);
}.bind(this);
} else if (finishOk.getRefund()) {
var apiMessage = new sdk.remotepay.RefundPaymentResponse();
apiMessage.setSuccess(true);
apiMessage.setRefund(finishOk.getRefund());
this.refundPaymentResponse = null;
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onRefundPaymentResponse(apiMessage);
}.bind(this);
} else {
// Something is wrong...
this.log.error(sdk.remotemessage.Method.FINISH_OK +
" received, but no payment, credit or refund attached to it!");
}
this.endOfOperationOK(endOfOperationCallback);
}.bind(this));
};
/**
* @private
*/
CloverConnectorImpl.prototype.mapFinishCancel = function () {
this.device.on(sdk.remotemessage.Method.FINISH_CANCEL, function (message) {
this.log.debug(message);
var finishCancel = new sdk.remotemessage.FinishCancelMessage();
this.remoteMessageParser.parseMessage(message, finishCancel);
var endOfOperationCallback = null;
var theLastRequest = this.lastRequest;
this.lastRequest = null;
if (this.matchsLastRequest(theLastRequest, sdk.remotepay.PreAuthRequest)) {
var preAuthResponse = new sdk.remotepay.PreAuthResponse();
this.populateCancelResponse(preAuthResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onPreAuthResponse(preAuthResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.AuthRequest)) {
var authResponse = new sdk.remotepay.AuthResponse();
this.populateCancelResponse(authResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onAuthResponse(authResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.SaleRequest)) {
var saleResponse = new sdk.remotepay.SaleResponse();
this.populateCancelResponse(saleResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onSaleResponse(saleResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.ManualRefundRequest)) {
var manualRefundResponse = new sdk.remotepay.ManualRefundResponse();
this.populateCancelResponse(manualRefundResponse);
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onManualRefundResponse(manualRefundResponse);
}.bind(this);
} else if (this.matchsLastRequest(theLastRequest, sdk.remotepay.RefundPaymentRequest)) {
/*
This case is a little different. The REFUND_RESPONSE message has greater details on failures,
so we will try to return information from that (if it is set)
*/
var apiMessage = this.refundPaymentResponse;
this.refundPaymentResponse = null;
if(!apiMessage) {
// The REFUND_RESPONSE did not set the variable, make a new one and populate it.
apiMessage = new sdk.remotepay.RefundPaymentResponse();
this.populateCancelResponse(apiMessage);
}
endOfOperationCallback = function () {
this.delegateCloverConnectorListener.onRefundPaymentResponse(apiMessage);
}.bind(this);
}
this.endOfOperationCancel(endOfOperationCallback);
}.bind(this));
};
/**
* @private
* @param message
*/
CloverConnectorImpl.prototype.processVerifySignature = function(message) {
var verifySignature = new sdk.remotemessage.VerifySignatureMessage();
this.remoteMessageParser.parseMessage(message, verifySignature);
var verifySignatureRequest = new sdk.remotepay.VerifySignatureRequest();
verifySignatureRequest.setPayment(verifySignature.getPayment());
verifySignatureRequest.setSignature(verifySignature.getSignature());
this.delegateCloverConnectorListener.onVerifySignatureRequest(verifySignatureRequest);
};
/**
* @private
* @param remoteMessage
*/
CloverConnectorImpl.prototype.processRetrievePendingPayments = function(remoteMessage) {
var message = new sdk.remotemessage.RetrievePendingPaymentsResponseMessage();
this.remoteMessageParser.parseMessage(remoteMessage, message);
var apiMessage = new sdk.remotepay.RetrievePendingPaymentsResponse();
this.populateGeneric(apiMessage, message);
apiMessage.setPendingPaymentEntries(message.getPendingPaymentEntries());
this.delegateCloverConnectorListener.onRetrievePendingPaymentsResponse(apiMessage);
};
/**
* @private
* @param message
*/
CloverConnectorImpl.prototype.processConfirmPayment = function(message) {
var confirmPayment = new sdk.remotemessage.ConfirmPaymentMessage();
this.remoteMessageParser.parseMessage(message, confirmPayment);
var confirmPaymentRequest = new sdk.remotepay.ConfirmPaymentRequest();
confirmPaymentRequest.setPayment(confirmPayment.getPayment());
confirmPaymentRequest.setChallenges(confirmPayment.getChallenges());
this.delegateCloverConnectorListener.onConfirmPaymentRequest(confirmPaymentRequest);
};
/**
* Other implementations send this without receiving a message.
* Not sure if this one should do the same.
*
* @private
* @param {payments.Payment} payment
*/
CloverConnectorImpl.prototype.sendVoidPaymentResponse = function(payment) {
var apiMessage = new sdk.remotepay.VoidPaymentResponse();
apiMessage.setSuccess(true);
apiMessage.setResult(sdk.remotepay.ResponseCode.SUCCESS);
apiMessage.setPaymentId(payment.getId());
this.delegateCloverConnectorListener.onVoidPaymentResponse(apiMessage);
};
/**
* action after an operation cancel
* @private
*/
CloverConnectorImpl.prototype.endOfOperationCancel = function(callback) {
// Build the transaction cancelled message to display
var protocolRequest = new sdk.remotemessage.TerminalMessage();
protocolRequest.setText(MessageBundle.TRANSACTION_CANCELLED);
// Send the message. Once the ACK is received for this, we will wait three
// seconds, then call the end of operation function, passing along
// the callback passed to this.
this.callOnACK(protocolRequest, function () {
if (this.device != null) {
setTimeout(
function () {
this.endOfOperationOK(callback);
}.bind(this), 3000 // three seconds
);
}
}.bind(this));
};
/**
* action after an operation ok
* @private
*/
CloverConnectorImpl.prototype.endOfOperationOK = function(callback) {
// Build the thank you message
var protocolRequest = new sdk.remotemessage.ThankYouMessage();
// Send the thank you message, wait for the "ACK" from it to present the
// Welcome Screen after three seconds.
this.callOnACK(protocolRequest, function () {
if (this.device != null) {
setTimeout(
function () {
if (this.device != null) {
// Build the "Welcome" message
var protocolRequest2 = new sdk.remotemessage.WelcomeMessage();
// Send the welcome message, wait for the "ACK" from it to call
// whatever callback was passed.
this.callOnACK(protocolRequest2, callback);
}
}.bind(this), 3000 // three seconds
);
}
}.bind(this));
};
CloverConnectorImpl.prototype.callOnACK = function(protocolRequest, callback) {
if(this.device != null) {
// Wait for an ACK... then call sendVoidPaymentResponse
var remoteMessage = this.messageBuilder.buildRemoteMessageObject(protocolRequest);
// This is a backwards compatibility hack.
if(this.deviceSupportsAckMessages) {
// If acknowledgements are supported, then
// wait for an ACK from the device. The ACK hook will call
// callback()
// Add the hook
// send the message
this.addAcknowledgementHook(remoteMessage.getId(), callback);
this.sendMessage(remoteMessage);
} else {
// ACK messages are not supported. We will just send the message before
// calling the callback. This causes threading issues, but is necessary for
// backwards compatibility.
this.sendMessage(remoteMessage);
if(callback) {
callback();
}
}
}
};
/**
* @private
* @param {remotepay.PaymentResponse} response
* @param {payments.Payment} payment
* @param {base.Signature} signature
*/
CloverConnectorImpl.prototype.populateOkPaymentResponse = function(response, payment, signature) {
response.setPayment(payment);
response.setSignature(signature);
response.setSuccess(true);
response.setIsPreAuth(
(payment.getResult() === sdk.payments.Result.AUTH) &&
(payment.getCardTransaction().getType() === sdk.payments.CardTransactionType.PREAUTH));
response.setIsSale(
(payment.getResult() === sdk.payments.Result.SUCCESS) &&
(payment.getCardTransaction().getType() === sdk.payments.CardTransactionType.AUTH));
response.setIsAuth(
(payment.getResult() === sdk.payments.Result.SUCCESS) &&
(payment.getCardTransaction().getType() === sdk.payments.CardTransactionType.PREAUTH));
};
/**
* @private
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateCancelResponse = function(response) {
response.setSuccess(false);
response.setResult(sdk.remotepay.ResponseCode.CANCEL);
return response;
};
/**
* @private
* @param {string} messagePrefix - a descriptive prefix for the message
* @param {remotepay.BaseResponse} response
* @returns {*}
*/
CloverConnectorImpl.prototype.populateConnectionError = function(messagePrefix, response) {
response.setSuccess(false);
response.setResult(sdk.remotepay.ResponseCode.ERROR);
response.setReason("Device Connection Error");
response.setMessage(messagePrefix + "The Clover device is not connected.");
return response;
};
/**
* @private
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateMerchantConfigurationUnsupported = function(response) {
response.setSuccess(false);
response.setResult(sdk.remotepay.ResponseCode.UNSUPPORTED);
response.setReason("Merchant Configuration Error");
return response;
};
/**
* @private
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateRequestValidationFail = function(response) {
response.setSuccess(false);
response.setResult(sdk.remotepay.ResponseCode.FAIL);
response.setReason("Request Validation Error");
return response;
};
/**
* @private
* @param {string} messagePrefix - a descriptive prefix for the message
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateEmptyRequestValidationFail = function(messagePrefix, response) {
this.populateRequestValidationFail(response);
response.setMessage(messagePrefix + "The request that was passed in for processing is empty.");
return response;
};
/**
* @private
* @param {string} messagePrefix - a descriptive prefix for the message
* @param {remotepay.BaseRequest} request
* @param {remotepay.BaseResponse} response
*/
CloverConnectorImpl.prototype.populateRequestAmountValidationFail = function(messagePrefix, request, response) {
this.populateRequestValidationFail(response);
response.setMessage(messagePrefix + "The request amount cannot be zero. Original Request = " + request);
return response;
};
/**
* Begin connecting.
*/
CloverConnectorImpl.prototype.initializeConnection = function() {
if (this.configuration.oauthToken) {
if(!this.configuration.merchantId) {
if(!this.cloverOAuth) {
// We must have the merchant id. This will make the merchant log in again.
this.configuration.oauthToken = this.getAuthToken(); // calls initializeConnection
return;
} else {
this.configuration.merchantId = this.cloverOAuth.getURLParams()["merchant_id"];
}
}
if(!this.configuration.merchantId) {
// could not connect, not enough info
var errorResponse1 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse1.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse1.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse1.setMessage("Cannot determine merchant to use. " +
"Configuration is missing merchant id (merchantId)");
this.delegateCloverConnectorListener.onDeviceError(errorResponse1);
} else if (this.configuration.domain) {
var endpointConfig = new EndPointConfig(this.configuration);
var endpoints = new Endpoints(endpointConfig);
if (this.configuration.deviceId) {
// Contact the server and tell it to send an alert to the device
this.sendNotificationToDevice(endpoints);
} else {
this.getDeviceId(endpoints);
}
} else {
// Note: Could default to www.clover.com here
var errorResponse2 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse2.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse2.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse2.setMessage("Cannot determine domain to use. " +
"Configuration is missing domain (domain)");
this.delegateCloverConnectorListener.onDeviceError(errorResponse2);
}
} else if (this.configuration.clientId && this.configuration.domain) {
this.configuration.oauthToken = this.getAuthToken(); // calls initializeConnection
} else {
var errorResponse = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse2.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse.setMessage("Cannot determine client id or domain to use. " +
"Configuration is missing domain (domain), or client id (clientId)");
this.delegateCloverConnectorListener.onDeviceError(errorResponse);
}
};
/**
* @private
* @param endpoints
*/
CloverConnectorImpl.prototype.getDeviceId = function(endpoints) {
if(this.configuration.deviceSerialId) {
var url = endpoints.getDevicesEndpoint(this.configuration.merchantId);
if(!this["devices"] || !this["devices"][url]) {
var xmlHttpSupport = new XmlHttpSupport();
xmlHttpSupport.getData(url,
function (devices) {
if(!this["devices"]) {
this.devices = {};
}
this.devices[url] = this.buildMapOfSerialToDevice(devices);
this.handleDeviceResult(this.devices[url]);
}.bind(this),
function (error) {
var errorResponse1 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse1.setType(sdk.remotepay.ErrorType.COMMUNICATION);
errorResponse1.setCode(sdk.remotepay.DeviceErrorEventCode.UnknownError);
errorResponse1.setMessage(error);
this.delegateCloverConnectorListener.onDeviceError(errorResponse1)
}.bind(this)
);
} else {
this.handleDeviceResult(this.devices[url]);
}
} else {
// could not connect, not enough info
var errorResponse = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse.setMessage("Cannot determine device to use. " +
"Configuration is missing device serial id (deviceSerialId)");
this.delegateCloverConnectorListener.onDeviceError(errorResponse);
}
};
/**
* This function is called with the list of devices for the merchant. The default implementation
* is to set the deviceId to theat of the device that maps to the this.configuration.deviceSerialId
* @param devices
*/
CloverConnectorImpl.prototype.handleDeviceResult = function(devices) {
var myDevice = devices[this.configuration.deviceSerialId];
if (null == myDevice) {
var errorResponse = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse.setMessage("Cannot determine device to use. " +
"Device " + this.configuration.deviceSerialId + " not in set returned.");
this.delegateCloverConnectorListener.onDeviceError(errorResponse);
} else {
// Stations do not support the kiosk/pay display.
// If the user has selected one, then print out a (loud) warning
if (myDevice.model == "Clover_C100") {
this.log.warn(
"Warning - Selected device model (" +
devices[this.configuration.deviceSerialId].model +
") does not support cloud pay display." +
" Will attempt to send notification to device, but no response" +
" should be expected.");
}
this.configuration.deviceId = myDevice.id;
this.initializeConnection();
}
};
/**
* Handle a set of devices. Sets up an internal map of devices from serial->device
* @private
* @param devicesVX
*/
CloverConnectorImpl.prototype.buildMapOfSerialToDevice = function (devicesVX) {
var devices = null;
var deviceBySerial = {};
// depending on the version of the call, the devices might be in a slightly different format.
// We would need to determine what devices were capable of doing what we want. This means we
// need to know if the device has the websocket connection enabled. The best way to do this is
// to make a different REST call, but we could filter the devices here.
if (devicesVX['devices']) {
devices = devicesVX.devices;
}
else if (devicesVX['elements']) {
devices = devicesVX.elements;
}
if (devices) {
var i;
for (i = 0; i < devices.length; i++) {
deviceBySerial[devices[i].serial] = devices[i];
}
}
return deviceBySerial;
};
/**
* @private
*/
CloverConnectorImpl.prototype.getAuthToken = function() {
this.cloverOAuth = new CloverOAuth2(this.configuration);
return this.cloverOAuth.getAccessToken(
// recurse
function(token) {
this.configuration.oauthToken = token;
this.initializeConnection();
}.bind(this)
);
};
/**
* @private
* @param {Endpoints} endpoints
*/
CloverConnectorImpl.prototype.sendNotificationToDevice = function(endpoints) {
var xmlHttpSupport = new XmlHttpSupport();
var noDashesDeviceId = this.configuration.deviceId.replace(/-/g, "");
var deviceContactInfo = new DeviceContactInfo(noDashesDeviceId, true);
xmlHttpSupport.postData(endpoints.getAlertDeviceEndpoint(this.configuration.merchantId),
function(data) { this.deviceNotificationSent(endpoints, deviceContactInfo, data);}.bind(this),
function(error) {
var errorResponse = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse.setType(sdk.remotepay.ErrorType.COMMUNICATION);
errorResponse.setCode(sdk.remotepay.DeviceErrorEventCode.SendNotificationFailure);
errorResponse.setMessage("Error sending alert to device." + error);
this.delegateCloverConnectorListener.onDeviceError(errorResponse);
}.bind(this),
deviceContactInfo);
};
/**
* The format of the data received is:
* {
* 'sent': true | false,
* 'host': web_socket_host,
* 'token': token_to_link_to_the_device
* }
* Use this data to build the web socket url
* Note "!data.hasOwnProperty('sent')" is included to allow for
* backwards compatibility. If the property is NOT included, then
* we will assume an earlier version of the protocol on the server,
* and assume that the notification WAS SENT.
*
* @private
* @param {Endpoints} endpoints
* @param {DeviceContactInfo} deviceContactInfo
* @param {NotificationResponse} data
*/
CloverConnectorImpl.prototype.deviceNotificationSent = function(endpoints, deviceContactInfo, data) {
// When using the cloud, we need to be able to send another notification to bootstrap
// the device. Set a boot strap function on the device to do this on reconnect.
var xmlHttpSupport = new XmlHttpSupport();
this.device.bootStrapReconnect = function(callback) {
xmlHttpSupport.postData(endpoints.getAlertDeviceEndpoint(this.configuration.merchantId),
callback, callback, deviceContactInfo);
}.bind(this);
// Note "!data.hasOwnProperty('sent')" is included to allow for
// backwards compatibility. If the property is NOT included, then
// we will assume an earlier version of the protocol on the server,
// and assume that the notification WAS SENT.
if (!data.hasOwnProperty('sent') || data.sent) {
var url = data.host + Endpoints.WEBSOCKET_PATH + '?token=' + data.token;
this.log.debug("Server responded with information on how to contact device. " +
"Opening communication channel...");
// The response to this will be reflected in the device.onopen method (or on error),
// That function will attempt the discovery.
this.configuration.deviceURL = url;
//recurse
this.device.contactDevice(this.configuration.deviceURL);
} else {
var errorResponse = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse.setCode(sdk.remotepay.DeviceErrorEventCode.SendNotificationFailure);
errorResponse.setMessage("Error sending alert to device. Device is not connected to server.");
this.delegateCloverConnectorListener.onDeviceError(errorResponse);
}
};
/**
* Send a signature acceptance
* @param {remotepay.VerifySignatureRequest} request
* @return void
*/
CloverConnectorImpl.prototype.acceptSignature = function(request) {
if(this.validateSignatureRequest("In AcceptSignature : ", request)) {
var payment = request.getPayment();
var protocolRequest = new sdk.remotemessage.SignatureVerifiedMessage();
protocolRequest.setPayment(payment);
protocolRequest.setVerified(true);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
}
};
/**
* Reject a signature
* @param {remotepay.VerifySignatureRequest} request
* @return void
*/
CloverConnectorImpl.prototype.rejectSignature = function(request) {
if(this.validateSignatureRequest("In RejectSignature : ", request)) {
var payment = request.getPayment();
var protocolRequest = new sdk.remotemessage.SignatureVerifiedMessage();
protocolRequest.setPayment(payment);
protocolRequest.setVerified(false);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
}
};
/**
* @private
* @param messagePrefix - used in error message if validation fails.
* @param {remotepay.VerifySignatureRequest} request
* @returns {boolean} true if validation passed, else false
*/
CloverConnectorImpl.prototype.validateSignatureRequest = function(messagePrefix, request) {
if(request == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage(messagePrefix + "VerifySignatureRequest cannot be null. ");
this.delegateCloverConnectorListener.onDeviceError(response);
return false;
}
if (!request.getPayment() || !request.getPayment().getId()) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage(messagePrefix + "VerifySignatureRequest.Payment must have an ID. ");
this.delegateCloverConnectorListener.onDeviceError(response);
return false;
}
return true;
};
/**
* Accepts a payment that has been challenged.
* @param {payments.Payment} payment
* @return void
*/
CloverConnectorImpl.prototype.acceptPayment = function(payment) {
if(payment == null || payment.getId() == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In AcceptPayment: The Payment ID cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.PaymentConfirmedMessage();
protocolRequest.setPayment(payment);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Rejects a payment that has been challenged.
* @param {payments.Payment} payment
* @param {base.Challenge} challenge
* @return void
*/
CloverConnectorImpl.prototype.rejectPayment = function(payment, challenge) {
if(payment == null || payment.getId() == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In RejectPayment: The Payment ID cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.PaymentRejectedMessage();
protocolRequest.setPayment(payment);
protocolRequest.setVoidReason(challenge.getReason());
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Request an authorization operation.
* @param {remotepay.AuthRequest} request
* @return void
*/
CloverConnectorImpl.prototype.auth = function(request) {
if(!this.validateSaleAuthPreauthManref(
"In Auth : AuthRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateSupportsTipAdjust(
"In Auth : AuthRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.TxStartRequestMessage();
this.verifyValidAmount(request.getAmount());
var payIntent = this.populateBasePayIntent(request);
payIntent.setTaxAmount(request.getTaxAmount());
payIntent.setIsDisableCashBack(request.getDisableCashback() === undefined
? this.configuration.disableCashback : request.getDisableCashback());
payIntent.setAllowOfflinePayment(request.getAllowOfflinePayment() === undefined
? this.configuration.allowOfflinePayment : request.getAllowOfflinePayment());
payIntent.setApproveOfflinePaymentWithoutPrompt(request.getApproveOfflinePaymentWithoutPrompt() === undefined
? this.configuration.approveOfflinePaymentWithoutPrompt : request.getApproveOfflinePaymentWithoutPrompt());
protocolRequest.setSuppressOnScreenTips(true);
protocolRequest.setPayIntent(payIntent);
this.lastRequest = request;
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Handle a check to see if the payment id is blank
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validatePaymentId = function(messagePrefix, responseType, request, onresponse) {
if ( !request.getPaymentId() )
{
var response = new responseType;
this.populateRequestValidationFail(response);
response.setMessage(messagePrefix + "PaymentID cannot be empty.");
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Handle a check to see if the merchant supports tip adjust.
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateSupportsManualRefunds = function(messagePrefix, responseType, request, onresponse) {
if (!this.merchantInfo.getSupportsManualRefunds())
{
var response = new responseType;
this.populateMerchantConfigurationUnsupported(response);
response.setMessage(messagePrefix + "Manual refunds are not supported by the merchant configured gateway." + request);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Handle a check to see if the merchant supports tip adjust.
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateSupportsTipAdjust = function(messagePrefix, responseType, request, onresponse) {
if (!this.merchantInfo.getSupportsTipAdjust())
{
var response = new responseType;
this.populateMerchantConfigurationUnsupported(response);
response.setMessage(messagePrefix + "Auths are not enabled for the payment gateway. Original Request = " + request);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Handle a check to see if the merchant supports pre auth.
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateSupportsPreAuth = function(messagePrefix, responseType, request, onresponse) {
if (!this.merchantInfo.getSupportsPreAuths())
{
var response = new responseType;
this.populateMerchantConfigurationUnsupported(response);
response.setMessage(messagePrefix + "PreAuths are not enabled for the payment gateway. Original Request = " + request);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Do common validation for a valid tip amount
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateTipAmount = function(messagePrefix, responseType, request, onresponse) {
if (request.getTipAmount() && request.getTipAmount() < 0) {
var response = new responseType;
this.populateRequestValidationFail(response);
response.setMessage(messagePrefix + "The request tip amount cannot be less than zero. Original Request = " + request);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Do common validation for ready + request + amount
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateReadyRequest = function(messagePrefix, responseType, request, onresponse) {
if( !this.validateReady(messagePrefix, responseType, request, onresponse) ||
!this.validateRequest(messagePrefix, responseType, request, onresponse)) {
return false;
}
return true;
};
/**
* Do common validation for ready + request + amount
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateReady = function(messagePrefix, responseType, request, onresponse) {
if (this.device == null || !this.isReady) {
var response = new responseType;
this.populateConnectionError(messagePrefix, response);
if (onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Do common validation for ready + request + amount
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateRequest = function(messagePrefix, responseType, request, onresponse) {
if (request == null) {
var response = new responseType;
this.populateEmptyRequestValidationFail(messagePrefix, response);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Do common validation for amount
*
* @private
* @param {String} messagePrefix
* @param responseType - a response type
* @param request - a request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateAmount = function(messagePrefix, responseType, request, onresponse) {
if(request.getAmount() <= 0) {
var response = new responseType;
this.populateRequestAmountValidationFail(messagePrefix, request, response);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* Do common validation for sale auth and preauth
*
* @private
* @param {String} messagePrefix
* @param responseType - a sale, auth or preauth response type
* @param request - a sale, auth or preauth request
* @param onresponse - the appropriate callback on validation fail
* @returns {boolean} if validation succeeds, else false
*/
CloverConnectorImpl.prototype.validateSaleAuthPreauthManref = function(messagePrefix, responseType, request, onresponse) {
if(! this.validateReadyRequest(messagePrefix, responseType, request, onresponse)) {
return false;
}
if(!this.validateAmount(messagePrefix, responseType, request, onresponse)) {
return false;
}
if(!request.getExternalId() || !request.getExternalId().trim()) {
var response = new responseType;
this.populateRequestValidationFail(response);
response.setMessage(messagePrefix + "The request ExternalId cannot be null or blank. Original Request = " + request);
if(onresponse) {
onresponse(response);
}
return false;
}
if (request.getVaultedCard() && !this.merchantInfo.getSupportsVaultCards()) {
var response = new responseType;
this.populateMerchantConfigurationUnsupported(response);
response.setMessage(messagePrefix + "Vault Card support is not offered by the merchant configured gateway. Original Request = " + request);
if(onresponse) {
onresponse(response);
}
return false;
}
return true;
};
/**
* @private
* @param {remotepay.TransactionRequest} request
*/
CloverConnectorImpl.prototype.populateBasePayIntent = function(request) {
var payIntent = new sdk.remotemessage.PayIntent();
if(!request.getExternalId()) {
throw new CloverError(CloverError.INVALID_DATA, "externalId is required");
}
payIntent.setExternalPaymentId(request.getExternalId()); //
// Following maps exactly, but different types are attempt to isolate
// remotemessage <-> remotepay
//noinspection JSCheckFunctionSignatures
payIntent.setTransactionType(request.getType());//
payIntent.setAmount(request.getAmount()); //
payIntent.setVaultedCard(request.getVaultedCard());//
payIntent.setIsCardNotPresent(request.getCardNotPresent()); //
payIntent.setCardEntryMethods(request.getCardEntryMethods() === undefined //
? this.configuration.cardEntryMethods : request.getCardEntryMethods());
payIntent.setDisableRestartTransactionWhenFailed(request.getDisableRestartTransactionOnFail() === undefined //
? this.configuration.disableRestartTransactionWhenFailed : request.getDisableRestartTransactionOnFail());
// The CloverShouldHandleReceipts flag will be available in the 1.2 version. This was the result of
// updating the remote-pay-cloud-api version too soon. Rather than back out other valuable changes,
// this was added to allow for forward (and backward) compatibility
if(request.hasOwnProperty("getCloverShouldHandleReceipts")) {
payIntent.setRemotePrint(request.getCloverShouldHandleReceipts() === undefined //
? this.configuration.remotePrint : !request.getCloverShouldHandleReceipts());
} else if (request.hasOwnProperty("getDisablePrinting")) {
payIntent.setRemotePrint(request.getDisablePrinting() === undefined //
? this.configuration.remotePrint : request.getDisablePrinting());
} else {
this.log.warn("Unable to determine remote print flag from request. Expecting either 'DisablePrinting' or 'CloverShouldHandleReceipts'");
}
payIntent.setRequiresRemoteConfirmation(true);
// employeeId? - "id": "DFLTEMPLOYEE"
return payIntent;
};
/**
* @private
* @param {Number} amount - an integer
* @param {Boolean} [allowZero] - if true then the amount can be zero
* @throws {CloverError} if the amount is undefined, not an integer or not positive
*/
CloverConnectorImpl.prototype.verifyValidAmount = function (amount, allowZero) {
if ((amount === undefined) || !CloverConnectorImpl.isInt(amount) || (amount < 0) || (!allowZero && amount === 0)) {
throw new CloverError(CloverError.INVALID_DATA, "Amount must be an integer with a value greater than 0");
}
};
/**
* Request a preauth operation.
* @param {remotepay.PreAuthRequest} request
* @return void
*/
CloverConnectorImpl.prototype.preAuth = function(request) {
if(!this.validateSaleAuthPreauthManref(
"In PreAuth : PreAuthRequest - ",
sdk.remotepay.PreAuthResponse,
request,
this.delegateCloverConnectorListener.onPreAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if (!this.merchantInfo.getSupportsPreAuths())
{
var response = new sdk.remotepay.PreAuthResponse();
this.populateMerchantConfigurationUnsupported(response);
response.setMessage("In PreAuth : PreAuthRequest - " + "PreAuths are not enabled for the payment gateway. Original Request = " + request);
this.delegateCloverConnectorListener.onSaleResponse(response);
return;
}
var protocolRequest = new sdk.remotemessage.TxStartRequestMessage();
this.verifyValidAmount(request.getAmount());
var payIntent = this.populateBasePayIntent(request);
protocolRequest.setPayIntent(payIntent);
this.lastRequest = request;
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Request a cancel be sent to the device.
* @return void
*/
CloverConnectorImpl.prototype.cancel = function() {
var protocolRequest = new sdk.remotemessage.KeyPressMessage();
protocolRequest.setKeyPress(sdk.remotemessage.KeyPress.ESC);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Request a preauth be captured.
* @param {remotepay.CapturePreAuthRequest} request
* @return void
*/
CloverConnectorImpl.prototype.capturePreAuth = function(request) {
if(!this.validateReadyRequest(
"In CapturePreAuth : CapturePreAuthRequest - ",
sdk.remotepay.CapturePreAuthResponse,
request,
this.delegateCloverConnectorListener.onCapturePreAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateAmount(
"In CapturePreAuth : CapturePreAuthRequest - ",
sdk.remotepay.CapturePreAuthResponse,
request,
this.delegateCloverConnectorListener.onCapturePreAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateSupportsPreAuth(
"In CapturePreAuth : CapturePreAuthRequest - ",
sdk.remotepay.CapturePreAuthResponse,
request,
this.delegateCloverConnectorListener.onCapturePreAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateTipAmount(
"In CapturePreAuth : CapturePreAuthRequest - ",
sdk.remotepay.CapturePreAuthResponse,
request,
this.delegateCloverConnectorListener.onCapturePreAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.CapturePreAuthMessage();
this.verifyValidAmount(request.getAmount());
protocolRequest.setAmount(request.getAmount());
protocolRequest.setTipAmount(request.getTipAmount());
protocolRequest.setPaymentId(request.getPaymentId());
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Request a closeout.
* @param {remotepay.CloseoutRequest} closeoutRequest
* @return void
*/
CloverConnectorImpl.prototype.closeout = function(closeoutRequest) {
if(!this.validateReadyRequest(
"In Closeout: CloseoutRequest - ",
sdk.remotepay.CloseoutResponse,
request,
this.delegateCloverConnectorListener.onCloseoutResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.CloseoutRequestMessage();
protocolRequest.setAllowOpenTabs(closeoutRequest.getAllowOpenTabs());
protocolRequest.setBatchId(closeoutRequest.getBatchId());
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Show the customer facing receipt option screen for the specified credit
* @param {string} orderId
* @param {string} creditId
* @return void
*/
/*
v2
showManualRefundReceiptOptions = function(orderId, creditId) {
// Waiting on changes in remote-pay.
throw new CloverError(CloverError.NOT_IMPLEMENTED);
};
*/
/**
* Show the customer facing receipt option screen for the specified payment
* @param {string} orderId
* @param {string} paymentId
* @return void
*/
CloverConnectorImpl.prototype.showPaymentReceiptOptions = function(orderId, paymentId) {
if(orderId == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In DisplayPaymentReceiptOptions: The orderId cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
if(paymentId == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In DisplayPaymentReceiptOptions: The paymentId cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.ShowPaymentReceiptOptionsMessage();
protocolRequest.setOrderId(orderId);
protocolRequest.setPaymentId(paymentId);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Show the customer facing receipt option screen for the specified payment refund
* @param {string} orderId
* @param {string} refundId
* @return void
*/
/**
* Display orderObj information on the screen. Calls to this method will cause the DisplayOrder
* to show on the clover device. If a DisplayOrder is already showing on the Clover device,
* it will replace the existing DisplayOrder on the device.
* @param {order.DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.showDisplayOrder = function(orderObj) {
if(orderObj == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In ShowDisplayOrder : DisplayOrder object cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.OrderUpdateMessage();
protocolRequest.setOrder(orderObj);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Removes the Display orderObj information on the screen.
* @param {DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.removeDisplayOrder = function(orderObj) {
this.showWelcomeScreen();
};
/**
* Notify device of a discount being added to the orderObj. The discount will then reflect in the displayOrder.
* Note: This is independent of a discount being added to a display line item.
* @param {order.DisplayDiscount} discountObj
* @param {order.DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.discountAddedToDisplayOrder = function(discountObj, orderObj) {
var protocolRequest = new sdk.remotemessage.OrderUpdateMessage();
protocolRequest.setOrder(orderObj);
var discountsAddedOperation = new sdk.order.operation.DiscountsAddedOperation();
discountsAddedOperation.setOrderId(orderObj.getId());
discountsAddedOperation.setIds([discountObj.getId()]);
protocolRequest.setDiscountsAddedOperation(discountsAddedOperation);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Notify device of a discount being removed to the orderObj. The discount will then reflect in the displayOrder.
* Note: This is independent of a discount being removed to a display line item.
* @param {order.DisplayDiscount} discount
* @param {order.DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.discountRemovedFromDisplayOrder = function(discount, orderObj) {
var protocolRequest = new sdk.remotemessage.OrderUpdateMessage();
protocolRequest.setOrder(orderObj);
var discountsDeletedOperation = new sdk.order.operation.DiscountsDeletedOperation();
discountsDeletedOperation.setOrderId(orderObj.getId());
discountsDeletedOperation.setIds([discount.getId()]);
protocolRequest.setDiscountsDeletedOperation(discountsDeletedOperation);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Notify device of a line item being added to the orderObj. The line item will then reflect in the displayOrder.
* Note: This is independent of a line item being added to a display line item.
* @param {order.DisplayLineItem} lineItem
* @param {order.DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.lineItemAddedToDisplayOrder = function(lineItem, orderObj) {
var protocolRequest = new sdk.remotemessage.OrderUpdateMessage();
protocolRequest.setOrder(orderObj);
var lineItemsAddedOperation = new sdk.order.operation.LineItemsAddedOperation();
lineItemsAddedOperation.setOrderId(orderObj.getId());
lineItemsAddedOperation.setIds([lineItem.getId()]);
protocolRequest.setLineItemsAddedOperation(lineItemsAddedOperation);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Notify device of a line item being removed to the orderObj. The line item will then reflect in the displayOrder.
* Note: This is independent of a line item being removed to a display line item.
* @param {order.DisplayLineItem} lineItem
* @param {order.DisplayOrder} orderObj
* @return void
*/
CloverConnectorImpl.prototype.lineItemRemovedFromDisplayOrder = function(lineItem, orderObj) {
var protocolRequest = new sdk.remotemessage.OrderUpdateMessage();
protocolRequest.setOrder(orderObj);
var lineItemsDeletedOperation = new sdk.order.operation.LineItemsDeletedOperation();
lineItemsDeletedOperation.setOrderId(orderObj.getId());
lineItemsDeletedOperation.setIds([lineItem.getId()]);
protocolRequest.setLineItemsDeletedOperation(lineItemsDeletedOperation);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* This can be used internally, but will remain hidden for now.
*
* When called, it will return a tuple with the last transactional request sent to the Clover Mini, and
* the corresponding response from the Mini if there was one (NULL if not). Works for sale, auth, manual refund
* and refund.
*
* @private
* @return void
*/
CloverConnectorImpl.prototype.getLastTransaction = function() {
var protocolRequest = new sdk.remotemessage.LastMessageRequestMessage();
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Destroy the connector. After this is called, the connection to the device is severed, and this object is
* no longer usable
* @return void
*/
CloverConnectorImpl.prototype.dispose = function() {
if (this.device) {
try {
this.device.reconnect = false;
this.cancel();
} catch (e) {
this.log.info(e);
}
try {
this.log.info("Calling disconnectFromDevice");
this.device.disconnectFromDevice();
} catch (e) {
this.log.info(e);
}
this.device = null;
}
};
/**
* @return void
*/
CloverConnectorImpl.prototype.reconnect = function() {
if (this.device) {
try {
this.device.attemptReconnect();
} catch (e) {
this.log.info(e);
}
}
};
/**
* Send a keystroke to the device. When in non secure displays are on the device, this can be used to
* act in the role of the user to 'press' available keys.
* @param {remotemessage.InputOption} io
* @return void
*/
CloverConnectorImpl.prototype.invokeInputOption = function(io) {
if(io == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In InvokeInputOption: The InputOption object cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.KeyPressMessage();
protocolRequest.setKeyPress(io.getKeyPress());
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Do a refund to a card.
* @param {remotepay.ManualRefundRequest} request
* @return void
*/
CloverConnectorImpl.prototype.manualRefund = function(request) {
if(!this.validateSaleAuthPreauthManref(
"In ManualRefund : ManualRefundRequest - ",
sdk.remotepay.SaleResponse,
request,
this.delegateCloverConnectorListener.onSaleResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateSupportsManualRefunds(
"In ManualRefund : ManualRefundRequest - ",
sdk.remotepay.SaleResponse,
request,
this.delegateCloverConnectorListener.onSaleResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.TxStartRequestMessage();
this.verifyValidAmount(request.getAmount());
var payIntent = this.populateBasePayIntent(request);
// Negate the amount
payIntent.setAmount(Math.abs(payIntent.getAmount()) * -1);
protocolRequest.setPayIntent(payIntent);
this.lastRequest = request;
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Do a refund on a previously made payment.
* @param {remotepay.RefundPaymentRequest} request
* @return void
*/
CloverConnectorImpl.prototype.refundPayment = function(request) {
if(! this.validateReadyRequest(
"In RefundPayment : RefundPaymentRequest - ",
sdk.remotepay.RefundPaymentResponse,
request,
this.delegateCloverConnectorListener.onRefundPaymentResponse.bind(this.delegateCloverConnectorListener) )) {
return;
}
if(! this.validatePaymentId(
"In RefundPayment : RefundPaymentRequest - ",
sdk.remotepay.RefundPaymentResponse,
request,
this.delegateCloverConnectorListener.onRefundPaymentResponse.bind(this.delegateCloverConnectorListener) )) {
return;
}
if(request.getAmount() <= 0 && !request.getFullRefund()) {
var response = new sdk.remotepay.RefundPaymentResponse();
this.populateRequestValidationFail(response);
response.setMessage("In RefundPayment : RefundPaymentRequest - " + "Amount must be greater than zero when FullRefund is set to false. " + request);
this.delegateCloverConnectorListener.onRefundPaymentResponse(response);
return;
}
var protocolRequest = new sdk.remotemessage.RefundRequestMessage();
// In the initial and unspecified version, an amount of '0' indicated
// a full refund. In the version 2 of the message, the flag 'fullRefund' was
// added.
protocolRequest.setVersion(2);
protocolRequest.setFullRefund(request.getFullRefund());
if(!request.getFullRefund()) {
this.verifyValidAmount(request.getAmount());
protocolRequest.setAmount(request.getAmount())
}
protocolRequest.setOrderId(request.getOrderId());
protocolRequest.setPaymentId(request.getPaymentId());
this.lastRequest = request;
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Open the first cash drawer that is found connected to the clover device.
* @param {string} reason
* @return void
*/
CloverConnectorImpl.prototype.openCashDrawer = function(reason) {
if(reason == null) {
var response = new sdk.remotepay.CloverDeviceErrorEvent();
response.setType(sdk.remotepay.ErrorType.VALIDATION);
response.setMessage("In OpenCashDrawer: The reason cannot be null.");
this.delegateCloverConnectorListener.onDeviceError(response);
return;
}
var protocolRequest = new sdk.remotemessage.OpenCashDrawerMessage();
protocolRequest.setReason(reason);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Print the passed image. bitmap is a language specific object that represents an image.
* @param {Img} bitmap - an HTML DOM IMG object.
*
* @return void
*/
CloverConnectorImpl.prototype.printImage = function(bitmap) {
if(!bitmap) {
var errorResponse1 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse1.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse1.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse1.setMessage("In PrintImage : Bitmap object cannot be null. ");
this.delegateCloverConnectorListener.onDeviceError(errorResponse1);
return;
}
var protocolRequest = new sdk.remotemessage.ImagePrintMessage();
protocolRequest.setPng(ImageUtil.getBase64Image(bitmap));
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Print an image on the clover device that is found at the passed url.
* @param {string} imgUrl
* @return void
*/
CloverConnectorImpl.prototype.printImageFromURL = function(imgUrl) {
if(!imgUrl) {
var errorResponse1 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse1.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse1.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse1.setMessage("In PrintImageFromURL : imgUrl cannot be null. ");
this.delegateCloverConnectorListener.onDeviceError(errorResponse1);
return;
}
var protocolRequest = new sdk.remotemessage.ImagePrintMessage();
protocolRequest.setUrlString(imgUrl);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Print text on the clover device printer.
* @param {Array.<String>} messages An array of
* @return void
*/
CloverConnectorImpl.prototype.printText = function(messages) {
if(!messages) {
var errorResponse1 = new sdk.remotepay.CloverDeviceErrorEvent();
errorResponse1.setCode(sdk.remotepay.DeviceErrorEventCode.InvalidConfig);
errorResponse1.setType(sdk.remotepay.ErrorType.VALIDATION);
errorResponse1.setMessage("In PrintText : message list cannot be null. ");
this.delegateCloverConnectorListener.onDeviceError(errorResponse1);
return;
}
var protocolRequest = new sdk.remotemessage.TextPrintMessage();
protocolRequest.setTextLines(messages);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Send a message to the device to reset back to the welcome screen. Can be used when the device is in
* an unknown state.
* @return void
*/
CloverConnectorImpl.prototype.resetDevice = function() {
var protocolRequest = new sdk.remotemessage.BreakMessage();
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Begin a sale transaction.
* @param {remotepay.SaleRequest} request
* @return void
*/
CloverConnectorImpl.prototype.sale = function(request) {
if(!this.validateSaleAuthPreauthManref(
"In Sale : SaleRequest - ",
sdk.remotepay.SaleResponse,
request,
this.delegateCloverConnectorListener.onSaleResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateTipAmount(
"In Sale : SaleRequest - ",
sdk.remotepay.SaleResponse,
request,
this.delegateCloverConnectorListener.onSaleResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.TxStartRequestMessage();
this.verifyValidAmount(request.getAmount());
var payIntent = this.populateBasePayIntent(request);
payIntent.setTippableAmount(request.getTippableAmount());
payIntent.setTipAmount(request.getTipAmount() == undefined ? 0 : request.getTipAmount());
payIntent.setTaxAmount(request.getTaxAmount());
payIntent.setIsDisableCashBack(request.getDisableCashback() === undefined
? this.configuration.disableCashback : request.getDisableCashback());
payIntent.setAllowOfflinePayment(request.getAllowOfflinePayment() === undefined
? this.configuration.allowOfflinePayment : request.getAllowOfflinePayment());
payIntent.setApproveOfflinePaymentWithoutPrompt(request.getApproveOfflinePaymentWithoutPrompt() === undefined
? this.configuration.approveOfflinePaymentWithoutPrompt : request.getApproveOfflinePaymentWithoutPrompt());
// For sale tip is defined. If no tip, then it is set to 0.
protocolRequest.setPayIntent(payIntent);
protocolRequest.setSuppressOnScreenTips(request.getDisableTipOnScreen());
this.lastRequest = request;
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Show a text message on the device.
* @param {string} message
* @return void
*/
CloverConnectorImpl.prototype.showMessage = function(message) {
var protocolRequest = new sdk.remotemessage.TerminalMessage();
protocolRequest.setText(message);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Show the thank you display on the device.
* @return void
*/
CloverConnectorImpl.prototype.showThankYouScreen = function() {
var protocolRequest = new sdk.remotemessage.ThankYouMessage();
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Show the welcome display on the device.
* @return void
*/
CloverConnectorImpl.prototype.showWelcomeScreen = function() {
var protocolRequest = new sdk.remotemessage.WelcomeMessage();
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Tip adjust an existing auth
* @param {remotepay.TipAdjustAuthRequest} request
* @return void
*/
CloverConnectorImpl.prototype.tipAdjustAuth = function(request) {
if(! this.validateReadyRequest(
"In TipAdjustAuth : TipAdjustAuthRequest - ",
sdk.remotepay.TipAdjustAuthResponse,
request,
this.delegateCloverConnectorListener.onTipAdjustAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(! this.validateTipAmount(
"In TipAdjustAuth : TipAdjustAuthRequest - ",
sdk.remotepay.TipAdjustAuthResponse,
request,
this.delegateCloverConnectorListener.onTipAdjustAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validateSupportsTipAdjust(
"In TipAdjustAuth : TipAdjustAuthRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validatePaymentId(
"In TipAdjustAuth : TipAdjustAuthRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.TipAdjustMessage();
this.verifyValidAmount(request.getTipAmount(), true);
protocolRequest.setTipAmount(request.getTipAmount());
protocolRequest.setOrderId(request.getOrderId());
protocolRequest.setPaymentId(request.getPaymentId());
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Vault a card using optional cardEntryMethods
* @param {Number} cardEntryMethods must be an integer
* @return void
*/
CloverConnectorImpl.prototype.vaultCard = function(cardEntryMethods) {
if (!this.merchantInfo.getSupportsVaultCards()) {
var response = new sdk.remotepay.VaultCardResponse();
this.populateMerchantConfigurationUnsupported(response);
response.setMessage("In VaultCard: - Vault card is not supported by the merchant configured gateway.");
this.delegateCloverConnectorListener.onVaultCardResponse(response);
return;
}
var protocolRequest = new sdk.remotemessage.VaultCardMessage();
cardEntryMethods =
cardEntryMethods===undefined ? this.configuration.cardEntryMethods :
CloverConnectorImpl.isInt(cardEntryMethods) ? cardEntryMethods :
this.configuration.cardEntryMethods;
protocolRequest.setCardEntryMethods(cardEntryMethods);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(protocolRequest));
};
/**
* Void a payment
* @param {remotepay.VoidPaymentRequest} request
* @return void
*/
CloverConnectorImpl.prototype.voidPayment = function(request) {
if(!this.validateReadyRequest(
"In VoidPayment : VoidPaymentRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if(!this.validatePaymentId(
"In VoidPayment : VoidPaymentRequest - ",
sdk.remotepay.AuthResponse,
request,
this.delegateCloverConnectorListener.onAuthResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
var protocolRequest = new sdk.remotemessage.VoidPaymentMessage();
var payment = new sdk.payments.Payment();
payment.setId(request.getPaymentId());
var orderReference = new sdk.base.Reference();
orderReference.setId(request.getOrderId());
payment.setOrder(orderReference);
var employeeReference = new sdk.base.Reference();
employeeReference.setId(request.getEmployeeId() == null
? this.configuration.defaultEmployeeId : request.getEmployeeId());
payment.setEmployee(employeeReference);
protocolRequest.setPayment(payment);
protocolRequest.setVoidReason(sdk.order.VoidReason[request.getVoidReason()]);
// Wait for an ACK... then call sendVoidPaymentResponse
var remoteMessage = this.messageBuilder.buildRemoteMessageObject(protocolRequest);
// This is a backwards compatibility hack.
if(this.deviceSupportsAckMessages) {
// If acknowledgements are supported, then
// wait for an ACK from the device for the message.
this.addAcknowledgementHook(remoteMessage.getId(), function () {
this.sendVoidPaymentResponse(payment)
}.bind(this));
} else {
//If not just send the response after 1 second.
setTimeout(function () {
this.sendVoidPaymentResponse(payment)
}.bind(this), 1000);
}
this.sendMessage(remoteMessage);
};
/**
* Returns information on the SDK. This is a string that is identified by the SDK type, a colon, and the
* version with any release candidate appended.
* @return {String}
*/
CloverConnectorImpl.prototype.SDKInfo = function() {
return CloverConnectorImpl.RemoteSourceSDK + ":" +
CLOVER_CLOUD_SDK_VERSION;
};
/**
* In some cases we want to get an acknowledgement of a message, then execute some functionality.
* This allows for that. The most obvious case of that is the 'voidPayment' call above.
*
* @private
* @param {string} id the id of the message to wait for
* @param {function} callback the function called when the acknowledgement is received. Note that
* if no acknowledgement is ever received for the passed id, the callback will never be removed.
*/
CloverConnectorImpl.prototype.addAcknowledgementHook = function(id, callback) {
if(!this.deviceSupportsAckMessages) {
this.log.warn("addAcknowledgementHook called, but device does not support ACK messages. " +
"Callback will never be called or removed from internal acknowledgementHooks store.");
}
this.acknowledgementHooks[id] = callback;
};
/**
* Add a listener that will be notified by this connector
* @param {ICloverConnectorListener} connectorListener
*/
CloverConnectorImpl.prototype.addCloverConnectorListener = function(connectorListener) {
this.cloverConnectorListeners.push(connectorListener);
};
/**
* Remove a listener that will be notified by this connector
* @param {ICloverConnectorListener} connectorListener
*/
CloverConnectorImpl.prototype.removeCloverConnectorListener = function(connectorListener) {
var indexOfListener = this.cloverConnectorListeners.indexOf(connectorListener);
if(indexOfListener !== -1) {
this.cloverConnectorListeners.splice(indexOfListener, 1);
}
};
/**
* Used internally
* @protected
* @returns {Array}
*/
CloverConnectorImpl.prototype.getListeners = function() {
return this.cloverConnectorListeners;
};
/**
* Used internally
* @protected
* @param {remotemessage.LogLevelEnum} logLevel - the logging level
* @param {Object} messages - a mappiing of string->string that is passed directly in the message
*/
CloverConnectorImpl.prototype.logMessageRemotely = function(logLevel, messages) {
var logMessage = new sdk.remotemessage.LogMessage();
logMessage.setLogLevel(logLevel);
logMessage.setMessages(messages);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(logMessage));
};
CloverConnectorImpl.prototype.retrievePendingPayments = function() {
var retrievePendingPaymentsMessage = new sdk.remotemessage.RetrievePendingPaymentsMessage();
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(retrievePendingPaymentsMessage));
};
/**
* Sends a request to read card information and call back with the information collected.
* @see ICloverConnectorListener.onReadCardDataResponse(ReadCardDataResponse)
* @memberof sdk.remotepay.ICloverConnector
*
* @param {remotepay.ReadCardDataRequest} request
* @return void
*/
CloverConnectorImpl.prototype.readCardData = function(request) {
if(!this.validateReadyRequest(
"In ReadCardData : ReadCardDataRequest - ",
sdk.remotepay.ReadCardDataResponse,
request,
this.delegateCloverConnectorListener.onReadCardDataResponse.bind(this.delegateCloverConnectorListener))) {
return;
}
if (request.getCardEntryMethods() === 0) {
var response = new sdk.remotepay.ReadCardDataResponse();
this.populateRequestValidationFail(response);
response.setMessage("In ReadCardData : ReadCardDataRequest - " + "The CardEntryMethods field cannot be '" + request.getCardEntryMethods() + "'");
this.delegateCloverConnectorListener.onReadCardDataResponse(response);
return;
}
var cardDataRequestMessage = new sdk.remotemessage.CardDataRequestMessage();
var payIntent = new sdk.remotemessage.PayIntent();
payIntent.setTransactionType(sdk.remotemessage.TransactionType.DATA);
payIntent.setIsForceSwipePinEntry(request.getIsForceSwipePinEntry());
payIntent.setCardEntryMethods(request.getCardEntryMethods());
cardDataRequestMessage.setPayIntent(payIntent);
this.sendMessage(this.messageBuilder.buildRemoteMessageObject(cardDataRequestMessage));
};
/**
* @private
* @param value
* @returns {boolean}
*/
CloverConnectorImpl.isInt = function(value) {
var x;
if (isNaN(value)) {
return false;
}
x = parseFloat(value);
return (x | 0) === x;
};
CloverConnectorImpl.WebSocketPackage = "com.clover.remote.protocol.websocket";
CloverConnectorImpl.NetworkPackage = "com.clover.remote.protocol.lan";
CloverConnectorImpl.RemoteSourceSDK = "com.clover.cloverconnector.cloud";
CloverConnectorImpl.KEY_Package = "code_package";
CloverConnectorImpl.KEY_FriendlyId = "friendlyId";
/**
* The shutdown method type
* This is a special type only present in the cloud adaptor.
*/
CloverConnectorImpl.SHUTDOWN = "SHUTDOWN";
/**
* The acknowledgement method type
* This is a special type only present in the cloud adaptor.
*/
CloverConnectorImpl.ACK = "ACK";
/**
* The acknowledgement method type
* This is a special type only present in the cloud adaptor.
*/
CloverConnectorImpl.ERROR = "ERROR";
//
// Expose the module.
//
//noinspection JSUnresolvedVariable
if ('undefined' !== typeof module) {
//noinspection JSUnresolvedVariable
module.exports = CloverConnectorImpl;
}
/**
* @typedef {Object} NotificationResponse the result of a notification request. Contains
* information on how to connect to a device
* @property {boolean} sent
* @property {string} host
* @property {string} token
*/