EFTExtraInfo

Introduction

When implementing EFT integrations it is often required to store custom information on each payment line such as EFT transaction IDs or any number of extra information that is specific to the PSP (Payment Service Provider). The IEFTService interface defines a EFTExtraInfo property that you can choose to implement. If you do not wish to use this feature simply return null.

To facilitate this you can implement the IEFTExtraInfo interface and attach your information to the payment line. The LS One POS will then persist this information in the following way:

  • Serialize- and deserialize as XML so that no information is lost in case the application stops unexpectedly.
  • Saves the information stored on your IEFTExtraInfo object to the database at the same time that the transaction is saved by the LS One POS.
  • Rebuilds the information from the database when a transaction is rebuilt (e.g. when returning a transaction or reprinting a receipt).

The following diagram shows the flow when a user runs the Authorize card operation to finalize a transaction. This assumes that a customized EFTService has been implemented to communicate with a PSP:

IEFTExtraInfo interface

Defines methods to add additional information to EFTInfo that need to be stored during a transactions lifetime

 

Name Description
XElement ToXml(IErrorLog errorLogger = null) Serializes the contents of this instance to XML and returns it as a XElement
void ToClass(XElement xmlExtraInfo, IErrorLog errorLogger = null) Deserializes xmlExtraInfo and populates this instance with the information
void Insert(IConnectionManager entry, IRetailTransaction retailTransaction) Saves any relevant information to the database. This is called when the transaction is saved into RBOTRANSACTIONTABLE
void Rebuild(IConnectionManager entry, IRetailTransaction retailTransaction)

Called when the transaction has been rebuilt from data from the database. E.g. when returning a transaction or when it's being viewed from the journal. This will rebuild this instance from data from the posted transaction and any other data required from the transaction tables.

 

Implementing IEFTExtraInfo

Fully implementing and utilizing this functionality requires not only implementing the interface itself but also touching on the following :

  • Returning your instance from your EFTService implementation
  • Adding database table/tables to the database to store the information that you are adding
  • Adding your instance of IEFTExtraInfo onto the card payment line that gets added to the transaction

Below are code samples from the integration made to LS Pay. This is to show how this functionality is implemented in a real-life scenario.

1: Adding the database table

For this integration we needed to add a table to store information related to card payments. This is added as an update script in DatabaseUtil:

IF NOT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'RBOTRANSACTIONEFTEXTRAINFO')
BEGIN
	CREATE TABLE [dbo].[RBOTRANSACTIONEFTEXTRAINFO](
		[TRANSACTIONID] [nvarchar](20) NOT NULL,
		[PAYMENTID] [nvarchar](40) NOT NULL,
        [LINENUM] [numeric](28,12) NOT NULL,
		[STOREID] [nvarchar](20) NOT NULL,
		[TERMINALID] [nvarchar](20) NOT NULL,
		[EFTTRANSACTIONID] [nvarchar](80) NOT NULL,
		[TRANSACTIONDATETIME] [nvarchar](40) NOT NULL,
		[ADDITIONALID] [nvarchar](2048) NULL,
		[BATCHNUMBER] [nvarchar](20) NULL,
        [PAIDAMOUNT] [numeric](28,12) NOT NULL,
        [REFUNDEDAMOUNT] [numeric](28,12) NOT NULL,
		[REFUNDPAYMENTID] [nvarchar](40) NOT NULL
	 CONSTRAINT [PK_RBOTRANSACTIONEFTEXTRAINFO] PRIMARY KEY CLUSTERED
	(
		[TRANSACTIONID] ASC, [PAYMENTID] ASC, [LINENUM] ASC, [STOREID] ASC, [TERMINALID] ASC
	)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
	) ON [PRIMARY]
	
	EXEC spDB_SetTableDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'Contains extra info returned by the card payment device';

	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'TRANSACTIONID', 'Unique ID of the POS transaction';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'PAYMENTID', 'Unique ID of the payment transaction';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'LINENUM', 'Payment line number';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'STOREID', 'ID of the store';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'TERMINALID', 'ID of the terminal';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'EFTTRANSACTIONID', 'Unique ID of the transaction generated by the payment terminal';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'TRANSACTIONDATETIME', 'Date of the payment';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'ADDITIONALID', 'Any other ID used to identify the payment transaction (optional)';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'BATCHNUMBER', 'Batch number used to identify the payment transaction (optional)';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'PAIDAMOUNT', 'How much was paid with the card';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'REFUNDEDAMOUNT', 'How much was refunded to the card';
	EXECUTE spDB_SetFieldDescription_1_0 'RBOTRANSACTIONEFTEXTRAINFO', 'REFUNDPAYMENTID', 'ID of the original payment in case of reference refunds';
