Hi Tuon
Thank you very much for you post. This is just what I was looking for.
I look forward to seing some code from you.
Regards
Anders
hi Anders,
I'm glad that it helps, here is the link to the code :https://www.dropbox.com/s/wfym093vfak7eg2/EPIServer_Comerce_7.5_PaymentMethods_Sample.zip
Thanks
Thank you for sharing your code and your thoughts.
So far, I think that some of your solutions fit very well with our site.
Regards
Anders
HI Anders,
That is really good. We are in the process of upgrading one of our customer from EPiServer Commerce 6 R2 to 7.5, and there is a lot of new things that we want to share with the rest of EPiServer World, like how to drag and drop catalog node and variant on to content area with different display options, using of html5 for pages history and stuff, but my request to create blog seem got lost somewhere, and I couldn't see the syndycation function on the website .
Hi Tuon
That sounds like some interresting scenarios, that I would like to hear more about.
I can't help you with the Blog request, but perhaps you should try to request it again?
Regards
Anders
hi Andres,
Thanks, I have created my blog now, you could see it from episerver world, will keep the posts coming regularly.
I have been requested for a blog to share our journey on working on a EPIServer 7.5 Commerce project, but while waiting for approval to create one. I am just gonna share the experiences on forum here , hopefully it could help others, and also for us to learn by your comments and feedback.
According to the webform sample from EPiServer Commerce, each payment method has a paymenthod.ascx file that will handle the payment form, validation and process to add to cart order form. These will be loaded on the payment page check out.
However, it is totally different approach when working with MVC and we have found the following solution for it:
General idea is:
Here is how the problem was tackled:
First we need few model classes for current payment types (hold the actual payment details within the payment method):
[Serializable]
public abstract class PaymentModel
{
public Guid PaymentMethodId { get; set; }
public string PaymentMethodSystemName { get; set; }
public int OrderFormId { get; set; }
public int OrderGroupId { get; set; }
public virtual string Type { get; set; }
}
[Serializable]
public class OtherPaymentModel : PaymentModel
{
public override string Type
{
get { return "Niteco.Web.App.Areas.Commerce.Models.PaymentMethods.OtherPaymentModel," + Assembly.GetExecutingAssembly(); }
}
}
[Serializable]
public class CreditCardPaymentModel:PaymentModel
{
public string CardType { get; set; }
[RequiredIf("RequireCardName",true)]
public string CreditCardName { get; set; }
[Required]
[CreditCard]
public string CreditCardNumber { get; set; }
//More within the zip code
}
}
For each payment method, we will be creating 2 of the following classes for each: paymentmethodmodel, and paymentmetoption, .i.e:
public class GenericPaymentMethodModel : PaymentMethodModel
{
public OtherPaymentModel Payment { get; set; }
}
public class GenericPaymentOption : IPaymentOption
{
public bool ValidateData()
{
return true;
}
public OtherPaymentModel Payment { get; set; }
public GenericPaymentOption(OtherPaymentModel model)
{
Payment = model;
}
public Payment PreProcess(OrderForm form)
{
var otherPayment=new OtherPayment {BillingAddressId = form.BillingAddressId};
return otherPayment;
}
//More within the zip code
}
For other method that use CreditCard, we will need to replace OtherPaymentModel with CreditCardPaymentModel
Now we will need to create the controller for each payment method, for example:
public class AuthorizePaymentMethodController : Controller
{
//
// GET: /Commerce/Authorize/
[RestoreModelStateFromTempData]
public ActionResult PaymentMethod(Guid paymentMethodId, string paymentMethodSystemName, CreditCardPaymentModel paymentModel )
{
var model = new AuthorizePaymentMethodModel
{
Payment = paymentModel ?? new CreditCardPaymentModel() { ExpirationMonth = DateTime.Now.Month, ExpirationYear = DateTime.Now.Year }
};
//More within the zip code
model.Payment.PaymentMethodSystemName = paymentMethodSystemName;
return PartialView("AuthorizePaymentMethod", model);
}
}
The view for it will look like:
@model Niteco.Web.App.Areas.Commerce.Models.PaymentMethods.AuthorizePaymentMethodModel
<h5>@Model.PaymentMethodFriendlyName</h5>
<br/>
@Html.HiddenFor(x => x.Payment.PaymentMethodSystemName)
@Html.HiddenFor(x => x.Payment.PaymentMethodId)
@Html.HiddenFor(x => x.Payment.Type)
@*More within the zip code *@
The same apply for all other payment methods, need to make sure the controller name is made up of {PaymentMethodSystemKeyword} + “PaymentMethodController”.
Then we will need a model class for holding the payment method information
public class PaymentMethodModel
{
public Guid PaymentMethodId { get; set; }
public string PaymentMethodSystemName { get; set; }
public string PaymentMethodFriendlyName { get; set; }
public string PaymentDescription { get; set; }
public MarketId MarketId { get; set; }
}
And here is the model class to hold the payment data
public class PaymentFormModel
{
public List<PaymentMethodModel> PaymentMethods { get; set; }
[Required]
public string SelectedPaymentMethod { get; set; }
public PaymentModel Payment { get; set; }
}
And when return the model for the form, you will need to return the form you will need to create a new instance for it,.i.e:
return View(new PaymentPageModel(currentPage) { PaymentFormModel = new PaymentFormModel (){ PaymentMethods =PaymentHelper.GetPaymentMethods() }});
The View will be generated like this:
@using (Ajax.BeginForm("Pay", "Checkout", new AjaxOptions() { UpdateTargetId = "paymentMethodContainer" }))
{
<div class="checkout_block_title">
<h4>
@Html.Translate("/PaymentMethods/paymentoptiontitle")
</h4>
</div>
<div id="paymentMethodContainer">
@{ Html.RenderPartial("PaymentForm", Model. PaymentFormModel); }
</div>
<input type="submit" class="btn_02" value="Submit" />
}
The partial view “PaymentForm “ will be like this:
<div class="control-group">
@Html.LabelFor(x => x.SelectedPaymentMethod, Html.Translate("/PaymentMethods/SelectPaymentMethodTitle"), new { @class = "control-label" })
<span class="important">*</span>
<div class="controls">
@Html.DropDownList("SelectedPaymentMethod",
new SelectList(Model.PaymentMethods, "PaymentMethodId", "PaymentMethodSystemName",
Model.SelectedPaymentMethod), Html.Translate("/Paymentmethods/selectpaymentmethodlabel"), new { @class = "select_01" })
<div class="error">@Html.ValidationMessageFor(x => x.SelectedPaymentMethod)</div>
<br />
</div>
</div>
<div id="selectedPaymentContainer">
<div class="error">
@if (!ViewData.ModelState.IsValid)
{
<h5>@Html.Translate("/Paymentmethods/PaymentErrorSummary")</h5>
@Html.ValidationSummary()
}
</div>
@if (Model.Payment != null)
{
Html.RenderAction("PaymentMethod", Model.Payment.PaymentMethodSystemName + "PaymentMethod", new RouteValueDictionary(new
{
paymentMethodId = Model.Payment.PaymentMethodId,
paymentMethodSystemName = Model.Payment.PaymentMethodSystemName,
paymentModel = Model.Payment
}));
}
</div>
<script type="text/javascript" language="javascript">
$('select[name="SelectedPaymentMethod"]').change(function () {
var selectedMethodId = $(this).val();
var selectedMethodName = $('select[name="SelectedPaymentMethod"] option:selected').text();
var href = "/" + selectedMethodName + "PaymentMethod/PaymentMethod";
$.ajax(
{
url: href,
data: { paymentMethodId: selectedMethodId, paymentMethodSystemName: selectedMethodName },
success: function (data) {
$("#selectedPaymentContainer").html(data);
}
});
});
</script>
Now on the submit action we will be able to get the payment details to be added to cart:
public ActionResult Signup(ClubSignupFormModel model)
{
model.PaymentMethods = PaymentHelper.GetPaymentMethods();
if (!ModelState.IsValid) return PartialView("Signup",model);
//foreach (var paymentMethod in model.PaymentMethods.Values)
if (model.Payment!=null)
{
var paymentMethod = model.Payment;
var paymentMethodType = Type.GetType("Niteco.Web.App.Areas.Commerce.Models.PaymentMethods." + paymentMethod.PaymentMethodSystemName + "PaymentOption," + Assembly.GetExecutingAssembly());
if (paymentMethodType != null)
{
var paymentOptionType = Activator.CreateInstance(paymentMethodType, paymentMethod);
var paymentOption = paymentOptionType as IPaymentOption;
//This is to handle payment if needed, for example
if (paymentOption != null)
{
//Add payment to cart here
//return Content("Payment added to cart");
}
}
ModelState.AddModelError("NoPaymentSelected", LocalizationService.Current.GetString("/PaymentMethods/UnableToProcessPayment"));
return PartialView("Signup",model);
}
ModelState.AddModelError("NoPaymentSelected", LocalizationService.Current.GetString("/PaymentMethods/NoPaymentSelectedError"));
return PartialView("SignUp",model);
}
One more thing that we need to handle is the model binding for our abstract payment model class, this will be needed:
public class CommerceModelBinder:DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType != typeof (PaymentModel))
return base.CreateModel(controllerContext, bindingContext, modelType);
var instantiationTypeString = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type").ConvertTo(typeof(string)).ToString();
var instantiationType = Type.GetType(instantiationTypeString);
if (instantiationType == null) return base.CreateModel(controllerContext, bindingContext, modelType);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
}
This class is very important for the payment data to be binding correctly when form is submitted. This will need to be registered on global asax on Application Start:
ModelBinders.Binders.DefaultBinder=new CommerceModelBinder();
That is it, here is a screencast of how the payment options will work like in our MVC EPiServer Commerce Project:
http://www.screencast.com/t/RwBMip2jc
I will zip up the code and attach to this thread later on.