package com.stripe.net; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.stripe.Stripe; import com.stripe.exception.APIConnectionException; import com.stripe.exception.APIException; import com.stripe.exception.AuthenticationException; import com.stripe.exception.CardException; import com.stripe.exception.InvalidRequestException; import com.stripe.model.ChargeRefundCollection; import com.stripe.model.ChargeRefundCollectionDeserializer; import com.stripe.model.Dispute; import com.stripe.model.DisputeDataDeserializer; import com.stripe.model.EventData; import com.stripe.model.EventDataDeserializer; import com.stripe.model.ExpandableField; import com.stripe.model.ExpandableFieldDeserializer; import com.stripe.model.ExternalAccountTypeAdapterFactory; import com.stripe.model.FeeRefundCollection; import com.stripe.model.FeeRefundCollectionDeserializer; import com.stripe.model.HasId; import com.stripe.model.Source; import com.stripe.model.SourceDeserializer; import com.stripe.model.StripeCollectionInterface; import com.stripe.model.StripeObject; import com.stripe.model.StripeRawJsonObject; import com.stripe.model.StripeRawJsonObjectDeserializer; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Map; public abstract class APIResource extends StripeObject { private static StripeResponseGetter stripeResponseGetter = new LiveStripeResponseGetter(); public static void setStripeResponseGetter(StripeResponseGetter srg) { APIResource.stripeResponseGetter = srg; } public static final Gson GSON = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(EventData.class, new EventDataDeserializer()) .registerTypeAdapter(ChargeRefundCollection.class, new ChargeRefundCollectionDeserializer()) .registerTypeAdapter(FeeRefundCollection.class, new FeeRefundCollectionDeserializer()) .registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectDeserializer()) .registerTypeAdapter(Dispute.class, new DisputeDataDeserializer()) .registerTypeAdapter(Source.class, new SourceDeserializer()) .registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer()) .registerTypeAdapterFactory(new ExternalAccountTypeAdapterFactory()) .create(); private static String className(Class<?> clazz) { String className = clazz.getSimpleName().toLowerCase().replace("$", " "); // TODO: Delurk this, with invoiceitem being a valid url, we can't get too // fancy yet. if (className.equals("applicationfee")) { return "application_fee"; } else if (className.equals("fileupload")) { return "file"; } else if (className.equals("bitcoinreceiver")) { return "bitcoin_receiver"; } else if (className.equals("countryspec")) { return "country_spec"; } else if (className.equals("orderreturn")) { return "order_return"; } else if (className.equals("threedsecure")) { return "three_d_secure"; } else if (className.equals("applepaydomain")) { return "apple_pay_domain"; } else if (className.equals("subscriptionitem")) { return "subscription_item"; } else { 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 { try { return String.format("%s/%s", classURL(clazz, apiBase), urlEncode(id)); } catch (UnsupportedEncodingException e) { throw new InvalidRequestException("Unable to encode parameters to " + CHARSET + ". Please contact support@stripe.com for assistance.", null, null, 0, e); } } public static final String CHARSET = "UTF-8"; public enum RequestMethod { GET, POST, DELETE } public enum RequestType { NORMAL, MULTIPART } public static String urlEncode(String str) throws UnsupportedEncodingException { // 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; } else { return URLEncoder.encode(str, CHARSET); } } public static <T> T multipartRequest(APIResource.RequestMethod method, String url, Map<String, Object> params, Class<T> clazz, RequestOptions options) throws AuthenticationException, InvalidRequestException, APIConnectionException, CardException, APIException { return APIResource.stripeResponseGetter.request(method, url, params, clazz, APIResource.RequestType.MULTIPART, options); } public static <T> T request(APIResource.RequestMethod method, String url, Map<String, Object> params, Class<T> clazz, RequestOptions options) throws AuthenticationException, InvalidRequestException, APIConnectionException, CardException, APIException { return APIResource.stripeResponseGetter.request(method, url, params, clazz, APIResource.RequestType.NORMAL, 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 extends StripeCollectionInterface> T requestCollection( String url, Map<String, Object> params, Class<T> clazz, RequestOptions options) throws AuthenticationException, InvalidRequestException, APIConnectionException, CardException, APIException { T collection = request(RequestMethod.GET, url, params, clazz, options); if (collection != null) { collection.setRequestOptions(options); collection.setRequestParams(params); } return collection; } /** * 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 <T extends HasId> ExpandableField<T> setExpandableFieldID(String newId, ExpandableField<T> currentObject) { if (currentObject == null || (currentObject.isExpanded() && (currentObject.getId() != newId))) { return new ExpandableField<T>(newId, null); } return new ExpandableField<T>(newId, currentObject.getExpanded()); } }