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.
          * 

Reply via email to