In payments, as the volume of transactions increases, the number of ways things can go wrong also increases. This is particularly true at the point where a processor interacts with its bank acquirer. Here, declines can happen for a whole host of reasons — insufficient funds, mismatched addresses, expired cards, etc. Being able to provide test coverage for all these failure conditions is crucial in assuring the quality of payment processing.

Yet this isn’t always easy. In the payments industry, many services offered by financial institutions are based on Secure File Transfer Protocol (SFTP) in the backend. SFTP has its advantages — it can handle a large volume of data as well as asynchronous data exchange. Yet that also means SFTP-based backends usually operate by sending files in batches containing thousands of transactions, and these batch files are usually sent daily.

This, frankly, is too slow. In order to ensure good test coverage, it’s necessary to increase transaction frequency by mocking these backends.

There are further considerations, as well. For a single request file, multiple response files can be generated at different times. For example, once the request file is transmitted through SFTP, a confirmation file will be generated first. However, if some of the transactions in the request fail to go through, a reject file will be generated at a later time. Therefore, a realistic mock service will follow these timing rules. An engineering implication of this is that the request data need to be saved persistently in order to be able to generate different responses at different times.

This post is aimed at explaining a setup for mocking out the service backends of financial institutions we use at WePay. It explains the basic requirements, functionality and technical choices, intended for software developers working in the financial and payment service industry.

How WePay does it

At WePay, we simulate the SFTP backends for both Valitor and Litle, our two gateway providers. They both work similarly, except the request/response file formats and login credentials are different. Because of the similarity, we use a single mock service to handle both. This mock service is encapsulated in a Docker container. Inside the docker container, we set up the inbound and outbound file directories for the request and response respectively.

Here is our overall picture of our setup: The Docker container that we are using is based on the latest Ubuntu. To enable SFTP access, the OpenSSH-server package needs to be installed and a mock user account created. To monitor and auto-restart multiple service daemons, a supervisor is used.

A PHP code is constantly monitoring the inbound directory in a daemon process. When a request file is detected, the PHP code will parse the request file and store the data:

$this->iNotify->on(IN_MOVED_TO, function ($path) {
    $name = dirname($path);
    if ($name == $this->config->getDirectory() . 'inbound') {
        echo "IN_MOVED_TO: Inbound file detected" . $path . PHP_EOL;
        echo "IN_MOVED_TO: Attempting to process" . PHP_EOL;
        $this->processPath($path);
    } else {
        if ($name == $this->config->getDirectory() . 'outbound'){
            echo "Output file " . $path . " generated" . PHP_EOL;
        } else {
            echo "Error " . $path . PHP_EOL;
        }
    }
});

In this PHP snippet, we have set up a monitoring and processing daemon process for the inbound directory. To save data persistently, there are a lot of options — MySQL, SQLite or even a csv file. At WePay, we use Doctrine to map PHP objects to the relational database that comes with the Doctrine API. Information related to a particular transaction such as the amount, status, transaction id is mapped to into Doctrine Entities.

class PaymentRecord
{
    /** @Id @Column(type="integer") @GeneratedValue */
    private $id;

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @Column(type="string")
     * @var string
     */
    protected $recordId;

    /**
     * @Column(type="string")
     * @var string
     */
    protected $paymentMethod;

    /**
     * @Column(type="string")
     * @var string
     */
    protected $creditFlag;
    ...
}

In the PHP snippet above, a payment record is mapped to an Entity following Doctrine’s coding convention. Once a payment object is mapped, it can be saved persistently using the persis() method and retrieved by the find() method provided by Doctrine.

Once data is persisted in request, responses can be generated from it. Different passing and failing responses are mocked out based on the request data. Depending on the payment processor, the response format can be in plain text or a structured file such as XML.

To emulate a particular payment processor’s timing, different cron jobs are scheduled to generate various reports. One example is that a confirmation file is generated every minute and a reject file is generated every two minutes to keep the order of the timing while also keeping things relatively short to ensure a faster turnaround rate.

Conclusion

As we are mocking out more and more service backends, this setup can be scaled to add more processors. Since we are not particularly interested in how different service backends perform under heavy load, very high performance is not stressed in this setup. For better performance, a load balancer can be used to distribute requests to multiple processors simultaneously.

WePay’s framework to mock out SFTP payment processing service is currently being used as an integral part of the integration tests. To cover different fail conditions, matching front-end test cases must be provided in order to see how the payment processing system as a whole reacts to these conditions.