Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# stripe-java

## Testing

- Run all tests: `just test`
- Run a single test class: `just test-one com.stripe.net.HttpClientTest`
- Pass extra Gradle args: `just test --tests com.stripe.SomeTest`

## Formatting

- Format: `just format` (uses Spotless via Gradle)
- Format check: `just format-check`

## Key Locations

- HTTP client abstract base (retry logic): `src/main/java/com/stripe/net/HttpClient.java`
- HTTP implementation: `src/main/java/com/stripe/net/HttpURLConnectionClient.java`
- Header management: `src/main/java/com/stripe/net/HttpHeaders.java`
- Request building: `src/main/java/com/stripe/net/StripeRequest.java`
- Authentication: `src/main/java/com/stripe/net/Authenticator.java`, `BearerTokenAuthenticator.java`
- Response getter (request dispatch): `src/main/java/com/stripe/net/LiveStripeResponseGetter.java`
- Main config/version: `src/main/java/com/stripe/Stripe.java`
- Client class: `src/main/java/com/stripe/StripeClient.java`

## Generated Code

- Files containing `File generated from our OpenAPI spec` at the top are generated; do not edit. Similarly, any code block starting with `The beginning of the section generated from our OpenAPI spec` is generated and should not be edited directly.
- If something in a generated file/range needs to be updated, add a summary of the change to your report but don't attempt to edit it directly.
- Resource model classes under `src/main/java/com/stripe/model/` are largely generated.
- The `net/` package (HTTP client, headers, request/response) is NOT generated.

## Conventions

- Uses Java's built-in `HttpURLConnection` for HTTP
- Requires JDK 17 to build
- Gradle build system
- Work is not complete until `just test` passes

### Comments

- Comments MUST only be used to:
1. Document a function
2. Explain the WHY of a piece of code
3. Explain a particularly complicated piece of code
- Comments NEVER should be used to:
1. Say what used to be there. That's no longer relevant!
2. Explain the WHAT of a piece of code (unless it's very non-obvious)

It's ok not to put comments on/in a function if their addition wouldn't meaningfully clarify anything.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ jobs:
((github.event_name == 'workflow_dispatch') || (github.event_name == 'push')) &&
startsWith(github.ref, 'refs/tags/v') &&
!contains(github.ref, 'beta') &&
!contains(github.ref, 'alpha') &&
endsWith(github.actor, '-stripe')
needs: [build, test]
runs-on: "ubuntu-24.04"
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ This release changes the pinned API version to `2026-02-25.preview`.
* Add support for `purpose` on `v2.moneymanagement.OutboundPaymentCreateParams` and `v2.moneymanagement.OutboundPayment`
* Add support for `branchNumber` and `swiftCode` on `v2.moneymanagement.PayoutMethod.bank_account`

## 31.4.1 - 2026-03-06
* [#2168](https://github.com/stripe/stripe-java/pull/2168) Support serializing Stripe objects with ApiResource.GSON
* `ApiResource.GSON` now supports serializing Stripe objects back into compatible JSON
* [#2165](https://github.com/stripe/stripe-java/pull/2165) Add AI Agent information to UserAgent

## 31.4.0 - 2026-02-25
This release changes the pinned API version to `2026-02-25.clover`.

Expand Down
2 changes: 1 addition & 1 deletion CODEGEN_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e10daa4ed23a4fe87d6ea60836226446e042fdd3
6dded749243c6d0c16f48c648aa32d13cb66c439
2 changes: 1 addition & 1 deletion OPENAPI_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2186
v2189
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
}
final String discriminator = "object";
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<com.stripe.model.BalanceTransactionSource> balanceTransactionSourceAdapter =
gson.getDelegateAdapter(
this, TypeToken.get(com.stripe.model.BalanceTransactionSource.class));
final TypeAdapter<com.stripe.model.ApplicationFee> applicationFeeAdapter =
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.ApplicationFee.class));
final TypeAdapter<com.stripe.model.Charge> chargeAdapter =
Expand Down Expand Up @@ -68,7 +65,13 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
new TypeAdapter<BalanceTransactionSource>() {
@Override
public void write(JsonWriter out, BalanceTransactionSource value) throws IOException {
balanceTransactionSourceAdapter.write(out, value);
@SuppressWarnings("unchecked")
TypeAdapter<BalanceTransactionSource> adapter =
(TypeAdapter<BalanceTransactionSource>)
gson.getDelegateAdapter(
BalanceTransactionSourceTypeAdapterFactory.this,
TypeToken.get(value.getClass()));
adapter.write(out, value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
}
final String discriminator = "object";
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<com.stripe.model.ExternalAccount> externalAccountAdapter =
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.ExternalAccount.class));
final TypeAdapter<com.stripe.model.BankAccount> bankAccountAdapter =
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.BankAccount.class));
final TypeAdapter<com.stripe.model.Card> cardAdapter =
Expand All @@ -39,7 +37,12 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
new TypeAdapter<ExternalAccount>() {
@Override
public void write(JsonWriter out, ExternalAccount value) throws IOException {
externalAccountAdapter.write(out, value);
@SuppressWarnings("unchecked")
TypeAdapter<ExternalAccount> adapter =
(TypeAdapter<ExternalAccount>)
gson.getDelegateAdapter(
ExternalAccountTypeAdapterFactory.this, TypeToken.get(value.getClass()));
adapter.write(out, value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
}
final String discriminator = "object";
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<com.stripe.model.PaymentSource> paymentSourceAdapter =
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.PaymentSource.class));
final TypeAdapter<com.stripe.model.Account> accountAdapter =
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.Account.class));
final TypeAdapter<com.stripe.model.BankAccount> bankAccountAdapter =
Expand All @@ -40,7 +38,12 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
new TypeAdapter<PaymentSource>() {
@Override
public void write(JsonWriter out, PaymentSource value) throws IOException {
paymentSourceAdapter.write(out, value);
@SuppressWarnings("unchecked")
TypeAdapter<PaymentSource> adapter =
(TypeAdapter<PaymentSource>)
gson.getDelegateAdapter(
PaymentSourceTypeAdapterFactory.this, TypeToken.get(value.getClass()));
adapter.write(out, value);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.stripe.model;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;

public class StripeRawJsonObjectSerializer implements JsonSerializer<StripeRawJsonObject> {
@Override
public JsonElement serialize(
StripeRawJsonObject src, Type typeOfSrc, JsonSerializationContext context) {
if (src.json != null) {
return src.json;
}
return JsonNull.INSTANCE;
}
}
7 changes: 5 additions & 2 deletions src/main/java/com/stripe/model/v2/core/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,15 @@ protected StripeObject fetchRelatedObject(RelatedObject relatedObject) throws St
objectClass = StripeRawJsonObject.class;
}

RequestOptions opts = null;
RequestOptions.RequestOptionsBuilder optsBuilder = new RequestOptions.RequestOptionsBuilder();
// optsBuilder.setStripeRequestTrigger("event=" + id); // TODO https://go/j/DEVSDK-3018

if (context != null) {
opts = new RequestOptions.RequestOptionsBuilder().setStripeAccount(context).build();
optsBuilder.setStripeAccount(context);
}

RequestOptions opts = optsBuilder.build();

return this.responseGetter.request(
new ApiRequest(
BaseAddress.API, ApiResource.RequestMethod.GET, relatedObject.getUrl(), null, opts),
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/stripe/net/ApiResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ private static Gson createGson(boolean shouldSetResponseGetter) {
.registerTypeAdapter(Event.Request.class, new EventRequestDeserializer())
.registerTypeAdapter(StripeContext.class, new StripeContextDeserializer())
.registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer())
.registerTypeAdapter(ExpandableField.class, new ExpandableFieldSerializer())
.registerTypeAdapter(Instant.class, new InstantDeserializer())
.registerTypeAdapterFactory(new EventTypeAdapterFactory())
.registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectDeserializer())
.registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectSerializer())
.registerTypeAdapterFactory(new StripeCollectionItemTypeSettingFactory())
.addReflectionAccessFilter(
new ReflectionAccessFilter() {
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/stripe/net/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;

/** Base abstract class for HTTP clients used to send requests to Stripe's API. */
public abstract class HttpClient {
Expand Down Expand Up @@ -137,12 +138,44 @@ public StripeResponseStream requestStreamWithRetries(StripeRequest request)
return sendWithRetries(request, (r) -> this.requestStream(r));
}

static String detectAIAgent() {
return detectAIAgent(System::getenv);
}

static String detectAIAgent(Function<String, String> getEnv) {
String[][] agents = {
// The beginning of the section generated from our OpenAPI spec
{"ANTIGRAVITY_CLI_ALIAS", "antigravity"},
{"CLAUDECODE", "claude_code"},
{"CLINE_ACTIVE", "cline"},
{"CODEX_SANDBOX", "codex_cli"},
{"CODEX_THREAD_ID", "codex_cli"},
{"CODEX_SANDBOX_NETWORK_DISABLED", "codex_cli"},
{"CODEX_CI", "codex_cli"},
{"CURSOR_AGENT", "cursor"},
{"GEMINI_CLI", "gemini_cli"},
{"OPENCODE", "open_code"},
// The end of the section generated from our OpenAPI spec
};
for (String[] agent : agents) {
String val = getEnv.apply(agent[0]);
if (val != null && !val.isEmpty()) {
return agent[1];
}
}
return "";
}

/**
* Builds the value of the {@code User-Agent} header.
*
* @return a string containing the value of the {@code User-Agent} header
*/
protected static String buildUserAgentString(StripeRequest request) {
return buildUserAgentString(request, detectAIAgent());
}

static String buildUserAgentString(StripeRequest request, String aiAgent) {
String apiMode = request.apiMode() == ApiMode.V2 ? "v2" : "v1";

String userAgent = String.format("Stripe/%s JavaBindings/%s", apiMode, Stripe.VERSION);
Expand All @@ -151,6 +184,10 @@ protected static String buildUserAgentString(StripeRequest request) {
userAgent += " " + formatAppInfo(Stripe.getAppInfo());
}

if (!aiAgent.isEmpty()) {
userAgent += " AIAgent/" + aiAgent;
}

return userAgent;
}

Expand All @@ -160,6 +197,10 @@ protected static String buildUserAgentString(StripeRequest request) {
* @return a string containing the value of the {@code X-Stripe-Client-User-Agent} header
*/
protected static String buildXStripeClientUserAgentString() {
return buildXStripeClientUserAgentString(detectAIAgent());
}

static String buildXStripeClientUserAgentString(String aiAgent) {
String[] propertyNames = {
"os.name",
"os.version",
Expand All @@ -182,6 +223,10 @@ protected static String buildXStripeClientUserAgentString() {
}
getGsonVersion().ifPresent(ver -> propertyMap.put("gson.version", ver));

if (!aiAgent.isEmpty()) {
propertyMap.put("ai_agent", aiAgent);
}

return ApiResource.INTERNAL_GSON.toJson(propertyMap);
}

Expand Down
Loading