• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stripe / stripe-java / #16624

07 Nov 2024 10:04PM UTC coverage: 12.419% (-0.1%) from 12.519%
#16624

push

github

web-flow
Merge pull request #1917 from stripe/latest-codegen-beta

Update generated code for beta

17 of 1341 new or added lines in 57 files covered. (1.27%)

33 existing lines in 29 files now uncovered.

18855 of 151828 relevant lines covered (12.42%)

0.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.66
/src/main/java/com/stripe/net/LiveStripeResponseGetter.java
1
package com.stripe.net;
2

3
import com.google.gson.JsonElement;
4
import com.google.gson.JsonObject;
5
import com.google.gson.JsonPrimitive;
6
import com.google.gson.JsonSyntaxException;
7
import com.stripe.Stripe;
8
import com.stripe.exception.*;
9
import com.stripe.exception.oauth.InvalidClientException;
10
import com.stripe.exception.oauth.InvalidGrantException;
11
import com.stripe.exception.oauth.InvalidScopeException;
12
import com.stripe.exception.oauth.OAuthException;
13
import com.stripe.exception.oauth.UnsupportedGrantTypeException;
14
import com.stripe.exception.oauth.UnsupportedResponseTypeException;
15
import com.stripe.model.*;
16
import com.stripe.model.oauth.OAuthError;
17
import com.stripe.util.Stopwatch;
18
import java.io.IOException;
19
import java.io.InputStream;
20
import java.lang.reflect.Type;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Optional;
24

25
public class LiveStripeResponseGetter implements StripeResponseGetter {
26
  private final HttpClient httpClient;
27
  private final StripeResponseGetterOptions options;
28

29
  private final RequestTelemetry requestTelemetry = new RequestTelemetry();
1✔
30

31
  @FunctionalInterface
32
  private interface RequestSendFunction<R> {
33
    R apply(StripeRequest request) throws StripeException;
34
  }
35

36
  private <T extends AbstractStripeResponse<?>> T sendWithTelemetry(
37
      StripeRequest request, List<String> usage, RequestSendFunction<T> send)
38
      throws StripeException {
39

40
    Stopwatch stopwatch = Stopwatch.startNew();
1✔
41

42
    T response = send.apply(request);
1✔
43

44
    stopwatch.stop();
1✔
45

46
    requestTelemetry.maybeEnqueueMetrics(response, stopwatch.getElapsed(), usage);
1✔
47

48
    return response;
1✔
49
  }
50

51
  /**
52
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class with default
53
   * parameters.
54
   */
55
  public LiveStripeResponseGetter() {
56
    this(null, null);
1✔
57
  }
1✔
58

59
  /**
60
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class.
61
   *
62
   * @param httpClient the HTTP client to use
63
   */
64
  public LiveStripeResponseGetter(HttpClient httpClient) {
65
    this(null, httpClient);
1✔
66
  }
1✔
67

68
  /**
69
   * Initializes a new instance of the {@link LiveStripeResponseGetter} class.
70
   *
71
   * @param options the client options instance to use
72
   * @param httpClient the HTTP client to use
73
   */
74
  public LiveStripeResponseGetter(StripeResponseGetterOptions options, HttpClient httpClient) {
1✔
75
    this.options = options != null ? options : GlobalStripeResponseGetterOptions.INSTANCE;
1✔
76
    this.httpClient = (httpClient != null) ? httpClient : buildDefaultHttpClient();
1✔
77
  }
1✔
78

79
  private StripeRequest toStripeRequest(ApiRequest apiRequest, RequestOptions mergedOptions)
80
      throws StripeException {
81
    String fullUrl = fullUrl(apiRequest);
1✔
82

83
    Optional<String> telemetryHeaderValue = requestTelemetry.pollPayload();
1✔
84
    StripeRequest request =
1✔
85
        StripeRequest.create(
1✔
86
            apiRequest.getMethod(),
1✔
87
            fullUrl,
88
            apiRequest.getParams(),
1✔
89
            mergedOptions,
90
            apiRequest.getApiMode());
1✔
91
    if (telemetryHeaderValue.isPresent()) {
1✔
92
      request =
1✔
93
          request.withAdditionalHeader(RequestTelemetry.HEADER_NAME, telemetryHeaderValue.get());
1✔
94
    }
95
    return request;
1✔
96
  }
97

98
  private StripeRequest toRawStripeRequest(RawApiRequest apiRequest, RequestOptions mergedOptions)
99
      throws StripeException {
100
    String fullUrl = fullUrl(apiRequest);
1✔
101

102
    Optional<String> telemetryHeaderValue = requestTelemetry.pollPayload();
1✔
103
    StripeRequest request =
1✔
104
        StripeRequest.createWithStringContent(
1✔
105
            apiRequest.getMethod(),
1✔
106
            fullUrl,
107
            apiRequest.getRawContent(),
1✔
108
            mergedOptions,
109
            apiRequest.getApiMode());
1✔
110

111
    if (telemetryHeaderValue.isPresent()) {
1✔
112
      request =
1✔
113
          request.withAdditionalHeader(RequestTelemetry.HEADER_NAME, telemetryHeaderValue.get());
1✔
114
    }
115
    return request;
1✔
116
  }
117

118
  @Override
119
  @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
120
  public <T extends StripeObjectInterface> T request(ApiRequest apiRequest, Type typeToken)
121
      throws StripeException {
122

123
    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());
1✔
124

125
    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
1✔
126
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
1✔
127
    }
