Creating a Custom Payment Method

TABLE OF CONTENTS

Creating a Custom Payment Method

Introduction

This article mentions the steps to add/implement a new payment method 

The User is redirected to a payment portal (outside of the Znode system), payment information is entered, and upon completion (or cancellation) the customer is returned to a Znode system URL where final processing occurs.

Prerequisite

  1. Sandbox Details:
    • Sandbox credentials,  these credentials may include API keys, merchant IDs, secret keys, or other authentication tokens necessary for integrating with payment solutions.   

    • Use valid credentials for the sandbox URLs to validate all transactions.

  2. Integration details:
    • A Developer Account with a URL and valid credentials is required for creating API tokens and managing your integration.

Notes: 

  1. Understanding the Znode SDK's structure and being able to customize it is valuable.
  2. We have provided details based on the high-level analysis. Each gateway has a different implementation, so we may need some additional changes. This document includes the high-level flow details for introducing the new implementation.

Payment Application Changes

Code Level Changes:-

  • Enum Changes:-
    Purpose: To add a new payment gateway in an ‘enum’ modify GatewayType.cs file. This ‘enum’ type will help match the payment gateway with the database. 
    • Location: PaymentApp → Znode.Multifront.PaymentApplication →
    • Znode.Multifront.PaymentApplication.Models
    • FileName: GatewayType.cs
    • Code Sample (add yellow highlighted line of code): 
    • public enum GatewayType
      {
      PAYPAL EXPRESS = 0,
      AUTHORIZENET = 1,
      TWOCHECKOUT = 2,
      CYBERSOURCE = 3,
      STRIPE = 4,
      PAYPAL = 5,
      PAYMENTECH = 6,
      WORLDPAY = 7,
      BRAINTREE = 8,
      PAYFLOW = 9,
      <YOUR PAYMENT GATEWAY>=10
       }
  • Payment Controller Changes:-
    Purpose: Modify IsCustomerProfileIdExist method of PaymentController class to identify <YOUR PAYMENT GATEWAY>
    • Location: PaymentApp → Znode.Multifront.PaymentApplication →
    • Znode.Multifront.PaymentApplication.Api→ Controllers
    • FileName: PaymentController.cs
    • MethodName: IsCustomerProfileIdExist 
    • Code  Sample (add yellow highlighted line of code):

      private bool IsCustomerProfileIdExist(PaymentModel paymentModel)
       => ((string.IsNullOrEmpty(paymentModel.CustomerProfileId)
                       || string.IsNullOrEmpty(paymentModel.CustomerPaymentProfileId))
                      && (!string.IsNullOrEmpty(paymentModel.GatewayType))
                      && Equals(paymentModel.GatewayType.ToLower(), Convert.ToString(GatewayType.BRAINTREE).ToLower())
                       || Equals(paymentModel.GatewayType.ToLower(), Convert.ToString(GatewayType.PAYFLOW).ToLower())
                      || Equals(paymentModel.GatewayType.ToLower(), Convert.ToString(GatewayType.PAYPAL).ToLower()))
                       || Equals(paymentModel.GatewayType.ToLower(), Convert.ToString(GatewayType.YOURPAYMENTGATEWAY).ToLower()));


Database Changes:-

  • ZnodePaymentGateway Table (Add Payment Gateway in the below mention table) :-
    Purpose: To Add a new payment gateway in the database. The PaymentGatewayId should match with the GatewayType EnumId. Insert an entry into the ZNodePaymentGateway table of both Multifront and Payment databases. Check the script below.

    Payment Database:-
    INSERT INTO [dbo].[ZNodePaymentGateway] ([PaymentGatewayId],[GatewayName],[WebsiteURL],[ClassName],[CreatedDate],[ModifiedDate], [GatewayCode])

VALUES

(7,'<YOURPAYMENTGATEWAY>','<YOURPAYMENTGATEWAYWEBSITEURL>', '<YOURPAYMENTGATEWAYCLASSNAME>',GETDATE(),GETDATE(),’<YOURGATEWAYCODE>’)


  • ZnodePaymentType Table (Add Payment Type in below mention table) :-

Purpose: To Add a new payment gateway in the database..Insert an entry into the ZnodePaymentType table of both the Multifront and Payment databases.Check the script below.

Payment Database:-

INSERT INTO [ZnodePaymentType] 

([Name],[Description],[IsActive],[CreatedDate],[ModifiedDate],[BehaviorType],[Code])

 VALUES                  ('<PaymentName>','<Description>',1,GETDATE(),GETDATE(),'<BehaviorType>','<Code>')

