Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RetrievePaymentResponse externalId problem #180

Closed
thoughtspacewebsites opened this issue Aug 26, 2021 · 8 comments
Closed

RetrievePaymentResponse externalId problem #180

thoughtspacewebsites opened this issue Aug 26, 2021 · 8 comments

Comments

@thoughtspacewebsites
Copy link

thoughtspacewebsites commented Aug 26, 2021

I'm working on trying to implement open payment checking in my app to ensure that there are no transactions left in an open state before beginning a new transaction. This process is described here:

https://docs.clover.com/docs/payment-reconciliation-and-recovery

right now, I'm just using localStorage to stash the externalPaymentId that I generate for each payment request. When I get the successful response back from the device for a payment, I then wipe the localStorage key. Before running a new payment, I first check to see if anything is saved in the local storage value, and if so, it implies that a break down occurred between when a payment request was sent and the response was received. This means we have a payment request in an unknown state.

So here's the thing, upon testing, it only seems to work in certain scenarios. For example, if I begin a payment request on the device and then restart my POS app on my computer, it successfully looks up the half finished transaction on the next reload and returns its info. If, however, I power cycle the Clover device while it's in the middle of a payment request, then when it comes back online and my POS app reconnects, the next payment lookup attempt results in a "NOT_FOUND" status code, as if the Clover device forgot about this external ID. If I open the orders app on the Clover device, however, I can see the transaction hanging out in there as "open", and I can't do anything about it. Not sure why the external ID seems to not work after a Clover device reboot. Is this a known issue, or possibly a result of misunderstanding of how external IDs should be used? I assumed since I see the open transactions hanging out, it means they're on the device, but if my external ID lookup no longer works, I have no idea how to recover to a state where I can complete / void these transactions. Also, is there any way to obtain a list of ALL open transactions (or even all transactions, open and closed) through this SDK? That seems like it would help negate this issue as I wouldn't have to check for individual payment IDs, but rather could just get the open batch list and iterate it to check for any non-complete payments.

@david-clover-com
Copy link
Contributor

  1. "If, however, I power cycle the Clover device while it's in the middle of a payment request, then when it comes back online and my POS app reconnects, the next payment lookup attempt results in a "NOT_FOUND" status code"
    Provide the exact steps you are taking so that we can attempt to reproduce this scenario, we need to know, for example, exactly where you are in the payment flow when you turn the device off. Providing the Clover payment id and your external id would also be helpful.

  2. "Also, is there any way to obtain a list of ALL open transactions (or even all transactions, open and closed) through this SDK?"
    No, but you shouldn't need that because you shouldn't be starting a new payment until the prior payment has been completed.

@thoughtspacewebsites
Copy link
Author

Inside of a processPayment function that I set up, I do the following:

  1. first, check localstorage to see if I have an externalId saved
  2. If I don't, proceed to set up a onSaleResponse listener that removes the localstorage externalId (payment is done) and then removes itself. Then create a new externalId / payment request. This will be caught by the listener I just set up in this step.
  3. IF, however, there was an externalId saved in step 1, bypass step two, and instead, divert to another function that set up a onRetrievePaymentResponse listener and passes said externalId to the clover device via retrievePayment. The listener returns the response back to the processPayment function which then interprets the response and determines if it's ok to proceed with payment (step 2) or not.

That's the base of the logical flow. Beyond that, my test cases:

  1. Start a new salerequest, don't do anything on the device, reboot the POS app. After the POS reconnects to the clover device, run another sale (Clover device still sitting on the payment prompt). An existing sale request is caught and the sale is blocked. Expected behavior
  2. Do the same as above, except instead of rebooting the POS app, reboot the Clover device. Wait for reconnect. Try another sale, and on lookup of the existing localStorage externalId, it fails and returns a NOT_FOUND code. Unexpected.

After test case 2 above, the transaction does show up as "Open" under the orders app on the clover device, but it returns a not found on externalId lookup through the SDK.

Here's a relevant chunk of code too:

    processPayment: function(amount){
            var that = this;
            var existingId = that.getExternalPaymentId();
            return new Promise(function(resolve, reject){
                if(!that.connector || !that.listener){
                    reject('Clover connector / listener not enabled, please reboot app and try again');
                }
                else if(!amount){
                    reject('No payment amount specified!')
                }
                else if(existingId){
                    that.getExistingPaymentInfo(existingId).then(function(result){
                        reject(result)
                    }).catch(function(error){
                        //If we couldn't find our payment ID, remove it from the cache and try again
                        console.log('payment id not found', error);
                        alert('payment ID not found');
                        that.removeExternalPaymentId();
                        return that.processPayment(amount);
                    });
                }
                else{
                    //First, add a new temp listener for this sale
                    that.tempListeners['sale'] = Object.assign({}, that.listener, {
                        onSaleResponse: function (response) {
                            //console.log('sale response:', response);
                            //Check if sale was successful or not and react accordingly...
                            if(response.success){
                                resolve(response);
                                that.connector.showMessage('Payment processed successfully! Thank you!')
                                window.setTimeout(function(){
                                    that.connector.showWelcomeScreen();
                                }, 3000);
                            }
                            else{
                                reject(response);
                            }

                            //Now remove our cached external id
                            that.removeExternalPaymentId();
                            that.connector.removeCloverConnectorListener(that.tempListeners['sale']);
                        }
                    });
                    that.connector.addCloverConnectorListener(that.tempListeners['sale']);

                    //Now run the transaction request over to the Clover device
                    var saleRequest = new sdk.remotepay.SaleRequest();
                    var externalId = clover.CloverID.getNewId();
                    //Make sure to save the external ID in a local cache for this session...
                    that.cacheExternalPaymentId(externalId);

                    saleRequest.setExternalId(externalId);
                    saleRequest.setAmount(amount);
                    saleRequest.setCardEntryMethods(clover.CardEntryMethods.ALL);
                    saleRequest.setAutoAcceptSignature(true);
                    saleRequest.setAutoAcceptPaymentConfirmations(true);
                    saleRequest.setDisableReceiptSelection(true);
                    //console.log({message: "Sending sale", request: saleRequest});
                    that.connector.sale(saleRequest);

                }
            });
        },



        /**
         * Used to save an external payment id into local storage
         * This allows us to make sure no payments are left open / hanging
         */
        cacheExternalPaymentId: function(id){
            if(id){
                localStorage.setItem('cloverExternalPaymentId', id);
            }
        },


        /**
         * Returns the external payment ID save in our localStorage if there is one
         */
        getExternalPaymentId: function(){
            var id = localStorage.getItem('cloverExternalPaymentId');
            if(id){
                return id;
            }
            else{
                return false;
            }
        },



        /**
         * Gets rid of our cached external payment ID
         */
        removeExternalPaymentId: function(){
            localStorage.removeItem('cloverExternalPaymentId');
        },





        getExistingPaymentInfo: function(id){
            var that = this;
            return new Promise(function(resolve, reject){
                //First, add a new temp listener for this sale
                that.tempListeners['existingPayment'] = Object.assign({}, that.listener, {
                    onRetrievePaymentResponse: function (response) {
                        if(response.getQueryStatus() == clover.remotepay.QueryStatus.NOT_FOUND){
                            reject(response);
                        }
                        resolve(response);
                        that.connector.removeCloverConnectorListener(that.tempListeners['existingPayment']);
                    }
                });
                that.connector.addCloverConnectorListener(that.tempListeners['existingPayment']);
                
                
                const retrievePaymentRequest = new clover.remotepay.RetrievePaymentRequest();
                retrievePaymentRequest.setExternalPaymentId(id);
                that.connector.retrievePayment(retrievePaymentRequest);
            });
        },

@thoughtspacewebsites
Copy link
Author

Also, regarding your second point, if there was an endpoint or tool I could use to retrieve all payments currently sitting on the Clover device, I could show a list in my app on the employee facing side which would allow for more thorough linking of my orders in my database to the associated transactions on my Clover device. Definitely seems like it could be a useful administration tool.

@david-clover-com
Copy link
Contributor

david-clover-com commented Aug 26, 2021

You should use our community site if you are unsure about something or have a question - https://community.clover.com/index.html.

1. Start a new salerequest, don't do anything on the device, reboot the POS app. After the POS reconnects to the clover device, run another sale (Clover device still sitting on the payment prompt). An existing sale request is caught and the sale is blocked. Expected behavior
2. Do the same as above, except instead of rebooting the POS app, reboot the Clover device. Wait for reconnect. Try another sale, and on lookup of the existing localStorage externalId, it fails and returns a NOT_FOUND code. Unexpected.

Based on this description, there should NOT be a Clover payment in this scenario, so the NOT_FOUND response is accurate. You haven't done anything on the device, so the device should be on the payment screen when you reboot it. We don't create a payment until the card is processed. We do create an order but an order is not a payment. In Clover the order is king and you can't have a payment without an order. As a result when a payment is initiated we create an order for you (behind the scenes) but the order is not something a third party POS should be concerned with or need to reconcile. So, in summary, there is nothing for you to void etc. A payment doesn't exist for the external id you have provided and you can proceed with a new payment in this scenario.

@thoughtspacewebsites
Copy link
Author

Sorry, opened here as I thought this was an issue. If I open the closeout app on the device, it shows the button to close the batch as disabled and I can't click it. Upon investigation I found a bunch of "open" orders in the orders app. I understand I can now safely ignore those as they don't reflect actual open transactions, but hopefully you can understand me opening this request here as I was under the impression this was undesired behavior. What's causing me to be unable to close out transactions then? I know manual close (via the SDK) isn't enabled on my merchant account, but I thought I would still be able to use the closeout app. I thought those open orders were what was causing the problem.

@david-clover-com
Copy link
Contributor

An open order (with no payments) isn't going to prevent you from closing out. Manual closeout is either disabled for your merchant (default in sandbox) or you are in a region that doesn't support manual closeout.

@thoughtspacewebsites
Copy link
Author

Thanks for the help here.

@david-clover-com
Copy link
Contributor

No, problem. Happy to continue to help but I believe the community is a better initial forum. If you do find an issue we will create a ticket internally. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants