• Posted By: Lee Messenger
  • Tagged: PaymentProviders

If you have been to one of our Merchello workshops, you will have learnt about Gateway Providers.

Merchello has four different types of Gateway Providers, these are Notification, Payment, Shipping and Taxation. Merchello uses “Gateway Providers” to interact with various services and APIs, most often dealing with integrations with applications outside of the Merchello Core.

In this blog post we are going to cover Payment Providers, and create a Purchase Order payment provider and then create the UI for the checkout and integrate it into our FastTrack starter kit.

The provider is just the code to get Merchello to recognise it, and also any custom configuration/settings you may need. Once you have completed the provider, you then need to implement a way to interact with it based on how your solution is setup.

Part 1 - The Provider

The Provider itself, is broken down into a minimum of two main classes. The Gateway Provider and one or more gateway method(s). The provider class is the one that registers your provider with Merchello and points Merchello to which Gateway Methods to use.

The Gateway Method class, as the name says, is all the actual methods that your Provider can use to process the Payment. i.e. PerformAuthorizePayment, PerformAuthorizeCapturePayment etc...

solution-tree

First thing I need to do is create a couple of marker Interfaces. These are just used as a way of getting/differentiating your provider and method classes via reflection if needed.

public interface IPurchaseOrderPaymentGatewayMethod { }
  
public interface IPurchaseOrderPaymentGatewayProvider { }

PurchaseOrderPaymentGatewayProvider

Now we need to create the PurchaseOrderPaymentGatewayProvider class, which will implement the marker interface and also inherit from PaymentGatewayProviderBase which will allow us to override the base methods we need to use in our provider.

We also need to use the [GatewayProviderActivation] attribute, which tells Merchello this class is the one to use for our Payment Provider. The GatewayProviderActivation attribute takes three values which are key, name and description. The Key is a Guid, and just like Umbraco property editors needs to be unique and not match any other provider.

Our attribute will look like this:

[GatewayProviderActivation("f8fa58d9-2c0d-4e07-aaac-e0f86f5b622e", "Purchase Order Payment Provider", "Purchase Order Payment Provider")]

Below is the complete class, where you can see the methods I have overridden so I can use our PurchaseOrderPaymentGatewayMethod class to process them. The next section will cover creating the PurchaseOrderPaymentGatewayMethod class.

