Repository: moov-io/customers
Pull request #103 (2020-08-06 21:35:00)

accounts: return InstitutionDetails next to routing number
This was requested by @joshsadler for the UI. It'll include FI information related to each `Account`. 

Example:
```
$ curl -s localhost:8087/customers/52840ffb2becc6633778e9689e5c9bb50f7ce151/accounts | jq . 
```
```json
[
  {
    "accountID": "375e4036a22b928b4bba4a63eb3ed5c2471be84d",
    "maskedAccountNumber": "******1000",
    "routingNumber": "323274270",
    "status": "validated",
    "type": "Checking",
    "institution": {
      "name": "OREGON STATE CREDIT UNION",
      "routingNumber": "323274270",
      "phoneNumber": "5417144000",
      "address": {
        "address1": "PO BOX 306",
        "city": "CORVALLIS",
        "state": "OR",
        "zip": "97339"
      }
    }
  }
]
```


Patch:
diff --git a/client/README.md b/client/README.md
index 1b1f63f..9cd1457 100644
--- a/client/README.md
+++ b/client/README.md
@@ -75,6 +75,8 @@ Class | Method | HTTP request | Description
  - [Disclaimer](docs/Disclaimer.md)
  - [Document](docs/Document.md)
  - [Error](docs/Error.md)
+ - [InstitutionAddress](docs/InstitutionAddress.md)
+ - [InstitutionDetails](docs/InstitutionDetails.md)
  - [OfacSearch](docs/OfacSearch.md)
  - [Phone](docs/Phone.md)
  - [TransitAccountNumber](docs/TransitAccountNumber.md)
diff --git a/client/api/openapi.yaml b/client/api/openapi.yaml
index c5dd178..dd04d27 100644
--- a/client/api/openapi.yaml
+++ b/client/api/openapi.yaml
@@ -1399,6 +1399,16 @@ components:
       example:
         accountID: e1b1544a
         routingNumber: "051504597"
+        institution:
+          routingNumber: "123456780"
+          phoneNumber: "5551112222"
+          address:
+            zip: "43724"
+            address2: address2
+            city: CALDWELL
+            address1: 430 NORTH ST
+            state: OH
+          name: First Bank
         type: checking
         maskedAccountNumber: "0001027028"
         status: microdeposits
@@ -1419,12 +1429,67 @@ components:
           $ref: '#/components/schemas/AccountStatus'
         type:
           $ref: '#/components/schemas/AccountType'
+        institution:
+          $ref: '#/components/schemas/InstitutionDetails'
       required:
       - accountID
       - maskedAccountNumber
       - routingNumber
       - status
       - type
+    InstitutionDetails:
+      example:
+        routingNumber: "123456780"
+        phoneNumber: "5551112222"
+        address:
+          zip: "43724"
+          address2: address2
+          city: CALDWELL
+          address1: 430 NORTH ST
+          state: OH
+        name: First Bank
+      properties:
+        name:
+          example: First Bank
+          type: string
+        routingNumber:
+          example: "123456780"
+          type: string
+        phoneNumber:
+          example: "5551112222"
+          type: string
+        address:
+          $ref: '#/components/schemas/InstitutionAddress'
+      required:
+      - name
+      - routingNumber
+    InstitutionAddress:
+      example:
+        zip: "43724"
+        address2: address2
+        city: CALDWELL
+        address1: 430 NORTH ST
+        state: OH
+      properties:
+        address1:
+          example: 430 NORTH ST
+          type: string
+        address2:
+          type: string
+        city:
+          example: CALDWELL
+          type: string
+        state:
+          example: OH
+          type: string
+        zip:
+          example: "43724"
+          type: string
+      required:
+      - address1
+      - city
+      - state
+      - zip
     UpdateValidation:
       example:
         strategy: micro-deposits
diff --git a/client/docs/Account.md b/client/docs/Account.md
index 60c6167..01b020d 100644
--- a/client/docs/Account.md
+++ b/client/docs/Account.md
@@ -9,6 +9,7 @@ Name | Type | Description | Notes
 **RoutingNumber** | **string** | The ABA routing transit number for the bank account. | 
 **Status** | [**AccountStatus**](AccountStatus.md) |  | 
 **Type** | [**AccountType**](AccountType.md) |  | 
+**Institution** | [**InstitutionDetails**](InstitutionDetails.md) |  | [optional] 
 
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 
diff --git a/client/docs/InstitutionAddress.md b/client/docs/InstitutionAddress.md
new file mode 100644
index 0000000..eea7d74
--- /dev/null
+++ b/client/docs/InstitutionAddress.md
@@ -0,0 +1,15 @@
+# InstitutionAddress
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**Address1** | **string** |  | 
+**Address2** | **string** |  | [optional] 
+**City** | **string** |  | 
+**State** | **string** |  | 
+**Zip** | **string** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/client/docs/InstitutionDetails.md b/client/docs/InstitutionDetails.md
new file mode 100644
index 0000000..5e44168
--- /dev/null
+++ b/client/docs/InstitutionDetails.md
@@ -0,0 +1,14 @@
+# InstitutionDetails
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**Name** | **string** |  | 
+**RoutingNumber** | **string** |  | 
+**PhoneNumber** | **string** |  | [optional] 
+**Address** | [**InstitutionAddress**](InstitutionAddress.md) |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/client/model_account.go b/client/model_account.go
index 836c67f..645e8a1 100644
--- a/client/model_account.go
+++ b/client/model_account.go
@@ -16,7 +16,8 @@ type Account struct {
 	// The masked account number for the bank account
 	MaskedAccountNumber string `json:"maskedAccountNumber"`
 	// The ABA routing transit number for the bank account.
-	RoutingNumber string        `json:"routingNumber"`
-	Status        AccountStatus `json:"status"`
-	Type          AccountType   `json:"type"`
+	RoutingNumber string             `json:"routingNumber"`
+	Status        AccountStatus      `json:"status"`
+	Type          AccountType        `json:"type"`
+	Institution   InstitutionDetails `json:"institution,omitempty"`
 }
diff --git a/client/model_institution_address.go b/client/model_institution_address.go
new file mode 100644
index 0000000..860e775
--- /dev/null
+++ b/client/model_institution_address.go
@@ -0,0 +1,19 @@
+/*
+ * Customers API
+ *
+ * Customers focuses on solving authentic identification of humans who are legally able to hold and transfer currency within the US. Primarily this project solves [Know Your Customer](https://en.wikipedia.org/wiki/Know_your_customer) (KYC), [Customer Identification Program](https://en.wikipedia.org/wiki/Customer_Identification_Program) (CIP), [Office of Foreign Asset Control](https://www.treasury.gov/about/organizational-structure/offices/Pages/Office-of-Foreign-Assets-Control.aspx) (OFAC) checks and verification workflows to comply with US federal law and ensure authentic transfers. Customers has an objective to be a service for detailed due diligence on individuals and companies for Financial Institutions and services in a modernized and extensible way.  Customer phone numbers and addresses are stored and partially used in KYC/OFAC validation. Arbitrary key/value pairs can be stored for a Customer. Documents and Disclaimers, and their acknowledgement are also stored under a Customer as they're accepted. Bank Accounts, which can be validated with micro-deposits currently, are stored under each Customer.  ![](https://raw.githubusercontent.com/adamdecaf/customers/create-accounts/docs/images/customer.png)
+ *
+ * API version: v1
+ * Generated by: OpenAPI Generator (https://openapi-generator.tech)
+ */
+
+package client
+
+// InstitutionAddress struct for InstitutionAddress
+type InstitutionAddress struct {
+	Address1 string `json:"address1"`
+	Address2 string `json:"address2,omitempty"`
+	City     string `json:"city"`
+	State    string `json:"state"`
+	Zip      string `json:"zip"`
+}
diff --git a/client/model_institution_details.go b/client/model_institution_details.go
new file mode 100644
index 0000000..3f8b44d
--- /dev/null
+++ b/client/model_institution_details.go
@@ -0,0 +1,18 @@
+/*
+ * Customers API
+ *
+ * Customers focuses on solving authentic identification of humans who are legally able to hold and transfer currency within the US. Primarily this project solves [Know Your Customer](https://en.wikipedia.org/wiki/Know_your_customer) (KYC), [Customer Identification Program](https://en.wikipedia.org/wiki/Customer_Identification_Program) (CIP), [Office of Foreign Asset Control](https://www.treasury.gov/about/organizational-structure/offices/Pages/Office-of-Foreign-Assets-Control.aspx) (OFAC) checks and verification workflows to comply with US federal law and ensure authentic transfers. Customers has an objective to be a service for detailed due diligence on individuals and companies for Financial Institutions and services in a modernized and extensible way.  Customer phone numbers and addresses are stored and partially used in KYC/OFAC validation. Arbitrary key/value pairs can be stored for a Customer. Documents and Disclaimers, and their acknowledgement are also stored under a Customer as they're accepted. Bank Accounts, which can be validated with micro-deposits currently, are stored under each Customer.  ![](https://raw.githubusercontent.com/adamdecaf/customers/create-accounts/docs/images/customer.png)
+ *
+ * API version: v1
+ * Generated by: OpenAPI Generator (https://openapi-generator.tech)
+ */
+
+package client
+
+// InstitutionDetails struct for InstitutionDetails
+type InstitutionDetails struct {
+	Name          string             `json:"name"`
+	RoutingNumber string             `json:"routingNumber"`
+	PhoneNumber   string             `json:"phoneNumber,omitempty"`
+	Address       InstitutionAddress `json:"address,omitempty"`
+}
diff --git a/cmd/server/accounts/router.go b/cmd/server/accounts/router.go
index e1ef383..9959bc7 100644
--- a/cmd/server/accounts/router.go
+++ b/cmd/server/accounts/router.go
@@ -26,14 +26,14 @@ import (
 )
 
 func RegisterRoutes(logger log.Logger, r *mux.Router, repo Repository, fedClient fed.Client, paygateClient paygate.Client, keeper, transitKeeper *secrets.StringKeeper) {
-	r.Methods("GET").Path("/customers/{customerID}/accounts").HandlerFunc(getCustomerAccounts(logger, repo))
+	r.Methods("GET").Path("/customers/{customerID}/accounts").HandlerFunc(getCustomerAccounts(logger, repo, fedClient))
 	r.Methods("POST").Path("/customers/{customerID}/accounts").HandlerFunc(createCustomerAccount(logger, repo, fedClient, keeper))
 	r.Methods("POST").Path("/customers/{customerID}/accounts/{accountID}/decrypt").HandlerFunc(decryptAccountNumber(logger, repo, keeper, transitKeeper))
 	r.Methods("DELETE").Path("/customers/{customerID}/accounts/{accountID}").HandlerFunc(removeCustomerAccount(logger, repo))
 	r.Methods("PUT").Path("/customers/{customerID}/accounts/{accountID}/validate").HandlerFunc(validateAccount(logger, repo, paygateClient))
 }
 
-func getCustomerAccounts(logger log.Logger, repo Repository) http.HandlerFunc {
+func getCustomerAccounts(logger log.Logger, repo Repository, fedClient fed.Client) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w = route.Responder(logger, w, r)
 
@@ -43,6 +43,7 @@ func getCustomerAccounts(logger log.Logger, repo Repository) http.HandlerFunc {
 			moovhttp.Problem(w, err)
 			return
 		}
+		accounts = decorateInstitutionDetails(accounts, fedClient)
 
 		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 		w.WriteHeader(http.StatusOK)
@@ -50,6 +51,15 @@ func getCustomerAccounts(logger log.Logger, repo Repository) http.HandlerFunc {
 	}
 }
 
+func decorateInstitutionDetails(accounts []*client.Account, client fed.Client) []*client.Account {
+	for i := range accounts {
+		if details, _ := client.LookupInstitution(accounts[i].RoutingNumber); details != nil {
+			accounts[i].Institution = *details
+		}
+	}
+	return accounts
+}
+
 type createAccountRequest struct {
 	AccountNumber string             `json:"accountNumber"`
 	RoutingNumber string             `json:"routingNumber"`
@@ -114,7 +124,7 @@ func createCustomerAccount(logger log.Logger, repo Repository, fedClient fed.Cli
 			return
 		}
 
-		if err := fedClient.LookupRoutingNumber(request.RoutingNumber); err != nil {
+		if _, err := fedClient.LookupInstitution(request.RoutingNumber); err != nil {
 			logger.Log("accounts", fmt.Sprintf("problem looking up routing number=%q: %v", request.RoutingNumber, err), "requestID", moovhttp.GetRequestID(r))
 			moovhttp.Problem(w, err)
 			return
diff --git a/cmd/server/fed/cache.go b/cmd/server/fed/cache.go
new file mode 100644
index 0000000..0e38815
--- /dev/null
+++ b/cmd/server/fed/cache.go
@@ -0,0 +1,55 @@
+// Copyright 2020 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package fed
+
+import (
+	"errors"
+
+	"github.com/moov-io/customers/client"
+
+	hashlru "github.com/hashicorp/golang-lru"
+)
+
+type cacheClient struct {
+	underlying Client
+	cache      *hashlru.Cache
+}
+
+func NewCacheClient(client Client, maxSize int) Client {
+	cache, _ := hashlru.New(maxSize)
+	return &cacheClient{
+		underlying: client,
+		cache:      cache,
+	}
+}
+
+func (c *cacheClient) Ping() error {
+	if c == nil || c.underlying == nil {
+		return nil
+	}
+	return c.underlying.Ping()
+}
+
+func (c *cacheClient) LookupInstitution(routingNumber string) (*client.InstitutionDetails, error) {
+	if c == nil || c.underlying == nil {
+		return nil, errors.New("nil Client")
+	}
+
+	val, exists := c.cache.Get(routingNumber)
+	if exists {
+		if details, ok := val.(*client.InstitutionDetails); ok {
+			return details, nil
+		}
+	}
+
+	details, err := c.underlying.LookupInstitution(routingNumber)
+	if err != nil {
+		return nil, err
+	}
+	if details != nil {
+		c.cache.Add(routingNumber, details)
+	}
+	return details, nil
+}
diff --git a/cmd/server/fed/client.go b/cmd/server/fed/client.go
index c41a4de..75a8849 100644
--- a/cmd/server/fed/client.go
+++ b/cmd/server/fed/client.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/moov-io/base/http/bind"
 	"github.com/moov-io/base/k8s"
+	"github.com/moov-io/customers/client"
 	moovfed "github.com/moov-io/fed/client"
 
 	"github.com/antihax/optional"
@@ -21,7 +22,7 @@ import (
 
 type Client interface {
 	Ping() error
-	LookupRoutingNumber(routingNumber string) error
+	LookupInstitution(routingNumber string) (*client.InstitutionDetails, error)
 }
 
 type moovClient struct {
@@ -47,7 +48,7 @@ func (c *moovClient) Ping() error {
 	return err
 }
 
-func (c *moovClient) LookupRoutingNumber(routingNumber string) error {
+func (c *moovClient) LookupInstitution(routingNumber string) (*client.InstitutionDetails, error) {
 	// create a context just for this so ping requests don't require the setup of one
 	ctx, cancelFn := context.WithTimeout(context.TODO(), 10*time.Second)
 	defer cancelFn()
@@ -59,17 +60,28 @@ func (c *moovClient) LookupRoutingNumber(routingNumber string) error {
 		resp.Body.Close()
 	}
 	if resp == nil {
-		return fmt.Errorf("FED ping failed: %v", err)
+		return nil, fmt.Errorf("FED ping failed: %v", err)
 	}
 	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
-		return fmt.Errorf("FED ping got status: %s", resp.Status)
+		return nil, fmt.Errorf("FED ping got status: %s", resp.Status)
 	}
 	for i := range achDict.ACHParticipants {
 		if achDict.ACHParticipants[i].RoutingNumber == routingNumber {
-			return nil // found match
+			part := achDict.ACHParticipants[i]
+			return &client.InstitutionDetails{
+				Name:          part.CustomerName,
+				RoutingNumber: part.RoutingNumber,
+				PhoneNumber:   part.PhoneNumber,
+				Address: client.InstitutionAddress{
+					Address1: part.AchLocation.Address,
+					City:     part.AchLocation.City,
+					State:    part.AchLocation.State,
+					Zip:      part.AchLocation.PostalCode,
+				},
+			}, nil
 		}
 	}
-	return errors.New("no ACH participants found")
+	return nil, errors.New("no ACH participants found")
 }
 
 var (
diff --git a/cmd/server/fed/fed.go b/cmd/server/fed/fed.go
new file mode 100644
index 0000000..9c99c9f
--- /dev/null
+++ b/cmd/server/fed/fed.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package fed
+
+import (
+	"os"
+	"strconv"
+
+	"github.com/moov-io/customers/internal/util"
+
+	"github.com/go-kit/kit/log"
+)
+
+func Cache(logger log.Logger, endpoint string) Client {
+	client := NewClient(logger, endpoint)
+
+	data := util.Or(os.Getenv("FED_CACHE_SIZE"), "1024")
+	maxSize, _ := strconv.ParseInt(data, 10, 32)
+
+	return NewCacheClient(client, int(maxSize))
+}
diff --git a/cmd/server/fed/mock_client.go b/cmd/server/fed/mock_client.go
index a0517cf..564645f 100644
--- a/cmd/server/fed/mock_client.go
+++ b/cmd/server/fed/mock_client.go
@@ -4,14 +4,22 @@
 
 package fed
 
+import (
+	"github.com/moov-io/customers/client"
+)
+
 type MockClient struct {
-	Err error
+	Err     error
+	Details *client.InstitutionDetails
 }
 
 func (c *MockClient) Ping() error {
 	return c.Err
 }
 
-func (c *MockClient) LookupRoutingNumber(routingNumber string) error {
-	return c.Err
+func (c *MockClient) LookupInstitution(routingNumber string) (*client.InstitutionDetails, error) {
+	if c.Err != nil {
+		return nil, c.Err
+	}
+	return c.Details, nil
 }
diff --git a/cmd/server/main.go b/cmd/server/main.go
index fc73c32..12e2243 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -150,7 +150,7 @@ func main() {
 	}
 	transitStringKeeper := secrets.NewStringKeeper(transitKeeper, 10*time.Second)
 
-	fedClient := fed.NewClient(logger, os.Getenv("FED_ENDPOINT"))
+	fedClient := fed.Cache(logger, os.Getenv("FED_ENDPOINT"))
 	adminServer.AddLivenessCheck("fed", fedClient.Ping)
 
 	paygateClient := paygate.NewClient(logger, os.Getenv("PAYGATE_ENDPOINT"))
diff --git a/go.mod b/go.mod
index b6e2c39..e21e0f6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,25 @@ module github.com/moov-io/customers
 go 1.14
 
 require (
+	github.com/PuerkitoBio/goquery v1.5.1 // indirect
+	github.com/antihax/optional v1.0.0
+	github.com/aws/aws-sdk-go v1.31.13
 	github.com/go-kit/kit v0.10.0
 	github.com/go-sql-driver/mysql v1.5.0
+	github.com/gorilla/mux v1.7.4
+	github.com/hashicorp/golang-lru v0.5.4
+	github.com/hashicorp/vault/api v1.0.4
 	github.com/lopezator/migrator v0.3.0
-	github.com/mattn/go-sqlite3 v1.14.0
+	github.com/mattn/go-sqlite3 v2.0.3+incompatible
+	github.com/moov-io/ach v1.4.4
 	github.com/moov-io/base v0.11.0
+	github.com/moov-io/fed v0.5.1
+	github.com/moov-io/paygate v0.8.0
+	github.com/moov-io/watchman v0.14.2
 	github.com/ory/dockertest/v3 v3.6.0
 	github.com/prometheus/client_golang v1.7.1
+	gocloud.dev v0.20.0
+	gocloud.dev/secrets/hashivault v0.20.0
+	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
+	gopkg.in/yaml.v2 v2.2.8
 )
diff --git a/go.sum b/go.sum
index a73a201..3ccc3ec 100644
--- a/go.sum
+++ b/go.sum
@@ -26,14 +26,17 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/firestore v1.2.0/go.mod h1:iISCjWnTpnoJT1R287xRdjvQHJrxQOpeah4phb5D3h0=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
@@ -59,6 +62,7 @@ github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9a
 github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg=
 github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
 github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
+github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY=
 github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
 github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go v27.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -71,6 +75,7 @@ github.com/Azure/azure-service-bus-go v0.10.1/go.mod h1:E/FOceuKAFUfpbIJDKWz/May
 github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
 github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o=
 github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
+github.com/Azure/azure-storage-blob-go v0.9.0 h1:kORqvzXP8ORhKbW13FflGUaSE5CMyDWun9UwMxY8gPs=
 github.com/Azure/azure-storage-blob-go v0.9.0/go.mod h1:8UBPbiOhrMQ4pLPi3gA1tXnpjrS76UYE/fo5A40vf4g=
 github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
 github.com/Azure/go-amqp v0.12.7/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
@@ -82,10 +87,12 @@ github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
 github.com/Azure/go-autorest v12.0.0+incompatible h1:N+VqClcomLGD/sHb3smbSYYtNMgKpVV3Cd5r5i8z6bQ=
 github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4=
 github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
 github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
 github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
+github.com/Azure/go-autorest/autorest/adal v0.8.3 h1:O1AGG9Xig71FxdX9HO5pGNyZ7TbSyHaVg+5eJO/jSGw=
 github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
 github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
 github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
@@ -99,6 +106,7 @@ github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRY
 github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
 github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190418212003-6ac0b49e7197/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
@@ -139,6 +147,7 @@ github.com/aws/aws-sdk-go v1.19.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
 github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.19.45 h1:jAxmC8qqa7mW531FDgM8Ahbqlb3zmiHgTpJU6fY3vJ0=
 github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.29.1 h1:U2vZ5WprhGAMjzb4bKVzl2QecUtZFW2BXVqa5bnd+OY=
 github.com/aws/aws-sdk-go v1.29.1/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
@@ -146,6 +155,7 @@ github.com/aws/aws-sdk-go v1.30.24 h1:y3JPD51VuEmVqN3BEDVm4amGpDma2cKJcDPuAU1OR5
 github.com/aws/aws-sdk-go v1.30.24/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.31.0 h1:ITLZ0oy7IOB1NGt2Ee75bLevBaH1jaAXE2eyGbPRbCg=
 github.com/aws/aws-sdk-go v1.31.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.31.13 h1:UeWMTRTL0XAKLR7vxDL4/u7KOtz/LtfJr+lXtxN4YEQ=
 github.com/aws/aws-sdk-go v1.31.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.33.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.33.7 h1:vOozL5hmWHHriRviVTQnUwz8l05RS0rehmEFymI+/x8=
@@ -168,6 +178,7 @@ github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r
 github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
@@ -225,6 +236,7 @@ github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
 github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -332,6 +344,7 @@ github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -404,6 +417,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/vault v1.5.0 h1:XeXq1ijob4AaCHg2Qz2GE1neqaDBfcMRH+6Kzu5phB8=
 github.com/hashicorp/vault/api v1.0.1/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE=
 github.com/hashicorp/vault/api v1.0.2 h1:/V9fULvLwt58vme/6Rkt/p/GtlresQv+Z9E6dgdANhs=
 github.com/hashicorp/vault/api v1.0.2/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE=
@@ -437,7 +451,9 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -452,6 +468,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@@ -477,6 +494,7 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA=
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
 github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
 github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -526,6 +544,8 @@ github.com/moov-io/ach v1.4.0 h1:d3clitIGNf1ZGjNXgrdKpCBbmIcPqoN+uUO23vcGShg=
 github.com/moov-io/ach v1.4.0/go.mod h1:NykiZmpbunOekMWxW4USJ7kkbCdOH2imRB/ZrjRcVOQ=
 github.com/moov-io/ach v1.4.1 h1:GiYO67z4SxCw+GBwfUd0qeUfemKlKobMzo3diAG85tw=
 github.com/moov-io/ach v1.4.1/go.mod h1:NykiZmpbunOekMWxW4USJ7kkbCdOH2imRB/ZrjRcVOQ=
+github.com/moov-io/ach v1.4.4 h1:lNp/Acq2xhz3MmmlY8NTMJ3EagCT+xR9Ox3L8D5cgRI=
+github.com/moov-io/ach v1.4.4/go.mod h1:q0jS0WXzgLPlkKkbHSZN1yWyCUoDiSllWucHCfwDqGM=
 github.com/moov-io/base v0.10.0/go.mod h1:pPu/TAc9PkaaegbREVEeDHsGqyAlvji9vqTuARuAnd0=
 github.com/moov-io/base v0.11.0-rc1.0.20191121181647-cd3e7a9609db h1:O6qBkf9lubIX+/7WboVLEXYKXKPx8soVRjdtuXiDDss=
 github.com/moov-io/base v0.11.0-rc1.0.20191121181647-cd3e7a9609db/go.mod h1:oXZRZBQiO7HX+Wc7Q3jJHLfkDPxrMXRbgYhURQqfXD8=
@@ -534,6 +554,7 @@ github.com/moov-io/base v0.11.0/go.mod h1:jEkZukoSaJ4rDFoe00S5zuoLE+p3U3BEunSEPc
 github.com/moov-io/customers v0.4.0-rc4/go.mod h1:wNz/qIiG24rJrjnK3VOvzj0MbdfGznj6INO/uyntgNM=
 github.com/moov-io/customers v0.4.0-rc4.0.20200519011716-05c4c319c49b/go.mod h1:wmBTMaZ7OzoXhDtLqGngNrEaRlsk7VFLxY0kl3HzoHc=
 github.com/moov-io/customers v0.4.0-rc5.0.20200605165738-057f20d4d580/go.mod h1:2viqzwFr1cp5gXL9lhOIXl7KE1pDIA8Z4I5smZBRUSo=
+github.com/moov-io/customers v0.4.1/go.mod h1:YYpD3WdP4u6tujf7yE4vQ8o5apMDvPsbO7ai52UaPnA=
 github.com/moov-io/fed v0.5.0 h1:HH6cxVzU1h2GJVTGEZGhMAYSzj10Q98aBPaLI7xjJx4=
 github.com/moov-io/fed v0.5.0/go.mod h1:dvWhJv0X7xYixKuJ9JnfVL+AHfG5YDmLRBxQkwJ0958=
 github.com/moov-io/fed v0.5.1 h1:ccxiWjpL9O20Q/7YnXbj8Sstbs82St2WqK55LR9Zhoc=
@@ -698,7 +719,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
@@ -727,6 +750,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -780,6 +804,8 @@ gocloud.dev v0.20.0/go.mod h1:+Y/RpSXrJthIOM8uFNzWp6MRu9pFPNFEEZrQMxpkfIc=
 gocloud.dev/pubsub/kafkapubsub v0.19.0/go.mod h1:tQgteR3gnYFlcU5riE/96KZkbB5UXUL6AweVJNJiW4k=
 gocloud.dev/secrets/hashivault v0.19.0 h1:WU1m8spYGDS0SAo1URKQukoURtu60ecj1+4qhL7BIIE=
 gocloud.dev/secrets/hashivault v0.19.0/go.mod h1:doHlds85r+VwAs0T46bhjLwboWUfgfo3ovqDA8azNWo=
+gocloud.dev/secrets/hashivault v0.20.0 h1:919urzRWksXrZNNqqlHvek9IE6lWvYM4m8dvFqXq4HU=
+gocloud.dev/secrets/hashivault v0.20.0/go.mod h1:2nNlZ76i4JlT9qrPVKbINB01L8BGgv4wmq2Cqyz22dA=
 gocloud.dev/secrets/vault v0.15.0/go.mod h1:e4fxJocdhqd+lnNxqmxrjjl4pohRfy4z9zS4f2n1Mxg=
 golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -825,6 +851,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -833,6 +860,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -878,6 +906,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
 golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -893,6 +922,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -953,6 +983,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1009,6 +1040,7 @@ golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 h1:MI14dOfl3OG6Zd32w3ugsrvcUO810fDZdWakTq39dH4=
 golang.org/x/tools v0.0.0-20200608174601-1b747fd94509/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
@@ -1045,6 +1077,7 @@ google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1158,6 +1191,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1166,6 +1200,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 pack.ag/amqp v0.8.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
 pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
diff --git a/openapi.yaml b/openapi.yaml
index 880a0f2..8984b89 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -1106,12 +1106,52 @@ components:
           $ref: '#/components/schemas/AccountStatus'
         type:
           $ref: '#/components/schemas/AccountType'
+        institution:
+          $ref: '#/components/schemas/InstitutionDetails'
       required:
         - accountID
         - maskedAccountNumber
         - routingNumber
         - status
         - type
+    InstitutionDetails:
+      properties:
+        name:
+          type: string
+          example: First Bank
+        routingNumber:
+          type: string
+          example: "123456780"
+        phoneNumber:
+          type: string
+          example: "5551112222"
+        address:
+          $ref: '#/components/schemas/InstitutionAddress'
+      required:
+        - name
+        - routingNumber
+    InstitutionAddress:
+      properties:
+        address1:
+          type: string
+          example: "430 NORTH ST"
+        address2:
+          type: string
+          example: ""
+        city:
+          type: string
+          example: "CALDWELL"
+        state:
+          type: string
+          example: "OH"
+        zip:
+          type: string
+          example: "43724"
+      required:
+        - address1
+        - city
+        - state
+        - zip
     UpdateValidation:
       type: object
       properties:


Test patch:
diff --git a/cmd/server/fed/cache_test.go b/cmd/server/fed/cache_test.go
new file mode 100644
index 0000000..9bc0de9
--- /dev/null
+++ b/cmd/server/fed/cache_test.go
@@ -0,0 +1,63 @@
+// Copyright 2020 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package fed
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/moov-io/customers/client"
+)
+
+func TestCache(t *testing.T) {
+	mock := &MockClient{}
+	cache := NewCacheClient(mock, 2)
+
+	// Lookup, but find nothing
+	details, err := cache.LookupInstitution("987654320")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if details != nil {
+		t.Errorf("unexpected InstitutionDetails: %#v", details)
+	}
+
+	// set details and cache
+	mock.Details = &client.InstitutionDetails{
+		RoutingNumber: "987654320",
+	}
+	details, err = cache.LookupInstitution("987654320")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if details == nil {
+		t.Error("expected details")
+	}
+
+	// read details again (from cache)
+	details, err = cache.LookupInstitution("987654320")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if details == nil {
+		t.Error("expected details")
+	}
+}
+
+func TestCachePing(t *testing.T) {
+	mock := &MockClient{}
+	cache := NewCacheClient(mock, 2)
+
+	// Healthy
+	if err := cache.Ping(); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+
+	// Healthy
+	mock.Err = errors.New("bad error")
+	if err := cache.Ping(); err == nil {
+		t.Error("expected error")
+	}
+}
diff --git a/cmd/server/fed/client_test.go b/cmd/server/fed/client_test.go
index 377445b..27f981f 100644
--- a/cmd/server/fed/client_test.go
+++ b/cmd/server/fed/client_test.go
@@ -27,7 +27,7 @@ func TestFED(t *testing.T) {
 	}
 	svc.Close()
 
-	// test LookupRoutingNumber
+	// test LookupInstitution
 	svc = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.URL.Path != "/fed/ach/search" {
 			w.WriteHeader(http.StatusBadRequest)
@@ -38,8 +38,12 @@ func TestFED(t *testing.T) {
 	}))
 
 	client = NewClient(log.NewNopLogger(), svc.URL)
-	if err := client.LookupRoutingNumber("121042882"); err != nil {
+	if details, err := client.LookupInstitution("121042882"); err != nil {
 		t.Fatal(err)
+	} else {
+		if details.RoutingNumber != "121042882" {
+			t.Errorf("unexpected ACH details: %#v", details)
+		}
 	}
 	svc.Close()
 }