November Happy Hour will be moved to Thursday December 5th.

Serialized payment issue

Vote:
 

I am trying to implement a custom payment type for Serialized carts, it is failing when we convert the cart to a purchase order. Below are the different ways I tried out but I am still having the same issue.

Attempt 1: Inheriting from Payment (I opened the reflector and I see payment types like CashCardPayment, CreditCardPayment etc are doing the same) 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using EPiServer.Commerce.Order;
using Mediachase.Commerce.Orders;
using Mediachase.MetaDataPlus.Configurator;

namespace Whereoware.Commerce.Payments
{
    [Serializable]
    public class TermsPayment : Payment
    {
        private static MetaClass _MetaClass;

        /// <summary>
        /// Gets the credit card payment meta class.
        /// </summary>
        /// <value>The credit card payment meta class.</value>
        public static MetaClass TermsPaymentMetaClass => _MetaClass ?? (_MetaClass = MetaClass.Load(OrderContext.MetaDataContext, "TermsPayment"));
        public TermsPayment() : base(TermsPayment.TermsPaymentMetaClass)
        {
            this.PaymentType = PaymentType.Other;
            this.ImplementationClass = GetType().AssemblyQualifiedName;
        }
        public TermsPayment(MetaClass metaClass) : base(metaClass)
        {
            this.PaymentType = PaymentType.Other;
            this.ImplementationClass = GetType().AssemblyQualifiedName;
        }
        protected TermsPayment(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            this.PaymentType = PaymentType.Other;
            this.ImplementationClass = GetType().AssemblyQualifiedName;
        }

        public string TermsOption
        {
            get
            {
                return this.GetString("TermsOption");
            }
            set
            {
                this["TermsOption"] = (object)value;
            }
        }
        public string PurchaseOrderNumber
        {
            get
            {
                return this.GetString("PurchaseOrderNumber");
            }
            set
            {
                this["PurchaseOrderNumber"] = (object)value;
            }
        }
    }
}

I also have a TermsPaymentMethod class that implements PaymentMethodBase which is an abstract class.

using System;
using EPiServer.Commerce.Order;
using EPiServer.ServiceLocation;
using Mediachase.Commerce.Orders.Managers;

namespace CustomPayment.Commerce.PaymentMethods
{
    [ServiceConfiguration(typeof(IPaymentMethod))]
    public class TermsPaymentMethod : PaymentMethodBase
    {
        public override string SystemKeyword => "Manual";
        protected readonly Guid _paymentMethodId;

        public TermsPaymentMethod(Guid paymentMethodId) : this(ServiceLocator.Current.GetInstance<IOrderGroupFactory>(), paymentMethodId)
        {
            _paymentMethodId = paymentMethodId;
        }

        public TermsPaymentMethod(IOrderGroupFactory orderGroupFactory, Guid paymentMethodId) : base(orderGroupFactory, paymentMethodId)
        {
        }

        public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
        {
            string implementationClassName = PaymentManager.GetPaymentMethod(base.PaymentMethodId, false).PaymentMethod[0].PaymentImplementationClassName;
            var type = Type.GetType(implementationClassName);
            var payment = type == null ? orderGroup.CreatePayment(OrderGroupFactory) : orderGroup.CreatePayment(OrderGroupFactory, type);

            payment.PaymentMethodId = PaymentMethodId;
            payment.PaymentMethodName = Name;
            payment.Amount = amount;
            payment.PaymentType = Mediachase.Commerce.Orders.PaymentType.Other;

            return payment;
        }

        public override bool ValidateData()
        {
            return true;
        }
    }
}

I then create a Payment type inside my checkout controller and use the CreatePayment(decimal amount, IOrderGroup orderGroup) of IPaymentMethod which in my case triggers TermsPaymentMethod implementation.

I can see that my implementation class is now TermsPayment as well in the payment object. But it fails when I convert the cart to a purchase order.

https://www.screencast.com/t/y0czxqCnP5m

Below is the stack trace:

Activation error occurred while trying to get instance of type, key "TermsPayment"

at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key)
at EPiServer.Commerce.Order.Internal.OrderGroupBuilder.CreatePayment(Type paymentType)
at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyPayments(IOrderForm sourceForm, IOrderForm destinationForm, IOrderGroup destinationOrderGroup, IOrderGroupFactory orderGroupFactory)
at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyOrderForm(IOrderGroup orderGroup, IOrderGroup sourceOrderGroup, IOrderGroupFactory orderGroupFactory)
at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyFrom(IOrderGroup orderGroup, IOrderGroup sourceOrderGroup, IOrderGroupFactory orderGroupFactory)
at EPiServer.Commerce.Order.Internal.SerializableCartProvider.SaveAsPurchaseOrder(ICart cart)
at EPiServer.Commerce.Order.Internal.DefaultOrderRepository.SaveAsPurchaseOrder(IOrderGroup cart)

Upon checking the reflector I see that Epi tries to create a payment type :

public IPayment CreatePayment(Type paymentType)
{
   return ServiceLocator.Current.GetInstance(paymentType) as IPayment;
}

It is unable to convert it to IPayment  and gives a null exception.

Attempt 2:

Everything is the same as the previous attempt other than the payment type implementation

using System.Collections;
using EPiServer.Commerce.Order;
using EPiServer.Commerce.Order.Internal;
using EPiServer.Commerce.Storage;
using Mediachase.Commerce.Orders;
using Newtonsoft.Json;

namespace Whereoware.Commerce.Payments
{
    [JsonConverter(typeof(PaymentConverter))]
    public class TermsPayment : SerializablePayment, IPayment, IExtendedProperties
    {
        public TermsPayment()
        {
            this.Properties = new Hashtable();
            this.BillingAddress = (IOrderAddress)new SerializableOrderAddress();
            this.PaymentType = PaymentType.Other;
            this.ImplementationClass = GetType().AssemblyQualifiedName;
        }
    }
}

I refferd to the article below for the second attempt:

https://world.episerver.com/forum/developer-forum/Episerver-Commerce/Thread-Container/2017/10/how-to-make-serializable-carts-to-work-with-icreditcardpayment-authorize-net/

But still have the same issue when it converts it to a purchase order:

"Value cannot be null"

   at Mediachase.Commerce.Storage.MetaStorageObservableCollection`2.OnListChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
   at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyPayments(IOrderForm sourceForm, IOrderForm destinationForm, IOrderGroup destinationOrderGroup, IOrderGroupFactory orderGroupFactory)
   at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyOrderForm(IOrderGroup orderGroup, IOrderGroup sourceOrderGroup, IOrderGroupFactory orderGroupFactory)
   at EPiServer.Commerce.Order.IOrderGroupExtensions.CopyFrom(IOrderGroup orderGroup, IOrderGroup sourceOrderGroup, IOrderGroupFactory orderGroupFactory)
   at EPiServer.Commerce.Order.Internal.SerializableCartProvider.SaveAsPurchaseOrder(ICart cart)
   at EPiServer.Commerce.Order.Internal.DefaultOrderRepository.SaveAsPurchaseOrder(IOrderGroup cart)

I am also attaching a screen shot of the PaymentMethod table:

https://www.screencast.com/t/tRdQ5xs4

I have been struggling on this since 2 days now, any help or leads on this is highly appreciated.

#205873
Edited, Jul 26, 2019 0:56
Vote:
 

Regarding your first attempt, 

Activation error occurred while trying to get instance of type, key "TermsPayment"

is not caused by 

It is unable to convert it to IPayment  and gives a null exception.

The problem is your default constructor (as seen by structuremap) is public TermsPayment(MetaClass metaClass), and it does not know how to create an instance of MetaClass.

#205878
Jul 26, 2019 9:06
Siddharth Gupta - Jul 26, 2019 16:16
Thanks Quan!
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.