Handle challenges during payment processing

Challenges are potential issues the Clover device may encounter while attempting to process a Payment. A Challenge is triggered by a potential duplicate Payment (DUPLICATE_CHALLENGE) or an offline Payment (OFFLINE_CHALLENGE).

When a Challenge is raised, the Clover device calls the ICloverConnectorListener.OnConfirmPaymentRequest() method and passes a ConfirmPaymentRequest object that contains the Payment and the list of Challenges it encountered. In response, the merchant must either accept the Payment and any risk associated with it, or reject the Payment. Until the Payment is accepted or rejected, the Clover device displays on the "Merchant is verifying your payment" screen.

📘

Note

On-device semi-integrations that use PaymentConnector or Intents do not need to handle challenges. Secure Payments manage challenges instead.

Handle offline challenges

To accommodate the higher risk associated with an offline transaction, Clover provides you and the merchant with an OFFLINE_CHALLENGE to let you decide whether or not you want to accept or reject the transaction. An OFFLINE_CHALLENGE is raised when the Clover device attempts to process a card transaction while the device is not connected to the network. The Clover device is unable to verify with the payment gateway that the card is valid and has sufficient funds.

There are three common ways to handle offline challenges:

  • Accept all payments processed offline, with the merchant assuming the associated risk.
  • Reject all payments processed offline, minimizing the merchant's risk at the expense of potentially missing revenue.
  • Let the POS indicate that the card transaction occurred offline, and state the risk associated with accepting the payment. The POS screen displays two buttons for accepting and rejecting the payment.

If the merchant chooses to accept the payment, the payment request is added to the device offline queue, and then sent to the payment gateway when the device regains network connectivity.

Handle duplicate payment challenges

A DUPLICATE_CHALLENGE is raised when multiple Payments are made with the same card type and the last 4 digits within the same hour.

🚧

Important

Clover recommends that you do not programmatically call CloverConnector.RejectPayment() on all instances of DUPLICATE_CHALLENGE.

To avoid false positives for DUPLICATE_CHALLENGE, Clover suggests that you either:

  • Implement your own logic that also compares the Payment amount.
  • Generate a pop-up that asks the merchant if the Payment is a duplicate.

Identify duplicate payment requests

Compare payment amounts

You can determine whether a DUPLICATE_CHALLENGE was triggered by an actual duplicate Payment request rather than a different Payment made with the same card type and last 4 digits within the same hour. To do so compare the Payment amount with the previous transaction amount. The following code snippet demonstrates how to implement this logic for Remote Pay Windows:

//1. Make a sale.
//2. Save the SaleResponse from OnSaleResponse(SaleResponse)
//3. Call cloverConnector.RetrievePayment(RetrievePaymentResponse)

OnRetrievePaymentResponse(RetrievePaymentResponse response) 
{
    if (response.Success)
    {
        uiThread.Send(delegate (object state)
        {
            String details = "No matching payment";
            Payment payment = response.Payment;
            if (payment != null)
            {
                if(payment.amount == lastSalesResponse.Payment.amount)
                {
                    details = "Created:" + dateFormat(payment.createdTime) + "\nResult: " + payment.result
               + "\nPaymentId: " + payment.id + "\nOrderId: " + payment.order.id
                + "\nAmount: " + currencyFormat(payment.amount) + " Tip: " + currencyFormat(payment.tipAmount) + " Tax: " + currencyFormat(payment.taxAmount);
                }
            }
            AlertForm.Show(this, response.QueryStatus.ToString(), details);
        }, null);
    }
    else if (response.Result.Equals(ResponseCode.FAIL))
    {
        uiThread.Send(delegate (object state)
        {
            AlertForm.Show(this, response.Reason, response.Message);
        }, null);
    }
    else if (response.Result.Equals(ResponseCode.CANCEL))
    {
        uiThread.Send(delegate (object state)
        {
            AlertForm.Show(this, response.Reason, response.Message);
        }, null);
    }
}

Request merchant verification

Alternatively, you can prompt the merchant to verify whether the Payment is a duplicate.

bool lastChallenge = false;
for (int i = 0; i < request.Challenges.Count; i++)
{
    uiThread.Send(delegate (object state)
    {
        if (i == request.Challenges.Count - 1) // if this is the last challenge
        {
            lastChallenge = true;
        }
        Challenge challenge = request.Challenges[i];
        ConfirmPaymentForm confirmForm = new ConfirmPaymentForm(parentForm, challenge, lastChallenge);
        confirmForm.FormClosed += (object s, FormClosedEventArgs ce) =>
        {
            if (confirmForm.Status == DialogResult.No)
            {
                cloverConnector.RejectPayment(request.Payment, challenge);
                i = request.Challenges.Count;
            }
            else if (confirmForm.Status == DialogResult.OK) // Last challenge was accepted
            {
                cloverConnector.AcceptPayment(request.Payment);
            }
            confirmPaymentFormBusy.Set(); //release the confirmPaymentFormBusy WaitOne lock
        };
        confirmForm.Show();
    }, null);
    confirmPaymentFormBusy.WaitOne(); //wait here until Accept or Reject pressed
}

Accept and reject payments

You can accept a Payment by calling CloverConnector.AcceptPayment(Payment), using the Payment obtained from ConfirmPaymentRequest.Payment. The following snippet demonstrates how to do this with Remote Pay Windows:

cloverConnector.AcceptPayment(request.Payment);

To reject the Payment and prevent it from proceeding, call CloverConnector.RejectPayment(Payment, Challenge) using the:

  • Payment obtained from ConfirmPaymentRequest.Payment and
  • Challenge obtained from ConfirmPaymentRequest.Challenges
cloverConnector.RejectPayment(request.Payment, request.Challenges[i]);

If you have further questions, visit the Clover Developer Community, or contact us at [email protected].