3
\$\begingroup\$

I am developing a class library for processing credit cards. There is a thought in management in the short-term future to look for a new credit card processor. So I want to write once and use everywhere (I should be anyway, right?).

We are currently using First Data's Global Gateway Web Service API. I need a separate web reference setup for live and test.

I am trying to do the following:

//using ProcessCreditCard.WebReference;
using ProcessCreditCard.WebReferenceTest;

public class ProcessCreditCard
{
    private FDGGWSApiOrderService oFDGGWSApiOrderService = null;

    #region Intialize the object
    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessCreditCard"/> class.
    /// </summary>
    public ProcessCreditCard()
    {
        ServicePointManager.Expect100Continue = false;
        // Initialize Service Object 
        oFDGGWSApiOrderService = new FDGGWSApiOrderService();
        // Set the WSDL URL
        oFDGGWSApiOrderService.Url = @Properties.Settings.Default.CcApiUrl;
        // Configure Client Certificate  
        oFDGGWSApiOrderService.ClientCertificates.Add(X509Certificate.CreateFromCertFile(Properties.Settings.Default.CertFile));
        // Set the Authentication Credentials
        NetworkCredential nc = new NetworkCredential(Properties.Settings.Default.CertUser, Properties.Settings.Default.CertPass);
        oFDGGWSApiOrderService.Credentials = nc;
    }

    /// <summary>
    /// Initializes a new instance of the test version of the <see cref="ProcessCreditCard"/> class.
    /// </summary>
    /// <param name="test">if set to <c>true</c> [test].</param>
    public ProcessCreditCard(bool test)
    {
        ServicePointManager.Expect100Continue = false;
        // Initialize Service Object 
        oFDGGWSApiOrderService = new FDGGWSApiOrderService();
        // Set the WSDL URL
        oFDGGWSApiOrderService.Url = @Properties.Settings.Default.CcApiUrl_Test;
        // Configure Client Certificate  
        oFDGGWSApiOrderService.ClientCertificates.Add(X509Certificate.CreateFromCertFile(Properties.Settings.Default.CertFile_Test));
        // Set the Authentication Credentials
        NetworkCredential nc = new NetworkCredential(Properties.Settings.Default.CertUser_Test, Properties.Settings.Default.CertPass_Test);
        oFDGGWSApiOrderService.Credentials = nc;
    }
    #endregion

    /// <summary>
    /// Charges the credit card.
    /// </summary>
    /// <returns></returns>
    public MktoResponse ChargeCreditCard()
    {
        /// Code here for setting up the transaction
        return BuildResponse(oFDGGWSApiOrderService.FDGGWSApiOrder(oOrderRequest));
    }

So in my calling code, I can create the right environment based on the boolean:

ProcessCreditCard.ProcessCreditCard pcc = new ProcessCreditCard.ProcessCreditCard(true);

How can I get this so that when I reference the oFDGGWSApiOrderService object that I am using the right environment?

\$\endgroup\$
  • \$\begingroup\$ Is your objective to support both the test and live processing in a single environment? Usually your live environment goes to a live CC processor, and your test to test. Thus, why wouldn't you have one group of configuration properties, and set those in your config accordingly to the target environment? This does not address your concern of switching out to a completely different payment processor, but keeps configuration of test and live in the configurataion and your code then uses the same logic, driven by your configuration. \$\endgroup\$ – Art Lucia Oct 17 '11 at 23:35
4
\$\begingroup\$

First, I hope this question is still relevant in your company. One point that is of great interest to me in the question, is the management's vision to use other 3rd party payment processing providers. This directly implies that the code should accomodate this by abstracting the operation of processing orders and resolving dependecies on external systems.

I will give some simple suggestions intended to decouple the code from a specific implementation (therefore enhancing code re-use and testability)

Step I, Resolve dependency on 3rd party credit services

This is acheived by abstracting the functionalities exposed by the service (of course every service will have a different API, but the processing logic will follow more or less the same workflow):

public interface IPaymentGateWayService
{
    List<X509Certificate> ClientCertificates { get; }
    string Url { set; }
    NetworkCredential Credential { set; }
}

So now your Payment processor is agnostic to which service it is using as such:

class PaymentProessor
{
    private readonly IPaymentGateWayService _paymentGateWayService;

    public PaymentProessor(IPaymentGateWayService paymentGateWayService)
    {
        _paymentGateWayService = paymentGateWayService;
        _propertyProvider = propertyProvider;
    }

    public void ProcessCreditCardPayment()
    {
        _paymentGateWayService.Url = /*service url*/;
        _paymentGateWayService.ClientCertificates.Add(/*X509 certificate*/)
        _paymentGateWayService.Credential = new NetworkCredential(/*username and password*/);

    }
}

You then create a wrapper for your FDGGWApiOrderService that implements the IPaymentGateEayService interface:

class FDGGWS : IPaymentGateWayService
{
    private FDGGWSApiOrderService _fdggwsApiOrderService;