namespace Merchello.Providers.Payment.PurchaseOrder.Provider
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Core.Gateways;
    using Core.Gateways.Payment;
    using Core.Models;
    using Core.Services;
    using Umbraco.Core.Cache;
    using Umbraco.Core.Logging;

    /// <summary>
    /// The purchase order payment gateway provider.
    /// </summary>
    [GatewayProviderActivation("f8fa58d9-2c0d-4e07-aaac-e0f86f5b622e", "Purchase Order Payment Provider", "Purchase Order Payment Provider")] 
    public class PurchaseOrderPaymentGatewayProvider : PaymentGatewayProviderBase, IPurchaseOrderPaymentGatewayProvider
    {
        #region AvailableResources

        /// <summary>
        /// The available resources.
        /// </summary>
        internal static readonly IEnumerable<IGatewayResource> AvailableResources = new List<IGatewayResource>
        {
            new GatewayResource("PurchaseOrder", "Purchase Order")
        };

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="PurchaseOrderPaymentGatewayProvider"/> class.
        /// </summary>
        /// <param name="gatewayProviderService">
        /// The gateway provider service.
        /// </param>
        /// <param name="gatewayProviderSettings">
        /// The gateway provider settings.
        /// </param>
        /// <param name="runtimeCacheProvider">
        /// The runtime cache provider.
        /// </param>
        public PurchaseOrderPaymentGatewayProvider(IGatewayProviderService gatewayProviderService, 
            IGatewayProviderSettings gatewayProviderSettings, 
            IRuntimeCacheProvider runtimeCacheProvider) 
            : base(gatewayProviderService, gatewayProviderSettings, runtimeCacheProvider)
        {
        }

        /// <summary>
        /// Creates a <see cref="IPaymentGatewayMethod"/>
        /// </summary>
        /// <param name="name">The name of the payment method</param>
        /// <param name="description">The description of the payment method</param>
        /// <returns>A <see cref="IPaymentGatewayMethod"/></returns>
        public IPaymentGatewayMethod CreatePaymentMethod(string name, string description)
        {
            return CreatePaymentMethod(AvailableResources.First(), name, description);
        }

        /// <summary>
        /// Creates a <see cref="IPaymentGatewayMethod"/>
        /// </summary>
        /// <param name="gatewayResource">
        /// The gateway Resource.
        /// </param>
        /// <param name="name">
        /// The name of the payment method
        /// </param>
        /// <param name="description">
        /// The description of the payment method
        /// </param>
        /// <returns>
        /// A <see cref="IPaymentGatewayMethod"/>
        /// </returns>
        public override IPaymentGatewayMethod CreatePaymentMethod(IGatewayResource gatewayResource, string name, string description)
        {
            var paymentCode = gatewayResource.ServiceCode + "-" + Guid.NewGuid();

            var attempt = GatewayProviderService.CreatePaymentMethodWithKey(GatewayProviderSettings.Key, name, description, paymentCode);

            if (attempt.Success)
            {
                PaymentMethods = null;

                return new PurchaseOrderPaymentGatewayMethod(GatewayProviderService, attempt.Result);
            }

            LogHelper.Error<PurchaseOrderPaymentGatewayProvider>(string.Format("Failed to create a payment method name: {0}, description {1}, paymentCode {2}", name, description, paymentCode), attempt.Exception);

            throw attempt.Exception;
        }

        /// <summary>
        /// Gets a <see cref="IPaymentGatewayMethod"/> by it's unique 'key'
        /// </summary>
        /// <param name="paymentMethodKey">The key of the <see cref="IPaymentMethod"/></param>
        /// <returns>A <see cref="IPaymentGatewayMethod"/></returns>
        public override IPaymentGatewayMethod GetPaymentGatewayMethodByKey(Guid paymentMethodKey)
        {
            var paymentMethod = PaymentMethods.FirstOrDefault(x => x.Key == paymentMethodKey);

            if (paymentMethod == null) throw new NullReferenceException("PaymentMethod not found");

            return new PurchaseOrderPaymentGatewayMethod(GatewayProviderService, paymentMethod);

        }

        /// <summary>
        /// Gets a <see cref="IPaymentGatewayMethod"/> by it's payment code
        /// </summary>
        /// <param name="paymentCode">The payment code of the <see cref="IPaymentGatewayMethod"/></param>
        /// <returns>A <see cref="IPaymentGatewayMethod"/></returns>
        public override IPaymentGatewayMethod GetPaymentGatewayMethodByPaymentCode(string paymentCode)
        {
            var paymentMethod = PaymentMethods.FirstOrDefault(x => x.PaymentCode == paymentCode);

            if (paymentMethod == null) throw new NullReferenceException("PaymentMethod not found");

            return new PurchaseOrderPaymentGatewayMethod(GatewayProviderService, paymentMethod);
        }

        /// <summary>
        /// Returns a list of remaining available resources
        /// </summary>
        /// <returns>
        /// The collection of <see cref="IGatewayResource"/>.
        /// </returns>
        public override IEnumerable<IGatewayResource> ListResourcesOffered()
        {
            return AvailableResources;
        }

    }
}

PurchaseOrderPaymentGatewayMethod

Just like we did for the Provider class above, we need to implement our marker interface and now inherit from PaymentGatewayMethodBase which again will allow us to override the methods we want to use. We also have an attribute that we need to to use on the class called GatewayMethodUi.

This attribute is just a key that we can use in the UI so during checkout we know which payment provider the user has chosen to pay with - We’ll use ‘PurchaseOrder.PurchaseOrder’ as the key.

Below is the full PaymentGatewayMethodBase class, and you will see we have overridden several methods. The code in each of the methods is actually taken from the Cash payment provider as both the Purchase Order and Cash providers are similar. The only difference, is that we want the user to enter a Purchase Order number during checkout, and it get saved onto the invoice so we can see it in the back office.

namespace Merchello.Providers.Payment.PurchaseOrder.Provider
{
    using System;
    using System.Linq;
    using Core;
    using Core.Gateways;
    using Core.Gateways.Payment;
    using Core.Models;
    using Core.Services;
    using Umbraco.Core;

    /// <summary>
    /// Represents an PurchaseOrder Payment Method
    /// </summary>
    [GatewayMethodUi("PurchaseOrder.PurchaseOrder")]
    public class PurchaseOrderPaymentGatewayMethod : PaymentGatewayMethodBase, IPurchaseOrderPaymentGatewayMethod
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PurchaseOrderPaymentGatewayMethod"/> class.
        /// </summary>
        /// <param name="gatewayProviderService">
        /// The gateway provider service.
        /// </param>
        /// <param name="paymentMethod">
        /// The payment method.
        /// </param>
        public PurchaseOrderPaymentGatewayMethod(IGatewayProviderService gatewayProviderService, IPaymentMethod paymentMethod)
            : base(gatewayProviderService, paymentMethod)
        {
        }

        /// <summary>
        /// Does the actual work of creating and processing the payment
        /// </summary>
        /// <param name="invoice">The <see cref="IInvoice"/></param>
        /// <param name="args">Any arguments required to process the payment.</param>
        /// <returns>The <see cref="IPaymentResult"/></returns>
        protected override IPaymentResult PerformAuthorizePayment(IInvoice invoice, ProcessorArgumentCollection args)
        {
            var po = args.AsPurchaseOrderFormData();

            var payment = GatewayProviderService.CreatePayment(PaymentMethodType.PurchaseOrder, invoice.Total, PaymentMethod.Key);
            payment.CustomerKey = invoice.CustomerKey;
            payment.PaymentMethodName = PaymentMethod.Name;
            payment.ReferenceNumber = PaymentMethod.PaymentCode + "-" + invoice.PrefixedInvoiceNumber();
            payment.Collected = false;
            payment.Authorized = true;

            if (string.IsNullOrEmpty(po.PurchaseOrderNumber))
            {
                return new PaymentResult(Attempt<IPayment>.Fail(payment, new Exception("Error Purchase Order Number is empty")), invoice, false);
            }

            invoice.PoNumber = po.PurchaseOrderNumber;
            MerchelloContext.Current.Services.InvoiceService.Save(invoice);

            GatewayProviderService.Save(payment);

            // In this case, we want to do our own Apply Payment operation as the amount has not been collected -
            // so we create an applied payment with a 0 amount.  Once the payment has been "collected", another Applied Payment record will
            // be created showing the full amount and the invoice status will be set to Paid.
            GatewayProviderService.ApplyPaymentToInvoice(payment.Key, invoice.Key, AppliedPaymentType.Debit, string.Format("To show promise of a {0} payment", PaymentMethod.Name), 0);

            //// If this were using a service we might want to store some of the transaction data in the ExtendedData for record
            ////payment.ExtendData

            return new PaymentResult(Attempt.Succeed(payment), invoice, false);
        }

        /// <summary>
        /// Does the actual work of authorizing and capturing a payment
        /// </summary>
        /// <param name="invoice">The <see cref="IInvoice"/></param>
        /// <param name="amount">The amount to capture</param>
        /// <param name="args">Any arguments required to process the payment.</param>
        /// <returns>The <see cref="IPaymentResult"/></returns>
        protected override IPaymentResult PerformAuthorizeCapturePayment(IInvoice invoice, decimal amount, ProcessorArgumentCollection args)
        {
            var payment = GatewayProviderService.CreatePayment(PaymentMethodType.PurchaseOrder, amount, PaymentMethod.Key);
            payment.CustomerKey = invoice.CustomerKey;
            payment.PaymentMethodName = PaymentMethod.Name;
            payment.ReferenceNumber = PaymentMethod.PaymentCode + "-" + invoice.PrefixedInvoiceNumber();
            payment.Collected = true;
            payment.Authorized = true;

            var po = args.AsPurchaseOrderFormData();

            if (string.IsNullOrEmpty(po.PurchaseOrderNumber))
            {
                return new PaymentResult(Attempt<IPayment>.Fail(payment, new Exception("Error Purchase Order Number is empty")), invoice, false);
            }

            invoice.PoNumber = po.PurchaseOrderNumber;
            MerchelloContext.Current.Services.InvoiceService.Save(invoice);

            GatewayProviderService.Save(payment);

            GatewayProviderService.ApplyPaymentToInvoice(payment.Key, invoice.Key, AppliedPaymentType.Debit, "Cash payment", amount);

            return new PaymentResult(Attempt<IPayment>.Succeed(payment), invoice, CalculateTotalOwed(invoice).CompareTo(amount) <= 0);
        }

