There are a number of hurdles for the merchant checkout/shopping-cart to overcome when accepting credit card transactions. There are a number of obvious and outwardly facing challenges like:
- PCI-DSS
- Acquirer contracts
- Shopping Cart
- Banking
- requirements – payments, recurring payments
Once you make it past, not so technical speed bumps, there are a number of implementation details that follow. On the one hand there is the user experience and how that is implemented by the website; and then there are the many acquirers and the different protocols and payload formats. This is usually referred to as transaction impedance.
What does this mean? How is that implemented in Redis?
Let’s start with the user experience. At some point the user will want to complete the purchase by providing some credit card information that you are going to use to send to an acquirer for processing. Given the number of ways this can be accomplished the best way will be an internal implementation using an iFrame. This way you can encapsulate and reuse the checkout in multiple places within your app.
The iFrame will then POST via REST or Ajax type message to the URL that provided the iFrame. The form data should be validated here and then forwarded to the message broker. I’m suggesting RestMQ is a good option as it uses Redis. The message is put on the message queue. Shortly thereafter a worker daemon that has been blocking on an empty queue will awaken, pull a message from the queue, reformat the message for the acquirer and forward the message to the Acquirer.
Here is where it get’s tricky. Depending on the protocol either the worker is going to wait for a response from the Acquirer in response to the request or the worker is going to move on to the next message in the broker’s queue. This depends on the protocol with the Acquirer. If the Acquirer is implemented in REST or HTTP then the worker can simply wait. Of course there can be as many workers for as many simultaneous connections the acquirer will permit.
On the other hand, many acquirers like to use a single socket and process the transactions in an asynchronous fashion. In which case you’ll need two threads and a cache for the transactions in flight. I know that’s a tough concept…. here goes…
A worker thread pulls a message from the queue, assigns a UUID, stores the transaction in a hash with an expiration date, then writes the message to the async socket.
Some other thread is blocking while reading from the remote socket. When a response is received, the working will queue the response. Another worker will read from that side-queue, locate the UUID, get the request from the cache, parse the response, construct a response for the user checkout and then forward the response to the user’s iFrame.
WOW… and now the interesting stuff. Since the messages in the queue and cache can have expiration dates transactions that timeout can be allowed to timeout all over the system without adversely effecting the overall performance of the system. Historically transaction nodes have allowed transactions to timeout rather than send back errors in response to transactions that timeout. The logic required to insure that the error responses make their way around the “system” are costly. And in fact have demonstrated that users get nutty with the “retry” and they can essentially DDOS your payment system.
This is a pretty exciting design. Tornado and Cyclone use epoll and address the C10K problem nicely. Redis can perform 100K writes per second. Daemon tools can handle keeping the system alive. Redis offers some replication although it is master-slave so HA is still possible. Since this is a cluster solution it is possible to distribute the transaction load over several servers. However, in a cluster arrangement you may need to route the transactions (cluster affinity) so that the same card numbers follow the same route… should there be any sort of stand-in processing, duplicate transactions, etc… you want to have the latest information possible. Implementing routing via cluster affinity is another use-case for Redis as you can store the routes and then replicate that data and read the record from the slaves. With any luck this will be faster than an errant user. But you still have to keep an you out there for evil does… so a good blacklist is also helpful.
Some elements I left out… end of day batch file, recurring transactions, reporting, and customer care.
[UPDATE: Redis does not like combining EXPIRE and replication. The EXPIRE can have unpredictable results when executing queries against the slave(s). OK, so we have learned that FIFOs and Caches are useful in MQ broker construction and as part of the impedance mismatch correction. Redis/RestMQ seem to be strong tools.]