Not Storing Credit Card Numbers In Domino
For the past few weeks I've been adding a shopping cart to an existing Domino application. The existing system stored credit card numbers in plain text, which - as I'm sure you know - is a massive huge great no-no when it comes to being PCI DSS compliant.
I'll talk more about how I implemented a gateway part of the cart another day. For now I want to discuss how I avoided, at all costs, storing the credit card number entered by the user.
Normally, to stop a field being saved on the backend document you'd just make it a Computed For Display field, right? Like this:
The field itself is hidden, but we display an input field of the same name to the user using Passthru HTML. This way they can enter a value, we can reference it in our WebQuerySave code and it never gets stored.
Normally it would be as simple as that, but in my case things were complicated. The developer of the existing system had turned on the "Generate HTML For All Fields" Form option. I had no idea why and didn't dare turn it off for fear of what affect this might have on what was already a monstrously-complicated system.
The trouble with the "generate all fields" Form option is that as well as our own PassThru field Domino generates another (type="hidden") one towards the bottom of the HTML form with the same name. It does this because our CFD field is hidden.
When the Form is submitted to the web server the server only accepts the value from the bottom-most field (always ""), so the value entered by the user is never available to us.
To get round this I could use JavaScript to see if there are two fields called CreditCardNumber and, if so, set the value of the second to the value of the first before submitting the form.
var f = document.forms[0]; if (f.CreditCardNumber && f.CreditCardNumber.length>0){ f.CreditCardNumber[1].value = f.CreditCardNumber[0].value; }
BUT. Could I live with myself if I was responsible for an e-commerce site that relied on JavaScript to work? Those who know me well enough will know the answer is a resounding NO!!!
The Only Other Solution
My only option was to un-hide the CreditCardNumber field and make it editable. The issue then became how to make sure it never ever ever got stored on the document. To do this I removed it at the very end of the Java WQS agent. Here's how:
public class JavaAgent extends AgentBase { private Document document; public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Database database = session.getCurrentDatabase(); document = agentContext.getDocumentContext(); //Do the credit card processing part here. } catch(Exception e) { e.printStackTrace(); } finally { try{ document.removeItem("CreditCardNumber"); }catch(Exception ex) { ex.printStackTrace(); } } } }
Notice how we defined the contextual Document outside the NotesMain function? This is so that we can refer to it in the "finally" section. Code inside the "finally" clause will run even if the main code section results in an error for any reason. So, it almost guarantees the field will get removed during saving the document.
Or will it. What if!! What if the Agent fails to run for some reason other than an error in the code? Say the Agent times-out or the server is under heavy traffic and it fails to run completely. What happens then!?
To get round the idea that the Agent might fail to run I added an extra line of @Formula after the call to run the Agent in the Form's WebQuerySave event, like so:
As far as I can tell this is almost fool-proof. What say you? Am I missing something here or can I sleep soundly at night, safe in the knowledge this will always remove the field?
If the "(WebProcessPayment)" agent fails in someway that you don't catch the @SetField won't get run.
Having been down this road over the past 13 years with my eCommerce site, can I just state one thing which you really should make clear for the best security. The Credit Card info (and personal info) should NEVER be stored in the database which is used to run the website.
That never as in don't save it, don't view it, don't edit it. Having talked at UKLUG on the subject of security of Domino sites, having a job in security, having had over £8.2 millon transactions through my sites and having hacked more than a few websites (in the white hat sense) you should never store the info even for an instant.
Customer info is sacrosanct. It is the make or break of any business. Once it gets out you face more trouble than you know. Don't risk it.
The trick here is to use you website as a pass through to a 2nd database where all the info is kept under lock and key and is encrypted to the hilt. Submit to an agent and let that handle the necessary workings. Never save to a document under any circumstances.
Reply
Are you saying that if the Agent called from line 1 of the @Formula in the Form's WebQuerySave Event fails for any reason then the second line of the formula (the @SetField) won't be called?
In this scenario does the document itself get committed to the database at all?
Another idea I had was to set SaveOptions field to "0" on the form submitted and only change it to "1" at the end of the Java agent (after the ccnumber field was removed).
I concur entirely with your approach to not storing CC data. But I'm between a rock and a hard place. You wouldn't believe what a beast of a system the previous developer had come up with. Turning off the "generate all field" option is what I want to do. I just daren't do it.
Reply
Show the rest of this thread
The other option is I go back to the hidden CFD field and use JavaScript to switch values between the fields at the point the form's submitted. I'd rather claw out my own eyeballs though.
Another option is a scheduled agent to look for documents that do have the field on them and let it regularly purge the fields from them.
Reply
Hear hear. When implementing a shopping cart at a privious engaement I opted for using an external party (Ogone) to handle the creditcard stuff.
Reply
Mark Barton and I integrated successfully with PayPal for the same purpose. The trick was converting their php handshake file to a java agent to work properly - but that was 5 years ago now. :-(
Reply
Hi
Try this:
In "Input Translation" for the field just put what ever you want saved in the field instead of what gets posted from the browser.
/brgds Jesper Kiaer
http://nevermind.dk
Reply
But then the Java agent in the WQS can't read the cc number.
Reply
To extend on Dragon's comments, the personal information should not even be stored on any server that is accessible from the web.
I'm working on a credit card processing site for work. We added in TWO new domino servers, each in their own Domino domains and only one of them accessible via http in our dmz, the second is on internal network only, has no http etc.
The two servers are cross certified and when the user enters in credit card and personal data the data is captured via agent and moved to the storage database on the other server.
Reply
Why store the data at all though? On the system I'm working on the Java agent communicates over a URLConnection with a 3rd party gateway which returns a success/failure message and all happens over an SSL connection. No need to store the cc number at all. Passes the responsibility for PCI compliance to somebody else.
Reply
How about this?
Set item = doc.GetFirstItem("CCNumber")
item.SaveToDisk = False
Reply
But what if the agent that that code is in fails to run?
Reply
Show the rest of this thread
Have you tried putting /form (close form tag) at the bottom of the notes form. If I remember correctly, the hidden fields are generated after your markup. Then the auto-generated field should be ignored in the submit.
Reply
Hey Tommy! Long time!!
Closing the form tag like would, in effect, be just the same as turning off the "generate all fields" property on the form. I tried this and all hell broke loose and it just stopped working all together. The old developer used extensive JavaScript that refers to (and sets) the values in these fields. Now, I could re-write all his code but you would NOT believe what a mess it is and I just don't have the scope to mess about with it.
Reply
Show the rest of this thread
Whatever the initial in-process solution is, you might also want to add another mechanism to make sure possible errors in the initial processing won't become catastrophic: add a scheduled agent (5 min) to process all new or modified documents and clear the credit card field in case it's not already cleared (as it should be). Make the agent send you an email as well to alarm you of potential trouble in the initial field handling.
Reply
I am always very wary of scheduled agents doing anything vital. All sorts of things can stop them from running.
Reply
Hey Jake,
Another way to do this is to create the form and fields through HTML and post this HTML form to an agent. This way you are not actually saving a document on the submit, you are just posting the data to the agent. It is then up to your agent, as to how you process the data. This way you can 100% guarantee that the record will only be processed, once the agent runs successfully.
From your code, you appear to be running Java for your agent. With the above solution, you could implement a Java Servlet to handle all the data.
Later
Patrick
Reply