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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
package com.stablecoin.payments.onramp.config;

import com.stablecoin.payments.onramp.domain.port.PspGateway;
import com.stablecoin.payments.onramp.domain.port.PspPaymentResult;
import com.stablecoin.payments.onramp.domain.port.PspRefundResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.UUID;

/**
* Provides fallback (dev/test) implementations of outbound ports
* via {@code @ConditionalOnMissingBean}. Real adapters are registered
* by provider-specific configurations under
* {@code infrastructure/provider/<name>/}.
*
* <p>Adapters will be added here as domain ports are defined.
*/
@Slf4j
@Configuration
public class FallbackAdaptersConfig {

// Fallback beans will be added as domain ports are implemented.
// Pattern: @Bean @ConditionalOnMissingBean public <Port> fallback<Port>() { ... }
@Bean
@ConditionalOnMissingBean
public PspGateway fallbackPspGateway() {
return new PspGateway() {
@Override
public PspPaymentResult initiatePayment(
com.stablecoin.payments.onramp.domain.port.PspPaymentRequest request) {
log.warn("[FALLBACK-PSP] Using dev PSP gateway for payment collectionId={}",
request.collectionId());
return new PspPaymentResult("dev-pi-" + UUID.randomUUID(), "succeeded");
}

@Override
public PspRefundResult initiateRefund(
com.stablecoin.payments.onramp.domain.port.PspRefundRequest request) {
log.warn("[FALLBACK-PSP] Using dev PSP gateway for refund collectionId={}",
request.collectionId());
return new PspRefundResult("dev-re-" + UUID.randomUUID(), "succeeded");
}
};
Comment on lines +26 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use imported types instead of fully-qualified class names.

PspPaymentRequest and PspRefundRequest should be imported at the top of the file rather than using FQCNs inline.

Proposed fix

Add to imports:

import com.stablecoin.payments.onramp.domain.port.PspPaymentRequest;
import com.stablecoin.payments.onramp.domain.port.PspRefundRequest;

Then update the method signatures:

             `@Override`
-            public PspPaymentResult initiatePayment(
-                    com.stablecoin.payments.onramp.domain.port.PspPaymentRequest request) {
+            public PspPaymentResult initiatePayment(PspPaymentRequest request) {
                 log.warn("[FALLBACK-PSP] Using dev PSP gateway for payment collectionId={}",
                         request.collectionId());
                 return new PspPaymentResult("dev-pi-" + UUID.randomUUID(), "succeeded");
             }

             `@Override`
-            public PspRefundResult initiateRefund(
-                    com.stablecoin.payments.onramp.domain.port.PspRefundRequest request) {
+            public PspRefundResult initiateRefund(PspRefundRequest request) {
                 log.warn("[FALLBACK-PSP] Using dev PSP gateway for refund collectionId={}",
                         request.collectionId());
                 return new PspRefundResult("dev-re-" + UUID.randomUUID(), "succeeded");
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/FallbackAdaptersConfig.java`
around lines 26 - 42, Replace the fully-qualified parameter types in the
anonymous PspGateway implementation with the imported types PspPaymentRequest
and PspRefundRequest: add imports for
com.stablecoin.payments.onramp.domain.port.PspPaymentRequest and
com.stablecoin.payments.onramp.domain.port.PspRefundRequest at the top of the
file, then change the method signatures of initiatePayment(...) and
initiateRefund(...) to use PspPaymentRequest and PspRefundRequest respectively
instead of the FQCNs; keep the rest of the PspGateway implementation unchanged.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.stablecoin.payments.onramp.infrastructure.provider.stripe;

record StripePaymentIntentResponse(
String id,
String status,
Long amount,
String currency,
String clientSecret
) {
}
Comment on lines +3 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing @JsonProperty for client_secret mapping.

Same issue as StripeRefundResponse — Stripe returns client_secret, not clientSecret.

Proposed fix
 package com.stablecoin.payments.onramp.infrastructure.provider.stripe;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+
 record StripePaymentIntentResponse(
         String id,
         String status,
         Long amount,
         String currency,
-        String clientSecret
+        `@JsonProperty`("client_secret") String clientSecret
 ) {
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
record StripePaymentIntentResponse(
String id,
String status,
Long amount,
String currency,
String clientSecret
) {
}
record StripePaymentIntentResponse(
String id,
String status,
Long amount,
String currency,
`@JsonProperty`("client_secret") String clientSecret
) {
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePaymentIntentResponse.java`
around lines 3 - 10, The record StripePaymentIntentResponse maps Stripe's JSON
but misses the client_secret mapping; annotate the clientSecret component with
`@JsonProperty`("client_secret") in the StripePaymentIntentResponse record and add
the necessary import (com.fasterxml.jackson.annotation.JsonProperty) so Jackson
correctly binds Stripe's client_secret to the clientSecret field.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.stablecoin.payments.onramp.infrastructure.provider.stripe;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app.psp.stripe")
public record StripeProperties(String baseUrl, String apiKey, int timeoutSeconds) {

public StripeProperties {
if (baseUrl == null || baseUrl.isBlank()) {
baseUrl = "https://api.stripe.com";
}
if (apiKey == null || apiKey.isBlank()) {
apiKey = "sk_test_default";
}
if (timeoutSeconds <= 0) {
timeoutSeconds = 10;
}
}
Comment on lines +8 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent fallback to default API key may mask production misconfiguration.

Defaulting apiKey to sk_test_default when blank could cause production issues if the environment variable isn't set — requests would silently fail authentication against Stripe.

Consider using @Validated with @NotBlank on apiKey to fail fast, or at minimum log a warning when falling back.

Alternative: fail-fast validation
 package com.stablecoin.payments.onramp.infrastructure.provider.stripe;
 
+import jakarta.validation.constraints.NotBlank;
 import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
 
+@Validated
 `@ConfigurationProperties`(prefix = "app.psp.stripe")
-public record StripeProperties(String baseUrl, String apiKey, int timeoutSeconds) {
+public record StripeProperties(
+        String baseUrl,
+        `@NotBlank`(message = "Stripe API key must be configured") String apiKey,
+        int timeoutSeconds) {
 
     public StripeProperties {
         if (baseUrl == null || baseUrl.isBlank()) {
             baseUrl = "https://api.stripe.com";
         }
-        if (apiKey == null || apiKey.isBlank()) {
-            apiKey = "sk_test_default";
-        }
         if (timeoutSeconds <= 0) {
             timeoutSeconds = 10;
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeProperties.java`
around lines 8 - 18, The StripeProperties constructor silently falls back to a
test API key; change this to fail fast by adding validation: annotate the
properties class with `@Validated` and mark the apiKey field or constructor
parameter with `@NotBlank` (remove the silent default assignment of
"sk_test_default"), so Spring will fail startup when apiKey is missing; if you
prefer a softer change, keep the fallback but emit a warning via the logger in
the StripeProperties constructor when apiKey is null/blank instead of silently
assigning "sk_test_default".

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.stablecoin.payments.onramp.infrastructure.provider.stripe;

import com.stablecoin.payments.onramp.domain.port.PspGateway;
import com.stablecoin.payments.onramp.domain.port.PspPaymentRequest;
import com.stablecoin.payments.onramp.domain.port.PspPaymentResult;
import com.stablecoin.payments.onramp.domain.port.PspRefundRequest;
import com.stablecoin.payments.onramp.domain.port.PspRefundResult;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;

import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.time.Duration;

@Slf4j
@Component
@ConditionalOnProperty(name = "app.psp.provider", havingValue = "stripe")
@EnableConfigurationProperties(StripeProperties.class)
public class StripePspAdapter implements PspGateway {

private final RestClient restClient;

public StripePspAdapter(StripeProperties properties) {
var httpClient = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(properties.timeoutSeconds()))
.build();

var requestFactory = new JdkClientHttpRequestFactory(httpClient);
requestFactory.setReadTimeout(Duration.ofSeconds(properties.timeoutSeconds()));

this.restClient = RestClient.builder()
.baseUrl(properties.baseUrl())
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + properties.apiKey())
.requestFactory(requestFactory)
.build();
}

@Override
@CircuitBreaker(name = "stripe", fallbackMethod = "initiatePaymentFallback")
public PspPaymentResult initiatePayment(PspPaymentRequest request) {
log.info("[STRIPE] Initiating payment collectionId={} amount={} currency={}",
request.collectionId(), request.amount().amount(), request.amount().currency());

MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("amount", toMinorUnits(request));
formData.add("currency", request.amount().currency().toLowerCase());
formData.add("payment_method_types[]", "us_bank_account");
formData.add("confirm", "true");
formData.add("metadata[collection_id]", request.collectionId().toString());

var response = restClient.post()
.uri("/v1/payment_intents")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData)
.retrieve()
.body(StripePaymentIntentResponse.class);

log.info("[STRIPE] Payment initiated collectionId={} pspRef={} status={}",
request.collectionId(), response.id(), response.status());

return new PspPaymentResult(response.id(), response.status());
}
Comment on lines +61 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NPE if response is null.

retrieve().body(...) can return null if the response body is empty. While RestClient typically throws on non-2xx statuses, defensive null handling would be safer.

Defensive approach
         var response = restClient.post()
                 .uri("/v1/payment_intents")
                 .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                 .body(formData)
                 .retrieve()
                 .body(StripePaymentIntentResponse.class);
 
+        if (response == null || response.id() == null) {
+            throw new IllegalStateException("Empty response from Stripe payment intent API");
+        }
+
         log.info("[STRIPE] Payment initiated collectionId={} pspRef={} status={}",
                 request.collectionId(), response.id(), response.status());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var response = restClient.post()
.uri("/v1/payment_intents")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData)
.retrieve()
.body(StripePaymentIntentResponse.class);
log.info("[STRIPE] Payment initiated collectionId={} pspRef={} status={}",
request.collectionId(), response.id(), response.status());
return new PspPaymentResult(response.id(), response.status());
}
var response = restClient.post()
.uri("/v1/payment_intents")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData)
.retrieve()
.body(StripePaymentIntentResponse.class);
if (response == null || response.id() == null) {
throw new IllegalStateException("Empty response from Stripe payment intent API");
}
log.info("[STRIPE] Payment initiated collectionId={} pspRef={} status={}",
request.collectionId(), response.id(), response.status());
return new PspPaymentResult(response.id(), response.status());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java`
around lines 61 - 72, The current StripePspAdapter code assumes the
StripePaymentIntentResponse (response) is non-null after
restClient.post()...retrieve().body(StripePaymentIntentResponse.class); add a
defensive null-check for the response returned by that call (e.g., immediately
after the body(...) call), and handle the null case by logging an error via
log.error with context (collectionId) and either throwing a clear exception or
returning a failure PspPaymentResult, rather than dereferencing
response.id()/response.status(); update the log.info and the returned
PspPaymentResult usage to occur only when response != null.


@Override
@CircuitBreaker(name = "stripe", fallbackMethod = "initiateRefundFallback")
public PspRefundResult initiateRefund(PspRefundRequest request) {
log.info("[STRIPE] Initiating refund collectionId={} pspRef={} amount={}",
request.collectionId(), request.pspReference(), request.refundAmount().amount());

MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("payment_intent", request.pspReference());
formData.add("amount", toMinorUnitsFromRefund(request));
formData.add("reason", "requested_by_customer");
formData.add("metadata[collection_id]", request.collectionId().toString());
Comment on lines +80 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Refund reason from request is ignored.

PspRefundRequest includes a reason field (passed as "customer_request" in tests), but it's hardcoded to "requested_by_customer" here. Consider mapping the request's reason to Stripe's valid enum values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java`
around lines 80 - 84, The refund reason is being hardcoded in StripePspAdapter
(formData.add("reason", "requested_by_customer")) instead of using the
PspRefundRequest.reason; update StripePspAdapter to map
PspRefundRequest.reason() to Stripe's accepted values (e.g., "duplicate",
"fraudulent", "requested_by_customer") and pass that mapped value into the
formData "reason" field; validate/normalize the incoming reason (or use a safe
default like "requested_by_customer") and keep other fields (payment_intent,
amount via toMinorUnitsFromRefund, metadata[collection_id]) unchanged.


var response = restClient.post()
.uri("/v1/refunds")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData)
.retrieve()
.body(StripeRefundResponse.class);

log.info("[STRIPE] Refund initiated collectionId={} refundRef={} status={}",
request.collectionId(), response.id(), response.status());

return new PspRefundResult(response.id(), response.status());
}

@SuppressWarnings("unused")
private PspPaymentResult initiatePaymentFallback(PspPaymentRequest request, Exception ex) {
log.error("[STRIPE] Circuit breaker open — payment initiation failed collectionId={}",
request.collectionId(), ex);
throw new IllegalStateException("Stripe payment initiation unavailable", ex);
}

@SuppressWarnings("unused")
private PspRefundResult initiateRefundFallback(PspRefundRequest request, Exception ex) {
log.error("[STRIPE] Circuit breaker open — refund initiation failed collectionId={}",
request.collectionId(), ex);
throw new IllegalStateException("Stripe refund initiation unavailable", ex);
}

private String toMinorUnits(PspPaymentRequest request) {
return String.valueOf(request.amount().amount().movePointRight(2).longValueExact());
}

private String toMinorUnitsFromRefund(PspRefundRequest request) {
return String.valueOf(request.refundAmount().amount().movePointRight(2).longValueExact());
}
Comment on lines +113 to +119

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Stripe API zero decimal currencies list

💡 Result:

Stripe “zero-decimal currencies” (currencies where you don’t multiply by 100—the amount is already in the smallest unit) are: BIF, CLP, DJF, GNF, JPY, KMF, KRW, MGA, PYG, RWF, UGX, VND, VUV, XAF, XOF, XPF. [1][2][3]

Source references: Stripe’s general rule for zero-decimal currencies is documented in their “Supported currencies” guide (even if the list rendering can vary by page/view). [1]
Notes: Stripe also documents some special cases (for example ISK and UGX backward-compatibility behavior, and payout handling for HUF/TWD). [1]

References:
[1] https://docs.stripe.com/currencies
[2] stripe/stripe-java#874
[3] https://growpanel.io/docs/integrations/stripe/


🏁 Script executed:

cat -n fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java | head -150

Repository: Puneethkumarck/stablebridge-platform

Length of output: 6491


🏁 Script executed:

find . -name "*.java" -type f | xargs grep -l "interface PspRefundRequest\|class PspRefundRequest" 2>/dev/null | head -5

Repository: Puneethkumarck/stablebridge-platform

Length of output: 62


🏁 Script executed:

rg "PspRefundRequest" --type java -A 10 | head -50

Repository: Puneethkumarck/stablebridge-platform

Length of output: 7001


🏁 Script executed:

rg "class Money|record Money" --type java -A 5 | head -30

Repository: Puneethkumarck/stablebridge-platform

Length of output: 3926


Minor units conversion fails for zero-decimal and special-decimal currencies.

movePointRight(2) hardcodes a 2-decimal assumption incompatible with Stripe's payment calculation rules. Zero-decimal currencies (JPY, KRW, BIF, CLP, DJF, GNF, KMF, MGA, PYG, RWF, UGX, VND, VUV, XAF, XOF, XPF) require no multiplication; others may differ. Currency data is available via request.amount().currency() and request.refundAmount().currency(), so apply appropriate conversion per currency.

Both toMinorUnits() and toMinorUnitsFromRefund() require fixing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java`
around lines 113 - 119, The conversion to minor units in
toMinorUnits(PspPaymentRequest) and toMinorUnitsFromRefund(PspRefundRequest)
incorrectly hardcodes movePointRight(2); instead, read the currency via
request.amount().currency() and request.refundAmount().currency(), determine the
correct exponent for that currency (0 for Stripe zero-decimal currencies like
JPY, KRW, etc., otherwise typically 2, or consult a currency-to-decimal map),
then multiply by 10^exponent (e.g. movePointRight(exponent)) and call
longValueExact() to produce the minor-unit string; implement a small helper or
map inside StripePspAdapter to return the exponent for a given currency code and
reuse it from both methods.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.stablecoin.payments.onramp.infrastructure.provider.stripe;

record StripeRefundResponse(
String id,
String status,
Long amount,
String currency,
String paymentIntent
) {
}
Comment on lines +3 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Spring RestClient use Jackson for JSON deserialization by default and does Jackson support snake_case to camelCase mapping automatically?

💡 Result:

  • Yes (when Jackson is on the classpath): Spring’s RestClient uses Spring MVC HTTP message converters, which are registered by default, including a Jackson-based JSON converter that reads/writes JSON using Jackson and requires jackson-databind. (docs.spring.io)

    • (In Spring Framework 6.x this is typically MappingJackson2HttpMessageConverter; in Spring Framework 7.x it’s JacksonJsonHttpMessageConverter.) (docs.spring.io)
  • No, not automatically: Jackson’s default property naming leaves Java property names essentially “as-is” (i.e., it does not auto-map snake_case JSON names to camelCase Java properties unless configured). (fasterxml.github.io)

    • To map snake_casecamelCase, configure a naming strategy such as PropertyNamingStrategies.SnakeCaseStrategy / SNAKE_CASE (or use annotations like @JsonProperty, @JsonNaming). (fasterxml.github.io)

Citations:


🏁 Script executed:

#!/bin/bash

# Search for Jackson configuration classes
find . -type f -name "*.java" | xargs grep -l "PropertyNamingStrategy\|PropertyNamingStrategies\|@JsonNaming" 2>/dev/null | head -20

# Look for Spring configuration files
find . -type f -name "*.java" | xargs grep -l "ObjectMapper\|RestClient\|HttpMessageConverter" 2>/dev/null | grep -i config | head -10

# Search for any Jackson related bean configurations
rg -t java "ObjectMapper|@Configuration|@Bean" -A 3 | grep -A 3 "ObjectMapper" | head -30

Repository: Puneethkumarck/stablebridge-platform

Length of output: 4290


🏁 Script executed:

#!/bin/bash

# Check if there's any global naming strategy configuration in application properties
find . -type f \( -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" \) -exec cat {} \; 2>/dev/null | grep -i "jackson\|naming"

# Look for any test files that show how StripeRefundResponse is deserialized
find . -path "*/test*" -name "*.java" | xargs grep -l "StripeRefundResponse" 2>/dev/null

Repository: Puneethkumarck/stablebridge-platform

Length of output: 64


🏁 Script executed:

#!/bin/bash

# Find StripeRefundResponse usage in the codebase
find . -type f -name "*.java" | xargs grep -l "StripeRefundResponse" 2>/dev/null

# Check the actual imports in StripeRefundResponse.java
find . -path "*stripe*" -name "StripeRefundResponse.java" -exec head -20 {} \;

# Look for Stripe client configuration
find . -type f -name "*.java" | xargs grep -l "Stripe\|stripe" 2>/dev/null | grep -i config | head -5

# Search for any Stripe API response mapping examples
find . -type f -name "*.java" | xargs grep -B 3 -A 3 "payment_intent\|paymentIntent" 2>/dev/null | head -40

Repository: Puneethkumarck/stablebridge-platform

Length of output: 7360


🏁 Script executed:

#!/bin/bash

# Check for Jackson dependencies in pom.xml or build.gradle files
find . -name "pom.xml" -o -name "build.gradle" | xargs grep -i "jackson" 2>/dev/null | head -20

# Look for any RestClient usage with StripeRefundResponse
rg -t java "StripeRefundResponse" -B 5 -A 5 | head -50

Repository: Puneethkumarck/stablebridge-platform

Length of output: 3176


🏁 Script executed:

#!/bin/bash

# Check StripePspAdapter.java to see the full refund method and how RestClient is configured
find . -path "*stripe*" -name "StripePspAdapter.java" -exec cat {} \;

# Look for any tests that verify StripeRefundResponse deserialization
find . -path "*/test*" -name "*.java" | xargs grep -l "StripeRefundResponse\|StripePspAdapter" 2>/dev/null | head -5

Repository: Puneethkumarck/stablebridge-platform

Length of output: 5781


🏁 Script executed:

#!/bin/bash

# Check if there are any JSON test fixtures or mocked Stripe responses
find . -name "*.json" | xargs grep -l "payment_intent\|refund" 2>/dev/null | head -10

# Look for RestClient configuration or ObjectMapper bean definitions
rg -t java "@Bean|ObjectMapper|RestClient" -B 2 -A 5 --max-count=50 | head -80

Repository: Puneethkumarck/stablebridge-platform

Length of output: 13392


🏁 Script executed:

#!/bin/bash

# Check the test file for StripeRefundResponse to see if deserialization is tested
cat ./fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java

# Look for WireMock stripe mappings and response files to see actual API response format
find . -path "*wiremock*" -name "*stripe*.json" -exec echo "=== {} ===" \; -exec cat {} \;

Repository: Puneethkumarck/stablebridge-platform

Length of output: 11589


🏁 Script executed:

#!/bin/bash

# Check if there's a parent Spring Boot config or application properties file
find . -name "application*.properties" -o -name "application*.yml" | head -10 | xargs grep -l "jackson\|objectMapper" 2>/dev/null

# Look for any RestClient or Jackson configuration in the fiat-on-ramp module
find ./fiat-on-ramp -name "*.java" | xargs grep -l "ObjectMapper\|@Configuration" 2>/dev/null | head -5

Repository: Puneethkumarck/stablebridge-platform

Length of output: 513


🏁 Script executed:

#!/bin/bash

# Look at the full StripePaymentIntentResponse to see if it also has this issue
find . -path "*stripe*" -name "StripePaymentIntentResponse.java" -exec cat {} \;

# Check if there's any global Jackson configuration in Spring Boot auto-config
find ./fiat-on-ramp -name "*.java" | xargs grep -B 5 -A 5 "objectMapper\|Jackson\|PropertyNamingStrategy" 2>/dev/null | head -40

Repository: Puneethkumarck/stablebridge-platform

Length of output: 309


🏁 Script executed:

#!/bin/bash

# Verify that the test actually expects the paymentIntent field to be deserialized
# by checking if the test asserts on it
grep -A 10 -B 5 "paymentIntent\|payment_intent" ./fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java

# Check the StripePaymentIntentResponse also
find . -type f -name "*.java" -path "*stripe*" | xargs grep -l "StripePaymentIntentResponse" | head -1 | xargs cat

Repository: Puneethkumarck/stablebridge-platform

Length of output: 6717


Add @JsonProperty annotations for snake_case JSON fields in both Stripe response records.

Stripe API returns payment_intent and client_secret (snake_case), but StripeRefundResponse and StripePaymentIntentResponse use camelCase field names. Without explicit mapping, Jackson silently leaves these fields null, creating a silent failure that tests won't catch (they only assert on id and status).

Proposed fix
 package com.stablecoin.payments.onramp.infrastructure.provider.stripe;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+
 record StripeRefundResponse(
         String id,
         String status,
         Long amount,
         String currency,
-        String paymentIntent
+        `@JsonProperty`("payment_intent") String paymentIntent
 ) {
 }
 package com.stablecoin.payments.onramp.infrastructure.provider.stripe;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+
 record StripePaymentIntentResponse(
         String id,
         String status,
         Long amount,
         String currency,
-        String clientSecret
+        `@JsonProperty`("client_secret") String clientSecret
 ) {
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
record StripeRefundResponse(
String id,
String status,
Long amount,
String currency,
String paymentIntent
) {
}
record StripeRefundResponse(
String id,
String status,
Long amount,
String currency,
`@JsonProperty`("payment_intent") String paymentIntent
) {
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeRefundResponse.java`
around lines 3 - 10, Stripe API returns snake_case fields but the record fields
are camelCase, so annotate the record components with `@JsonProperty` to map JSON
names to Java names: add import com.fasterxml.jackson.annotation.JsonProperty
and annotate StripeRefundResponse(String id, String status, Long amount, String
currency, `@JsonProperty`("payment_intent") String paymentIntent) and similarly
update StripePaymentIntentResponse to annotate the clientSecret component with
`@JsonProperty`("client_secret"); this ensures Jackson binds payment_intent and
client_secret into paymentIntent and clientSecret respectively.

Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ springdoc:
app:
security:
enabled: false
psp:
provider: stripe
stripe:
base-url: https://api.stripe.com
api-key: ${STRIPE_API_KEY:sk_test_default}
timeout-seconds: 10

logging:
level:
Expand Down
Loading
Loading