        /// <summary>
        /// Does the actual work capturing a payment
        /// </summary>
        /// <param name="invoice">The <see cref="IInvoice"/></param>
        /// <param name="payment">The previously Authorize payment to be captured</param>
        /// <param name="amount">The amount to capture</param>
        /// <param name="args">Any arguments required to process the payment.</param>
        /// <returns>The <see cref="IPaymentResult"/></returns>
        protected override IPaymentResult PerformCapturePayment(IInvoice invoice, IPayment payment, decimal amount, ProcessorArgumentCollection args)
        {
            // We need to determine if the entire amount authorized has been collected before marking
            // the payment collected.
            var appliedPayments = GatewayProviderService.GetAppliedPaymentsByPaymentKey(payment.Key);
            var applied = appliedPayments.Sum(x => x.Amount);

            payment.Collected = (amount + applied) == payment.Amount;
            payment.Authorized = true;

            GatewayProviderService.Save(payment);

            GatewayProviderService.ApplyPaymentToInvoice(payment.Key, invoice.Key, AppliedPaymentType.Debit, "Purchase Order Payment", amount);

            return new PaymentResult(Attempt<IPayment>.Succeed(payment), invoice, CalculateTotalOwed(invoice).CompareTo(amount) <= 0);
        }

        /// <summary>
        /// Does the actual work of refunding a payment
        /// </summary>
        /// <param name="invoice">The <see cref="IInvoice"/></param>
        /// <param name="payment">The previously Authorize payment to be captured</param>
        /// <param name="amount">The amount to be refunded</param>
        /// <param name="args">Any arguments required to process the payment.</param>
        /// <returns>The <see cref="IPaymentResult"/></returns>
        protected override IPaymentResult PerformRefundPayment(IInvoice invoice, IPayment payment, decimal amount, ProcessorArgumentCollection args)
        {
            foreach (var applied in payment.AppliedPayments())
            {
                applied.TransactionType = AppliedPaymentType.Refund;
                applied.Amount = 0;
                applied.Description += " - Refunded";
                GatewayProviderService.Save(applied);
            }

            payment.Amount = payment.Amount - amount;

            if (payment.Amount != 0)
            {
                GatewayProviderService.ApplyPaymentToInvoice(payment.Key, invoice.Key, AppliedPaymentType.Debit, "To show partial payment remaining after refund", payment.Amount);
            }

            GatewayProviderService.Save(payment);

            return new PaymentResult(Attempt<IPayment>.Succeed(payment), invoice, false);
        }
    }
}

I won’t run through all the code, as if you have a look through it’s ‘fairly’ self explanatory and there are comments in the code explaining what is happening. The only part I want to point out, is in the Authorize payment methods.

Each method has a class in the signature called ‘ProcessorArgumentCollection’ this is actually just a ‘Dictionary<string, string>’ and is how custom payment providers pass custom data into the providers and then we (The implementers) can use it however we need to.

In our case, we have used this to store our Purchase Order number/key that the user has entered. I have a couple of extra helper classes below, which help deal with the getting/retrieving Purchase Order number/key from the ‘ProcessorArgumentCollection’ - As you can see they are very simple.

PurchaseOrderConstants - This is just a constant file to the DictionaryKey so we can set and get the PO out of the Dictionary.

   public static class PurchaseOrderConstants
    {
        public static string PoStringKey = "purchaseOrderNumber";
    }

PurchaseOrderFormData - Just a little POCO model that we use to store the PO number in when we get it back out the ‘ProcessorArgumentCollection’.

    public class PurchaseOrderFormData
    {        
        /// <summary>
        /// The Purchase Orders Invoice Number.  
        /// </summary>
        public string PurchaseOrderNumber { get; set; }
    }

