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.1.0-beta.3
Show newest version
package com.stripe.net;

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);
  }

  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 =
        new StripeRequest(apiRequest.getMethod(), fullUrl, apiRequest.getParams(), mergedOptions);

    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);
    ApiMode apiMode = apiRequest.getPath().startsWith("/v2") ? ApiMode.PREVIEW : ApiMode.V1;
    Optional telemetryHeaderValue = requestTelemetry.pollPayload();
    StripeRequest request =
        StripeRequest.createWithStringContent(
            apiRequest.getMethod(),
            fullUrl,
            apiRequest.getRawContent(),
            mergedOptions,
            apiMode == ApiMode.PREVIEW);

    if (apiMode == ApiMode.PREVIEW) {
      request = request.withAdditionalHeader("Stripe-Version", Stripe.PREVIEW_API_VERSION);
    }
    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);
    }

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

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

    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);
    }

    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) {
      handleApiError(response);
    }

    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 void raiseMalformedJsonError(
      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 void handleError(StripeResponse response) throws StripeException {
    JsonObject responseBody = ApiResource.GSON.fromJson(response.body(), JsonObject.class);

    /*
     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.
    */
    if (responseBody.has("error") && responseBody.get("error").isJsonPrimitive()) {
      JsonPrimitive error = responseBody.getAsJsonPrimitive("error");
      if (error.isString()) {
        handleOAuthError(response);
      }
    } else {
      handleApiError(response);
    }
  }

  private void handleApiError(StripeResponse response) throws StripeException {
    StripeError error = null;
    StripeException exception = null;

    try {
      JsonObject jsonObject =
          ApiResource.INTERNAL_GSON
              .fromJson(response.body(), JsonObject.class)
              .getAsJsonObject("error");
      error = ApiResource.deserializeStripeObject(jsonObject.toString(), StripeError.class, this);
    } catch (JsonSyntaxException e) {
      raiseMalformedJsonError(response.body(), response.code(), response.requestId(), e);
    }
    if (error == null) {
      raiseMalformedJsonError(response.body(), response.code(), response.requestId(), null);
    }

    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 handleOAuthError(StripeResponse response) throws StripeException {
    OAuthError error = null;
    StripeException exception = null;

    try {
      error = ApiResource.deserializeStripeObject(response.body(), OAuthError.class, this);
    } catch (JsonSyntaxException e) {
      raiseMalformedJsonError(response.body(), response.code(), response.requestId(), e);
    }
    if (error == null) {
      raiseMalformedJsonError(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.getApiKey() == null) && this.options.getApiKey() == 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;
      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 - 2024 Weber Informatics LLC | Privacy Policy