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

@mx51/spi-client-js

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mx51/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": "@mx51/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": {

@@ -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);

@@ -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) {

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 @@ {

@@ -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}`;
}
}
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 too big to display

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