Integration of IPN Easy for .NET with Windows Communication Framework (WCF) is only possible when using the Enterprise edition of the component.
By using WCF integration, it is possible for IPN Easy for .NET to be used with WCF supported bindings such as:
- WSHttpBinding
- NetTcpBinding
- NetMsmqBinding
The Enterprise edition of IPN Easy for .NET includes WCF compatible service and data contracts for easy out of the box WCF implementation.
Some of the possible applications of using WCF with IPN Easy for .NET are:
- Use MSMQ to provide improved resilience to your solution
- Physically separate your IPN receiving and you eCommerce hosts
- Easily integrate IPN Easy for .NET with Windows Workflow (WF) via WCF.
The following example shows how to use IPN Easy for .NET with WCF using NetMsmqBinding. Then, on the receiving side, we show how to host and invoke a Workflow (WF) in a windows service.
-
Configure MSMQ service binding in .config file as follows:
CopyXML<system.serviceModel> <bindings> <netMsmqBinding> <binding name="ThisMsmqBinding" receiveRetryCount="20" maxRetryCycles="0" retryCycleDelay="00:00:05" maxReceivedMessageSize="1073741824"> <readerQuotas maxArrayLength="1073741824" /> <security> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None" /> </security> </binding> <binding name="PoisonBinding" receiveRetryCount="0" maxRetryCycles="1" retryCycleDelay="00:00:05" receiveErrorHandling="Fault"> <security> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None" /> </security> </binding> </netMsmqBinding> </bindings> </system.serviceModel>
-
Create a private, transactional queue called paypaltransactions.
Grant appropriate read/write permissions to this queue using the Windows Message Queuing admin tool. If you are using IPN Easy for .NET hosted in IIS, you need to grant the NETWORK SERVICE account READ/WRITE permissions to the queue.
If you are running Vista, Windows 7, Server 2008 or higher, you may also need to grant permissions to MSDTC as follows:
- Start > Run > dcomcnfg.exe
- Component Services > My Computer > Distributed Transaction Coordinator
- Right click Local DTC
- Security tab
- Check "Network DTC Access"
- Check "Allow Inbound"
- Check "Allow Outbound"
-
Create a WCF submitter implementation based on the service contract interface IIpnReceivedContract
An example is as follows:
Code/Submitters/PayPal/WcfQueuingPaypalIpnSubmitterSubmitter.cs
CopyC#using System; using System.ServiceModel; using Rolosoft.IpnEasy.Net; using Rolosoft.IpnEasy.Net.PayPal; namespace MyProject.Code.Submitters.PayPal { public class WcfQueuingPaypalIpnSubmitterSubmitter:IIpnReceivedContract { - #region Privates private readonly IIpnReceivedContract _channel; #endregion - #region Ctors public WcfQueuingPaypalIpnSubmitterSubmitter(string server) { //sEndpointAddress is the location of the MSMQ. var sEndpointAddress = "net.msmq://localhost/paypaltransactions"; var endpointAddress = new EndpointAddress(new Uri(sEndpointAddress)); var clientBinding = new NetMsmqBinding("ThisMsmqBinding"); var channelFactory = new ChannelFactory<IIpnReceivedContract>(clientBinding, endpointAddress); _channel = channelFactory.CreateChannel(); } #endregion - #region Implementation of IIpnReceivedContract /// <summary> /// <para> /// The Service Contract interface for WCF implementations of PayPal IPN integration.* /// </para> /// <para> /// Submit IPN data to queue and for onward processing. /// </para> /// </summary> /// <param name="transaction">PayPal IPN transaction data</param> /// <param name="log">The <see cref="T:Rolosoft.IpnEasy.Net.Log"/> of the PayPal IPN interaction</param> public void SubmitIpn(Transaction transaction, Log log) { if (_channel == null) return; if (transaction == null) return; _channel.SubmitIpn(transaction, log); } #endregion } }
CopyVB.NETImports System.ServiceModel Imports Rolosoft.IpnEasy.Net Imports Rolosoft.IpnEasy.Net.PayPal Namespace MyProject.Code.Submitters.PayPal Public Class WcfQueuingPaypalIpnSubmitterSubmitter Implements IIpnReceivedContract - #Region "Privates" Private ReadOnly _channel As IIpnReceivedContract #End Region - #Region "Ctors" Public Sub New(server As String) 'sEndpointAddress is the location of the MSMQ. Dim sEndpointAddress = "net.msmq://localhost/paypaltransactions" Dim endpointAddress = New EndpointAddress(New Uri(sEndpointAddress)) Dim clientBinding = New NetMsmqBinding("ThisMsmqBinding") Dim channelFactory = New ChannelFactory(Of IIpnReceivedContract)(clientBinding, endpointAddress) _channel = channelFactory.CreateChannel() End Sub #End Region - #Region "Implementation of IIpnReceivedContract" ''' <summary> ''' <para> ''' The Service Contract interface for WCF implementations of PayPal IPN integration.* ''' </para> ''' <para> ''' Submit IPN data to queue and for onward processing. ''' </para> ''' </summary> ''' <param name="transaction">PayPal IPN transaction data</param> ''' <param name="log">The <see cref="T:Rolosoft.IpnEasy.Net.Log"/> of the PayPal IPN interaction</param> Public Sub SubmitIpn(transaction As Transaction, log As Log) If _channel Is Nothing Then Return End If If transaction Is Nothing Then Return End If _channel.SubmitIpn(transaction, log) End Sub #End Region End Class End Namespace
-
PayPal IPN can be configured to post IPN messages to any public URL. For further information on configuring PayPal IPN messages, please refer to the appropriate PayPal documentation.
For testing purposes, you could use the PayPal IPN Test Tool from the PayPal developer sandbox.
The following example shows a web handler that is hosted in an ASP.NET web application.
PayPal/Ipn.ashx
CopyC#using System; using System.Diagnostics; using System.Globalization; using System.Web; using System.Web.Configuration; using System.Web.Services; using Resources; using MyProject.Code.Submitters.PayPal; using Rolosoft.IpnEasy.Net.PayPal; namespace MyProject.PayPal { /// <summary> /// Receives PayPal IPN notifications and submits messages to MSMQ for processing /// </summary> [WebService(Namespace = "http://tempuri.org")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Ipn : IHttpHandler { - #region Privates private readonly IpnEasy.Net.Ipn _ipnEasy; #endregion - #region Ctors public Ipn() { _ipnEasy = new IpnEasy.Net.Ipn(); _ipnEasy.TransactionReceived += IpnEasyTransactionReceived; } #endregion - #region Events static void IpnEasyTransactionReceived(object sender, TransactionEventArgs e) { var info = e.Transaction; //submit to VALID queue for normal processing var submitter = new WcfQueuingPaypalIpnSubmitterSubmitter("."); submitter.SubmitIpn(info,e.Log); } #endregion - #region Methods public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; _ipnEasy.Validate(); } #endregion - #region Properties public bool IsReusable { get{return false;} } #endregion } }
CopyVB.NETImports System.Diagnostics Imports System.Globalization Imports System.Web Imports System.Web.Configuration Imports System.Web.Services Imports Resources Imports MyProject.Code.Submitters.PayPal Imports Rolosoft.IpnEasy.Net.PayPal Namespace MyProject.PayPal ''' <summary> ''' Receives PayPal IPN notifications and submits messages to MSMQ for processing ''' </summary> <WebService([Namespace] := "http://tempuri.org")> _ <WebServiceBinding(ConformsTo := WsiProfiles.BasicProfile1_1)> _ Public Class Ipn Implements IHttpHandler - #Region "Privates" Private ReadOnly _ipnEasy As IpnEasy.Net.Ipn #End Region - #Region "Ctors" Public Sub New() _ipnEasy = New IpnEasy.Net.Ipn() AddHandler _ipnEasy.TransactionReceived, AddressOf IpnEasyTransactionReceived End Sub #End Region - #Region "Events" Private Shared Sub IpnEasyTransactionReceived(sender As Object, e As TransactionEventArgs) Dim info = e.Transaction 'submit to VALID queue for normal processing Dim submitter = New WcfQueuingPaypalIpnSubmitterSubmitter(".") submitter.SubmitIpn(info, e.Log) End Sub #End Region - #Region "Methods" Public Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest context.Response.ContentType = "text/plain" _ipnEasy.Validate() End Sub #End Region - #Region "Properties" Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return False End Get End Property #End Region End Class End Namespace
Note:
If you are using the PayPal developer sandbox IPN test tool, your IPN listener must be visible from the internet.
It may be possible to configure your router to use NAT port redirection to allow you to use the PayPal sandbox IPN test tool to send IPN messages to your local development machine. If you need assistance with this, please consult your network administrator.
-
The following illustration shows how to create a WCF listener based on the NetMsmqBinding implementation in this example.
-
Create a new project (e.g. a windows service or .exe).
-
Add a project reference to IPN Easy for .NET.
-
Modify your project .config file with service binding configuration as follows:
CopyXML<system.serviceModel> <bindings> <netMsmqBinding> <binding name="ThisMsmqBinding" maxRetryCycles="0" receiveRetryCount="20" retryCycleDelay="00:00:05" maxReceivedMessageSize="1073741824"> <readerQuotas maxArrayLength="1073741824"/> <security> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/> </security> </binding> </netMsmqBinding> </bindings> </system.serviceModel>
-
Create Code\Watchers\PayPalIpnItemFoundEventArgs.cs
CopyC#using System; using Rolosoft.IpnEasy.Net; using Rolosoft.IpnEasy.Net.PayPal; namespace MyProject.Code.Watchers { class PayPalIpnItemFoundEventArgs: EventArgs { - #region Ctors public PayPalIpnItemFoundEventArgs(Transaction transaction,Log log) { PayPalTransaction = transaction; Log = log; } #endregion - #region Properties public Transaction PayPalTransaction { get; set; } public Log Log { get; set; } #endregion } }
CopyVB.NETImports Rolosoft.IpnEasy.Net Imports Rolosoft.IpnEasy.Net.PayPal Namespace MyProject.Code.Watchers Class PayPalIpnItemFoundEventArgs Inherits EventArgs - #Region "Ctors" Public Sub New(transaction As Transaction, log__1 As Log) PayPalTransaction = transaction Log = log__1 End Sub #End Region - #Region "Properties" Public Property PayPalTransaction() As Transaction Get Return m_PayPalTransaction End Get Set m_PayPalTransaction = Value End Set End Property Private m_PayPalTransaction As Transaction Public Property Log() As Log Get Return m_Log End Get Set m_Log = Value End Set End Property Private m_Log As Log #End Region End Class End Namespace
-
Create Code\Watchers\PayPalIpnItemWatcher.cs
CopyC#-#region Usings using System; using Rolosoft.IpnEasy.Net; using Rolosoft.IpnEasy.Net.PayPal; #endregion namespace MyProject.Code.Watchers { internal abstract class PayPalIpnItemWatcher { - #region Events public static event EventHandler<PayPalIpnItemFoundEventArgs> PayPalIpnReceived; #endregion - #region Methods protected virtual void OnPayPalIpnReceived(Transaction transaction,Log log) { if (transaction == null) return; if (PayPalIpnReceived != null) PayPalIpnReceived(this, new PayPalIpnItemFoundEventArgs(transaction,log)); } public abstract void StartWatching(); public abstract void StopWatching(); #endregion } }
CopyVB.NET-#Region "Usings" Imports Rolosoft.IpnEasy.Net Imports Rolosoft.IpnEasy.Net.PayPal #End Region Namespace MyProject.Code.Watchers Friend MustInherit Class PayPalIpnItemWatcher - #Region "Events" Public Shared Event PayPalIpnReceived As EventHandler(Of PayPalIpnItemFoundEventArgs) #End Region - #Region "Methods" Protected Overridable Sub OnPayPalIpnReceived(transaction As Transaction, log As Log) If transaction Is Nothing Then Return End If RaiseEvent PayPalIpnReceived(Me, New PayPalIpnItemFoundEventArgs(transaction, log)) End Sub Public MustOverride Sub StartWatching() Public MustOverride Sub StopWatching() #End Region End Class End Namespace
-
Create Code\Watchers\WcfQueuingPayPalIpnItemWatcher.cs
CopyC#-#region Usings using System; using System.ServiceModel; using Rolosoft.IpnEasy.Net; using Rolosoft.IpnEasy.Net.PayPal; #endregion namespace MyProject.Code.Watchers { [ServiceBehavior] internal class WcfQueuingPayPalIpnItemWatcher : PayPalIpnItemWatcher, IIpnReceivedContract , IDisposable { - #region Privates private readonly ServiceHost _serviceHost; private bool _disposed; #endregion - #region Ctors public WcfQueuingPayPalIpnItemWatcher() { _serviceHost = new ServiceHost(typeof (WcfQueuingPayPalIpnItemWatcher)); } #endregion - #region Methods - #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion - #region IIpnReceivedContract Members /// <summary> /// <para> /// The Service Contract interface for WCF implementations of PayPal IPN integration.* /// </para> /// <para> /// Submit IPN data to queue and for onward processing. /// </para> /// </summary> /// <param name="transaction">PayPal IPN transaction data</param> /// <param name="log">The <see cref="T:Rolosoft.IpnEasy.Net.Log"/> of the PayPal IPN interaction</param> [OperationBehavior(TransactionAutoComplete = true)] public void SubmitIpn(Transaction transaction, Log log) { OnPayPalIpnReceived(transaction, log); } #endregion public override void StartWatching() { //sEndpointAddress is the location of the MSMQ. var sEndpointAddress = "net.msmq://localhost/paypaltransactions"; var endpointUri = new Uri(sEndpointAddress); var serviceBinding = new NetMsmqBinding("ThisMsmqBinding"); _serviceHost.AddServiceEndpoint(typeof(IIpnReceivedContract), serviceBinding, endpointUri); _serviceHost.Open(); } public override void StopWatching() { if (_serviceHost != null) _serviceHost.Close(); } public void Close() { Dispose(); } ~WcfQueuingPayPalIpnItemWatcher() { Dispose(); } protected virtual void Dispose(bool disposing) { if (!disposing) return; if (_disposed) return; if (_serviceHost == null) return; _serviceHost.Close(); _disposed = true; } #endregion } }
CopyVB.NET-#Region "Usings" Imports System.ServiceModel Imports Rolosoft.IpnEasy.Net Imports Rolosoft.IpnEasy.Net.PayPal #End Region Namespace MyProject.Code.Watchers <ServiceBehavior> _ Friend Class WcfQueuingPayPalIpnItemWatcher Inherits PayPalIpnItemWatcher Implements IIpnReceivedContract Implements IDisposable - #Region "Privates" Private ReadOnly _serviceHost As ServiceHost Private _disposed As Boolean #End Region - #Region "Ctors" Public Sub New() _serviceHost = New ServiceHost(GetType(WcfQueuingPayPalIpnItemWatcher)) End Sub #End Region - #Region "Methods" - #Region "IDisposable Members" Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region - #Region "IIpnReceivedContract Members" ''' <summary> ''' <para> ''' The Service Contract interface for WCF implementations of PayPal IPN integration.* ''' </para> ''' <para> ''' Submit IPN data to queue and for onward processing. ''' </para> ''' </summary> ''' <param name="transaction">PayPal IPN transaction data</param> ''' <param name="log">The <see cref="T:Rolosoft.IpnEasy.Net.Log"/> of the PayPal IPN interaction</param> <OperationBehavior(TransactionAutoComplete := True)> _ Public Sub SubmitIpn(transaction As Transaction, log As Log) OnPayPalIpnReceived(transaction, log) End Sub #End Region Public Overrides Sub StartWatching() 'sEndpointAddress is the location of the MSMQ. Dim sEndpointAddress = "net.msmq://localhost/paypaltransactions" Dim endpointUri = New Uri(sEndpointAddress) Dim serviceBinding = New NetMsmqBinding("ThisMsmqBinding") _serviceHost.AddServiceEndpoint(GetType(IIpnReceivedContract), serviceBinding, endpointUri) _serviceHost.Open() End Sub Public Overrides Sub StopWatching() If _serviceHost IsNot Nothing Then _serviceHost.Close() End If End Sub Public Sub Close() Dispose() End Sub Protected Overrides Sub Finalize() Try Dispose() Finally MyBase.Finalize() End Try End Sub Protected Overridable Sub Dispose(disposing As Boolean) If Not disposing Then Return End If If _disposed Then Return End If If _serviceHost Is Nothing Then Return End If _serviceHost.Close() _disposed = True End Sub #End Region End Class End Namespace
-
The WCF listener is now ready for use. In the following example we show how to host the WCF listener in a Windows Service and invoke a Windows Workflow (WF) on receipt of a PayPal IPN.
Create Code\Services\PaymentWorkRouter.cs
CopyC#using System; using System.Collections.Generic; using System.Diagnostics; using System.ServiceProcess; using System.Threading; using System.Workflow.Runtime; using MyProject.Code.Watchers; using MyProject.WorkFlows.PayPalIpnWorkflow; //This code sample is not available. Please implement your own workflow namespace MyProject.Services.PaymentWorkRouter.Services { partial class PayPalIpnListener : ServiceBase { - #region Privates //Note: Define your own configuration section handler to set up WF connections, tracking and persistence services. private const string ConfigSectionName = "MyRuntime"; private readonly PayPalIpnItemWatcher _watcher; #endregion - #region Ctors public PayPalIpnListener() { _watcher = new WcfQueuingPayPalIpnItemWatcher(); PayPalIpnItemWatcher.PayPalIpnReceived += PayPalIpnItemWatcherPayPalIpnReceived; InitializeComponent(); } #endregion - #region Methods static void PayPalIpnItemWatcherPayPalIpnReceived(object sender, PayPalIpnItemFoundEventArgs e) { using (var reset = new AutoResetEvent(false)) { using (var runtime = new WorkflowRuntime(ConfigSectionName)) { runtime.WorkflowCompleted += delegate { reset.Set(); }; runtime.WorkflowTerminated += delegate { reset.Set(); }; runtime.WorkflowAborted += delegate { reset.Set(); }; // Pass the PayPal IPN transaction data into the workflow var parms = new Dictionary<string, object> { {"PayPalTransaction", e.PayPalTransaction} }; //Note: Please implment your own workflow and invoke it here. var instance = runtime.CreateWorkflow(typeof (IpnManager), parms); // Start the workflow & process the IPN instance.Start(); reset.WaitOne(); } } } protected override void OnStart(string[] args) { _watcher.StartWatching(); base.OnStart(args); } protected override void OnStop() { _watcher.StopWatching(); WriteLineConsole("Stopped looking for work"); base.OnStop(); } protected override void OnPause() { _watcher.StopWatching(); base.OnPause(); } protected override void OnContinue() { _watcher.StartWatching(); base.OnContinue(); } #endregion } }
CopyVB.NETImports System.Collections.Generic Imports System.Diagnostics Imports System.ServiceProcess Imports System.Threading Imports System.Workflow.Runtime Imports MyProject.Code.Watchers Imports MyProject.WorkFlows.PayPalIpnWorkflow 'This code sample is not available. Please implement your own workflow Namespace MyProject.Services.PaymentWorkRouter.Services Partial Class PayPalIpnListener Inherits ServiceBase - #Region "Privates" 'Note: Define your own configuration section handler to set up WF connections, tracking and persistence services. Private Const ConfigSectionName As String = "MyRuntime" Private ReadOnly _watcher As PayPalIpnItemWatcher #End Region - #Region "Ctors" Public Sub New() _watcher = New WcfQueuingPayPalIpnItemWatcher() AddHandler PayPalIpnItemWatcher.PayPalIpnReceived, AddressOf PayPalIpnItemWatcherPayPalIpnReceived InitializeComponent() End Sub #End Region - #Region "Methods" Private Shared Sub PayPalIpnItemWatcherPayPalIpnReceived(sender As Object, e As PayPalIpnItemFoundEventArgs) Using reset = New AutoResetEvent(False) Using runtime = New WorkflowRuntime(ConfigSectionName) runtime.WorkflowCompleted += Function() Do reset.[Set]() End Function runtime.WorkflowTerminated += Function() Do reset.[Set]() End Function runtime.WorkflowAborted += Function() Do reset.[Set]() End Function ' Pass the PayPal IPN transaction data into the workflow Dim parms = New Dictionary(Of String, Object)() From { _ {"PayPalTransaction", e.PayPalTransaction} _ } 'Note: Please implment your own workflow and invoke it here. Dim instance = runtime.CreateWorkflow(GetType(IpnManager), parms) ' Start the workflow & process the IPN instance.Start() reset.WaitOne() End Using End Using End Sub Protected Overrides Sub OnStart(args As String()) _watcher.StartWatching() MyBase.OnStart(args) End Sub Protected Overrides Sub OnStop() _watcher.StopWatching() WriteLineConsole("Stopped looking for work") MyBase.OnStop() End Sub Protected Overrides Sub OnPause() _watcher.StopWatching() MyBase.OnPause() End Sub Protected Overrides Sub OnContinue() _watcher.StartWatching() MyBase.OnContinue() End Sub #End Region End Class End Namespace
