* * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ class WePay { /** * Version number - sent in user agent string */ const VERSION = '0.3.1'; /** * Scope fields * Passed into Wepay::getAuthorizationUri as array */ const SCOPE_MANAGE_ACCOUNTS = 'manage_accounts'; // Open and interact with accounts const SCOPE_COLLECT_PAYMENTS = 'collect_payments'; // Create and interact with checkouts const SCOPE_VIEW_USER = 'view_user'; // Get details about authenticated user const SCOPE_PREAPPROVE_PAYMENTS = 'preapprove_payments'; // Create and interact with preapprovals const SCOPE_MANAGE_SUBSCRIPTIONS = 'manage_subscriptions'; // Subscriptions const SCOPE_SEND_MONEY = 'send_money'; // For withdrawals /** * Application's client ID */ private static $client_id; /** * Application's client secret */ private static $client_secret; /** * API Version * https://www.wepay.com/developer/reference/versioning */ private static $api_version; /** * @deprecated Use WePay::getAllScopes() instead. */ public static $all_scopes = array( self::SCOPE_MANAGE_ACCOUNTS, self::SCOPE_COLLECT_PAYMENTS, self::SCOPE_PREAPPROVE_PAYMENTS, self::SCOPE_VIEW_USER, self::SCOPE_SEND_MONEY, self::SCOPE_MANAGE_SUBSCRIPTIONS ); /** * Determines whether to use WePay's staging or production servers */ private static $production = null; /** * cURL handle */ private static $ch = NULL; /** * Authenticated user's access token */ private $token; /** * Pass WePay::getAllScopes() into getAuthorizationUri if your application desires full access */ public static function getAllScopes() { return array( self::SCOPE_MANAGE_ACCOUNTS, self::SCOPE_MANAGE_SUBSCRIPTIONS, self::SCOPE_COLLECT_PAYMENTS, self::SCOPE_PREAPPROVE_PAYMENTS, self::SCOPE_VIEW_USER, self::SCOPE_SEND_MONEY ); } /** * Generate URI used during oAuth authorization * Redirect your user to this URI where they can grant your application * permission to make API calls * * @link https://www.wepay.com/developer/reference/oauth2 * * @param array $scope List of scope fields for which your application wants access. * @param string $redirect_uri Where user goes after logging in at WePay (domain must match application settings). * @param array $options `user_name,user_email` which will be pre-filled on login form, state to be returned * in query string of redirect_uri. The default value is an empty array. * @return string URI to which you must redirect your user to grant access to your application */ public static function getAuthorizationUri(array $scope, $redirect_uri, array $options = array()) { // This does not use WePay::getDomain() because the user authentication // domain is different than the API call domain if (self::$production === null) { throw new RuntimeException('You must initialize the WePay SDK with WePay::useStaging() or WePay::useProduction()'); } $domain = self::$production ? 'https://www.wepay.com' : 'https://stage.wepay.com'; $uri = $domain . '/v2/oauth2/authorize?'; $uri .= http_build_query(array( 'client_id' => self::$client_id, 'redirect_uri' => $redirect_uri, 'scope' => implode(',', $scope), 'state' => empty($options['state']) ? '' : $options['state'], 'user_name' => empty($options['user_name']) ? '' : $options['user_name'], 'user_email' => empty($options['user_email']) ? '' : $options['user_email'], 'user_country' => empty($options['user_country']) ? '' : $options['user_country'], ), '', '&'); return $uri; } private static function getDomain() { if (self::$production === true) { return 'https://wepayapi.com/v2/'; } elseif (self::$production === false) { return 'https://stage.wepayapi.com/v2/'; } else { throw new RuntimeException('You must initialize the WePay SDK with WePay::useStaging() or WePay::useProduction()'); } } /** * Exchange a temporary access code for a (semi-)permanent access token * @param string $code 'code' field from query string passed to your redirect_uri page * @param string $redirect_uri Where user went after logging in at WePay (must match value from getAuthorizationUri) * @return StdClass|false * user_id * access_token * token_type */ public static function getToken($code, $redirect_uri) { $params = (array( 'client_id' => self::$client_id, 'client_secret' => self::$client_secret, 'redirect_uri' => $redirect_uri, 'code' => $code, 'state' => '', // do not hardcode )); $result = self::make_request('oauth2/token', $params); return $result; } /** * Configure SDK to run against WePay's production servers * @param string $client_id Your application's client id * @param string $client_secret Your application's client secret * @return void * @throws RuntimeException */ public static function useProduction($client_id, $client_secret, $api_version = null) { if (self::$production !== null) { throw new RuntimeException('API mode has already been set.'); } self::$production = true; self::$client_id = $client_id; self::$client_secret = $client_secret; self::$api_version = $api_version; } /** * Configure SDK to run against WePay's staging servers * @param string $client_id Your application's client id * @param string $client_secret Your application's client secret * @return void * @throws RuntimeException */ public static function useStaging($client_id, $client_secret, $api_version = null) { if (self::$production !== null) { throw new RuntimeException('API mode has already been set.'); } self::$production = false; self::$client_id = $client_id; self::$client_secret = $client_secret; self::$api_version = $api_version; } /** * Returns the current environment. * @return string "none" (not configured), "production" or "staging". */ public static function getEnvironment() { if (self::$production === null) { return 'none'; } elseif (self::$production) { return 'production'; } else { return 'staging'; } } /** * Set Api Version * https://www.wepay.com/developer/reference/versioning * * @param string $version Api Version to send in call request header */ public static function setApiVersion($version) { self::$api_version = $version; } /** * Create a new API session * @param string $token - access_token returned from WePay::getToken */ public function __construct($token) { if ($token && !is_string($token)) { throw new InvalidArgumentException('$token must be a string, ' . gettype($token) . ' provided'); } $this->token = $token; } /** * Clean up cURL handle */ public function __destruct() { if (self::$ch) { curl_close(self::$ch); self::$ch = NULL; } } /** * create the cURL request and execute it */ private static function make_request($endpoint, $values, $headers = array()) { self::$ch = curl_init(); $headers = array_merge(array("Content-Type: application/json"), $headers); // always pass the correct Content-Type header // send Api Version header if (!empty(self::$api_version)) { $headers[] = "Api-Version: " . self::$api_version; } curl_setopt(self::$ch, CURLOPT_USERAGENT, 'WePay v2 PHP SDK v' . self::VERSION . ' Client id:' . self::$client_id); curl_setopt(self::$ch, CURLOPT_RETURNTRANSFER, true); curl_setopt(self::$ch, CURLOPT_HTTPHEADER, $headers); curl_setopt(self::$ch, CURLOPT_TIMEOUT, 30); // 30-second timeout, adjust to taste curl_setopt(self::$ch, CURLOPT_POST, !empty($values)); // WePay's API is not strictly RESTful, so all requests are sent as POST unless there are no request values // Force TLS 1.2 connections curl_setopt(self::$ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt(self::$ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt(self::$ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); $uri = self::getDomain() . $endpoint; curl_setopt(self::$ch, CURLOPT_URL, $uri); if (!empty($values)) { curl_setopt(self::$ch, CURLOPT_POSTFIELDS, json_encode($values)); } $raw = curl_exec(self::$ch); if ($errno = curl_errno(self::$ch)) { // Set up special handling for request timeouts if ($errno == CURLE_OPERATION_TIMEOUTED) { throw new WePayServerException("Timeout occurred while trying to connect to WePay"); } throw new Exception('cURL error while making API call to WePay: cURL Errno - ' . $errno . ', ' . curl_error(self::$ch), $errno); } $result = json_decode($raw); $httpCode = curl_getinfo(self::$ch, CURLINFO_HTTP_CODE); if ($httpCode >= 400) { if (!isset($result->error_code)) { throw new WePayServerException("WePay returned an error response with no error_code, please alert api@wepay.com. Original message: $result->error_description", $httpCode, $result, 0); } if ($httpCode >= 500) { throw new WePayServerException($result->error_description, $httpCode, $result, $result->error_code); } switch ($result->error) { case 'invalid_request': throw new WePayRequestException($result->error_description, $httpCode, $result, $result->error_code); case 'access_denied': default: throw new WePayPermissionException($result->error_description, $httpCode, $result, $result->error_code); } } return $result; } /** * Make API calls against authenticated user * @param string $endpoint - API call to make (ex. 'user', 'account/find') * @param array $values - Associative array of values to send in API call * @param string $risk_token - WePay-supplied risk token associated with this API call * @param string $client_ip - Client's IP address associated with this API call * @return StdClass * @throws WePayException on failure * @throws Exception on catastrophic failure (non-WePay-specific cURL errors) */ public function request($endpoint, array $values = array(), $risk_token = null, $client_ip = null) { $headers = array(); if ($this->token) { // if we have an access_token, add it to the Authorization header $headers[] = "Authorization: Bearer $this->token"; } if ($risk_token) { // if we have a risk_token, add it to the WePay-Risk-Token header $headers[] = "WePay-Risk-Token: " . $risk_token; } if ($client_ip) { // if we have a client_ip, add it to the Client-IP header $headers[] = "Client-IP: " . $client_ip; } $result = self::make_request($endpoint, $values, $headers); return $result; } } /** * Different problems will have different exception types so you can * catch and handle them differently. * * WePayServerException indicates some sort of 500-level error code and * was unavoidable from your perspective. You may need to re-run the * call, or check whether it was received (use a "find" call with your * reference_id and make a decision based on the response) * * WePayRequestException indicates a development error - invalid endpoint, * erroneous parameter, etc. * * WePayPermissionException indicates your authorization token has expired, * was revoked, or is lacking in scope for the call you made */ class WePayException extends Exception { public function __construct($description = '', $http_code = FALSE, $response = FALSE, $code = 0, $previous = NULL) { $this->response = $response; if (!defined('PHP_VERSION_ID')) { $version = explode('.', PHP_VERSION); define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); } if (PHP_VERSION_ID < 50300) { parent::__construct($description, $code); } else { parent::__construct($description, $code, $previous); } } } class WePayRequestException extends WePayException {} class WePayPermissionException extends WePayException {} class WePayServerException extends WePayException {}