128

129
    StripeRequest request = toStripeRequest(apiRequest, mergedOptions);
1✔
130
    StripeResponse response =
1✔
131
        sendWithTelemetry(request, apiRequest.getUsage(), r -> httpClient.requestWithRetries(r));
1✔
132

133
    int responseCode = response.code();
1✔
134
    String responseBody = response.body();
1✔
135
    String requestId = response.requestId();
1✔
136

137
    if (responseCode < 200 || responseCode >= 300) {
1✔
138
      handleError(response, apiRequest.getApiMode());
×
139
    }
140

141
    T resource = null;
1✔
142
    try {
143
      resource = (T) ApiResource.deserializeStripeObject(responseBody, typeToken, this);
1✔
144
    } catch (JsonSyntaxException e) {
1✔
145
      throw makeMalformedJsonError(responseBody, responseCode, requestId, e);
×
146
    }
1✔
147

148
    if (resource instanceof StripeCollectionInterface<?>) {
1✔
149
      ((StripeCollectionInterface<?>) resource).setRequestOptions(apiRequest.getOptions());
1✔
150
      ((StripeCollectionInterface<?>) resource).setRequestParams(apiRequest.getParams());
1✔
151
    }
152

153
    if (resource instanceof com.stripe.model.v2.StripeCollection<?>) {
1✔
154
      ((com.stripe.model.v2.StripeCollection<?>) resource)
1✔
155
          .setRequestOptions(apiRequest.getOptions());
1✔
156
    }
157

158
    resource.setLastResponse(response);
1✔
159

160
    return resource;
1✔
161
  }
162

163
  @Override
164
  public InputStream requestStream(ApiRequest apiRequest) throws StripeException {
165
    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());
1✔
166

167
    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
1✔
168
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
×
169
    }
170

171
    StripeRequest request = toStripeRequest(apiRequest, mergedOptions);
1✔
172
    StripeResponseStream responseStream =
1✔
173
        sendWithTelemetry(
1✔
174
            request, apiRequest.getUsage(), r -> httpClient.requestStreamWithRetries(r));
1✔
175

176
    int responseCode = responseStream.code();
1✔
177

178
    if (responseCode < 200 || responseCode >= 300) {
1✔
179
      StripeResponse response;
180
      try {
181
        response = responseStream.unstream();
×
182
      } catch (IOException e) {
×
183
        throw new ApiConnectionException(
×
184
            String.format(
×
185
                "IOException during API request to Stripe (%s): %s "
186
                    + "Please check your internet connection and try again. If this problem persists,"
187
                    + "you should check Stripe's service status at https://twitter.com/stripestatus,"
188
                    + " or let us know at support@stripe.com.",
189
                Stripe.getApiBase(), e.getMessage()),
×
190
            e);
191
      }
×
192
      handleError(response, apiRequest.getApiMode());
×
193
    }
194

195
    return responseStream.body();
1✔
196
  }
197

198
  @Override
199
  public StripeResponse rawRequest(RawApiRequest apiRequest) throws StripeException {
200
    RequestOptions mergedOptions = RequestOptions.merge(this.options, apiRequest.getOptions());
1✔
201

202
    if (RequestOptions.unsafeGetStripeVersionOverride(mergedOptions) != null) {
1✔
203
      apiRequest = apiRequest.addUsage("unsafe_stripe_version_override");
×
204
    }
205

206
    StripeRequest request = toRawStripeRequest(apiRequest, mergedOptions);
1✔
207

208
    Map<String, String> additionalHeaders = apiRequest.getOptions().getAdditionalHeaders();
1✔
209

210
    if (additionalHeaders != null) {
1✔
211
      for (Map.Entry<String, String> entry : additionalHeaders.entrySet()) {
1✔
212
        String key = entry.getKey();
1✔
213
        String value = entry.getValue();
1✔
214
        request = request.withAdditionalHeader(key, value);
1✔
215
      }
1✔
216
    }
217

218
    StripeResponse response =
1✔
219
        sendWithTelemetry(request, apiRequest.getUsage(), r -> httpClient.requestWithRetries(r));
1✔
220

221
    int responseCode = response.code();
1✔
222

223
    if (responseCode < 200 || responseCode >= 300) {
1✔
224
      handleError(response, apiRequest.getApiMode());
×
225
    }
226

227
    return response;
1✔
228
  }
229

230
  @Override
231
  @SuppressWarnings({"TypeParameterUnusedInFormals", "deprecation"})
232
  public <T extends StripeObjectInterface> T request(
233
      BaseAddress baseAddress,
234
      ApiResource.RequestMethod method,
235
      String path,
236
      Map<String, Object> params,
237
      Type typeToken,
238
      RequestOptions options,
239
      ApiMode apiMode)
240
      throws StripeException {
241
    return this.request(new ApiRequest(baseAddress, method, path, params, options), typeToken);
×
242
  }
243

244
  @Override
245
  @SuppressWarnings({"TypeParameterUnusedInFormals", "deprecation"})
246
  public InputStream requestStream(
247
      BaseAddress baseAddress,
248
      ApiResource.RequestMethod method,
249
      String path,
250
      Map<String, Object> params,
251
      RequestOptions options,
252
      ApiMode apiMode)
253
      throws StripeException {
254
    return this.requestStream(new ApiRequest(baseAddress, method, path, params, options));
×
255
  }
256

257
  private static HttpClient buildDefaultHttpClient() {
258
    return new HttpURLConnectionClient();
1✔
259
  }
260

261
  private static ApiException makeMalformedJsonError(
262
      String responseBody, int responseCode, String requestId, Throwable e) throws ApiException {
263
    String details = e == null ? "none" : e.getMessage();
1✔
264
    throw new ApiException(
1✔
265
        String.format(
1✔
266
            "Invalid response object from API: %s. (HTTP response code was %d). Additional details: %s.",
267
            responseBody, responseCode, details),
1✔
268
        requestId,
269
        null,
270
        responseCode,
1✔
271
        e);
272
  }
273

274
  private StripeError parseStripeError(
275
      String body, int code, String requestId, Class<? extends StripeError> klass)
276
      throws StripeException {
277
    StripeError ret;
278
    try {
279
      JsonObject jsonObject =
1✔
280
          ApiResource.GSON.fromJson(body, JsonObject.class).getAsJsonObject("error");
1✔
281
      ret = (StripeError) StripeObject.deserializeStripeObject(jsonObject, klass, this);
1✔
282
      if (ret != null) return ret;
1✔
283
    } catch (JsonSyntaxException e) {
×
284
      throw makeMalformedJsonError(body, code, requestId, e);
×
285
    }
×
286
    throw makeMalformedJsonError(body, code, requestId, null);
×
287
  }
288

289
  private void handleError(StripeResponse response, ApiMode apiMode) throws StripeException {
290
    try {
291
      /*
292
      OAuth errors are JSON objects where `error` is a string. In
293
      contrast, in API errors, `error` is a hash with sub-keys. We use
294
      this property to distinguish between OAuth and API errors.
295
      */
296
      JsonObject responseBody = ApiResource.GSON.fromJson(response.body(), JsonObject.class);
1✔
297
      if (responseBody.has("error") && responseBody.get("error").isJsonPrimitive()) {
1✔
298
        JsonPrimitive error = responseBody.getAsJsonPrimitive("error");
1✔
299
        if (error.isString()) {
1✔
NEW
300
          handleOAuthError(response);
×
301
        }
302
      } else if (apiMode == ApiMode.V2) {
1✔
NEW
303
        handleV2ApiError(response);
×
304
      } else {
NEW
305
        handleV1ApiError(response);
×
306
      }
307
    } catch (JsonSyntaxException e) {
1✔
NEW
308
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), e);
×
309
    }
×
310
  }
×
311

312
  private void handleV1ApiError(StripeResponse response) throws StripeException {
313
    StripeException exception = null;
1✔
314

315
    StripeError error =
1✔
316
        parseStripeError(response.body(), response.code(), response.requestId(), StripeError.class);
1✔
317

318
    error.setLastResponse(response);
1✔
319
    switch (response.code()) {
1✔
320
      case 400:
321
      case 404:
322
        if ("idempotency_error".equals(error.getType())) {
1✔
323
          exception =
1✔
324
              new IdempotencyException(
325
                  error.getMessage(), response.requestId(), error.getCode(), response.code());
1✔
326
        } else {
327
          exception =
1✔
328
              new InvalidRequestException(
329
                  error.getMessage(),
1✔
330
                  error.getParam(),
1✔
331
                  response.requestId(),
1✔
332
                  error.getCode(),
1✔
333
                  response.code(),
1✔
334
                  null);
335
        }
336
        break;
1✔
337
      case 401:
338
        exception =
×
339
            new AuthenticationException(
340
                error.getMessage(), response.requestId(), error.getCode(), response.code());
×
341
        break;
×
342
      case 402:
343
        exception =
×
344
            new CardException(
345
                error.getMessage(),
×
346
                response.requestId(),
×
347
                error.getCode(),
×
348
                error.getParam(),
×
349
                error.getDeclineCode(),
×
350
                error.getCharge(),
×
351
                response.code(),
×
352
                null);
353
        break;
×
354
      case 403:
355
        exception =
×
356
            new PermissionException(
357
                error.getMessage(), response.requestId(), error.getCode(), response.code());
×
358
        break;
×
359
      case 429:
360
        exception =
×
361
            new RateLimitException(
362
                error.getMessage(),
×
363
                error.getParam(),
×
364
                response.requestId(),
×
365
                error.getCode(),
×
366
                response.code(),
×
367
                null);
368
        break;
×
369
      default:
370
        exception =
×
371
            new ApiException(
372
                error.getMessage(), response.requestId(), error.getCode(), response.code(), null);
×
373
        break;
374
    }
375
    exception.setStripeError(error);
1✔
376

377
    throw exception;
1✔
378
  }
379

380
  private void handleV2ApiError(StripeResponse response) throws StripeException {
381
    JsonObject body =
1✔
382
        ApiResource.GSON.fromJson(response.body(), JsonObject.class).getAsJsonObject("error");
1✔
383

384
    JsonElement typeElement = body == null ? null : body.get("type");
1✔
385
    JsonElement codeElement = body == null ? null : body.get("code");
1✔
386
    String type = typeElement == null ? "<no_type>" : typeElement.getAsString();
1✔
387
    String code = codeElement == null ? "<no_code>" : codeElement.getAsString();
1✔
388

389
    StripeException exception =
1✔
390
        StripeException.parseV2Exception(type, body, response.code(), response.requestId(), this);
1✔
391
    if (exception != null) {
1✔
392
      throw exception;
1✔
393
    }
394

395
    StripeError error;
396
    try {
397
      error =
1✔
398
          parseStripeError(
1✔
399
              response.body(), response.code(), response.requestId(), StripeError.class);
1✔
400
    } catch (ApiException e) {
1✔
401
      String message = "Unrecognized error type '" + type + "'";
1✔
402
      JsonElement messageField = body == null ? null : body.get("message");
1✔
403
      if (messageField != null && messageField.isJsonPrimitive()) {
1✔
404
        message = messageField.getAsString();
×
405
      }
406

407
      throw new ApiException(message, response.requestId(), code, response.code(), null);
1✔
408
    }
1✔
409

410
    error.setLastResponse(response);
1✔
411
    exception =
1✔
412
        new ApiException(error.getMessage(), response.requestId(), code, response.code(), null);
1✔
413
    exception.setStripeV2Error(error);
1✔
414
    throw exception;
1✔
415
  }
