This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 9a9c07ebd051 CAMEL-26166: camel-stripe - convert bytes and stream
bodies, read the CamelStripeOperation header, no retrieve before update (#28944)
9a9c07ebd051 is described below
commit 9a9c07ebd051530d801f41ae2839ade50f82b610
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:51:45 2026 +0100
CAMEL-26166: camel-stripe - convert bytes and stream bodies, read the
CamelStripeOperation header, no retrieve before update (#28944)
… CamelStripeOperation header, no retrieve before update
The producer accepted only a Map or a String body, so JSON as bytes or a
stream
(the body of a rest or platform-http route) failed. Any other body is now
converted to a String and parsed as JSON.
The CamelStripeOperation header was documented but never read. It now
overrides
the operation of the endpoint, as a String or a StripeOperation.
update, capture, delete and cancel retrieved the object first, so each call
made two HTTP requests. They now call the API on the id directly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../apache/camel/catalog/components/stripe.json | 4 +-
.../camel/catalog/docs/stripe-component.adoc | 9 +-
.../org/apache/camel/component/stripe/stripe.json | 4 +-
.../src/main/docs/stripe-component.adoc | 9 +-
.../camel/component/stripe/StripeConstants.java | 4 +-
.../camel/component/stripe/StripeEndpoint.java | 6 +-
.../camel/component/stripe/StripeProducer.java | 116 ++++++------
.../stripe/StripeProducerRequestTest.java | 198 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 6 +
.../endpoint/dsl/StripeEndpointBuilderFactory.java | 3 +-
10 files changed, 286 insertions(+), 73 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/stripe.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/stripe.json
index 44cb1b4cc4be..950c478b2f0e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/stripe.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/stripe.json
@@ -30,12 +30,12 @@
"apiKey": { "index": 3, "kind": "property", "displayName": "Api Key",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "description": "The Stripe API key for
authentication" }
},
"headers": {
- "CamelStripeOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform", "constantName":
"org.apache.camel.component.stripe.StripeConstants#OPERATION_HEADER" },
+ "CamelStripeOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform, such as customers or
paymentIntents. Overrides the operation of the endpoint.", "constantName":
"org.apache.camel.component.stripe.StripeConstants#OPERATION_HEADER" },
"CamelStripeMethod": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The method to invoke (create, retrieve, update, delete,
list, cancel, capture)", "constantName":
"org.apache.camel.component.stripe.StripeConstants#METHOD_HEADER" },
"CamelStripeObjectId": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The ID of the object to retrieve, update, or delete",
"constantName": "org.apache.camel.component.stripe.StripeConstants#OBJECT_ID" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "map", "text", "null" ], "description": "The
request parameters of the call: a Map, or a JSON object as text; null for none.
Bytes, a stream or a POJO fails: convert it to a String first. retrieve, delete
and cancel ignore the body; they and update and capture take the id of the
object from the CamelStripeObjectId header" },
+ "producerIn": { "carriers": [ "map", "text", "bytes", "stream", "null" ],
"description": "The request parameters of the call: a Map, or a JSON object as
text, bytes or a stream; null for none. A POJO fails. retrieve, delete and
cancel ignore the body; they and update and capture take the id of the object
from the CamelStripeObjectId header" },
"producerOut": { "carriers": [ "pojo" ], "description": "The Stripe object
the call returns, such as a com.stripe.model.Customer; list gives a collection
of them, such as a CustomerCollection, with the objects in its data" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/stripe-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/stripe-component.adoc
index 6c76349fc9a9..3b69cc282ab5 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/stripe-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/stripe-component.adoc
@@ -55,6 +55,8 @@ Where `operation` is one of the following:
* `prices` - Create, retrieve, update, and list prices
* `balanceTransactions` - Retrieve and list balance transactions
+The `CamelStripeOperation` header overrides the operation of the endpoint for
a single call.
+
// component-configure options: START
== Configuring Options
@@ -322,7 +324,8 @@ from("direct:payment")
.log("Payment intent created: ${body.id}");
----
-Using JSON (useful when integrating with REST endpoints):
+Using JSON (useful when integrating with REST endpoints). The JSON can be a
String, bytes or a stream, such as the body
+of a `rest` or `platform-http` route:
[tabs]
====
@@ -331,7 +334,6 @@ Java::
[source,java]
----
from("rest:post:/payments")
- .convertBodyTo(String.class)
.log("Received payment request: ${body}")
.to("stripe:paymentIntents?apiKey={{stripe.apiKey}}")
.log("Payment intent created: ${body.id}");
@@ -343,7 +345,6 @@ XML::
----
<route>
<from uri="rest:post:/payments"/>
- <convertBodyTo type="java.lang.String"/>
<log message="Received payment request: ${body}"/>
<to uri="stripe:paymentIntents?apiKey={{stripe.apiKey}}"/>
<log message="Payment intent created: ${body.id}"/>
@@ -358,8 +359,6 @@ YAML::
from:
uri: rest:post:/payments
steps:
- - convertBodyTo:
- type: java.lang.String
- log:
message: "Received payment request: ${body}"
- to:
diff --git
a/components/camel-stripe/src/generated/resources/META-INF/org/apache/camel/component/stripe/stripe.json
b/components/camel-stripe/src/generated/resources/META-INF/org/apache/camel/component/stripe/stripe.json
index 44cb1b4cc4be..950c478b2f0e 100644
---
a/components/camel-stripe/src/generated/resources/META-INF/org/apache/camel/component/stripe/stripe.json
+++
b/components/camel-stripe/src/generated/resources/META-INF/org/apache/camel/component/stripe/stripe.json
@@ -30,12 +30,12 @@
"apiKey": { "index": 3, "kind": "property", "displayName": "Api Key",
"group": "security", "label": "security", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "description": "The Stripe API key for
authentication" }
},
"headers": {
- "CamelStripeOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform", "constantName":
"org.apache.camel.component.stripe.StripeConstants#OPERATION_HEADER" },
+ "CamelStripeOperation": { "index": 0, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The operation to perform, such as customers or
paymentIntents. Overrides the operation of the endpoint.", "constantName":
"org.apache.camel.component.stripe.StripeConstants#OPERATION_HEADER" },
"CamelStripeMethod": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The method to invoke (create, retrieve, update, delete,
list, cancel, capture)", "constantName":
"org.apache.camel.component.stripe.StripeConstants#METHOD_HEADER" },
"CamelStripeObjectId": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The ID of the object to retrieve, update, or delete",
"constantName": "org.apache.camel.component.stripe.StripeConstants#OBJECT_ID" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "map", "text", "null" ], "description": "The
request parameters of the call: a Map, or a JSON object as text; null for none.
Bytes, a stream or a POJO fails: convert it to a String first. retrieve, delete
and cancel ignore the body; they and update and capture take the id of the
object from the CamelStripeObjectId header" },
+ "producerIn": { "carriers": [ "map", "text", "bytes", "stream", "null" ],
"description": "The request parameters of the call: a Map, or a JSON object as
text, bytes or a stream; null for none. A POJO fails. retrieve, delete and
cancel ignore the body; they and update and capture take the id of the object
from the CamelStripeObjectId header" },
"producerOut": { "carriers": [ "pojo" ], "description": "The Stripe object
the call returns, such as a com.stripe.model.Customer; list gives a collection
of them, such as a CustomerCollection, with the objects in its data" }
},
"properties": {
diff --git a/components/camel-stripe/src/main/docs/stripe-component.adoc
b/components/camel-stripe/src/main/docs/stripe-component.adoc
index 6c76349fc9a9..3b69cc282ab5 100644
--- a/components/camel-stripe/src/main/docs/stripe-component.adoc
+++ b/components/camel-stripe/src/main/docs/stripe-component.adoc
@@ -55,6 +55,8 @@ Where `operation` is one of the following:
* `prices` - Create, retrieve, update, and list prices
* `balanceTransactions` - Retrieve and list balance transactions
+The `CamelStripeOperation` header overrides the operation of the endpoint for
a single call.
+
// component-configure options: START
== Configuring Options
@@ -322,7 +324,8 @@ from("direct:payment")
.log("Payment intent created: ${body.id}");
----
-Using JSON (useful when integrating with REST endpoints):
+Using JSON (useful when integrating with REST endpoints). The JSON can be a
String, bytes or a stream, such as the body
+of a `rest` or `platform-http` route:
[tabs]
====
@@ -331,7 +334,6 @@ Java::
[source,java]
----
from("rest:post:/payments")
- .convertBodyTo(String.class)
.log("Received payment request: ${body}")
.to("stripe:paymentIntents?apiKey={{stripe.apiKey}}")
.log("Payment intent created: ${body.id}");
@@ -343,7 +345,6 @@ XML::
----
<route>
<from uri="rest:post:/payments"/>
- <convertBodyTo type="java.lang.String"/>
<log message="Received payment request: ${body}"/>
<to uri="stripe:paymentIntents?apiKey={{stripe.apiKey}}"/>
<log message="Payment intent created: ${body.id}"/>
@@ -358,8 +359,6 @@ YAML::
from:
uri: rest:post:/payments
steps:
- - convertBodyTo:
- type: java.lang.String
- log:
message: "Received payment request: ${body}"
- to:
diff --git
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeConstants.java
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeConstants.java
index fdf71b2a5137..71f20be42cb1 100644
---
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeConstants.java
+++
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeConstants.java
@@ -35,7 +35,9 @@ public final class StripeConstants {
public static final String OPERATION_BALANCE_TRANSACTIONS =
"balanceTransactions";
// Headers
- @Metadata(description = "The operation to perform", javaType = "String")
+ @Metadata(description = "The operation to perform, such as customers or
paymentIntents. Overrides the operation"
+ + " of the endpoint.",
+ javaType = "String")
public static final String OPERATION_HEADER = HEADER_PREFIX + "Operation";
@Metadata(description = "The method to invoke (create, retrieve, update,
delete, list, cancel, capture)",
javaType = "String")
diff --git
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeEndpoint.java
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeEndpoint.java
index 8043256f51a8..2d3979716d75 100644
---
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeEndpoint.java
+++
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeEndpoint.java
@@ -33,10 +33,10 @@ import org.apache.camel.support.DefaultEndpoint;
*/
@UriEndpoint(firstVersion = "4.17.0", scheme = "stripe", title = "Stripe",
syntax = "stripe:operation",
category = { Category.SAAS, Category.FINANCE }, producerOnly =
true, headersClass = StripeConstants.class)
-@BodyTypes(producerIn = @BodyType(value = { Carrier.MAP, Carrier.TEXT,
Carrier.NULL },
+@BodyTypes(producerIn = @BodyType(value = { Carrier.MAP, Carrier.TEXT,
Carrier.BYTES, Carrier.STREAM, Carrier.NULL },
description = "The request parameters of the
call: a Map, or a JSON object as"
- + " text; null for none.
Bytes, a stream or a POJO fails: convert it"
- + " to a String first.
retrieve, delete and cancel ignore the body;"
+ + " text, bytes or a stream;
null for none. A POJO fails."
+ + " retrieve, delete and
cancel ignore the body;"
+ " they and update and
capture take the id of the object from the"
+ " CamelStripeObjectId
header"),
producerOut = @BodyType(value = Carrier.POJO,
diff --git
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeProducer.java
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeProducer.java
index a1c38bab4f85..fe4757fd841b 100644
---
a/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeProducer.java
+++
b/components/camel-stripe/src/main/java/org/apache/camel/component/stripe/StripeProducer.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.stripe;
import java.util.Collections;
import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stripe.model.BalanceTransaction;
@@ -30,6 +32,7 @@ import com.stripe.model.Price;
import com.stripe.model.Product;
import com.stripe.model.Refund;
import com.stripe.model.Subscription;
+import com.stripe.net.ApiResource;
import com.stripe.net.RequestOptions;
import org.apache.camel.Exchange;
import org.apache.camel.support.DefaultProducer;
@@ -76,6 +79,12 @@ public class StripeProducer extends DefaultProducer {
@Override
public void process(Exchange exchange) throws Exception {
StripeOperation operation =
getEndpoint().getConfiguration().getOperation();
+ Object operationHeader =
exchange.getIn().getHeader(StripeConstants.OPERATION_HEADER);
+ if (operationHeader instanceof StripeOperation op) {
+ operation = op;
+ } else if (ObjectHelper.isNotEmpty(operationHeader)) {
+ operation =
StripeOperation.fromValue(exchange.getIn().getHeader(StripeConstants.OPERATION_HEADER,
String.class));
+ }
String method =
exchange.getIn().getHeader(StripeConstants.METHOD_HEADER, String.class);
if (operation == null) {
@@ -91,7 +100,8 @@ public class StripeProducer extends DefaultProducer {
}
/**
- * Converts the exchange body to a Map for Stripe API calls. Supports Map
objects and JSON strings.
+ * Converts the exchange body to a Map for Stripe API calls. Supports Map
objects and JSON as a String, or as any
+ * other body that converts to a String, such as bytes or a stream.
*
* @param exchange the Camel exchange
* @return a Map of parameters, or an empty map if body is null
@@ -107,19 +117,34 @@ public class StripeProducer extends DefaultProducer {
return (Map<String, Object>) body;
}
- if (body instanceof String json) {
- if (json.trim().isEmpty()) {
- return Collections.emptyMap();
- }
- try {
- return OBJECT_MAPPER.readValue(json, Map.class);
- } catch (Exception e) {
- throw new IllegalArgumentException("Failed to parse JSON body:
" + e.getMessage(), e);
- }
+ String json = body instanceof String text ? text :
exchange.getIn().getBody(String.class);
+ if (json == null) {
+ throw new IllegalArgumentException(
+ "Body must be a Map<String, Object> or a JSON String, but
was: " + body.getClass().getName());
+ }
+ if (json.trim().isEmpty()) {
+ return Collections.emptyMap();
+ }
+ try {
+ return OBJECT_MAPPER.readValue(json, Map.class);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse JSON body: " +
e.getMessage(), e);
}
+ }
+
+ /**
+ * Creates a Stripe object that has only the given id, so update, delete,
capture and cancel call the API on the id
+ * directly, without retrieving the object first.
+ */
+ private static <T extends ApiResource> T forId(Supplier<T> factory,
BiConsumer<T, String> idSetter, String id) {
+ T resource = factory.get();
+ idSetter.accept(resource, id);
+ resource.setResponseGetter(ApiResource.getGlobalResponseGetter());
+ return resource;
+ }
- throw new IllegalArgumentException(
- "Body must be a Map<String, Object> or a JSON String, but was:
" + body.getClass().getName());
+ private static String getObjectId(Exchange exchange) {
+ return exchange.getIn().getHeader(StripeConstants.OBJECT_ID,
String.class);
}
private Object performOperation(Exchange exchange, StripeOperation
operation, String method) throws Exception {
@@ -157,15 +182,13 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Charge.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String chargeId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Charge charge = Charge.retrieve(chargeId, requestOptions);
- return charge.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Charge::new, Charge::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_LIST:
return Charge.list(getParametersFromBody(exchange),
requestOptions);
case StripeConstants.METHOD_CAPTURE:
- String captureId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Charge captureCharge = Charge.retrieve(captureId,
requestOptions);
- return captureCharge.capture(getParametersFromBody(exchange),
requestOptions);
+ return forId(Charge::new, Charge::setId,
getObjectId(exchange)).capture(getParametersFromBody(exchange),
+ requestOptions);
default:
throw new IllegalArgumentException("Unsupported method for
charges: " + method);
}
@@ -179,13 +202,10 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Customer.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String customerId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Customer customer = Customer.retrieve(customerId,
requestOptions);
- return customer.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Customer::new, Customer::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_DELETE:
- String deleteId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Customer deleteCustomer = Customer.retrieve(deleteId,
requestOptions);
- return deleteCustomer.delete(requestOptions);
+ return forId(Customer::new, Customer::setId,
getObjectId(exchange)).delete(requestOptions);
case StripeConstants.METHOD_LIST:
return Customer.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -201,13 +221,10 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return PaymentIntent.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String piId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- PaymentIntent pi = PaymentIntent.retrieve(piId,
requestOptions);
- return pi.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(PaymentIntent::new, PaymentIntent::setId,
getObjectId(exchange))
+ .update(getParametersFromBody(exchange),
requestOptions);
case StripeConstants.METHOD_CANCEL:
- String cancelId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- PaymentIntent cancelPi = PaymentIntent.retrieve(cancelId,
requestOptions);
- return cancelPi.cancel(requestOptions);
+ return forId(PaymentIntent::new, PaymentIntent::setId,
getObjectId(exchange)).cancel(requestOptions);
case StripeConstants.METHOD_LIST:
return PaymentIntent.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -223,9 +240,8 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return PaymentMethod.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String pmId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- PaymentMethod pm = PaymentMethod.retrieve(pmId,
requestOptions);
- return pm.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(PaymentMethod::new, PaymentMethod::setId,
getObjectId(exchange))
+ .update(getParametersFromBody(exchange),
requestOptions);
case StripeConstants.METHOD_LIST:
return PaymentMethod.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -241,9 +257,8 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Refund.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String refundId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Refund refund = Refund.retrieve(refundId, requestOptions);
- return refund.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Refund::new, Refund::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_LIST:
return Refund.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -259,13 +274,11 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Subscription.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String subId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Subscription subscription = Subscription.retrieve(subId,
requestOptions);
- return subscription.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Subscription::new, Subscription::setId,
getObjectId(exchange))
+ .update(getParametersFromBody(exchange),
requestOptions);
case StripeConstants.METHOD_CANCEL:
- String cancelId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Subscription cancelSub = Subscription.retrieve(cancelId,
requestOptions);
- return cancelSub.cancel((Map<String, Object>) null,
requestOptions);
+ return forId(Subscription::new, Subscription::setId,
getObjectId(exchange)).cancel((Map<String, Object>) null,
+ requestOptions);
case StripeConstants.METHOD_LIST:
return Subscription.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -281,9 +294,8 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Invoice.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String invoiceId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Invoice invoice = Invoice.retrieve(invoiceId, requestOptions);
- return invoice.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Invoice::new, Invoice::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_LIST:
return Invoice.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -299,13 +311,10 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Product.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String productId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Product product = Product.retrieve(productId, requestOptions);
- return product.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Product::new, Product::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_DELETE:
- String deleteId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Product deleteProduct = Product.retrieve(deleteId,
requestOptions);
- return deleteProduct.delete(requestOptions);
+ return forId(Product::new, Product::setId,
getObjectId(exchange)).delete(requestOptions);
case StripeConstants.METHOD_LIST:
return Product.list(getParametersFromBody(exchange),
requestOptions);
default:
@@ -321,9 +330,8 @@ public class StripeProducer extends DefaultProducer {
String id =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
return Price.retrieve(id, requestOptions);
case StripeConstants.METHOD_UPDATE:
- String priceId =
exchange.getIn().getHeader(StripeConstants.OBJECT_ID, String.class);
- Price price = Price.retrieve(priceId, requestOptions);
- return price.update(getParametersFromBody(exchange),
requestOptions);
+ return forId(Price::new, Price::setId,
getObjectId(exchange)).update(getParametersFromBody(exchange),
+ requestOptions);
case StripeConstants.METHOD_LIST:
return Price.list(getParametersFromBody(exchange),
requestOptions);
default:
diff --git
a/components/camel-stripe/src/test/java/org/apache/camel/component/stripe/StripeProducerRequestTest.java
b/components/camel-stripe/src/test/java/org/apache/camel/component/stripe/StripeProducerRequestTest.java
new file mode 100644
index 000000000000..4285b0e1309a
--- /dev/null
+++
b/components/camel-stripe/src/test/java/org/apache/camel/component/stripe/StripeProducerRequestTest.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.stripe;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.stripe.model.Charge;
+import com.stripe.model.Customer;
+import com.stripe.model.PaymentIntent;
+import com.stripe.model.Subscription;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+/**
+ * Tests the requests the producer sends, against a local HTTP server as the
Stripe API.
+ */
+public class StripeProducerRequestTest {
+
+ private final List<String> requests = new CopyOnWriteArrayList<>();
+ private final List<String> requestBodies = new CopyOnWriteArrayList<>();
+ private HttpServer server;
+ private CamelContext context;
+ private ProducerTemplate template;
+ private String apiBase;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ server = HttpServer.create(new InetSocketAddress(0), 0);
+ server.createContext("/v1/", exchange -> {
+ String path = exchange.getRequestURI().getPath();
+ requests.add(exchange.getRequestMethod() + " " + path);
+ try (InputStream is = exchange.getRequestBody()) {
+ requestBodies.add(URLDecoder.decode(new
String(is.readAllBytes(), StandardCharsets.UTF_8),
+ StandardCharsets.UTF_8));
+ }
+ byte[] bytes = responseFor(path).getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().set("Content-Type",
"application/json");
+ exchange.sendResponseHeaders(200, bytes.length);
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(bytes);
+ }
+ });
+ server.start();
+ apiBase = "http://localhost:" + server.getAddress().getPort();
+
+ context = new DefaultCamelContext();
+ context.start();
+ template = context.createProducerTemplate();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (context != null) {
+ context.stop();
+ }
+ if (server != null) {
+ server.stop(0);
+ }
+ }
+
+ private static String responseFor(String path) {
+ String[] parts = path.split("/");
+ String id = parts.length > 3 ? parts[3] : "cus_new";
+ String object = switch (parts[2]) {
+ case "charges" -> "charge";
+ case "payment_intents" -> "payment_intent";
+ case "subscriptions" -> "subscription";
+ default -> "customer";
+ };
+ return "{\"id\":\"" + id + "\",\"object\":\"" + object + "\"}";
+ }
+
+ private String uri(String operation) {
+ return "stripe:" + operation + "?apiKey=sk_test_123&apiBase=" +
apiBase;
+ }
+
+ @Test
+ public void testCreateFromJsonBytes() {
+ byte[] body = "{\"name\":\"Jane
Doe\",\"email\":\"[email protected]\"}".getBytes(StandardCharsets.UTF_8);
+
+ Object result = template.requestBody(uri("customers"), body);
+
+ assertInstanceOf(Customer.class, result);
+ assertEquals(List.of("POST /v1/customers"), requests);
+ assertEquals("name=Jane Doe&[email protected]",
requestBodies.get(0));
+ }
+
+ @Test
+ public void testCreateFromJsonStream() {
+ InputStream body = new ByteArrayInputStream(
+ "{\"name\":\"Jane
Doe\",\"email\":\"[email protected]\"}".getBytes(StandardCharsets.UTF_8));
+
+ Object result = template.requestBody(uri("customers"), body);
+
+ assertInstanceOf(Customer.class, result);
+ assertEquals(List.of("POST /v1/customers"), requests);
+ assertEquals("name=Jane Doe&[email protected]",
requestBodies.get(0));
+ }
+
+ @Test
+ public void testOperationHeaderOverridesEndpoint() {
+ Object result = template.requestBodyAndHeaders(uri("charges"), null,
+ Map.of(StripeConstants.OPERATION_HEADER, "customers",
+ StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_RETRIEVE,
+ StripeConstants.OBJECT_ID, "cus_123"));
+
+ assertInstanceOf(Customer.class, result);
+ assertEquals(List.of("GET /v1/customers/cus_123"), requests);
+ }
+
+ @Test
+ public void testOperationHeaderAsEnum() {
+ Object result = template.requestBodyAndHeaders(uri("charges"), null,
+ Map.of(StripeConstants.OPERATION_HEADER,
StripeOperation.PAYMENT_INTENTS,
+ StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_RETRIEVE,
+ StripeConstants.OBJECT_ID, "pi_123"));
+
+ assertInstanceOf(PaymentIntent.class, result);
+ assertEquals(List.of("GET /v1/payment_intents/pi_123"), requests);
+ }
+
+ @Test
+ public void testUpdateSendsOneRequest() {
+ Object result = template.requestBodyAndHeaders(uri("customers"),
Map.of("name", "John Doe"),
+ Map.of(StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_UPDATE,
+ StripeConstants.OBJECT_ID, "cus_123"));
+
+ assertInstanceOf(Customer.class, result);
+ assertEquals("cus_123", ((Customer) result).getId());
+ assertEquals(List.of("POST /v1/customers/cus_123"), requests);
+ assertEquals("name=John Doe", requestBodies.get(0));
+ }
+
+ @Test
+ public void testDeleteSendsOneRequest() {
+ Object result = template.requestBodyAndHeaders(uri("customers"), null,
+ Map.of(StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_DELETE,
+ StripeConstants.OBJECT_ID, "cus_123"));
+
+ assertInstanceOf(Customer.class, result);
+ assertEquals(List.of("DELETE /v1/customers/cus_123"), requests);
+ }
+
+ @Test
+ public void testCaptureSendsOneRequest() {
+ Object result = template.requestBodyAndHeaders(uri("charges"), null,
+ Map.of(StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_CAPTURE,
+ StripeConstants.OBJECT_ID, "ch_123"));
+
+ assertInstanceOf(Charge.class, result);
+ assertEquals(List.of("POST /v1/charges/ch_123/capture"), requests);
+ }
+
+ @Test
+ public void testCancelSendsOneRequest() {
+ Object result = template.requestBodyAndHeaders(uri("paymentIntents"),
null,
+ Map.of(StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_CANCEL,
+ StripeConstants.OBJECT_ID, "pi_123"));
+ assertInstanceOf(PaymentIntent.class, result);
+
+ result = template.requestBodyAndHeaders(uri("subscriptions"), null,
+ Map.of(StripeConstants.METHOD_HEADER,
StripeConstants.METHOD_CANCEL,
+ StripeConstants.OBJECT_ID, "sub_123"));
+ assertInstanceOf(Subscription.class, result);
+
+ assertEquals(List.of("POST /v1/payment_intents/pi_123/cancel", "DELETE
/v1/subscriptions/sub_123"), requests);
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 5c3eff78d434..c6c2feb52962 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -624,6 +624,12 @@ closed that client for every other endpoint.
Camel now closes only the clients it created. A client set with
`amazonKinesisClient` or `amazonKinesisAsyncClient`, on
the component or on an endpoint, is no longer closed when Camel stops: close
it yourself when you no longer need it.
+=== camel-stripe - the CamelStripeOperation header chooses the operation
+
+The `CamelStripeOperation` header now overrides the operation of the endpoint,
as documented. Before, the header was
+ignored and the operation of the endpoint was always used. Remove the header
from a message that should use the
+operation of the endpoint.
+
=== camel-telemetry - the span of an asynchronous step ends when the step is
done
This applies to `camel-opentelemetry2`, `camel-micrometer-observability` and
`camel-telemetry-dev`. The span of a step
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StripeEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StripeEndpointBuilderFactory.java
index 92dcd89ff4dc..bd9833544718 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StripeEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StripeEndpointBuilderFactory.java
@@ -205,7 +205,8 @@ public interface StripeEndpointBuilderFactory {
public static final StripeHeaderNameBuilder INSTANCE = new
StripeHeaderNameBuilder();
/**
- * The operation to perform.
+ * The operation to perform, such as customers or paymentIntents.
+ * Overrides the operation of the endpoint.
*
* The option is a: {@code String} type.
*