API  Application Changes

  • Database Changes:-
    • Multifront Database:-
      • ZnodePaymentGateway Table:-
        Purpose: To Add a new payment gateway in the database..Insert an entry into ZnodePaymentType table of both Multifront and Payment database.Check script below.

INSERT INTO [dbo].[ZnodePaymentType] ([Code],[Name],[Description],[IsActive],[CreatedBy],[CreatedDate],[ModifiedBy], [ModifiedDate],[IsCallToPaymentAPI],[BehaviorType],[IsUsedForOfflinePayment])

VALUES

('<PaymentCode>','<PaymentName>','<Description>',1,1,GETDATE(),1, GETDATE(),0,'<BehaviorType>',0)

  • ZnodePaymentType Table (Add Payment Type in the below mention table):-
    Purpose: To Add a new payment gateway in the database..Insert an entry into ZnodePaymentType table of both Multifront and Payment database.Check script below.

INSERT INTO [dbo].[ZnodePaymentType] ([Code],[Name],[Description],[IsActive],[CreatedBy],[CreatedDate],[ModifiedBy], [ModifiedDate],[IsCallToPaymentAPI],[BehaviorType],[IsUsedForOfflinePayment])

VALUES

('<PaymentCode>','<PaymentName>','<Description>',1,1,GETDATE(),1, GETDATE(),0,'<BehaviorType>',0)



Admin Application Changes

Code Level Changes:-

  • Agent Changes:-
    Purpose: Create a custom agent in the following path and override the GetPaymentGatewayView() method of the PaymentAgent class which would be inherited from the PaymentAgent Class. This custom agent will have the logic specific to returning the views related to YOUR PAYMENT GATEWAY. 
    • Location:  Projects → Libraries → Znode.Admin.Custom → Agents→ Payment
    • FileName: CustomPaymentAgent.cs
    • Method: GetPaymentGatewayView
    • Code Sample (add yellow highlighted line of code):
      public const int <YOURPAYMENT GATEWAY> = 7;

public const string <YOURPAYMENT GATEWAY>View = "~/Views/Payment/_YOURPAYMENTGATEWAY.cshtml";

public override string GetPaymentGatewayView(int paymentGatewayId)

{
switch (paymentGatewayId)
{
          case <YOUR PAYMENT GATEWAY>:
                            return YOUR PAYMENT GATEWAY>View;
Default: base.GetPaymentGatewayView(paymentGatewayId); 

} }
  • Save Payment Details:-
    Purpose: To save your payment gateway details override the AddPaymentSetting method of the paymentAgent class and write logic to store information related to <YOUR PAYMENT GATEWAY> in the database. The location to add a new class is mentioned below.
    • Location: Projects → Libraries → Znode.Admin.Custom → Agents→ Payment
    • Method Name: AddPaymentSetting
    • Code Sample For Reference (add a yellow highlighted line of code): 

public override PaymentSettingViewModel AddPaymentSetting(PaymentSettingViewModel paymentSettingViewModel)

{
// write custom logic to save payment details in the payment database, if any. Else below line of code will help to save the data.
         base.AddPaymentSetting(paymentSettingViewModel);
 }
  • Controller Changes:-
    Purpose: Add a new controller which should inherit from OrderController and add a method to process payment. Register new controller in dependency.
    • Location: Projects →  Libraries → Znode.Admin.Custom →  Controllers
    • Code Sample For Reference

Dependency Registration Code:


builder.RegisterType<CustomOrderController >().As<OrderController >().InstancePerDependency();


Controller Changes:

public class CustomOrderController : OrderController

{
    private read-only IOrderAgent _orderAgent;
    private read-only IStoreAgent _storeAgent;       
    private read-only IWebSiteAgent _websiteAgent;