PurchaseOrderInfoExtensions - Just a couple of extension methods to help with getting the PO number from the ‘ProcessorArgumentCollection’ and returning our model above.

    public static class PurchaseOrderInfoExtensions
    {

        public static PurchaseOrderFormData AsPurchaseOrderFormData(this ProcessorArgumentCollection args)
        {
            return new PurchaseOrderFormData
            {
                PurchaseOrderNumber = args.ArgValue(PurchaseOrderConstants.PoStringKey)
            };
        }

        private static string ArgValue(this ProcessorArgumentCollection args, string key)
        {
            return args.ContainsKey(key) ? args[key] : string.Empty;
        }

    }

The main difference between our provider and the Core cash provider are these lines of code in the Perform...Payment methods.

var po = args.AsPurchaseOrderFormData();

 if (string.IsNullOrEmpty(po.PurchaseOrderNumber))
 {
    return new PaymentResult(Attempt<IPayment>.Fail(payment, new Exception("Error Purchase Order Number is empty")), invoice, false);
}

invoice.PoNumber = po.PurchaseOrderNumber;
MerchelloContext.Current.Services.InvoiceService.Save(invoice);

As you can see, all we do is use the extension method to pull out the Purchase Order number from the ProcessorArgumentCollection. Then check if it’s null or empty, and if so return a failed PaymentResult.

If we do have a value, then we simple update the supplied invoice with the new PONumber and then save the invoice.

This completes the actual provider implementation, and we can now enable the provider in Merchello. However, we will still need to create the customer UI to interact with our provider. Something we’ll cover in our next section.

To enable the new Payment Provider, go to the Gateway Providers in Merchello and in the list of providers you’ll see the Purchase Order Provider listed. Firstly we just need to enable it. So click on the lightbulb and the provider will be enabled.

enable-po

Once enabled, we now need to register with Merchello the actual Payment Method. Although our provider only has a single method. Some providers can have multiple, for example the Braintree provider has four different methods to choose from.

Go to the Payment tab, and click the ‘Add Method’ under the ‘Purchase Order Payment Provider’ section. You’ll see you only have the one method, select it and click ‘Add’. The side Dialog will open, just click save and now the provider is active.

payment-method

Let’s move onto the UI for the customer.

Part 2 - Checkout UI

I’m going to use the FastTrack checkout in this post, as all the standard checkout logic is there. So I just need to add on the Purchase Order part. If you are starting out with Merchello I would highly recommend using the FastTrack to start you off. As you will see in this section, it will make adding things like custom payment providers much easier and quicker to do.

For the FastTrack, as it’s all just standard MVC code (ActionResults etc...). First we need to create a controller, where we’ll have three ActionResults to render the UI for the PurchaseOrder (it will just be a textbox), handle the form post during the checkout for the PurchaseOrder provider and also redirect the user to the next stage of the checkout.

This will be done with a normal Controller, but, to simplify routing,  we can to inherit from the CheckoutPaymentControllerBase<T>. In order to do this, we have to have a custom model for the base controller to use. As we are using the FastTrack we have a ‘FastTrackPaymentModel’ that we can inherit from which holds other information which is essential during checkout.

And all our model needs to be is a string property to hold the Purchase Order Number in. We will add a DisplayName and Required attribute too, so the validation will work in the front end (Again, this is just standard ASP.NET MVC).

namespace Merchello.FastTrack.Models.Payment
{
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public class PurchaseOrderPaymentModel : FastTrackPaymentModel
    {
        /// <summary>
        /// Gets or sets the PO number.
        /// </summary>
        [Required]
        [DisplayName(@"Purchase Order Number")]
        public string PurchaseOrderNumber { get; set; }
    }
}

Now the we have our own Payment Model, we can create a controller and inherit from the base controller using the new model. We’re also going to use the [GatewayMethodUi("PurchaseOrder.PurchaseOrder")] attribute again on this class and going to also use the Umbraco PluginController attribute like so [PluginController("FastTrack")].

Below is the full controller, which contains the three action results mentioned at the beginning of this section.

namespace Merchello.FastTrack.Controllers.Payment
{
    using System.Web.Mvc;
    using Core.Gateways;
    using Models.Payment;
    using Umbraco.Core;
    using Web.Controllers;
    using System;
    using Core.Gateways.Payment;
    using Providers.Payment.PurchaseOrder;
    using Umbraco.Web.Mvc;
    using Web.Models.Ui;

    [PluginController("FastTrack")]
    [GatewayMethodUi("PurchaseOrder.PurchaseOrder")]
    public class PurchaseOrderPaymentController : CheckoutPaymentControllerBase<PurchaseOrderPaymentModel>
    {
        /// <summary>
        /// Handles the redirection for the receipt.
        /// </summary>
        /// <param name="model">
        /// The <see cref="FastTrackPaymentModel"/>.
        /// </param>
        /// <returns>
        /// The <see cref="ActionResult"/>.
        /// </returns>
        protected override ActionResult HandlePaymentSuccess(PurchaseOrderPaymentModel model)
        {
            // Set the invoice key in the customer context (cookie)
            if (model.ViewData.Success)
            {
                CustomerContext.SetValue("invoiceKey", model.ViewData.InvoiceKey.ToString());
            }

            return model.ViewData.Success && !model.SuccessRedirectUrl.IsNullOrWhiteSpace() ?
                Redirect(model.SuccessRedirectUrl) :
                base.HandlePaymentSuccess(model);
        }

        /// <summary>
        /// Processes the PO payment.
        /// </summary>
        /// <param name="model">
        /// The <see cref="ICheckoutPaymentModel"/>.
        /// </param>
        /// <returns>
        /// The <see cref="ActionResult"/>.
        /// </returns>
        [HttpPost]
        public virtual ActionResult Process(PurchaseOrderPaymentModel model)
        {
            try
            {
                var paymentMethod = this.CheckoutManager.Payment.GetPaymentMethod();

                // Create the processor argument collection, where we'll pass in the purchase order
                var args = new ProcessorArgumentCollection
                                    {
                                        {PurchaseOrderConstants.PoStringKey, model.PurchaseOrderNumber}
                                    };

                // For PO payments we can only perform an authorize
                var attempt = this.CheckoutManager.Payment.AuthorizePayment(paymentMethod.Key, args);

                var resultModel = this.CheckoutPaymentModelFactory.Create(CurrentCustomer, paymentMethod, attempt);

                // merge the models so we can be assured that any hidden values are passed on
                model.ViewData = resultModel.ViewData;

                // Send the notification
                HandleNotificiation(model, attempt);

                return this.HandlePaymentSuccess(model);
            }
            catch (Exception ex)
            {
                return this.HandlePaymentException(model, ex);
            }
        }

        /// <summary>
        /// Renders the Purchase Order payment form.
        /// </summary>
        /// <param name="view">
        /// The optional view.
        /// </param>
        /// <returns>
        /// The <see cref="ActionResult"/>.
        /// </returns>
        [ChildActionOnly]
        [GatewayMethodUi("PurchaseOrder.PurchaseOrder")]
        public override ActionResult PaymentForm(string view = "")
        {
            var paymentMethod = this.CheckoutManager.Payment.GetPaymentMethod();
            if (paymentMethod == null) return this.InvalidCheckoutStagePartial();

            var model = this.CheckoutPaymentModelFactory.Create(CurrentCustomer, paymentMethod);

            return view.IsNullOrWhiteSpace() ? this.PartialView(model) : this.PartialView(view, model);
        }
    }
}

The key method you need to look at is the process one. Again, almost exactly the same as the Cash payment provider but in this implementation we want to capture the PO number and pass it to our Purchase Order Payment Provider we created in the previous section.

As the method takes our custom Payment Model (PurchaseOrderPaymentModel) we know we have the property PurchaseOrderNumber. And if you remember in the previous section, the provider methods pull the PurchaseOrder number out of the ‘ProcessorArgumentCollection’.

So all we need to do in the process method, is create a new ProcessorArgumentCollection. And pass in our PO number using the constant key from the previous section. Finally, we pass the ProcessorArgumentCollection into the AuthorizePayment(paymentMethod.Key, args) which will resolve to our PurchaseOrderPaymentMethod class.

// Create the processor argument collection, where we'll pass in the purchase order
var args = new ProcessorArgumentCollection
    {
       {PurchaseOrderConstants.PoStringKey, model.PurchaseOrderNumber}
    };

 // For PO payments we can only perform an authorize
var attempt = this.CheckoutManager.Payment.AuthorizePayment(paymentMethod.Key, args);

Now we have the Controller done, we need to create the PaymentForm. If you look at the last method in the controller we have a ChildActionOnly call and it returns a view. This will be our form that contains the textbox for the user to enter the PurchaseOrder number.

Again, as we are using FastTrack we’ll stick to the same locations for the payment views. In the following folder we’ll create the payment form view.

App_Plugins\FastTrack\Views\PurchaseOrderPayment\PaymentForm.cshml

Our PaymentForm uses our PurchaseOrderPaymentModel and is just a normal MVC view/form with a few standard FastTrack bits in it.

@inherits Umbraco.Web.Mvc.UmbracoViewPage<Merchello.FastTrack.Models.Payment.PurchaseOrderPaymentModel>
@using System.Web.Mvc.Html
@using Merchello.FastTrack.Controllers.Payment
@using Merchello.FastTrack.Ui
@using Merchello.Web.Models.Ui
@using Umbraco.Web
@{
    Model.SuccessRedirectUrl = ExampleUiHelper.Content.GetReceipt().Url;
}
<h3>Purchase Order Number</h3>
@using (Html.BeginUmbracoForm<PurchaseOrderPaymentController>("Process", new { area = "FastTrack" }))
{
    
        @Html.AntiForgeryToken()
        @Html.HiddenFor(x => x.SuccessRedirectUrl)

        <div class="form-group">        
            @Html.LabelFor(x => x.PurchaseOrderNumber)
            @Html.TextBoxFor(x => x.PurchaseOrderNumber, new { @class = "form-control" })
            @Html.ValidationMessageFor(x => x.PurchaseOrderNumber)
        </div>

        <a href="@ExampleUiHelper.CheckoutWorkflow.GetPageForStage(CheckoutStage.PaymentMethod).Url" class="btn btn-default">Back</a>
        @Html.Partial("_RequireJsSubmitBtn", Model)
    
}

In FastTrack when going through the checkout, it has a view with a switch to determine which provider payment form to show. We just need to update it, so when they choose purchase order our form is rendered.

Remember when we added the attribute [GatewayMethodUi("PurchaseOrder.PurchaseOrder")] to our classes. Well here is an example of how it’s used. In the FastTrack there is a view located at:

App_Plugins\FastTrack\Views\CheckoutPaymentMethod\ResolvePayment.cshtml

This view contains the simple switch statement, based on the value of the GateWayMethodUi attribute. We simply need to add our Action at the bottom like so (You can see the alias of PurchaseOrder.PurchaseOrder is used).

    switch (Model.Alias)
    {
        case "CashPaymentMethod":
            @Html.Action("PaymentForm", "CashPayment", new { area = "FastTrack" })
            break;
        case "BrainTree.StandardTransaction":
            @Html.Action("PaymentForm", "BraintreeStandardCc", new { area = "FastTrack" })
            break;
        case "BrainTree.PayPal.OneTime":
            @Html.Action("PaymentForm", "BraintreePayPal", new { area = "FastTrack" })
            break;
        case "PayPal.ExpressCheckout":
            @Html.Action("PaymentForm", "PayPalExpressPayment", new { area = "Merchello" })
            break;
        case "PurchaseOrder.PurchaseOrder":
            @Html.Action("PaymentForm", "PurchaseOrderPayment", new { area = "FastTrack" })
            break;
    }

And now if we go through the FastTrack checkout. When we get to the Payment section we are able to choose ‘Purchase Order’ and our PaymentForm is rendered.

po-form

We hope you were able to follow along with this post, and it shows how you can make your own Payment providers.

If you are looking for examples of more complex scenarios, then we’d recommend you have a look at the Braintree provider in the core as this is probably the most comprehensive example of a provider you will find for providers that don’t redirect away from your website.

Have a look at the PayPalExpressProvider as a great example of a payment provider that redirects and comes back to the website.

Become A Merchello Solution Partner

Merchello's goal is to make you and your clients successful.

Because the needs of every partner are different, we offer two levels of partnership - Gold and Silver.

Each level has unique benefits, all with the goal of connecting partners to our community and helping them succeed.

Apply Today!

  • Get Listed On the official Merchello website
  • Priority Placement Get your business noticed
  • Get Merchello Leads From the Merchello website
  • Premium Support For your Merchello projects
  • Discounts On official Merchello plugins
  • Your Company Featured In our Newsletter