Skip to content

Commit 0b7d4c4

Browse files
Add setHttpClient to StripeClientBuilder; clean up apiKey/authenticator coupling (#2182)
* Clean up apiKey/authenticator coupling * Add setHttpClient to StripeClientBuilder Allow users to provide a custom HttpClient implementation when building a StripeClient, rather than always using the default HttpURLConnectionClient. This is useful for testing and for integrating alternative HTTP libraries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Committed-By-Agent: claude * Fix fomatting --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2bdec26 commit 0b7d4c4

7 files changed

Lines changed: 91 additions & 33 deletions

File tree

src/main/java/com/stripe/StripeClient.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,7 @@ public static final class StripeClientBuilder {
10481048
private String meterEventsBase = Stripe.METER_EVENTS_API_BASE;
10491049
private String stripeAccount;
10501050
private String stripeContext;
1051+
private HttpClient httpClient;
10511052

10521053
/**
10531054
* Constructs a request options builder with the global parameters (API key and client ID) as
@@ -1059,19 +1060,29 @@ public Authenticator getAuthenticator() {
10591060
return this.authenticator;
10601061
}
10611062

1063+
/**
1064+
* Sets the authenticator used to authorize requests. Use this for custom authentication
1065+
* strategies. For standard API key authentication, prefer {@link #setApiKey(String)}.
1066+
*
1067+
* <p>This shares a backing field with {@link #setApiKey(String)} — calling one overwrites the
1068+
* other.
1069+
*
1070+
* @param authenticator the authenticator to use
1071+
*/
10621072
public StripeClientBuilder setAuthenticator(Authenticator authenticator) {
10631073
this.authenticator = authenticator;
10641074
return this;
10651075
}
10661076

1067-
public String getApiKey() {
1068-
if (authenticator instanceof BearerTokenAuthenticator) {
1069-
return ((BearerTokenAuthenticator) authenticator).getApiKey();
1070-
}
1071-
1072-
return null;
1073-
}
1074-
1077+
/**
1078+
* Sets the API key for bearer token authentication. This is a convenience method equivalent to
1079+
* calling {@code setAuthenticator(new BearerTokenAuthenticator(apiKey))}.
1080+
*
1081+
* <p>This shares a backing field with {@link #setAuthenticator(Authenticator)} — calling one
1082+
* overwrites the other.
1083+
*
1084+
* @param apiKey the API key; if null, clears the authenticator
1085+
*/
10751086
public StripeClientBuilder setApiKey(String apiKey) {
10761087
if (apiKey == null) {
10771088
this.authenticator = null;
@@ -1179,7 +1190,8 @@ public StripeClientBuilder setProxyCredential(PasswordAuthentication proxyCreden
11791190
* Set the base URL for the Stripe API. By default this is "https://api.stripe.com".
11801191
*
11811192
* <p>This only affects requests made with a {@link com.stripe.net.BaseAddress} of API. Use
1182-
* {@link setFilesBase} or {@link setConnectBase} to interpect requests with other bases.
1193+
* {@link #setFilesBase}, {@link #setConnectBase} or {@link #setMeterEventsBase} to interpect
1194+
* requests with other bases.
11831195
*/
11841196
public StripeClientBuilder setApiBase(String address) {
11851197
this.apiBase = address;
@@ -1256,9 +1268,23 @@ public String getStripeContext() {
12561268
return this.stripeContext;
12571269
}
12581270

1271+
/**
1272+
* Sets the HTTP client to use for making requests to the Stripe API. If not set, a default
1273+
* {@link HttpURLConnectionClient} will be created.
1274+
*
1275+
* <p>This is useful for providing a custom HTTP client implementation, e.g. for testing or for
1276+
* using a different HTTP library.
1277+
*
1278+
* @param httpClient the HTTP client to use
1279+
*/
1280+
public StripeClientBuilder setHttpClient(HttpClient httpClient) {
1281+
this.httpClient = httpClient;
1282+
return this;
1283+
}
1284+
12591285
/** Constructs a {@link StripeResponseGetterOptions} with the specified values. */
12601286
public StripeClient build() {
1261-
return new StripeClient(new LiveStripeResponseGetter(buildOptions(), null));
1287+
return new StripeClient(new LiveStripeResponseGetter(buildOptions(), this.httpClient));
12621288
}
12631289

12641290
StripeResponseGetterOptions buildOptions() {

src/main/java/com/stripe/net/Authenticator.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
import com.stripe.exception.StripeException;
44

5-
/** * Represents a request authentication mechanism. */
5+
/**
6+
* Interface for applying authentication to a request. This is used by {@link StripeResponseGetter}
7+
* to apply authentication to requests before they are sent. Implementations of this interface
8+
* should not modify the request in-place, but should instead return a new request with the
9+
* appropriate authentication headers applied. The default implementation of this interface is
10+
* {@link BearerTokenAuthenticator}, which applies the API key as a Bearer token in the
11+
* Authorization header. Users can also implement this interface to provide custom authentication
12+
* logic, often by wrapping the BearerTokenAuthenticator and applying additional headers or logic as
13+
* needed.
14+
*/
615
public interface Authenticator {
716
/**
817
* * Authenticate the request

src/main/java/com/stripe/net/RequestOptions.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,6 @@ public Authenticator getAuthenticator() {
8585
return this.authenticator;
8686
}
8787

88-
public String getApiKey() {
89-
if (authenticator instanceof BearerTokenAuthenticator) {
90-
return ((BearerTokenAuthenticator) authenticator).getApiKey();
91-
}
92-
93-
return null;
94-
}
95-
9688
public String getClientId() {
9789
return clientId;
9890
}
@@ -210,19 +202,29 @@ public Authenticator getAuthenticator() {
210202
return this.authenticator;
211203
}
212204

205+
/**
206+
* Sets the authenticator used to authorize requests. Use this for custom authentication
207+
* strategies. For standard API key authentication, prefer {@link #setApiKey(String)}.
208+
*
209+
* <p>This shares a backing field with {@link #setApiKey(String)} — calling one overwrites the
210+
* other.
211+
*
212+
* @param authenticator the authenticator to use
213+
*/
213214
public RequestOptionsBuilder setAuthenticator(Authenticator authenticator) {
214215
this.authenticator = authenticator;
215216
return this;
216217
}
217218

218-
public String getApiKey() {
219-
if (authenticator instanceof BearerTokenAuthenticator) {
220-
return ((BearerTokenAuthenticator) authenticator).getApiKey();
221-
}
222-
223-
return null;
224-
}
225-
219+
/**
220+
* Sets the API key for bearer token authentication. This is a convenience method equivalent to
221+
* calling {@code setAuthenticator(new BearerTokenAuthenticator(apiKey))}.
222+
*
223+
* <p>This shares a backing field with {@link #setAuthenticator(Authenticator)} — calling one
224+
* overwrites the other.
225+
*
226+
* @param apiKey the API key; if null, clears the authenticator
227+
*/
226228
public RequestOptionsBuilder setApiKey(String apiKey) {
227229
if (apiKey == null) {
228230
this.authenticator = null;

src/test/java/com/stripe/StripeClientTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,23 @@ public void parsesUnknownEventNotification()
303303
assertInstanceOf(Event.class, e.fetchEvent());
304304
}
305305

306+
@Test
307+
public void builderUsesProvidedHttpClient() throws StripeException {
308+
HttpClient customHttpClient = Mockito.spy(new HttpURLConnectionClient());
309+
310+
StripeClient client =
311+
StripeClient.builder()
312+
.setApiKey(TEST_API_KEY)
313+
.setApiBase(getStripeMockUrl())
314+
.setHttpClient(customHttpClient)
315+
.build();
316+
317+
client.customers().create();
318+
319+
ArgumentCaptor<StripeRequest> requestCaptor = ArgumentCaptor.forClass(StripeRequest.class);
320+
Mockito.verify(customHttpClient, Mockito.atLeastOnce()).request(requestCaptor.capture());
321+
}
322+
306323
@Test
307324
public void stripeClientWithStripeAccount() throws StripeException {
308325

src/test/java/com/stripe/model/PagingIteratorTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ public void testAutoPaginationRequestOptionsPropagation() throws StripeException
343343
Stripe.apiKey = null;
344344
final PageableModelCollection collection =
345345
PageableModel.list(page0Params, RequestOptions.builder().setApiKey("sk_test_xyz").build());
346-
assertEquals(collection.getRequestOptions().getApiKey(), "sk_test_xyz");
346+
assertEquals(
347+
new BearerTokenAuthenticator("sk_test_xyz"),
348+
collection.getRequestOptions().getAuthenticator());
347349
final List<PageableModel> models = new ArrayList<>();
348350

349351
for (PageableModel model : collection.autoPagingIterable()) {

src/test/java/com/stripe/model/SearchPagingIteratorTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ public void testAutoPaginationRequestOptionsPropagation() throws StripeException
235235
final SearchableModelCollection collection =
236236
SearchableModel.search(
237237
page0Params, RequestOptions.builder().setApiKey("sk_test_xyz").build());
238-
assertEquals(collection.getRequestOptions().getApiKey(), "sk_test_xyz");
238+
assertEquals(
239+
new BearerTokenAuthenticator("sk_test_xyz"),
240+
collection.getRequestOptions().getAuthenticator());
239241
final List<SearchableModel> models = new ArrayList<>();
240242

241243
for (SearchableModel model : collection.autoPagingIterable()) {

src/test/java/com/stripe/net/RequestOptionsTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void testPersistentValuesInToBuilder() {
3434

3535
// only api keys and account should persist
3636
// assuming these are stable across a given stripe integration
37-
assertEquals("sk_foo", optsRebuilt.getApiKey());
37+
assertEquals(new BearerTokenAuthenticator("sk_foo"), optsRebuilt.getAuthenticator());
3838
assertEquals("acct_bar", optsRebuilt.getStripeAccount());
3939

4040
assertNull(optsRebuilt.getClientId());
@@ -158,7 +158,7 @@ public void mergeOverwritesClientOptions() {
158158
.build();
159159

160160
RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions);
161-
assertEquals("key2", merged.getApiKey());
161+
assertEquals(new BearerTokenAuthenticator("key2"), merged.getAuthenticator());
162162
assertEquals(3, merged.getConnectTimeout());
163163
assertEquals(4, merged.getMaxNetworkRetries());
164164
assertEquals(5, merged.getReadTimeout());
@@ -242,7 +242,7 @@ public void mergeFallsBackToClientOptions() {
242242
RequestOptions requestOptions = RequestOptions.builder().build();
243243

244244
RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions);
245-
assertEquals("key1", merged.getApiKey());
245+
assertEquals(new BearerTokenAuthenticator("key1"), merged.getAuthenticator());
246246
assertEquals(1, merged.getConnectTimeout());
247247
assertEquals(1, merged.getMaxNetworkRetries());
248248
assertEquals(1, merged.getReadTimeout());
@@ -257,7 +257,7 @@ public void mergeFallsBackToClientOptions() {
257257
@Test
258258
public void defaultsToAllNullValues() {
259259
RequestOptions merged = RequestOptions.getDefault();
260-
assertEquals(null, merged.getApiKey());
260+
assertNull(merged.getAuthenticator());
261261
assertEquals(null, merged.getConnectTimeout());
262262
assertEquals(null, merged.getMaxNetworkRetries());
263263
assertEquals(null, merged.getReadTimeout());

0 commit comments

Comments
 (0)