Payment reconciliation and recovery

It is critical for your integrated POS to complete the payment flow on the Clover device while the customer is present. Under normal circumstances this will be handled when the POS receives a payment response (for example, onSaleResponse()). However, the POS must consider and code for other situations. Some examples include the following:

  • Incomplete payment - The customer leaves before completing the payment flow on the Clover device. For example, the merchant requires a signature, but the customer fails to provide one.
  • Poor connectivity - An unreliable connection could cause message loss between the POS and Clover device. For example, the POS is set to handle duplicate payment challenges, but a challenge message is not received by the POS. In this situation, the Clover device is waiting for the duplicate challenge to be verified and for the merchant to accept or reject the payment. If the customer is allowed to leave, the payment is still active on the device, and the merchant is unable to proceed with the next transaction. The merchant may then close the payment application on the Clover device, or the POS may call resetDevice; either action can void the payment, but the merchant cannot reprocess the payment because the customer has left.
  • POS issues - The POS crashes or the user shuts down the POS in the middle of a payment

Pending payment state on the POS

In order to recover from the scenario where a payment is not completed as expected, your POS must do the following:

  1. Create a unique ID per transaction (externalId). This ID is used to confirm or recover the payment state. See Tracking transactions with external IDs for more information.
  2. Maintain the state of the current pending payment. The pending payment request (or, at a minimum, the externalId) should be stored in a persistent fashion so that your POS can recover from POS shutdowns, browser refreshes, and other situations where communication is disrupted.

retrievePayment API

All of the Remote Pay SDKs provide an API to retrieve a specific payment from the device that took the payment. This can be done using CloverConnector.retrievePayment(). Clover recommends using this API to confirm or recover payment information. Your POS should consider using it in the following scenarios, but it is not limited to these situations.

  1. Before a new payment is initiated
  2. Before resetDevice() is called. resetDevice() clears the state of the Clover device to allow it to take a new payment. However, if a payment is active when resetDevice() is called, the active payment may be voided. It is essential that the POS determine the existence and state of any pending payments before calling resetDevice().
  3. On demand, at the merchant’s request (with a Check payment status button or similar feature)
  4. On timeout (if your POS implements a payment timeout)

REST API

Clover’s REST API can also be used to reconcile payments, but there are a few important caveats:

  • The REST API may not accurately reflect the state of payments that were taken offline
  • The REST API cannot be used while the payment flow is still active on the device. The device’s state must be validated with retrieveDeviceStatus() (see Retrieving the device state) and the returned state must be “IDLE” before the REST API can be used.
  • The REST API can't be used to reconcile payment information with merchant reporting systems unless your transaction requests include an externalReferenceId.

Understanding these limitations, the REST API can be used to get payment information:

Use the GET /v3/merchants/mId/payments endpoint to retrieve the payment. Set the filter parameter to the specific externalPaymentId. You must also expand the voids field to get complete information about the transaction. If an externalReferenceId was set for the transaction, this is also returned.

curl -X https://apisandbox.dev.clover.com/v3/merchants/{{mId}}/payments?
filter=externalPaymentId%3D{{externalPaymentId}}&expand=voids

Implementation examples

The following code examples use the Remote Pay Cloud SDK to illustrate the concepts described in previous sections. The same API is available in all Remote Pay SDKs.

The POS should store data about pending payment. For this example, we store the entire SaleRequest. For a POS in production, pendingSaleRequest should be saved in a persistent store.

let pendingSaleRequest = null;

The POS should provide the merchant user the ability to check the status of the pending payment. If a pendingSaleRequest is present, the POS will need to perform two actions to properly assess the status of the device and payment.

checkPaymentStatus: function () {
  if (pendingSaleRequest) {
    const retrieveDeviceStatusRequest = new clover.remotepay.RetrieveDeviceStatusRequest();
    retrieveDeviceStatusRequest.setSendLastMessage(true);
    cloverConnector.retrieveDeviceStatus(retrieveDeviceStatusRequest);
    // Retrieve the payment status.
    updateStatus(`Payment ${pendingSaleRequest.getExternalId()} is currently pending. Checking payment status ...`);
    const retrievePaymentRequest = new clover.remotepay.RetrievePaymentRequest();
    retrievePaymentRequest.setExternalPaymentId(pendingSaleRequest.getExternalId());
    cloverConnector.retrievePayment(retrievePaymentRequest);
  } else {
    updateStatus(`There is currently no pending payment.`);
  }
}
  1. Retrieve the device status and request that the device send the last message. This information may allow the POS to recover from a dropped challenge or transaction response. For example, if the POS crashes and the device is waiting for the merchant to verify a signature, this request will cause the device to resend the signature challenge and allow the POS to continue the transaction.
  2. Determine the state of the payment. Use the retrievePayment() API with the externalId stored on the pendingSaleRequest.

The checkPaymentStatus function should be called in the following situations:

  1. Before starting a new payment. By checking the status, the merchant user can be stopped from interfering with an ongoing transaction and warned of the consequences if they choose to proceed.
  2. Before calling resetDevice(). Because a reset can void a transaction in progress, the merchant should be warned that the action can result in a lost payment.
  3. If the merchant is stuck or wants to check the status. Your POS can provide the merchant a Check Payment Status button when there is a pending payment.

When a pending payment request exists, there are a few different cases your POS needs to handle for a retrievePaymentResponse.

onRetrievePaymentResponse: function(retrievePaymentResponse) {
  console.log({message: "onRetrievePaymentResponse", response: retrievePaymentResponse});
  if (pendingSaleRequest) {
    if (retrievePaymentResponse.getExternalPaymentId() === pendingSaleRequest.getExternalId()) {
      if (retrievePaymentResponse.getQueryStatus() === clover.remotepay.QueryStatus.FOUND) {
        // The payment's result can be used to resolve the payment in your POS.
        pendingSaleRequest = null; // The pending sale is complete.
      } else if (retrievePaymentResponse.getQueryStatus() === clover.remotepay.QueryStatus.IN_PROGRESS) {
        updateStatus(`Payment: ${pendingSaleRequest.getExternalId()} for $${pendingSaleRequest.getAmount() / 100} is in progress.  If you would like to start a new payment, you may reset the device. However, doing so may void payment ${pendingSaleRequest.getExternalId()}. If you would like to reset the device anyway please <a onclick="forceResetDevice()" href="#">click here</a> to confirm.`, null, "pendingStatusContainer", "pendingMessage");
      } else if (retrievePaymentResponse.getQueryStatus() === clover.remotepay.QueryStatus.NOT_FOUND) {
        // The payment wasn’t taken or was voided.
        pendingSaleRequest = null; // The pending sale is complete.
      }
    }
  }
}
  1. First, check the externalId of the payment against that of the retrievePaymentResponse.
  2. If the externalIds match, check the queryStatus of the response.
    a. If the queryStatus is FOUND, the payment has been found and is complete. The payment object is included in the response. Your POS can resolve the payment and display this confirmation to the user.
    b. If the queryStatus is IN_PROGRESS, the Clover device is still processing the payment. In this situation, you may choose to display pending payment information to let the user know the payment is still in progress. You can also allow them to reset the Clover device, but be sure to warn them that the payment in progress may be voided if they choose to reset.
    c. If the queryStatus is NOT_FOUND, the payment was either not taken or was voided.

If the merchant is allowed to reset the device, you must re-check the status of the payment after the reset has occurred. This is necessary because the reset may void the payment in progress.

onResetDeviceResponse(onResetDeviceResponse) {
  if (pendingSaleRequest) {
    // Verify the payment status, the reset will generally void payments, but not in all cases.
    const retrievePaymentRequest = new clover.remotepay.RetrievePaymentRequest();
    retrievePaymentRequest.setExternalPaymentId(pendingSaleRequest.getExternalId());
    cloverConnector.retrievePayment(retrievePaymentRequest);
  }
}

onDeviceReady()

onDeviceReady() is called when the Clover device is ready to process transactions. If at any point the POS is disconnected and reconnected, onDeviceReady() will be called again. This provides the POS the opportunity to recover from any messages missed while it was disconnected.

onDeviceReady: function (merchantInfo) {
  if (pendingSaleRequest) {
    const retrieveDeviceStatusRequest = new clover.remotepay.RetrieveDeviceStatusRequest();
    retrieveDeviceStatusRequest.setSendLastMessage(true);
    cloverConnector.retrieveDeviceStatus(retrieveDeviceStatusRequest);
  }
  …
}

If a pendingSaleRequest is present, you may be able to recover by requesting and handling the last message (using retrieveDeviceStatus()). This can help the POS recover from a dropped challenge or sale response.

resetDevice()

❗️

WARNING

Calling resetDevice() may void the payment in progress.

It is critical that the POS check the status of any pending payments before calling resetDevice() and, most importantly, warn the merchant if there is a pending payment. If you choose to call resetDevice(), you must also check on the status of the payment once you receive a response (onResetDeviceResponse()).


Did this page help you?