Hi,
I just figured out a way to add credit cards to a contact in a PCI compliant way. For a reason I can’t explain this API is not documented by Infusionsoft and I wanted to share my findings.
Important:
Before I show you how to do it, I would like to make you aware that this way is not perfect. At the bottom of this post I have included a feature request for IS to add a MUCH better way to handle PCI compliance on custom order forms. Pleas check it out and contact IS to request. If we show there is big demand for it, perhaps they will do it soon.
Background:
It’s always been possible to charge orders through the API after adding credit cards through DataService.add. But the problem is that it has to be done server-side, which means you have to send credit card details to your own sever.
That classifies you as a “credit card data processor” according to PCI and you become liable to the most stringent requirements of PCI compliance SAQ D. These are VERY difficult to pass.
Well, it turns out IS has an undocumented API to add credit cards through an AJAX direct post.
This makes you liable for the SAQ A-EP compliance. This compliance is still a pain in the… and you’ll have to do quarterly security scans of your pages, but it is much easier to pass than SAQ D. It makes it feasible (although still unnecessarily annoying) to create custom order forms and be PCI compliant.
With this post I want to share what I’ve learned about this process.
The PCI Compliant Process:
- Make a server-side API call to CreditCardSubmissionService.requestSubmissionToken to get a submission token
- Do a client-side AJAX HTTP Post to IS’ endpoint with the credit card details to create a card
- Make server-side API call to CreditCardSubmissionService.requestCreditCardId to retrieve the IS credit card ID
- Make a server-side API call to charge the order using the credit card id
Here is an example of how this could be done in python and the browser console.
In [40]: infusionsoft.CreditCardSubmissionService('requestSubmissionToken', 727953, "https://your.server.com/cc_success", "https://your.server.com/cc_failure")`
Out[40]: '8fa6567f-fbbe-4c8f-a739-85ebe31bfa55'
The returned value is the unique token. This should be used when you POST the credit card info to IS. If the POST is successful, IS will redirect you to the success url and if it fails to the failure one.
Here is the AJAX Post request that I did in the web inspector console. I used the same field names as in the CreditCard table reference. I assume you can submit all the other fields that are saved for a credit card, but I haven’t tested it yet.
data = {'token': '8fa6567f-fbbe-4c8f-a739-85ebe31bfa55', 'NameOnCard': "Test User Browser 6", 'CardNumber': "4698226949164271", 'CardType': "Visa", 'CVV2': "123", 'ExpirationMonth': "12", 'ExpirationYear': "2020"}
var success = function( data, textStatus, xhr ) {
console.log("Success: " + xhr.getAllResponseHeaders());
};
$.post("https://XXXXXX.infusionsoft.com/app/creditCardSubmission/addCreditCard", data, success).fail(function( data, textStatus, xhr ) {
console.log("Request failed, but we expected that");
});
The result was:
19:07:44.454 2018_04_ta_secure_order_exception_f8481e8:1 Failed to load https://XXXX.infusionsoft.com/app/creditCardSubmission/addCreditCard: Redirect from 'https://XXXX.infusionsoft.com/app/creditCardSubmission/addCreditCard' to 'https://your.server.com/cc_success?cardType=Visa&expirationMonth=12&expirationYear=2020' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://your.server.com' is therefore not allowed access.
19:07:44.454 VM550:6 Request failed, but we expected that
Here are the response headers IS sent (copied from the browser’s web inspector Network tab):
cache-control: no-cache, no-store
content-language: en-US
content-length: 0
date: Mon, 07 May 2018 11:07:43 GMT
expires: Mon, 07 May 2018 11:07:44 GMT
location: https://your.server.com/cc_success?cardType=Visa&expirationMonth=12&expirationYear=2020
pragma: no-cache
server: Apache-Coyote
set-cookie: JSESSIONID=9D7CF429C25339F0E58B218C50E996A9; Path=/; Secure; HttpOnly
set-cookie: app-lb=!w1WF0ptGvp1qSWAUzgOrpnnCD5SNotHmtKGR0OLseFbJQHIY1bhScwAPLLhRbd5QsRA33agIZou19Dct4X4lY1b+rFLYQw/gj0c9INBsj9ZIG+r1IDAag19U2XnlOObKe0QBzTl9cVZ8jentTSlJxUgl0q+nIZ8=; path=/; Httponly; Secure
status: 302
strict-transport-security: max-age=31536000; includeSubDomains
vary: Accept-Encoding
x-frame-options: SAMEORIGIN
IS is redirecting the request to the success URL. The card has been added successfully.
But as you can see the AJAX request fails due to cross-origin policy, because IS doesn’t set the ‘Access-Control-Allow-Origin’ on this POST result.
Although the request fails and we see an ugly error in the console, IS is still adding the card successfully. So we don’t need to worry about that.
Next we can retrieve the card ID server-side with the same token we received earlier:
In [41]: infusionsoft.CreditCardSubmissionService('requestCreditCardId', '8fa6567f-fbbe-4c8f-a739-85ebe31bfa55')
Out[41]:
{ 'capturedVerificationCode': 'false',
'cardType': 'Visa',
'companyName': '',
'contactId': '727953',
'creditCardValidator': '',
'email': '',
'expirationMonth': '12',
'expirationYear': '2020',
'externallyUpdated': 'false',
'id': '101073',
'last4': '4271',
'status': '3',
'statusMsg': 'Validated 5/7/2018',
'verificationCode': ',
[...]
'}
As you can see the card was added successfully. The real output included a lot more fields, such as billing address, but I redacted them, because they were not set (since we didn’t set them in the POST request).
Now the card is added and we can create and charge an order with it.
As I said, the advantage of this process is that the card information is not processed by you and your PCI scope is dramatically reduced.
Feature Request: An iframe method to add a credit card
Although this is better than the DataService.add method, it still creates a certain amount of risk for our customers and requires us IS users to have higher security standards on our websites.
The best way to add a credit card form is through an iFrame, which makes you liable for the simplest PCI compliance form SAQ A. It’s just 20 questions you have to answer.
Most other payment processor such as stripe, braintree and so on are offering this:
The only way to be liable for SAQ A with Infusionsoft is to use the hosted webforms, but I consider that a non-option in 2018…
- The URLs are not trust worthy for our customers, because they are not on our domain
- The pages are not attractive and not mobile responsive
And yes, I know there are services that make the forms prettier, but is it reasonable that we need expensive third party options for something so simple?
Questions & Requests to IS:
-
Can you please properly document this?
-
Can you please add the Access-Control-Allow-Origin header to the POST request page?
Call to action to all IS users:
PLEASE reply here, share this and contact IS requesting for them to add an iFrame option.
Further Resources:
This guide from PCI explains the PCI challenge and the different compliance forms in detail: