All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.stripe.net.LiveStripeResponseGetter Maven / Gradle / Ivy

There is a newer version: 28.2.0
Show newest version
package com.stripe.net;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.stripe.Stripe;
import com.stripe.exception.*;
import com.stripe.exception.oauth.InvalidClientException;
import com.stripe.exception.oauth.InvalidGrantException;
import com.stripe.exception.oauth.InvalidScopeException;
import com.stripe.exception.oauth.OAuthException;
import com.stripe.exception.oauth.UnsupportedGrantTypeException;
import com.stripe.exception.oauth.UnsupportedResponseTypeException;
import com.stripe.model.*;
import com.stripe.model.oauth.OAuthError;
import com.stripe.util.Stopwatch;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class LiveStripeResponseGetter implements StripeResponseGetter {
  private final HttpClient httpClient;
  private final StripeResponseGetterOptions options;

  private final RequestTelemetry requestTelemetry = new RequestTelemetry();

  @FunctionalInterface
  private interface RequestSendFunction {
    R apply(StripeRequest request) throws StripeException;
  }

  private > T sendWithTelemetry(
      StripeRequest request, List usage, RequestSendFunction send)
      throws StripeException {

    Stopwatch stopwatch = Stopwatch.startNew();

    T response = send.apply(request);

    stopwatch.stop();

    requestTelemetry.maybeEnqueueMetrics(response, stopwatch.getElapsed(), usage);

    return response;
  }

  /**
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class with default
   * parameters.
   */
  public LiveStripeResponseGetter() {
    this(null, null);
  }

  /**
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class.
   *
   * @param httpClient the HTTP client to use
   */
  public LiveStripeResponseGetter(HttpClient httpClient) {
    this(null, httpClient);
  }

  /**
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class.
   *
   * @param options the client options instance to use
   * @param httpClient the HTTP client to use
   */
  public LiveStripeResponseGetter(StripeResponseGetterOptions options, HttpClient httpClient) {
    this.options = options != null ? options : GlobalStripeResponseGetterOptions.INSTANCE;
    this.httpClient = (httpClient != null) ? httpClient : buildDefaultHttpClient();
  }

  private StripeRequest toStripeRequest(ApiRequest apiRequest, RequestOptions mergedOptions)
      throws StripeException {
    String fullUrl = fullUrl(apiRequest);

    Optional telemetryHeaderValue = requestTelemetry.pollPayload();
    StripeRequest request =
        StripeRequest.create(
            apiRequest.getMethod(),
            fullUrl,
            apiRequest.getParams(),
            mergedOptions,
            apiRequest.getApiMode());
    if (telemetryHeaderValue.isPresent()) {
      request =
          request.withAdditionalHeader(RequestTelemetry.HEADER_NAME, telemetryHeaderValue.get());
    }
    return request;
  }

  private StripeRequest toRawStripeRequest(RawApiRequest apiRequest, RequestOptions mergedOptions)
      throws StripeException {
    String fullUrl = fullUrl(apiRequest);

    Optional telemetryHeaderValue = requestTelemetry.pollPayload();
    StripeRequest request =
        StripeRequest.createWithStringContent(
            apiRequest.getMethod(),
            fullUrl,
            apiRequest.getRawContent(),
            mergedOptions,
            apiRequest.getApiMode());

    if (telemetryHeaderValue.isPresent()) {
      request =
          request.withAdditionalHeader(RequestTelemetry.HEADER_NAME, telemetryHeaderValue.get());
    }
    return request;
  }

  @Override
  @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
  public  T request(ApiRequest apiRequest, Type typeToken)
      throws StripeException {

    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());

    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
    }

    StripeRequest request = toStripeRequest(apiRequest, mergedOptions);
    StripeResponse response =
        sendWithTelemetry(request, apiRequest.getUsage(), r -> httpClient.requestWithRetries(r));

    int responseCode = response.code();
    String responseBody = response.body();
    String requestId = response.requestId();

    if (responseCode < 200 || responseCode >= 300) {
      handleError(response, apiRequest.getApiMode());
    }

    T resource = null;
    try {
      resource = (T) ApiResource.deserializeStripeObject(responseBody, typeToken, this);
    } catch (JsonSyntaxException e) {
      throw makeMalformedJsonError(responseBody, responseCode, requestId, e);
    }

    if (resource instanceof StripeCollectionInterface) {
      ((StripeCollectionInterface) resource).setRequestOptions(apiRequest.getOptions());
      ((StripeCollectionInterface) resource).setRequestParams(apiRequest.getParams());
    }

    if (resource instanceof com.stripe.model.v2.StripeCollection) {
      ((com.stripe.model.v2.StripeCollection) resource)
          .setRequestOptions(apiRequest.getOptions());
    }

    resource.setLastResponse(response);

    return resource;
  }

  @Override
  public InputStream requestStream(ApiRequest apiRequest) throws StripeException {
    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());

    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
    }

    StripeRequest request = toStripeRequest(apiRequest, mergedOptions);
    StripeResponseStream responseStream =
        sendWithTelemetry(
            request, apiRequest.getUsage(), r -> httpClient.requestStreamWithRetries(r));

    int responseCode = responseStream.code();

    if (responseCode < 200 || responseCode >= 300) {
      StripeResponse response;
      try {
        response = responseStream.unstream();
      } catch (IOException e) {
        throw new ApiConnectionException(
            String.format(
                "IOException during API request to Stripe (%s): %s "
                    + "Please check your internet connection and try again. If this problem persists,"
                    + "you should check Stripe's service status at https://twitter.com/stripestatus,"
                    + " or let us know at [email protected].",
                Stripe.getApiBase(), e.getMessage()),
            e);
      }
      handleError(response, apiRequest.getApiMode());
    }

    return responseStream.body();
  }

  @Override
  public StripeResponse rawRequest(RawApiRequest apiRequest) throws StripeException {
    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());

    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
    }

    StripeRequest request = toRawStripeRequest(apiRequest, mergedOptions);

    Map additionalHeaders = apiRequest.getOptions().getAdditionalHeaders();

    if (additionalHeaders != null) {
      for (Map.Entry entry : additionalHeaders.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        request = request.withAdditionalHeader(key, value);
      }
    }

    StripeResponse response =
        sendWithTelemetry(request, apiRequest.getUsage(), r -> httpClient.requestWithRetries(r));

    int responseCode = response.code();

    if (responseCode < 200 || responseCode >= 300) {
      handleError(response, apiRequest.getApiMode());
    }

    return response;
  }

  @Override
  @SuppressWarnings({"TypeParameterUnusedInFormals", "deprecation"})
  public  T request(
      BaseAddress baseAddress,
      ApiResource.RequestMethod method,
      String path,
      Map params,
      Type typeToken,
      RequestOptions options,
      ApiMode apiMode)
      throws StripeException {
    return this.request(new ApiRequest(baseAddress, method, path, params, options), typeToken);
  }

  @Override
  @SuppressWarnings({"TypeParameterUnusedInFormals", "deprecation"})
  public InputStream requestStream(
      BaseAddress baseAddress,
      ApiResource.RequestMethod method,
      String path,
      Map params,
      RequestOptions options,
      ApiMode apiMode)
      throws StripeException {
    return this.requestStream(new ApiRequest(baseAddress, method, path, params, options));
  }

  private static HttpClient buildDefaultHttpClient() {
    return new HttpURLConnectionClient();
  }

  private static ApiException makeMalformedJsonError(
      String responseBody, int responseCode, String requestId, Throwable e) throws ApiException {
    String details = e == null ? "none" : e.getMessage();
    throw new ApiException(
        String.format(
            "Invalid response object from API: %s. (HTTP response code was %d). Additional details: %s.",
            responseBody, responseCode, details),
        requestId,
        null,
        responseCode,
        e);
  }

  private StripeError parseStripeError(
      String body, int code, String requestId, Class klass)
      throws StripeException {
    StripeError ret;
    try {
      JsonObject jsonObject =
          ApiResource.GSON.fromJson(body, JsonObject.class).getAsJsonObject("error");
      ret = (StripeError) StripeObject.deserializeStripeObject(jsonObject, klass, this);
      if (ret != null) return ret;
    } catch (JsonSyntaxException e) {
      throw makeMalformedJsonError(body, code, requestId, e);
    }
    throw makeMalformedJsonError(body, code, requestId, null);
  }

  private void handleError(StripeResponse response, ApiMode apiMode) throws StripeException {
    try {
      /*
      OAuth errors are JSON objects where `error` is a string. In
      contrast, in API errors, `error` is a hash with sub-keys. We use
      this property to distinguish between OAuth and API errors.
      */
      JsonObject responseBody = ApiResource.GSON.fromJson(response.body(), JsonObject.class);
      if (responseBody.has("error") && responseBody.get("error").isJsonPrimitive()) {
        JsonPrimitive error = responseBody.getAsJsonPrimitive("error");
        if (error.isString()) {
          handleOAuthError(response);
        }
      } else if (apiMode == ApiMode.V2) {
        handleV2ApiError(response);
      } else {
        handleV1ApiError(response);
      }
    } catch (JsonSyntaxException e) {
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), e);
    }
  }

  private void handleV1ApiError(StripeResponse response) throws StripeException {
    StripeException exception = null;

    StripeError error =
        parseStripeError(response.body(), response.code(), response.requestId(), StripeError.class);

    error.setLastResponse(response);
    switch (response.code()) {
      case 400:
      case 404:
        if ("idempotency_error".equals(error.getType())) {
          exception =
              new IdempotencyException(
                  error.getMessage(), response.requestId(), error.getCode(), response.code());
        } else {
          exception =
              new InvalidRequestException(
                  error.getMessage(),
                  error.getParam(),
                  response.requestId(),
                  error.getCode(),
                  response.code(),
                  null);
        }
        break;
      case 401:
        exception =
            new AuthenticationException(
                error.getMessage(), response.requestId(), error.getCode(), response.code());
        break;
      case 402:
        exception =
            new CardException(
                error.getMessage(),
                response.requestId(),
                error.getCode(),
                error.getParam(),
                error.getDeclineCode(),
                error.getCharge(),
                response.code(),
                null);
        break;
      case 403:
        exception =
            new PermissionException(
                error.getMessage(), response.requestId(), error.getCode(), response.code());
        break;
      case 429:
        exception =
            new RateLimitException(
                error.getMessage(),
                error.getParam(),
                response.requestId(),
                error.getCode(),
                response.code(),
                null);
        break;
      default:
        exception =
            new ApiException(
                error.getMessage(), response.requestId(), error.getCode(), response.code(), null);
        break;
    }
    exception.setStripeError(error);

    throw exception;
  }

  private void handleV2ApiError(StripeResponse response) throws StripeException {
    JsonObject body =
        ApiResource.GSON.fromJson(response.body(), JsonObject.class).getAsJsonObject("error");

    JsonElement typeElement = body == null ? null : body.get("type");
    JsonElement codeElement = body == null ? null : body.get("code");
    String type = typeElement == null ? "" : typeElement.getAsString();
    String code = codeElement == null ? "" : codeElement.getAsString();

    StripeException exception =
        StripeException.parseV2Exception(type, body, response.code(), response.requestId(), this);
    if (exception != null) {
      throw exception;
    }

    StripeError error;
    try {
      error =
          parseStripeError(
              response.body(), response.code(), response.requestId(), StripeError.class);
    } catch (ApiException e) {
      String message = "Unrecognized error type '" + type + "'";
      JsonElement messageField = body == null ? null : body.get("message");
      if (messageField != null && messageField.isJsonPrimitive()) {
        message = messageField.getAsString();
      }

      throw new ApiException(message, response.requestId(), code, response.code(), null);
    }

    error.setLastResponse(response);
    exception =
        new ApiException(error.getMessage(), response.requestId(), code, response.code(), null);
    exception.setStripeV2Error(error);
    throw exception;
  }

  private void handleOAuthError(StripeResponse response) throws StripeException {
    OAuthError error = null;
    StripeException exception = null;

    try {
      error = StripeObject.deserializeStripeObject(response.body(), OAuthError.class, this);
    } catch (JsonSyntaxException e) {
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), e);
    }
    if (error == null) {
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), null);
    }

    error.setLastResponse(response);

    String code = error.getError();
    String description = (error.getErrorDescription() != null) ? error.getErrorDescription() : code;

    switch (code) {
      case "invalid_client":
        exception =
            new InvalidClientException(
                code, description, response.requestId(), response.code(), null);
        break;
      case "invalid_grant":
        exception =
            new InvalidGrantException(
                code, description, response.requestId(), response.code(), null);
        break;
      case "invalid_request":
        exception =
            new com.stripe.exception.oauth.InvalidRequestException(
                code, description, response.requestId(), response.code(), null);
        break;
      case "invalid_scope":
        exception =
            new InvalidScopeException(
                code, description, response.requestId(), response.code(), null);
        break;
      case "unsupported_grant_type":
        exception =
            new UnsupportedGrantTypeException(
                code, description, response.requestId(), response.code(), null);
        break;
      case "unsupported_response_type":
        exception =
            new UnsupportedResponseTypeException(
                code, description, response.requestId(), response.code(), null);
        break;
      default:
        exception = new ApiException(code, response.requestId(), null, response.code(), null);
        break;
    }

    if (exception instanceof OAuthException) {
      ((OAuthException) exception).setOauthError(error);
    }

    throw exception;
  }

  @Override
  public void validateRequestOptions(RequestOptions options) {
    if ((options == null || options.getAuthenticator() == null)
        && this.options.getAuthenticator() == null) {
      throw new ApiKeyMissingException(
          "API key is not set. You can set the API key globally using Stripe.ApiKey, or by passing RequestOptions");
    }
  }

  private String fullUrl(BaseApiRequest apiRequest) {
    BaseAddress baseAddress = apiRequest.getBaseAddress();
    RequestOptions options = apiRequest.getOptions();
    String relativeUrl = apiRequest.getPath();
    String baseUrl;
    switch (baseAddress) {
      case API:
        baseUrl = this.options.getApiBase();
        break;
      case CONNECT:
        baseUrl = this.options.getConnectBase();
        break;
      case FILES:
        baseUrl = this.options.getFilesBase();
        break;
      case METER_EVENTS:
        baseUrl = this.options.getMeterEventsBase();
        break;
      default:
        throw new IllegalArgumentException("Unknown base address " + baseAddress);
    }
    if (options != null && options.getBaseUrl() != null) {
      baseUrl = options.getBaseUrl();
    }
    return String.format("%s%s", baseUrl, relativeUrl);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy