Search code examples
payment-gatewaykentico

Custom payment form - unable to set order status to Authorized without setting PaymentIsCompleted to true


We are attempting to use the checkout process in Kentico to authorize a credit card payment rather than charge it. To that effect, once the checkout process has been completed by the user, we want the order's status to be something like 'Payment authorized', and its OrderIsPaid property set to false.

In the payment form, in my override of ProcessPayment(), I do not set base.PaymentResult.PaymentIsCompleted to true, because I am only trying to authorize a payment, rather than actually charge the card. (Again, that is our desired result for the checkout process: Credit card authorization; not actually charging the card.) At some point after this function call, the order's status is set to payment failure (specified in store configuration -> payment gateway).

The outcome I had hoped for was to have the order's status be set to authorized (which I specified in store configuration) and the order's OrderIsPaid field be set to false.

Based on the observed behavior, I assume the success of the payment process depends upon setting PaymentIsCompleted to true. I also am assuming that property is the same as OrderInfo.OrderIsPaid.

Does anyone know how I can achieve the desired state?

----------------- UPDATE: Solution based on Trevor's answer --------

I didn't set OrderCustomData during the payment process. Rather, I base my decision to override status on current status, whether PaymentStatusValue=='complete', and whether we've overridden the status for this particular PaymentTransactionID before.

void UpdateAfter(object sender, ObjectEventArgs e)
{
    try
    {
        if (e.Object.TypeInfo.ObjectClassName.ToLower().Equals("ecommerce.order"))
        {
            /* 
             * The standard payment process behavior will not allow us to set 'IsPaid' to false without forcing the order status to the failed state.
             * (As specified in the Store Configuration app under payment methods -> edit payment method -> [Order status if payment fails:])
             * The workaround is to configure the payment methods failure status to be 'Authorization Failed' (internal name 'PaymentFailed'),
             * to set IsPaid=false in the payment code, and to use this event handler to override the status failed status based on .
            */

            OrderInfo order = (OrderInfo)e.Object;
            if (order == null)
                throw new ArgumentNullException("Order object of ecommerce.order event is null.");

            // If a payment hasn't been made, then there's nothing to override.
            if (order.OrderPaymentResult == null)
                return;

            // We will log the payment's transaction ID in ordercustomdata so next time we don't override 
            // order status unless the payment transaction ID has changed.
            string successfulPaymentTransactionId = null;
            try
            {
                successfulPaymentTransactionId = (string)order.OrderCustomData.GetValue("SuccessfulPaymentTransactionID");
            }
            catch 
            { }

            // Do not override status on an order that has the same payment transaction ID as when we last overrode it.
            if (successfulPaymentTransactionId != null && successfulPaymentTransactionId == order.OrderPaymentResult.PaymentTransactionID)
                return;

            PaymentOptionInfo paymentOption = PaymentOptionInfoProvider.GetPaymentOptionInfo(order.OrderPaymentOptionID);
            if (paymentOption == null)
                throw new ArgumentNullException(String.Format("Payment option for OrderPaymentOptionID '{0}' is null.", order.OrderPaymentOptionID));

            // Override status if: status is failed but payment status is 'completed'.
            // avoid overriding manually; Only override if we haven't yet overriden status based on the currrent payment transaction ID.
            if (order.OrderStatusID == paymentOption.PaymentOptionFailedOrderStatusID 
                && order.OrderPaymentResult.PaymentStatusValue == "completed" 
                && (successfulPaymentTransactionId == null || successfulPaymentTransactionId != order.OrderPaymentResult.PaymentTransactionID))
            {
                order.OrderStatusID = paymentOption.PaymentOptionSucceededOrderStatusID;
                order.OrderCustomData["SuccessfulPaymentTransactionID"] = order.OrderPaymentResult.PaymentTransactionID;
                order.Update();
            }
        }
    }
    catch (Exception ex)
    {
        EventLogProvider.LogException("EcommerceOrderInfoModule", "UpdateAfter", ex, 0, null, null);
    }
}

Solution

  • You are correct that the "PaymentIsComplete" will trigger either a Paid or UnPaid status. In Kentico though for your Payment, you can set what "Order Status" is applied for a payment failure and payment success.

    Store Configuration -> Payment Methods -> (Edit yours) -> Set the "Order status if payment fails" to "Payment Authorized" (may need to create it)

    Now granted, this may not be ideal because really you have a 3 state item here, Complete Failure, Success but Not Paid, and Paid. So you may need to update the Order with custom data, then use an event hook to catch the order status change and check this value.

    // If authorized, then set this to true, set paymentiscomplete to false regardless
    Order.OrderCustomData.SetValue("Authorized", true);
    PaymentResult.PaymentIsCompleted = false;
    

    And in a InitializationModule

    protected override void OnInit()
    {
        base.OnInit();
        CMS.DataEngine.ObjectEvents.Update.After += Update_After;
    }
    
    void Update_After(object sender, ObjectEventArgs e)
    {
        try { 
        switch (e.Object.TypeInfo.ObjectClassName.ToLower())
        {
            case "ecommerce.order":
                OrderInfo theOrderObj = (OrderInfo)e.Object;
    
                bool hasAuthorizedStatus = CMS.Helpers.ValidationHelper.GetBoolean(theOrderObj.OrderCustomData.GetValue("Authorized"), false);
                if (hasAuthorizedStatus && theOrderObj.OrderStatusID == OrderStatusInfoProvider.GetOrderStatusInfo("ThePaymentFailureStatus", SiteContext.CurrentSiteName).StatusID)
                {
                    theOrderObj.OrderStatusID = OrderStatusInfoProvider.GetOrderStatusInfo("PaymentAuthorized", SiteContext.CurrentSiteName).StatusID;
                    theOrderObj.Update();
                }
                break;
        }
        }
        catch (Exception ex)
        {
            CMS.EventLog.EventLogProvider.LogException("CustomInitializationModule", "UpdateAfterError", ex);
        }
    }