Visa recently launched a revamped version of their developer portal. I have previously looked into the Visa and Mastercard APIs, and with this update to their portal I decided to take a closer look.
What’s changed?
The last time I took a look at the API portal, it was not overly impressive. The front end looked like it needed a good revamp, the navigation was fair and the information was available. The new iteration has brought a fresh new look, with a complete overhaul on the front and back end. The dashboard is now far closer to what we would expect from an application devleoped in 2015 / 2016.
The biggest difference is the access to the sandbox. Previously, in order to just see the code you would have to be approved through mailing the developer account, and (assumingly) being backed by a finance institution. Now, using the API in sandbox mode is open and accessible for developers to develop their apps against before going into production. When going into production, the company/developer will then go through the application process and for this they will (rightfully) need to be approved as a vendor. Mastercard also implements this workflow, more information at their developer portal.
With the sandbox now being opened, I decided to take a closer look and start developing against the API. For the project I have in mind, I signed up to use to the Visa Direct services, which will allow payments between two accounts - the crux of consumer banking.
Documentation
The documentation and guides are great. They have put loads of effort into making the documentation accessible and easy, even including Stripe style in browser request and response.
In addition to the documentation, there are also numerous code samples available for developers at Visa’s Github. Going through these samples is incredibly helpful in implementating the finer aspects of requests.
Go
As I’ve noted, I’m a big fan of Go and try to dive deeper every chance I get. In addition, I am also trying to use a Test Driven Development approach. TDD is new to me, and Go has some pretty good built-in support for tests.
Due to the Visa developer program being new, there is currently no package for the Visa API. I have not built a package in Go yet, and I thought this would be a good opportunity to do so.
Functionality
Building a package is different from building a program. The usage has to be abstracted so that in can be dropped into another application and used without editing any source code. Coming from that angle, the most prominent issue is the setting of configuration variables and the hiding of internals.
Configuration
For now, the implementation I have gone with is calling a function to set the variables which will be global to the package. This is implemented as follows:
import visa "github.com/ksred.me/visa"
visa.setVariables("USER_ID", "USER_PASSWORD")
The function is currently generic and will be extended to include both sets of certificates, as well as other config data.
Internals
The point of using a library is to hide away the dirty work. I want to be able to call an abstracted function and get a sane response which I can handle.
request := FundsTransactionRequest{ /* Struct variables */ }
response := PullFundsTransactionsPost(request)
The response is a struct of type visa.FundsTransactionResponse
, containing all the fields needed for dealing with that specific request.
The actual function looks as follows:
func PullFundsTransactionsPost(request FundsTransactionRequest) (response FundsTransactionResponse) {
body, err := json.Marshal(request)
if err != nil {
// @TODO: No not fatal log
log.Fatalf("Error json encoding PullFundsTransactionsPost request: %v", err)
return
}
responseJson := Client(USER_ID, USER_PASSWORD, PULL_FUNDS_TRANSACTIONS_URL, "POST", false, body, "0")
// Unmarshall response
err = json.Unmarshal(responseJson, &response)
return
}
// Client function
func Client(userId string, userPassword string, url string, reqType string, production bool, body []byte, transactionID string) (response []byte) {
setVariables(userId, userPassword)
authHeader := createAuthHeader()
req, err := http.NewRequest(reqType, url, bytes.NewBuffer(body))
req.Header.Set("X-Client-Transaction-ID", transactionID)
req.Header.Set("Authorization:Basic ", authHeader)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept: application/json", "application/json")
// Load client cert
cert, err := tls.LoadX509KeyPair(SSL_PUBLIC_KEY_PATH, SSL_PRIVATE_KEY_PATH)
if err != nil {
log.Fatalf("Could not load key pair: %v", err)
}
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Could not load HTTPS client: %v", err)
}
defer resp.Body.Close()
response, _ = ioutil.ReadAll(resp.Body)
//fmt.Println(response)
return
}
The Client
function in turn uses more functions, further abstracted. So goes modular and testable programming.
Tests
With TDD being new to me, I had to do a fair amount of reading to get up to speed with some idea of how to implement testing in Go.
Luckily, testing in Go is a breeze. The test for creating the auth header, function createAuthHeader
:
package visa
import (
"encoding/base64"
"testing"
)
func TestAuthHeader(t *testing.T) {
cases := []struct {
user_id, user_password string
}{
{"test_user_id", "dfjLRHzl4Mujn3aihy318OpySANPvO"},
// More test cases
}
for _, c := range cases {
testBase64 := base64.StdEncoding.EncodeToString([]byte(c.user_id + ":" + c.user_password))
setVariables(c.user_id, c.user_password)
authHeaderTest := createAuthHeader()
if authHeaderTest != testBase64 {
t.Errorf("Auth header does not pass. Looking for %s, got %s", testBase64, authHeaderTest)
}
}
}
As you can see, it is pretty straight forward. You can also hook the tests to run on every save, or you can just run them manually when you have completed a block of functionality.
Side note: if you’re a Vim user, head over and install vim-go.
JSON Marshal
Field names
JSON marshal gets a special mention here. The Visa API requires JSON to be sent in index:value format:
{"name" : "Kyle", "age" : 29}
You would use the following struct and marshal to get the above result:
type TestJson struct {
name string
age int
}
testVar := TestJson{"Kyle", 29}
test1, _ := json.Marshal(testVar)
test1string := string(test1)
fmt.Println(test1string)
As it turns out, the above leaves out the indexes, resulting in { "Kyle", 29 }
. To get the index to come through, the struct fields
must be exported - have leading capitals.
type TestJson struct {
Name string
Age int
}
This now results in {"Name" : "Kyle", "Age" : 29}
. The Visa API, as many others, require s specific format returned, in this case
the index must be lead with a lowercase. This is accomplished with adding a suffix on the struct value.
type TestJson struct {
Name string `json:"name"`
Age int `json:"age"`
}
This results in our desired output of { "name" : "Kyle", "age" : 29 }
.
Empty structs
The Visa API also requires that any empty (optional or conditional) fields are not sent through. For example, if card data
is not present, the field magneticStripeData
must not only be empty, but it must also not be included in the JSON sent through.
This is accomplished by using a reference and omitempty
:
type FundsTransactionRequest struct {
...
MagneticStripeData *MagneticStripeData `json:"magneticStripeData,omitempty"`
...
}
More can be viewed at the source and corresponding test.
The library
All of the above has been implemented as a first step. The calls are being made to the sandbox server, authed, and responses converted into a struct. The library is currently in its infancy, but will be developed out to include the remaining eleven functions on the Visa Direct API, as well as the Watch List Screening API and the mVisa API.
Tests are also running successfully, which calls the above functionality. In the spirit of TDD, the functionality both small and large will be specd out and the code written to make these tests run successfully.
Conclusion
The code is available on my Github. Consider the code very alpha at this stage, but the API will be continually developed on.
If you want to contribute, have experience in testing or packages, please feel free to get in touch or send a pull request.
As a side note: It is fantastic that so many financial service providers are opening up access to key tools for Fintech development. I’m really excited to be contributing in any way to the industry, and I am sure there are many more developers who feel the same.