    public CustomOrderController(IOrderAgent orderAgent, IAccountAgent accountAgent, IUserAgent userAgent, ICustomerAgent customerAgent, IShippingAgent shippingAgent, ICartAgent cartAgent, IPaymentAgent paymentAgent, IAccountQuoteAgent quoteAgent, IStoreAgent storeAgent, IRMARequestAgent rmaRequestAgent, IWebSiteAgent websiteAgent) : base(orderAgent, accountAgent, userAgent, customerAgent, shippingAgent, cartAgent, paymentAgent, quoteAgent, storeAgent, rmaRequestAgent)     {         _orderAgent = orderAgent;                    _storeAgent = storeAgent;                 } 
    [HttpPost]     public virtual JsonResult ProcessIPayPayment(SubmitPaymentViewModel payment model)     {         //Write custom login              }
    [HttpGet]     public virtual ActionResult SubmitIPayOrder(string token, int paymentOptionId, int shippingId,       string additionalNotes)     {         //Write custom login when control return from your payment gateway and Save  Order // This is the sample code  SubmitPaymentViewModel model = new SubmitPaymentViewModel();  model.Token = token;  model.PaymentSettingId = paymentOptionId;  model.ShippingOptionId = shippingId;  model.AdditionalInfo = additionalNotes; SubmitOrderViewModel order = _orderAgent.ProcessCreditCardPayment(model , true);     } }
  • View Changes:-
    Purpose: Add a new Partial View which will store information related to <YOUR PAYMENT GATEWAY> in the database. Below sample partial view can be used to customize the code and to save details related to the payment gateway. Location of partial view is:
    • Location:  Projects → Znode.Engine.Admin → Views → Payment → YOUR PARTIAL VIEW_NAME
    • Code Sample For Reference (code below will help the user to create 

a new Partial View): 


@model Znode.Engine.Admin.ViewModels.PaymentSettingViewModel @{     string preauthorize = Convert.ToBoolean(Model.PreAuthorize) ?  "checked='checked'" : string.Empty;     string isRmaCompatible = Convert.ToBoolean(Model.IsRmaCompatible)  ?"checked='checked'" : string.Empty; }
<div id="PaymentGetwayForm-container">     <h3 class="section-heading margin-top-25">MERCHANT GATEWAY SETTINGS</h3>     <div class="form-group">         <div class="col-sm-12 nopadding">             <div class="control-label">                 @Html.Label("Select Gateway Mode")            </div>             <div class="control-md">             @Html.DropDownListFor(m => Model.TestMode, new  List<SelectListItem>()             {             new SelectListItem {Text = 'Test Mode',Value="true"}, new SelectListItem {Text = 'Live Mode' ,Value="false"}},  new {   @id = "ddlTestMode" })             </div>         </div>     </div>     <div class="form-group">         <div class="col-sm-12 nopadding">             <div class="control-label">                 @Html.Label("Merchant Login", new { @class = "required" })             </div>             <div class="control-md">                 @Html.TextBoxFor(m => Model.GatewayUsername, new { required = "required" })                 @Html.ValidationMessageFor(m => Model.GatewayUsername, "Merchant Login is required.")             </div>         </div>     </div>     <div class="form-group">         <div class="col-sm-12 nopadding">             <div class="control-label">                 @Html.Label("Merchant account password", new { @class = "required" })             </div>             <div class="control-md">                 @Html.PasswordFor(m => Model.GatewayPassword, new { required = "required" })                 @Html.ValidationMessageFor(m => Model.GatewayPassword, "Enter merchant password")             </div>         </div>     </div>     <div class="form-group">         <div class="col-sm-12 nopadding">             <div class="control-label">                 @Html.Label("API Signature (PayPal only)")             </div>             <div class="control-md">                 @Html.TextBoxFor(m => Model.TransactionKey)             </div>         </div>     </div> </div>

    

  • Order Flow View Changes:-
    Purpose: Add button in view to show button
    • Location: Projects → Znode.Engine.Admin → Views → Order
    • FileName: CreateOrder.cshtml
    • Code  Sample (add yellow highlighted line of code):

      @*IPay*@
        <div id="ipay-button" class="col-xs-12 nopadding" style="display:none"></div>

<script>     IPay.Button.render({         env: 'sandbox',         commit: true,          payment: function () {             Order.prototype.GetPaymentDetails($("#ddlPaymentTypes").val());            var ipayResponse = Order.prototype.IPayCheckout();            return ipayResponse[1];         },         onAuthorize: function (data, actions) {             window.location.href = data.returnUrl;         }     }, '#ipay-button'); </script>


  • Javascript Changes:-
    • Order.ts Changes:-
      • Description:  Modify ShowAndSetPayment  method to show the button of  your payment gateway
      • Location: Projects → Znode.Engine.Admin →  Scripts → Core → Znode
      • Method: ShowAndSetPayment → Add condition to show button after selection of your payment gateway.

        Code Sample (add yellow highlighted line of code): ShowAndSetPayment(selectionId, isFirstTime) {
        $("#ipay-button").hide();
        if (paymentType.toLowerCase() == "cod") {
        }
        else if (paymentType.toLowerCase() == "IPay") {
        $("#ipay-button").show();
        }
        }
      • Method: Add method to process payment after your payment type button click.
      • Code  Sample: 
      • IPayCheckout(): any

        {
        var isValid = true; isValid = Order.prototype.ValidateDetails("false");
        if (isValid) {
        return Order.prototype.IPayPaymentProcess();
        }
        }
      • Method: Add condition in ValidateDetails method for validation data before processing payment.
      • Code  Sample:
        if ($("#ddlPaymentTypes option:selected").attr("id").toLowerCase() == 'IPay') { isValid = true;
         }
      • Method: Add a new method to process payment.
      • Code  Sample: 
IPayPaymentProcess(): any {
    var Total = $("#hdnTotalAmt").val();
    if (Order.prototype.IsOrderTotalGreaterThanZero(Total)) {
        var paymentOptionId = $('#ddlPaymentTypes').val();
        var urlhost = document.location.origin;//window.location.host;
        var cancelUrl = urlhost + "/order/createorder";
        var returnUrl = urlhost + "/CustomOrder/SubmitIPayOrder?paymentOptionId=" + paymentOptionId;
        var shippingId = $("input[name='ShippingId']:checked").val();
        var additionalInformation = $("#additionalInstructions").val();
        if (shippingId == undefined) {
            returnUrl = returnUrl + "&shippingId=0&additionalNotes=" + additionalInformation;
        }
        else {
            returnUrl = returnUrl + "&shippingId=" + shippingId + "&additionalNotes=" + additionalInformation;
        }
        var billingaddressId = $("#UserAddressDataViewModel_BillingAddress_AddressId").val();
        var shipingaddressId = $("#UserAddressDataViewModel_ShippingAddress_AddressId").val();
        var paymentmodel = {
            PaymentSettingId: paymentOptionId,
            UserId: $("#hdnUserId").val(),
            BillingAddressId: billingaddressId,
            ShippingAddressId: shipingaddressId,
            PortalId: Order.prototype.GetPortalId(),
            PortalCatalogId: $("#PortalCatalogId").val(),
            PaymentApplicationSettingId: $("#hdnPaymentApplicationSettingId").val(),
            IPayReturnUrl: returnUrl,
            IPayCancelUrl: cancelUrl
        };
        var ipayDetails = [];
        Endpoint.prototype.ProcessIPayPayment(paymentmodel, function (response) {
            var message = response.message;
            if (message.toLowerCase().indexOf("false") >= 0) {                    Order.prototype.ClearPaymentAndDisplayMessage(ZnodeBase.prototype.getResourceByKeyName("ErrorProcessPayment"));
                $("#ipay-button").hide();
                return false;
            }
            else {
                if (message != undefined && message.indexOf('Message=') >= 0) {
                    var errorMessage = message.substr(message.indexOf('=') + 1);                    Order.prototype.ClearPaymentAndDisplayMessage(ZnodeBase.prototype.getResourceByKeyName("ErrorOrderOverDueAmount"));
                    $("#ddlPaymentTypes").val('');
                } else if (message.indexOf("http") != -1) {
                    ipayDetails .push(response.message);
                    ipayDetails .push(response.token)
                }
                else {
                    Order.prototype.ClearPaymentAndDisplayMessage(message);
                    $("#ddlPaymentTypes").val(paymentOptionId);
                }
            }
        });
        return ipayDetails;
    }
}
  • Endpoint.ts Changes:-
    • Description: Add a new endpoint to process payment.
    • Location: Projects → Znode.Engine.Admin →  Core→ Endpoint
    • Code Sample For Reference:
ProcessIPayPayment(paymentmodel: any, callbackMethod) {
    super.ajaxRequest("/CustomOrder/ProcessIPayPayment", "post", { "paymentmodel": paymentmodel }, callbackMethod, "json", false);
}
  • Database Changes:-
    • Add Entry for new actions and controller added in admin project.
INSERT INTO [dbo].[ZnodeActions]
         ([AreaName],[ControllerName],[ActionName],[IsGlobalAccess],[CreatedBy] ,
        [CreatedDate],[ModifiedBy] ,[ModifiedDate])
VALUES
('NULL','CustomPayment',Payment,1,2,GETUTCDATE(),2    ,GETUTCDATE()) 


Webstore Application Changes

Code Level Changes:-

  • Webstore Controller Changes:-
  • Webstore View Changes:-
  • Webstore Javascript/TS Changes:-
    • Checkout.ts Changes:-
      Purpose:  Add methods to operate on button click.
    • Location: Projects → Znode.Engine.WebStore →  Scripts → Core → Znode
    • FileName: Checkout.ts
    • Code  Sample (add a yellow highlighted line of code):

      OnClickIPayExpress(): any 
      {
            return Checkout.prototype.IPayPaymentProcess();
          }
            
IPayPaymentProcess(): any {
    var Total = $("#Total").val();
    var url = [];
    if (Checkout.prototype.IsOrderTotalGreaterThanZero(Total)) {
        Endpoint.prototype.GetPaymentDetails($('#PaymentSettingId').val(), false,
 function (response) {
            Checkout.prototype.SetPaymentDetails(response);
            if (!response.HasError) {
                url = Checkout.prototype.IPayPayment();  
            }  
        });
    }
    if (url != null)
        return url;        
    return false;
}

SetPaymentDetails(response): any {     if (!response.HasError) {         $("#hdnGatwayName").val(response.GatewayName);         $("#paymentProfileId").val(response.PaymentProfileId);         $("#hdnPaymentApplicationSettingId").val(response.PaymentApplicationSettingId);         $("#hdnEncryptedTotalAmount").val(response.Total);     } }
 public IPayPayment(): any {     var urlhost = document.location.origin;     var ShippingOptionId = $("input[name='ShippingOptions']:checked").val();     var AdditionalInstruction = $("#AdditionalInstruction").val();     var ShippingAddressId = $("#shipping-content").find("#AddressId").val();     var BillingAddressId = $("#billing-content").find("#AddressId").val();
    var PaymentSettingId = $('#PaymentSettingId').val();     var cancelUrl = urlhost + "/checkout/index";     var returnUrl = urlhost + "/customcheckout/SubmitIPayOrder?ShippingAddressId=" + ShippingAddressId + "&BillingAddressId=" + BillingAddressId + "&ShippingOptionId=" + ShippingOptionId + "&AdditionalInstruction=" + AdditionalInstruction + "&PaymentSettingId=" + PaymentSettingId + "";
    var submitPaymentViewModel = {         PaymentSettingId: PaymentSettingId,         PaymentApplicationSettingId: $("#hdnPaymentApplicationSettingId").val(),         ShippingAddressId: ShippingAddressId,         BillingAddressId: BillingAddressId,         ShippingOptionId: ShippingOptionId,         AdditionalInstruction: AdditionalInstruction,         PayPalReturnUrl: returnUrl,         PayPalCancelUrl: cancelUrl,         PaymentType: "IPay",         Total: $("#Total").val(),         SubTotal: $('#SubTotal').val()     };
    var ipaypal = [];     $.ajax({         type: "POST",         url: "/customcheckout/ProcessIPayOrder",         data: submitPaymentViewModel,         async: false,         success: function (response) {             if (response.error != null && response.error != "" && response.error != 'undefined') {                 Checkout.prototype.ClearPaymentAndDisplayMessage(response.error);                 $("#ipay-button").hide();                 return false;             } else if (response.responseText != null && response.responseText != "" && response.responseText != 'undefined') {                 $("#ipay-button").hide();                 if (response.responseText != undefined && response.responseText.indexOf('Message=') >= 0) {                     var errorMessage = response.responseText.substr(response.responseText.indexOf('=') + 1);                     Checkout.prototype.ClearPaymentAndDisplayMessage(ZnodeBase.prototype.getResourceByKeyName("SelectCOD"));
                } else if (response.responseText.indexOf("http") != -1) {                     ipaypal.push(response.responseText);                     ipaypal.push(response.responseToken)                 }                 else {                     Checkout.prototype.ClearPaymentAndDisplayMessage(ZnodeBase.prototype.getResourceByKeyName("ErrorProcessPayment"));                 }             }         },         error: function () {             Checkout.prototype.ClearPaymentAndDisplayMessage(ZnodeBase.prototype.getResourceByKeyName("ErrorProcessOrder"));             return false;         }     });     return ipaypal; }


  • ZnodeEndpoint.ts Changes:-
    Purpose:  Add endpoint to process payment.
    • Location: Projects → Znode.Engine.WebStore → Projects → Scripts →Core → Endpoint
    • FileName: ZnodeEndpoint.ts
    • Code  Sample:        
GetPaymentDetails(paymentSettingId, isAsync: boolean, callbackMethod) {
        super.ajaxRequest("/CustomCheckout/GetPaymentDetails", "get", { "paymentSettingId": paymentSettingId }, callbackMethod, "json", isAsync);
    }

How to add newly implemented payment gateway 

  • After implementation of the new payment gateway, we have to add it from the Admin site which will help to display in the Webstore. 
  • To add a new payment gateway in UI please go to Znode Knowledge Base

Did you find it helpful? Yes No

Send feedback
Sorry we couldn't be helpful. Help us improve this article with your feedback.