Production Implementation Requirements
In order to provide a production quality integration with LatitudePay the application developer must undertake a number of steps.
Power Failure/Crash Recovery
A reliable integration must handle situation where the POS process dies while a LatitudePay order (or refund) is pending, due to a power/hardware failure, OS restart, bug etc. In this case the usual pattern is:
On start-up, or another suitable event, check a persistent store for a list of outstanding requests. For each pending request, recover at least the MerchantReference, and preferably the order id (if it was received and persisted from the initial CreateOrder request). Ensure the merchant reference is used for both the payment reference AND the IdempotentKey value of the create request.
If you are only able to retrieve the merchant reference, retry the order create request ensuring the IdempotentKey is again set to the merchant reference. This will ensure that even if the original request was actually processed by the LatitudePay servers, a second payment won't be created, instead a copy of the original response containing the PaymentPlanToken required for the following steps will be returned.
If a pending order is found, begin polling for the status of that order. If the order is still pending after the first check either continue polling or cancel immediately. When a final state is reached the POS should relate this answer back to the pending POS transaction, provide some way for the user to apply it to a new transaction or perform an automatic refund depending on how the POS is designed.
If a pending refund is found, retry the refund request using the same merchant reference and IdempotentKey value as on the original refund request, if you want to ensure the refund occurred. Using the IdempotentKey value like this and sending the same request again will only result in at most one refund being applied.
Before sending a create pos purchase or create refund request, generate a unique MerchantReference to be used on that request and save it to persistent storage so it can be used for crash recovery (see above). For a CreateOrderRequest also store the order id returned as soon as possible if the call is successful.
When a POS transaction is completed (or next persisted to permanent storage) with an order or refund that has reached a final status, remove that requests reference from the persistent store of pending requests so it will no longer be rechecked on a restart (see previous steps).
Error Handling
All methods in the LatitudePayClient class might throw exceptions. Some exceptions such as System.ArgumentException and System.ArgumentNullException occur before the request is sent, if the request data doesn't meet the minimum known requirements for LatitudePay. These should be handled probably presented to the user to tell them what is missing.
In addition, any error normally thrown by the .Net HttpClient class including System.TaskCanceledException and HttpRequestException could be re-thrown from these methods. TaskCanceled and timeout exceptions may require special handling. For example if these exceptions are thrown during a create pos purchase and the POS is not going to try again then another call should be made to CancelPurchase to ensure the order is not subsequently accepted by the user.
HttpResponseExceptions may indicate a transient error, such as the network being briefly down, or a service unavailable/too many requests response from the server. In this case the POS should wait a short period and then retry the operation.
Any error returned by the LatitudePay API (indicating the HTTP call was received by the server but resulted in an error) is converted to a LatitudePayApiException. This has an error message and the http status code returned by the API.
Polling Timeout
It's possible that due to a long network outage or similar problem that a create order request may never reach an approved or declined state and cannot be immediately cancelled. In this case the POS should have a timeout where it gives up polling. This timeout should not be too short, or should prompt the operator to ask if it should give up, as new customers can sometimes take several minutes to complete the sign up process. It is up to the POS to decide how to behave, but the usual logic is:
- Treat the request as declined
- Store the request somewhere to be rechecked at a later time when the network is available again.
- If the customer still wants to make payment they will have to choose another payment method (since the network is not available and LatitudePay cannot be used)
- On recheck (once the network is available again):
- If the payment is pending, cancel it.
- If the payment is approved, refund it.
- If the payment is cancelled or declined, there is nothing to do except remove it from the list of rechecks
Manual Cancellation
It is possible the POS operator may need to cancel an in-progress payment, if the end consumer is unable to do so for some reason. It's also possible the customer's device may run out of battery, have no network connection to receive the payment request, or get dropped and damaged etc. In any of these cases where the payment cannot be processed for a known reason the POS user should have the option to manually cancel the request without waiting for a long timeout. This needs to use the CancelPurchase method of the LatitudePayClient object to notify LatitudePay of the cancellation, and only treat the payment as cancelled if the cancellation request is successful.
Cancelling Orders
When making a CancelPurchase request an exception might be thrown, and this could be caused by the order having been approved or declined between when you last performed a status check and when the cancellation request was received. If an LatitudePayApiException is thrown as the result of a cancellation request your code should re-check the current status of the order and take action depending on the latest status.