London Dev Meetup Rescheduled! Due to unavoidable reasons, the event has been moved to 21st May. Speakers remain the same—any changes will be communicated. Seats are limited—register here to secure your spot!

Serialized payment issue

Vote:
0

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:
0

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.