Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@assemblypayments/spi-client-js

Package Overview
Dependencies
Maintainers
8
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@assemblypayments/spi-client-js - npm Package Compare versions

Comparing version 2.0.4 to 2.1.0

src/Cashout.js

15

karma.conf.js

@@ -22,8 +22,14 @@ module.exports = function (config) {

'src/**/*.js',
'Pos.js',
// Include the unit tests
'tests/**/*.spec.js'
'tests/**/*.spec.js',
// Mock fixtures
'./tests/fixtures/**/*.json'
],
// plugins: [
// 'karma-json-fixtures-preprocessor'
// ],
// list of files to exclude

@@ -36,3 +42,8 @@ exclude: [

preprocessors: {
'./tests/fixtures/**/*.json': ['json_fixtures']
},
jsonFixturesPreprocessor: {
// strip this from the file path \ fixture name
stripPrefix: 'tests/fixtures/'
},

@@ -39,0 +50,0 @@

10

package.json
{
"name": "@assemblypayments/spi-client-js",
"version": "2.0.4",
"version": "2.1.0",
"description": "JavaScript Client Library for Assembly Payments' In-Store Integration.",

@@ -8,3 +8,4 @@ "main": "karma.conf.js",

"test": "karma start",
"transpile": "babel src --out-file ./dist/spi-client-js.js"
"transpile": "babel src --out-file ./dist/spi-client-js.js",
"build": "babel src --out-file ./dist/spi-client-js.js"
},

@@ -28,5 +29,6 @@ "repository": {

"jasmine": "^2.8.0",
"karma": "^1.7.1",
"karma": "^2.0.2",
"karma-chrome-launcher": "^2.2.0",
"karma-jasmine": "^1.1.1"
"karma-jasmine": "^1.1.1",
"karma-json-fixtures-preprocessor": "0.0.6"
},

@@ -33,0 +35,0 @@ "bugs": {

@@ -0,0 +0,0 @@ # SPI Client JavaScript

@@ -7,4 +7,18 @@ const ConnectionState = {

const SPI_PROTOCOL = 'spi.2.0.0';
const SPI_PROTOCOL = 'spi.2.1.0';
class ConnectionStateEventArgs
{
constructor(connectionState) {
this.ConnectionState = connectionState;
}
}
class MessageEventArgs
{
constructor(message) {
this.Message = message;
}
}
class Connection {

@@ -39,3 +53,3 @@ constructor() {

document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: this.State}));
document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: new ConnectionStateEventArgs(ConnectionState.Connecting)}));
}

@@ -67,3 +81,3 @@

this.Connected = true;
document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: this.State}));
document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: new ConnectionStateEventArgs(ConnectionState.Connected)}));
}

@@ -75,3 +89,3 @@

this._ws = null;
document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: this.State}));
document.dispatchEvent(new CustomEvent('ConnectionStatusChanged', {detail: new ConnectionStateEventArgs(ConnectionState.Disconnected)}));
}

@@ -94,8 +108,8 @@

onMessageReceived(message) {
document.dispatchEvent(new CustomEvent('MessageReceived', {detail: message}));
document.dispatchEvent(new CustomEvent('MessageReceived', {detail: new MessageEventArgs(message.data)}));
}
onError(err) {
document.dispatchEvent(new CustomEvent('ErrorReceived', {detail: err}));
document.dispatchEvent(new CustomEvent('ErrorReceived', {detail: new MessageEventArgs(err)}));
}
}