    public FDGGWS()
    {
        _fdggwsApiOrderService = new FDGGWSApiOrderService();
    }

    public List<X509Certificate> ClientCertificates
    {
        /*delegate to _fdggwsApiOrderService*/
    }

    public string Url
    {
        /*delegate to _fdggwsApiOrderService*/
    }

    public NetworkCredential Credential
    {
        /*delegate to _fdggwsApiOrderService*/
    }
}

So now when you create your payment processor, you pass the instance of the service you want to use.

new PaymentProessor(new FDGGWS());

This simply allows you to reuse your code to accomodate different service providers, also, you can support multiple providers at the same time by creating a service factory that returns the desired concrete instance of teh service depending on a configuration setting.

Step II, Handle environment settings

There are tons of techniques that allow you to read values from configuration stores, like App.config or a web.config. But for the purpose of my answer I will leverage the dependency injection technique and resolve the dependency on environment properties using constructor injection, similar to what is used to abstract the service interface.

So we create an interface for the configuration settings:

internal interface IPropertyProvider
{
    string Cert_File { get; }
    string Cer_User { get; }
    string Cer_Password { get; }
    string ServiceUrl { get; }
}

And you can create as many flavours of the interface as there are environments, in your case of for test and one for production, here is an abridged example of either:

class ProductionPropertyProvider : IPropertyProvider
{
    public string Cert_File
    {
        get { return "Path to Production file"; }
    }

    /* other properties */
}

class TestPropertyProvider : IPropertyProvider
{
    public string Cert_File
    {
        get { return "Path to test file"; }
    }

    /* other properties */
}

And here is how you perform the dependency injection:

class PaymentProessor
{
    private readonly IPaymentGateWayService _paymentGateWayService;
    private readonly IPropertyProvider _propertyProvider;

    public PaymentProessor()
        :this(new FDGGWS(), new ProductionPropertyProvider())
    {}
    public PaymentProessor(IPaymentGateWayService paymentGateWayService, IPropertyProvider propertyProvider)
    {
        _paymentGateWayService = paymentGateWayService;
        _propertyProvider = propertyProvider;
    }

    public void ProcessCreditCardPayment()
    {
        _paymentGateWayService.Url = _propertyProvider.ServiceUrl;
        _paymentGateWayService.ClientCertificates.Add(X509Certificate.CreateFromCertFile(_propertyProvider.Cert_File));
        _paymentGateWayService.Credential = new NetworkCredential(_propertyProvider.Cer_User, _propertyProvider.Cer_Password);

    }
}

Notice the default constructor that creates default service and property provider instances. This is just to demonstrate that you can specify the default dependencies in the default constructor

Lastly, all you need is one configuration key in your app.config that specifies what environment is it.. we use that key to determine the type of the IPropertyProvider.

Example

//Read env setting from the application configuration file
var environment = ConfigurationManager.AppSettings["env"];

//create appropriate PropertyProvider
IPropertyProvider propertyProvider;
if (environment == "test")
    propertyProvider = new TestPropertyProvider();
else
    propertyProvider = new ProductionPropertyProvider();

//process the payment
new PaymentProessor(new FDGGWS(), propertyProvider).ProcessCreditCardPayment();
\$\endgroup\$
  • \$\begingroup\$ Wow... I'll have to parse this in the daylight at work. A great looking answer and at least looks like what I was trying to work toward as a wrapper around or processor's API so it would be easier to change in the future. \$\endgroup\$ – Mike Wills Oct 21 '11 at 4:32
0
\$\begingroup\$

Here's one method. In your settings file, define a "Mode" setting. Toggle that for Live/Test. Then create a public property that uses that (I put it in my Settings.cs file).

    /// <summary>
    /// Gets the DB connection string
    /// </summary>
    /// <value>The DB connect.</value>
    public string DBConnect {
      get {            
        //  Determine which database should be connected to
        switch (this.DBConnect_Mode) {
          //  Connect to the live database
          case "Live": return this.DBConnect_Live;            
          //  Connect to the dev database
          case "Dev": return this.DBConnect_Dev;            
          //  All other cases, connect to the live database
          default: return this.DBConnect_Live;            
        }
      }
    }

Then in my calling code I just use this:

var connection = Properties.Settings.Default.DBConnect;

This way, you can easily switch and even have it be an optional setting on a configuration screen. You could also use symbols instead of a 3rd setting.

\$\endgroup\$
  • 1
    \$\begingroup\$ I think the OP is using a web service API for connecting to the c/c processing gateway. I don't see any database connections being used. \$\endgroup\$ – dodgy_coder Sep 16 '11 at 5:22
  • \$\begingroup\$ Why would you downvote me ? It wasn't that this is a database connection -- it's "THIS IS HOW YOU CAN DO THIS". \$\endgroup\$ – Matthew M. Sep 16 '11 at 16:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.