mode = $mode; if ( self::MODE_SANDBOX === $mode ) { $this->api_url = 'https://api-m.sandbox.paypal.com'; } else { $this->api_url = 'https://api-m.paypal.com'; } if ( empty( $credentials ) ) { $credentials = array( 'client_id' => edd_get_option( 'paypal_' . $this->mode . '_client_id' ), 'client_secret' => edd_get_option( 'paypal_' . $this->mode . '_client_secret' ), ); } $this->set_credentials( $credentials ); } /** * Magic getter * * @param string $property * * @since 2.10 * @return mixed */ public function __get( $property ) { return isset( $this->{$property} ) ? $this->{$property} : null; } /** * Sets the credentials to use for API requests. * * @param array $creds { * Credentials to set. * * @type string $client_id PayPal client ID. * @type string $client_secret PayPal client secret. * @type string $cache_key Cache key used for storing the access token until it expires. Should be unique to * the set of credentials. The mode is automatically appended, so should not be * included manually. * } * * @since 2.11 * @throws Authentication_Exception */ public function set_credentials( $creds ) { $creds = wp_parse_args( $creds, array( 'client_id' => '', 'client_secret' => '', 'cache_key' => 'edd_paypal_commerce_access_token' ) ); $required_creds = array( 'client_id', 'client_secret', 'cache_key' ); foreach ( $required_creds as $cred_id ) { if ( empty( $creds[ $cred_id ] ) ) { throw new Authentication_Exception( sprintf( /* Translators: %s - The ID of the PayPal credential */ __( 'Missing PayPal credential: %s', 'easy-digital-downloads' ), $cred_id ) ); } } foreach ( $creds as $cred_id => $cred_value ) { $this->{$cred_id} = $cred_value; } $this->token_cache_key = sanitize_key( $creds['cache_key'] . '_' . $this->mode ); } /** * Retrieves the access token. This checks cache first, and if the cached token isn't valid then * a new one is generated from the API. * * @since 2.11 * @return Token * @throws API_Exception */ public function get_access_token() { try { $token = Token::from_json( (string) get_option( $this->token_cache_key ) ); return ! $token->is_expired() ? $token : $this->generate_access_token(); } catch ( \RuntimeException $e ) { return $this->generate_access_token(); } } /** * Generates a new access token and caches it. * * @since 2.11 * @return Token * @throws API_Exception */ private function generate_access_token() { $response = wp_remote_post( $this->api_url . '/v1/oauth2/token', array( 'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => sprintf( 'Basic %s', base64_encode( sprintf( '%s:%s', $this->client_id, $this->client_secret ) ) ), 'timeout' => 15 ), 'body' => array( 'grant_type' => 'client_credentials' ) ) ); $body = json_decode( wp_remote_retrieve_body( $response ) ); $code = intval( wp_remote_retrieve_response_code( $response ) ); if ( is_wp_error( $response ) ) { throw new API_Exception( $response->get_error_message(), $code ); } if ( ! empty( $body->error_description ) ) { throw new API_Exception( $body->error_description, $code ); } if ( 200 !== $code ) { throw new API_Exception( sprintf( /* Translators: %d - HTTP response code. */ __( 'Unexpected response code: %d', 'easy-digital-downloads' ), $code ), $code ); } $token = new Token( $body ); update_option( $this->token_cache_key, $token->to_json() ); return $token; } /** * Makes an API request. * * @param string $endpoint API endpoint. * @param array $body Array of data to send in the request. * @param array $headers Array of headers. * @param string $method HTTP method. * * @since 2.11 * @return mixed * @throws API_Exception */ public function make_request( $endpoint, $body = array(), $headers = array(), $method = 'POST' ) { $headers = wp_parse_args( $headers, array( 'Content-Type' => 'application/json', 'Authorization' => sprintf( 'Bearer %s', $this->get_access_token()->token() ), 'PayPal-Partner-Attribution-Id' => EDD_PAYPAL_PARTNER_ATTRIBUTION_ID ) ); $request_args = array( 'method' => $method, 'timeout' => 15, 'headers' => $headers, 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), ); if ( ! empty( $body ) ) { $request_args['body'] = json_encode( $body ); } // In a few rare cases, we may be providing a full URL to `$endpoint` instead of just the path. $api_url = ( 'https://' === substr( $endpoint, 0, 8 ) ) ? $endpoint : $this->api_url . '/' . $endpoint; $response = wp_remote_request( $api_url, $request_args ); if ( is_wp_error( $response ) ) { throw new API_Exception( $response->get_error_message() ); } $this->last_response_code = intval( wp_remote_retrieve_response_code( $response ) ); return json_decode( wp_remote_retrieve_body( $response ) ); } }