@@ -13,8 +13,8 @@ class Crypto {

/// <summary>
/// Encrypt a block using CBC and PKCS7.
/// </summary>
/// <param name="key">The key value</param>
/// <param name="data">The message to encrypt</param>
/// <returns>Returns the resulting encrypted string data as HEX.</returns>
// <summary>
// Encrypt a block using CBC and PKCS7.
// </summary>
// <param name="key">The key value</param>
// <param name="data">The message to encrypt</param>
// <returns>Returns the resulting encrypted string data as HEX.</returns>
static AesEncrypt (key, data) {

@@ -31,8 +31,8 @@ let bytes = aesjs.utils.hex.toBytes(key);

/// <summary>
/// Decrypt a block using a CBC and PKCS7.
/// </summary>
/// <param name="key">The key value</param>
/// <param name="data">the data to decrypt</param>
/// <returns>Returns the resulting data decrypted in plaintext.</returns>
// <summary>
// Decrypt a block using a CBC and PKCS7.
// </summary>
// <param name="key">The key value</param>
// <param name="data">the data to decrypt</param>
// <returns>Returns the resulting data decrypted in plaintext.</returns>
static AesDecrypt(key, data) {

@@ -49,8 +49,8 @@ let bytes = aesjs.utils.hex.toBytes(key);

/// <summary>
/// Calculates the HMACSHA256 signature of a message.
/// </summary>
/// <param name="key">The Hmac Key as HEX</param>
/// <param name="messageToSign">The message to sign</param>
/// <returns>The HMACSHA256 signature as a hex string</returns>
// <summary>
// Calculates the HMACSHA256 signature of a message.
// </summary>
// <param name="key">The Hmac Key as HEX</param>
// <param name="messageToSign">The message to sign</param>
// <returns>The HMACSHA256 signature as a hex string</returns>
static HmacSignature(key, messageToSign) {

@@ -57,0 +57,0 @@ let shaObj = new jsSHA("SHA-256", "TEXT");

@@ -5,10 +5,10 @@ // This creates the private and public keys for diffie-hellman (https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange#Cryptographic_explanation)

/// <summary>
/// This class implements the Diffie-Hellman algorithm using BigIntegers.
/// It can do the 3 main things:
/// 1. Generate a random Private Key for you.
/// 2. Generate your Public Key based on your Private Key.
/// 3. Generate the Secret given their Public Key and your Private Key
/// p and g are the shared constants for the algorithm, aka primeP and primeG.
/// </summary>
// <summary>
// This class implements the Diffie-Hellman algorithm using BigIntegers.
// It can do the 3 main things:
// 1. Generate a random Private Key for you.
// 2. Generate your Public Key based on your Private Key.
// 3. Generate the Secret given their Public Key and your Private Key
// p and g are the shared constants for the algorithm, aka primeP and primeG.
// </summary>
class DiffieHellman {

@@ -26,7 +26,7 @@

/// <summary>
/// Generates a random Private Key that you can use.
/// </summary>
/// <param name="p"></param>
/// <returns>Random Private Key</returns>
// <summary>
// Generates a random Private Key that you can use.
// </summary>
// <param name="p"></param>
// <returns>Random Private Key</returns>
RandomPrivateKey(maxValue) {

@@ -45,9 +45,9 @@ let maxValueBN = new BN(maxValue);

/// <summary>
/// Calculates the Public Key from a Private Key.
/// </summary>
/// <param name="p"></param>
/// <param name="g"></param>
/// <param name="privateKey"></param>
/// <returns>Public Key (Hex)</returns>
// <summary>
// Calculates the Public Key from a Private Key.
// </summary>
// <param name="p"></param>
// <param name="g"></param>
// <param name="privateKey"></param>
// <returns>Public Key (Hex)</returns>
PublicKey(p, g, privateKey) {

@@ -63,9 +63,9 @@ const aHex = new BN(privateKey, 16);

/// <summary>
/// Calculates the shared secret given their Public Key (A) and your Private Key (b)
/// </summary>
/// <param name="p"></param>
/// <param name="theirPublicKey"></param>
/// <param name="yourPrivateKey"></param>
/// <returns></returns>
// <summary>
// Calculates the shared secret given their Public Key (A) and your Private Key (b)
// </summary>
// <param name="p"></param>
// <param name="theirPublicKey"></param>
// <param name="yourPrivateKey"></param>
// <returns></returns>
Secret(p, theirPublicKey, yourPrivateKey) {

@@ -72,0 +72,0 @@ const bHex = new BN(theirPublicKey, 16);

@@ -0,0 +0,0 @@ class KeyRollingHelper {

@@ -0,0 +0,0 @@ class Logger {

@@ -1,4 +0,4 @@

/// <summary>
/// Events statically declares the various event names in messages.
/// </summary>
// <summary>
// Events statically declares the various event names in messages.
// </summary>
const Events = {

@@ -10,2 +10,3 @@ PairRequest : "pair_request",

PairResponse : "pair_response",
DropKeysAdvice : "drop_keys",

@@ -28,6 +29,16 @@ LoginRequest : "login_request",

SignatureAccepted : "signature_accept",
AuthCodeRequired : "authorisation_code_required",
AuthCodeAdvice : "authorisation_code_advice",
CashoutOnlyRequest : "cash",
CashoutOnlyResponse : "cash_response",
MotoPurchaseRequest : "moto_purchase",
MotoPurchaseResponse : "moto_purchase_response",
SettleRequest : "settle",
SettleResponse : "settle_response",
SettlementEnquiryRequest : "settlement_enquiry",
SettlementEnquiryResponse : "settlement_enquiry_response",
KeyRollRequest : "request_use_next_keys",

@@ -38,3 +49,10 @@ KeyRollResponse : "response_use_next_keys",

InvalidHmacSignature : "_INVALID_SIGNATURE_"
InvalidHmacSignature : "_INVALID_SIGNATURE_",
// Pay At Table Related Messages
PayAtTableGetTableConfig : "get_table_config", // incoming. When eftpos wants to ask us for P@T configuration.
PayAtTableSetTableConfig : "set_table_config", // outgoing. When we want to instruct eftpos with the P@T configuration.
PayAtTableGetBillDetails : "get_bill_details", // incoming. When eftpos wants to aretrieve the bill for a table.
PayAtTableBillDetails : "bill_details", // outgoing. We reply with this when eftpos requests to us get_bill_details.
PayAtTableBillPayment : "bill_payment" // incoming. When the eftpos advices
};

@@ -46,6 +64,6 @@

/// <summary>
/// MessageStamp represents what is required to turn an outgoing Message into Json
/// including encryption and date setting.
/// </summary>
// <summary>
// MessageStamp represents what is required to turn an outgoing Message into Json
// including encryption and date setting.
// </summary>
class MessageStamp {

@@ -59,30 +77,30 @@ constructor(posId, secrets, serverTimeDelta) {

/// <summary>
/// MessageEnvelope represents the outer structure of any message that is exchanged
/// between the Pos and the PinPad and vice-versa.
/// See http://www.simplepaymentapi.com/#/api/message-encryption
/// </summary>
// <summary>
// MessageEnvelope represents the outer structure of any message that is exchanged
// between the Pos and the PinPad and vice-versa.
// See http://www.simplepaymentapi.com/#/api/message-encryption
// </summary>
class MessageEnvelope {
constructor(message, enc, hmac, posId) {
/// <summary>
/// The Message field is set only when in Un-encrypted form.
/// In fact it is the only field in an envelope in the Un-Encrypted form.
/// </summary>
// <summary>
// The Message field is set only when in Un-encrypted form.
// In fact it is the only field in an envelope in the Un-Encrypted form.
// </summary>
this.Message = message;
/// <summary>
/// The enc field is set only when in Encrypted form.
/// It contains the encrypted Json of another MessageEnvelope
/// </summary>
// <summary>
// The enc field is set only when in Encrypted form.
// It contains the encrypted Json of another MessageEnvelope
// </summary>
this.Enc = enc;
/// <summary>
/// The hmac field is set only when in Encrypted form.
/// It is the signature of the "enc" field.
/// </summary>
// <summary>
// The hmac field is set only when in Encrypted form.
// It is the signature of the "enc" field.
// </summary>
this.Hmac = hmac;
/// <summary>
/// The pos_id field is only filled for outgoing Encrypted messages.
/// </summary>
// <summary>
// The pos_id field is only filled for outgoing Encrypted messages.
// </summary>
this.PosId = posId;

@@ -101,6 +119,6 @@ }

/// <summary>
/// Message represents the contents of a Message.
/// See http://www.simplepaymentapi.com/#/api/message-encryption
/// </summary>
// <summary>
// Message represents the contents of a Message.
// See http://www.simplepaymentapi.com/#/api/message-encryption
// </summary>
class Message {

@@ -127,5 +145,9 @@ constructor(id, eventName, data, needsEncryption) {

GetError() {
return this.Data.error_reason ? this.Data.error_reason : "NONE";
return this.Data.error_reason ? this.Data.error_reason : "";
}
GetErrorDetail() {
return this.Data.error_detail;
}
GetServerTimeDelta()

@@ -147,2 +169,14 @@ {

// Helper method to parse bank date format 20042018 (ddMMyyyy)
static ParseBankDate(bankDate) {
if(bankDate.length !== 8) return null;
return new Date(`${bankDate.substr(4,4)}-${bankDate.substr(2,2)}-${bankDate.substr(0,2)}`);
}
// Parses a bank date & time str from "05Oct17" / "05:00" ("ddMMMyy/HH:mm") into date obj
static ParseBankDateTimeStr(date, time) {
return new Date(`${date.substr(0,2)} ${date.substr(2,3)} ${date.substr(5,2)} ${time}`);
}
static FromJson(msgJson, secrets) {

@@ -157,2 +191,9 @@ let env = JSON.parse(msgJson);

if (secrets == null)
{
// This may happen if we somehow received an encrypted message from eftpos but we're not configered with secrets.
// For example, if we cancel the pairing process a little late in the game and we get an encrypted key_check message after we've dropped the keys.
return new Message("UNKNOWN", "NOSECRETS", null, false);
}
// Its encrypted, verify sig

@@ -179,3 +220,3 @@ let sig = Crypto.HmacSignature(secrets.HmacKey, env.enc);

} catch(e) {
return new Message("Unknown", "unparseable", {"msg": decryptedJson}, false);
return new Message("UNKNOWN", "UNPARSEABLE", {"msg": decryptedJson}, false);
}

@@ -182,0 +223,0 @@ }

@@ -1,4 +0,4 @@

/// <summary>
/// Pairing Interaction 1: Outgoing
/// </summary>
// <summary>
// Pairing Interaction 1: Outgoing
// </summary>
class PairRequest {

@@ -11,3 +11,3 @@ ToMessage() {

/// Pairing Interaction 2: Incoming
// Pairing Interaction 2: Incoming
class KeyRequest {

@@ -21,3 +21,3 @@ constructor(m) {

/// Pairing Interaction 3: Outgoing
// Pairing Interaction 3: Outgoing
class KeyResponse {

@@ -44,3 +44,3 @@ constructor(requestId, Benc, Bhmac) {

/// Pairing Interaction 4: Incoming
// Pairing Interaction 4: Incoming
class KeyCheck {

@@ -52,3 +52,3 @@ constructor(m) {

/// Pairing Interaction 5: Incoming
// Pairing Interaction 5: Incoming
class PairResponse {

@@ -60,3 +60,3 @@ constructor(m) {

/// Holder class for Secrets and KeyResponse, so that we can use them together in method signatures.
// Holder class for Secrets and KeyResponse, so that we can use them together in method signatures.
class SecretsAndKeyResponse {

@@ -68,1 +68,9 @@ constructor(secrets, keyResponse) {

}
class DropKeysRequest
{
ToMessage()
{
return new Message(RequestIdHelper.Id("drpkys"), Events.DropKeysAdvice, null, true);
}
}

@@ -7,11 +7,11 @@ // This is the generator used for diffie-hellman in 2048-bit MODP Group 14 as per (https://tools.ietf.org/html/rfc3526#section-3)

/// <summary>
/// This static class helps you with the pairing process as documented here:
/// http://www.simplepaymentapi.com/#/api/pairing-process
/// </summary>
// <summary>
// This static class helps you with the pairing process as documented here:
// http://www.simplepaymentapi.com/#/api/pairing-process
// </summary>
class PairingHelper {
/// <summary>
/// Generates a pairing Request.
/// </summary>
/// <returns>New PairRequest</returns>
// <summary>
// Generates a pairing Request.
// </summary>
// <returns>New PairRequest</returns>
static NewPairRequest() {

@@ -21,7 +21,7 @@ return new PairRequest();

/// <summary>
/// Calculates/Generates Secrets and KeyResponse given an incoming KeyRequest.
/// </summary>
/// <param name="keyRequest"></param>
/// <returns>Secrets and KeyResponse to send back.</returns>
// <summary>
// Calculates/Generates Secrets and KeyResponse given an incoming KeyRequest.
// </summary>
// <param name="keyRequest"></param>
// <returns>Secrets and KeyResponse to send back.</returns>
GenerateSecretsAndKeyResponse(keyRequest) {

@@ -42,8 +42,8 @@ let encPubAndSec = this._calculateMyPublicKeyAndSecret(keyRequest.Aenc);

/// <summary>
/// Turns an incoming "A" value from the PinPad into the outgoing "B" value
/// and the secret value using DiffieHelmman helper.
/// </summary>
/// <param name="theirPublicKey">The incoming A value</param>
/// <returns>Your B value and the Secret</returns>
// <summary>
// Turns an incoming "A" value from the PinPad into the outgoing "B" value
// and the secret value using DiffieHelmman helper.
// </summary>
// <param name="theirPublicKey">The incoming A value</param>
// <returns>Your B value and the Secret</returns>
_calculateMyPublicKeyAndSecret(theirPublicKey) {

@@ -61,9 +61,9 @@

/// <summary>
/// Converts the DH secret BigInteger into the hex-string to be used as the secret.
/// There are some "gotchyas" here which is why this piece of work is abstracted so it can be tested separately.
/// See: http://www.simplepaymentapi.com/#/api/pairing-process
/// </summary>
/// <param name="secretBI">Secret as BigInteger</param>
/// <returns>Secret as Hex-String</returns>
// <summary>
// Converts the DH secret BigInteger into the hex-string to be used as the secret.
// There are some "gotchyas" here which is why this piece of work is abstracted so it can be tested separately.
// See: http://www.simplepaymentapi.com/#/api/pairing-process
// </summary>
// <param name="secretBI">Secret as BigInteger</param>
// <returns>Secret as Hex-String</returns>
DHSecretToSPISecret(secret)

@@ -83,5 +83,5 @@ {

/// <summary>
/// Internal Holder class for Public and Secret, so that we can use them together in method signatures.
/// </summary>
// <summary>
// Internal Holder class for Public and Secret, so that we can use them together in method signatures.
// </summary>
class PublicKeyAndSecret {

@@ -88,0 +88,0 @@ constructor(myPublicKey, sharedSecretKey) {

@@ -0,0 +0,0 @@ class PongHelper

@@ -0,0 +0,0 @@ /**

class PurchaseRequest {
constructor(amountCents, id) {
constructor(amountCents, posRefId) {
this.PosRefId = posRefId;
this.PurchaseAmount = amountCents;
this.TipAmount = 0;
this.CashoutAmount = 0;
this.PromptForCashout = false;
this.Config = new SpiConfig();
// Library Backwards Compatibility
this.Id = posRefId;
this.AmountCents = amountCents;
this.Id = id;
}
AmountSummary()
{
return `Purchase: ${(this.PurchaseAmount / 100.0).toFixed(2)};
Tip: ${(this.TipAmount / 100.0).toFixed(2)};
Cashout: ${(this.CashoutAmount / 100.0).toFixed(2)};`;
}
ToMessage() {
let data = {
amount_purchase: this.AmountCents
pos_ref_id: this.PosRefId,
purchase_amount: this.PurchaseAmount,
tip_amount: this.TipAmount,
cash_amount: this.CashoutAmount,
prompt_for_cashout: this.PromptForCashout
};
return new Message(this.Id, Events.PurchaseRequest, data, true);
this.Config.addReceiptConfig(data);
return new Message(RequestIdHelper.Id("prchs"), Events.PurchaseRequest, data, true);
}

@@ -20,5 +40,7 @@ }

{
this._m = m;
this.RequestId = m.Id;
this._m = m;
this.PosRefId = m.Data.pos_ref_id;
this.SchemeName = m.Data.scheme_name;
this.SchemeAppName = m.Data.scheme_name;
this.Success = m.GetSuccessState() == SuccessState.Success;

@@ -32,2 +54,27 @@ }

GetPurchaseAmount()
{
return this._m.Data.purchase_amount;
}
GetTipAmount()
{
return this._m.Data.tip_amount;
}
GetCashoutAmount()
{
return this._m.Data.cash_amount;
}
GetBankNonCashAmount()
{
return this._m.Data.bank_noncash_amount;
}
GetBankCashAmount()
{
return this._m.Data.bank_cash_amount;
}
GetCustomerReceipt()

@@ -48,2 +95,65 @@ {

GetResponseCode()
{
return this._m.Data.host_response_code;
}
GetTerminalReferenceId()
{
return this._m.Data.terminal_ref_id;
}
GetCardEntry()
{
return this._m.Data.card_entry;
}
GetAccountType()
{
return this._m.Data.account_type;
}
GetAuthCode()
{
return this._m.Data.auth_code;
}
GetBankDate()
{
return this._m.Data.bank_date;
}
GetBankTime()
{
return this._m.Data.bank_time;
}
GetMaskedPan()
{
return this._m.Data.masked_pan;
}
GetTerminalId()
{
return this._m.Data.terminal_id;
}
WasMerchantReceiptPrinted()
{
return this._m.Data.merchant_receipt_printed;
}
WasCustomerReceiptPrinted()
{
return this._m.Data.customer_receipt_printed;
}
GetSettlementDate()
{
//"bank_settlement_date":"20042018"
var dateStr = this._m.Data.bank_settlement_date;
if (!dateStr) return null;
return Message.ParseBankDate(dateStr);
}
GetResponseValue(attribute)

@@ -53,2 +163,21 @@ {

}
ToPaymentSummary()
{
return {
account_type: this.GetAccountType(),
auth_code: this.GetAuthCode(),
bank_date: this.GetBankDate(),
bank_time: this.GetBankTime(),
host_response_code: this.GetResponseCode(),
host_response_text: this.GetResponseText(),
masked_pan: this.GetMaskedPan(),
purchase_amount: this.GetPurchaseAmount(),
rrn: this.GetRRN(),
scheme_name: this.SchemeName,
terminal_id: this.GetTerminalId(),
terminal_ref_id: this.GetTerminalReferenceId(),
tip_amount: this.GetTipAmount()
};
}
}

@@ -67,5 +196,2 @@

{
constructor(id) {
this.Id = id;
}
ToMessage()

@@ -82,3 +208,2 @@ {

this._m = m;
this.Success = (m.GetSuccessState() === SuccessState.Success);
}

@@ -90,4 +215,5 @@

// as retrieval may be successful, but the retrieved transaction was a fail.
// So we check if we got back an RRN.
return !!this.GetRRN();
// So we check if we got back an ResponseCode.
// (as opposed to say an operation_in_progress_error)
return !!this.GetResponseCode();
}

@@ -97,17 +223,25 @@

{
return this._m.GetError() == "OPERATION_IN_PROGRESS";
return this._m.GetError().startsWith("OPERATION_IN_PROGRESS");
}
GetSuccessState()
IsWaitingForSignatureResponse()
{
return this._m.GetSuccessState();
return this._m.GetError().startsWith("OPERATION_IN_PROGRESS_AWAITING_SIGNATURE");
}
GetResponseText()
IsWaitingForAuthCode()
{
return this._m.Data.host_response_text | "";
return this._m.GetError().startsWith("OPERATION_IN_PROGRESS_AWAITING_PHONE_AUTH_CODE");
}
GetMerchantReceipt()
IsStillInProgress(posRefId)
{
return this._m.Data.merchant_receipt || "";
return this.WasOperationInProgressError() && this.GetPosRefId() == posRefId;
}
GetSuccessState()
{
return this._m.GetSuccessState();
}
WasSuccessfulTx()

@@ -123,2 +257,7 @@ {

GetPosRefId()
{
return this._m.Data.pos_ref_id;
}
GetSchemeApp()

@@ -128,3 +267,8 @@ {

}
GetSchemeName()
{
return this._m.Data.scheme_name;
}
GetAmount()

@@ -151,14 +295,19 @@ {

GetResponseValue(attribute)
GetResponseText()
{
return this._m.Data[attribute];
}
return this._m.Data.host_response_text | "";
}
/// <summary>
/// There is a bug, VSV-920, whereby the customer_receipt is missing from a glt response.
/// The current recommendation is to use the merchant receipt in place of it if required.
/// This method modifies the underlying incoming message data by copying
/// the merchant receipt into the customer receipt only if there
/// is a merchant_receipt and there is not a customer_receipt.
/// </summary>
GetResponseCode()
{
return this._m.Data.host_response_code;
}
// <summary>
// There is a bug, VSV-920, whereby the customer_receipt is missing from a glt response.
// The current recommendation is to use the merchant receipt in place of it if required.
// This method modifies the underlying incoming message data by copying
// the merchant receipt into the customer receipt only if there
// is a merchant_receipt and there is not a customer_receipt.
// </summary>
CopyMerchantReceiptToCustomerReceipt()

@@ -177,6 +326,8 @@ {

{
constructor(amountCents, id)
constructor(amountCents, posRefId)
{
this.AmountCents = amountCents;
this.Id = id;
this.Id = RequestIdHelper.Id("refund");
this.PosRefId = posRefId;
this.Config = new SpiConfig();
}

@@ -186,6 +337,6 @@

{
let data = {amount_purchase: this.AmountCents};
return new Message(this.Id, Events.RefundRequest, data, true);
let data = {refund_amount: this.AmountCents, pos_ref_id: this.PosRefId};
this.Config.addReceiptConfig(data);
return new Message(RequestIdHelper.Id("refund"), Events.RefundRequest, data, true);
}
}

@@ -197,8 +348,15 @@

{
this._m = m;
this.RequestId = m.Id;
this._m = m;
this.PosRefId = m.Data.pos_ref_id;
this.SchemeName = m.Data.scheme_name;
this.SchemeAppName = m.Data.scheme_name;
this.Success = m.GetSuccessState() == SuccessState.Success;
}
GetRefundAmount()
{
return this._m.Data.refund_amount;
}
GetRRN()

@@ -218,3 +376,3 @@ {

}
GetResponseText()

@@ -224,3 +382,57 @@ {

}
GetResponseCode()
{
return this._m.Data.host_response_code || "";
}
GetTerminalReferenceId()
{
return this._m.Data.terminal_ref_id || "";
}
GetCardEntry()
{
return this._m.Data.card_entry || "";
}
GetAccountType()
{
return this._m.Data.account_type || "";
}
GetAuthCode()
{
return this._m.Data.auth_code || "";
}
GetBankDate()
{
return this._m.Data.bank_date || "";
}
GetBankTime()
{
return this._m.Data.bank_time || "";
}
GetMaskedPan()
{
return this._m.Data.masked_pan || "";
}
GetTerminalId()
{
return this._m.Data.terminal_id || "";
}
WasMerchantReceiptPrinted()
{
return this._m.Data.merchant_receipt_printed;
}
WasCustomerReceiptPrinted()
{
return this._m.Data.customer_receipt_printed;
}
GetSettlementDate()
{
//"bank_settlement_date":"20042018"
var dateStr = this._m.Data.bank_settlement_date;
if (!dateStr) return null;
return Message.ParseBankDate(dateStr);
}
GetResponseValue(attribute)

@@ -237,8 +449,16 @@ {

this.RequestId = m.Id;
this._m = m;
this.PosRefId = m.Data.pos_ref_id;
this._receiptToSign = m.Data.merchant_receipt;
}
SignatureRequired(posRefId, requestId, receiptToSign)
{
this.RequestId = requestId;
this.PosRefId = posRefId;
this._receiptToSign = receiptToSign;
}
GetMerchantReceipt()
{
return this._m.Data.merchant_receipt;
return this._receiptToSign;
}

@@ -249,5 +469,5 @@ }

{
constructor(signatureRequiredRequestId)
constructor(posRefId)
{
this.SignatureRequiredRequestId = signatureRequiredRequestId;
this.PosRefId = posRefId;
}

@@ -257,3 +477,6 @@

{
return new Message(this.SignatureRequiredRequestId, Events.SignatureDeclined, null, true);
var data = {
pos_ref_id: this.PosRefId
};
return new Message(RequestIdHelper.Id("sigdec"), Events.SignatureDeclined, data, true);
}

@@ -264,5 +487,5 @@ }

{
constructor(signatureRequiredRequestId)
constructor(posRefId)
{
this.SignatureRequiredRequestId = signatureRequiredRequestId;
this.PosRefId = posRefId;
}

@@ -272,4 +495,84 @@

{
return new Message(this.SignatureRequiredRequestId, Events.SignatureAccepted, null, true);
var data = {
pos_ref_id: this.PosRefId
};
return new Message(RequestIdHelper.Id("sigacc"), Events.SignatureAccepted, data, true);
}
}
class MotoPurchaseRequest
{
constructor(amountCents, posRefId)
{
this.PosRefId = posRefId;
this.PurchaseAmount = amountCents;
this.Config = new SpiConfig();
}
ToMessage()
{
var data = {
pos_ref_id: this.PosRefId,
purchase_amount: this.PurchaseAmount
};
this.Config.addReceiptConfig(data);
return new Message(RequestIdHelper.Id("moto"), Events.MotoPurchaseRequest, data, true);
}
}
class MotoPurchaseResponse
{
constructor(m)
{
this.PurchaseResponse = new PurchaseResponse(m);
this.PosRefId = PurchaseResponse.PosRefId;
}
}
class PhoneForAuthRequired
{
constructor(...args)
{
if(args.length === 4) {
this.PosRefId = args[0];
this.RequestId = args[1];
this._phoneNumber = args[2];
this._merchantId = args[3];
} else if(args.length === 1) {
this.RequestId = args[0].Id;
this.PosRefId = args[0].Data.pos_ref_id;
this._phoneNumber = args[0].Data.auth_centre_phone_number;
this._merchantId = args[0].Data.merchant_id;
} else {
throw new Error('Invalid call sig for Phone auth required class');
}
}
GetPhoneNumber()
{
return this._phoneNumber;
}
GetMerchantId()
{
return this._merchantId;
}
}
class AuthCodeAdvice
{
constructor(posRefId, authCode)
{
this.PosRefId = posRefId;
this.AuthCode = authCode;
}
ToMessage()
{
var data = {
pos_ref_id: this.PosRefId,
auth_code: this.AuthCode
};
return new Message(RequestIdHelper.Id("authad"), Events.AuthCodeAdvice, data, true);
}
}

@@ -8,2 +8,14 @@ class PurchaseHelper

static CreatePurchaseRequestV2(posRefId, purchaseAmount, tipAmount, cashoutAmount, promptForCashout)
{
var pr = Object.assign(new PurchaseRequest(purchaseAmount, posRefId),
{
CashoutAmount: cashoutAmount,
TipAmount: tipAmount,
PromptForCashout: promptForCashout
});
return pr;
}
static CreateRefundRequest(amountCents, purchaseId)

@@ -10,0 +22,0 @@ {

@@ -0,0 +0,0 @@ let __RequestIdHelperCounter = 1;

@@ -0,0 +0,0 @@ class Secrets {

@@ -7,3 +7,3 @@ class SettleRequest {

ToMessage() {
return new Message(RequestIdHelper.Id("stl"), Events.SettleRequest, null, true);
return new Message(this.Id, Events.SettleRequest, null, true);
}

@@ -19,2 +19,43 @@ }

GetSettleByAcquirerCount()
{
return this._m.Data.accumulated_settle_by_acquirer_count;
}
GetSettleByAcquirerValue()
{
return this._m.Data.accumulated_settle_by_acquirer_value;
}
GetTotalCount()
{
return this._m.Data.accumulated_total_count;
}
GetTotalValue()
{
return this._m.Data.accumulated_total_value;
}
GetPeriodStartTime()
{
var timeStr = this._m.Data.settlement_period_start_time; // "05:00"
var dateStr = this._m.Data.settlement_period_start_date; // "05Oct17"
return Message.ParseBankDateTimeStr(dateStr, timeStr);
}
GetPeriodEndTime()
{
var timeStr = this._m.Data.settlement_period_end_time; // "05:00"
var dateStr = this._m.Data.settlement_period_end_date; // "05Oct17"
return Message.ParseBankDateTimeStr(dateStr, timeStr);
}
GetTriggeredTime()
{
var timeStr = this._m.Data.settlement_triggered_time; // "05:00:45"
var dateStr = this._m.Data.settlement_triggered_date; // "05Oct17"
return Message.ParseBankDateTimeStr(dateStr, timeStr);
}
GetResponseText()

@@ -29,2 +70,60 @@ {

}
GetTransactionRange()
{
return this._m.Data.transaction_range;
}
GetTerminalId()
{
return this._m.Data.terminal_id;
}
GetSchemeSettlementEntries()
{
var schemes = this._m.Data.schemes;
if (!schemes) return [];
return schemes.map((scheme) => {
return new SchemeSettlementEntry(scheme);
});
}
}
class SchemeSettlementEntry
{
// SchemeSettlementEntry(string schemeName, bool settleByAcquirer, int totalCount, int totalValue)
// SchemeSettlementEntry(Object schemeObj)
constructor(...args)
{
if(args.length === 1) {
this.SchemeName = args[0].scheme_name;
this.SettleByAcquirer = args[0].settle_by_acquirer.toLowerCase() == "yes";
this.TotalValue = parseInt(args[0].total_value,10);
this.TotalCount = parseInt(args[0].total_count,10);
} else if(args.length === 4) {
this.SchemeName = args[0];
this.SettleByAcquirer = args[1];
this.TotalCount = args[2];
this.TotalValue = args[3];
}
}
ToString()
{
return `SchemeName: ${this.SchemeName}, SettleByAcquirer: ${this.SettleByAcquirer}, TotalCount: ${this.TotalCount}, TotalValue: ${this.TotalValue}`;
}
}
class SettlementEnquiryRequest
{
constructor(id)
{
this.Id = id;
}
ToMessage()
{
return new Message(this.Id, Events.SettlementEnquiryRequest, null, true);
}
}

@@ -16,3 +16,3 @@ class Spi {

constructor(posId, eftposAddress, secrets, log)
constructor(posId, eftposAddress, secrets)
{

@@ -23,2 +23,3 @@ this._posId = posId;

this._log = console;
this.Config = new SpiConfig();

@@ -48,9 +49,18 @@ // Our stamp for signing outgoing messages

this.CurrentTxFlowState = null;
}
this._resetConn();
EnablePayAtTable()
{
this._spiPat = new SpiPayAtTable(this);
return this._spiPat;
}
EnablePreauth()
{
this._spiPreauth = new SpiPreauth(this);
return this._spiPreauth;
}
Start() {
this._resetConn();
this._startTransactionMonitoringThread();

@@ -61,2 +71,3 @@

{
this._log.info("Starting in Paired State");
this._currentStatus = SpiStatus.PairedConnecting;

@@ -67,2 +78,3 @@ this._conn.Connect(); // This is non-blocking

{
this._log.info("Starting in Unpaired State");
this._currentStatus = SpiStatus.Unpaired;

@@ -72,6 +84,6 @@ }

/// <summary>
/// Allows you to set the PosId which identifies this instance of your POS.
/// Can only be called in thge Unpaired state.
/// </summary>
// <summary>
// Allows you to set the PosId which identifies this instance of your POS.
// Can only be called in thge Unpaired state.
// </summary>
SetPosId(posId)

@@ -87,7 +99,7 @@ {

/// <summary>
/// Allows you to set the PinPad address. Sometimes the PinPad might change IP address
/// (we recommend reserving static IPs if possible).
/// Either way you need to allow your User to enter the IP address of the PinPad.
/// </summary>
// <summary>
// Allows you to set the PinPad address. Sometimes the PinPad might change IP address
// (we recommend reserving static IPs if possible).
// Either way you need to allow your User to enter the IP address of the PinPad.
// </summary>
SetEftposAddress(address)

@@ -104,9 +116,9 @@ {

/// <summary>
/// Call this one when a flow is finished and you want to go back to idle state.
/// Typically when your user clicks the "OK" bubtton to acknowldge that pairing is
/// finished, or that transaction is finished.
/// When true, you can dismiss the flow screen and show back the idle screen.
/// </summary>
/// <returns>true means we have moved back to the Idle state. false means current flow was not finished yet.</returns>
// <summary>
// Call this one when a flow is finished and you want to go back to idle state.
// Typically when your user clicks the "OK" bubtton to acknowldge that pairing is
// finished, or that transaction is finished.
// When true, you can dismiss the flow screen and show back the idle screen.
// </summary>
// <returns>true means we have moved back to the Idle state. false means current flow was not finished yet.</returns>
AckFlowEndedAndBackToIdle()

@@ -132,13 +144,27 @@ {

/// <summary>
/// This will connect to the Eftpos and start the pairing process.
/// Only call this if you are in the Unpaired state.
/// Subscribe to the PairingFlowStateChanged event to get updates on the pairing process.
/// </summary>
static GetVersion()
{
return '2.1.0';
}
// endregion
// <summary>
// This will connect to the Eftpos and start the pairing process.
// Only call this if you are in the Unpaired state.
// Subscribe to the PairingFlowStateChanged event to get updates on the pairing process.
// </summary>
// <returns>Whether pairing has initiated or not</returns>
Pair()
{
if (this.CurrentStatus != SpiStatus.Unpaired) {
return;
this._log.warn("Tried to Pair but we're already so.");
return false;
}
if (!this._posId || !this._eftposAddress)
{
this._log.warn("Tried to Pair but missing posId or eftposAddress");
return false;
}
this.CurrentFlow = SpiFlow.Pairing;

@@ -157,8 +183,9 @@ this.CurrentPairingFlowState = new PairingFlowState

this._conn.Connect(); // Non-Blocking
return true;
}
/// <summary>
/// Call this when your user clicks yes to confirm the pairing code on your
/// screen matches the one on the Eftpos.
/// </summary>
// <summary>
// Call this when your user clicks yes to confirm the pairing code on your
// screen matches the one on the Eftpos.
// </summary>
PairingConfirmCode()

@@ -176,4 +203,6 @@ {

// But we are still waiting for confirmation from Eftpos side.
this._log.info("Pair Code Confirmed from POS side, but am still waiting for confirmation from Eftpos.");
this.CurrentPairingFlowState.Message =
"Click YES on EFTPOS if code is: " + this.CurrentPairingFlowState.ConfirmationCode;
document.dispatchEvent(new CustomEvent('PairingFlowStateChanged', {detail: this.CurrentPairingFlowState}));
}

@@ -183,12 +212,11 @@ else

// Already confirmed from Eftpos - So all good now. We're Paired also from the POS perspective.
this.CurrentPairingFlowState.Message = "Pairing Successful";
this._log.info("Pair Code Confirmed from POS side, and was already confirmed from Eftpos side. Pairing finalised.");
this._onPairingSuccess();
this._onReadyToTransact();
}
document.dispatchEvent(new CustomEvent('PairingFlowStateChanged', {detail: this.CurrentPairingFlowState}));
}
/// <summary>
/// Call this if your user clicks CANCEL or NO during the pairing process.
/// </summary>
// <summary>
// Call this if your user clicks CANCEL or NO during the pairing process.
// </summary>
PairingCancel()

@@ -200,14 +228,17 @@ {

this.CurrentPairingFlowState.Message = "Pairing Canelled";
if (this.CurrentPairingFlowState.AwaitingCheckFromPos && !this.CurrentPairingFlowState.AwaitingCheckFromEftpos)
{
// This means that the Eftpos already thinks it's paired.
// Let's tell it to drop keys
this._send(new DropKeysRequest().ToMessage());
}
this._onPairingFailed();
document.dispatchEvent(new CustomEvent('PairingFlowStateChanged', {detail: this.CurrentPairingFlowState}));
}
/// <summary>
/// Call this when your uses clicks the Unpair button.
/// This will disconnect from the Eftpos and forget the secrets.
/// The CurrentState is then changed to Unpaired.
/// Call this only if you are not yet in the Unpaired state.
/// </summary>
// <summary>
// Call this when your uses clicks the Unpair button.
// This will disconnect from the Eftpos and forget the secrets.
// The CurrentState is then changed to Unpaired.
// Call this only if you are not yet in the Unpaired state.
// </summary>
Unpair()

@@ -223,18 +254,19 @@ {

this.CurrentStatus = SpiStatus.Unpaired;
this._conn.Disconnect();
this._secrets = null;
this._spiMessageStamp.Secrets = null;
document.dispatchEvent(new CustomEvent('SecretsChanged', {detail: this._secrets}));
// Best effort letting the eftpos know that we're dropping the keys, so it can drop them as well.
this._send(new DropKeysRequest().ToMessage());
this._doUnpair();
return true;
}
/// <summary>
/// Initiates a purchase transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
/// </summary>
/// <param name="id">Alphanumeric Identifier for your purchase.</param>
/// <param name="amountCents">Amount in Cents to charge</param>
/// <returns>InitiateTxResult</returns>
InitiatePurchaseTx(id, amountCents)
// endregion
// region Transaction Methods
// <summary>
// Initiates a purchase transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
// </summary>
// <param name="posRefId">Alphanumeric Identifier for your purchase.</param>
// <param name="amountCents">Amount in Cents to charge</param>
// <returns>InitiateTxResult</returns>
InitiatePurchaseTx(posRefId, amountCents)
{

@@ -249,8 +281,10 @@ if (this.CurrentStatus == SpiStatus.Unpaired) {

var purchase = PurchaseHelper.CreatePurchaseRequest(amountCents, id);
var purchaseRequest = PurchaseHelper.CreatePurchaseRequest(amountCents, posRefId);
purchaseRequest.Config = this.Config;
var purchaseMsg = purchaseRequest.ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
id, TransactionType.Purchase, amountCents, purchase.ToMessage(),
posRefId, TransactionType.Purchase, amountCents, purchaseMsg,
`Waiting for EFTPOS connection to make payment request for ${amountCents / 100.0}`);
if (this._send(purchase.ToMessage()))
if (this._send(purchaseMsg))
{

@@ -264,10 +298,44 @@ this.CurrentTxFlowState.Sent(`Asked EFTPOS to accept payment for ${amountCents / 100.0}`);

/// <summary>
/// Initiates a refund transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
/// </summary>
/// <param name="id">Alphanumeric Identifier for your refund.</param>
/// <param name="amountCents">Amount in Cents to charge</param>
/// <returns>InitiateTxResult</returns>
InitiateRefundTx(id, amountCents)
// <summary>
// Initiates a purchase transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
// <para>Tip and cashout are not allowed simultaneously.</para>
// </summary>
// <param name="posRefId">An Unique Identifier for your Order/Purchase</param>
// <param name="purchaseAmount">The Purchase Amount in Cents.</param>
// <param name="tipAmount">The Tip Amount in Cents</param>
// <param name="cashoutAmount">The Cashout Amount in Cents</param>
// <param name="promptForCashout">Whether to prompt your customer for cashout on the Eftpos</param>
// <returns>InitiateTxResult</returns>
InitiatePurchaseTxV2(posRefId, purchaseAmount, tipAmount, cashoutAmount, promptForCashout)
{
if (this.CurrentStatus == SpiStatus.Unpaired) return new InitiateTxResult(false, "Not Paired");
if (tipAmount > 0 && (cashoutAmount > 0 || promptForCashout)) return new InitiateTxResult(false, "Cannot Accept Tips and Cashout at the same time.");
if (this.CurrentFlow != SpiFlow.Idle) return new InitiateTxResult(false, "Not Idle");
this.CurrentFlow = SpiFlow.Transaction;
var purchase = PurchaseHelper.CreatePurchaseRequestV2(posRefId, purchaseAmount, tipAmount, cashoutAmount, promptForCashout);
purchase.Config = this.Config;
var purchaseMsg = purchase.ToMessage();
this.CurrentTxFlowState = new TransactionFlowState(
posRefId, TransactionType.Purchase, purchaseAmount, purchaseMsg,
`Waiting for EFTPOS connection to make payment request. ${purchase.AmountSummary()}`);
if (this._send(purchaseMsg))
{
this.CurrentTxFlowState.Sent(`Asked EFTPOS to accept payment for ${purchase.AmountSummary()}`);
}
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new InitiateTxResult(true, "Purchase Initiated");
}
// <summary>
// Initiates a refund transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
// </summary>
// <param name="posRefId">Alphanumeric Identifier for your refund.</param>
// <param name="amountCents">Amount in Cents to charge</param>
// <returns>InitiateTxResult</returns>
InitiateRefundTx(posRefId, amountCents)
{
if (this.CurrentStatus == SpiStatus.Unpaired) {

@@ -281,10 +349,12 @@ return new InitiateTxResult(false, "Not Paired");

var purchase = PurchaseHelper.CreateRefundRequest(amountCents, id);
var refundRequest = PurchaseHelper.CreateRefundRequest(amountCents, posRefId);
refundRequest.Config = this.Config;
var refundMsg = refundRequest.ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
id, TransactionType.Refund, amountCents, purchase.ToMessage(),
`Waiting for EFTPOS connection to make refund request for ${amountCents / 100.0}`);
if (this._send(purchase.ToMessage()))
posRefId, TransactionType.Refund, amountCents, refundMsg,
`Waiting for EFTPOS connection to make refund request for ${(amountCents / 100.0).toFixed(2)}`);
if (this._send(refundMsg))
{
this.CurrentTxFlowState.Sent(`Asked EFTPOS to refund ${amountCents / 100.0}`);
this.CurrentTxFlowState.Sent(`Asked EFTPOS to refund ${(amountCents / 100.0).toFixed(2)}`);
}

@@ -296,13 +366,12 @@

/// <summary>
/// Let the EFTPOS know whether merchant accepted or declined the signature
/// </summary>
/// <param name="accepted">whether merchant accepted the signature from customer or not</param>
// <summary>
// Let the EFTPOS know whether merchant accepted or declined the signature
// </summary>
// <param name="accepted">whether merchant accepted the signature from customer or not</param>
AcceptSignature(accepted)
{
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.AwaitingSignatureCheck)
{
this._log.info("Asked to accept signature but I was not waiting for one.");
return;
return new MidTxResult(false, "Asked to accept signature but I was not waiting for one.");
}

@@ -313,13 +382,43 @@

this._send(accepted
? new SignatureAccept(sigReqMsg.RequestId).ToMessage()
: new SignatureDecline(sigReqMsg.RequestId).ToMessage());
? new SignatureAccept(this.CurrentTxFlowState.PosRefId).ToMessage()
: new SignatureDecline(this.CurrentTxFlowState.PosRefId).ToMessage());
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new MidTxResult(true, "");
}
/// <summary>
/// Attempts to cancel a Transaction.
/// Be subscribed to TxFlowStateChanged event to see how it goes.
/// Wait for the transaction to be finished and then see whether cancellation was successful or not.
/// </summary>
// <summary>
// Submit the Code obtained by your user when phoning for auth.
// It will return immediately to tell you whether the code has a valid format or not.
// If valid==true is returned, no need to do anything else. Expect updates via standard callback.
// If valid==false is returned, you can show your user the accompanying message, and invite them to enter another code.
// </summary>
// <param name="authCode">The code obtained by your user from the merchant call centre. It should be a 6-character alpha-numeric value.</param>
// <returns>Whether code has a valid format or not.</returns>
SubmitAuthCode(authCode)
{
if (authCode.length != 6)
{
return new SubmitAuthCodeResult(false, "Not a 6-digit code.");
}
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.AwaitingPhoneForAuth)
{
this._log.info("Asked to send auth code but I was not waiting for one.");
return new SubmitAuthCodeResult(false, "Was not waiting for one.");
}
this.CurrentTxFlowState.AuthCodeSent(`Submitting Auth Code ${authCode}`);
this._send(new AuthCodeAdvice(this.CurrentTxFlowState.PosRefId, authCode).ToMessage());
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new SubmitAuthCodeResult(true, "Valid Code.");
}
// <summary>
// Attempts to cancel a Transaction.
// Be subscribed to TxFlowStateChanged event to see how it goes.
// Wait for the transaction to be finished and then see whether cancellation was successful or not.
// </summary>
// <returns>MidTxResult - false only if you called it in the wrong state</returns>
CancelTransaction()

@@ -330,3 +429,3 @@ {

this._log.info("Asked to cancel transaction but I was not in the middle of one.");
return;
return new MidTxResult(false, "Asked to cancel transaction but I was not in the middle of one.");
}

@@ -348,10 +447,65 @@

document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new MidTxResult(true, "");
}
/// <summary>
/// Initiates a settlement transaction.
/// Be subscribed to TxFlowStateChanged event to get updates on the process.
/// </summary>
InitiateSettleTx(id)
// <summary>
// Initiates a cashout only transaction. Be subscribed to TxFlowStateChanged event to get updates on the process.
// </summary>
// <param name="posRefId">Alphanumeric Identifier for your transaction.</param>
// <param name="amountCents">Amount in Cents to cash out</param>
// <returns>InitiateTxResult</returns>
InitiateCashoutOnlyTx(posRefId, amountCents)
{
if (this.CurrentStatus == SpiStatus.Unpaired) return new InitiateTxResult(false, "Not Paired");
if (this.CurrentFlow != SpiFlow.Idle) return new InitiateTxResult(false, "Not Idle");
var cashoutOnlyRequest = new CashoutOnlyRequest(amountCents, posRefId);
cashoutOnlyRequest.Config = this.Config;
var cashoutMsg = cashoutOnlyRequest.ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
posRefId, TransactionType.CashoutOnly, amountCents, cashoutMsg,
`Waiting for EFTPOS connection to send cashout request for ${(amountCents / 100).toFixed(2)}`);
if (this._send(cashoutMsg))
{
this.CurrentTxFlowState.Sent(`Asked EFTPOS to do cashout for ${(amountCents / 100).toFixed(2)}`);
}
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new InitiateTxResult(true, "Cashout Initiated");
}
// <summary>
// Initiates a Mail Order / Telephone Order Purchase Transaction
// </summary>
// <param name="posRefId">Alphanumeric Identifier for your transaction.</param>
// <param name="amountCents">Amount in Cents</param>
// <returns>InitiateTxResult</returns>
InitiateMotoPurchaseTx(posRefId, amountCents)
{
if (this.CurrentStatus == SpiStatus.Unpaired) return new InitiateTxResult(false, "Not Paired");
if (this.CurrentFlow != SpiFlow.Idle) return new InitiateTxResult(false, "Not Idle");
var motoPurchaseRequest = new MotoPurchaseRequest(amountCents, posRefId);
motoPurchaseRequest.Config = this.Config;
var cashoutMsg = motoPurchaseRequest.ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
posRefId, TransactionType.MOTO, amountCents, cashoutMsg,
`Waiting for EFTPOS connection to send MOTO request for ${(amountCents / 100).toFixed(2)}`);
if (this._send(cashoutMsg))
{
this.CurrentTxFlowState.Sent(`Asked EFTPOS do MOTO for ${(amountCents / 100).toFixed(2)}`);
}
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new InitiateTxResult(true, "MOTO Initiated");
}
// <summary>
// Initiates a settlement transaction.
// Be subscribed to TxFlowStateChanged event to get updates on the process.
// </summary>
InitiateSettleTx(posRefId)
{
if (this.CurrentStatus == SpiStatus.Unpaired) {

@@ -365,9 +519,9 @@ return new InitiateTxResult(false, "Not Paired");

var settleRequest = new SettleRequest(RequestIdHelper.Id("settle"));
var settleRequestMsg = new SettleRequest(RequestIdHelper.Id("settle")).ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
id, TransactionType.Settle, 0, settleRequest.ToMessage(),
posRefId, TransactionType.Settle, 0, settleRequestMsg,
`Waiting for EFTPOS connection to make a settle request`);
if (this._send(settleRequest.ToMessage()))
if (this._send(settleRequestMsg))
{

@@ -381,9 +535,30 @@ this.CurrentTxFlowState.Sent(`Asked EFTPOS to settle.`);

/// <summary>
/// Initiates a Get Last Transaction. Use this when you want to retrieve the most recent transaction
/// that was processed by the Eftpos.
/// Be subscribed to TxFlowStateChanged event to get updates on the process.
/// </summary>
InitiateGetLastTx(id)
// <summary>
// </summary>
InitiateSettlementEnquiry(posRefId)
{
if (this.CurrentStatus == SpiStatus.Unpaired) return new InitiateTxResult(false, "Not Paired");
if (this.CurrentFlow != SpiFlow.Idle) return new InitiateTxResult(false, "Not Idle");
var stlEnqMsg = new SettlementEnquiryRequest(RequestIdHelper.Id("stlenq")).ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
this.CurrentTxFlowState = new TransactionFlowState(
posRefId, TransactionType.SettlementEnquiry, 0, stlEnqMsg,
"Waiting for EFTPOS connection to make a settlement enquiry");
if (this._send(stlEnqMsg))
{
this.CurrentTxFlowState.Sent("Asked EFTPOS to make a settlement enquiry.");
}
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new InitiateTxResult(true,"Settle Initiated");
}
// <summary>
// Initiates a Get Last Transaction. Use this when you want to retrieve the most recent transaction
// that was processed by the Eftpos.
// Be subscribed to TxFlowStateChanged event to get updates on the process.
// </summary>
InitiateGetLastTx()
{
if (this.CurrentStatus == SpiStatus.Unpaired) {

@@ -397,10 +572,10 @@ return new InitiateTxResult(false, "Not Paired");

var gltRequestMsg = new GetLastTransactionRequest(id);
var gltRequestMsg = new GetLastTransactionRequest().ToMessage();
this.CurrentFlow = SpiFlow.Transaction;
var posRefId = gltRequestMsg.Id; // GetLastTx is not trying to get anything specific back. So we just use the message id.
this.CurrentTxFlowState = new TransactionFlowState(
gltRequestMsg.Id, TransactionType.GetLastTx, 0, gltRequestMsg,
posRefId, TransactionType.GetLastTransaction, 0, gltRequestMsg,
"Waiting for EFTPOS connection to make a Get-Last-Transaction request.");
if (this._send(gltRequestMsg.ToMessage()))
if (this._send(gltRequestMsg))
{

@@ -414,7 +589,74 @@ this.CurrentTxFlowState.Sent(`Asked EFTPOS for last transaction.`);

// <summary>
// This is useful to recover from your POS crashing in the middle of a transaction.
// When you restart your POS, if you had saved enough state, you can call this method to recover the client library state.
// You need to have the posRefId that you passed in with the original transaction, and the transaction type.
// This method will return immediately whether recovery has started or not.
// If recovery has started, you need to bring up the transaction modal to your user a be listening to TxFlowStateChanged.
// </summary>
// <param name="posRefId">The is that you had assigned to the transaction that you are trying to recover.</param>
// <param name="txType">The transaction type.</param>
// <returns></returns>
InitiateRecovery(posRefId, txType)
{
if (this.CurrentStatus == SpiStatus.Unpaired) return new InitiateTxResult(false, "Not Paired");
if (this.CurrentFlow != SpiFlow.Idle) return new InitiateTxResult(false, "Not Idle");
this.CurrentFlow = SpiFlow.Transaction;
var gltRequestMsg = new GetLastTransactionRequest().ToMessage();
this.CurrentTxFlowState = new TransactionFlowState(
posRefId, txType, 0, gltRequestMsg,
"Waiting for EFTPOS connection to attempt recovery.");
if (this._send(gltRequestMsg))
{
this.CurrentTxFlowState.Sent(`Asked EFTPOS to recover state.`);
}
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
return new InitiateTxResult(true, "Recovery Initiated");
}
/// <summary>
/// Handling the 2nd interaction of the pairing process, i.e. an incoming KeyRequest.
/// </summary>
/// <param name="m">incoming message</param>
// <summary>
// GltMatch attempts to conclude whether a gltResponse matches an expected transaction and returns
// the outcome.
// If Success/Failed is returned, it means that the gtlResponse did match, and that transaction was succesful/failed.
// If Unknown is returned, it means that the gltResponse does not match the expected transaction.
// </summary>
// <param name="gltResponse">The GetLastTransactionResponse message to check</param>
// <param name="posRefId">The Reference Id that you passed in with the original request.</param>
// <returns></returns>
GltMatch(gltResponse, posRefId, ...deprecatedArgs)
{
// Obsolete method call check
// Old interface: GltMatch(GetLastTransactionResponse gltResponse, TransactionType expectedType, int expectedAmount, DateTime requestTime, string posRefId)
if(deprecatedArgs.length) {
if(deprecatedArgs.length == 2) {
this._log.info("Obsolete method call detected: Use GltMatch(gltResponse, posRefId)");
return this.GltMatch(gltResponse, deprecatedArgs[2]);
} else {
throw new Error("Obsolete method call with unknown args: Use GltMatch(GetLastTransactionResponse gltResponse, string posRefId)");
}
}
this._log.info(`GLT CHECK: PosRefId: ${posRefId}->${gltResponse.GetPosRefId()}`);
if (!posRefId == gltResponse.GetPosRefId())
{
return SuccessState.Unknown;
}
return gltResponse.GetSuccessState();
}
// endregion
// region Internals for Pairing Flow
// <summary>
// Handling the 2nd interaction of the pairing process, i.e. an incoming KeyRequest.
// </summary>
// <param name="m">incoming message</param>
_handleKeyRequest(m)

@@ -433,6 +675,6 @@ {

/// <summary>
/// Handling the 4th interaction of the pairing process i.e. an incoming KeyCheck.
/// </summary>
/// <param name="m"></param>
// <summary>
// Handling the 4th interaction of the pairing process i.e. an incoming KeyCheck.
// </summary>
// <param name="m"></param>
_handleKeyCheck(m)

@@ -448,7 +690,6 @@ {

/// <summary>
/// Handling the 5th and final interaction of the pairing process, i.e. an incoming PairResponse
/// </summary>
/// <param name="m"></param>
// <summary>
// Handling the 5th and final interaction of the pairing process, i.e. an incoming PairResponse
// </summary>
// <param name="m"></param>
_handlePairResponse(m)

@@ -464,7 +705,9 @@ {

// Still Waiting for User to say yes on POS
this._log.info("Got Pair Confirm from Eftpos, but still waiting for use to confirm from POS.");
this.CurrentPairingFlowState.Message = "Confirm that the following Code is what the EFTPOS showed";
document.dispatchEvent(new CustomEvent('PairingFlowStateChanged', {detail: this.CurrentPairingFlowState}));
}
else
{
this.CurrentPairingFlowState.Message = "Pairing Successful";
this._log.info("Got Pair Confirm from Eftpos, and already had confirm from POS. Now just waiting for first pong.");
this._onPairingSuccess();

@@ -478,7 +721,10 @@ }

{
this.CurrentPairingFlowState.Message = "Pairing Failed";
this._onPairingFailed();
}
}
document.dispatchEvent(new CustomEvent('PairingFlowStateChanged', {detail: this.CurrentPairingFlowState}));
_handleDropKeysAdvice(m)
{
this._log.Info("Eftpos was Unpaired. I shall unpair from my end as well.");
this._doUnpair();
}

@@ -510,6 +756,15 @@

/// <summary>
/// Sometimes the server asks us to roll our secrets.
/// </summary>
/// <param name="m"></param>
_doUnpair()
{
this.CurrentStatus = SpiStatus.Unpaired;
this._conn.Disconnect();
this._secrets = null;
this._spiMessageStamp.Secrets = null;
document.dispatchEvent(new CustomEvent('SecretsChanged', {detail: this._secrets}));
}
// <summary>
// Sometimes the server asks us to roll our secrets.
// </summary>
// <param name="m"></param>
_handleKeyRollingRequest(m)

@@ -525,14 +780,14 @@ {

/// <summary>
/// The PinPad server will send us this message when a customer signature is reqired.
/// We need to ask the customer to sign the incoming receipt.
/// And then tell the pinpad whether the signature is ok or not.
/// </summary>
/// <param name="m"></param>
// <summary>
// The PinPad server will send us this message when a customer signature is reqired.
// We need to ask the customer to sign the incoming receipt.
// And then tell the pinpad whether the signature is ok or not.
// </summary>
// <param name="m"></param>
_handleSignatureRequired(m)
{
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished)
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
this._log.info(`Received Signature Required but I was not waiting for one. ${m.DecryptedJson}`);
this._log.info(`Received Signature Required but I was not waiting for one. Incoming Pos Ref ID: ${incomingPosRefId}`);
return;

@@ -545,12 +800,31 @@ }

/// <summary>
/// The PinPad server will reply to our PurchaseRequest with a PurchaseResponse.
/// </summary>
/// <param name="m"></param>
// <summary>
// The PinPad server will send us this message when an auth code is required.
// </summary>
// <param name="m"></param>
_handleAuthCodeRequired(m)
{
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
_log.Info(`Received Auth Code Required but I was not waiting for one. Incoming Pos Ref ID: ${incomingPosRefId}`);
return;
}
var phoneForAuthRequired = new PhoneForAuthRequired(m);
var msg = `Auth Code Required. Call ${phoneForAuthRequired.GetPhoneNumber()} and quote merchant id ${phoneForAuthRequired.GetMerchantId()}`;
this.CurrentTxFlowState.PhoneForAuthRequired(phoneForAuthRequired, msg);
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
}
// <summary>
// The PinPad server will reply to our PurchaseRequest with a PurchaseResponse.
// </summary>
// <param name="m"></param>
_handlePurchaseResponse(m)
{
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished)
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
this._log.info(`Received Purchase response but I was not waiting for one. ${m.DecryptedJson}`);
this._log.info(`Received Purchase response but I was not waiting for one. Incoming Pos Ref ID: ${incomingPosRefId}"`);
return;

@@ -566,11 +840,52 @@ }

/// <summary>
/// The PinPad server will reply to our RefundRequest with a RefundResponse.
/// </summary>
/// <param name="m"></param>
// <summary>
// The PinPad server will reply to our CashoutOnlyRequest with a CashoutOnlyResponse.
// </summary>
// <param name="m"></param>
_handleCashoutOnlyResponse(m)
{
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
this._log.info(`Received Cashout Response but I was not waiting for one. Incoming Pos Ref ID: ${incomingPosRefId}`);
return;
}
// TH-1A, TH-2A
this.CurrentTxFlowState.Completed(m.GetSuccessState(), m, "Cashout Transaction Ended.");
// TH-6A, TH-6E
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
}
// <summary>
// The PinPad server will reply to our MotoPurchaseRequest with a MotoPurchaseResponse.
// </summary>
// <param name="m"></param>
_handleMotoPurchaseResponse(m)
{
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished || !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
this._log.info(`Received Moto Response but I was not waiting for one. Incoming Pos Ref ID: ${incomingPosRefId}`);
return;
}
// TH-1A, TH-2A
this.CurrentTxFlowState.Completed(m.GetSuccessState(), m, "Moto Transaction Ended.");
// TH-6A, TH-6E
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
}
// <summary>
// The PinPad server will reply to our RefundRequest with a RefundResponse.
// </summary>
// <param name="m"></param>
_handleRefundResponse(m)
{
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished)
var incomingPosRefId = m.Data.pos_ref_id;
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished | !this.CurrentTxFlowState.PosRefId == incomingPosRefId)
{
this._log.info(`Received Refund response but I was not waiting for one. ${m.DecryptedJson}`);
this._log.info(`Received Refund response but I was not waiting for this one. Incoming Pos Ref ID: ${incomingPosRefId}`);
return;

@@ -586,6 +901,6 @@ }

/// <summary>
/// TODO: Handle the Settlement Response received from the PinPad
/// </summary>
/// <param name="m"></param>
// <summary>
// TODO: Handle the Settlement Response received from the PinPad
// </summary>
// <param name="m"></param>
HandleSettleResponse(m)

@@ -606,6 +921,25 @@ {

/// <summary>
/// Sometimes we receive event type "error" from the server, such as when calling cancel_transaction and there is no transaction in progress.
/// </summary>
/// <param name="m"></param>
// <summary>
// Handle the Settlement Enquiry Response received from the PinPad
// </summary>
// <param name="m"></param>
_handleSettlementEnquiryResponse(m)
{
if (this.CurrentFlow != SpiFlow.Transaction || this.CurrentTxFlowState.Finished)
{
this._log.info(`Received Settlement Enquiry response but I was not waiting for one. ${m.DecryptedJson}`);
return;
}
// TH-1A, TH-2A
this.CurrentTxFlowState.Completed(m.GetSuccessState(), m, "Settlement Enquiry Ended.");
// TH-6A, TH-6E
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
}
// <summary>
// Sometimes we receive event type "error" from the server, such as when calling cancel_transaction and there is no transaction in progress.
// </summary>
// <param name="m"></param>
_handleErrorEvent(m)

@@ -627,65 +961,7 @@ {

}
/// <summary>
/// GltMatch attempts to conclude whether a gltResponse matches an expected transaction and returns
/// the outcome.
/// If Success/Failed is returned, it means that the gtlResponse did match, and that transaction was succesful/failed.
/// If Unknown is returned, it means that the gtlResponse does not match the expected transaction.
/// </summary>
/// <param name="gltResponse">The GetLastTransactionResponse message to check</param>
/// <param name="expectedType">The expected Type, Purchase/Refund/...</param>
/// <param name="expectedAmount">The expected Amount in cents</param>
/// <param name="requestTime">The time you made your request.</param>
/// <param name="posRefId">The Reference Id that you passed in with the original request. Currently not used.
/// But will be used in the future for abetter conclusion.</param>
/// <returns></returns>
GltMatch(gltResponse, expectedType, expectedAmount, requestTime, posRefId) {
// adjust request time for serverTime and also give 5 seconds slack.
var reqServerTime = new Date(requestTime + this._spiMessageStamp.ServerTimeDelta - 5000);
/* Transformat into ISO format */
var bankDateTime = gltResponse.GetBankDateTimeString();
var gtlBankTime = new Date(bankDateTime.substring(4, 8) + "-"
+ bankDateTime.substring(2, 4) + "-"
+ bankDateTime.substring(0, 2) + "T"
+ bankDateTime.substring(8, 10)+":"+ bankDateTime.substring(10, 12) +":"+bankDateTime.substring(12, 14));
// For now we use amount and date to match as best we can.
// In the future we will be able to pass our own pos_ref_id in the tx request that will be returned here.
this._log.info(`Amount: ${expectedAmount}->${gltResponse.GetTransactionAmount()}, Date: ${reqServerTime}->${gtlBankTime}`);
if (gltResponse.GetTransactionAmount() != expectedAmount)
{
return SuccessState.Unknown;
}
switch (gltResponse.GetTxType())
{
case "PURCHASE":
if (expectedType != TransactionType.Purchase)
return SuccessState.Unknown;
break;
case "REFUND":
if (expectedType != TransactionType.Refund)
return SuccessState.Unknown;
break;
default:
return SuccessState.Unknown;
}
if (reqServerTime > gtlBankTime)
{
return SuccessState.Unknown;
}
// For now we use amount and date to match as best we can.
// In the future we will be able to pass our own pos_ref_id in the tx request that will be returned here.
return gltResponse.GetSuccessState();
}
/// <summary>
/// When the PinPad returns to us what the Last Transaction was.
/// </summary>
/// <param name="m"></param>
// <summary>
// When the PinPad returns to us what the Last Transaction was.
// </summary>
// <param name="m"></param>
_handleGetLastTransactionResponse(m)

@@ -702,18 +978,35 @@ {

// Let's attempt recovery. This is step 4 of Transaction Processing Handling
this._log.info(`Got Last Transaction.. Attempting Recovery.`);
this._log.info(`Got Last Transaction..`);
txState.GotGltResponse();
var gtlResponse = new GetLastTransactionResponse(m);
if (!gtlResponse.WasRetrievedSuccessfully())
{
if (gtlResponse.WasOperationInProgressError())
if (gtlResponse.IsStillInProgress(txState.PosRefId))
{
// TH-4E - Operation In Progress
this._log.info(`Operation still in progress... stay waiting.`);
if (gtlResponse.IsWaitingForSignatureResponse() && !txState.AwaitingSignatureCheck)
{
this._log.info("Eftpos is waiting for us to send it signature accept/decline, but we were not aware of this. " +
"The user can only really decline at this stage as there is no receipt to print for signing.");
this.CurrentTxFlowState.SignatureRequired(new SignatureRequired(txState.PosRefId, m.Id, "MISSING RECEIPT\n DECLINE AND TRY AGAIN."), "Recovered in Signature Required but we don't have receipt. You may Decline then Retry.");
}
else if (gtlResponse.IsWaitingForAuthCode() && !txState.AwaitingPhoneForAuth)
{
this._log.info("Eftpos is waiting for us to send it auth code, but we were not aware of this. " +
"We can only cancel the transaction at this stage as we don't have enough information to recover from this.");
this.CurrentTxFlowState.PhoneForAuthRequired(new PhoneForAuthRequired(txState.PosRefId, m.Id, "UNKNOWN", "UNKNOWN"), "Recovered mid Phone-For-Auth but don't have details. You may Cancel then Retry.");
}
else
{
this._log.info("Operation still in progress... stay waiting.");
// No need to publish txFlowStateChanged. Can return;
return;
}
}
else
{
// TH-4X - Unexpected Error when recovering
this._log.info(`Unexpected error in Get Last Transaction Response during Transaction Recovery: ${m.GetError()}`);
txState.UnknownCompleted(`Unexpected Error when recovering Transaction Status. Check EFTPOS.`);
// TH-4X - Unexpected Response when recovering
this._log.info(`Unexpected Response in Get Last Transaction during - Received posRefId:${gtlResponse.GetPosRefId()} Error:${m.GetError()}`);
txState.UnknownCompleted("Unexpected Error when recovering Transaction Status. Check EFTPOS. ");
}

@@ -723,3 +1016,3 @@ }

{
if (txState.Type === TransactionType.GetLastTx)
if (txState.Type == TransactionType.GetLastTransaction)
{

@@ -730,10 +1023,11 @@ // THIS WAS A PLAIN GET LAST TRANSACTION REQUEST, NOT FOR RECOVERY PURPOSES.

txState.Completed(m.GetSuccessState(), m, "Last Transaction Retrieved");
}
else
}
else
{
let successState = this.GltMatch(gtlResponse, txState.Type, txState.AmountCents, txState.RequestTime, "_NOT_IMPL_YET");
if (successState == SuccessState.Unknown)
// TH-4A - Let's try to match the received last transaction against the current transaction
var successState = this.GltMatch(gtlResponse, txState.PosRefId);
if (successState == SuccessState.Unknown)
{
// TH-4N: Didn't Match our transaction. Consider Unknown State.
this._log.info(`Did not match transaction.`);
this._log.info("Did not match transaction.");
txState.UnknownCompleted("Failed to recover Transaction Status. Check EFTPOS. ");

@@ -745,5 +1039,5 @@ }

gtlResponse.CopyMerchantReceiptToCustomerReceipt();
txState.Completed(m.GetSuccessState(), m, "GLT Transaction Ended.");
txState.Completed(successState, m, "Transaction Ended.");
}
}
}
}

@@ -784,2 +1078,6 @@ document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: txState}));

// endregion
// region Internals for Connection Management
_resetConn()

@@ -793,14 +1091,14 @@ {

document.addEventListener('ConnectionStatusChanged', (e) => this._onSpiConnectionStatusChanged(e.detail));
document.addEventListener('MessageReceived', (e) => this._onSpiMessageReceived(e.detail.data));
document.addEventListener('MessageReceived', (e) => this._onSpiMessageReceived(e.detail));
document.addEventListener('ErrorReceived', (e) => this._onWsErrorReceived(e.detail));
}
/// <summary>
/// This method will be called when the connection status changes.
/// You are encouraged to display a PinPad Connection Indicator on the POS screen.
/// </summary>
/// <param name="state"></param>
// <summary>
// This method will be called when the connection status changes.
// You are encouraged to display a PinPad Connection Indicator on the POS screen.
// </summary>
// <param name="state"></param>
_onSpiConnectionStatusChanged(state)
{
switch (state)
switch (state.ConnectionState)
{

@@ -833,4 +1131,2 @@ case ConnectionState.Connecting:

this._missedPongsCount = 0;
this._mostRecentLoginResponse = null;
this._readyToTransact = false;
this._stopPeriodicPing();

@@ -849,4 +1145,4 @@

if (this._conn == null) return; // This means the instance has been disposed. Aborting.
this._log.info(`Will try to reconnect in 5s...`);
setTimeout(() => {

@@ -873,2 +1169,7 @@ if (this.CurrentStatus != SpiStatus.Unpaired)

// <summary>
// This is an important piece of the puzzle. It's a background thread that periodically
// sends Pings to the server. If it doesn't receive Pongs, it considers the connection as broken
// so it disconnects.
// </summary>
_startPeriodicPing() {

@@ -905,3 +1206,2 @@ this._stopPeriodicPing();

this._conn.Disconnect();
this._readyToTransact = false;
this._stopPeriodicPing();

@@ -920,8 +1220,10 @@ }

/// <summary>
/// We call this ourselves as soon as we're ready to transact with the PinPad after a connection is established.
/// This function is effectively called after we received the first Login Response from the PinPad.
/// </summary>
// <summary>
// We call this ourselves as soon as we're ready to transact with the PinPad after a connection is established.
// This function is effectively called after we received the first Login Response from the PinPad.
// </summary>
_onReadyToTransact()
{
this._log.info("On Ready To Transact!");
// So, we have just made a connection, pinged and logged in successfully.

@@ -943,11 +1245,18 @@ this.CurrentStatus = SpiStatus.PairedConnected;

this._send(this.CurrentTxFlowState.Request);
this.CurrentTxFlowState.Sent(`Asked EFTPOS to accept payment for ${this.CurrentTxFlowState.AmountCents / 100.0}`);
this.CurrentTxFlowState.Sent(`Sending Request Now...`);
document.dispatchEvent(new CustomEvent('TxFlowStateChanged', {detail: this.CurrentTxFlowState}));
}
}
else
{
// let's also tell the eftpos our latest table configuration.
if(this._spiPat) {
this._spiPat.PushPayAtTableConfig();
}
}
}
/// <summary>
/// When we disconnect, we should also stop the periodic ping.
/// </summary>
// <summary>
// When we disconnect, we should also stop the periodic ping.
// </summary>
_stopPeriodicPing() {

@@ -961,3 +1270,2 @@ if(this._periodicPingThread) {

// Send a Ping to the Server

@@ -969,8 +1277,9 @@ _doPing()

this._send(ping);
this._mostRecentPingSentTime = Date.now();
}
/// <summary>
/// Received a Pong from the server
/// </summary>
/// <param name="m"></param>
// <summary>
// Received a Pong from the server
// </summary>
// <param name="m"></param>
_handleIncomingPong(m)

@@ -981,57 +1290,24 @@ {

if (this._mostRecentLoginResponse == null ||
this._mostRecentLoginResponse.ExpiringSoon(this._spiMessageStamp.ServerTimeDelta))
if (this._mostRecentPongReceived == null)
{
// We have not logged in yet, or login expiring soon.
this._doLogin();
}
this._mostRecentPongReceived = m;
}
/// <summary>
/// Login is a mute thing but is required.
/// </summary>
_doLogin()
{
var lr = LoginHelper.NewLoginRequest();
this._send(lr.ToMessage());
}
/// <summary>
/// When the server replied to our LoginRequest with a LoginResponse, we take note of it.
/// </summary>
/// <param name="m"></param>
_handleLoginResponse(m)
{
var lr = new LoginResponse(m);
if (lr.Success)
{
this._mostRecentLoginResponse = lr;
if (!this._readyToTransact)
// First pong received after a connection, and after the pairing process is fully finalised.
if (this.CurrentStatus != SpiStatus.Unpaired)
{
// We are finally ready to make transactions.
// Let's notify ourselves so we can take some actions.
this._readyToTransact = true;
this._log.info(`Logged in Successfully. Expires: ${lr.Expires}`);
if (this.CurrentStatus != SpiStatus.Unpaired) {
this._onReadyToTransact();
}
this._log.info("First pong of connection and in paired state.");
this._onReadyToTransact();
}
else
{
this._log.info(`I have just refreshed my Login. Now Expires: ${lr.Expires}`);
this._log.info("First pong of connection but pairing process not finalised yet.");
}
}
else
{
this._log.info("Logged in Failure.");
this._conn.Disconnect();
}
this._mostRecentPongReceived = m;
this._log.debug(`PongLatency:${Date.now() - this._mostRecentPingSentTime}`);
}
/// <summary>
/// The server will also send us pings. We need to reply with a pong so it doesn't disconnect us.
/// </summary>
/// <param name="m"></param>
// <summary>
// The server will also send us pings. We need to reply with a pong so it doesn't disconnect us.
// </summary>
// <param name="m"></param>
_handleIncomingPing(m)

@@ -1043,5 +1319,5 @@ {

/// <summary>
/// Ask the PinPad to tell us what the Most Recent Transaction was
/// </summary>
// <summary>
// Ask the PinPad to tell us what the Most Recent Transaction was
// </summary>
_callGetLastTransaction()

@@ -1053,12 +1329,18 @@ {

/// <summary>
/// This method will be called whenever we receive a message from the Connection
/// </summary>
/// <param name="messageJson"></param>
// <summary>
// This method will be called whenever we receive a message from the Connection
// </summary>
// <param name="messageJson"></param>
_onSpiMessageReceived(messageJson)
{
// First we parse the incoming message
var m = Message.FromJson(messageJson, this._secrets);
var m = Message.FromJson(messageJson.Message, this._secrets);
this._log.info("Received:" + m.DecryptedJson);
if (SpiPreauth.IsPreauthEvent(m.EventName))
{
this._spiPreauth._handlePreauthMessage(m);
return;
}
// And then we switch on the event type.

@@ -1076,4 +1358,4 @@ switch (m.EventName)

break;
case Events.LoginResponse:
this._handleLoginResponse(m);
case Events.DropKeysAdvice:
this._handleDropKeysAdvice(m);
break;

@@ -1086,5 +1368,14 @@ case Events.PurchaseResponse:

break;
case Events.CashoutOnlyResponse:
this._handleCashoutOnlyResponse(m);
break;
case Events.MotoPurchaseResponse:
this._handleMotoPurchaseResponse(m);
break;
case Events.SignatureRequired:
this._handleSignatureRequired(m);
break;
case Events.AuthCodeRequired:
this._handleAuthCodeRequired(m);
break;
case Events.GetLastTransactionResponse:

@@ -1096,2 +1387,5 @@ this._handleGetLastTransactionResponse(m);

break;
case Events.SettlementEnquiryResponse:
this._handleSettlementEnquiryResponse(m);
break;
case Events.Ping:

@@ -1106,2 +1400,16 @@ this._handleIncomingPing(m);

break;
case Events.PayAtTableGetTableConfig:
if (this._spiPat == null)
{
this._send(PayAtTableConfig.FeatureDisableMessage(RequestIdHelper.Id("patconf")));
break;
}
this._spiPat._handleGetTableConfig(m);
break;
case Events.PayAtTableGetBillDetails:
this._spiPat._handleGetBillDetailsRequest(m);
break;
case Events.PayAtTableBillPayment:
this._spiPat._handleBillPaymentAdvice(m);
break;
case Events.Error:

@@ -1121,3 +1429,3 @@ this._handleErrorEvent(m);

{
this._log.warn("Received WS Error: " + error);
this._log.warn("Received WS Error: " + error.Message);
}

@@ -1124,0 +1432,0 @@

@@ -1,82 +0,82 @@

/// <summary>
/// Represents the 3 Pairing statuses that the Spi instanxce can be in.
/// </summary>
// <summary>
// Represents the 3 Pairing statuses that the Spi instanxce can be in.
// </summary>
const SpiStatus =
{
/// <summary>
/// Paired and Connected
/// </summary>
// <summary>
// Paired and Connected
// </summary>
PairedConnected: 'PairedConnected',
/// <summary>
/// Paired but trying to establish a connection
/// </summary>
// <summary>
// Paired but trying to establish a connection
// </summary>
PairedConnecting: 'PairedConnecting',
/// <summary>
/// Unpaired
/// </summary>
// <summary>
// Unpaired
// </summary>
Unpaired: 'Unpaired'
};
/// <summary>
/// The Spi instance can be in one of these flows at any point in time.
/// </summary>
// <summary>
// The Spi instance can be in one of these flows at any point in time.
// </summary>
const SpiFlow =
{
/// <summary>
/// Currently going through the Pairing Process Flow.
/// Happens during the Unpaired SpiStatus.
/// </summary>
// <summary>
// Currently going through the Pairing Process Flow.
// Happens during the Unpaired SpiStatus.
// </summary>
Pairing: 'Pairing',
/// <summary>
/// Currently going through the transaction Process Flow.
/// Cannot happen in the Unpaired SpiStatus.
/// </summary>
// <summary>
// Currently going through the transaction Process Flow.
// Cannot happen in the Unpaired SpiStatus.
// </summary>
Transaction: 'Transaction',
/// <summary>
/// Not in any of the other states.
/// </summary>
// <summary>
// Not in any of the other states.
// </summary>
Idle: 'Idle'
};
/// <summary>
/// Represents the Pairing Flow State during the pairing process
/// </summary>
// <summary>
// Represents the Pairing Flow State during the pairing process
// </summary>
class PairingFlowState
{
constructor(state) {
/// <summary>
/// Some text that can be displayed in the Pairing Process Screen
/// that indicates what the pairing process is up to.
/// </summary>
// <summary>
// Some text that can be displayed in the Pairing Process Screen
// that indicates what the pairing process is up to.
// </summary>
this.Message = null;
/// <summary>
/// When true, it means that the EFTPOS is shoing the confirmation code,
/// and your user needs to press YES or NO on the EFTPOS.
/// </summary>
// <summary>
// When true, it means that the EFTPOS is shoing the confirmation code,
// and your user needs to press YES or NO on the EFTPOS.
// </summary>
this.AwaitingCheckFromEftpos = null;
/// <summary>
/// When true, you need to display the YES/NO buttons on you pairing screen
/// for your user to confirm the code.
/// </summary>
// <summary>
// When true, you need to display the YES/NO buttons on you pairing screen
// for your user to confirm the code.
// </summary>
this.AwaitingCheckFromPos = null;
/// <summary>
/// This is the confirmation code for the pairing process.
/// </summary>
// <summary>
// This is the confirmation code for the pairing process.
// </summary>
this.ConfirmationCode = null;
/// <summary>
/// Indicates whether the Pairing Flow has finished its job.
/// </summary>
// <summary>
// Indicates whether the Pairing Flow has finished its job.
// </summary>
this.Finished = null;
/// <summary>
/// Indicates whether pairing was successful or not.
/// </summary>
// <summary>
// Indicates whether pairing was successful or not.
// </summary>
this.Successful = null;

@@ -94,10 +94,16 @@

Refund: 'Refund',
CashoutOnly: 'CashoutOnly',
MOTO: 'MOTO',
Settle: 'Settle',
GetLastTx: 'GetLastTx'
SettlementEnquiry: 'SettlementEnquiry',
GetLastTransaction: 'GetLastTransaction',
Preauth: 'Preauth',
AccountVerify: 'AccountVerify'
};
/// <summary>
/// Used as a return in the InitiateTx methods to signify whether
/// the transaction was initiated or not, and a reason to go with it.
/// </summary>
// <summary>
// Used as a return in the InitiateTx methods to signify whether
// the transaction was initiated or not, and a reason to go with it.
// </summary>
class InitiateTxResult

@@ -107,12 +113,12 @@ {

{
/// <summary>
/// Whether the tx was initiated.
/// When true, you can expect updated to your registered callback.
/// When false, you can retry calling the InitiateX method.
/// </summary>
// <summary>
// Whether the tx was initiated.
// When true, you can expect updated to your registered callback.
// When false, you can retry calling the InitiateX method.
// </summary>
this.Initiated = initiated;
/// <summary>
/// Text that gives reason for the Initiated flag, especially in case of false.
/// </summary>
// <summary>
// Text that gives reason for the Initiated flag, especially in case of false.
// </summary>
this.Message = message;

@@ -122,94 +128,123 @@ }

/// <summary>
/// Represents the State during a TransactionFlow
/// </summary>
// <summary>
// Used as a return in calls mid transaction to let you know
// whether the call was valid or not.
// These attributes work for COM interop.
// </summary>
class MidTxResult
{
// <summary>
// This default stucture works for COM interop.
// </summary>
constructor(valid, message)
{
this.Valid = valid;
this.Message = message;
}
}
// <summary>
// Represents the State during a TransactionFlow
// </summary>
class TransactionFlowState
{
constructor(id, type, amountCents, message, msg)
constructor(posRefId, type, amountCents, message, msg)
{
/// <summary>
/// The id given to this transaction
/// </summary>
this.Id = id;
// <summary>
// The id given to this transaction
// </summary>
this.PosRefId = posRefId;
this.Id = posRefId; // obsolete, but let's maintain it for now, to mean same as PosRefId.
/// <summary>
/// Purchase/Refund/Settle/...
/// </summary>
// <summary>
// Purchase/Refund/Settle/...
// </summary>
this.Type = type;
/// <summary>
/// Amount in cents for this transaction
/// </summary>
// <summary>
// A text message to display on your Transaction Flow Screen
// </summary>
this.DisplayMessage = msg;
// <summary>
// Amount in cents for this transaction
// </summary>
this.AmountCents = amountCents;
/// <summary>
/// Whther the request has been sent to the EFTPOS yet or not.
/// In the PairedConnecting state, the transaction is initiated
/// but the request is only sent once the connection is recovered.
/// </summary>
// <summary>
// Whther the request has been sent to the EFTPOS yet or not.
// In the PairedConnecting state, the transaction is initiated
// but the request is only sent once the connection is recovered.
// </summary>
this.RequestSent = false;
/// <summary>
/// The time when the request was sent to the EFTPOS.
/// </summary>
// <summary>
// The time when the request was sent to the EFTPOS.
// </summary>
this.RequestTime = null;
/// <summary>
/// The time when we last asked for an update, including the original request at first
/// </summary>
// <summary>
// The time when we last asked for an update, including the original request at first
// </summary>
this.LastStateRequestTime = null;
/// <summary>
/// Whether we're currently attempting to Cancel the transaction.
/// </summary>
// <summary>
// Whether we're currently attempting to Cancel the transaction.
// </summary>
this.AttemptingToCancel = null;
/// <summary>
/// When this flag is on, you need to display the dignature accept/decline buttons in your
/// transaction flow screen.
/// </summary>
// <summary>
// When this flag is on, you need to display the dignature accept/decline buttons in your
// transaction flow screen.
// </summary>
this.AwaitingSignatureCheck = false;
/// <summary>
/// Whether this transaction flow is over or not.
/// </summary>
// <summary>
// When this flag is on, you need to show your user the phone number to call to get the authorisation code.
// Then you need to provide your user means to enter that given code and submit it via SubmitAuthCode().
// </summary>
this.AwaitingPhoneForAuth = null;
// <summary>
// Whether this transaction flow is over or not.
// </summary>
this.Finished = false;
/// <summary>
/// The success state of this transaction. Starts off as Unknown.
/// When finished, can be Success, Failed OR Unknown.
/// </summary>
// <summary>
// The success state of this transaction. Starts off as Unknown.
// When finished, can be Success, Failed OR Unknown.
// </summary>
this.Success = SuccessState.Unknown;
/// <summary>
/// The request message that we are sending/sent to the server.
/// </summary>
this.Request = message;
/// <summary>
/// The response at the end of the transaction.
/// Might not be present in all edge cases.
/// You can then turn this Message into the appropriate structure,
/// such as PurchaseResponse, RefundResponse, etc
/// </summary>
// <summary>
// The response at the end of the transaction.
// Might not be present in all edge cases.
// You can then turn this Message into the appropriate structure,
// such as PurchaseResponse, RefundResponse, etc
// </summary>
this.Response = null;
/// <summary>
/// A text message to display on your Transaction Flow Screen
/// </summary>
this.DisplayMessage = msg;
/// <summary>
/// The message the we received from EFTPOS that told us that signature is required.
/// </summary>
// <summary>
// The message the we received from EFTPOS that told us that signature is required.
// </summary>
this.SignatureRequiredMessage = null;
/// <summary>
/// The time when the cancel attempt was made.
/// </summary>
// <summary>
// The message the we received from EFTPOS that told us that Phone For Auth is required.
// </summary>
this.PhoneForAuthRequiredMessage = null;
// <summary>
// The time when the cancel attempt was made.
// </summary>
this.CancelAttemptTime = null;
/// <summary>
/// Whether we're currently waiting for a Get Last Transaction Response to get an update.
/// </summary>
// <summary>
// The request message that we are sending/sent to the server.
// </summary>
this.Request = message;
// <summary>
// Whether we're currently waiting for a Get Last Transaction Response to get an update.
// </summary>
this.AwaitingGltResponse = null;

@@ -265,2 +300,15 @@ }

PhoneForAuthRequired(spiMessage, msg)
{
this.PhoneForAuthRequiredMessage = spiMessage;
this.AwaitingPhoneForAuth = true;
this.DisplayMessage = msg;
}
AuthCodeSent(msg)
{
this.AwaitingPhoneForAuth = false;
this.DisplayMessage = msg;
}
Completed(state, response, msg)

@@ -274,2 +322,3 @@ {

this.AwaitingSignatureCheck = false;
this.AwaitingPhoneForAuth = false;
this.DisplayMessage = msg;

@@ -286,4 +335,48 @@ }

this.AwaitingSignatureCheck = false;
this.AwaitingPhoneForAuth = false;
this.DisplayMessage = msg;
}
}
// <summary>
// Used as a return in the SubmitAuthCode method to signify whether Code is valid
// </summary>
class SubmitAuthCodeResult
{
constructor(validFormat, message)
{
this.ValidFormat = validFormat;
// <summary>
// Text that gives reason for Invalidity
// </summary>
this.Message = message;
}
}
class SpiConfig
{
constructor() {
this.PromptForCustomerCopyOnEftpos = false;
this.SignatureFlowOnEftpos = false;
}
addReceiptConfig(messageData)
{
if (this.PromptForCustomerCopyOnEftpos)
{
messageData.prompt_for_customer_copy = this.PromptForCustomerCopyOnEftpos;
}
if (this.SignatureFlowOnEftpos)
{
messageData.print_for_signature_required_transactions = this.SignatureFlowOnEftpos;
}
return messageData;
}
ToString()
{
return `PromptForCustomerCopyOnEftpos:${this.PromptForCustomerCopyOnEftpos} SignatureFlowOnEftpos:${this.SignatureFlowOnEftpos}`;
}
}

@@ -0,0 +0,0 @@ describe('DiffieHellman', function() {

@@ -0,0 +0,0 @@ describe('KeyRollingTest', function() {

describe('MessagesTest', function() {
'use strict';
const mockKeyRequest = JSON.stringify({
message: {
event: "key_request",
id: "62",
data: {
enc: {
A: "17E7BE43D53102647040FC090000C215810E28E5E0CBD4F47923E194AE72AB0CDADF922642B73C568AA94A84B61874A475549E1F95847BE2725462E3D635F019BE39B2064F1EFFBE6B80CE97FBB7C0913ADC06A2445980B57647778B127FFCCE8B28A44BADEDE0110A5AFB05FEF7AA3F54988AFB04310A113F713601683D8E30CA2BAFC2EC34879127019E3352D8CAB9603184283AE3C9359D40C12474500018B8640AF371DC8712A06A3A443DF41DA9C1C60FAD2ACB02564A6694382B18811AA30CE38A1FC251DE0669504CAB620C2BA4A84CCC8FBDCBB30BBB3EACA76008599F74C2FDF6231773DC0439969CB5F2904A71DDF57F7DF9394AA29CBE4856FC82"
},
hmac: {
A: "89708531EADF129B4F67F00ECBF883C825A0EF3D766E32BC2BA13508B53FC3F5928316DE05CBE82FA1BBF4116E58A68C6F9C3C8FEF492051498188F4E80F82D5764FF50331B34E418E41480FAE0C794F20D9F7AE9819CB317AD2351B165783D57D12C39F95D9A5A292B89D3A26F9BBDE5C218EEC3FE63D910DCB0E1A0E6B570AF94BBD3025EB5E23FFBD9E8D58FE68403B3E50566DA8E2E54EED1A4D754689ECB7266B3D4804E39FB868F1741896757E7844C3389DA49F87D23FB2E9F6ADDBE9C14CC92F322CF3B471CE217E48D0762D5C963827AA6F4316B905F19E0262A35DC4B62E2FB95B7AAD5616C61F31C9A74008EE51BAB2CD6F646320FA30A6DDC4D7"
}
}
}
});
const mockEncryptedMsg = JSON.stringify({

@@ -40,2 +25,3 @@ enc: "819A6FF34A7656DBE5274AC44A28A48DD6D723FCEF12570E4488410B83A1504084D79BA9DF05C3CE58B330C6626EA5E9EB6BAAB3BFE95345A8E9834F183A1AB2F6158E8CDC217B4970E6331B4BE0FCAA",

var mockKeyRequest = JSON.stringify(__fixtures__['KeyRequest']);
var incomingMessage = Message.FromJson(mockKeyRequest, null);

@@ -42,0 +28,0 @@

describe('PairingTests', function() {
'use strict';
const mockKeyRequest = JSON.stringify({
message: {
event: "key_request",
id: "62",
data: {
enc: {
A: "17E7BE43D53102647040FC090000C215810E28E5E0CBD4F47923E194AE72AB0CDADF922642B73C568AA94A84B61874A475549E1F95847BE2725462E3D635F019BE39B2064F1EFFBE6B80CE97FBB7C0913ADC06A2445980B57647778B127FFCCE8B28A44BADEDE0110A5AFB05FEF7AA3F54988AFB04310A113F713601683D8E30CA2BAFC2EC34879127019E3352D8CAB9603184283AE3C9359D40C12474500018B8640AF371DC8712A06A3A443DF41DA9C1C60FAD2ACB02564A6694382B18811AA30CE38A1FC251DE0669504CAB620C2BA4A84CCC8FBDCBB30BBB3EACA76008599F74C2FDF6231773DC0439969CB5F2904A71DDF57F7DF9394AA29CBE4856FC82"
},
hmac: {
A: "89708531EADF129B4F67F00ECBF883C825A0EF3D766E32BC2BA13508B53FC3F5928316DE05CBE82FA1BBF4116E58A68C6F9C3C8FEF492051498188F4E80F82D5764FF50331B34E418E41480FAE0C794F20D9F7AE9819CB317AD2351B165783D57D12C39F95D9A5A292B89D3A26F9BBDE5C218EEC3FE63D910DCB0E1A0E6B570AF94BBD3025EB5E23FFBD9E8D58FE68403B3E50566DA8E2E54EED1A4D754689ECB7266B3D4804E39FB868F1741896757E7844C3389DA49F87D23FB2E9F6ADDBE9C14CC92F322CF3B471CE217E48D0762D5C963827AA6F4316B905F19E0262A35DC4B62E2FB95B7AAD5616C61F31C9A74008EE51BAB2CD6F646320FA30A6DDC4D7"
}
}
}
});

@@ -22,2 +7,3 @@ it('TestPairingKeyResponse', function() {

var secrets = null;
var mockKeyRequest = JSON.stringify(__fixtures__['KeyRequest']);
var incomingMessage = Message.FromJson(mockKeyRequest, secrets);

@@ -24,0 +10,0 @@

@@ -9,6 +9,7 @@ describe('Purchase', function() {

var purchase = PurchaseHelper.CreatePurchaseRequest(purchaseAmountCents, RequestIdHelper.Id("p"));
var id = RequestIdHelper.Id("p");
var purchase = PurchaseHelper.CreatePurchaseRequest(purchaseAmountCents, id);
expect(purchase.AmountCents).toBe(10);
expect(purchase.Id).toBe('p1');
expect(purchase.Id).toBe(id);

@@ -22,5 +23,5 @@ var encryptedMsg = purchase.ToMessage().ToJson(spiMessageStamp);

expect(decryptedMsg.Id).toBe('p1');
expect(decryptedMsg.Id.substr(0,5)).toBe('prchs');
expect(decryptedMsg.EventName).toBe(Events.PurchaseRequest);
expect(decryptedMsg.Data.amount_purchase).toBe(purchaseAmountCents);
expect(decryptedMsg.Data.purchase_amount).toBe(purchaseAmountCents);

@@ -27,0 +28,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc