Skip to content

Commit e3ff4a9

Browse files
Merge pull request #2175 from stripe/xavdid/merge-java-beta
Merge to beta
2 parents 44f3b9a + d6d3990 commit e3ff4a9

13 files changed

Lines changed: 405 additions & 28 deletions

.claude/CLAUDE.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# stripe-java
2+
3+
## Testing
4+
5+
- Run all tests: `just test`
6+
- Run a single test class: `just test-one com.stripe.net.HttpClientTest`
7+
- Pass extra Gradle args: `just test --tests com.stripe.SomeTest`
8+
9+
## Formatting
10+
11+
- Format: `just format` (uses Spotless via Gradle)
12+
- Format check: `just format-check`
13+
14+
## Key Locations
15+
16+
- HTTP client abstract base (retry logic): `src/main/java/com/stripe/net/HttpClient.java`
17+
- HTTP implementation: `src/main/java/com/stripe/net/HttpURLConnectionClient.java`
18+
- Header management: `src/main/java/com/stripe/net/HttpHeaders.java`
19+
- Request building: `src/main/java/com/stripe/net/StripeRequest.java`
20+
- Authentication: `src/main/java/com/stripe/net/Authenticator.java`, `BearerTokenAuthenticator.java`
21+
- Response getter (request dispatch): `src/main/java/com/stripe/net/LiveStripeResponseGetter.java`
22+
- Main config/version: `src/main/java/com/stripe/Stripe.java`
23+
- Client class: `src/main/java/com/stripe/StripeClient.java`
24+
25+
## Generated Code
26+
27+
- 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.
28+
- 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.
29+
- Resource model classes under `src/main/java/com/stripe/model/` are largely generated.
30+
- The `net/` package (HTTP client, headers, request/response) is NOT generated.
31+
32+
## Conventions
33+
34+
- Uses Java's built-in `HttpURLConnection` for HTTP
35+
- Requires JDK 17 to build
36+
- Gradle build system
37+
- Work is not complete until `just test` passes
38+
39+
### Comments
40+
41+
- Comments MUST only be used to:
42+
1. Document a function
43+
2. Explain the WHY of a piece of code
44+
3. Explain a particularly complicated piece of code
45+
- Comments NEVER should be used to:
46+
1. Say what used to be there. That's no longer relevant!
47+
2. Explain the WHAT of a piece of code (unless it's very non-obvious)
48+
49+
It's ok not to put comments on/in a function if their addition wouldn't meaningfully clarify anything.

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ jobs:
153153
((github.event_name == 'workflow_dispatch') || (github.event_name == 'push')) &&
154154
startsWith(github.ref, 'refs/tags/v') &&
155155
!contains(github.ref, 'beta') &&
156+
!contains(github.ref, 'alpha') &&
156157
endsWith(github.actor, '-stripe')
157158
needs: [build, test]
158159
runs-on: "ubuntu-24.04"

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ This release changes the pinned API version to `2026-02-25.preview`.
1111
* Add support for `purpose` on `v2.moneymanagement.OutboundPaymentCreateParams` and `v2.moneymanagement.OutboundPayment`
1212
* Add support for `branchNumber` and `swiftCode` on `v2.moneymanagement.PayoutMethod.bank_account`
1313

14+
## 31.4.1 - 2026-03-06
15+
* [#2168](https://github.com/stripe/stripe-java/pull/2168) Support serializing Stripe objects with ApiResource.GSON
16+
* `ApiResource.GSON` now supports serializing Stripe objects back into compatible JSON
17+
* [#2165](https://github.com/stripe/stripe-java/pull/2165) Add AI Agent information to UserAgent
18+
1419
## 31.4.0 - 2026-02-25
1520
This release changes the pinned API version to `2026-02-25.clover`.
1621

src/main/java/com/stripe/model/BalanceTransactionSourceTypeAdapterFactory.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
2525
}
2626
final String discriminator = "object";
2727
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
28-
final TypeAdapter<com.stripe.model.BalanceTransactionSource> balanceTransactionSourceAdapter =
29-
gson.getDelegateAdapter(
30-
this, TypeToken.get(com.stripe.model.BalanceTransactionSource.class));
3128
final TypeAdapter<com.stripe.model.ApplicationFee> applicationFeeAdapter =
3229
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.ApplicationFee.class));
3330
final TypeAdapter<com.stripe.model.Charge> chargeAdapter =
@@ -68,7 +65,13 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
6865
new TypeAdapter<BalanceTransactionSource>() {
6966
@Override
7067
public void write(JsonWriter out, BalanceTransactionSource value) throws IOException {
71-
balanceTransactionSourceAdapter.write(out, value);
68+
@SuppressWarnings("unchecked")
69+
TypeAdapter<BalanceTransactionSource> adapter =
70+
(TypeAdapter<BalanceTransactionSource>)
71+
gson.getDelegateAdapter(
72+
BalanceTransactionSourceTypeAdapterFactory.this,
73+
TypeToken.get(value.getClass()));
74+
adapter.write(out, value);
7275
}
7376

7477
@Override

src/main/java/com/stripe/model/ExternalAccountTypeAdapterFactory.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
2828
}
2929
final String discriminator = "object";
3030
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
31-
final TypeAdapter<com.stripe.model.ExternalAccount> externalAccountAdapter =
32-
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.ExternalAccount.class));
3331
final TypeAdapter<com.stripe.model.BankAccount> bankAccountAdapter =
3432
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.BankAccount.class));
3533
final TypeAdapter<com.stripe.model.Card> cardAdapter =
@@ -39,7 +37,12 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
3937
new TypeAdapter<ExternalAccount>() {
4038
@Override
4139
public void write(JsonWriter out, ExternalAccount value) throws IOException {
42-
externalAccountAdapter.write(out, value);
40+
@SuppressWarnings("unchecked")
41+
TypeAdapter<ExternalAccount> adapter =
42+
(TypeAdapter<ExternalAccount>)
43+
gson.getDelegateAdapter(
44+
ExternalAccountTypeAdapterFactory.this, TypeToken.get(value.getClass()));
45+
adapter.write(out, value);
4346
}
4447

4548
@Override

src/main/java/com/stripe/model/PaymentSourceTypeAdapterFactory.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
2525
}
2626
final String discriminator = "object";
2727
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
28-
final TypeAdapter<com.stripe.model.PaymentSource> paymentSourceAdapter =
29-
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.PaymentSource.class));
3028
final TypeAdapter<com.stripe.model.Account> accountAdapter =
3129
gson.getDelegateAdapter(this, TypeToken.get(com.stripe.model.Account.class));
3230
final TypeAdapter<com.stripe.model.BankAccount> bankAccountAdapter =
@@ -40,7 +38,12 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
4038
new TypeAdapter<PaymentSource>() {
4139
@Override
4240
public void write(JsonWriter out, PaymentSource value) throws IOException {
43-
paymentSourceAdapter.write(out, value);
41+
@SuppressWarnings("unchecked")
42+
TypeAdapter<PaymentSource> adapter =
43+
(TypeAdapter<PaymentSource>)
44+
gson.getDelegateAdapter(
45+
PaymentSourceTypeAdapterFactory.this, TypeToken.get(value.getClass()));
46+
adapter.write(out, value);
4447
}
4548

4649
@Override
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.stripe.model;
2+
3+
import com.google.gson.JsonElement;
4+
import com.google.gson.JsonNull;
5+
import com.google.gson.JsonSerializationContext;
6+
import com.google.gson.JsonSerializer;
7+
import java.lang.reflect.Type;
8+
9+
public class StripeRawJsonObjectSerializer implements JsonSerializer<StripeRawJsonObject> {
10+
@Override
11+
public JsonElement serialize(
12+
StripeRawJsonObject src, Type typeOfSrc, JsonSerializationContext context) {
13+
if (src.json != null) {
14+
return src.json;
15+
}
16+
return JsonNull.INSTANCE;
17+
}
18+
}

src/main/java/com/stripe/model/v2/core/Event.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ protected StripeObject fetchRelatedObject(RelatedObject relatedObject) throws St
9898
objectClass = StripeRawJsonObject.class;
9999
}
100100

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

103104
if (context != null) {
104-
opts = new RequestOptions.RequestOptionsBuilder().setStripeAccount(context).build();
105+
optsBuilder.setStripeAccount(context);
105106
}
106107

108+
RequestOptions opts = optsBuilder.build();
109+
107110
return this.responseGetter.request(
108111
new ApiRequest(
109112
BaseAddress.API, ApiResource.RequestMethod.GET, relatedObject.getUrl(), null, opts),

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ private static Gson createGson(boolean shouldSetResponseGetter) {
5757
.registerTypeAdapter(Event.Request.class, new EventRequestDeserializer())
5858
.registerTypeAdapter(StripeContext.class, new StripeContextDeserializer())
5959
.registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer())
60+
.registerTypeAdapter(ExpandableField.class, new ExpandableFieldSerializer())
6061
.registerTypeAdapter(Instant.class, new InstantDeserializer())
6162
.registerTypeAdapterFactory(new EventTypeAdapterFactory())
6263
.registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectDeserializer())
64+
.registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectSerializer())
6365
.registerTypeAdapterFactory(new StripeCollectionItemTypeSettingFactory())
6466
.addReflectionAccessFilter(
6567
new ReflectionAccessFilter() {

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

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Optional;
1414
import java.util.Properties;
1515
import java.util.concurrent.ThreadLocalRandom;
16+
import java.util.function.Function;
1617

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

141+
static String detectAIAgent() {
142+
return detectAIAgent(System::getenv);
143+
}
144+
145+
static String detectAIAgent(Function<String, String> getEnv) {
146+
String[][] agents = {
147+
// The beginning of the section generated from our OpenAPI spec
148+
{"ANTIGRAVITY_CLI_ALIAS", "antigravity"},
149+
{"CLAUDECODE", "claude_code"},
150+
{"CLINE_ACTIVE", "cline"},
151+
{"CODEX_SANDBOX", "codex_cli"},
152+
{"CODEX_THREAD_ID", "codex_cli"},
153+
{"CODEX_SANDBOX_NETWORK_DISABLED", "codex_cli"},
154+
{"CODEX_CI", "codex_cli"},
155+
{"CURSOR_AGENT", "cursor"},
156+
{"GEMINI_CLI", "gemini_cli"},
157+
{"OPENCODE", "open_code"},
158+
// The end of the section generated from our OpenAPI spec
159+
};
160+
for (String[] agent : agents) {
161+
String val = getEnv.apply(agent[0]);
162+
if (val != null && !val.isEmpty()) {
163+
return agent[1];
164+
}
165+
}
166+
return "";
167+
}
168+
140169
/**
141170
* Builds the value of the {@code User-Agent} header.
142171
*
143172
* @return a string containing the value of the {@code User-Agent} header
144173
*/
145174
protected static String buildUserAgentString(StripeRequest request) {
175+
return buildUserAgentString(request, detectAIAgent());
176+
}
177+
178+
static String buildUserAgentString(StripeRequest request, String aiAgent) {
146179
String apiMode = request.apiMode() == ApiMode.V2 ? "v2" : "v1";
147180

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

187+
if (!aiAgent.isEmpty()) {
188+
userAgent += " AIAgent/" + aiAgent;
189+
}
190+
154191
return userAgent;
155192
}
156193

@@ -160,28 +197,36 @@ protected static String buildUserAgentString(StripeRequest request) {
160197
* @return a string containing the value of the {@code X-Stripe-Client-User-Agent} header
161198
*/
162199
protected static String buildXStripeClientUserAgentString() {
163-
String[] propertyNames = {
164-
"os.name",
165-
"os.version",
166-
"os.arch",
167-
"java.version",
168-
"java.vendor",
169-
"java.vm.version",
170-
"java.vm.vendor"
171-
};
200+
return buildXStripeClientUserAgentString(detectAIAgent());
201+
}
202+
203+
static String buildXStripeClientUserAgentString(String aiAgent) {
204+
String[] propertyNames = {"java.version", "java.vendor", "java.vm.version", "java.vm.vendor"};
172205

173206
Map<String, String> propertyMap = new HashMap<>();
174207
for (String propertyName : propertyNames) {
175208
propertyMap.put(propertyName, System.getProperty(propertyName));
176209
}
177210
propertyMap.put("bindings.version", Stripe.VERSION);
178211
propertyMap.put("lang", "Java");
179-
propertyMap.put("publisher", "Stripe");
212+
if (Stripe.enableTelemetry) {
213+
propertyMap.put(
214+
"platform",
215+
System.getProperty("os.name")
216+
+ " "
217+
+ System.getProperty("os.version")
218+
+ " "
219+
+ System.getProperty("os.arch"));
220+
}
180221
if (Stripe.getAppInfo() != null) {
181222
propertyMap.put("application", ApiResource.INTERNAL_GSON.toJson(Stripe.getAppInfo()));
182223
}
183224
getGsonVersion().ifPresent(ver -> propertyMap.put("gson.version", ver));
184225

226+
if (!aiAgent.isEmpty()) {
227+
propertyMap.put("ai_agent", aiAgent);
228+
}
229+
185230
return ApiResource.INTERNAL_GSON.toJson(propertyMap);
186231
}
187232

0 commit comments

Comments
 (0)