'WooCommerce Stripe Gateway', 'version' => WC_STRIPE_VERSION, 'url' => 'https://woocommerce.com/products/stripe/', ); return array( 'lang' => 'php', 'lang_version' => phpversion(), 'publisher' => 'woocommerce', 'uname' => php_uname(), 'application' => $app_info, ); } /** * Generates the headers to pass to API request. * * @since 4.0.0 * @version 4.0.0 */ public static function get_headers() { $user_agent = self::get_user_agent(); $app_info = $user_agent['application']; return apply_filters( 'woocommerce_stripe_request_headers', array( 'Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ), 'Stripe-Version' => self::STRIPE_API_VERSION, 'User-Agent' => $app_info['name'] . '/' . $app_info['version'] . ' (' . $app_info['url'] . ')', 'X-Stripe-Client-User-Agent' => json_encode( $user_agent ), ) ); } /** * Send the request to Stripe's API * * @since 3.1.0 * @version 4.0.6 * @param array $request * @param string $api * @param string $method * @param bool $with_headers To get the response with headers. * @return stdClass|array * @throws WC_Stripe_Exception */ public static function request( $request, $api = 'charges', $method = 'POST', $with_headers = false ) { WC_Stripe_Logger::log( "{$api} request: " . print_r( $request, true ) ); $headers = self::get_headers(); $idempotency_key = ''; if ( 'charges' === $api && 'POST' === $method ) { $customer = ! empty( $request['customer'] ) ? $request['customer'] : ''; $source = ! empty( $request['source'] ) ? $request['source'] : $customer; $idempotency_key = apply_filters( 'wc_stripe_idempotency_key', $request['metadata']['order_id'] . '-' . $source, $request ); $headers['Idempotency-Key'] = $idempotency_key; } $response = wp_safe_remote_post( self::ENDPOINT . $api, array( 'method' => $method, 'headers' => $headers, 'body' => apply_filters( 'woocommerce_stripe_request_body', $request, $api ), 'timeout' => 70, ) ); if ( is_wp_error( $response ) || empty( $response['body'] ) ) { WC_Stripe_Logger::log( 'Error Response: ' . print_r( $response, true ) . PHP_EOL . PHP_EOL . 'Failed request: ' . print_r( array( 'api' => $api, 'request' => $request, 'idempotency_key' => $idempotency_key, ), true ) ); throw new WC_Stripe_Exception( print_r( $response, true ), __( 'There was a problem connecting to the Stripe API endpoint.', 'woocommerce-gateway-stripe' ) ); } if ( $with_headers ) { return array( 'headers' => wp_remote_retrieve_headers( $response ), 'body' => json_decode( $response['body'] ), ); } return json_decode( $response['body'] ); } /** * Retrieve API endpoint. * * @since 4.0.0 * @version 4.0.0 * @param string $api */ public static function retrieve( $api ) { WC_Stripe_Logger::log( "{$api}" ); $response = wp_safe_remote_get( self::ENDPOINT . $api, array( 'method' => 'GET', 'headers' => self::get_headers(), 'timeout' => 70, ) ); if ( is_wp_error( $response ) || empty( $response['body'] ) ) { WC_Stripe_Logger::log( 'Error Response: ' . print_r( $response, true ) ); return new WP_Error( 'stripe_error', __( 'There was a problem connecting to the Stripe API endpoint.', 'woocommerce-gateway-stripe' ) ); } return json_decode( $response['body'] ); } /** * Send the request to Stripe's API with level 3 data generated * from the order. If the request fails due to an error related * to level3 data, make the request again without it to allow * the payment to go through. * * @since 4.3.2 * @version 4.3.2 * * @param array $request Array with request parameters. * @param string $api The API path for the request. * @param array $level3_data The level 3 data for this request. * @param WC_Order $order The order associated with the payment. * * @return stdClass|array The response */ public static function request_with_level3_data( $request, $api, $level3_data, $order ) { // Do not add level3 data it's the array is empty. if ( empty( $level3_data ) ) { return self::request( $request, $api ); } // If there's a transient indicating that level3 data was not accepted by // Stripe in the past for this account, do not try to add level3 data. if ( get_transient( 'wc_stripe_level3_not_allowed' ) ) { return self::request( $request, $api ); } // Add level 3 data to the request. $request['level3'] = $level3_data; $result = self::request( $request, $api ); $is_level3_param_not_allowed = ( isset( $result->error ) && isset( $result->error->code ) && 'parameter_unknown' === $result->error->code && isset( $result->error->param ) && 'level3' === $result->error->param ); $is_level_3data_incorrect = ( isset( $result->error ) && isset( $result->error->type ) && 'invalid_request_error' === $result->error->type ); if ( $is_level3_param_not_allowed ) { // Set a transient so that future requests do not add level 3 data. // Transient is set to expire in 3 months, can be manually removed if needed. set_transient( 'wc_stripe_level3_not_allowed', true, 3 * MONTH_IN_SECONDS ); } else if ( $is_level_3data_incorrect ) { // Log the issue so we could debug it. WC_Stripe_Logger::log( 'Level3 data sum incorrect: ' . PHP_EOL . print_r( $result->error->message, true ) . PHP_EOL . print_r( 'Order line items: ', true ) . PHP_EOL . print_r( $order->get_items(), true ) . PHP_EOL . print_r( 'Order shipping amount: ', true ) . PHP_EOL . print_r( $order->get_shipping_total(), true ) . PHP_EOL . print_r( 'Order currency: ', true ) . PHP_EOL . print_r( $order->get_currency(), true ) ); } // Make the request again without level 3 data. if ( $is_level3_param_not_allowed || $is_level_3data_incorrect ) { unset( $request['level3'] ); return WC_Stripe_API::request( $request, $api ); } return $result; } }