END
GO

IF NOT EXISTS(SELECT 1 FROM dbo.NUMBERSEQUENCETABLE where DATAAREAID = 'LSR' and NUMBERSEQUENCE = 'EFTPAYMENT')
BEGIN
	INSERT INTO dbo.NUMBERSEQUENCETABLE (NUMBERSEQUENCE,TXT,LOWEST,HIGHEST,FORMAT,INUSE,EMBEDSTOREID,CANBEDELETED,STOREID,DATAAREAID,EMBEDTERMINALID)
VALUES ('EFTPAYMENT','EFT Payments',1,999999999,'#########',1,0,0,'HO','LSR',1)
	INSERT INTO dbo.NUMBERSEQUENCEVALUE (NUMBERSEQUENCE,NEXTREC,STOREID,DATAAREAID)
VALUES ('EFTPAYMENT',1,'HO','LSR')
END
GO

This adds the table RBOTRANSACTIONEFTEXTRAINFO which will be used to store additional information for each card payment line.

 

2: Implementing the IEFTExtraInfo interface

Below is the full implementation from the LS Pay integration to store some additional information such as:

  • IDs used to identify payment transactions. Used for refunds, voiding- and lookup.
  • Payment amounts
  • Refund amounts
  • The LS One POS payment line ID for cross-reference
  • Saving the information to the RBOTRANSACTIONEFTEXTRAINFO table
  • Reading the information from RBOTRANSACTIONEFTEXTRAINFO table
public class EFTExtraInfo : IEFTExtraInfo
{
    public TransactionIdentification TransactionResponseIDs { get; set; }
    public decimal LineNumber { get; set; }
    public RecordIdentifier POSTransactionID { get; set; }
    public RecordIdentifier RefundPaymentID { get; set; }
    public decimal PaidAmount { get; set; }
    public decimal RefundedAmount { get; set; }

    public EFTExtraInfo()
    {
        TransactionResponseIDs = new TransactionIdentification();
    }

    public decimal GetAvailableAmountToRefund()
    {
        return PaidAmount - RefundedAmount;
    }
        
    public void ToClass(XElement xmlExtraInfo, IErrorLog errorLogger = null)
    {
        try
        {
            if (xmlExtraInfo.HasElements)
            {
                IEnumerable<XElement> classVariables = xmlExtraInfo.Elements();

                if(classVariables.Count() == 1)
                {
                    classVariables = classVariables.First().Elements();
                }

                foreach (XElement xVariable in classVariables)
                {
                    if (!xVariable.IsEmpty)
                    {
                        try
                        {
                            switch (xVariable.Name.ToString())
                            {
                                case "TransactionId":
                                    POSTransactionID = xVariable.Value;
                                    break;
                                case "RefundPaymentID":
                                    RefundPaymentID = xVariable.Value;
                                    break;
                                case "PaymentID":
                                    TransactionResponseIDs.TransactionId = xVariable.Value;
                                    break;
                                case "EFTTransactionId":
                                    TransactionResponseIDs.EFTTransactionId = xVariable.Value;
                                    break;
                                case "AdditionalId":
                                    TransactionResponseIDs.AdditionalId = xVariable.Value;
                                    break;
                                case "TransactionDateTime":
                                    TransactionResponseIDs.TransactionDateTime = xVariable.Value;
                                    break;
                                case "BatchNumber":
                                    TransactionResponseIDs.BatchNumber = xVariable.Value;
                                    break;
                                case "LineNum":
                                    LineNumber = Conversion.XmlStringToDecimal(xVariable.Value);
                                    break;
                                case "PaidAmount":
                                    PaidAmount = Conversion.XmlStringToDecimal(xVariable.Value);
                                    break;
                                case "RefundedAmount":
                                    RefundedAmount = Conversion.XmlStringToDecimal(xVariable.Value);
                                    break;
                            }
                        }
                        catch (Exception ex)
                        {
                            if (errorLogger != null)
                            {
                                errorLogger.LogMessage(LogMessageType.Error, "LSPay.EFTExtraInfo:" + xVariable.Name, ex);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            if (errorLogger != null)
            {
                errorLogger.LogMessage(LogMessageType.Error, "EFTExtraInfo.ToClass", ex);
            }

            throw;
        }
    }

    public XElement ToXml(IErrorLog errorLogger = null)
    {
        try
        {
            return new XElement("LSPayExtraInfo",
                new XElement("TransactionId", POSTransactionID),
                new XElement("RefundPaymentID", RefundPaymentID),
                new XElement("PaymentID", TransactionResponseIDs.TransactionId),
                new XElement("EFTTransactionId", TransactionResponseIDs.EFTTransactionId),
                new XElement("AdditionalId", TransactionResponseIDs.AdditionalId),
                new XElement("TransactionDateTime", TransactionResponseIDs.TransactionDateTime),
                new XElement("BatchNumber", TransactionResponseIDs.BatchNumber),
                new XElement("LineNum", Conversion.ToXmlString(LineNumber)),
                new XElement("PaidAmount", Conversion.ToXmlString(PaidAmount)),
                new XElement("RefundedAmount", Conversion.ToXmlString(RefundedAmount))
            );
        }
        catch (Exception ex)
        {
            if (errorLogger != null)
            {
                errorLogger.LogMessage(LogMessageType.Error, "EFTInfo.ToXML", ex);
            }
            throw;
        }
    }

    public void Insert(IConnectionManager entry, IRetailTransaction retailTransaction)
    {
        var statement = new SqlServerStatement("RBOTRANSACTIONEFTEXTRAINFO", StatementType.Insert, false);

        statement.AddKey("TRANSACTIONID", retailTransaction.TransactionId);
        statement.AddKey("PAYMENTID", TransactionResponseIDs.TransactionId);
        statement.AddKey("STOREID", retailTransaction.StoreId);
        statement.AddKey("TERMINALID", retailTransaction.TerminalId);
        statement.AddKey("LINENUM", (decimal)LineNumber, SqlDbType.Decimal);

        statement.AddField("EFTTRANSACTIONID", TransactionResponseIDs.EFTTransactionId);
        statement.AddField("TRANSACTIONDATETIME", TransactionResponseIDs.TransactionDateTime);
        statement.AddField("ADDITIONALID", TransactionResponseIDs.AdditionalId);
        statement.AddField("BATCHNUMBER", TransactionResponseIDs.BatchNumber);
        statement.AddField("PAIDAMOUNT", (decimal)PaidAmount, SqlDbType.Decimal);
        statement.AddField("REFUNDEDAMOUNT", (decimal)RefundedAmount, SqlDbType.Decimal);
        statement.AddField("REFUNDPAYMENTID", RefundPaymentID.StringValue);

        entry.Connection.ExecuteStatement(statement);
    }

    public void Rebuild(IConnectionManager entry, IRetailTransaction retailTransaction)
    {
        using (var cmd = entry.Connection.CreateCommand())
        {
            cmd.CommandText = @"SELECT T.TRANSACTIONID, 
                                T.PAYMENTID, 
                                T.LINENUM, 
                                T.STOREID, 
                                T.TERMINALID, 
                                T.EFTTRANSACTIONID, 
                                T.TRANSACTIONDATETIME, 
                                T.ADDITIONALID, 
                                T.BATCHNUMBER, 
                                T.PAIDAMOUNT, 
                                ISNULL(TR.REFUNDEDAMOUNT, 0) AS REFUNDEDAMOUNT, 
                                T.REFUNDPAYMENTID 
                                FROM RBOTRANSACTIONEFTEXTRAINFO T
                                LEFT JOIN (SELECT REFUNDPAYMENTID, SUM(REFUNDEDAMOUNT) AS REFUNDEDAMOUNT 
                                    FROM RBOTRANSACTIONEFTEXTRAINFO TR WHERE REFUNDEDAMOUNT > 0 GROUP BY REFUNDPAYMENTID) TR ON TR.REFUNDPAYMENTID = T.PAYMENTID
                                WHERE TRANSACTIONID = @TRANSACTIONID AND STOREID = @STOREID AND TERMINALID = @TERMINALID";

            SqlServerParameters.MakeParam(cmd, "TRANSACTIONID", retailTransaction.TransactionId);
            SqlServerParameters.MakeParam(cmd, "STOREID", retailTransaction.StoreId);
            SqlServerParameters.MakeParam(cmd, "TERMINALID", retailTransaction.TerminalId);

            IDataReader dr = entry.Connection.ExecuteReader(cmd, CommandType.Text);

            while (dr.Read())
            {
                EFTExtraInfo eftExtraInfo = new EFTExtraInfo();
                eftExtraInfo.LineNumber = (decimal)dr["LINENUM"];
                eftExtraInfo.PaidAmount = (decimal)dr["PAIDAMOUNT"];
                eftExtraInfo.RefundedAmount = (decimal)dr["REFUNDEDAMOUNT"];
                eftExtraInfo.POSTransactionID = (string)dr["TRANSACTIONID"];
                eftExtraInfo.RefundPaymentID = (string)dr["REFUNDPAYMENTID"];
                eftExtraInfo.TransactionResponseIDs.TransactionId = (string)dr["PAYMENTID"];
                eftExtraInfo.TransactionResponseIDs.EFTTransactionId = (string)dr["EFTTRANSACTIONID"];
                eftExtraInfo.TransactionResponseIDs.TransactionDateTime = (string)dr["TRANSACTIONDATETIME"];
                eftExtraInfo.TransactionResponseIDs.AdditionalId = dr["ADDITIONALID"] == DBNull.Value ? "" : (string)dr["ADDITIONALID"];
                eftExtraInfo.TransactionResponseIDs.BatchNumber = dr["BATCHNUMBER"] == DBNull.Value ? "" : (string)dr["BATCHNUMBER"];

                ITenderLineItem cardTenderLineItem = retailTransaction.ITenderLines.SingleOrDefault(x => x is ICardTenderLineItem && (decimal)x.LineId == eftExtraInfo.LineNumber);

                if(cardTenderLineItem != null)
                {
                    ((ICardTenderLineItem)cardTenderLineItem).EFTInfo.EFTExtraInfo = eftExtraInfo;
                }

                ITenderLineItem originalCardTenderLineItem = retailTransaction.IOriginalTenderLines.SingleOrDefault(x => x is ICardTenderLineItem && (decimal)x.LineId == eftExtraInfo.LineNumber);

                if (originalCardTenderLineItem != null)
                {
                    ((ICardTenderLineItem)originalCardTenderLineItem).EFTInfo.EFTExtraInfo = eftExtraInfo;
                }
            }

            dr.Close();
            dr.Dispose();
        }
    }
}

3: Returning EFTExtraInfo from EFTService

In order for the LS One POS to activate this functionality the EFTService implementation has to return the default instance of the implementation instead of a null value. To do this it is enough to just return a new instance:

public IEFTExtraInfo EFTExtraInfo => new EFTExtraInfo();

 

4: Adding EFTExtraInfo to the transaction

In the case of the LS Pay integration this is done when the EMV_AuthorizeCard function is called. The order of events is roughly as follows:

  1. Call the payment terminal with the desired amount
  2. Parse the response
  3. Create a ICardTenderLineItem and EFTInfo to populate the payment information required by the standard LS One POS
  4. Create and popualate our customized EFTExtraInfo and add to the EFTInfo object

Below are some code examples that show when the EFTExtraInfo is added to the transaction. This occurs after the instance of EFTInfo has been created:

// Save the IDs 
eftInfo.EFTExtraInfo = new EFTExtraInfo()
{
    TransactionResponseIDs = response.Ids,
    POSTransactionID = posTransaction.TransactionId,
    PaidAmount = rtCheck.IsReturn ? 0 : eftInfo.Amount,
    RefundedAmount = rtCheck.IsReturn ? -eftInfo.Amount : 0,
    RefundPaymentID = rtCheck.IsReferenceReturn ? ((EFTExtraInfo)rtCheck.EFTInfo.EFTExtraInfo).TransactionResponseIDs.TransactionId : ""
};

After we have added our information to the eftInfo variable this is then added ot the card tender line when it is created:

// Generate the tender line for the card
CardTenderLineItem tenderLine = new CardTenderLineItem
{
    TenderTypeId = eftInfo.TenderType,
    CardName = eftInfo.CardName,
    EFTInfo = eftInfo
};

After this the tenderLine variable needs to be populated and then added to the transaction, but at this point everything has been done to fully implement EFTExtraInfo.