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

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

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

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapterFactory;
import com.stripe.Stripe;
import com.stripe.exception.InvalidRequestException;
import com.stripe.exception.StripeException;
import com.stripe.model.EphemeralKey;
import com.stripe.model.EphemeralKeyDeserializer;
import com.stripe.model.EventData;
import com.stripe.model.EventDataDeserializer;
import com.stripe.model.EventRequest;
import com.stripe.model.EventRequestDeserializer;
import com.stripe.model.ExpandableField;
import com.stripe.model.ExpandableFieldDeserializer;
import com.stripe.model.HasId;
import com.stripe.model.StripeCollectionInterface;
import com.stripe.model.StripeObject;
import com.stripe.model.StripeObjectInterface;
import com.stripe.model.StripeRawJsonObject;
import com.stripe.model.StripeRawJsonObjectDeserializer;
import com.stripe.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

public abstract class ApiResource extends StripeObject {
  public static final Charset CHARSET = StandardCharsets.UTF_8;

  private static StripeResponseGetter stripeResponseGetter = new LiveStripeResponseGetter();

  public static final Gson GSON = createGson();

  public static void setStripeResponseGetter(StripeResponseGetter srg) {
    ApiResource.stripeResponseGetter = srg;
  }

  private static Gson createGson() {
    GsonBuilder builder =
        new GsonBuilder()
            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .registerTypeAdapter(EphemeralKey.class, new EphemeralKeyDeserializer())
            .registerTypeAdapter(EventData.class, new EventDataDeserializer())
            .registerTypeAdapter(EventRequest.class, new EventRequestDeserializer())
            .registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer())
            .registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectDeserializer());

    for (TypeAdapterFactory factory : ApiResourceTypeAdapterFactoryProvider.getAll()) {
      builder.registerTypeAdapterFactory(factory);
    }
    return builder.create();
  }

  private static String className(Class clazz) {
    // Convert CamelCase to snake_case
    String className = StringUtils.toSnakeCase(clazz.getSimpleName());

    // Handle namespaced resources by checking if the class is in a sub-package, and if so prepend
    // it to the class name
    String[] parts = clazz.getPackage().getName().split("\\.", -1);
    assert parts.length == 3 || parts.length == 4;
    if (parts.length == 4) {
      // The first three parts are always "com.stripe.model", the fourth part is the sub-package
      className = parts[3] + "/" + className;
    }

    // Handle special cases
    switch (className) {
      case "invoice_item":
        return "invoiceitem";
      case "file_upload":
        return "file";
      default:
        return className;
    }
  }

  protected static String singleClassUrl(Class clazz) {
    return singleClassUrl(clazz, Stripe.getApiBase());
  }

  protected static String singleClassUrl(Class clazz, String apiBase) {
    return String.format("%s/v1/%s", apiBase, className(clazz));
  }

  protected static String classUrl(Class clazz) {
    return classUrl(clazz, Stripe.getApiBase());
  }

  protected static String classUrl(Class clazz, String apiBase) {
    return String.format("%ss", singleClassUrl(clazz, apiBase));
  }

  protected static String instanceUrl(Class clazz, String id) throws InvalidRequestException {
    return instanceUrl(clazz, id, Stripe.getApiBase());
  }

  protected static String instanceUrl(Class clazz, String id, String apiBase)
      throws InvalidRequestException {
    return String.format("%s/%s", classUrl(clazz, apiBase), urlEncode(id));
  }

  protected static String subresourceUrl(Class clazz, String id, Class subClazz)
      throws InvalidRequestException {
    return subresourceUrl(clazz, id, subClazz, Stripe.getApiBase());
  }

  private static String subresourceUrl(Class clazz, String id, Class subClazz, String apiBase)
      throws InvalidRequestException {
    return String.format("%s/%s/%ss", classUrl(clazz, apiBase), urlEncode(id), className(subClazz));
  }

  public enum RequestMethod {
    GET,
    POST,
    DELETE
  }

  /** URL-encodes a string. */
  public static String urlEncode(String str) {
    // Preserve original behavior that passing null for an object id will lead
    // to us actually making a request to /v1/foo/null
    if (str == null) {
      return null;
    }

    try {
      // Don't use strict form encoding by changing the square bracket control
      // characters back to their literals. This is fine by the server, and
      // makes these parameter strings easier to read.
      return URLEncoder.encode(str, CHARSET.name()).replaceAll("%5B", "[").replaceAll("%5D", "]");
    } catch (UnsupportedEncodingException e) {
      // This can literally never happen, and lets us avoid having to catch
      // UnsupportedEncodingException in callers.
      throw new AssertionError("UTF-8 is unknown");
    }
  }

  /** URL-encode a string ID in url path formatting. */
  public static String urlEncodeId(String id) throws InvalidRequestException {
    if (id == null) {
      throw new InvalidRequestException(
          "Invalid null ID found for url path formatting. This can be because your string ID "
              + "argument to the API method is null, or the ID field in your stripe object "
              + "instance is null. Please contact [email protected] on the latter case. ",
          null,
          null,
          null,
          0,
          null);
    }

    return urlEncode(id);
  }

  public static  T request(
      ApiResource.RequestMethod method,
      String url,
      ApiRequestParams params,
      Class clazz,
      RequestOptions options)
      throws StripeException {
    checkNullTypedParams(url, params);
    return request(method, url, params.toMap(), clazz, options);
  }

  public static  T request(
      ApiResource.RequestMethod method,
      String url,
      Map params,
      Class clazz,
      RequestOptions options)
      throws StripeException {
    return ApiResource.stripeResponseGetter.request(method, url, params, clazz, options);
  }

  public static > T requestCollection(
      String url, ApiRequestParams params, Class clazz, RequestOptions options)
      throws StripeException {
    checkNullTypedParams(url, params);
    return requestCollection(url, params.toMap(), clazz, options);
  }

  /**
   * Similar to #request, but specific for use with collection types that come from the API (i.e.
   * lists of resources).
   *
   * 

Collections need a little extra work because we need to plumb request options and params * through so that we can iterate to the next page if necessary. */ public static > T requestCollection( String url, Map params, Class clazz, RequestOptions options) throws StripeException { T collection = request(RequestMethod.GET, url, params, clazz, options); if (collection != null) { collection.setRequestOptions(options); collection.setRequestParams(params); } return collection; } /** * Invalidate null typed parameters. * * @param url request url associated with the given parameters. * @param params typed parameters to check for null value. */ public static void checkNullTypedParams(String url, ApiRequestParams params) { if (params == null) { throw new IllegalArgumentException( String.format( "Found null params for %s. " + "Please pass empty params using param builder via `builder().build()` instead.", url)); } } /** * When setting a String ID for an ExpandableField, we need to be careful about keeping the String * ID and the expanded object in sync. If they specify a new String ID that is different from the * ID within the expanded object, we don't keep the object. */ public static ExpandableField setExpandableFieldId( String newId, ExpandableField currentObject) { if (currentObject == null || (currentObject.isExpanded() && !Objects.equals(currentObject.getId(), newId))) { return new ExpandableField<>(newId, null); } return new ExpandableField<>(newId, currentObject.getExpanded()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy