Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Matt Wootton 23 posts 319 karma points
    Jul 30, 2019 @ 17:19
    Matt Wootton
    0

    Stripe order not finalized on success page

    We are experiencing an issue having recently updated to the latest TeaCommerce (and switched to the new Stripe implementation via webhooks) whereby on the final success page we grab the finalized order but it's not actually finalized and crucially for us doesn't contain an OrderNumber.

    I'm guessing this is due to the fact the webhook hasn't been received yet to perform final finalization? If we refresh the final success page we note that the order retrieved is fully finalized and contains an order number but this only happens on refresh (after webhook received).

    Is this expected behaviour? Is there any way the OrderNumber can be populated beforehand as we display it on the success page and also use it in tracking codes/event tracking on the success page?

    To clarify we are using the latest StripePaymentForm view from the repo and on the success page the following line is used to grab the order:

    TC.GetCurrentFinalizedOrder( storeId );

    That does return an order BUT the OrderNumber is empty and the "IsFinalized" flag is strangely set to false??

    Thanks

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Jul 30, 2019 @ 19:30
    Matt Brailsford
    0

    Hey Matt,

    Unfortunately this is expected behavior and is the main problem with async finalization via webhooks (other payment providers that use webhooks will also have the same problem), however this is the recommended approach by Stripe hence why we went that route.

    Best I could think do would be to either create an interim page on which you run some JS on an interval to keep fetching the current finalized order and only continue to the confirmation page once the returned order has an order ID, or, maybe send the visitor to the confirmation page, but run similar JS on the confirmation and manually set the value in the page + trigger analytics once an order number is received.

    Unfortunately the order ID is only set once an order is finalized and I'd need to review what if any issues might occur by giving an order an order number prior to actually being finalized. It might be that we need to introduce another state for an order (Finalizing?), or, configure what triggers the post finalize actions (ie, maybe you could choose to trigger on finalize or on payment capture so we could finalize early, but only send the email notifications etc on payment capture).

    I hope the suggestions above might help in the interim however.

    Matt

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Jul 30, 2019 @ 20:09
    Matt Brailsford
    0

    Ok, I've had a bit of a further think on this and I think this JS could be integrated into the Stripe javascript we provide. My thought is this:

    1) Let stripe do it's thing
    2) When Stripe is done on the front end, rather than continuing straight away trigger an interval to do the following
    2.1) Use the TC JS API to get the current order (NOT the current finalized order).
    2.2) If we get an order object back then it means the current order hasn't been finalized yet as the act of getting the current order does some checks and if the order had become finalized it would have moved it current finalized order. Because we got one back though, ignore and let the interval try again in a moment.
    2.3) If we get no order back, it must mean that the order has moved to the finalized state and this should only have happened if the webhook changed it. At this point you now know you have a finalized order so can now continue to the continue URL.
    2.4) In your interval, if you don't get a finalized order in a decent timeframe then maybe continue to the continue URL anyway and you'll just have to detect on the final screen whether you have an order number or not.

    If you were to do this I think this could be done within the successHandler found here https://github.com/TeaCommerce/Tea-Commerce-Payment-Providers/blob/master/Source/TeaCommerce.PaymentProviders.UI/Views/Partials/StripePaymentForm.cshtml#L158

    I'll see if I can knock something up quickly

    Matt

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Jul 30, 2019 @ 20:29
    Matt Brailsford
    100

    Ok, so this is totally untested but I think it should work.

    Update the successHandler in the Stripe JS from here https://github.com/TeaCommerce/Tea-Commerce-Payment-Providers/blob/master/Source/TeaCommerce.PaymentProviders.UI/Views/Partials/StripePaymentForm.cshtml#L158 and change it to the following:

    function successHandler() {
        // Payment has been successfully sent to Stripe
        // but we still have to wait for the webhook to
        // finalize the order so we'll briefly hold here 
        // and see if we can detect a finalized order
        waitForFinalizedOrder(10);
    }
    
    function waitForFinalizedOrder(maxTrys, tryCount)
    {
        if (tryCount === undefined) {
            tryCount = 0;
        }
    
        setTimeout(function () {
            // Check for the existence of the current order, if we not longer
            // have one, it means it has become finalized via the Stripe webhook
            // and so should have an Order Number by now.
            // NB: We don't getCurrentFinalizedOrder as it might be that
            // the user has made a purchase in the past and the finalized order
            // cookie could still be set, so instead we check for the non-existance
            // of the current order.
            var currentOrder = TC.getCurrentOrder({ storeId:1 , autoCreate: false });
            if (!currentOrder || tryCount >= maxTrys) {
                doContinue()
            } else {
                waitForFinalizedOrder(maxTrys, tryCount++);
            }
        }, 1000);
    }
    
    function doContinue(){
        document.getElementById('payment-form').submit();
    }
    

    This should wait on the payment page until it can detect a finalized order, or until it's tried 10 times. You'll need to update the storeId (most people render it to the page as a JS variable you can access globally).

    You'll need to do something on the confirmation page to detect if there is an order number or not and if not, assume it's because the interval reach max tries (This could happen if the Stripe Webhook fails for some reason which if it does, another attempt won't be made by Stripe until the following hour so you'll just have to handle it gracefully saying "we'll get your order number to you via email, but if you do need to contact us, here is your cart number for now").

    Hope this helps

    Matt

  • Matt Wootton 23 posts 319 karma points
    Jul 30, 2019 @ 21:27
    Matt Wootton
    0

    Hi Mattt

    Thanks so much for the incredibly fast response and suggested workaround.

    I suspected this might be the case with the async finalization and would suggest a new order status might make sense here. Can I just clarify the state of the payment in Stripe whilst awaiting the webhook, is it in an "authorised" state but the payment hasn't been captured?

    The workaround sounds good and I think is the best way around this. For the vast majority the experience will be the same as before. My only concern is that when debugging through the payment success page a "finalized" order was retrieved strangely before the webhook hit.

    I'll implement and test and let you know how it goes. Will just need to make sure the form is disabled or some form of overlay is shown whilst awaiting finalization.

    Thanks again.

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Jul 31, 2019 @ 08:24
    Matt Brailsford
    0

    Hi Matt,

    I don't think the Order gets updated in any way to say the payment is "Authorized" as essentially it's not until we get some confirmation from Stripe. We could try to update it on the continue URL, but then we hit a problem where if the webhook is fast enough, we try to set it authorized at the same time the webhook is finalizing so currently it's just left for the webhook to finalize it. Short of implementing a queue I'm not sure the best way to handle this scenario, but I will give it some more thought.

    Regarding there being a "finalized" order available on the confirmation page, this is because internally the "continue url" pushes the current order in to the sessions "current finalized order" slot in order to allow people to access it as the current finalized order, even though it's not yet fully finalized. So essentially this is just a session thing, state wise the order is still considered un-finalized.

    I thought about potentially issuing it with an order number at that stage but not persisting it, and let the finalize do the persisting, but I think there is a potential edge case there where it could be possible for an order to be displayed with one order number, but if the cache got cleared when finalize occurred, it could be issued with another order number so they wouldn't match up.

    Hopefully my fix works and helps, and realistically the webhook should be pretty fast so I think you'd probably get a finalized order on the first try, so the delay shouldn't be that long, but I will give it some thought on any better approaches.

    Also, if the JS code works, or if you have to tweak it a little, do let me know as I might just add that into the code that's on Github for people to uses as a basis.

  • Matt Wootton 23 posts 319 karma points
    Jul 31, 2019 @ 17:09
    Matt Wootton
    0

    Hi Matt

    The code works fine and tested both with and without 3DS payment flows.

    Only thing I had to change was the hard coded store Id which I updated to use the StoreId from the model:

    var currentOrder = TC.getCurrentOrder({ storeId:@Model.StoreId, autoCreate: false });
    

    I would just caution that if implementing this approach it's really important that the form/submit button is disabled to avoid multiple payment submissions. I've not actually tested hitting it twice but I should imagine you could put through multiple payments this way.

    There are already "disableForm" and "enableForm" methods in the view but it's left to the developer to implement (which makes sense as most will want to customise) but you could possibly add a line in there to disable/enable the submit button in case the view is used verbatim.

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Jul 31, 2019 @ 17:16
    Matt Brailsford
    0

    Hey Matt,

    Cool. Not too far off for a bit of pseudo coding 😁

    Yea, I wasn't sure the best course of action with disabling the form as like you say, I don't know how people will integrate the code, but it probably is a good idea to add some kind of disabling.

    Glad we were able to find you a viable solution though and glad this resovled your issue.

    Matt

Please Sign in or register to post replies

Write your reply to:

Draft