blob: 6abaddb658a551fd417b84eedb6fd78445ef7ad2 [file] [log] [blame]
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Stripe_API class.
*
* Communicates with Stripe API.
*/
class WC_Stripe_API {
/**
* Stripe API Endpoint
*/
const ENDPOINT = 'https://api.stripe.com/v1/';
const STRIPE_API_VERSION = '2019-09-09';
/**
* Secret API Key.
*
* @var string
*/
private static $secret_key = '';
/**
* Set secret API Key.
*
* @param string $key
*/
public static function set_secret_key( $secret_key ) {
self::$secret_key = $secret_key;
}
/**
* Get secret key.
*
* @return string
*/
public static function get_secret_key() {
if ( ! self::$secret_key ) {
$options = get_option( 'woocommerce_stripe_settings' );
$secret_key = $options['secret_key'] ?? '';
$test_secret_key = $options['test_secret_key'] ?? '';
if ( isset( $options['testmode'] ) ) {
self::set_secret_key( 'yes' === $options['testmode'] ? $test_secret_key : $secret_key );
}
}
return self::$secret_key;
}
/**
* Generates the user agent we use to pass to API request so
* Stripe can identify our application.
*
* @since 4.0.0
* @version 4.0.0
*/
public static function get_user_agent() {
$app_info = [
'name' => 'WooCommerce Stripe Gateway',
'version' => WC_STRIPE_VERSION,
'url' => 'https://woocommerce.com/products/stripe/',
'partner_id' => 'pp_partner_EYuSt9peR0WTMg',
];
return [
'lang' => 'php',
'lang_version' => phpversion(),
'publisher' => 'woocommerce',
'uname' => 'Linux server 5.10.0-18-amd64 #1 SMP Debian 5.10.140-1 (2022-09-02) x86_64',
'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'];
$headers = apply_filters(
'woocommerce_stripe_request_headers',
[
'Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ),
'Stripe-Version' => self::STRIPE_API_VERSION,
]
);
// These headers should not be overridden for this gateway.
$headers['User-Agent'] = $app_info['name'] . '/' . $app_info['version'] . ' (' . $app_info['url'] . ')';
$headers['X-Stripe-Client-User-Agent'] = wp_json_encode( $user_agent );
return $headers;
}
/**
* 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,
[
'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(
[
'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 [
'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,
[
'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 5.1.0
*
* @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 ) {
// 1. Do not add level3 data if the array is empty.
// 2. Do not add level3 data if there's a transient indicating that level3 was
// not accepted by Stripe in the past for this account.
// 3. Do not try to add level3 data if merchant is not based in the US.
// https://stripe.com/docs/level3#level-iii-usage-requirements
// (Needs to be authenticated with a level3 gated account to see above docs).
if (
empty( $level3_data ) ||
get_transient( 'wc_stripe_level3_not_allowed' ) ||
'US' !== WC()->countries->get_base_country()
) {
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 );
} elseif ( $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 self::request(
$request,
$api
);
}
return $result;
}
}