Enable duplicate payment protection for payment connector

This article describes how to enable duplicate payment protection functionality in a payment connector that manages the integration with a payment terminal. A payment connector is an extension library that is written to integrate the POS with a payment terminal.

Overview

  • Required reading - List of articles that you should be read before starting the implementation of the duplicate payment protection functionality in a payment connector.
  • Prerequisites - List of prerequisites to enable duplicate payment protection in a payment connector implementation.
  • Understanding duplicate payment protection flows - Describes the various flows where the duplicate payment protection is invoked in the POS.
  • Implement duplicate payment requests - Describes the various payment-related requests that need to be implemented to support the duplicate payment protection feature.

Required reading

Be sure to read the following article before enabling duplicate payment protection for a given payment connector.

Prerequisites

The following prerequisites must be met before duplicate payment protection can be enabled for a payment connector implementation.

Support for unique transaction scope in payment terminal or payment gateway/processor

In order to enable support for duplicate payment protection, the corresponding payment terminal or payment gateway/processor must provide support for unique transaction scopes. This support is usually handled through a unique payment reference identifier that can be generated by the payment terminal or payment gateway/processor before the payment is being processed. Without support for this unique identifier, the connector will not be able to uniquely match a previously initiated transaction with a successful payment authorization, which is at the core of the duplicate payment protection feature.

Understanding duplicate payment protection flows

The Retail POS has been extended to invoke the new requests, GetTransactionReferencePaymentTerminalDeviceRequest and GetTransactionByTransactionReferencePaymentTerminalDeviceRequest in various scenarios across the POS, such as immediately before an Authorize request is issued to the payment connector. The purpose of these new requests is to detect and recover successfully processed payment through the payment connector before a new payment request is issued. The following diagram illustrates a simple scenario where a payment request is successfully processed through the payment connector but the POS has crashed before it can receive the response. Subsequently, the POS is able to recover the previously processed payment through the duplicate payment protection feature.

Duplicate Payment Protection Flows.

Supported POS flows

The following list describes all of the POS flows where the GetTransactionByTransactionReferencePaymentTerminalDeviceRequest is invoked to recover an existing payment. These are the most commonly executed flows when the POS crashes or loses connectivity to the payment terminal or payment gateway during the processing of a payment transaction.

  • Cashier invokes payment for any amount using a card payment
  • Cashier invokes payment for any amount using a cash payment
  • Cashier attempts to void a line on the cart
  • Cashier attempts to void the transaction
  • Cashier attempts to suspend the transaction

Implement duplicate payment requests

The following sample illustrates the requests on the INamedRequestHandler payment connector implementation that are required to fully enable the duplicate payment protection feature.

namespace Contoso.Commerce.HardwareStation.PaymentSample 
{ 
    /// <summary>
    /// <c>Simulator</c> manager payment device class.
    /// </summary>
    public class PaymentDeviceSample : INamedRequestHandler
    {
        /// <summary>
        /// Gets the collection of supported request types by this handler.
        /// </summary>
        public IEnumerable<Type> SupportedRequestTypes
        {
            get
            {
                return new[]
                {
                    // New request types specific to the duplicate payment protection feature.
                    typeof(GetTransactionReferencePaymentTerminalDeviceRequest),
                    typeof(GetTransactionByTransactionReferencePaymentTerminalDeviceRequest),
                    
                    // Extended with new functionality.
                    typeof(AuthorizePaymentTerminalDeviceRequest)
                };
            }
        }

        /// <summary>
        /// Executes the payment device simulator operation based on the incoming request type.
        /// </summary>
        /// <param name="request">The payment terminal device simulator request message.</param>
        /// <returns>Returns the payment terminal device simulator response.</returns>
        public Response Execute(Microsoft.Dynamics.Commerce.Runtime.Messages.Request request)
        {
            ThrowIf.Null(request, nameof(request));

            Type requestType = request.GetType();

            if (requestType == typeof(GetTransactionReferencePaymentTerminalDeviceRequest))
            {
                return this.GetTransactionReference((GetTransactionReferencePaymentTerminalDeviceRequest)request);
            }
            else if (requestType == typeof(GetTransactionByTransactionReferencePaymentTerminalDeviceRequest))
            {
                return this.GetTransactionByTransactionReference((GetTransactionByTransactionReferencePaymentTerminalDeviceRequest)request);
            }
            else if (requestType == typeof(AuthorizePaymentTerminalDeviceRequest))
            {
                return this.AuthorizePayment((AuthorizePaymentTerminalDeviceRequest)request);
            }
            ...

            return new NullResponse();
        }
    }
}

