File indexing completed on 2024-05-05 06:02:12

0001 <?php
0002 
0003 /**
0004  * Dwolla REST API Library for PHP
0005  *
0006  * MIT LICENSE
0007  *
0008  * Permission is hereby granted, free of charge, to any person obtaining
0009  * a copy of this software and associated documentation files (the
0010  * "Software"), to deal in the Software without restriction, including
0011  * without limitation the rights to use, copy, modify, merge, publish,
0012  * distribute, sublicense, and/or sell copies of the Software, and to
0013  * permit persons to whom the Software is furnished to do so, subject to
0014  * the following conditions:
0015  *
0016  * The above copyright notice and this permission notice shall be
0017  * included in all copies or substantial portions of the Software.
0018  *
0019  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
0020  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
0021  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
0022  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
0023  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
0024  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
0025  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0026  *
0027  * SUPPORT
0028  * Users should seek support on our API Support board located at:
0029  * https://discuss.dwolla.com/category/api-support
0030  *
0031  * @package   Dwolla
0032  * @author    Michael Schonfeld <michael@dwolla.com>
0033  * @copyright Copyright (c) 2012 Dwolla Inc. (http://www.dwolla.com)
0034  * @license   http://opensource.org/licenses/MIT MIT
0035  * @version   1.6.1
0036  * @link      http://www.dwolla.com
0037  */
0038 
0039 if (!function_exists('curl_init')) {
0040     throw new Exception("Dwolla's API Client Library requires the CURL PHP extension.");
0041 }
0042 
0043 if (!function_exists('json_decode')) {
0044     throw new Exception("Dwolla's API Client Library requires the JSON PHP extension.");
0045 }
0046 
0047 if (!function_exists('getallheaders')) {
0048     function getallheaders()
0049     {
0050         $headers = '';
0051 
0052         foreach ($_SERVER as $name => $value) {
0053             if (substr($name, 0, 5) == 'HTTP_') {
0054                 $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
0055             }
0056         }
0057 
0058         return $headers;
0059     }
0060 }
0061 
0062 class DwollaRestClient
0063 {
0064 
0065     const API_SERVER = "https://www.dwolla.com/";
0066     const SANDBOX_SERVER = "https://uat.dwolla.com/";
0067 
0068     /**
0069      * @var string Dwolla API key
0070      */
0071     private $apiKey;
0072     /**
0073      * @var string Dwolla API key
0074      */
0075     private $apiSecret;
0076     /**
0077      * @var string oauth token
0078      */
0079     private $oauthToken;
0080     /**
0081      * @var array oauth authentication scopes
0082      */
0083     private $permissions;
0084     /**
0085      *
0086      * @var string URL to return the user to after the authentication request
0087      */
0088     private $redirectUri;
0089     /**
0090      * @var string Transaction mode. Can be 'live' or 'test'
0091      */
0092     private $mode;
0093     /**
0094      * @var array Off-Site Gateway order items
0095      */
0096     private $gatewaySession;
0097     /**
0098      * @var string error messages returned from Dwolla
0099      */
0100     private $errorMessage = false;
0101     /**
0102      * @var string operate in debug mode
0103      */
0104     private $debugMode = false;
0105     /**
0106      * @var bool use sandbox for API_SERVER
0107      */
0108     private $sandboxMode = false;
0109     /** @var string */
0110     private $OAUTH_TAIL = "oauth/rest/";
0111 
0112     /**
0113      * Sets the initial state of the client
0114      *
0115      * @param bool|string $apiKey
0116      * @param bool|string $apiSecret
0117      * @param bool|string $redirectUri
0118      * @param array $permissions
0119      * @param string $mode
0120      * @param bool $debugMode
0121      * @param bool $sandboxMode
0122      */
0123     public function __construct($apiKey = false, $apiSecret = false, $redirectUri = false, $permissions = array("send", "transactions", "balance", "request", "contacts", "accountinfofull", "funding"), $mode = 'live', $debugMode = false, $sandboxMode = false)
0124     {
0125         $this->apiKey = $apiKey;
0126         $this->apiSecret = $apiSecret;
0127         $this->redirectUri = $redirectUri;
0128         $this->permissions = $permissions;
0129         $this->apiServerUrl = $sandboxMode ? self::SANDBOX_SERVER : self::API_SERVER;
0130         $this->setMode($mode);
0131         $this->debugMode = $debugMode;
0132     }
0133 
0134     /**
0135      * Get oauth authentication URL
0136      *
0137      * @return string URL
0138      */
0139     public function getAuthUrl()
0140     {
0141         $params = array(
0142             'client_id' => $this->apiKey,
0143             'response_type' => 'code',
0144             'scope' => implode('|', $this->permissions)
0145         );
0146 
0147         // Only append a redirectURI if one was explicitly specified
0148         if ($this->redirectUri) {
0149             $params['redirect_uri'] = $this->redirectUri;
0150         }
0151 
0152         $url = $this->apiServerUrl . 'oauth/v2/authenticate?' . http_build_query($params);
0153 
0154         return $url;
0155     }
0156 
0157     /**
0158      * Request oauth token from Dwolla
0159      *
0160      * @param string $code Temporary code returned from Dwolla
0161      * @return string oauth token
0162      */
0163     public function requestToken($code)
0164     {
0165         if (!$code) {
0166             return $this->setError('Please pass an oauth code.');
0167         }
0168 
0169         $params = array(
0170             'client_id' => $this->apiKey,
0171             'client_secret' => $this->apiSecret,
0172             'redirect_uri' => $this->redirectUri,
0173             'grant_type' => 'authorization_code',
0174             'code' => $code
0175         );
0176         $url = $this->apiServerUrl . 'oauth/v2/token?' . http_build_query($params);
0177         $response = $this->curl($url, 'GET');
0178 
0179         if (isset($response['error'])) {
0180             return $this->setError($response['error_description']);
0181         }
0182 
0183         if (!$response['access_token']) {
0184             return $this->setError($response['Message'] ? $response['Message'] : 'Failed to request token. No error message given. Use debug mode to find out more.');
0185         }
0186 
0187         return $response['access_token'];
0188     }
0189 
0190     /**
0191      * @param string $message Error message
0192      * @return bool
0193      */
0194     protected function setError($message)
0195     {
0196         $this->errorMessage = $message;
0197 
0198         return false;
0199     }
0200 
0201     /**
0202      * Execute curl request
0203      *
0204      * @param string $url URL to send requests
0205      * @param string $method HTTP method
0206      * @param array $params request params
0207      * @return array|null Returns array of results or null if json_decode fails
0208      */
0209     protected function curl($url, $method = 'GET', $params = array())
0210     {
0211         // Encode POST data
0212         $data = json_encode($params);
0213 
0214         // Set request headers
0215         $headers = array('Accept: application/json', 'Content-Type: application/json;charset=UTF-8');
0216         if ($method == 'POST') {
0217             $headers[] = 'Content-Length: ' . strlen($data);
0218         }
0219 
0220         // Set up our CURL request
0221         $ch = curl_init();
0222         curl_setopt($ch, CURLOPT_URL, $url);
0223         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
0224         curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
0225         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
0226         curl_setopt($ch, CURLOPT_TIMEOUT, 5);
0227         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
0228         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
0229         curl_setopt($ch, CURLOPT_HEADER, false);
0230         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
0231 
0232         // Windows require this certificate
0233         if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
0234             $ca = dirname(__FILE__);
0235             curl_setopt($ch, CURLOPT_CAINFO, $ca); // Set the location of the CA-bundle
0236             curl_setopt($ch, CURLOPT_CAINFO, $ca . '/cacert.pem'); // Set the location of the CA-bundle
0237         }
0238 
0239         // Initiate request
0240         $rawData = curl_exec($ch);
0241 
0242         // If HTTP response wasn't 200,
0243         // log it as an error!
0244         $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
0245         if ($code !== 200) {
0246             if ($this->debugMode) {
0247                 echo "Here is all the information we got from curl: \n";
0248                 print_r(curl_getinfo($ch));
0249                 print_r(curl_error($ch));
0250             }
0251 
0252             return array(
0253                 'Success' => false,
0254                 'Message' => "Request failed. Server responded with: {$code}"
0255             );
0256         }
0257 
0258         // All done with CURL
0259         curl_close($ch);
0260 
0261         // Otherwise, assume we got some
0262         // sort of a response
0263         return json_decode($rawData, true);
0264     }
0265 
0266     /**
0267      * Grabs the account information for the
0268      * authenticated user
0269      *
0270      * @return array Authenticated user's account information
0271      */
0272     public function me()
0273     {
0274         $response = $this->get('users/');
0275         return $this->parse($response);
0276     }
0277 
0278     /**
0279      * Executes GET requests against API
0280      *
0281      * @param string $request
0282      * @param array $params
0283      * @return array|null Array of results or null if json_decode fails in curl()
0284      */
0285     protected function get($request, $params = array())
0286     {
0287         $params['oauth_token'] = $this->oauthToken;
0288 
0289         $delimiter = (strpos($request, '?') === false) ? '?' : '&';
0290         $url = $this->apiServerUrl . $this->OAUTH_TAIL . $request . $delimiter . http_build_query($params);
0291 
0292         if ($this->debugMode) {
0293             echo "Getting request from: {$url} \n";
0294         }
0295 
0296         $rawData = $this->curl($url, 'GET');
0297 
0298         if ($this->debugMode) {
0299             echo "Got response:";
0300             print_r($rawData);
0301             echo "\n";
0302         }
0303 
0304         return $rawData;
0305     }
0306 
0307     /**
0308      * Parse Dwolla API response
0309      *
0310      * @param array $response
0311      * @return array
0312      */
0313     protected function parse($response)
0314     {
0315         if (!$response['Success']) {
0316             $this->setError($response['Message']);
0317 
0318             // Exception for /register method
0319             if ($response['Response']) {
0320                 $this->errorMessage .= " :: " . json_encode($response['Response']);
0321             }
0322 
0323             return false;
0324         }
0325 
0326         return $response['Response'];
0327     }
0328 
0329     /**
0330      * Grabs the basic account information for
0331      * the provided Dwolla account Id
0332      *
0333      * @param string $userId Dwolla Account Id
0334      * @return array Basic account information
0335      */
0336     public function getUser($userId)
0337     {
0338         $params = array(
0339             'client_id' => $this->apiKey,
0340             'client_secret' => $this->apiSecret
0341         );
0342 
0343         $response = $this->get("users/{$userId}", $params);
0344         $user = $this->parse($response);
0345 
0346         return $user;
0347     }
0348 
0349     /**
0350      * Get a list of users nearby a
0351      * given geo location
0352      *
0353      * @param $lat
0354      * @param $long
0355      * @return array Users
0356      */
0357     public function usersNearby($lat, $long)
0358     {
0359         $params = array(
0360             'client_id' => $this->apiKey,
0361             'client_secret' => $this->apiSecret,
0362             'latitude' => $lat,
0363             'longitude' => $long
0364         );
0365 
0366         $response = $this->get("users/nearby", $params);
0367         $users = $this->parse($response);
0368 
0369         return $users;
0370     }
0371 
0372     /**
0373      * Register new Dwolla user
0374      *
0375      * @param string $email
0376      * @param string $password
0377      * @param int $pin
0378      * @param string $firstName
0379      * @param string $lastName
0380      * @param string $address
0381      * @param string $city
0382      * @param string $state
0383      * @param int $zip
0384      * @param string $phone
0385      * @param string $dateOfBirth
0386      * @param bool $acceptTerms
0387      * @param string $address2
0388      * @param string $type Dwolla account type
0389      * @param string $organization
0390      * @param string $ein
0391      * @return array New user information
0392      */
0393     public function register($email, $password, $pin, $firstName, $lastName, $address, $city, $state, $zip, $phone, $dateOfBirth, $acceptTerms, $address2 = '', $type = 'Personal', $organization = '', $ein = ''
0394     )
0395     {
0396         $params = array(
0397             'client_id' => $this->apiKey,
0398             'client_secret' => $this->apiSecret,
0399             'email' => $email,
0400             'password' => $password,
0401             'pin' => $pin,
0402             'firstName' => $firstName,
0403             'lastName' => $lastName,
0404             'address' => $address,
0405             'address2' => $address2,
0406             'city' => $city,
0407             'state' => $state,
0408             'zip' => $zip,
0409             'phone' => $phone,
0410             'dateOfBirth' => $dateOfBirth,
0411             'type' => $type,
0412             'organization' => $organization,
0413             'ein' => $ein,
0414             'acceptTerms' => $acceptTerms
0415         );
0416         $response = $this->post('register/', $params, false); // false = don't include oAuth token
0417 
0418         $user = $this->parse($response);
0419 
0420         return $user;
0421     }
0422 
0423     /**
0424      * Executes POST request against API
0425      *
0426      * @param string $request
0427      * @param array|bool $params
0428      * @param bool $includeToken Include oauth token in request?
0429      * @return array|null
0430      */
0431     protected function post($request, $params = false, $includeToken = true)
0432     {
0433         $url = $this->apiServerUrl . $this->OAUTH_TAIL . $request . ($includeToken ? "?oauth_token=" . urlencode($this->oauthToken) : "");
0434 
0435         if ($this->debugMode) {
0436             echo "Posting request to: {$url} :: With params: \n";
0437             print_r($params);
0438         }
0439 
0440         $rawData = $this->curl($url, 'POST', $params);
0441 
0442         if ($this->debugMode) {
0443             echo "Got response:";
0444             print_r($rawData);
0445             echo "\n";
0446         }
0447 
0448         return $rawData;
0449     }
0450 
0451     /**
0452      * Search contacts
0453      *
0454      * @param bool|string $search Search term(s)
0455      * @param array $types Types of contacts (Dwolla, Facebook . . .)
0456      * @param int $limit Number of contacts to retrieve between 1 and 200.
0457      * @return array
0458      */
0459     public function contacts($search = false, $types = array('Dwolla'), $limit = 10)
0460     {
0461         $params = array(
0462             'search' => $search,
0463             'types' => implode(',', $types),
0464             'limit' => $limit
0465         );
0466         $response = $this->get('contacts', $params);
0467 
0468         $contacts = $this->parse($response);
0469 
0470         return $contacts;
0471     }
0472 
0473     /**
0474      * Use this method to retrieve nearby Dwolla spots within the range of the
0475      * provided latitude and longitude.
0476      *
0477      * Half of the limit are returned as spots with closest proximity. The other
0478      * half of the spots are returned as random spots within the range.
0479      * This call can return nearby venues on Foursquare but not Dwolla, they will have an Id of "null"
0480      *
0481      * @param float $latitude
0482      * @param float $longitude
0483      * @param int $range Range to search in miles
0484      * @param int $limit Limit search to this number results
0485      * @return array Search results
0486      */
0487     public function nearbyContacts($latitude, $longitude, $range = 10, $limit = 10)
0488     {
0489         $params = array(
0490             'latitude' => $latitude,
0491             'longitude' => $longitude,
0492             'limit' => $limit,
0493             'range' => $range,
0494             'client_id' => $this->apiKey,
0495             'client_secret' => $this->apiSecret,
0496         );
0497 
0498         $response = $this->get('contacts/nearby', $params);
0499         $contacts = $this->parse($response);
0500 
0501         return $contacts;
0502     }
0503 
0504     /**
0505      * Retrieve a list of verified funding sources for the user associated
0506      * with the authorized access token.
0507      *
0508      * @return array Funding Sources
0509      */
0510     public function fundingSources()
0511     {
0512         $response = $this->get('fundingsources');
0513         return $this->parse($response);
0514     }
0515 
0516     /**
0517      * Retrieve a verified funding source by identifier for the user associated
0518      * with the authorized access token.
0519      *
0520      * @param string $fundingSourceId Funding Source ID
0521      * @return array Funding Source Details
0522      */
0523     public function fundingSource($fundingSourceId)
0524     {
0525         $response = $this->get("fundingsources/{$fundingSourceId}");
0526         return $this->parse($response);
0527     }
0528 
0529     /**
0530      * Add a funding source for the user associated
0531      * with the authorized access token.
0532      *
0533      * @param $accountNumber
0534      * @param $routingNumber
0535      * @param $accountType
0536      * @param $accountName
0537      * @return array Funding Sources
0538      */
0539     public function addFundingSource($accountNumber, $routingNumber, $accountType, $accountName)
0540     {
0541         // Verify required parameters
0542         if (!$accountNumber) {
0543             return $this->setError('Please enter a bank account number.');
0544         } else if (!$routingNumber) {
0545             return $this->setError('Please enter a bank routing number.');
0546         } else if (!$accountType) {
0547             return $this->setError('Please enter an account type.');
0548         } else if (!$accountName) {
0549             return $this->setError('Please enter an account name.');
0550         }
0551 
0552         // Build request, and send it to Dwolla
0553         $params = array(
0554             'account_number' => $accountNumber,
0555             'routing_number' => $routingNumber,
0556             'account_type' => $accountType,
0557             'name' => $accountName
0558         );
0559 
0560         $response = $this->post('fundingsources/', $params);
0561         return $this->parse($response);
0562     }
0563 
0564     /**
0565      * Verify a funding source for the user associated
0566      * with the authorized access token.
0567      *
0568      * @param $fundingSourceId
0569      * @param $deposit1
0570      * @param $deposit2
0571      * @return array Funding Sources
0572      */
0573     public function verifyFundingSource($fundingSourceId, $deposit1, $deposit2)
0574     {
0575         // Verify required parameters
0576         if (!$deposit1) {
0577             return $this->setError('Please enter an amount for deposit1.');
0578         } else if (!$deposit2) {
0579             return $this->setError('Please enter an amount for deposit2.');
0580         } else if (!$fundingSourceId) {
0581             return $this->setError('Please enter a funding source ID.');
0582         }
0583 
0584         // Build request, and send it to Dwolla
0585         $params = array(
0586             'deposit1' => $deposit1,
0587             'deposit2' => $deposit2
0588         );
0589 
0590         $response = $this->post("fundingsources/{$fundingSourceId}/verify", $params);
0591         return $this->parse($response);
0592     }
0593 
0594     /**
0595      * Verify a funding source for the user associated
0596      * with the authorized access token.
0597      *
0598      * @param $fundingSourceId
0599      * @param $pin
0600      * @param $amount
0601      * @return array Funding Sources
0602      */
0603     public function withdraw($fundingSourceId, $pin, $amount)
0604     {
0605         // Verify required parameters
0606         if (!$pin) {
0607             return $this->setError('Please enter a PIN.');
0608         } else if (!$fundingSourceId) {
0609             return $this->setError('Please enter a funding source ID.');
0610         } else if (!$amount) {
0611             return $this->setError('Please enter an amount.');
0612         }
0613 
0614         // Build request, and send it to Dwolla
0615         $params = array(
0616             'pin' => $pin,
0617             'amount' => $amount
0618         );
0619 
0620         $response = $this->post("fundingsources/{$fundingSourceId}/withdraw", $params);
0621         return $this->parse($response);
0622     }
0623 
0624     /**
0625      * Verify a funding source for the user associated
0626      * with the authorized access token.
0627      *
0628      * @param $fundingSourceId
0629      * @param $pin
0630      * @param $amount
0631      * @return array Funding Sources
0632      */
0633     public function deposit($fundingSourceId, $pin, $amount)
0634     {
0635         // Verify required parameters
0636         if (!$pin) {
0637             return $this->setError('Please enter a PIN.');
0638         } else if (!$fundingSourceId) {
0639             return $this->setError('Please enter a funding source ID.');
0640         } else if (!$amount) {
0641             return $this->setError('Please enter an amount.');
0642         }
0643 
0644         // Build request, and send it to Dwolla
0645         $params = array(
0646             'pin' => $pin,
0647             'amount' => $amount
0648         );
0649 
0650         $response = $this->post("fundingsources/{$fundingSourceId}/deposit", $params);
0651         return $this->parse($response);
0652     }
0653 
0654     /**
0655      * Retrieve the account balance for the user with the given authorized
0656      * access token.
0657      *
0658      * @return float Balance in USD
0659      */
0660     public function balance()
0661     {
0662         $response = $this->get('balance/');
0663         return $this->parse($response);
0664     }
0665 
0666     /**
0667      * Send funds to a destination user from the user associated with the
0668      * authorized access token.
0669      * @param string $destinationId Dwolla identifier, Facebook identifier, Twitter identifier, phone number, or email address
0670      * @param float $amount Amount of funds to transfer to the destination user.
0671      * @param string $firstName The source bank account holder's first name
0672      * @param string $lastName The source bank account holder's last name
0673      * @param string $email The source bank account holder's email address
0674      * @param int $routingNumber The source bank routing number
0675      * @param int $accountNumber The source bank account number
0676      * @param string $accountType "Checking" or "Savings"
0677      *
0678      * The following are NON-essential params: *
0679      * @param bool $assumeCosts defaults to false
0680      * @param string $destinationType Type of destination user. Can be Dwolla, Facebook, Twitter, Email, or Phone. Defaults to Dwolla.
0681      * @param string $notes Note to attach to the transaction. Limited to 250 characters.
0682      * @param bool|int $groupId ID specified by the client application, to be associated with the transaction. May be used to group transactions by the given ID. Note: Transactions can be polled by their given 'groupId' using the transactions/Listing method.
0683      * @param array|bool $additionalFees Array of Additional facilitator fees, array('destinationId'=Facilitator's Dwolla ID,  'amount'=Faciliator Amount)
0684      * @param float $facilitatorAmount facilitatorAmount    Amount of the facilitator fee to override. Only applicable if the facilitator fee feature is enabled. If set to 0, facilitator fee is disabled for transaction. Cannot exceed 50% of the 'amount'.
0685      * @param bool $assumeAdditionalFees assumeAdditionalFees     Set to true if the sending user will assume all Facilitator fees. Set to false if the destination user will assume all Facilitator fees. Does not affect the Dwolla fee.
0686      * @return string Transaction Id
0687      */
0688 
0689     public function guestSend($destinationId, $amount, $firstName, $lastName, $email, $routingNumber, $accountNumber, $accountType, $assumeCosts = false, $destinationType = 'Dwolla', $notes = '', $groupId = false, $additionalFees = false, $facilitatorAmount = NULL, $assumeAdditionalFees = false)
0690     {
0691         // Verify required parameters
0692         if (!$destinationId) {
0693             return $this->setError('Please enter a destination ID.');
0694         } else if (!$amount) {
0695             return $this->setError('Please enter a transaction amount.');
0696         } else if (!$firstName) {
0697             return $this->setError('Please enter a destination ID.');
0698         } else if (!$lastName) {
0699             return $this->setError('Please enter a transaction amount.');
0700         } else if (!$email) {
0701             return $this->setError('Please enter a destination ID.');
0702         } else if (!$routingNumber) {
0703             return $this->setError('Please enter a transaction amount.');
0704         } else if (!$accountNumber) {
0705             return $this->setError('Please enter a destination ID.');
0706         } else if (!$accountType) {
0707             return $this->setError('Please enter a transaction amount.');
0708         }
0709 
0710         // Build request, and send it to Dwolla
0711         $params = array(
0712             'destinationId' => $destinationId,
0713             'amount' => $amount,
0714             'firstName' => $firstName,
0715             'lastName' => $lastName,
0716             'email' => $email,
0717             'routingNumber' => $routingNumber,
0718             'accountNumber' => $accountNumber,
0719             'accountType' => $accountType,
0720             'assumeCosts' => $assumeCosts,
0721             'destinationType' => $destinationType,
0722             'notes' => $notes,
0723             'groupId' => $groupId,
0724             'additionalFees' => $additionalFees,
0725             'facilitatorAmount' => $facilitatorAmount,
0726             'assumeAdditionalFees' => $assumeAdditionalFees
0727         );
0728         $response = $this->post('transactions/guestsend', $params);
0729 
0730         // Parse Dwolla's response
0731         $transactionId = $this->parse($response);
0732 
0733         return $transactionId;
0734     }
0735 
0736     /**
0737      * Send funds to a destination user from the user associated with the
0738      * authorized access token.
0739      *
0740      * @param bool|int $pin
0741      * @param bool|string $destinationId Dwolla identifier, Facebook identifier, Twitter identifier, phone number, or email address
0742      * @param bool|float $amount
0743      * @param string $destinationType Type of destination user. Can be Dwolla, Facebook, Twitter, Email, or Phone. Defaults to Dwolla.
0744      * @param string $notes Note to attach to the transaction. Limited to 250 characters.
0745      * @param float $facilitatorAmount
0746      * @param bool $assumeCosts Will sending user assume the Dwolla fee?
0747      * @param string $fundsSource Funding source ID to use. Defaults to Dwolla balance.
0748      * @param array|bool $additionalFees JSON array of facilitator fee objects ({$destinationId, $amount}
0749      * ).
0750      * @param bool $assumeAdditionalFees Determines whether or not the sender assumes the facilitator fees (false by default)
0751      * @return string Transaction Id
0752      */
0753     public function send($pin = false, $destinationId = false, $amount = false, $destinationType = 'Dwolla', $notes = '', $facilitatorAmount = NULL, $assumeCosts = false, $fundsSource = 'balance', $additionalFees = FALSE, $assumeAdditionalFees = FALSE)
0754     {
0755         // Verify required parameters
0756         if (!$pin) {
0757             return $this->setError('Please enter a PIN.');
0758         } else if (!$destinationId) {
0759             return $this->setError('Please enter a destination ID.');
0760         } else if (!$amount) {
0761             return $this->setError('Please enter a transaction amount.');
0762         }
0763 
0764         // Build request, and send it to Dwolla
0765         $params = array(
0766             'pin' => $pin,
0767             'destinationId' => $destinationId,
0768             'destinationType' => $destinationType,
0769             'amount' => $amount,
0770             'facilitatorAmount' => $facilitatorAmount,
0771             'assumeCosts' => $assumeCosts,
0772             'notes' => $notes,
0773             'fundsSource' => $fundsSource,
0774         );
0775         if ($additionalFees) {
0776             $params['additionalFees'] = $additionalFees;
0777         }
0778         if ($assumeAdditionalFees) {
0779             $params['assumeAdditionalFees'] = $assumeAdditionalFees;
0780         }
0781 
0782         $response = $this->post('transactions/send', $params);
0783 
0784         // Parse Dwolla's response
0785         $transactionId = $this->parse($response);
0786 
0787         return $transactionId;
0788     }
0789 
0790     /**
0791      * Request funds from a source user, originating from the user associated
0792      * with the authorized access token.
0793      *
0794      * @param bool|string $sourceId
0795      * @param bool|float $amount
0796      * @param string $sourceType
0797      * @param string $notes
0798      * @param float $facilitatorAmount
0799      * @return int Request Id
0800      */
0801     public function request($sourceId = false, $amount = false, $sourceType = 'Dwolla', $notes = '', $facilitatorAmount = NULL)
0802     {
0803         // Verify required parameters
0804         if (!$sourceId) {
0805             return $this->setError('Please enter a source ID.');
0806         } else if (!$amount) {
0807             return $this->setError('Please enter a transaction amount.');
0808         }
0809 
0810         // Build request, and send it to Dwolla
0811         $params = array(
0812             'sourceId' => $sourceId,
0813             'sourceType' => $sourceType,
0814             'amount' => $amount,
0815             'facilitatorAmount' => $facilitatorAmount,
0816             'notes' => $notes
0817         );
0818         $response = $this->post('requests/', $params);
0819 
0820         // Parse Dwolla's response
0821         $transactionId = $this->parse($response);
0822 
0823         return $transactionId;
0824     }
0825 
0826     /**
0827      * Get a request by its ID
0828      * @param $requestId
0829      * @return array Request with the given ID
0830      */
0831     public function requestById($requestId)
0832     {
0833         // Verify required parameters
0834         if (!$requestId) {
0835             return $this->setError('Please enter a request ID.');
0836         }
0837 
0838         // Build request, and send it to Dwolla
0839         $response = $this->get("requests/{$requestId}");
0840 
0841         // Parse Dwolla's response
0842         $request = $this->parse($response);
0843 
0844         return $request;
0845     }
0846 
0847     /**
0848      * Fulfill (:send) a pending payment request
0849      * @param $requestId
0850      * @param $pin
0851      * @param bool $amount
0852      * @param bool $notes
0853      * @param bool $fundsSource
0854      * @param bool $assumeCosts
0855      * @return array Transaction information
0856      */
0857     public function fulfillRequest($requestId, $pin, $amount = false, $notes = false, $fundsSource = false, $assumeCosts = false)
0858     {
0859         // Verify required parameters
0860         if (!$pin) {
0861             return $this->setError('Please enter a PIN.');
0862         } else if (!$requestId) {
0863             return $this->setError('Please enter a request ID.');
0864         }
0865 
0866         // Build request, and send it to Dwolla
0867         $params = array(
0868             'pin' => $pin
0869         );
0870         if ($amount) {
0871             $params['amount'] = $amount;
0872         }
0873         if ($notes) {
0874             $params['notes'] = $notes;
0875         }
0876         if ($fundsSource) {
0877             $params['fundsSource'] = $fundsSource;
0878         }
0879         if ($assumeCosts) {
0880             $params['assumeCosts'] = $assumeCosts;
0881         }
0882 
0883         $response = $this->post("requests/{$requestId}/fulfill", $params);
0884         return $this->parse($response);
0885     }
0886 
0887     /**
0888      * Cancel (:reject) a pending payment request
0889      * @param $requestId
0890      * @return array Transaction information
0891      */
0892     public function cancelRequest($requestId)
0893     {
0894         // Verify required parameters
0895         if (!$requestId) {
0896             return $this->setError('Please enter a request ID.');
0897         }
0898 
0899         $response = $this->post("requests/{$requestId}/cancel", array());
0900         return $this->parse($response);
0901     }
0902 
0903     /**
0904      * Get a list of pending money requests
0905      * @return array Pending Requests
0906      */
0907     public function requests()
0908     {
0909         // Build request, and send it to Dwolla
0910         $response = $this->get("requests/");
0911 
0912         // Parse Dwolla's response
0913         $requests = $this->parse($response);
0914 
0915         return $requests;
0916     }
0917 
0918     /**
0919      * Grab information for the given transaction ID with
0920      * app credentials (instead of oauth token)
0921      *
0922      * @param int $transactionId Transaction ID to which information is pulled
0923      * @return array Transaction information
0924      */
0925     public function transaction($transactionId)
0926     {
0927         // Verify required parameters
0928         if (!$transactionId) {
0929             return $this->setError('Please enter a transaction ID.');
0930         }
0931 
0932         $params = array(
0933             'client_id' => $this->apiKey,
0934             'client_secret' => $this->apiSecret
0935         );
0936 
0937         // Build request, and send it to Dwolla
0938         $response = $this->get("transactions/{$transactionId}", $params);
0939 
0940         // Parse Dwolla's response
0941         $transaction = $this->parse($response);
0942 
0943         return $transaction;
0944     }
0945 
0946     /**
0947      * Verify a signature that came back
0948      * with an offsite gateway redirect
0949      *
0950      * @param {string} Proposed signature; (required)
0951      * @param {string} Dwolla's checkout ID; (required)
0952      * @param {string} Dwolla's reported total amount; (required)
0953      *
0954      * @return {boolean} Whether or not the signature is valid
0955      */
0956 
0957     /**
0958      * Retrieve a list of transactions for the user associated with the
0959      * authorized access token.
0960      *
0961      * @param bool|string $sinceDate Earliest date and time for which to retrieve transactions.
0962      *        Defaults to 7 days prior to current date and time in UTC. Format: DD-MM-YYYY
0963      * @param array|bool $types Types of transactions to retrieve.  Options are money_sent, money_received, deposit, withdrawal, and fee.
0964      * @param int $limit Number of transactions to retrieve between 1 and 200
0965      * @param int $skip Number of transactions to skip
0966      * @return array Transaction search results
0967      */
0968     public function listings($sinceDate = false, $types = false, $limit = 10, $skip = 0)
0969     {
0970         $params = array(
0971             'limit' => $limit,
0972             'skip' => $skip
0973         );
0974 
0975         if ($sinceDate) {
0976             $params['sinceDate'] = $sinceDate;
0977         }
0978         if ($types) {
0979             $params['types'] = implode(',', $types);
0980         }
0981 
0982         // Build request, and send it to Dwolla
0983         $response = $this->get("transactions", $params);
0984 
0985         // Parse Dwolla's response
0986         $listings = $this->parse($response);
0987 
0988         return $listings;
0989     }
0990 
0991     /**
0992      * Retrieve transactions stats for the user associated with the authorized
0993      * access token.
0994      *
0995      * @param array $types Options are 'TransactionsCount' and 'TransactionsTotal'
0996      * @param string $startDate Starting date and time to for which to process transactions stats. Defaults to 0300 of the current day in UTC.
0997      * @param string $endDate Ending date and time to for which to process transactions stats. Defaults to current date and time in UTC.
0998      * @return array Transaction stats search results
0999      */
1000     public function stats($types = array('TransactionsCount', 'TransactionsTotal'), $startDate = null, $endDate = null)
1001     {
1002         $params = array(
1003             'types' => implode(',', $types),
1004             'startDate' => $startDate,
1005             'endDate' => $endDate
1006         );
1007 
1008         // Build request, and send it to Dwolla
1009         $response = $this->get("transactions/stats", $params);
1010 
1011         // Parse Dwolla's response
1012         $stats = $this->parse($response);
1013 
1014         return $stats;
1015     }
1016 
1017     /**
1018      * Creates an empty Off-Site Gateway Order Items array
1019      */
1020     public function startGatewaySession()
1021     {
1022         $this->gatewaySession = array();
1023     }
1024 
1025     /**
1026      * Adds new order item to gateway session
1027      *
1028      * @param string $name
1029      * @param float $price Item price in USD
1030      * @param int $quantity Number of items
1031      * @param string $description Item description
1032      */
1033     public function addGatewayProduct($name, $price, $quantity = 1, $description = '')
1034     {
1035         $product = array(
1036             'Name' => $name,
1037             'Description' => $description,
1038             'Price' => $price,
1039             'Quantity' => $quantity
1040         );
1041 
1042         $this->gatewaySession[] = $product;
1043     }
1044 
1045     /**
1046      * Creates and executes Server-to-Server checkout request
1047      * @link http://developers.dwolla.com/dev/docs/gateway#server-to-server
1048      *
1049      * @param string $destinationId
1050      * @param string $orderId
1051      * @param float|int $discount
1052      * @param float|int $shipping
1053      * @param float|int $tax
1054      * @param string $notes
1055      * @param string $callback
1056      * @param boolean $allowFundingSources
1057      * @param boolean $allowGuestCheckout
1058      * @param string $additionalFundingSources
1059      * @return string Checkout URL
1060      */
1061     public function getGatewayURL($destinationId, $orderId = null, $discount = 0, $shipping = 0, $tax = 0, $notes = '', $callback = null, $allowFundingSources = TRUE, $allowGuestCheckout = TRUE, $additionalFundingSources = 'true')
1062     {
1063         // TODO add validation? Throw exception if malformed?
1064         $destinationId = $this->parseDwollaID($destinationId);
1065 
1066         // Normalize optional parameters
1067         if (!$shipping) {
1068             $shipping = 0;
1069         } else {
1070             $shipping = floatval($shipping);
1071         }
1072         if (!$tax) {
1073             $tax = 0;
1074         } else {
1075             $tax = floatval($tax);
1076         }
1077         if (!$discount) {
1078             $discount = 0;
1079         } else {
1080             $discount = abs(floatval($discount));
1081         }
1082         if (!$notes) {
1083             $notes = '';
1084         }
1085 
1086         // Calculate subtotal
1087         $subtotal = 0;
1088 
1089         foreach ($this->gatewaySession as $product) {
1090             $subtotal += floatval($product['Price']) * floatval($product['Quantity']);
1091         }
1092 
1093         // Calculate grand total
1094         $total = round($subtotal - $discount + $shipping + $tax, 2);
1095 
1096         // Create request body
1097         $request = array(
1098             'Key' => $this->apiKey,
1099             'Secret' => $this->apiSecret,
1100             'Test' => ($this->mode == 'test') ? 'true' : 'false',
1101             'AdditionalFundingSources' => $additionalFundingSources,
1102             'AllowGuestCheckout' => $allowGuestCheckout ? 'true' : 'false',
1103             'AllowFundingSources' => $allowFundingSources ? 'true' : 'false',
1104             'PurchaseOrder' => array(
1105                 'DestinationId' => $destinationId,
1106                 'OrderItems' => $this->gatewaySession,
1107                 'Discount' => -$discount,
1108                 'Shipping' => $shipping,
1109                 'Tax' => $tax,
1110                 'Total' => $total,
1111                 'Notes' => $notes
1112             )
1113         );
1114 
1115         // Append optional parameters
1116         if ($this->redirectUri) {
1117             $request['Redirect'] = $this->redirectUri;
1118         }
1119 
1120         if ($callback) {
1121             $request['Callback'] = $callback;
1122         }
1123 
1124         if ($orderId) {
1125             $request['OrderId'] = $orderId;
1126         }
1127 
1128         // Send off the request
1129         $response = $this->curl(($this->apiServerUrl . 'payment/request/'), 'POST', $request);
1130 
1131         if ($response['Result'] != 'Success') {
1132             $this->setError($response['Message']);
1133         }
1134 
1135         return ($this->apiServerUrl . 'payment/checkout/' . $response['CheckoutId']);
1136     }
1137 
1138     /**
1139      * Returns properly formatted Dwolla Id
1140      *
1141      * @param string|int $id
1142      * @return string Properly formatted Dwolla Id
1143      */
1144     public function parseDwollaID($id)
1145     {
1146         $id = preg_replace("/[^0-9]/", "", $id);
1147         $id = preg_replace("/([0-9]{3})([0-9]{3})([0-9]{4})/", "$1-$2-$3", $id);
1148 
1149         return $id;
1150     }
1151 
1152     /**
1153      * Verify the signature returned from Offsite-Gateway Redirect
1154      *
1155      * @param bool|string $signature
1156      * @param bool|string $checkoutId
1157      * @param bool|float $amount
1158      * @return bool Is signature valid?
1159      */
1160     public function verifyGatewaySignature($signature = false, $checkoutId = false, $amount = false)
1161     {
1162         // Verify required parameters
1163         if (!$signature) {
1164             return $this->setError('Please pass a proposed signature.');
1165         }
1166         if (!$checkoutId) {
1167             return $this->setError('Please pass a checkout ID.');
1168         }
1169         if (!$amount) {
1170             return $this->setError('Please pass a total transaction amount.');
1171         }
1172         $amount = number_format($amount, 2);
1173         // Calculate an HMAC-SHA1 hexadecimal hash
1174         // of the checkoutId and amount ampersand separated
1175         // using the consumer secret of the application
1176         // as the hash key.
1177         //
1178         // @doc: http://developers.dwolla.com/dev/docs/gateway
1179         $hash = hash_hmac("sha1", "{$checkoutId}&{$amount}", $this->apiSecret);
1180 
1181         if ($hash !== $signature) {
1182             return $this->setError('Dwolla signature verification failed.');
1183         }
1184 
1185         return TRUE;
1186     }
1187 
1188     /**
1189      * Verify the signature returned from Webhook notifications
1190      *
1191      * @return bool Is signature valid?
1192      */
1193     public function verifyWebhookSignature()
1194     {
1195         // 1. Get the request body
1196         $body = file_get_contents('php://input');
1197 
1198         // 2. Get Dwolla's signature
1199         $headers = getallheaders();
1200         $signature = $headers['X-Dwolla-Signature'];
1201 
1202         // 3. Calculate hash, and compare to the signature
1203         $hash = hash_hmac('sha1', $body, $this->apiSecret);
1204         $validated = ($hash == $signature);
1205 
1206         if (!$validated) {
1207             return $this->setError('Dwolla signature verification failed.');
1208         }
1209 
1210         return TRUE;
1211     }
1212 
1213     /**
1214      * Create a MassPay job
1215      *
1216      * @param string $pin User's account PIN
1217      * @param array $filedata Array of Item JSON objects
1218      * @param bool|string $fundsSource Funding source ID of the desired funcding source from which payments will be processed from (if no value is set, it defaults to 'Balance')
1219      * @param bool $assumeCosts Set to true if the sender should assume the $0.25 Dwolla fee rather than the recipients (default: false)
1220      * @param bool|string $user_job_id Optional user defined job ID.
1221      * @return array Information on submitted job (view full obj "Response" details here: https://developers.dwolla.com/dev/docs/masspay/create)
1222      */
1223 
1224     public function massPayCreate($pin, $filedata, $fundsSource = FALSE, $assumeCosts = FALSE, $user_job_id = FALSE)
1225     {
1226         if (!$pin) {
1227             return $this->setError('Please enter a PIN.');
1228         } else if (!$filedata) {
1229             return $this->setError('Please pass the MassPay bulk data.');
1230         }
1231 
1232         // Create request body
1233         $params = array(
1234             'oauth_token' => $this->oauthToken,
1235             'fundsSource' => ($fundsSource) ? $fundsSource : 'Balance',
1236             'pin' => $pin,
1237             'items' => $filedata,
1238             'assumeCosts' => $assumeCosts
1239         );
1240         if ($user_job_id) {
1241             $params['userJobId'] = $user_job_id;
1242         }
1243 
1244         // Send off the request
1245         $response = $this->curl($this->apiServerUrl . $this->OAUTH_TAIL . 'masspay/', 'POST', $params);
1246 
1247         $job = $this->parseMassPay($response);
1248 
1249         return $job;
1250     }
1251 
1252     /**
1253      * Parse MassPay API response
1254      *
1255      * @param array $response
1256      * @return array
1257      */
1258     protected function parseMassPay($response)
1259     {
1260         $success = isset($response['Success']) ? $response['Success'] : $response['success'];
1261         if (!$success) {
1262             $message = isset($response['Message']) ? $response['Message'] : $response['message'];
1263             return $this->setError($message);
1264         }
1265         return $response['Response'];
1266     }
1267 
1268     /**
1269      * massPayDetails
1270      *
1271      * @param string $job_id The job ID of the desired MassPay job. This can either be the generated MP job id or a user-specified one.
1272      * @return array $response Contains details on the job tied to the passed in ID.
1273      */
1274 
1275     public function massPayDetails($job_id)
1276     {
1277         if (!$job_id) {
1278             return $this->setError('Please pass either a MassPay job ID, or a user assigned job ID.');
1279         }
1280 
1281         // Create request body
1282         $params = array(
1283             'oauth_token' => $this->oauthToken,
1284             'id' => $job_id
1285         );
1286 
1287         // Send off the request
1288         $response = $this->curl($this->apiServerUrl . $this->OAUTH_TAIL . 'masspay/job/', 'POST', $params);
1289 
1290         if (!$response['Success']) {
1291             $this->errorMessage = $response['Message'];
1292         }
1293         return $response['Response'];
1294     }
1295 
1296     /**
1297      * @return string|bool Error message or false if error message does not exist
1298      */
1299     public function getError()
1300     {
1301         if (!$this->errorMessage) {
1302             return false;
1303         }
1304 
1305         $error = $this->errorMessage;
1306         $this->errorMessage = false;
1307 
1308         return $error;
1309     }
1310 
1311     /**
1312      * @param string $token oauth token
1313      */
1314     public function setToken($token)
1315     {
1316         $this->oauthToken = $token;
1317     }
1318 
1319     /**
1320      * @return string oauth token
1321      */
1322     public function getToken()
1323     {
1324         return $this->oauthToken;
1325     }
1326 
1327     /**
1328      * @return string Client mode
1329      */
1330     public function getMode()
1331     {
1332         return $this->mode;
1333     }
1334 
1335     /**
1336      * Sets client mode.  Appropriate values are 'live' and 'test'
1337      *
1338      * @param string $mode
1339      * @throws InvalidArgumentException
1340      * @return void
1341      */
1342     public function setMode($mode = 'live')
1343     {
1344         $mode = strtolower($mode);
1345 
1346         if ($mode != 'live' && $mode != 'test') {
1347             throw new InvalidArgumentException('Appropriate mode values are live or test');
1348         }
1349 
1350         $this->mode = $mode;
1351     }
1352 
1353     /**
1354      * Set debug mode
1355      *
1356      * @param $mode
1357      * @return boolean True
1358      */
1359     public function setDebug($mode)
1360     {
1361         $this->debugMode = $mode;
1362 
1363         return true;
1364     }
1365 
1366     /**
1367      * Set sandbox mode
1368      *
1369      * @param $mode
1370      * @return boolean True
1371      */
1372     public function setSandbox($mode)
1373     {
1374         $this->sandboxMode = $mode;
1375         $this->apiServerUrl = $this->sandboxMode ? self::SANDBOX_SERVER : self::API_SERVER;
1376         return true;
1377     }
1378 
1379 }