moov-io / customers

Customer registry supporting Know Your Customer (KYC), Customer Identification Program (CIP), and OFAC checks
https://moov.io
Apache License 2.0
68 stars 19 forks source link

Instant account verification with Plaid, MX #124

Closed alovak closed 4 years ago

alovak commented 4 years ago

1. Initiate account verification

POST /customers/{customerID}/accounts/{accountID}/validate

Request:

{
  "strategy": "instant"
}

Response:

{
  "link_token":"link-sandbox-32771002-45e1-4f9b-93fd-f12442f8aa44",
  "expiration":"2020-08-25T13:07:19Z"
}

2. Use Plaid Link to obtain public_token

User should add Plaid Link integration depending on their paltform. Here is example for web:

<html>
    <body>
        <button id="link-button">Verify Account with Plaid</button>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
        <script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
        <script type="text/javascript">
            var handler;

            // get link_token for Plaid Link
            $.post('/verify', {}, function(data){
                handler = Plaid.create({
                    token: data.link_token,
                    onSuccess: function(public_token) {
                        console.log("public token", public_token);
                        // send public_token to api to verify account
                        $.ajax({
                            type: "PUT",
                            url: "/verify",
                            data: {
                                public_token: public_token,
                            },
                            success: function(data) {
                                console.log("Verification result: ", data);
                                // it should return account with status: verified
                            },
                        });
                    },
                });
            });

            $('#link-button').on('click', function(e) {
                handler.open();
            });
        </script>
    </body>
</html>
Screen Shot 2020-08-24 at 16 45 17

3. Complete account verification

PUT /customers/{customerID}/accounts/{accountID}/validate

Request:

{
  "strategy": "instant",
  "public_token": "public-sandbox-59eb4718-93d8-41a0-a338-9d731d83e549"
}

Response:


{
  "accountID": "e1b1544a",
  "maskedAccountNumber": "0001027028",
  "routingNumber": "051504597",
  "status": "validated", // verified?
  "type": "checking"
}

Questions.

  1. Can we change validation API? Right now it's a PUT request and it would be helpful to split it into two separate POST and PUT.
  2. Can micro-deposits validation expire/fail and then to be re-run? Is it an issue at all? With "instant" validation we can initiate and obtain new tokens and verify account multiple times, but with micro-deposits we can't.
  3. How user will decide what kind of validation to use (micro deposits or instant)?
  4. Should we change from validate to verify?
wadearnold commented 4 years ago

This will need to be an interface. We will end up supporting Plaid, Yodlee, MX, and half a dozen other concrete implementations. I am not sure if this service needs to be in customers, PayGate, or it's own service. We will need to keep track of the validation.

Good overview from NACHA about this. https://www.nacha.org/system/files/resources/Nacha_AcctValidation_WP_FINAL.pdf

adamdecaf commented 4 years ago

This is an interface already, "strategy": "micro-deposits" is another option when initiating this. It should live in Customers.

Can we call this plaid rather than instant? With other options we want to specify exactly which one.

I like the idea of POST and PUT (with a unique ID from POST?) so that an individual verification can be modified.

micro-deposits can be re-run but only after the originated file has been returned.

alovak commented 4 years ago

I've build rough integrations with MX and Plaid.

Server side

We can make interface where user can specify vendor for IAV:

{
  "strategy": "instant",
  "vendor": "plaid"
}

This looks flexible and we can add more providers later.

Request for each vendor will accept own set parameters and return own request.

Plaid

Initialize verification

POST /customers/{customerID}/accounts/{accountID}/validate

Request params:

Response params:

Complete verification

PUT /customers/{customerID}/accounts/{accountID}/validate

Request params:

Response params (account details):

MX

Initialize verification

POST /customers/{customerID}/accounts/{accountID}/validate

Request params:

Response params:

Complete verification

PUT /customers/{customerID}/accounts/{accountID}/validate

Request params:

Response params (account details):

Client side

For the client side we will provide html/js code snippets for each vendor. But users should implement client part on their own as it may be on web or mobile. I don't think we should create a wrapper for all vendors SDKs and all platforms.

Plaid client-side integration

<html>
    <body>
        <button id="link-button">Verify Account with Plaid</button>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
        <script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
        <script type="text/javascript">
            var handler;

            // get link_token for Plaid Link
            $.post('/verify', {}, function(data){
                handler = Plaid.create({
                    token: data.link_token,
                    onSuccess: function(public_token) {
                        console.log("public token", public_token);
                        // send public_token to api to verify account
                        $.ajax({
                            type: "PUT",
                            url: "/verify",
                            data: {
                                public_token: public_token,
                            },
                            success: function(data) {
                                console.log("Verification result: ", data);
                                // it should return account with status: verified
                            },
                        });
                    },
                });
            });

            $('#link-button').on('click', function(e) {
                handler.open();
            });
        </script>
    </body>
</html>

MX client-side integration

<html>
    <body>
        <button id="link-button">Verify Account with MX</button>
        <div id="verifyAccount"></div>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
        <script src="https://atrium.mx.com/connect.js"></script>
        <script type="text/javascript">
            var handler;

            // get link_token for Plaid Link
            $.post('/verify-mx', {}, function(data){
                console.log(data, data.connect_widget_url);

                handler = new MXConnect({
                    config: {
                        is_mobile_webview: false,
                    },
                    id: "verifyAccount",
                    url: data.connect_widget_url,
                    onLoad: function () {
                        console.log("On Load");
                    },
                    onSuccess: function (data) {
                        console.log("On Success", data);

                        $.ajax({
                            type: "PUT",
                            url: "/verify-mx",
                            data: data,
                            success: function(data) {
                                console.log("Verification result: ", data);
                                // it should return account with status: verified
                            },
                        });
                    },
                });
            });

            $('#link-button').on('click', function(e) {
                handler.load();
            });
        </script>
    </body>
</html>
alovak commented 4 years ago

Suggestion for API changes. We can have multiple verifications and track (if needed) them separately. Adding more vendors for account verification may lead us to the situation when we have to track the status of specific instance of verification (they may be delayed, etc.). My suggestion is to introduce "verification" resource:

POST /customers/{customerID}/accounts/{accountID}/verifications - creates verification with id GET and PUT requests will be for specific verification: /customers/{customerID}/accounts/{accountID}/verifications/:verification_id/

@adamdecaf @InfernoJJ what do you guys think about it?

adamdecaf commented 4 years ago

I like specifying the vendor name. Is there any reason we can't have "strategy": "plaid"? Previously I was thinking instant could try and auto-discover between Plaid/MX but that fancy trick seems like a bad idea.

I like the idea of a validationID that's used to track the progress. It should be a proper table that stores the timestamp, strategy, status, etc of the account. Right now it's a bit too crude to just update the account's status.

alovak commented 4 years ago

I think { "strategy": "plaid" } will make things easier on the code side. I wanted somehow to keep the indication that this verification will be instant 😃 Anyway, it should be clear from our documentation that this is an instant account verification.

Initially I also thought about just keeping only strategy parameter (without vendor) as I bet most users will use only one provider (plaid or mx or ...). I don't see how vendor selection UI may be built from the customer's perspective. But specifying vendor will make API straight.

BTW, Plaid has micro deposits verification 😄

adamdecaf commented 4 years ago

BTW, Plaid has micro deposits verification

Yep! I realized that when you said it on our call today. That makes me think we should have strategy and vendor it'll allow for this combination.

{
  "strategy": "micro-deposits",
  "vendor": "plaid"
}
InfernoJJ commented 4 years ago

I'm good with this all. I do believe strongly we need to keep plaid and mx javascript components alone and if they need their specific values to operate then we just need to make sure we return say the plaid token so the UX can use it.

Then we just need to track all the events and actions done on this for auditing purposes

Liking this!