416

417
  private void handleOAuthError(StripeResponse response) throws StripeException {
418
    OAuthError error = null;
1✔
419
    StripeException exception = null;
1✔
420

421
    try {
422
      error = StripeObject.deserializeStripeObject(response.body(), OAuthError.class, this);
1✔
423
    } catch (JsonSyntaxException e) {
×
424
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), e);
×
425
    }
1✔
426
    if (error == null) {
1✔
427
      throw makeMalformedJsonError(response.body(), response.code(), response.requestId(), null);
×
428
    }
429

430
    error.setLastResponse(response);
1✔
431

432
    String code = error.getError();
1✔
433
    String description = (error.getErrorDescription() != null) ? error.getErrorDescription() : code;
1✔
434

435
    switch (code) {
1✔
436
      case "invalid_client":
437
        exception =
1✔
438
            new InvalidClientException(
439
                code, description, response.requestId(), response.code(), null);
1✔
440
        break;
1✔
441
      case "invalid_grant":
442
        exception =
×
443
            new InvalidGrantException(
444
                code, description, response.requestId(), response.code(), null);
×
445
        break;
×
446
      case "invalid_request":
447
        exception =
×
448
            new com.stripe.exception.oauth.InvalidRequestException(
449
                code, description, response.requestId(), response.code(), null);
×
450
        break;
×
451
      case "invalid_scope":
452
        exception =
×
453
            new InvalidScopeException(
454
                code, description, response.requestId(), response.code(), null);
×
455
        break;
×
456
      case "unsupported_grant_type":
457
        exception =
×
458
            new UnsupportedGrantTypeException(
459
                code, description, response.requestId(), response.code(), null);
×
460
        break;
×
461
      case "unsupported_response_type":
462
        exception =
×
463
            new UnsupportedResponseTypeException(
464
                code, description, response.requestId(), response.code(), null);
×
465
        break;
×
466
      default:
467
        exception = new ApiException(code, response.requestId(), null, response.code(), null);
×
468
        break;
469
    }
470

471
    if (exception instanceof OAuthException) {
1✔
472
      ((OAuthException) exception).setOauthError(error);
1✔
473
    }
474

475
    throw exception;
1✔
476
  }
477

478
  @Override
479
  public void validateRequestOptions(RequestOptions options) {
480
    if ((options == null || options.getAuthenticator() == null)
1✔
481
        && this.options.getAuthenticator() == null) {
1✔
482
      throw new ApiKeyMissingException(
1✔
483
          "API key is not set. You can set the API key globally using Stripe.ApiKey, or by passing RequestOptions");
484
    }
485
  }
1✔
486

487
  private String fullUrl(BaseApiRequest apiRequest) {
488
    BaseAddress baseAddress = apiRequest.getBaseAddress();
1✔
489
    RequestOptions options = apiRequest.getOptions();
1✔
490
    String relativeUrl = apiRequest.getPath();
1✔
491
    String baseUrl;
492
    switch (baseAddress) {
1✔
493
      case API:
494
        baseUrl = this.options.getApiBase();
1✔
495
        break;
1✔
496
      case CONNECT:
497
        baseUrl = this.options.getConnectBase();
1✔
498
        break;
1✔
499
      case FILES:
500
        baseUrl = this.options.getFilesBase();
1✔
501
        break;
1✔
502
      case METER_EVENTS:
503
        baseUrl = this.options.getMeterEventsBase();
×
504
        break;
×
505
      default:
506
        throw new IllegalArgumentException("Unknown base address " + baseAddress);
×
507
    }
508
    if (options != null && options.getBaseUrl() != null) {
1✔
509
      baseUrl = options.getBaseUrl();
1✔
510
    }
511
    return String.format("%s%s", baseUrl, relativeUrl);
1✔
512
  }
513
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc