{"id":223,"date":"2017-08-10T14:17:14","date_gmt":"2017-08-10T14:17:14","guid":{"rendered":"https:\/\/mwoodruff.net\/articles\/?p=223"},"modified":"2020-09-17T15:49:06","modified_gmt":"2020-09-17T15:49:06","slug":"integrate-plaid-with-php","status":"publish","type":"post","link":"https:\/\/mwoodruff.net\/articles\/tutorial\/integrate-plaid-with-php\/","title":{"rendered":"Integrate Plaid with PHP"},"content":{"rendered":"\n<p>Plaid provides a simple method for IBV, or Instant Bank Verification. By integrating Plaid into your app or website, your customers can use their online banking usernames and passwords to give you secure, tokenized access to their bank account. With that access, you can verify their identity, check their balance, or take a look at their transactions, all without storing their sensitive account details on your server.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"plaidphp\">Plaid + PHP<\/h3>\n\n\n\n<p>When starting out with Plaid, I found it difficult to find good references for using it with PHP. The recommended libraries offered were for Ruby, Node JS, and other platforms requiring changing my server configuration and downloading 2.4 bazillion packages. Even the Community, but not &#8220;approved,&#8221; PHP libraries on Plaid&#8217;s site were rife with dependencies. Call me old fashioned, but I enjoy the simplicity of the Apache\/PHP setup for creating and running websites, so I set to getting Plaid working in my environment.<\/p>\n\n\n\n<p>The steps to using Plaid&#8217;s API are:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><a href=\"#setup\">Setup your Endpoints and Keys<\/a><\/li><li><a href=\"#createItem\">Create an Item<\/a><\/li><li><a href=\"#processItem\">Process your Item<\/a><\/li><li><a href=\"#acceptWebhooks\">Accept webhooks<\/a><\/li><li><a href=\"#useData\">Use your data<\/a><\/li><\/ol>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"setupyourendpointsandkeys\">Setup your Endpoints and Keys<\/h6>\n\n\n\n<p>You will need to create a Plaid account, and in their (very sparse) dashboard, you will find your keys.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><a href=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidKeysDashboard.png\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"569\" src=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidKeysDashboard.png\" alt=\"Plaid's minimalist dashboard\" class=\"wp-image-23\" srcset=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidKeysDashboard.png 800w, https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidKeysDashboard-300x213.png 300w, https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidKeysDashboard-768x546.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/a><figcaption>Plaid&#8217;s minimalist dashboard<\/figcaption><\/figure><\/div>\n\n\n\n<p>One cool thing about Plaid&#8217;s environments is that you never need to change your keys&#8211;just your endpoints. So, along with those keys in your config file, you will need to include the correct environment to use. Since we&#8217;ll be testing here, we&#8217;ll use the sandbox endpoint.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/Plaid-Sandbox \n$plaid_env = 'sandbox';\n$plaid_client_id = \"[[YOUR PLAID CLIENT ID]]\";\n$plaid_public = \"[[YOUR PLAID PUBLIC KEY]]\";\n$plaid_secret = \"[[YOUR PLAID SECRET]]\";\n$plaid_url = \"https:\/\/sandbox.plaid.com\";\n<\/pre>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"createanitem\">Create an Item<\/h6>\n\n\n\n<p>In Plaid, an Item is the Object that contains everything about the accounts for one customer at one financial institution. If you have a customer that uses your Plaid integration, an Item will be everything about that customer&#8217;s banking data that you have access to, including transactions.<\/p>\n\n\n\n<p>When using the Sandbox endpoint, Plaid displays the test credentials that give you a positive result (see the image below, at the bottom of each screen). This is especially helpful if you have other folks testing your implementation. For other testing scenarios, they have a way to <a href=\"https:\/\/plaid.com\/docs\/api\/#creating-items-in-the-sandbox\">generate any error you&#8217;re looking for<\/a> in the docs.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><a href=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidDropIn-1.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"558\" src=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidDropIn-1.jpg\" alt=\"Plaid's convenient drop-in module\" class=\"wp-image-20\" srcset=\"https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidDropIn-1.jpg 800w, https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidDropIn-1-300x209.jpg 300w, https:\/\/mwoodruff.net\/articles\/wp-content\/uploads\/2017\/09\/plaidDropIn-1-768x536.jpg 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/a><figcaption>Plaid&#8217;s convenient drop-in module<\/figcaption><\/figure><\/div>\n\n\n\n<p>Plaid uses a bit of Javascript to serve their front-end module to your customers. This has some benefits and detriments:<\/p>\n\n\n\n<table class=\"pro-con\">\n<thead>\n<tr>\n<td class=\"pro\">Pros<\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"pro\">You don&#8217;t have to code every bank logo and name into the interface.<\/td>\n<\/tr>\n<tr>\n<td class=\"pro\">It looks pretty slick, and each major bank&#8217;s branding is there, which should help your customers feel more comfortable divulging their credentials.<\/td>\n<\/tr>\n<tr>\n<td class=\"pro\">You can customize the text on the front end (N.B. This is new as of July 11, 2017).<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n\n\n<table class=\"pro-con\">\n<thead>\n<tr>\n<td class=\"con\">Cons<\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"con\">The customizable text is your-account-wide, meaning you can&#8217;t have two versions on your site.<\/td>\n<\/tr>\n<tr>\n<td class=\"con\">It may not match the look &amp; feel of the page you want to use it on.<\/td>\n<\/tr>\n<tr>\n<td class=\"con\">If a customer cannot find their bank, you have to rely on your language on the interface to get them back to the page you control. There&#8217;s no error from Plaid on this event, so knowing just how many of your customers do not find their bank is impossible, and there&#8217;s no way to direct them right away to using another method of bank verification, like providing their Bank Account ID and Routing number.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n\n\n<p>To implement on your page, first include the library between the HEAD tags:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\n&lt;script src=\"https:\/\/cdn.plaid.com\/link\/v2\/stable\/link-initialize.js\">&lt;\/script>  \n<\/pre>\n\n\n\n<p>Also in the HEAD section, use Javascript to trigger the Plaid module. I&#8217;m using jQuery here.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$(document).ready(function() { \n  \/\/ Trigger the standard Institution Select view \n  document.getElementById('linkButton').onclick = function() { \n    linkHandler.open(); \n  }; \n});\n<\/pre>\n\n\n\n<p>Near the end of your page, include the integration Javascript for initiating the Plaid module. Here, you can identify some settings for the interface, such as what aspects of the Item you want access to (Auth, Identity, etc.) and limiting choices to specific banks. Be sure to set <code>selectAccount: true<\/code> if you will be using the Plaid token with Stripe or other payment gateway.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">var linkHandler = Plaid.create({  \n    selectAccount: true,\n    env: '',\n    apiVersion: 'v2',\n    clientName: 'Client Name',\n    key: '',\n    product: ['auth', 'transactions', 'identity'],\n    webhook: 'https:\/\/myurl.com\/webhooks\/p_responses.php'\n<\/pre>\n\n\n\n<p>Finally, add the button link that you want a customer to click to open the Plaid module.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;button id=\"linkButton\">Link Your Bank Account&lt;\/button><\/pre>\n\n\n\n<p>To test, open the page and click the button. You should see the Plaid module take over the page and ask you to select your bank. If not, check your console (in Chrome, F12) for error messages from Plaid.<\/p>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"processyouritem\">Process Your Item<\/h6>\n\n\n\n<p>Using an AJAX post, use the data returned to POST to a page on your server finalize the Plaid authentication.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    onSuccess: function(public_token, metadata) {  \n    \/\/ The onSuccess function is called when the user has successfully\n    \/\/ authenticated and selected an account to use.    \n\n      $.post( 'process_plaid_token.php', {pt:public_token,md:metadata,id:\"\"}, function( data ) {                        \n          console.log(\"data : \"+data);\n           if (data==\"Success\"){              \n              console.log(\"Success\");\n              window.location.replace(\"thankyou.php\");\/\/Let users know the process was successful \n           }else{\n             console.log(\"Error\");\n             window.location.replace(\"error.php\");\/\/Let users know the process failed\n           }\n        });    \n    },\n<\/pre>\n\n\n\n<p>On the server side, in this case in <code>process_plaid_token.php<\/code>, you need to save the Access Token and Item ID in your DB. The Access Token is for accessing Transactions and other Item data, and the Item ID will be used to identify the Item to which a Webhook refers. Without a saved Item ID, your webhooks are SOL, so don&#8217;t skip this!<\/p>\n\n\n\n<p>To get these important pieces of information, you need to exchange your <strong>public token<\/strong> posted from Javascript (which will expire in 30 minutes) for an <strong>access token<\/strong> (which will not expire).<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  \/\/Exchange public token for Plaid access_token\n   $plaid_token = get_plaid_token($_POST['pt']);\n...\n\n    function get_plaid_token($public_token)\n    {\n        global $plaid_client_id, $plaid_secret, $plaid_url;\n        $data = array(\n            \"client_id\" =&gt; $plaid_client_id,\n            \"secret\" =&gt; $plaid_secret,\n            \"public_token\" =&gt; $public_token\n        );\n\n        $data_fields = json_encode($data);        \n\n        \/\/initialize session\n        $ch=curl_init($plaid_url . \"\/item\/public_token\/exchange\");\n\n        \/\/set options\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_fields);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n        curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          \n          'Content-Type: application\/json',                                                                                \n          'Content-Length: ' . strlen($data_fields))                                                                       \n        );   \n\n        \/\/execute session\n        $token_json = curl_exec($ch);\n        $exchange_token = json_decode($token_json,true);          \n        \/\/close session\n        curl_close($ch);        \n\n        return $exchange_token;\n    }\n<\/pre>\n\n\n\n<p>Another helpful piece of info to associate with your customer is their chosen bank account, if you have them select one. You will have access to ALL bank accounts on their Item, but if your terms of service are specific, you should limit your data to that one account.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------------------------------------------------------------------Save your tokens and IDs \n\/\/Plaid Access Token\n      saveCustomerToken($customer_id, \"Plaid Access Token\", $plaid_token[\"access_token\"]);      \n      \/\/Item ID\n      saveCustomerToken($customer_id, \"Item ID\", $plaid_token['item_id']);  \n        \/\/Institution ID\n      saveCustomerToken($customer_id, \"Institution ID\", $metadata['institution']['institution_id']);\n      \/\/Institution Name\n      saveCustomerToken($customer_id, \"Institution Name\", $metadata['institution']['name']);\n      \/\/Chosen Bank Account ID\n      saveCustomerToken($customer_id, \"Bank Selection Token\", $metadata['account_id']);\n...\nfunction saveCustomerToken($customer_id, $token_type, $token){\n\n    \/\/TODO Save to your Database\n    error_log(\"Customer ID: \" . $customer_id . $token_type . \": \" . $token);\n\n}\n<\/pre>\n\n\n\n<p>If you are integrating with another service, like Stripe or Dwolla, this a good time to do it, since you need to use the Plaid public token before it expires.<\/p>\n\n\n\n<p>If everything went as planned, our data (simply printing to our error log in this example) looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Customer ID: XX Institution ID: ins_1  \nCustomer ID: Institution Name: Bank of America  \nCustomer ID: Bank Selection Token: EmnlkNOTAREALBANKTOKEN5MNNB  \nCustomer ID: Plaid Access Token: access-sandbox-umn0p3-a000-0000-0000-e00000000e000,  \nCustomer ID: Item ID: jykrNOTAREALPLAIDITEMIDE67b  \n<\/pre>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"acceptwebhooks\">Accept Webhooks<\/h6>\n\n\n\n<p>You may be inclined to skip this part &#8220;for later,&#8221; but I recommend you do not. Plaid really expects you to use Webhooks to keep your data updated, and pulling data from them can be slow at times due to the many bank systems they need to connect with. Relying on just-in-time content from them is not sustainable at testing levels, let alone at scale. You should plan to use their webhooks to let you know when to update, and you will have a faster, but still up-to-date system.<\/p>\n\n\n\n<p>The URL of your webhook should have been included in your Javascript when you initiated an Item. When you create that file to accept the webhooks, you can consume the webhook data like so:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"> \n  $payload = @file_get_contents(\"php:\/\/input\");\n  $event=json_decode($payload,true);\n  \/\/ Do something with $event\n\n  \/\/ This checks for the specific Webhook Code that says bank data is ready\n  if($event['webhook_code']==\"DEFAULT_UPDATE\"){\n    \/\/TODO: Find customer ID by Plaid Item ID in $event['item_id']\n    \/\/TODO: Notify admin that this bank data is ready    \n  }  \n\n  http_response_code(200);\/\/let Plaid know you received the webhook\n<\/pre>\n\n\n\n<p>You can see all the <a href=\"https:\/\/plaid.com\/docs\/api\/#webhooks\">possible webhook values at Plaid.com<\/a>.<\/p>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"usethedata\">Use the Data<\/h6>\n\n\n\n<p>Now that you have a test Item and its matching access token, you can use the Plaid data. Use your webhooks to tell you when to make these pulls using cURL.<\/p>\n\n\n\n<p><strong>Balance<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/plaid.com\/docs\/api\/#balance\">Visit Plaid.com<\/a> to see the full data the call below can return.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">        $data = array(\n            \"client_id\" =&gt; $plaid_client_id,\n            \"secret\" =&gt; $plaid_secret,\n            \"access_token\"=&gt;$access_token\n        );  \n        $data_fields = json_encode($data);\n\n        \/\/initialize session\n        $ch=curl_init($plaid_url . \"\/accounts\/balance\/get\");\n\n        \/\/set options\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_fields);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n        curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          \n          'Content-Type: application\/json',                                                                                \n          'Content-Length: ' . strlen($data_fields))                                                                       \n        );   \n\n        \/\/execute session\n        $balance_json = curl_exec($ch);\n        $balance = json_decode($balance_json,true);  \n        \/\/check for errors\n        if(isset($balance['error_code'])){\n          error_log(\"Plaid Error Message: \" . $balance_json);\n        }            \n        \/\/close session\n        curl_close($ch);\n<\/pre>\n\n\n\n<p>Access any of the data below using the same format for your cURL calls, but change the endpoint URL to Plaid.<\/p>\n\n\n\n<p><strong>Identity<\/strong><\/p>\n\n\n\n<p>Get contact info for the bank account holder, which is helpful for verifying a customer is who they say they are. <a href=\"https:\/\/plaid.com\/docs\/api\/#identity\">Identity Documentation<\/a><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$ch=curl_init($plaid_url . \"\/identity\/get\");\n<\/pre>\n\n\n\n<p><strong>Transactions<\/strong><\/p>\n\n\n\n<p>Retrieve individual transactions for each account, for all account types and transaction types. <a href=\"https:\/\/plaid.com\/docs\/api\/#transactions\">Transaction Documentation<\/a><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$ch=curl_init($plaid_url . \"\/transactions\/get\");\n<\/pre>\n\n\n\n<p><strong>Income<\/strong><\/p>\n\n\n\n<p>If your application requires only income information, use this endpoint to get you the results of Plaid&#8217;s calculations. <a href=\"https:\/\/plaid.com\/docs\/api\/#income\">Income Documentation<\/a><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$ch=curl_init($plaid_url . \"\/income\/get\");\n<\/pre>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"nextsteps\">Next Steps<\/h6>\n\n\n\n<p><strong>Changing Endpoints<\/strong><\/p>\n\n\n\n<p>When you&#8217;re ready to move onto the Development endpoint, which allows you to use REAL bank credentials and, basically, do everything the Production endpoint can do except for free, you will need to contact Plaid Sales. I found their disclosure of this information lacking, and was very frustrated until I found it noted on a message board.<\/p>\n\n\n\n<p>In Development, you can see real data for customers, or just for testing, for 100 Items. You will need a contract agreeing to payment terms before you can go to Production, so once you have the Sandbox testing done, it makes sense to get the negotiations going and make sure the pricing is worth it for your application.<\/p>\n\n\n\n<p><strong>Further Integration, Error Checking, Etc.<\/strong><\/p>\n\n\n\n<p>Inevitably, your customers will change their passwords for their banks. You will need to give them a way to easily reauthorize your application, and Plaid has a way to do that. They also have ways to use their banking tokens for connections to payment gateways like Stripe and Dwolla. I recommend heading over to <a href=\"https:\/\/plaid.com\/docs\/api\/#overview\">Plaid&#8217;s Documentation<\/a> to continue reading about everything you can do with their developer-friendly banking product.<\/p>\n\n\n\n<h6 class=\"wp-block-heading\" id=\"fullplaidlinkcode\">Full Plaid Link Code<\/h6>\n\n\n\n<p>The PHP page that calls Plaid&#8217;s module&#8211;be sure to insert your own Plaid keys.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"> &lt;script src=\"https:\/\/cdn.plaid.com\/link\/v2\/stable\/link-initialize.js\">&lt;\/script>\n  &lt;script src=\"https:\/\/code.jquery.com\/jquery-3.2.1.min.js\" integrity=\"sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=\" crossorigin=\"anonymous\">&lt;\/script>\n\n  &lt;script type=\"text\/javascript\">\n\n$(document).ready(function() { \n  \/\/ Trigger the standard Institution Select view\n  document.getElementById('linkButton').onclick = function() {\n    linkHandler.open();\n  };  \n});        \n\n  &lt;\/script>\n\nPlease link your &lt;strong>primary bank account&lt;\/strong>.\n&lt;button id=\"linkButton\">Link Your Bank Account&lt;\/button>              \n\n  &lt;script>\n  var linkHandler = Plaid.create({\n    selectAccount: true,\n    env: '&lt;?php echo $plaid_env ?>',\n    apiVersion: 'v2',\n    clientName: 'Client Name',\n    key: '&lt;?php echo $plaid_public ?>',\n    product: ['auth', 'transactions', 'identity'],\n    webhook: 'https:\/\/myurl.com\/webhooks\/p_responses.php',\n    onLoad: function() {\n      \/\/ The Link module finished loading.\n    },\n    onSuccess: function(public_token, metadata) {\n    \/\/ The onSuccess function is called when the user has successfully\n    \/\/ authenticated and selected an account to use.    \n\n      $.post( 'process_plaid_token.php', {pt:public_token,md:metadata,id:\"&lt;?php echo $customer_id?>\"}, function( data ) {                        \n          console.log(\"data : \"+data);\n           if (data==\"Success\"){              \n              console.log(\"Success\");\n              window.location.replace(\"thankyou.php\");\/\/Let users know the process was successful \n           }else{\n             console.log(\"Error\");\n             window.location.replace(\"error.php\");\/\/Let users know the process failed\n           }\n        });    \n    },\n    onExit: function(err, metadata) {\n      \/\/ The user exited the Link flow. This is not an Error, so much as a user-directed exit   \n      if (err != null) {\n        console.log(err);\n        console.log(metadata);        \n      }\n    },\n  });\n  &lt;\/script> \n\n   \n<\/pre>\n\n\n\n<p>Server-side token processing page.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;?php  \nerror_reporting(E_ALL);  \nini_set('display_errors', 1);  \n  \/\/Process Banking\n  \/\/Plaid-Sandbox  \/\/N.B. This should not be used on the page like this!\n  $plaid_env = 'sandbox';\n  $plaid_client_id = \"[[YOUR PLAID CLIENT ID]]\";\n  $plaid_public = \"[[YOUR PLAID PUBLIC KEY]]\";\n  $plaid_secret = \"[[YOUR PLAID SECRET]]\";\n  $plaid_url = \"https:\/\/sandbox.plaid.com\";\n\n$error=\"\";\nif(!empty($_POST))  \n{   \n  if (isset($_POST['id'])){\/\/This is our customer ID\n    if((isset($_POST['pt']))&amp;&amp;(isset($_POST['md']))){\n      $customer_id = $_POST['id'];\n      $metadata = $_POST['md'];\n      \/\/Exchange public token for Plaid access_token\n      $plaid_token = get_plaid_token($_POST['pt']);\n      \/\/---------------------------------------------------------------------------Save your tokens and IDs                   \n      \/\/Plaid Access Token\n      saveCustomerToken($customer_id, \"Plaid Access Token\", $plaid_token[\"access_token\"]);      \n      \/\/Item ID\n      saveCustomerToken($customer_id, \"Item ID\", $plaid_token['item_id']);  \n        \/\/Institution ID\n      saveCustomerToken($customer_id, \"Institution ID\", $metadata['institution']['institution_id']);\n      \/\/Institution Name\n      saveCustomerToken($customer_id, \"Institution Name\", $metadata['institution']['name']);\n      \/\/Chosen Bank Account ID\n      saveCustomerToken($customer_id, \"Bank Selection Token\", $metadata['account_id']);\n\n      echo \"Success\";\/\/The message Javascript code will look for\n    }else{\n      echo \"Failed: Plaid Link Data Missing\";      \n      error_log(\"Banking Authentication Failed. Plaid Link Data Missing for \" . $customer_id);\n    }\n  }else error_log(\"Banking Authentication Failed. Customer ID Missing \" . $customer_id); \n\n}\n\n    function get_plaid_token($public_token)\n    {\n        global $plaid_client_id, $plaid_secret, $plaid_url;\n        $data = array(\n            \"client_id\" =--> $plaid_client_id,\n            \"secret\" =&gt; $plaid_secret,\n            \"public_token\" =&gt; $public_token\n        );\n\n        $data_fields = json_encode($data);        \n\n        \/\/initialize session\n        $ch=curl_init($plaid_url . \"\/item\/public_token\/exchange\");\n\n        \/\/set options\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_fields);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n        curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          \n          'Content-Type: application\/json',                                                                                \n          'Content-Length: ' . strlen($data_fields))                                                                       \n        );   \n\n        \/\/execute session\n        $token_json = curl_exec($ch);\n        $exchange_token = json_decode($token_json,true);          \n        \/\/close session\n        curl_close($ch);        \n\n        return $exchange_token;\n    }\n\nfunction saveCustomerToken($customer_id, $token_type, $token){\n\n    \/\/TODO Save to your Database\n    error_log(\"Customer ID: \" . $customer_id . $token_type . \": \" . $token);\n\n}\n\n?><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Plaid provides a simple method for IBV, or Instant Bank Verification. By integrating Plaid into your app or website, your customers can use their online banking usernames and passwords to give you secure, tokenized access to their bank account. With that access, you can verify their identity, check their balance, or take a look at &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/mwoodruff.net\/articles\/tutorial\/integrate-plaid-with-php\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Integrate Plaid with PHP&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":21,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24],"tags":[11,9,12,13,10,8],"class_list":["post-223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorial","tag-api","tag-financial-app","tag-ibv","tag-instant-bank-verification","tag-php","tag-plaid"],"_links":{"self":[{"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/posts\/223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/comments?post=223"}],"version-history":[{"count":9,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/posts\/223\/revisions"}],"predecessor-version":[{"id":236,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/posts\/223\/revisions\/236"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/media\/21"}],"wp:attachment":[{"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/media?parent=223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/categories?post=223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mwoodruff.net\/articles\/wp-json\/wp\/v2\/tags?post=223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}