diff --git a/README.md b/README.md index 6ea972c84da..15cbaf9d1b0 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,32 @@ String secondaryValue = .getAsString(); ``` +> [!NOTE] +> `.getRawJsonObject()` is only available on the top-level object returned by an API call. For most requests (like `.retrieve()` or `.create()`) you'll get the object itself. But for `.list()` calls, the top level object is a `List`, so you can only access the raw json of an individual object by going through the list itself. +> +> ```java +> var cards = stripeClient +> .v1() +> .issuing() +> .cards() +> .list(params); +> +> // doesn't work: +> cards +> .getData() +> .get(0) +> .getRawJsonObject(); // null +> +> // instead, go through the list: +> cards +> .getRawJsonObject() +> .getAsJsonArray("data") +> .get(0) +> .getAsJsonObject() +> .getAsJsonPrimitive("undocumented-val") +> .getAsString(); // "some-val" +> ``` + ### Writing a plugin If you're writing a plugin that uses the library, we'd appreciate it if you @@ -259,7 +285,7 @@ Stripe.enableTelemetry = false; Stripe has features in the [public preview phase](https://docs.stripe.com/release-phases) that can be accessed via versions of this package that have the `-beta.X` suffix like `25.2.0-beta.2`. We would love for you to try these as we incrementally release new features and improve them based on your feedback. - To install, pick the latest version with the `beta` suffix by reviewing the [releases page](https://github.com/stripe/stripe-java/releases/) and then use it [installation steps above](#installation). +To install, pick the latest version with the `beta` suffix by reviewing the [releases page](https://github.com/stripe/stripe-java/releases/) and then use it [installation steps above](#installation). > **Note** > There can be breaking changes between two versions of the public preview SDKs without a bump in the major version. Therefore we recommend pinning the package version to a specific version. This way you can install the same version each time without breaking changes unless you are intentionally looking for the latest public preview SDK. @@ -269,6 +295,7 @@ Some preview features require a name and version to be set in the `Stripe-Versio ```java Stripe.addBetaVersion("feature_beta", "v3"); ``` + ### Private Preview SDKs Stripe has features in the [private preview phase](https://docs.stripe.com/release-phases) that can be accessed via versions of this package that have the `-alpha.X` suffix like `25.2.0-alpha.2`. These are invite-only features. Once invited, you can install the private preview SDKs by following the same instructions as for the [public preview SDKs](https://github.com/stripe/stripe-java?tab=readme-ov-file#public-preview-sdks) above and replacing the term `beta` with `alpha`. diff --git a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java index fda88ce18f3..b04b29f1fcb 100644 --- a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java +++ b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java @@ -22,8 +22,11 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.logging.Logger; public class LiveStripeResponseGetter implements StripeResponseGetter { + private static final Logger logger = Logger.getLogger("Stripe"); + private final HttpClient httpClient; private final StripeResponseGetterOptions options; @@ -151,6 +154,8 @@ public T request(ApiRequest apiRequest, Type typeToken) StripeResponse response = sendWithTelemetry(request, apiRequest.getUsage(), r -> httpClient.requestWithRetries(r)); + maybeEmitStripeNotice(response.headers()); + int responseCode = response.code(); String responseBody = response.body(); String requestId = response.requestId(); @@ -194,6 +199,8 @@ public InputStream requestStream(ApiRequest apiRequest) throws StripeException { sendWithTelemetry( request, apiRequest.getUsage(), r -> httpClient.requestStreamWithRetries(r)); + maybeEmitStripeNotice(responseStream.headers()); + int responseCode = responseStream.code(); if (responseCode < 200 || responseCode >= 300) { @@ -275,6 +282,10 @@ public InputStream requestStream( return this.requestStream(new ApiRequest(baseAddress, method, path, params, options)); } + private static void maybeEmitStripeNotice(HttpHeaders headers) { + headers.firstValue("Stripe-Notice").ifPresent(logger::warning); + } + private static HttpClient buildDefaultHttpClient() { return new HttpURLConnectionClient(); } diff --git a/src/test/java/com/stripe/functional/LiveStripeResponseGetterTest.java b/src/test/java/com/stripe/functional/LiveStripeResponseGetterTest.java index 8a470940027..e4e93fb31a8 100644 --- a/src/test/java/com/stripe/functional/LiveStripeResponseGetterTest.java +++ b/src/test/java/com/stripe/functional/LiveStripeResponseGetterTest.java @@ -1,9 +1,13 @@ package com.stripe.functional; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gson.JsonSyntaxException; import com.stripe.BaseStripeTest; import com.stripe.exception.ApiException; @@ -18,12 +22,92 @@ import com.stripe.net.StripeRequest; import com.stripe.net.StripeResponse; import com.stripe.net.StripeResponseGetter; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; public class LiveStripeResponseGetterTest extends BaseStripeTest { + private Logger stripeLogger; + private CapturingHandler logHandler; + + private static class CapturingHandler extends Handler { + final List records = new ArrayList<>(); + + @Override + public void publish(LogRecord record) { + records.add(record); + } + + @Override + public void flush() {} + + @Override + public void close() {} + } + + @BeforeEach + public void setUpLogger() { + stripeLogger = Logger.getLogger("Stripe"); + logHandler = new CapturingHandler(); + logHandler.setLevel(Level.ALL); + stripeLogger.addHandler(logHandler); + stripeLogger.setLevel(Level.ALL); + stripeLogger.setUseParentHandlers(false); + } + + @AfterEach + public void tearDownLogger() { + stripeLogger.removeHandler(logHandler); + stripeLogger.setUseParentHandlers(true); + } + + @Test + public void testStripeNoticeHeaderEmitsWarning() throws StripeException { + HttpClient spy = Mockito.spy(new HttpURLConnectionClient()); + StripeResponseGetter srg = new LiveStripeResponseGetter(spy); + ApiResource.setGlobalResponseGetter(srg); + StripeResponse response = + new StripeResponse( + 200, + HttpHeaders.of( + ImmutableMap.of( + "Stripe-Notice", + ImmutableList.of("This API version will be deprecated soon."))), + "{\"id\": \"sub_123\", \"object\": \"subscription\"}"); + Mockito.doReturn(response).when(spy).requestWithRetries(Mockito.any()); + Subscription.retrieve("sub_123"); + + assertEquals(1, logHandler.records.size()); + assertEquals(Level.WARNING, logHandler.records.get(0).getLevel()); + assertEquals( + "This API version will be deprecated soon.", logHandler.records.get(0).getMessage()); + } + + @Test + public void testNoStripeNoticeHeaderEmitsNoWarning() throws StripeException { + HttpClient spy = Mockito.spy(new HttpURLConnectionClient()); + StripeResponseGetter srg = new LiveStripeResponseGetter(spy); + ApiResource.setGlobalResponseGetter(srg); + StripeResponse response = + new StripeResponse( + 200, + HttpHeaders.of(Collections.emptyMap()), + "{\"id\": \"sub_123\", \"object\": \"subscription\"}"); + Mockito.doReturn(response).when(spy).requestWithRetries(Mockito.any()); + Subscription.retrieve("sub_123"); + + assertTrue(logHandler.records.isEmpty()); + } + @Test public void testInvalidJson() throws StripeException { HttpClient spy = Mockito.spy(new HttpURLConnectionClient());