File indexing completed on 2024-12-22 05:36:18
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 }