GetTransactionReferencePaymentTerminalDeviceRequest / GetTransactionReferencePaymentTerminalDeviceResponse

Description

The GetTransactionReferencePaymentTerminalDeviceRequest is invoked by the Retail POS at the beginning of a payment transaction and sets the scope of the payment. The scope of this request ends once the payment line is successfully added to the cart or an error, indicating that a card cannot be used and returned from the payment connector. The corresponding GetTransactionReferencePaymentTerminalDeviceResponse response will contain the corresponding unique identifier generated by the payment connector to look up a payment transaction. Note that the payment connector implementation must not cache the generated ID because the ID must survive the application's restarts, typically the ID should be generated by the Payment Gateway.

Request signature

public GetTransactionReferencePaymentTerminalDeviceRequest(string lockToken, string posTerminalId, string eftTerminalId);

Request variables

Variable Description
lockToken Unique token value that is generated when the payment terminal is initially locked for the transaction.
posTerminalId Unique name of the POS register.
eftTerminalId EFT POS terminal number configured either on the POS register or the shared Hardware Station.

Response signature

public GetTransactionReferencePaymentTerminalDeviceResponse(string id);

Response variables

Variable Description
id Unique identifier used for the scope of the payment transaction.

PaymentTransactionReferenceData

After the GetTransactionReferencePaymentTerminalDeviceRequest is executed, the Retail POS will generate a new instance of the PaymentTransactionReferenceData class to carry the required contextual data that is needed for the POS to maintain the duplicate payment protection scope. This variable will be stored and maintained in the POS and then used to check for existing transactions during key payment operations.

Properties

Variable Description
Command Payment related command that was invoked. Possible values are Sale, Refund, Activate, or Load.
IdFromConnector Unique identifier generated through the GetTransactionReferencePaymentTerminalDeviceRequest request.
InitiatedDate Date and time when the original payment-related transaction was initiated.
UniqueTransactionId Unique identifier for the cart transaction (not specific to a payment itself).
Amount Amount of the payment transaction being handled.

AuthorizePaymentTerminalDeviceRequest

Description

AuthorizePaymentTerminalDeviceRequest parameter has now been extended to provide a new constructor that supports the new request, PaymentTransactionReferenceData. The purpose of this parameter is to maintain the contextual reference data and stamp the authorization request to the payment terminal or payment gateway/processor with the unique identifier generated by the payment connector and used by the POS to later query for and identify duplicate payments.

GetTransactionByTransactionReferencePaymentTerminalDeviceRequest / GetTransactionByTransactionReferencePaymentTerminalDeviceResponse

GetTransactionByTransactionReferencePaymentTerminalDeviceRequest is invoked by the Retail POS immediately before certain payment-related operations to identify whether an existing payment transaction was already invoked and handled by the payment terminal or payment gateway/processor. If an existing transaction is recovered by the payment connector, it will be returned and short circuit subsequent calls to the connector to trigger a new transaction.

Request signature

public GetTransactionByTransactionReferencePaymentTerminalDeviceRequest(string lockToken, PaymentTransactionReferenceData transactionReferenceData);

Request variables

Variable Description
lockToken Unique token value that is generated when the payment terminal is initially locked for the transaction.
transactionReferenceData Property bag containing various properties used to uniquely identify a payment transaction. For more information, see the section PaymentTransactionReferenceData in this article.

Response signature

public GetTransactionByTransactionReferencePaymentTerminalDeviceResponse(PaymentInfo paymentInfo);

Response variables

Variable Description
paymentInfo The recovered payment transaction. This is identical to the payment response returned for any other payment request, such as Authorize or Refund.