Commerce with GlobalPayments handle declined cards

I have created a Payment Provider for GlobalPayments and it works fine.

However, if the user enters a card which is then declined, I would like to return the user to a page from where they can then return to the payment page, with the current basket still intact.

I have “FinalizeAtContinueUrl” = true.

In “ProcessCallbackAsync” I return a status of Cancelled. I get to the required page but the basket is now empty.

Below is the basics of “ProcessCallback”

    public override Task<CallbackResult> ProcessCallbackAsync(PaymentProviderContext<GlobalPaymentsSettings> context, CancellationToken cancellationToken = default(CancellationToken))
    {
        try
        {
            var responseCode = response.ResponseCode; // 00

            if (responseCode == "00")
            {
                return Task.FromResult<CallbackResult>(new CallbackResult
                {
                    TransactionInfo = new TransactionInfo
                    {
                        AmountAuthorized = (decimal)context.Order.TransactionAmount.Value,
                        TransactionFee = 0M,
                        TransactionId = response.OrderId,
                        PaymentStatus = PaymentStatus.Captured
                    },
                    MetaData = metaData
                });
            }


            return Task.FromResult<CallbackResult>(new CallbackResult
            {
                TransactionInfo = new TransactionInfo
                {
                    TransactionId = response.OrderId,
                    PaymentStatus = PaymentStatus.Cancelled
                }
            });
        }
        catch (ApiException ex)
        {
            return Task.FromResult(CallbackResult.BadRequest());
        }
    }

After return from GlobalPayments (and having gone through “processCallback”), using “GetCurrentOrder(storeId)” does not return the current order as required, so the basket is then empty.

How can I handle a declined card response from GlobalPayments and then return to the checkout flow with the current basket (Commerce Order) still active?

A finalized order is terminal, and returning transaction info with a payment status signals to Umbraco Commerce that the order is in an final state and can’t go any further. If your order is recoverable then don’t return transaction info and instead have it redirect back to your error page for them to try again.

In this instance, you are using the Cancelled status incorrectly. A payment status of Cancelled really signifies a previously Authorized payment has now been Cancelled. It’s not to signal that the checkout process was cancelled.

Hi Matt,

how do I redirect, from within the “ProcessCallbackAsync” call? Do I return a Redirect somehow or “just” redirect?

The CallbackResult object has a HttpResponse property you can set to a HttpResponseMessage. Give that response message a 302 status code and set its location header to make it redirect.

I tried this and it did redirect (although it lost the “id” parameter) but the current order is still empty

            string? paymentErrorPage = _siteHelper.GetCheckoutErrorPage()?.Url(mode:UrlMode.Absolute);

            var url = $"{paymentErrorPage}?id={context.Order.Id.ToString()}";
            var resp = new HttpResponseMessage(HttpStatusCode.Found);
            resp.Headers.Location = new Uri(url);

            return Task.FromResult <CallbackResult>(new CallbackResult
            {
                HttpResponse = resp
            });

Then, using

        var order = _sessionManager.GetCurrentOrder(store.Id);

returns an empty order. What am I doing wrong? :thinking:

Hmm, then it sounds like that might be a bug. In which case it’ll need raising in the issue tracker GitHub · Where software is built