I'm integrating a number of e-comm sites into different banks and decided the easiest method was to add in the dotnetcharge (www.dotnetcharge.com) library. It works well and means I can keep much of my code the same for each bank type and transaction. However, their support is a bit sucky (4 emails sent, 1 reply) and I'm utterly baffled on the 3D Secure issue.
Does anyone have experience with dotnetcharge and 3D Secure? I have set the MerchantURL and the actual 3D Secure screen comes up - but I'm unsure how to get the system to 'flow' properly. Does anyone have any code examples or even pointers in the right direction? Failing that, does anyone know how to make support respond!
This particular integration is with SagePay, which also has God-awful documentation and support.
Code for reference is as follows;
Dim Amount As Decimal = ordertotal
' ApplySecure3D options:
' 0 = If 3D-Secure checks are possible and rules allow, perform the checks and apply the authorization rules.
' 1 = Force 3D-Secure checks for this transaction only (if your account is 3D-enabled) and apply rules for authorization.
' 2 = Do not perform 3D-Secure checks for this transaction only and always authorize.
' 3 = Force 3D-Secure checks for this transaction (if your account is 3D-enabled) but ALWAYS obtain an auth code, irrespective of rule base.
Dim ProtxLogin As String = "xxx"
Dim ProtxPassword As String = "xxx"
Dim ProtxApply3DSecure As Integer = 1
Dim ProtxMerchantURL As String = "https://www.mydomain.com/processing/"
Dim Number As String = txtCardNo.Text '//luhn/mod10 here.
Dim AVS As String = txtCVN.Text
Dim DD As String = "01"
Dim MM As String = ddlValidTo_month.SelectedValue.ToString()
Dim YY As String = ddlValidTo_year.SelectedValue.ToString()
Dim ProcessingResult As Integer = 0
Dim Protx As New dotnetCHARGE.CC()
Protx.Login = ProtxLogin
Protx.Password = ProtxPassword
Protx.ApplySecure3D = ProtxApply3DSecure
Protx.MerchantUrl = ProtxMerchantURL
Dim AVSResponse As String = ""
Dim CVV2 As String = ""
Protx.OrderID = GoogleOrderNumber
Protx.Month = MM
Protx.Year = YY
Protx.TransactionType = dotnetCHARGE.TransactionType.Sale
Protx.Amount = ordertotal
Protx.Number = Number
Protx.Currency = "GBP"
Protx.CustomerID = CustomerId
'//loads of params removed for brevity
Protx.ClientIP = Request.UserHostAddress.ToString()
Protx.CardType = ddlCardType.SelectedValue.ToString()
Protx.Description = "My Order"
Protx.Code = AVS
Protx.TestMode = True
Protx.TransactionType = dotnetCHARGE.TransactionType.Sale
ProcessingResult = Protx.Charge(Processor.Protx)
Help appreciated.
I decided to return to this question to explain how the final result was acheived. Hopefully some SO users will find it useful.
To acheive the correct 'flow' you'll need two pages. You won't realistically be able to do the whole transaction processing in a single page. The first page will have the card entry details; card number, expiry date, CVN, billing address etc. On hitting pay/submit I'd recommend saving the transaction to your datasource as 'unprocessed' or something similiar. Once all your details are saved - no card processing done thus far - redirect with HTTPS to the second page.
Your customer may never know this page exist depending on how you set this up. The second page will have the .netCharge code within it as my question and process the card. When 3D secure is enabled (.Apply3DSecure = 1), the customer will be redirected to their bank to enter some further details and it will return to this second page. It doesn't behave like a postback or a refresh, so do not worry about a returning call to the page processing twice. You will receive 1 of 3 possible statuses; Authorised, Error and Declined. Your page can redirect to further necessary pages (therefore the customer nevers know this middle page exists) or display the results directly on this processing page.
There is one final 'gotcha', which you'll see very quickly. The second page (the processing page) needs the card details to actually process. You can't just pass card details on a form or even a querystring, that's irresponsible. .netCharge comes with an .Encrypt and .Decrypt function; just pass it the value to encrypt and a hash of some sort and save these details temporarily on the first page, read and decrypt on the second page and then remove them. This means the details are secure, are saved for less than 5 seconds in most cases and you have no exposure because they are destroyed.
I hope this helps - if anyone has any questions, just give me a shout.
Related
I am working on a project that is a .NET WebForms solution that contains many PageMethod's to act like an MVC hybrid.
We have a registration service in our product that upon typing your username, will call a service to immediately check if the username has already been chosen (without calling a post-back).
What would be the most appropriate way of preventing someone from mapping out the entire username database by brute-forcing requests for true/false answers on if a string exists as a username.
The best method I can currently think of is throttling requests based on session, which would not completely stop the problem, but like encryption, make the time-cost to effectively succeed not worth it for the intruders.
In my head I was thinking along the lines of:
Public Function SomeFunction() as String
Dim throttle As Throttle = New Throttle()
If Not HttpContext.Current.Session("IsUsernameExistsThrottle") Is Nothing Then
throttle = DirectCast(HttpContext.Current.Session("IsUsernameExistsThrottle"), Throttle)
If Not throttle.SetExpiry Is Nothing Then
If throttle.SetExpiry > DateTime.Now Then
Throw New Exception("Please wait 1 minute before accessing this functionality again.")
Else
throttle.SetExpiry = Nothing
End If
Else
throttle.RequestCount += 1
If throttle.RequestCount >= 10 Then
throttle.SetExpiry = DateTime.Now.AddMinutes(1)
End If
End If
Else
throttle.SetExpiry = Nothing
throttle.RequestCount = 1
End If
HttpContext.Current.Session("IsUsernameExistsThrottle") = throttle
Return SomeValue
End Function
Public Class Throttle
Public RequestCount As Integer
Public SetExpiry As DateTime?
End Class
What this function would do is allow the first 10 requests for free, and then throttle every request afterwards to 1 per minute.
Any thoughts or suggestions, or recommendations for best practice?
Thanks,
I am trying to use formula to open a document from within a field when it comes to validation. Basically i have set up a database that checks for duplicate entries and I want the database to (if a form is created) check that it doesn't already exist and if it does ask the user if it wants to open the form. I have got it all set up and acquired the UNID but when I come to save the document to test (with everything working perfectly) it gives the following error
#commands and other UI functions are not allowed in this context
Not sure what to try. Any help would be appreciate. The command I used is as follows
#Command([EditDocument];1;Unid) //where Unid is an ID I have acquired previously
This is a task, that cannot be done with formula language. As the message sais: #Commands cannot be used in any of the contexts that could help you with this task.
You need to use LotusScript in any matching event (e.g. QuerySave) to do this Check and open the other document. Here is some example code:
%include "lsconst.lss"
Sub QuerySave( Source as NotesUIDocument, Continue as Variant )
Dim ses as New NotesSession
Dim db as NotesDatabase
Dim ws as New NotesUIWorkspace
Dim doc as NotesDocument
Dim ok as Variant
Dim strOtherUnid as String
Set db = ses.CurrentDatabase
strOtherUnid = Source.Document.OtherUnid(0)
If strOtherUnid <> "" then
Continue = False
ok = Messagebox ("There is already another document, do you want to edit it?" , MB_YESNO + MB_ICONQUESTION, "QUESTION" )
if ok = IDYES then
Set doc = db.GetDocumentByUNID( strOtherUnid )
Call ws.EditDocument( False, doc )
End if
End if
End Sub
This code is not tested at all, just to be used as a starting point. It needs a field called "OtherUnid" that contains the universalid of the other document.
I am looking for a way to use Campaign Monitor's API in my ASP.NET/VB Web application.
I have not used any API before, thus reading their documentation is very difficult to understand.
If anyone has used it and is able to provide some instructions I would appreciate it; or if someone has some general usage instructions (if applied on any APi), be my guest! :)
I know this is not the typical "I have a problem and this is my problem and here's my effort so far" but any help would be much appreciated.
You can also use the Campaign Monitor API client library which is available on Nuget:
AuthenticationDetails auth = new ApiKeyAuthenticationDetails(apiKey);
var fields = new List<SubscriberCustomField>() {
new SubscriberCustomField() { Key = "MyCustomField", Value = myVal }
};
var subscriber = new Subscriber(auth, listId);
subscriber.Add(email, fullName, fields, false);
I use campaign monitor for populating subscriber lists.
There are two methods to post your subscribers to lists. I'm going to stick to the simplest one. Let's round up somethings you need first.
You'll need an API key (which I am sure you have).
You'll need to create a subscribers list and after you create this
list you'll need the list ID. To get the ID (which is wierd).You'll
need to click into your subscriber list. This look for this towards
the top. Single opt-in list (change name/type) Note: You are not
going to change the name or edit anything but you have to click in
here to get the ID. On the third section you will see this: API
Subscriber List ID. If you're using the API, you'll need this ID to
access this list. 000x0000xx0x0xx00x00xx (just an example.)
You'll need a form to capture Name and Email. You'll need your listid which
you got in the previous point.
Then you'll need to code a communication object.
If you are doing a straight forward call you'll need the name, email, and listid.
ListID ="000x0000xx0x0xx00x00xx";
Email ="JoeM#somethingemail.com";
Name = "Joe Middle";
APIKey = yourAPIKey;
APIURL = "http://api.createsend.com/";
ApiCall = variables.APIURL;
ApiCall &= "api/api.asmx/Subscriber.Add?ApiKey=" & variables.APIKey;
ApiCall &= "&ListID=" & URLEncodedFormat(arguments.ListID);
ApiCall &= "&Email=" & URLEncodedFormat(arguments.Email);
ApiCall &= "&Name=" & URLEncodedFormat(arguments.Name);
Once you have your url build you use whatever method .net uses to post http.
Then you'll want to code for success or fail and do something with that info. post to http and call the result. apiResult.
apiResult = xmlParse(apiResult.fileContent);
try {intCount = ArrayLen(apiResult.Result.XMLChildren);}
catch(Any e){intCount = 0;}
if (intCount gt 0){apiResult = apiResult.Result.xmlChildren;}
// Error handling
if ( apiResult[1].xmlName eq "Code" and apiResult[2].xmlName eq "Message" ){
returnStruct['blnSuccess'] = 0;
returnStruct['errorCode'] = apiResult[1].xmlText;
returnStruct['errorMessage'] = apiResult[2].xmlText;
}
// Success
else {
// Return str
returnStruct['blnSuccess'] = 1;
returnStruct['returnString'] = apiResult.Result.xmlText;
}
The code above was adapted from coldfusion and I didn't build it but it is cfscript which is not CFML and you can kind of interpret what is happening.
If you adapt this to .NET then all you are missing is your HTTP call stuff method.
To check log into Campaign Monitor and click on your list. You should see additions showing up, if not it is either you API key (not usually the case), your listID (could be the case), your code (most likely culprit).
This was hammered out in a hurry so apologies if the flow is weird.
Good luck!
I have got a many to many relationship, briefly
Cases -----< CaseSubjectRelationships >------ CaseSubjects
More fully:
Cases(ID, CaseTypeID, .......)
CaseSubjects(ID, DisplayName, CRMSPIN)
CaseSubjectsRelationships(CaseID, SubjectID, PrimarySubject, RelationToCase, ...)
In my many-to-many link table are additional properties relating to the subject's association with the specific case - such as, start date, end date, free-text relationship to case (observer, creator, etc)
An Entity Framework data model has been created - ASP.NET version 4.0
I have a WCF service with a method called CreateNewCase which accepts as its parameter a Case object (an entity created by the Entity Framework) - its job is to save the case into the database.
The WCF service is invoked by a third party tool. Here is the SOAP sent:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<CreateNewCase xmlns="http://tempuri.org/">
<c xmlns:a="http://schemas.datacontract.org/2004/07/CAMSModel">
<a:CaseSubjectsRelationships>
<a:CaseSubjectsRelationship>
<a:CaseSubject>
<a:CRMSPIN>601</a:CRMSPIN>
<a:DisplayName>Fred Flintstone</a:DisplayName>
</a:CaseSubject>
<a:PrimarySubject>true</a:PrimarySubject>
<a:RelationToCase>Interested</a:RelationToCase>
<a:StartDate>2011-07-12T00:00:00</a:StartDate>
</a:CaseSubjectsRelationship>
<a:CaseSubjectsRelationship>
<a:CaseSubject>
<a:CRMSPIN>602</a:CRMSPIN>
<a:DisplayName>Barney Rubble</a:DisplayName>
</a:CaseSubject>
<a:RelationToCase>Observer</a:RelationToCase>
<a:StartDate>2011-07-12T00:00:00</a:StartDate>
</a:CaseSubjectsRelationship>
</a:CaseSubjectsRelationships>
<a:CaseType>
<a:Identifier>Change of Occupier</a:Identifier>
</a:CaseType>
<a:Description>Case description</a:Description>
<a:Priority>5</a:Priority>
<a:QueueIdentifier>Queue One</a:QueueIdentifier>
<a:Title>Case title</a:Title>
</c>
</CreateNewCase>
</s:Body>
</s:Envelope>
The WCF engine deserializes this into a Case entity for me correctly and when I look in the debugger everything is set up properly.
What I want to do, is only create a new CaseSubject if there is not already an entry in the database with that CRMSPIN specified (CRMSPIN is a reference number from a central customer database)
So, in the below example, I want to see if I already have an entry in CaseSubjects for somebody with CRMSPIN 601 and if I do, I don't want to create another (duplicate) entry but instead make the new case link to the existing subject (although a new row will need, obviously, need creating in CaseSubjectsRelationships with the specific 'additional' information such as relationship etc)
Here is the .NET code I have tried to do this.
Public Class CamsService
Implements ICamsService
Public Function CreateNewCase(c As CAMSModel.Case) As String Implements ICamsService.CreateNewCase
Using ctx As New CAMSEntities
' Find the case type '
Dim ct = ctx.CaseTypes.SingleOrDefault(Function(x) x.Identifier.ToUpper = c.CaseType.Identifier.ToUpper)
' Give an error if no such case type '
If ct Is Nothing Then
Throw New CaseTypeInvalidException(String.Format("The case type {0} is not valid.", c.CaseType.Identifier.ToString))
End If
' Set the case type based on that found in database: '
c.CaseType = ct
For Each csr In c.CaseSubjectsRelationships
Dim spin As String = csr.CaseSubject.CRMSPIN
Dim s As CaseSubject = ctx.CaseSubjects.SingleOrDefault(Function(x) x.CRMSPIN = spin)
If Not s Is Nothing Then
' The subject has been found based on CRMSPIN so set the subject in the relationship '
csr.CaseSubject = s
End If
Next
c.CreationChannel = "Web service"
c.CreationDate = Now.Date
' Save it '
ctx.AddToCases(c)
ctx.SaveChanges()
End Using
' Return the case reference '
Return c.ID.ToString
End Function
End Class
As you can see, instead the For Each loop, I try to get a subject based on the CRMSPIN and if I get something, then I update the "CaseSubject" entity. (I have also tried csr.SubjectID = s.ID instead of setting the whole entity and also I have tried setting them both!).
However, even when putting a breakpoint on the ctx.SaveChanges() line and looking at how the subjects are set up and seeing in the debugger that it looks fine, it is always creating a new row in the CaseSubjects table.
I can see in principle this should work - you'll see I've done exactly the same thing for Case Type - I have picked the identifier sent in the XML, found the entity with that identifier via the context, then changed the case's .CaseType to the entity I found. When it saves, it works perfectly and as-expected and with no duplicated rows.
I'm just having trouble trying to apply the same theory to one side of a many-to-many relationship.
Here are some (hopefully relevant) extracts from the .edmx
<EntitySet Name="Cases" EntityType="CAMSModel.Store.Cases" store:Type="Tables" Schema="dbo" />
<EntitySet Name="CaseSubjects" EntityType="CAMSModel.Store.CaseSubjects" store:Type="Tables" Schema="dbo" />
<EntitySet Name="CaseSubjectsRelationships" EntityType="CAMSModel.Store.CaseSubjectsRelationships" store:Type="Tables" Schema="dbo" />
<AssociationSet Name="FK_CaseSubjectsRelationships_Cases" Association="CAMSModel.Store.FK_CaseSubjectsRelationships_Cases">
<End Role="Cases" EntitySet="Cases" />
<End Role="CaseSubjectsRelationships" EntitySet="CaseSubjectsRelationships" />
</AssociationSet>
<AssociationSet Name="FK_CaseSubjectsRelationships_CaseSubjects" Association="CAMSModel.Store.FK_CaseSubjectsRelationships_CaseSubjects">
<End Role="CaseSubjects" EntitySet="CaseSubjects" />
<End Role="CaseSubjectsRelationships" EntitySet="CaseSubjectsRelationships" />
</AssociationSet>
EDIT: The property setters for the CaseSubject property of the CaseSubjectsRelationships object:
/// <summary>
/// No Metadata Documentation available.
/// </summary>
<XmlIgnoreAttribute()>
<SoapIgnoreAttribute()>
<DataMemberAttribute()>
<EdmRelationshipNavigationPropertyAttribute("CAMSModel", "FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject")>
Public Property CaseSubject() As CaseSubject
Get
Return CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of CaseSubject)("CAMSModel.FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject").Value
End Get
Set
CType(Me, IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of CaseSubject)("CAMSModel.FK_CaseSubjectsRelationships_CaseSubjects", "CaseSubject").Value = value
End Set
End Property
You didn't specify what context model are you working with, so I'll assume you're using the default (ie. you don't have some explicit .tt files to generate your entities).
So, basically, this is what I think is happening.
In your code, when you fetch something from context:
Dim ct = ctx.CaseTypes.SingleOrDefault(Function(x) x.Identifier.ToUpper = c.CaseType.Identifier.ToUpper)
this ct is in context. The method argument that you deserialized from service (the c) is not in context. You can regard the context as the "object tracking and fetching" entity, that makes sure that everything attached to it can know about any changes, if it's new, deleted etc.
So, when you get to the part:
' Set the case type based on that found in database: '
c.CaseType = ct
at the moment you assign something that's attached to something not attached, the unattached object will get pulled into context as well - there can't be "partially" attached entities - if it's attached, everything it references has to be attached as well. So, this is the moment where the c gets "dragged" into the context (implicitly). When it enters the context, it will get marked as "new" since it doesn't know anything about it yet (it has no knowledge of it, no change tracking info...).
So, now that everything about that object c is in context, when you query the context for this:
Dim s As CaseSubject = ctx.CaseSubjects.SingleOrDefault(Function(x) x.CRMSPIN = spin)
it will figure that indeed there is an object with that CRMSPIN and it's already attached - "hey, no need to go to database, I already have this!" (trying to be smart and avoid a db hit), and it will return your own object.
Finally, when you save everything, it will be saved, but your attached c and all of it's child objects that are marked as 'new' will be inserted instead of updated.
The easiest fix would be to first query everything you need from context, and only then start assigning it to properties of your object. Also, take a look at UpdateCurrentValues, it may also be helpful...
OK: So the resolution to this was a combination of what #veljkoz said in his answer (which was very useful to help me out to reach the final resolution, but on its own was not the full resolution)
By moving the For Each loop to the first thing done before anything else (As hinted by #veljkoz), that got rid of the Collection was modified, enumeration may not continue error I was getting when I set csr.CaseSubject = Nothing.
It also turned out to be important to not attach entities (e.g. not to set csr.CaseSubject to an entity but only to Nothing) but instead to use the .SubjectID property. A combination of all the above led me to the following code, which works perfectly and doesn't try to insert duplicate rows.
+1 to #veljkoz for the assist but also note that the resolution includes setting the entity reference to Nothing and using the ID property.
Full, working code:
Public Function CreateNewCase(c As CAMSModel.Case) As String Implements ICamsService.CreateNewCase
Using ctx As New CAMSEntities
' Subjects first, otherwise when you try to set csr.CaseSubject = Nothing you get an exception '
For Each csr In c.CaseSubjectsRelationships
Dim spin As String = csr.CaseSubject.CRMSPIN
Dim s As CaseSubject = ctx.CaseSubjects.SingleOrDefault(Function(x) x.CRMSPIN = spin)
If Not s Is Nothing Then
' The subject has been found based on CRMSPIN so set the subject in the relationship '
csr.CaseSubject = Nothing
csr.SubjectID = s.ID
End If
Next
' Find the case type '
Dim ct = ctx.CaseTypes.SingleOrDefault(Function(x) x.Identifier.ToUpper = c.CaseType.Identifier.ToUpper)
' Give an error if no such case type '
If ct Is Nothing Then
Throw New CaseTypeInvalidException(String.Format("The case type {0} is not valid.", c.CaseType.Identifier.ToString))
End If
' Set the case type based on that found in database: '
c.CaseType = ct
c.CreationChannel = "Web service"
c.CreationDate = Now.Date
' Save it '
ctx.AddToCases(c)
ctx.SaveChanges()
End Using
' Return the case reference '
Return c.ID.ToString
End Function
I've got an ASP.Net application, which starts throwing NUllReferenceExceptions, after a long period of running. The code in question is used early on in each individual session, where we're trying to establish some kind of referrer information. The thing is, I can't fathom out what can throw this exception.
The method in question (topmost in the stack trace) is:
Private Function ResolveReferrer(ByVal wrRequest As HttpRequest) As Referral
'1) If we don't find a domain, try and get a match on any query strings
If wrRequest.QueryString.Count > 0 Then
For Each item As Referral In Me
For Each sKey As String In wrRequest.QueryString.Keys
If Not sKey Is Nothing AndAlso item.Names.Contains(sKey.ToLower) Then
Return item
End If
Next sKey
Next item
End If
Dim strSubDomain As String = Utility.RequestSubDomain(wrRequest.Url)
'2) If we don't find one on the domain, see if we can find the domain in query string
If Not wrRequest.QueryString.Item("domain") Is Nothing Then
strSubDomain = wrRequest.QueryString.Item("domain")
strSubDomain = HttpUtility.UrlDecode(strSubDomain)
' OK found a "domain" query string, so make up a referrer object to return
' ... just use the domain we've found for all the parameters
Dim oRef As New Referral(strSubDomain, strSubDomain, strSubDomain)
Return oref
End If
'3) If no query string of "domain", then see if the referring field is presented by the browser
If Not wrRequest.UrlReferrer Is Nothing Then
Dim sURL As String = wrRequest.UrlReferrer.ToString
strSubDomain = Utility.RequestSubDomain(wrRequest.UrlReferrer)
Dim oRef As New Referral(sURL, sURL, strSubDomain)
Return oRef
End If
'4) See if we can find the domain defined in the web.config
For Each item As Referral In Me
' See if we can find a referrer from the domain name
If String.Compare(strSubDomain, item.FromDomain, False) = 0 Then
Return item
End If
Next item
'5) If we still can't find one, make one up with a value of "Unknown"
Return New Referral("Unknown", "Unknown", "Unknown", "Unknown")
End Function
The class that this is a part of inherits from ArrayList. I've checked, and the only things added to this ArrayList are instances of the Referral class (which has multiple constructors, all simple).
What we do know is that, we can have a request that comes in with no referrer information, and it causes the exception to be thrown. At the same time, if a request with a referrer comes in, it works fine. In neither case is anything passed in the query string (so I think you can skip down to the '3 comment.
So, my question is, what in this method can cause a NullReferenceException to be thrown? If you need additional snippets added, or class definitions, just shout.
Utility.RequestSubDomain has reasonable complexity, so I doubt that it's being inlined and removed from the stack trace. And it's top is:
Public Shared Function RequestSubDomain(ByVal uri As System.Uri) As String
If uri Is Nothing Then
Return ""
End If
Any help, or suggestions for finding more information would be appreciated. It's obviously (as with so many problems) only happening in production, so I don't want to switch on debugging.
I took a good hard look and the only two things that come to mind that seem like the most likely are:
wrRequest could be null.
The ArrayList could have null values in it resulting in an enumerator that returns null values.
If it were me I would first focus my attention on this section. Is there any way possible that the Me.GetEnumerator will return an enumerator with one of the items having a null value?
For Each item As Referral In Me
item.Names ' Can item be null here causing the exception on the getter of Names?
Next item
As it turned out, there was a NULL in the arraylist - it turns out that, in certain circumstances, multiple threads were working on the same object (which inherits from arraylist) and calling Add(). So the null was appearing because the internal index was being incremented twice by different threads.