diff --git a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/config/IdempotencyKeyFilter.java b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/config/IdempotencyKeyFilter.java
index 0bcd88b8..6f988402 100644
--- a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/config/IdempotencyKeyFilter.java
+++ b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/config/IdempotencyKeyFilter.java
@@ -31,14 +31,6 @@
import java.util.HexFormat;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) -- excluding auth, JWKS, and actuator endpoints.
- * Uses INSERT-first reservation pattern to prevent TOCTOU races:
- * 1. Try INSERT with status_code=0 (reservation)
- * 2. If INSERT succeeds, proceed with request and UPDATE with real response
- * 3. If INSERT fails (duplicate), re-read stored record for replay or conflict
- */
@Slf4j
@Component
@Order(2)
@@ -242,10 +234,6 @@ private String computeSha256(byte[] body) {
record IdempotencyRecord(String idempotencyKey, String requestHash, String responseBody, int statusCode) {}
- /**
- * Request wrapper that replays a cached body so downstream filters and
- * controllers can read the request body after it has already been consumed.
- */
private static class CachedBodyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
diff --git a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/MerchantScopeEnforcer.java b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/MerchantScopeEnforcer.java
index 2b0a9727..95b146f2 100644
--- a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/MerchantScopeEnforcer.java
+++ b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/MerchantScopeEnforcer.java
@@ -12,11 +12,6 @@
import java.util.UUID;
-/**
- * Extracts the authenticated principal's merchant ID from the SecurityContext
- * and enforces that it matches the target merchant ID.
- * Used via {@code @PreAuthorize("@merchantScopeEnforcer.hasAccess(#merchantId)")}.
- */
@Component
@RequiredArgsConstructor
public class MerchantScopeEnforcer {
@@ -24,12 +19,6 @@ public class MerchantScopeEnforcer {
private final ApiKeyRepository apiKeyRepository;
private final AccessTokenRepository accessTokenRepository;
- /**
- * Checks whether the authenticated principal has access to the given merchant.
- *
- * @return true if the principal's merchant ID matches the target
- * @throws MerchantAccessDeniedException if no merchant-scoped authentication is present or IDs don't match
- */
public boolean hasAccess(UUID targetMerchantId) {
var principalMerchantId = authenticatedMerchantId();
if (!principalMerchantId.equals(targetMerchantId)) {
@@ -38,37 +27,18 @@ public boolean hasAccess(UUID targetMerchantId) {
return true;
}
- /**
- * Checks whether the authenticated principal owns the API key.
- *
- * @return true if the key's merchant ID matches the principal's
- * @throws ApiKeyNotFoundException if the key does not exist
- * @throws MerchantAccessDeniedException if the principal does not own the key
- */
public boolean hasAccessToApiKey(UUID keyId) {
var apiKey = apiKeyRepository.findById(keyId)
.orElseThrow(() -> ApiKeyNotFoundException.byId(keyId));
return hasAccess(apiKey.getMerchantId());
}
- /**
- * Checks whether the authenticated principal owns the access token.
- *
- * @return true if the token's merchant ID matches the principal's
- * @throws TokenRevokedException if the token does not exist
- * @throws MerchantAccessDeniedException if the principal does not own the token
- */
public boolean hasAccessToToken(UUID jti) {
var token = accessTokenRepository.findByJti(jti)
.orElseThrow(() -> TokenRevokedException.of(jti));
return hasAccess(token.getMerchantId());
}
- /**
- * Returns the authenticated principal's merchant ID.
- *
- * @throws MerchantAccessDeniedException if no merchant-scoped authentication is present
- */
public UUID authenticatedMerchantId() {
var auth = SecurityContextHolder.getContext().getAuthentication();
return extractMerchantId(auth);
diff --git a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserAuthentication.java b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserAuthentication.java
index 6486b563..f63acdc8 100644
--- a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserAuthentication.java
+++ b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserAuthentication.java
@@ -7,10 +7,6 @@
import java.util.Objects;
import java.util.UUID;
-/**
- * Spring Security principal for S13-authenticated users accessing S10 gateway.
- * Distinct from {@link MerchantAuthentication} which represents merchant/client-level auth.
- */
public class UserAuthentication extends AbstractAuthenticationToken {
private final UUID userId;
diff --git a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilter.java b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilter.java
index 0da1d4dc..c990b37f 100644
--- a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilter.java
+++ b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilter.java
@@ -21,15 +21,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Validates S13-issued user JWTs at the S10 gateway.
- * Fetches S13's JWKS to verify ES256 signatures, then extracts user claims
- * into a {@link UserAuthentication} principal.
- *
- *
Runs after the S10 merchant JWT filter. If the token's issuer doesn't match
- * the S13 issuer, this filter passes through (the token may be an S10 merchant JWT
- * that was already handled or will be rejected upstream).
- */
@Slf4j
@RequiredArgsConstructor
public class UserJwtAuthenticationFilter extends OncePerRequestFilter {
diff --git a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain/port/UserJwksProvider.java b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain/port/UserJwksProvider.java
index 12c151b9..d91704a7 100644
--- a/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain/port/UserJwksProvider.java
+++ b/api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain/port/UserJwksProvider.java
@@ -1,17 +1,7 @@
package com.stablecoin.payments.gateway.iam.domain.port;
-import com.stablecoin.payments.gateway.iam.domain.exception.UserJwksUnavailableException;
-/**
- * Outbound port for fetching the JWKS (JSON Web Key Set) from the Merchant IAM service (S13).
- * Infrastructure decides caching strategy and failover behaviour.
- */
public interface UserJwksProvider {
- /**
- * Returns the JWKS JSON from S13.
- *
- * @throws UserJwksUnavailableException if S13 is unreachable and no cached value is available
- */
String fetchJwks();
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/ApiKeyAuthenticationFilterTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/ApiKeyAuthenticationFilterTest.java
index 5bb7199e..66682d3d 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/ApiKeyAuthenticationFilterTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/ApiKeyAuthenticationFilterTest.java
@@ -26,10 +26,9 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ApiKeyAuthenticationFilterTest {
@@ -64,7 +63,7 @@ class WhenNoApiKeyHeader {
void shouldPassThroughWithoutHeader() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@@ -74,8 +73,8 @@ void shouldPassThroughWithBlankHeader() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(apiKeyService, never()).validate(anyString(), anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(apiKeyService).shouldHaveNoInteractions();
}
}
@@ -106,11 +105,11 @@ void shouldAuthenticateWithValidKey() throws ServletException, IOException {
.version(0)
.build();
- when(apiKeyService.validate(rawKey, "10.0.0.1")).thenReturn(apiKey);
+ given(apiKeyService.validate(rawKey, "10.0.0.1")).willReturn(apiKey);
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
var auth = SecurityContextHolder.getContext().getAuthentication();
assertThat(auth).isInstanceOf(MerchantAuthentication.class);
var merchantAuth = (MerchantAuthentication) auth;
@@ -127,12 +126,12 @@ class WhenInvalidApiKey {
@Test
void shouldRejectNotFoundKey() throws ServletException, IOException {
request.addHeader("X-API-Key", "invalid_key");
- when(apiKeyService.validate("invalid_key", "127.0.0.1"))
- .thenThrow(ApiKeyNotFoundException.byHash());
+ given(apiKeyService.validate("invalid_key", "127.0.0.1"))
+ .willThrow(ApiKeyNotFoundException.byHash());
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -140,12 +139,12 @@ void shouldRejectNotFoundKey() throws ServletException, IOException {
void shouldRejectRevokedKey() throws ServletException, IOException {
var keyId = UUID.randomUUID();
request.addHeader("X-API-Key", "revoked_key");
- when(apiKeyService.validate("revoked_key", "127.0.0.1"))
- .thenThrow(ApiKeyRevokedException.of(keyId));
+ given(apiKeyService.validate("revoked_key", "127.0.0.1"))
+ .willThrow(ApiKeyRevokedException.of(keyId));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -153,12 +152,12 @@ void shouldRejectRevokedKey() throws ServletException, IOException {
void shouldRejectExpiredKey() throws ServletException, IOException {
var keyId = UUID.randomUUID();
request.addHeader("X-API-Key", "expired_key");
- when(apiKeyService.validate("expired_key", "127.0.0.1"))
- .thenThrow(ApiKeyExpiredException.of(keyId));
+ given(apiKeyService.validate("expired_key", "127.0.0.1"))
+ .willThrow(ApiKeyExpiredException.of(keyId));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -166,12 +165,12 @@ void shouldRejectExpiredKey() throws ServletException, IOException {
void shouldRejectDisallowedIp() throws ServletException, IOException {
request.addHeader("X-API-Key", "valid_key");
request.setRemoteAddr("192.168.1.1");
- when(apiKeyService.validate("valid_key", "192.168.1.1"))
- .thenThrow(IpNotAllowedException.of("192.168.1.1"));
+ given(apiKeyService.validate("valid_key", "192.168.1.1"))
+ .willThrow(IpNotAllowedException.of("192.168.1.1"));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
}
@@ -185,7 +184,7 @@ void shouldSkipWhenAlreadyAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(apiKeyService, never()).validate(anyString(), anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(apiKeyService).shouldHaveNoInteractions();
}
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/AuditLogFilterTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/AuditLogFilterTest.java
index 62b05e91..64d3aa3d 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/AuditLogFilterTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/AuditLogFilterTest.java
@@ -23,9 +23,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doThrow;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class AuditLogFilterTest {
@@ -58,14 +58,14 @@ void tearDown() {
void shouldAlwaysCallFilterChain() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
}
@Test
void shouldSkipAuditWhenNotAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(auditLogRepository, never()).save(any());
+ then(auditLogRepository).should(never()).save(any());
}
@Nested
@@ -88,7 +88,7 @@ void shouldPersistAuditLogEntry() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
var captor = ArgumentCaptor.forClass(AuditLogEntry.class);
- verify(auditLogRepository).save(captor.capture());
+ then(auditLogRepository).should().save(captor.capture());
var entry = captor.getValue();
assertThat(entry.getMerchantId()).isEqualTo(merchantId);
@@ -110,30 +110,30 @@ void shouldRecordJwtAuthMethod() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
var captor = ArgumentCaptor.forClass(AuditLogEntry.class);
- verify(auditLogRepository).save(captor.capture());
+ then(auditLogRepository).should().save(captor.capture());
assertThat(captor.getValue().getDetail()).containsEntry("auth_method", "JWT");
}
@Test
void shouldNotFailWhenRepositoryThrows() throws ServletException, IOException {
- doThrow(new RuntimeException("DB down"))
- .when(auditLogRepository).save(any());
+ willThrow(new RuntimeException("DB down"))
+ .given(auditLogRepository).save(any());
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
// No exception propagated — filter swallows it
}
@Test
void shouldAuditEvenWhenFilterChainThrows() throws ServletException, IOException {
- doThrow(new ServletException("downstream error"))
- .when(filterChain).doFilter(request, response);
+ willThrow(new ServletException("downstream error"))
+ .given(filterChain).doFilter(request, response);
assertThatThrownBy(() -> filter.doFilterInternal(request, response, filterChain))
.isInstanceOf(ServletException.class);
- verify(auditLogRepository).save(any());
+ then(auditLogRepository).should().save(any());
}
}
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/JwtAuthenticationFilterTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/JwtAuthenticationFilterTest.java
index 79bb5b17..a1ee019f 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/JwtAuthenticationFilterTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/JwtAuthenticationFilterTest.java
@@ -20,10 +20,9 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class JwtAuthenticationFilterTest {
@@ -61,7 +60,7 @@ class WhenNoBearerToken {
void shouldPassThroughWithoutAuthHeader() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@@ -71,8 +70,8 @@ void shouldPassThroughWithNonBearerAuthHeader() throws ServletException, IOExcep
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(tokenIssuer, never()).parseAndVerify(anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(tokenIssuer).shouldHaveNoInteractions();
}
}
@@ -88,13 +87,13 @@ void shouldAuthenticateWithValidJwt() throws ServletException, IOException {
var token = "valid.jwt.token";
request.addHeader("Authorization", "Bearer " + token);
- when(tokenIssuer.parseAndVerify(token)).thenReturn(
+ given(tokenIssuer.parseAndVerify(token)).willReturn(
new TokenIssuer.ParsedToken(jti, merchantId, clientId, List.of("payments:read"), 9999999999L));
- when(tokenRevocationCache.isRevoked(jti)).thenReturn(false);
+ given(tokenRevocationCache.isRevoked(jti)).willReturn(false);
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
var auth = SecurityContextHolder.getContext().getAuthentication();
assertThat(auth).isInstanceOf(MerchantAuthentication.class);
var merchantAuth = (MerchantAuthentication) auth;
@@ -109,13 +108,13 @@ void shouldRejectRevokedToken() throws ServletException, IOException {
var token = "revoked.jwt.token";
request.addHeader("Authorization", "Bearer " + token);
- when(tokenIssuer.parseAndVerify(token)).thenReturn(
+ given(tokenIssuer.parseAndVerify(token)).willReturn(
new TokenIssuer.ParsedToken(jti, merchantId, clientId, List.of(), 9999999999L));
- when(tokenRevocationCache.isRevoked(jti)).thenReturn(true);
+ given(tokenRevocationCache.isRevoked(jti)).willReturn(true);
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getContentAsString()).contains("revoked");
}
@@ -127,24 +126,24 @@ class WhenInvalidToken {
@Test
void shouldRejectExpiredToken() throws ServletException, IOException {
request.addHeader("Authorization", "Bearer expired.jwt.token");
- when(tokenIssuer.parseAndVerify("expired.jwt.token"))
- .thenThrow(new IllegalArgumentException("JWT has expired"));
+ given(tokenIssuer.parseAndVerify("expired.jwt.token"))
+ .willThrow(new IllegalArgumentException("JWT has expired"));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@Test
void shouldRejectMalformedToken() throws ServletException, IOException {
request.addHeader("Authorization", "Bearer not-a-jwt");
- when(tokenIssuer.parseAndVerify("not-a-jwt"))
- .thenThrow(new IllegalArgumentException("Invalid JWT"));
+ given(tokenIssuer.parseAndVerify("not-a-jwt"))
+ .willThrow(new IllegalArgumentException("Invalid JWT"));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
}
@@ -158,7 +157,7 @@ void shouldSkipWhenAlreadyAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(tokenIssuer, never()).parseAndVerify(anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(tokenIssuer).shouldHaveNoInteractions();
}
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/RateLimitFilterTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/RateLimitFilterTest.java
index 686003c0..11b642e2 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/RateLimitFilterTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/RateLimitFilterTest.java
@@ -2,6 +2,7 @@
import com.stablecoin.payments.gateway.iam.domain.model.Merchant;
import com.stablecoin.payments.gateway.iam.domain.model.MerchantStatus;
+import com.stablecoin.payments.gateway.iam.domain.model.RateLimitPolicy;
import com.stablecoin.payments.gateway.iam.domain.model.RateLimitTier;
import com.stablecoin.payments.gateway.iam.domain.port.MerchantRepository;
import com.stablecoin.payments.gateway.iam.domain.port.RateLimitEventRepository;
@@ -28,10 +29,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RateLimitFilterTest {
@@ -93,18 +93,18 @@ private void setAuthentication() {
void shouldPassThroughWhenNotAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(rateLimiter, never()).check(any(), any(), any());
+ then(filterChain).should().doFilter(request, response);
+ then(rateLimiter).shouldHaveNoInteractions();
}
@Test
void shouldRejectWhenMerchantNotFound() throws ServletException, IOException {
setAuthentication();
- when(merchantRepository.findById(merchantId)).thenReturn(Optional.empty());
+ given(merchantRepository.findById(merchantId)).willReturn(Optional.empty());
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getContentAsString()).contains("GW-1001");
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
@@ -113,13 +113,13 @@ void shouldRejectWhenMerchantNotFound() throws ServletException, IOException {
@Test
void shouldSkipRateLimitingForUnlimitedTier() throws ServletException, IOException {
setAuthentication();
- when(merchantRepository.findById(merchantId))
- .thenReturn(Optional.of(buildMerchant(RateLimitTier.UNLIMITED)));
+ given(merchantRepository.findById(merchantId))
+ .willReturn(Optional.of(buildMerchant(RateLimitTier.UNLIMITED)));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(rateLimiter, never()).check(any(), any(), any());
+ then(filterChain).should().doFilter(request, response);
+ then(rateLimiter).shouldHaveNoInteractions();
}
@Nested
@@ -152,13 +152,15 @@ class WhenWithinLimits {
void shouldAllowRequestAndSetHeaders() throws ServletException, IOException {
setAuthentication();
var merchant = buildMerchant(RateLimitTier.STARTER);
- when(merchantRepository.findById(merchantId)).thenReturn(Optional.of(merchant));
- when(rateLimiter.check(eq(merchantId), any(), any()))
- .thenReturn(new RateLimitResult(true, 5, 60, "1m", 0));
+ var endpoint = "GET /v1/payments";
+ var policy = new RateLimitPolicy(RateLimitTier.STARTER);
+ given(merchantRepository.findById(merchantId)).willReturn(Optional.of(merchant));
+ given(rateLimiter.check(merchantId, endpoint, policy))
+ .willReturn(new RateLimitResult(true, 5, 60, "1m", 0));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(response.getHeader("X-RateLimit-Limit")).isEqualTo("60");
assertThat(response.getHeader("X-RateLimit-Remaining")).isEqualTo("55");
}
@@ -171,13 +173,15 @@ class WhenExceedingLimits {
void shouldRejectWithMinuteBreachAndRetryAfter() throws ServletException, IOException {
setAuthentication();
var merchant = buildMerchant(RateLimitTier.STARTER);
- when(merchantRepository.findById(merchantId)).thenReturn(Optional.of(merchant));
- when(rateLimiter.check(eq(merchantId), any(), any()))
- .thenReturn(new RateLimitResult(false, 61, 60, "1m", 60));
+ var endpoint = "GET /v1/payments";
+ var policy = new RateLimitPolicy(RateLimitTier.STARTER);
+ given(merchantRepository.findById(merchantId)).willReturn(Optional.of(merchant));
+ given(rateLimiter.check(merchantId, endpoint, policy))
+ .willReturn(new RateLimitResult(false, 61, 60, "1m", 60));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(429);
assertThat(response.getHeader("Retry-After")).isEqualTo("60");
assertThat(response.getContentAsString()).contains("GW-6001");
@@ -187,13 +191,15 @@ void shouldRejectWithMinuteBreachAndRetryAfter() throws ServletException, IOExce
void shouldRejectWithDayBreachAndRetryAfter() throws ServletException, IOException {
setAuthentication();
var merchant = buildMerchant(RateLimitTier.STARTER);
- when(merchantRepository.findById(merchantId)).thenReturn(Optional.of(merchant));
- when(rateLimiter.check(eq(merchantId), any(), any()))
- .thenReturn(new RateLimitResult(false, 10001, 10000, "1d", 3600));
+ var endpoint = "GET /v1/payments";
+ var policy = new RateLimitPolicy(RateLimitTier.STARTER);
+ given(merchantRepository.findById(merchantId)).willReturn(Optional.of(merchant));
+ given(rateLimiter.check(merchantId, endpoint, policy))
+ .willReturn(new RateLimitResult(false, 10001, 10000, "1d", 3600));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(429);
assertThat(response.getHeader("Retry-After")).isEqualTo("3600");
}
@@ -202,13 +208,15 @@ void shouldRejectWithDayBreachAndRetryAfter() throws ServletException, IOExcepti
void shouldPersistRateLimitEvent() throws ServletException, IOException {
setAuthentication();
var merchant = buildMerchant(RateLimitTier.STARTER);
- when(merchantRepository.findById(merchantId)).thenReturn(Optional.of(merchant));
- when(rateLimiter.check(eq(merchantId), any(), any()))
- .thenReturn(new RateLimitResult(false, 61, 60, "1m", 60));
+ var endpoint = "GET /v1/payments";
+ var policy = new RateLimitPolicy(RateLimitTier.STARTER);
+ given(merchantRepository.findById(merchantId)).willReturn(Optional.of(merchant));
+ given(rateLimiter.check(merchantId, endpoint, policy))
+ .willReturn(new RateLimitResult(false, 61, 60, "1m", 60));
filter.doFilterInternal(request, response, filterChain);
- verify(rateLimitEventRepository).save(any());
+ then(rateLimitEventRepository).should().save(any());
}
}
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilterTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilterTest.java
index 9d1717e6..54580f59 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilterTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/application/security/UserJwtAuthenticationFilterTest.java
@@ -33,9 +33,9 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class UserJwtAuthenticationFilterTest {
@@ -113,7 +113,7 @@ class WhenNoBearerToken {
void shouldPassThroughWithoutAuthHeader() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@@ -123,7 +123,7 @@ void shouldPassThroughWithNonBearerHeader() throws ServletException, IOException
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
}
}
@@ -139,7 +139,7 @@ void shouldSkipWhenAlreadyAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication())
.isInstanceOf(MerchantAuthentication.class);
}
@@ -155,11 +155,11 @@ void shouldAuthenticateWithValidUserJwt() throws Exception {
var token = buildToken(S13_ISSUER, AUDIENCE,
Instant.now().plusSeconds(3600), userId, merchantId);
request.addHeader("Authorization", "Bearer " + token);
- when(userJwksProvider.fetchJwks()).thenReturn(jwksJson);
+ given(userJwksProvider.fetchJwks()).willReturn(jwksJson);
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
var auth = SecurityContextHolder.getContext().getAuthentication();
assertThat(auth).isInstanceOf(UserAuthentication.class);
var userAuth = (UserAuthentication) auth;
@@ -182,7 +182,7 @@ void shouldPassThroughForNonS13Issuer() throws Exception {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
}
@@ -198,7 +198,7 @@ void shouldRejectExpiredToken() throws Exception {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -210,7 +210,7 @@ void shouldRejectWrongAudience() throws Exception {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -240,11 +240,11 @@ void shouldRejectTokenSignedByDifferentKey() throws Exception {
jwt.sign(new ECDSASigner(otherKey));
request.addHeader("Authorization", "Bearer " + jwt.serialize());
- when(userJwksProvider.fetchJwks()).thenReturn(jwksJson);
+ given(userJwksProvider.fetchJwks()).willReturn(jwksJson);
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
@@ -254,7 +254,7 @@ void shouldRejectGarbageToken() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
}
@@ -267,12 +267,12 @@ void shouldReturn503WhenJwksUnavailable() throws Exception {
var token = buildToken(S13_ISSUER, AUDIENCE,
Instant.now().plusSeconds(3600), UUID.randomUUID(), UUID.randomUUID());
request.addHeader("Authorization", "Bearer " + token);
- when(userJwksProvider.fetchJwks())
- .thenThrow(new UserJwksUnavailableException("S13 down", new RuntimeException()));
+ given(userJwksProvider.fetchJwks())
+ .willThrow(new UserJwksUnavailableException("S13 down", new RuntimeException()));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(503);
assertThat(response.getContentAsString()).contains("GW-5001");
}
diff --git a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/infrastructure/auth/CachedUserJwksProviderTest.java b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/infrastructure/auth/CachedUserJwksProviderTest.java
index 1991643b..2c997b83 100644
--- a/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/infrastructure/auth/CachedUserJwksProviderTest.java
+++ b/api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/infrastructure/auth/CachedUserJwksProviderTest.java
@@ -16,8 +16,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
@ExtendWith(MockitoExtension.class)
class CachedUserJwksProviderTest {
@@ -44,7 +44,7 @@ void setUp() {
"payment-platform",
24);
provider = new CachedUserJwksProvider(merchantIamClient, redis, properties);
- when(redis.opsForValue()).thenReturn(valueOps);
+ given(redis.opsForValue()).willReturn(valueOps);
}
@Nested
@@ -52,24 +52,24 @@ class WhenS13Available {
@Test
void shouldFetchAndCacheJwks() {
- when(valueOps.get(CACHE_KEY)).thenReturn(null);
- when(merchantIamClient.fetchJwks()).thenReturn(JWKS_JSON);
+ given(valueOps.get(CACHE_KEY)).willReturn(null);
+ given(merchantIamClient.fetchJwks()).willReturn(JWKS_JSON);
var result = provider.fetchJwks();
assertThat(result).isEqualTo(JWKS_JSON);
- verify(valueOps).set(CACHE_KEY, JWKS_JSON, Duration.ofHours(24));
+ then(valueOps).should().set(CACHE_KEY, JWKS_JSON, Duration.ofHours(24));
}
@Test
void shouldRefreshCacheEvenWhenCachedValueExists() {
- when(valueOps.get(CACHE_KEY)).thenReturn("old-jwks");
- when(merchantIamClient.fetchJwks()).thenReturn(JWKS_JSON);
+ given(valueOps.get(CACHE_KEY)).willReturn("old-jwks");
+ given(merchantIamClient.fetchJwks()).willReturn(JWKS_JSON);
var result = provider.fetchJwks();
assertThat(result).isEqualTo(JWKS_JSON);
- verify(valueOps).set(CACHE_KEY, JWKS_JSON, Duration.ofHours(24));
+ then(valueOps).should().set(CACHE_KEY, JWKS_JSON, Duration.ofHours(24));
}
}
@@ -78,8 +78,8 @@ class WhenS13Unavailable {
@Test
void shouldReturnCachedValueWhenAvailable() {
- when(valueOps.get(CACHE_KEY)).thenReturn(JWKS_JSON);
- when(merchantIamClient.fetchJwks()).thenThrow(new RuntimeException("Connection refused"));
+ given(valueOps.get(CACHE_KEY)).willReturn(JWKS_JSON);
+ given(merchantIamClient.fetchJwks()).willThrow(new RuntimeException("Connection refused"));
var result = provider.fetchJwks();
@@ -88,8 +88,8 @@ void shouldReturnCachedValueWhenAvailable() {
@Test
void shouldThrowWhenNoCachedValue() {
- when(valueOps.get(CACHE_KEY)).thenReturn(null);
- when(merchantIamClient.fetchJwks()).thenThrow(new RuntimeException("Connection refused"));
+ given(valueOps.get(CACHE_KEY)).willReturn(null);
+ given(merchantIamClient.fetchJwks()).willThrow(new RuntimeException("Connection refused"));
assertThatThrownBy(() -> provider.fetchJwks())
.isInstanceOf(UserJwksUnavailableException.class)
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainMonitorConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainMonitorConfig.java
index 275b54c2..3cd3bc10 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainMonitorConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainMonitorConfig.java
@@ -8,10 +8,6 @@
import java.util.Map;
-/**
- * Application-layer implementation of {@link ChainConfirmationProperties} and {@link TokenContractResolver}.
- * Binds to {@code app.chains.*} YAML properties.
- */
@ConfigurationProperties(prefix = "app")
public record ChainMonitorConfig(
Map chains
@@ -51,9 +47,6 @@ public String resolveContract(ChainId chainId, StablecoinTicker stablecoin) {
return contracts.get(stablecoin.ticker());
}
- /**
- * Per-chain configuration properties.
- */
public record ChainProperties(
int minConfirmations,
int avgFinalityS,
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainSelectionConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainSelectionConfig.java
index b1eff69e..e8c8ff29 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainSelectionConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ChainSelectionConfig.java
@@ -5,10 +5,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-/**
- * Configuration for the chain selection engine.
- * Maps {@code app.custody.chain-selection.*} properties to {@link ChainSelectionWeights}.
- */
@Configuration
public class ChainSelectionConfig {
@@ -27,10 +23,6 @@ public ChainSelectionWeights chainSelectionWeights(ChainSelectionProperties prop
.build();
}
- /**
- * Mutable properties bean for Spring Boot's {@code @ConfigurationProperties} binding.
- * Defaults match {@link ChainSelectionWeights#defaults()}.
- */
public static class ChainSelectionProperties {
private double costWeight = ChainSelectionWeights.DEFAULT_COST_WEIGHT;
private double speedWeight = ChainSelectionWeights.DEFAULT_SPEED_WEIGHT;
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ClockConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ClockConfig.java
index 59e780e7..d6b3dea4 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ClockConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/ClockConfig.java
@@ -6,11 +6,6 @@
import java.time.Clock;
-/**
- * Provides a system UTC {@link Clock} bean for time-dependent domain logic.
- *
- * Tests can override this with a fixed clock for deterministic behaviour.
- */
@Configuration
public class ClockConfig {
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/security/SecurityConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/SecurityConfig.java
similarity index 96%
rename from ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/security/SecurityConfig.java
rename to blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/SecurityConfig.java
index 0bc6fa7e..626973d8 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/security/SecurityConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.ledger.application.security;
+package com.stablecoin.payments.custody.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/TransferMonitorConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/TransferMonitorConfig.java
index bbc5a848..90274505 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/TransferMonitorConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/config/TransferMonitorConfig.java
@@ -3,10 +3,6 @@
import com.stablecoin.payments.custody.domain.port.TransferMonitorProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
-/**
- * Application-layer implementation of {@link TransferMonitorProperties}.
- * Binds to {@code app.transfer.*} YAML properties.
- */
@ConfigurationProperties(prefix = "app.transfer")
public record TransferMonitorConfig(
int resubmitTimeoutS,
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/controller/TransferController.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/controller/TransferController.java
index 28932dfb..2844f9f6 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/controller/TransferController.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/controller/TransferController.java
@@ -25,10 +25,6 @@
import static org.springframework.http.HttpStatus.ACCEPTED;
import static org.springframework.http.HttpStatus.OK;
-/**
- * Thin REST controller for blockchain transfer operations.
- * Delegates all business logic to {@link TransferCommandHandler}.
- */
@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/BalanceSyncJob.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/BalanceSyncJob.java
index 6789a00c..47073650 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/BalanceSyncJob.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/BalanceSyncJob.java
@@ -7,11 +7,6 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
-/**
- * Scheduled job that syncs blockchain balances from RPC every 30 seconds.
- *
- * Delegates all business logic to {@link BalanceSyncCommandHandler}.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/TransferMonitorJob.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/TransferMonitorJob.java
index 74382850..155cafd4 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/TransferMonitorJob.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/scheduler/TransferMonitorJob.java
@@ -7,11 +7,6 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
-/**
- * Scheduled job that polls in-flight chain transfers every 15 seconds.
- *
- * Delegates all business logic to {@link TransferMonitorCommandHandler}.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/ChainUnavailableException.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/ChainUnavailableException.java
index f47bad29..437105bd 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/ChainUnavailableException.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/ChainUnavailableException.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.custody.domain.exception;
-/**
- * Thrown when no healthy blockchain chain is available for a transfer.
- */
public class ChainUnavailableException extends RuntimeException {
public static final String ERROR_CODE = "BC-1002";
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/CustodySigningException.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/CustodySigningException.java
index 9f077e0b..a39eee57 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/CustodySigningException.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/CustodySigningException.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.custody.domain.exception;
-/**
- * Thrown when the custody engine fails to sign or submit a transaction.
- */
public class CustodySigningException extends RuntimeException {
public static final String ERROR_CODE = "BC-1004";
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/InsufficientBalanceException.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/InsufficientBalanceException.java
index 660084ee..256b132e 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/InsufficientBalanceException.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/InsufficientBalanceException.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.custody.domain.exception;
-/**
- * Thrown when a wallet has insufficient available balance for a transfer.
- */
public class InsufficientBalanceException extends RuntimeException {
public static final String ERROR_CODE = "BC-1001";
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/TransferNotFoundException.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/TransferNotFoundException.java
index e4765204..c8e540e1 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/TransferNotFoundException.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/TransferNotFoundException.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.custody.domain.exception;
-/**
- * Thrown when a chain transfer is not found.
- */
public class TransferNotFoundException extends RuntimeException {
public static final String ERROR_CODE = "BC-1003";
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/WalletNotFoundException.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/WalletNotFoundException.java
index 4638cca0..597f7ceb 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/WalletNotFoundException.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/exception/WalletNotFoundException.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.custody.domain.exception;
-/**
- * Thrown when a wallet is not found.
- */
public class WalletNotFoundException extends RuntimeException {
public static final String ERROR_CODE = "BC-1005";
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainCandidate.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainCandidate.java
index cba160aa..205046ea 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainCandidate.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainCandidate.java
@@ -2,12 +2,6 @@
import lombok.Builder;
-/**
- * A candidate chain evaluation result used during chain selection.
- *
- * Each candidate records the fee estimate, finality time, health score,
- * the composite weighted score, and whether this candidate was ultimately selected.
- */
@Builder(toBuilder = true)
public record ChainCandidate(
ChainId chainId,
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionResult.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionResult.java
index 119fc3ad..ff8143f1 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionResult.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionResult.java
@@ -5,10 +5,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * The result of a chain selection evaluation, containing the selected chain,
- * all evaluated candidates with their scores, and the transfer ID.
- */
@Builder(toBuilder = true)
public record ChainSelectionResult(
ChainId selectedChain,
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionWeights.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionWeights.java
index 33a5ca49..e60b8cad 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionWeights.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainSelectionWeights.java
@@ -2,11 +2,6 @@
import lombok.Builder;
-/**
- * Immutable weights for the multi-criteria chain selection scoring model.
- *
- * All weights must be non-negative and sum to approximately 1.0 (±0.001 tolerance).
- */
@Builder(toBuilder = true)
public record ChainSelectionWeights(
double costWeight,
@@ -37,9 +32,6 @@ public record ChainSelectionWeights(
}
}
- /**
- * Returns the default weights (cost=0.4, speed=0.35, reliability=0.25).
- */
public static ChainSelectionWeights defaults() {
return new ChainSelectionWeights(DEFAULT_COST_WEIGHT, DEFAULT_SPEED_WEIGHT, DEFAULT_RELIABILITY_WEIGHT);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainTransfer.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainTransfer.java
index 8d67617f..2e1ede0a 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainTransfer.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/ChainTransfer.java
@@ -28,18 +28,6 @@
import static com.stablecoin.payments.custody.domain.model.TransferTrigger.START_SIGNING;
import static com.stablecoin.payments.custody.domain.model.TransferTrigger.SUBMIT;
-/**
- * Aggregate root for a blockchain chain transfer.
- *
- * Enforces the transfer lifecycle via an internal state machine:
- * {@code PENDING -> CHAIN_SELECTED -> SIGNING -> SUBMITTED -> CONFIRMING -> CONFIRMED}.
- *
- * Resubmission path handles mempool drops: {@code SUBMITTED -> RESUBMITTING -> SUBMITTED}.
- *
- * Failure can occur from multiple states: CHAIN_SELECTED, SIGNING, SUBMITTED, RESUBMITTING, CONFIRMING.
- *
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record ChainTransfer(
UUID transferId,
@@ -72,19 +60,16 @@ public record ChainTransfer(
private static final StateMachine STATE_MACHINE =
new StateMachine<>(List.of(
- // -- Happy path -------------------------------------------------
new StateTransition<>(PENDING, SELECT_CHAIN, CHAIN_SELECTED),
new StateTransition<>(CHAIN_SELECTED, START_SIGNING, SIGNING),
new StateTransition<>(SIGNING, SUBMIT, SUBMITTED),
new StateTransition<>(SUBMITTED, START_CONFIRMING, CONFIRMING),
new StateTransition<>(CONFIRMING, CONFIRM, CONFIRMED),
- // -- Resubmission path ------------------------------------------
new StateTransition<>(SUBMITTED, RESUBMIT, RESUBMITTING),
new StateTransition<>(CONFIRMING, RESUBMIT, RESUBMITTING),
new StateTransition<>(RESUBMITTING, CONFIRM_SUBMISSION, SUBMITTED),
- // -- Failure from multiple states --------------------------------
new StateTransition<>(CHAIN_SELECTED, FAIL, FAILED),
new StateTransition<>(SIGNING, FAIL, FAILED),
new StateTransition<>(SUBMITTED, FAIL, FAILED),
@@ -92,11 +77,7 @@ public record ChainTransfer(
new StateTransition<>(CONFIRMING, FAIL, FAILED)
));
- // -- Factory Method -------------------------------------------------
- /**
- * Creates a new chain transfer in PENDING state.
- */
public static ChainTransfer initiate(UUID paymentId, UUID correlationId,
TransferType transferType, UUID parentTransferId,
StablecoinTicker stablecoin, BigDecimal amount,
@@ -146,11 +127,7 @@ public static ChainTransfer initiate(UUID paymentId, UUID correlationId,
.build();
}
- // -- State Transition Methods ---------------------------------------
- /**
- * Selects the blockchain chain. Transitions PENDING -> CHAIN_SELECTED.
- */
public ChainTransfer selectChain(ChainId selectedChainId) {
assertNotTerminal();
if (selectedChainId == null) {
@@ -164,9 +141,6 @@ public ChainTransfer selectChain(ChainId selectedChainId) {
.build();
}
- /**
- * Starts the signing process. Transitions CHAIN_SELECTED -> SIGNING.
- */
public ChainTransfer startSigning(Long signingNonce) {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, START_SIGNING);
@@ -177,9 +151,6 @@ public ChainTransfer startSigning(Long signingNonce) {
.build();
}
- /**
- * Submits the signed transaction to the chain. Transitions SIGNING -> SUBMITTED.
- */
public ChainTransfer submit(String transactionHash) {
assertNotTerminal();
if (transactionHash == null || transactionHash.isBlank()) {
@@ -194,9 +165,6 @@ public ChainTransfer submit(String transactionHash) {
.build();
}
- /**
- * Starts the confirmation monitoring. Transitions SUBMITTED -> CONFIRMING.
- */
public ChainTransfer startConfirming() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, START_CONFIRMING);
@@ -206,10 +174,6 @@ public ChainTransfer startConfirming() {
.build();
}
- /**
- * Confirms the transfer after sufficient block confirmations.
- * Transitions CONFIRMING -> CONFIRMED.
- */
public ChainTransfer confirm(long confirmedBlockNumber, int confirmedConfirmations,
BigDecimal confirmedGasUsed, BigDecimal confirmedGasPriceGwei) {
assertNotTerminal();
@@ -225,10 +189,6 @@ public ChainTransfer confirm(long confirmedBlockNumber, int confirmedConfirmatio
.build();
}
- /**
- * Marks the transfer for resubmission (mempool drop / timeout).
- * Transitions SUBMITTED -> RESUBMITTING.
- */
public ChainTransfer markForResubmission() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, RESUBMIT);
@@ -238,9 +198,6 @@ public ChainTransfer markForResubmission() {
.build();
}
- /**
- * Resubmits with a new transaction hash. Transitions RESUBMITTING -> SUBMITTED.
- */
public ChainTransfer resubmit(String newTxHash) {
assertNotTerminal();
if (newTxHash == null || newTxHash.isBlank()) {
@@ -255,11 +212,6 @@ public ChainTransfer resubmit(String newTxHash) {
.build();
}
- /**
- * Claims a resubmission attempt by incrementing the attempt counter.
- * Must be persisted BEFORE calling the custody engine to ensure crash safety.
- * Stays in RESUBMITTING state.
- */
public ChainTransfer claimResubmission() {
assertNotTerminal();
if (status != RESUBMITTING) {
@@ -272,11 +224,6 @@ public ChainTransfer claimResubmission() {
.build();
}
- /**
- * Completes a previously claimed resubmission with the new transaction hash.
- * Transitions RESUBMITTING -> SUBMITTED without incrementing attempt count
- * (already incremented by {@link #claimResubmission()}).
- */
public ChainTransfer confirmResubmission(String newTxHash) {
assertNotTerminal();
if (newTxHash == null || newTxHash.isBlank()) {
@@ -290,9 +237,6 @@ public ChainTransfer confirmResubmission(String newTxHash) {
.build();
}
- /**
- * Fails the transfer. Can be triggered from multiple non-terminal states.
- */
public ChainTransfer fail(String reason, String code) {
var nextState = STATE_MACHINE.transition(status, FAIL);
return toBuilder()
@@ -303,23 +247,15 @@ public ChainTransfer fail(String reason, String code) {
.build();
}
- // -- Query Methods --------------------------------------------------
- /**
- * Returns true if this transfer is in a terminal state (CONFIRMED or FAILED).
- */
public boolean isTerminal() {
return TERMINAL_STATES.contains(status);
}
- /**
- * Returns true if a given trigger can be applied from the current status.
- */
public boolean canApply(TransferTrigger trigger) {
return STATE_MACHINE.canTransition(status, trigger);
}
- // -- Invariant Guards -----------------------------------------------
private void assertNotTerminal() {
if (isTerminal()) {
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/NonceAssignment.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/NonceAssignment.java
index 37e7affd..9e914308 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/NonceAssignment.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/NonceAssignment.java
@@ -1,42 +1,24 @@
package com.stablecoin.payments.custody.domain.model;
-/**
- * Result of a nonce assignment operation.
- *
- * @param nonce the assigned nonce, or {@code null} for chains that do not use nonces (e.g. Solana)
- * @param source how the nonce was obtained
- */
public record NonceAssignment(
Long nonce,
NonceSource source
) {
public enum NonceSource {
- /** Fresh nonce — incremented from the database */
INCREMENTED,
- /** Reused nonce — same nonce for replace-by-fee */
REUSED,
- /** Chain does not use nonces (e.g. Solana uses recent blockhash) */
NOT_APPLICABLE
}
- /**
- * Creates a nonce assignment for a chain that does not use nonces.
- */
public static NonceAssignment notApplicable() {
return new NonceAssignment(null, NonceSource.NOT_APPLICABLE);
}
- /**
- * Creates a nonce assignment from a freshly incremented nonce.
- */
public static NonceAssignment incremented(long nonce) {
return new NonceAssignment(nonce, NonceSource.INCREMENTED);
}
- /**
- * Creates a nonce assignment from a reused nonce (replace-by-fee).
- */
public static NonceAssignment reused(long nonce) {
return new NonceAssignment(nonce, NonceSource.REUSED);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/StablecoinTicker.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/StablecoinTicker.java
index 4c4f3f62..080333b4 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/StablecoinTicker.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/StablecoinTicker.java
@@ -31,9 +31,6 @@ private record StablecoinInfo(String issuer, int decimals) {}
}
}
- /**
- * Factory that auto-fills issuer and decimals from the ticker.
- */
public static StablecoinTicker of(String ticker) {
return new StablecoinTicker(ticker, null, 0);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferLifecycleEvent.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferLifecycleEvent.java
index 7e04c7d5..e5467896 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferLifecycleEvent.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferLifecycleEvent.java
@@ -6,9 +6,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Audit record for a state change in a chain transfer's lifecycle.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record TransferLifecycleEvent(
UUID eventId,
@@ -19,11 +16,7 @@ public record TransferLifecycleEvent(
Instant occurredAt
) {
- // -- Factory Methods ------------------------------------------------
- /**
- * Records a simple state change event.
- */
public static TransferLifecycleEvent record(UUID transferId, String state) {
if (transferId == null) {
throw new IllegalArgumentException("transferId is required");
@@ -40,9 +33,6 @@ public static TransferLifecycleEvent record(UUID transferId, String state) {
.build();
}
- /**
- * Records a state change event with participant details.
- */
public static TransferLifecycleEvent record(UUID transferId, String state,
String participantType, String address) {
if (transferId == null) {
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferParticipant.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferParticipant.java
index 18bc2bce..83035b28 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferParticipant.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferParticipant.java
@@ -6,9 +6,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Represents a participant (input, output, or fee) in a chain transfer.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record TransferParticipant(
UUID participantId,
@@ -20,11 +17,7 @@ public record TransferParticipant(
String assetCode
) {
- // -- Factory Method -------------------------------------------------
- /**
- * Creates a new transfer participant.
- */
public static TransferParticipant create(UUID transferId, ParticipantType participantType,
String address, UUID walletId,
BigDecimal amount, String assetCode) {
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferResult.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferResult.java
index 92c9a9d1..c3419ab8 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferResult.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/TransferResult.java
@@ -1,6 +1,3 @@
package com.stablecoin.payments.custody.domain.model;
-/**
- * Wrapper to distinguish newly created transfers (202) from idempotent replays (200).
- */
public record TransferResult(ChainTransfer transfer, boolean created) {}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/Wallet.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/Wallet.java
index bcb3577e..dcafdd03 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/Wallet.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/Wallet.java
@@ -6,12 +6,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Represents a custody wallet on a specific blockchain chain.
- *
- * Wallets are managed by a custody provider (e.g., Fireblocks) and have a specific
- * tier (HOT/WARM/COLD) and purpose (ON_RAMP/OFF_RAMP/SWEEP/RESERVE).
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record Wallet(
UUID walletId,
@@ -28,11 +22,7 @@ public record Wallet(
Instant deactivatedAt
) {
- // -- Factory Method -------------------------------------------------
- /**
- * Creates a new active wallet.
- */
public static Wallet create(ChainId chainId, String address, String addressChecksum,
WalletTier tier, WalletPurpose purpose,
String custodian, String vaultAccountId,
@@ -78,11 +68,7 @@ public static Wallet create(ChainId chainId, String address, String addressCheck
.build();
}
- // -- Domain Methods -------------------------------------------------
- /**
- * Deactivates this wallet. Returns a new instance with active=false.
- */
public Wallet deactivate() {
if (!active) {
throw new IllegalStateException(
@@ -94,11 +80,7 @@ public Wallet deactivate() {
.build();
}
- // -- Query Methods --------------------------------------------------
- /**
- * Returns true if this wallet is currently active.
- */
public boolean isActive() {
return active;
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/WalletBalance.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/WalletBalance.java
index 99a600b1..52abe916 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/WalletBalance.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/model/WalletBalance.java
@@ -7,16 +7,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Tracks the balance of a specific stablecoin in a wallet.
- *
- * Maintains three balance views:
- *
- * {@code availableBalance} — funds available for new transfers
- * {@code reservedBalance} — funds locked for in-flight transfers
- * {@code blockchainBalance} — last indexed on-chain balance
- *
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record WalletBalance(
UUID balanceId,
@@ -43,11 +33,7 @@ public record WalletBalance(
}
}
- // -- Factory Method -------------------------------------------------
- /**
- * Initializes a new wallet balance with zero balances.
- */
public static WalletBalance initialize(UUID walletId, ChainId chainId,
StablecoinTicker stablecoin) {
if (walletId == null) {
@@ -74,12 +60,7 @@ public static WalletBalance initialize(UUID walletId, ChainId chainId,
.build();
}
- // -- Domain Methods -------------------------------------------------
- /**
- * Reserves an amount for an in-flight transfer.
- * Moves funds from available to reserved.
- */
public WalletBalance reserve(BigDecimal amount) {
validatePositiveAmount(amount);
if (availableBalance.compareTo(amount) < 0) {
@@ -94,9 +75,6 @@ public WalletBalance reserve(BigDecimal amount) {
.build();
}
- /**
- * Releases a previously reserved amount back to available.
- */
public WalletBalance release(BigDecimal amount) {
validatePositiveAmount(amount);
if (reservedBalance.compareTo(amount) < 0) {
@@ -111,9 +89,6 @@ public WalletBalance release(BigDecimal amount) {
.build();
}
- /**
- * Confirms a debit after chain confirmation. Reduces reserved balance.
- */
public WalletBalance confirmDebit(BigDecimal amount) {
validatePositiveAmount(amount);
if (reservedBalance.compareTo(amount) < 0) {
@@ -127,9 +102,6 @@ public WalletBalance confirmDebit(BigDecimal amount) {
.build();
}
- /**
- * Syncs the balance from on-chain data. Updates blockchain balance and recalculates available.
- */
public WalletBalance syncFromChain(BigDecimal onChainBalance, long blockNumber) {
if (onChainBalance == null || onChainBalance.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("On-chain balance must be non-negative");
@@ -151,11 +123,7 @@ public WalletBalance syncFromChain(BigDecimal onChainBalance, long blockNumber)
.build();
}
- // -- Query Methods --------------------------------------------------
- /**
- * Returns true if the wallet has sufficient available balance for the given amount.
- */
public boolean hasSufficientBalance(BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
return false;
@@ -163,7 +131,6 @@ public boolean hasSufficientBalance(BigDecimal amount) {
return availableBalance.compareTo(amount) >= 0;
}
- // -- Validation Helpers ---------------------------------------------
private static void validatePositiveAmount(BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainConfirmationProperties.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainConfirmationProperties.java
index d7b2b989..0aa817e1 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainConfirmationProperties.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainConfirmationProperties.java
@@ -1,16 +1,6 @@
package com.stablecoin.payments.custody.domain.port;
-/**
- * Domain port for per-chain confirmation configuration.
- */
public interface ChainConfirmationProperties {
- /**
- * Returns the minimum confirmations required for a given chain.
- *
- * @param chainId the chain identifier (e.g., "base", "ethereum", "solana")
- * @return minimum confirmations (always positive)
- * @throws IllegalStateException if chain is not configured
- */
int getMinConfirmations(String chainId);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainFeeProvider.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainFeeProvider.java
index 43ce2035..4fe460b5 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainFeeProvider.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainFeeProvider.java
@@ -3,17 +3,7 @@
import com.stablecoin.payments.custody.domain.model.ChainId;
import com.stablecoin.payments.custody.domain.model.StablecoinTicker;
-/**
- * Port for estimating transaction fees on a given blockchain chain.
- */
public interface ChainFeeProvider {
- /**
- * Estimates the fee in USD for transferring a stablecoin on the given chain.
- *
- * @param chainId the target chain
- * @param stablecoin the stablecoin to transfer
- * @return estimated fee in USD
- */
double estimateFeeUsd(ChainId chainId, StablecoinTicker stablecoin);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainHealthProvider.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainHealthProvider.java
index 99cfb83d..0b0e5ec1 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainHealthProvider.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainHealthProvider.java
@@ -2,18 +2,7 @@
import com.stablecoin.payments.custody.domain.model.ChainId;
-/**
- * Port for retrieving the current health score of a blockchain chain.
- *
- * Returns 0.0 for unhealthy/down chains and 1.0 for healthy chains.
- */
public interface ChainHealthProvider {
- /**
- * Returns the health score for the given chain.
- *
- * @param chainId the chain to evaluate
- * @return 0.0 (unhealthy) or 1.0 (healthy)
- */
double getHealthScore(ChainId chainId);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainSelectionLogRepository.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainSelectionLogRepository.java
index a11f166c..bbd77035 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainSelectionLogRepository.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/ChainSelectionLogRepository.java
@@ -4,16 +4,7 @@
import java.util.UUID;
-/**
- * Port for persisting chain selection evaluation results.
- */
public interface ChainSelectionLogRepository {
- /**
- * Saves the chain selection result for the given transfer.
- *
- * @param transferId the transfer for which chain was selected
- * @param result the selection result including all scored candidates
- */
void save(UUID transferId, ChainSelectionResult result);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/IsolatedTransactionExecutor.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/IsolatedTransactionExecutor.java
new file mode 100644
index 00000000..d482df9b
--- /dev/null
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/IsolatedTransactionExecutor.java
@@ -0,0 +1,6 @@
+package com.stablecoin.payments.custody.domain.port;
+
+public interface IsolatedTransactionExecutor {
+
+ void executeInNewTransaction(Runnable action);
+}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/NonceRepository.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/NonceRepository.java
index 62041ae4..ca78678a 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/NonceRepository.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/NonceRepository.java
@@ -5,40 +5,9 @@
import java.util.Optional;
import java.util.UUID;
-/**
- * Port for nonce persistence and wallet-level serialization.
- *
- * Implementations serialize nonce assignment for a given wallet to prevent
- * concurrent transactions from receiving the same nonce. The serialization
- * mechanism (advisory locks, row-level locks, etc.) is an infrastructure concern.
- */
public interface NonceRepository {
- /**
- * Atomically acquires a wallet-level lock, reads the current nonce, increments it
- * in the database, and returns the value before the increment (i.e. the
- * nonce to use for the next transaction).
- *
- * If no nonce row exists for the wallet/chain pair, one is created with
- * {@code current_nonce = 1} and {@code 0} is returned.
- *
- * Implementations must serialize concurrent calls for the same wallet/chain
- * pair to guarantee unique nonce assignment.
- *
- * @param walletId the wallet ID
- * @param chainId the blockchain chain
- * @return the nonce to use for the next transaction
- */
long assignNextNonce(UUID walletId, ChainId chainId);
- /**
- * Returns the current nonce counter for a wallet on a specific chain
- * without incrementing. Used for resubmit (replace-by-fee) scenarios
- * where the same nonce must be reused.
- *
- * @param walletId the wallet ID
- * @param chainId the blockchain chain
- * @return the current nonce counter, or empty if no nonce row exists yet
- */
Optional getCurrentNonce(UUID walletId, ChainId chainId);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TokenContractResolver.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TokenContractResolver.java
index 470c1bc5..f884a883 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TokenContractResolver.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TokenContractResolver.java
@@ -3,18 +3,7 @@
import com.stablecoin.payments.custody.domain.model.ChainId;
import com.stablecoin.payments.custody.domain.model.StablecoinTicker;
-/**
- * Domain port for resolving token contract addresses per chain and stablecoin.
- */
public interface TokenContractResolver {
- /**
- * Resolves the on-chain token contract address for the given chain and stablecoin.
- *
- * @param chainId the blockchain identifier
- * @param stablecoin the stablecoin ticker
- * @return the contract address (e.g., ERC-20 address or SPL mint)
- * @throws IllegalStateException if no contract is configured for the given chain/stablecoin
- */
String resolveContract(ChainId chainId, StablecoinTicker stablecoin);
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TransferMonitorProperties.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TransferMonitorProperties.java
index c5c27764..58f68cf6 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TransferMonitorProperties.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/port/TransferMonitorProperties.java
@@ -1,25 +1,11 @@
package com.stablecoin.payments.custody.domain.port;
-/**
- * Domain port for transfer monitor configuration.
- */
public interface TransferMonitorProperties {
- /**
- * Returns the number of seconds before a SUBMITTED transfer is considered stuck.
- */
int resubmitTimeoutS();
- /**
- * Returns the maximum number of submission attempts before a transfer is failed.
- */
int maxAttempts();
- /**
- * Returns the number of seconds a CONFIRMING transfer can remain without a receipt
- * before being marked for resubmission (e.g., after a chain reorg).
- * Defaults to 300 seconds (5 minutes).
- */
default int confirmingTimeoutS() {
return 300;
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/BalanceSyncCommandHandler.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/BalanceSyncCommandHandler.java
index c0aef53c..6efb6b4e 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/BalanceSyncCommandHandler.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/BalanceSyncCommandHandler.java
@@ -8,14 +8,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
-/**
- * Domain service responsible for syncing wallet balances from on-chain data.
- *
- * Queries all wallet balances and updates them with the latest on-chain balance
- * from the RPC provider. No class-level transaction — each balance save runs in
- * its own transaction via the repository adapter, keeping RPC calls outside
- * any database transaction.
- */
@Slf4j
@Service
@RequiredArgsConstructor
@@ -26,13 +18,6 @@ public class BalanceSyncCommandHandler {
private final ChainRpcProvider chainRpcProvider;
private final TokenContractResolver tokenContractResolver;
- /**
- * Syncs all wallet balances from on-chain data.
- *
- * For each balance record, fetches the current on-chain balance and latest block number,
- * then updates via {@code WalletBalance.syncFromChain()}.
- * Failures for individual balances are logged and skipped.
- */
public void syncAllBalances() {
var balances = walletBalanceRepository.findAll();
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/ChainSelectionEngine.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/ChainSelectionEngine.java
index 5318afda..91f4a9f8 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/ChainSelectionEngine.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/ChainSelectionEngine.java
@@ -24,20 +24,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Domain service that evaluates candidate blockchain chains using a weighted scoring model
- * and selects the optimal chain for a given transfer.
- *
- *
Algorithm:
- *
- * Get MVP candidate chains (Base, Ethereum, Solana)
- * Filter by wallet liquidity — chain must have an ON_RAMP wallet with sufficient balance
- * Filter by health — chain must have health score > 0
- * Score each candidate: score = costWeight * (1/feeUsd) + speedWeight * (1/finalitySeconds) + reliabilityWeight * healthScore
- * If preferredChain is set and healthy, select it regardless of score
- * Otherwise select the candidate with the highest score
- *
- */
@Slf4j
@Service
@RequiredArgsConstructor
@@ -50,9 +36,6 @@ public class ChainSelectionEngine {
private final WalletBalanceRepository walletBalanceRepository;
private final ChainSelectionLogRepository chainSelectionLogRepository;
- /**
- * Request record for chain selection.
- */
public record ChainSelectionRequest(
UUID transferId,
StablecoinTicker stablecoin,
@@ -73,9 +56,6 @@ public record ChainSelectionRequest(
}
}
- /**
- * MVP candidate chain configurations.
- */
private static final Map MVP_CHAINS = Map.of(
"base", new ChainConfig(
new ChainId("base"), 1, 12, "ETH",
@@ -88,14 +68,6 @@ public record ChainSelectionRequest(
List.of("https://sol-rpc.example.com"), "https://explorer.solana.com")
);
- /**
- * Selects the optimal chain for a transfer based on cost, speed, reliability,
- * wallet liquidity, and chain health.
- *
- * @param request the selection request containing transfer details
- * @return the selection result with the chosen chain and all scored candidates
- * @throws ChainUnavailableException if no healthy, funded chain is available
- */
public ChainSelectionResult selectChain(ChainSelectionRequest request) {
log.info("Selecting chain for transfer={}, stablecoin={}, amount={}",
request.transferId(), request.stablecoin().ticker(), request.amount());
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/NonceManager.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/NonceManager.java
index f1278856..3f092c8a 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/NonceManager.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/NonceManager.java
@@ -10,21 +10,6 @@
import java.util.Set;
import java.util.UUID;
-/**
- * Domain service that manages nonce assignment for blockchain wallets.
- *
- * EVM chains (Ethereum, Base, Polygon, Avalanche, Tron) use an account-based nonce
- * model where each transaction from a wallet must carry a strictly incrementing nonce.
- * This service delegates to {@link NonceRepository} for serialized nonce assignment
- * to prevent two concurrent transactions from receiving the same nonce.
- *
- * Non-EVM chains such as Solana use a different model (recent blockhash) and do not
- * require nonce management -- for those chains, {@link NonceAssignment#notApplicable()}
- * is returned.
- *
- * Resubmission (replace-by-fee): when a transaction is resubmitted, the same
- * nonce is reused so that the new transaction replaces the stuck one in the mempool.
- */
@Slf4j
@Service
@RequiredArgsConstructor
@@ -35,14 +20,6 @@ public class NonceManager {
private final NonceRepository nonceRepository;
- /**
- * Assigns a nonce for a wallet on the given chain.
- *
- * @param walletId the wallet that will sign the transaction
- * @param chainId the target blockchain chain
- * @param isResubmit {@code true} when resubmitting a stuck transaction (reuse nonce)
- * @return the nonce assignment result
- */
public NonceAssignment assignNonce(UUID walletId, ChainId chainId, boolean isResubmit) {
if (walletId == null) {
throw new IllegalArgumentException("walletId is required");
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferCommandHandler.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferCommandHandler.java
index aa2180a3..7c63137a 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferCommandHandler.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferCommandHandler.java
@@ -34,12 +34,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Domain command handler that orchestrates blockchain transfer submission.
- *
- * Full flow: chain selection → balance reservation → nonce acquisition →
- * custody signing → persist + lifecycle events + participants + outbox event.
- */
@Slf4j
@Service
@Transactional
@@ -56,12 +50,6 @@ public class TransferCommandHandler {
private final CustodyEngine custodyEngine;
private final TransferEventPublisher transferEventPublisher;
- /**
- * Initiates a new chain transfer.
- *
- * Idempotent: if a transfer already exists for the given paymentId + transferType,
- * returns the existing transfer with {@code created=false}.
- */
public TransferResult initiateTransfer(UUID paymentId, UUID correlationId,
TransferType transferType, UUID parentTransferId,
StablecoinTicker stablecoin, BigDecimal amount,
@@ -169,9 +157,6 @@ public TransferResult initiateTransfer(UUID paymentId, UUID correlationId,
return new TransferResult(transfer, true);
}
- /**
- * Retrieves a transfer by ID.
- */
@Transactional(readOnly = true)
public ChainTransfer getTransfer(UUID transferId) {
return chainTransferRepository.findById(transferId)
@@ -179,9 +164,6 @@ public ChainTransfer getTransfer(UUID transferId) {
"Transfer not found: " + transferId));
}
- /**
- * Retrieves wallet balance details.
- */
@Transactional(readOnly = true)
public WalletBalanceDetails getWalletBalance(UUID walletId) {
var wallet = walletRepository.findById(walletId)
@@ -196,9 +178,6 @@ private void releaseBalance(WalletBalance reservedBalance, BigDecimal amount) {
walletBalanceRepository.save(released);
}
- /**
- * Wrapper for wallet + balance list used by the controller.
- */
public record WalletBalanceDetails(
Wallet wallet,
List balances
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandler.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandler.java
index ab70c26a..cd6899a6 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandler.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandler.java
@@ -9,41 +9,24 @@
import com.stablecoin.payments.custody.domain.port.ChainRpcProvider;
import com.stablecoin.payments.custody.domain.port.ChainTransferRepository;
import com.stablecoin.payments.custody.domain.port.CustodyEngine;
+import com.stablecoin.payments.custody.domain.port.IsolatedTransactionExecutor;
import com.stablecoin.payments.custody.domain.port.SignRequest;
import com.stablecoin.payments.custody.domain.port.TransferEventPublisher;
import com.stablecoin.payments.custody.domain.port.TransferLifecycleEventRepository;
import com.stablecoin.payments.custody.domain.port.TransferMonitorProperties;
import com.stablecoin.payments.custody.domain.port.WalletBalanceRepository;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.support.TransactionTemplate;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
-/**
- * Domain service responsible for monitoring in-flight chain transfers.
- *
- * Handles three categories of transfers:
- *
- * SUBMITTED — checks for receipt; if found, transitions to CONFIRMING;
- * if stuck beyond timeout, transitions to RESUBMITTING
- * CONFIRMING — checks if confirmations meet the chain's minimum;
- * if so, confirms and releases reserved balance.
- * If receipt disappears (reorg) beyond grace window, marks for resubmission.
- * RESUBMITTING — re-signs and resubmits; fails if max attempts exceeded.
- * Uses claim-before-submit pattern for crash safety.
- *
- *
- * Each transfer is processed in its own transaction to prevent partial commits
- * and isolate failures between transfers.
- */
@Slf4j
@Service
+@RequiredArgsConstructor
public class TransferMonitorCommandHandler {
private final ChainTransferRepository chainTransferRepository;
@@ -55,36 +38,8 @@ public class TransferMonitorCommandHandler {
private final TransferMonitorProperties transferMonitorProperties;
private final ChainConfirmationProperties chainConfirmationProperties;
private final Clock clock;
- private final TransactionTemplate transactionTemplate;
+ private final IsolatedTransactionExecutor isolatedTx;
- public TransferMonitorCommandHandler(
- ChainTransferRepository chainTransferRepository,
- WalletBalanceRepository walletBalanceRepository,
- TransferLifecycleEventRepository lifecycleEventRepository,
- ChainRpcProvider chainRpcProvider,
- CustodyEngine custodyEngine,
- TransferEventPublisher transferEventPublisher,
- TransferMonitorProperties transferMonitorProperties,
- ChainConfirmationProperties chainConfirmationProperties,
- Clock clock,
- PlatformTransactionManager transactionManager) {
- this.chainTransferRepository = chainTransferRepository;
- this.walletBalanceRepository = walletBalanceRepository;
- this.lifecycleEventRepository = lifecycleEventRepository;
- this.chainRpcProvider = chainRpcProvider;
- this.custodyEngine = custodyEngine;
- this.transferEventPublisher = transferEventPublisher;
- this.transferMonitorProperties = transferMonitorProperties;
- this.chainConfirmationProperties = chainConfirmationProperties;
- this.clock = clock;
- this.transactionTemplate = new TransactionTemplate(transactionManager);
- this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
- }
-
- /**
- * Polls all in-flight transfers and processes them according to their current status.
- * Each transfer is processed in its own transaction.
- */
public void monitorPendingTransfers() {
var submitted = chainTransferRepository.findByStatus(TransferStatus.SUBMITTED);
var confirming = chainTransferRepository.findByStatus(TransferStatus.CONFIRMING);
@@ -105,7 +60,7 @@ public void monitorPendingTransfers() {
for (var transfer : allTransfers) {
try {
- transactionTemplate.executeWithoutResult(status -> processTransfer(transfer));
+ isolatedTx.executeInNewTransaction(() -> processTransfer(transfer));
} catch (Exception e) {
log.error("Error monitoring transfer transferId={}: {}",
transfer.transferId(), e.getMessage(), e);
@@ -147,7 +102,6 @@ private void processSubmitted(ChainTransfer transfer) {
return;
}
- // No receipt yet — check if stuck
var stuckThreshold = Instant.now(clock).minusSeconds(transferMonitorProperties.resubmitTimeoutS());
if (stuckThreshold.isAfter(transfer.updatedAt())) {
var resubmitting = transfer.markForResubmission();
@@ -219,7 +173,6 @@ private void processResubmitting(ChainTransfer transfer) {
return;
}
- // Step 1: Claim resubmission (increment attempt count) and persist BEFORE calling custody.
// If crash occurs after custody call but before final persist, next poll will see
// the incremented attempt count and re-attempt with the same nonce (idempotent on-chain).
var claimed = transfer.claimResubmission();
@@ -227,7 +180,6 @@ private void processResubmitting(ChainTransfer transfer) {
lifecycleEventRepository.save(
TransferLifecycleEvent.record(claimed.transferId(), "RESUBMISSION_CLAIMED"));
- // Step 2: Call custody engine
var signRequest = new SignRequest(
transfer.transferId(),
transfer.chainId(),
@@ -240,7 +192,6 @@ private void processResubmitting(ChainTransfer transfer) {
);
var signResult = custodyEngine.signAndSubmit(signRequest);
- // Step 3: Complete resubmission with actual tx hash
var resubmitted = claimed.confirmResubmission(signResult.txHash());
chainTransferRepository.save(resubmitted);
lifecycleEventRepository.save(
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/FallbackAdaptersConfig.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/config/FallbackAdaptersConfig.java
similarity index 81%
rename from blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/FallbackAdaptersConfig.java
rename to blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/config/FallbackAdaptersConfig.java
index 0c175298..573a6988 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/FallbackAdaptersConfig.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/config/FallbackAdaptersConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.custody.config;
+package com.stablecoin.payments.custody.infrastructure.config;
import com.stablecoin.payments.custody.domain.model.ChainId;
import com.stablecoin.payments.custody.domain.model.StablecoinTicker;
@@ -24,12 +24,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
-/**
- * Provides fallback (dev/test) implementations for external provider ports.
- * Activated only when {@code app.fallback-adapters.enabled=true}.
- * Production adapters (activated via their own {@code @ConditionalOnProperty})
- * are used when this config is disabled.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "true")
@@ -41,32 +35,21 @@ public class FallbackAdaptersConfig {
"solana", 0.005
);
- /**
- * Fallback health provider that returns 1.0 (healthy) for all chains.
- */
@Bean
+ @ConditionalOnMissingBean
public ChainHealthProvider fallbackChainHealthProvider() {
log.info("Using fallback ChainHealthProvider (all chains healthy)");
return (ChainId chainId) -> 1.0;
}
- /**
- * Fallback fee provider with realistic defaults:
- * Base=0.01, Ethereum=2.50, Solana=0.005 USD.
- */
@Bean
+ @ConditionalOnMissingBean
public ChainFeeProvider fallbackChainFeeProvider() {
log.info("Using fallback ChainFeeProvider (static fee estimates)");
return (ChainId chainId, StablecoinTicker stablecoin) ->
DEFAULT_FEES.getOrDefault(chainId.value(), 1.0);
}
- /**
- * Fallback in-memory nonce repository for dev/test environments without PostgreSQL.
- * Uses a simple ConcurrentHashMap — no advisory locks (not needed without concurrency).
- * Gated by its own property since the real NonceManagerPersistenceAdapter is always
- * available when a database is present (e.g., integration tests with TestContainers).
- */
@Bean
@ConditionalOnProperty(name = "app.custody.nonce-repository.in-memory", havingValue = "true")
public NonceRepository fallbackNonceRepository() {
@@ -74,10 +57,6 @@ public NonceRepository fallbackNonceRepository() {
return new InMemoryNonceRepository();
}
- /**
- * Fallback custody engine for dev/test environments without Fireblocks.
- * Returns deterministic dev results.
- */
@Bean
@ConditionalOnMissingBean
public CustodyEngine fallbackCustodyEngine() {
@@ -104,11 +83,8 @@ public TransactionStatus getTransactionStatus(String txId) {
};
}
- /**
- * Fallback chain RPC provider for dev/test environments without EVM RPC nodes.
- * Returns deterministic mock data.
- */
@Bean
+ @ConditionalOnMissingBean
public ChainRpcProvider fallbackChainRpcProvider() {
log.info("Using fallback ChainRpcProvider (returns mock receipts)");
return new ChainRpcProvider() {
@@ -134,7 +110,6 @@ public BigDecimal getTokenBalance(ChainId chainId, String address, String tokenC
};
}
-
static class InMemoryNonceRepository implements NonceRepository {
private final ConcurrentHashMap nonces = new ConcurrentHashMap<>();
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/metrics/CustodyMetrics.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/metrics/CustodyMetrics.java
index cc37e62d..bf0ec178 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/metrics/CustodyMetrics.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/metrics/CustodyMetrics.java
@@ -5,20 +5,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Blockchain and Custody service.
- *
- * Tracks transfer initiation, confirmation, failure, and confirmation duration.
- */
@Component
@RequiredArgsConstructor
public class CustodyMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a chain transfer initiation event.
- */
public void recordTransferInitiated(String chain, String token) {
meterRegistry.counter("custody.transfer.initiated",
"chain", chain,
@@ -26,9 +18,6 @@ public void recordTransferInitiated(String chain, String token) {
).increment();
}
- /**
- * Records a chain transfer confirmation event.
- */
public void recordTransferConfirmed(String chain, String token) {
meterRegistry.counter("custody.transfer.confirmed",
"chain", chain,
@@ -36,9 +25,6 @@ public void recordTransferConfirmed(String chain, String token) {
).increment();
}
- /**
- * Records a chain transfer failure event.
- */
public void recordTransferFailed(String chain, String reason) {
meterRegistry.counter("custody.transfer.failed",
"chain", chain,
@@ -46,16 +32,10 @@ public void recordTransferFailed(String chain, String reason) {
).increment();
}
- /**
- * Starts a timer sample for measuring transfer confirmation duration.
- */
public Timer.Sample startConfirmationTimer() {
return Timer.start(meterRegistry);
}
- /**
- * Stops the timer sample and records the transfer confirmation duration as a histogram.
- */
public void recordConfirmationDuration(Timer.Sample sample, String chain) {
sample.stop(meterRegistry.timer("custody.transfer.confirmation.duration", "chain", chain));
}
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/ChainSelectionLogPersistenceAdapter.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/ChainSelectionLogPersistenceAdapter.java
index 5d0d1bda..8517cd7b 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/ChainSelectionLogPersistenceAdapter.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/ChainSelectionLogPersistenceAdapter.java
@@ -14,10 +14,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * JdbcTemplate-based persistence adapter for the chain_selection_log table.
- * Uses Jackson 3 for JSONB serialization of candidate evaluations.
- */
@Slf4j
@Repository
@RequiredArgsConstructor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/NonceManagerPersistenceAdapter.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/NonceManagerPersistenceAdapter.java
index 9c47bbd1..fd55362d 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/NonceManagerPersistenceAdapter.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/persistence/NonceManagerPersistenceAdapter.java
@@ -12,18 +12,6 @@
import java.util.Optional;
import java.util.UUID;
-/**
- * PostgreSQL-backed implementation of {@link NonceRepository}.
- *
- * Uses {@code pg_advisory_xact_lock} for transaction-scoped locking to serialize
- * nonce assignment per wallet. The lock key is derived from the wallet UUID via
- * {@code hashtext()} to produce a stable 32-bit integer key. The lock is automatically
- * released when the transaction commits or rolls back.
- *
- * The blocking advisory lock + upsert + atomic increment pattern guarantees
- * unique, sequential nonce assignment even under concurrent load. Concurrent
- * callers will wait until the lock is released rather than failing immediately.
- */
@Slf4j
@Repository
@RequiredArgsConstructor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java
index 9880c802..28568940 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java
@@ -101,13 +101,8 @@ SignResult signAndSubmitEvm(SignRequest request) {
var credentials = Credentials.create(properties.evmPrivateKey());
var usdcContract = chainConfig.usdcContract();
- var scaledAmount = request.amount().movePointRight(USDC_DECIMALS).stripTrailingZeros();
- if (scaledAmount.scale() > 0) {
- throw new DevCustodyException(
- "Amount has sub-minor-unit precision after scaling to %d decimals: %s"
- .formatted(USDC_DECIMALS, request.amount()));
- }
- var amountMinorUnits = scaledAmount.toBigInteger();
+ var truncatedAmount = request.amount().setScale(USDC_DECIMALS, java.math.RoundingMode.DOWN);
+ var amountMinorUnits = truncatedAmount.movePointRight(USDC_DECIMALS).toBigIntegerExact();
var data = encodeErc20Transfer(request.toAddress(), amountMinorUnits);
var nonce = request.nonce() != null ? BigInteger.valueOf(request.nonce()) : BigInteger.ZERO;
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java
new file mode 100644
index 00000000..de48eaa6
--- /dev/null
+++ b/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java
@@ -0,0 +1,24 @@
+package com.stablecoin.payments.custody.infrastructure.transaction;
+
+import com.stablecoin.payments.custody.domain.port.IsolatedTransactionExecutor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW;
+
+@Component
+public class IsolatedTransactionExecutorAdapter implements IsolatedTransactionExecutor {
+
+ private final TransactionTemplate requiresNewTx;
+
+ public IsolatedTransactionExecutorAdapter(PlatformTransactionManager transactionManager) {
+ this.requiresNewTx = new TransactionTemplate(transactionManager);
+ this.requiresNewTx.setPropagationBehavior(PROPAGATION_REQUIRES_NEW);
+ }
+
+ @Override
+ public void executeInNewTransaction(Runnable action) {
+ requiresNewTx.executeWithoutResult(status -> action.run());
+ }
+}
diff --git a/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/application/controller/TransferControllerTest.java b/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/application/controller/TransferControllerTest.java
index ac68a567..bcabb6e5 100644
--- a/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/application/controller/TransferControllerTest.java
+++ b/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/application/controller/TransferControllerTest.java
@@ -1,6 +1,6 @@
package com.stablecoin.payments.custody.application.controller;
-import com.stablecoin.payments.custody.config.SecurityConfig;
+import com.stablecoin.payments.custody.application.config.SecurityConfig;
import com.stablecoin.payments.custody.domain.exception.ChainUnavailableException;
import com.stablecoin.payments.custody.domain.exception.CustodySigningException;
import com.stablecoin.payments.custody.domain.exception.InsufficientBalanceException;
diff --git a/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandlerTest.java b/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandlerTest.java
index 2a0fc215..7d804751 100644
--- a/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandlerTest.java
+++ b/blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/domain/service/TransferMonitorCommandHandlerTest.java
@@ -8,6 +8,7 @@
import com.stablecoin.payments.custody.domain.port.ChainRpcProvider;
import com.stablecoin.payments.custody.domain.port.ChainTransferRepository;
import com.stablecoin.payments.custody.domain.port.CustodyEngine;
+import com.stablecoin.payments.custody.domain.port.IsolatedTransactionExecutor;
import com.stablecoin.payments.custody.domain.port.SignRequest;
import com.stablecoin.payments.custody.domain.port.TransferEventPublisher;
import com.stablecoin.payments.custody.domain.port.TransferLifecycleEventRepository;
@@ -19,10 +20,6 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.SimpleTransactionStatus;
import java.time.Clock;
import java.util.List;
@@ -70,21 +67,8 @@ class TransferMonitorCommandHandlerTest {
// Use system clock for the default handler — transfers are created with Instant.now()
private static final Clock SYSTEM_CLOCK = Clock.systemUTC();
- private static PlatformTransactionManager passthroughTransactionManager() {
- return new PlatformTransactionManager() {
- @Override
- public TransactionStatus getTransaction(TransactionDefinition definition) {
- return new SimpleTransactionStatus();
- }
-
- @Override
- public void commit(TransactionStatus status) {
- }
-
- @Override
- public void rollback(TransactionStatus status) {
- }
- };
+ private static IsolatedTransactionExecutor passthroughTransactionExecutor() {
+ return Runnable::run;
}
@BeforeEach
@@ -99,7 +83,7 @@ void setUp() {
defaultMonitorProperties(),
defaultChainConfirmationProperties(),
SYSTEM_CLOCK,
- passthroughTransactionManager()
+ passthroughTransactionExecutor()
);
}
@@ -203,7 +187,7 @@ void shouldMarkResubmittingWhenStuckBeyondTimeout() {
defaultMonitorProperties(),
defaultChainConfirmationProperties(),
futureClock,
- passthroughTransactionManager()
+ passthroughTransactionExecutor()
);
var transfer = aSubmittedTransferOnBase();
@@ -353,7 +337,7 @@ void shouldMarkForResubmissionWhenReceiptDisappearsBeyondGraceWindow() {
defaultMonitorProperties(),
defaultChainConfirmationProperties(),
futureClock,
- passthroughTransactionManager()
+ passthroughTransactionExecutor()
);
var transfer = aConfirmingTransferOnBase();
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainSelectionFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainSelectionFixtures.java
index 4a7a93e0..cd761435 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainSelectionFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainSelectionFixtures.java
@@ -12,9 +12,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Test fixtures for chain selection engine tests.
- */
public final class ChainSelectionFixtures {
private ChainSelectionFixtures() {}
@@ -27,57 +24,36 @@ private ChainSelectionFixtures() {}
public static final ChainId CHAIN_ETHEREUM = new ChainId("ethereum");
public static final ChainId CHAIN_SOLANA = new ChainId("solana");
- /**
- * Default weights (cost=0.4, speed=0.35, reliability=0.25).
- */
public static ChainSelectionWeights defaultWeights() {
return ChainSelectionWeights.defaults();
}
- /**
- * Base chain configuration: fast and cheap (L2).
- */
public static ChainConfig baseConfig() {
return new ChainConfig(
CHAIN_BASE, 1, 12, "ETH",
List.of("https://base-rpc.example.com"), "https://basescan.org");
}
- /**
- * Ethereum chain configuration: slow and expensive (L1).
- */
public static ChainConfig ethereumConfig() {
return new ChainConfig(
CHAIN_ETHEREUM, 32, 300, "ETH",
List.of("https://eth-rpc.example.com"), "https://etherscan.io");
}
- /**
- * Solana chain configuration: fastest and cheapest.
- */
public static ChainConfig solanaConfig() {
return new ChainConfig(
CHAIN_SOLANA, 1, 5, "SOL",
List.of("https://sol-rpc.example.com"), "https://explorer.solana.com");
}
- /**
- * A selection request with default values (no preferred chain).
- */
public static ChainSelectionRequest aSelectionRequest() {
return new ChainSelectionRequest(TRANSFER_ID, USDC, AMOUNT, null);
}
- /**
- * A selection request with a preferred chain.
- */
public static ChainSelectionRequest aSelectionRequestWithPreferredChain(String preferredChain) {
return new ChainSelectionRequest(TRANSFER_ID, USDC, AMOUNT, preferredChain);
}
- /**
- * A selection request with a specific amount.
- */
public static ChainSelectionRequest aSelectionRequestWithAmount(BigDecimal amount) {
return new ChainSelectionRequest(TRANSFER_ID, USDC, amount, null);
}
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java
index f0dec245..0cf27cbb 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java
@@ -24,9 +24,6 @@ private ChainTransferFixtures() {}
public static final StablecoinTicker USDC = StablecoinTicker.of("USDC");
public static final BigDecimal AMOUNT = new BigDecimal("1000.00");
- /**
- * JSON body for a FORWARD transfer request on Base chain.
- */
public static String aTransferRequestJson() {
return """
{
@@ -41,9 +38,6 @@ public static String aTransferRequestJson() {
""".formatted(PAYMENT_ID, CORRELATION_ID, TO_ADDRESS);
}
- /**
- * A fresh PENDING transfer (FORWARD type, no parent).
- */
public static ChainTransfer aPendingTransfer() {
return ChainTransfer.initiate(
PAYMENT_ID, CORRELATION_ID, TransferType.FORWARD, null,
@@ -51,9 +45,6 @@ public static ChainTransfer aPendingTransfer() {
);
}
- /**
- * A PENDING RETURN transfer with parentTransferId.
- */
public static ChainTransfer aPendingReturnTransfer() {
return ChainTransfer.initiate(
PAYMENT_ID, CORRELATION_ID, TransferType.RETURN, PARENT_TRANSFER_ID,
@@ -61,53 +52,32 @@ public static ChainTransfer aPendingReturnTransfer() {
);
}
- /**
- * A transfer in CHAIN_SELECTED state.
- */
public static ChainTransfer aChainSelectedTransfer() {
return aPendingTransfer().selectChain(CHAIN_BASE);
}
- /**
- * A transfer in SIGNING state.
- */
public static ChainTransfer aSigningTransfer() {
return aChainSelectedTransfer().startSigning(42L);
}
- /**
- * A transfer in SUBMITTED state (attemptCount = 1).
- */
public static ChainTransfer aSubmittedTransfer() {
return aSigningTransfer().submit(TX_HASH);
}
- /**
- * A transfer in CONFIRMING state.
- */
public static ChainTransfer aConfirmingTransfer() {
return aSubmittedTransfer().startConfirming();
}
- /**
- * A transfer in CONFIRMED (terminal) state.
- */
public static ChainTransfer aConfirmedTransfer() {
return aConfirmingTransfer().confirm(
12345L, 15, new BigDecimal("0.002100"), new BigDecimal("25.5")
);
}
- /**
- * A transfer in RESUBMITTING state.
- */
public static ChainTransfer aResubmittingTransfer() {
return aSubmittedTransfer().markForResubmission();
}
- /**
- * A transfer in FAILED (terminal) state.
- */
public static ChainTransfer aFailedTransfer() {
return aChainSelectedTransfer().fail("Insufficient gas", "GAS_LIMIT_EXCEEDED");
}
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/CustodyEngineFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/CustodyEngineFixtures.java
index d80d6665..d75f66e0 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/CustodyEngineFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/CustodyEngineFixtures.java
@@ -14,9 +14,6 @@ private CustodyEngineFixtures() {}
public static final UUID TRANSFER_ID = UUID.fromString("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
public static final String VAULT_ACCOUNT_ID = "42";
- /**
- * A sign request for Base USDC transfer.
- */
public static SignRequest aSignRequest() {
return new SignRequest(
TRANSFER_ID,
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/DevCustodyFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/DevCustodyFixtures.java
index 229b8e53..7dfcb955 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/DevCustodyFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/DevCustodyFixtures.java
@@ -19,9 +19,6 @@ private DevCustodyFixtures() {}
public static final String SOL_FROM_ADDRESS = "FbGeZS8LiPCZiFpFwdUUeF2yxXtSsdfJoHTsVMvM8STh";
public static final String SOL_TO_ADDRESS = "9WzDXwBbmPg2WjM1CdGUgJb4Mqp7TAUKM3MEPQDwKrSM";
- /**
- * A sign request for Base USDC transfer via dev custody.
- */
public static SignRequest aDevSignRequest() {
return new SignRequest(
DEV_TRANSFER_ID,
@@ -35,9 +32,6 @@ public static SignRequest aDevSignRequest() {
);
}
- /**
- * A sign request for Ethereum USDC transfer via dev custody.
- */
public static SignRequest aDevEthereumSignRequest() {
return new SignRequest(
DEV_TRANSFER_ID,
@@ -51,9 +45,6 @@ public static SignRequest aDevEthereumSignRequest() {
);
}
- /**
- * A sign request for Solana USDC transfer via dev custody.
- */
public static SignRequest aDevSolanaSignRequest() {
return new SignRequest(
DEV_TRANSFER_ID,
@@ -67,9 +58,6 @@ public static SignRequest aDevSolanaSignRequest() {
);
}
- /**
- * A sign request for an unsupported chain (stellar) — used for error path testing.
- */
public static SignRequest aDevUnsupportedChainSignRequest() {
return new SignRequest(
DEV_TRANSFER_ID,
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/NonceFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/NonceFixtures.java
index e336a340..2051a356 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/NonceFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/NonceFixtures.java
@@ -19,23 +19,14 @@ private NonceFixtures() {}
public static final ChainId CHAIN_SOLANA = new ChainId("solana");
public static final ChainId CHAIN_POLYGON = new ChainId("polygon");
- /**
- * A nonce assignment for a fresh EVM nonce (incremented).
- */
public static NonceAssignment anIncrementedAssignment(long nonce) {
return new NonceAssignment(nonce, INCREMENTED);
}
- /**
- * A nonce assignment for a resubmitted transaction (reused nonce).
- */
public static NonceAssignment aReusedAssignment(long nonce) {
return new NonceAssignment(nonce, REUSED);
}
- /**
- * A nonce assignment for a non-nonce chain (e.g. Solana).
- */
public static NonceAssignment aNotApplicableAssignment() {
return new NonceAssignment(null, NOT_APPLICABLE);
}
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/TransferMonitorFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/TransferMonitorFixtures.java
index af19bed1..9c7f6e8f 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/TransferMonitorFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/TransferMonitorFixtures.java
@@ -15,9 +15,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Test fixtures for transfer monitor and balance sync tests.
- */
public final class TransferMonitorFixtures {
private TransferMonitorFixtures() {}
@@ -45,9 +42,6 @@ private TransferMonitorFixtures() {}
"solana", 1
);
- /**
- * Default transfer monitor properties: 120s resubmit timeout, 3 max attempts, 300s confirming timeout.
- */
public static TransferMonitorProperties defaultMonitorProperties() {
return new TransferMonitorProperties() {
@Override
@@ -67,10 +61,6 @@ public int confirmingTimeoutS() {
};
}
- /**
- * Default chain confirmation properties with Base (1), Ethereum (32), Solana (1).
- * Throws IllegalStateException for unknown chains (fail-fast).
- */
public static ChainConfirmationProperties defaultChainConfirmationProperties() {
return chainId -> {
var confirmations = CHAIN_CONFIRMATIONS.get(chainId);
@@ -81,16 +71,10 @@ public static ChainConfirmationProperties defaultChainConfirmationProperties() {
};
}
- /**
- * Default token contract resolver mapping USDC to test contract addresses.
- */
public static TokenContractResolver defaultTokenContractResolver() {
return (chainId, stablecoin) -> USDC_BASE_CONTRACT;
}
- /**
- * A SUBMITTED transfer on Base chain (ready for monitoring).
- */
public static ChainTransfer aSubmittedTransferOnBase() {
return ChainTransfer.initiate(
PAYMENT_ID, CORRELATION_ID, TransferType.FORWARD, null,
@@ -98,9 +82,6 @@ public static ChainTransfer aSubmittedTransferOnBase() {
).selectChain(CHAIN_BASE).startSigning(42L).submit(TX_HASH);
}
- /**
- * A SUBMITTED transfer on Ethereum chain (requires 32 confirmations).
- */
public static ChainTransfer aSubmittedTransferOnEthereum() {
return ChainTransfer.initiate(
PAYMENT_ID, CORRELATION_ID, TransferType.FORWARD, null,
@@ -108,23 +89,14 @@ public static ChainTransfer aSubmittedTransferOnEthereum() {
).selectChain(CHAIN_ETHEREUM).startSigning(42L).submit(TX_HASH);
}
- /**
- * A CONFIRMING transfer on Base chain.
- */
public static ChainTransfer aConfirmingTransferOnBase() {
return aSubmittedTransferOnBase().startConfirming();
}
- /**
- * A RESUBMITTING transfer on Base chain (attempt 1).
- */
public static ChainTransfer aResubmittingTransfer() {
return aSubmittedTransferOnBase().markForResubmission();
}
- /**
- * A RESUBMITTING transfer that has reached max attempts (3).
- */
public static ChainTransfer aMaxAttemptsResubmittingTransfer() {
var transfer = aSubmittedTransferOnBase(); // attempt 1
transfer = transfer.markForResubmission();
@@ -135,30 +107,18 @@ public static ChainTransfer aMaxAttemptsResubmittingTransfer() {
return transfer;
}
- /**
- * A successful transaction receipt.
- */
public static TransactionReceipt aSuccessfulReceipt() {
return new TransactionReceipt(TX_HASH, RECEIPT_BLOCK, true, GAS_USED, GAS_PRICE, 10);
}
- /**
- * A failed (reverted) transaction receipt.
- */
public static TransactionReceipt aFailedReceipt() {
return new TransactionReceipt(TX_HASH, RECEIPT_BLOCK, false, GAS_USED, GAS_PRICE, 10);
}
- /**
- * A sign result for resubmission.
- */
public static SignResult aResubmitSignResult() {
return new SignResult(RESUBMIT_TX_HASH, "custody-tx-resubmit");
}
- /**
- * A wallet balance with reserved funds (suitable for confirmDebit).
- */
public static WalletBalance aBalanceWithReserved() {
var balance = WalletBalance.initialize(FROM_WALLET_ID, CHAIN_BASE, USDC);
var synced = balance.syncFromChain(new BigDecimal("5000.00"), 50L);
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletBalanceFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletBalanceFixtures.java
index 40eaaf80..677d5d73 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletBalanceFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletBalanceFixtures.java
@@ -15,17 +15,10 @@ private WalletBalanceFixtures() {}
public static final ChainId CHAIN_BASE = new ChainId("base");
public static final StablecoinTicker USDC = StablecoinTicker.of("USDC");
- /**
- * A freshly initialized balance with all zeros.
- */
public static WalletBalance aZeroBalance() {
return WalletBalance.initialize(WALLET_ID, CHAIN_BASE, USDC);
}
- /**
- * A balance with specific available and reserved amounts.
- * Achieved by syncing from chain and then reserving.
- */
public static WalletBalance aBalanceWith(BigDecimal available, BigDecimal reserved) {
var total = available.add(reserved);
var synced = aZeroBalance().syncFromChain(total, 100L);
diff --git a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletFixtures.java b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletFixtures.java
index 35b8d410..21dcd276 100644
--- a/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletFixtures.java
+++ b/blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/WalletFixtures.java
@@ -19,9 +19,6 @@ private WalletFixtures() {}
public static final String VAULT_ACCOUNT_ID = "vault-001";
public static final StablecoinTicker USDC = StablecoinTicker.of("USDC");
- /**
- * An active wallet with default values.
- */
public static Wallet anActiveWallet() {
return Wallet.create(
CHAIN_BASE, ADDRESS, ADDRESS_CHECKSUM,
@@ -30,9 +27,6 @@ public static Wallet anActiveWallet() {
);
}
- /**
- * A deactivated wallet.
- */
public static Wallet aDeactivatedWallet() {
return anActiveWallet().deactivate();
}
diff --git a/buildSrc/src/main/kotlin/stablebridge.service.gradle.kts b/buildSrc/src/main/kotlin/stablebridge.service.gradle.kts
index 1d3cb259..6bf28ad5 100644
--- a/buildSrc/src/main/kotlin/stablebridge.service.gradle.kts
+++ b/buildSrc/src/main/kotlin/stablebridge.service.gradle.kts
@@ -188,7 +188,9 @@ tasks.withType {
// ---------------------------------------------------------------------------
tasks.withType {
jvmArgs("-Dnet.bytebuddy.experimental=true")
- useJUnitPlatform()
+ useJUnitPlatform {
+ excludeTags("sandbox")
+ }
testLogging {
events("passed", "skipped", "failed")
showExceptions = true
@@ -199,7 +201,7 @@ tasks.withType {
}
// ---------------------------------------------------------------------------
-// JaCoCo
+// JaCoCo — disabled until JaCoCo supports Java 25 (0.8.14 is incompatible)
// ---------------------------------------------------------------------------
jacoco {
toolVersion = "0.8.14"
@@ -207,9 +209,16 @@ jacoco {
tasks.test {
configure {
- excludes = listOf("sun.*", "jdk.*", "com.sun.*", "java.*", "javax.*")
+ isEnabled = false
}
- finalizedBy(tasks.jacocoTestReport)
+}
+
+tasks.jacocoTestReport {
+ enabled = false
+}
+
+tasks.jacocoTestCoverageVerification {
+ enabled = false
}
val baseJacocoExclusions = listOf(
diff --git a/compliance-travel-rule/compliance-travel-rule-api/src/main/java/com/stablecoin/payments/compliance/api/model/package-info.java b/compliance-travel-rule/compliance-travel-rule-api/src/main/java/com/stablecoin/payments/compliance/api/model/package-info.java
index c0d0bb8e..ad6a203f 100644
--- a/compliance-travel-rule/compliance-travel-rule-api/src/main/java/com/stablecoin/payments/compliance/api/model/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule-api/src/main/java/com/stablecoin/payments/compliance/api/model/package-info.java
@@ -1,6 +1 @@
-/**
- * Shared API models (request/response DTOs) for the Compliance and Travel Rule service.
- *
- * DTOs will be defined here as API endpoints are implemented.
- */
package com.stablecoin.payments.compliance.api.model;
diff --git a/compliance-travel-rule/compliance-travel-rule-client/src/main/java/com/stablecoin/payments/compliance/client/package-info.java b/compliance-travel-rule/compliance-travel-rule-client/src/main/java/com/stablecoin/payments/compliance/client/package-info.java
index ad807e94..d37e6b3d 100644
--- a/compliance-travel-rule/compliance-travel-rule-client/src/main/java/com/stablecoin/payments/compliance/client/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule-client/src/main/java/com/stablecoin/payments/compliance/client/package-info.java
@@ -1,6 +1 @@
-/**
- * Feign client for inter-service calls to the Compliance and Travel Rule service.
- *
- * The Feign client interface will be defined here as API endpoints are implemented.
- */
package com.stablecoin.payments.compliance.client;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/RiskScoringConfig.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/RiskScoringConfig.java
index d4c4b42b..b153654b 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/RiskScoringConfig.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/RiskScoringConfig.java
@@ -7,10 +7,6 @@
import java.util.Map;
-/**
- * Configuration for the risk scoring engine.
- * Maps {@code app.compliance.risk-scoring.*} properties to {@link RiskScoringWeights}.
- */
@Configuration
public class RiskScoringConfig {
@@ -37,10 +33,6 @@ public RiskScoringWeights riskScoringWeights(RiskScoringProperties props) {
.build();
}
- /**
- * Mutable properties bean for Spring Boot's {@code @ConfigurationProperties} binding.
- * Defaults match {@link RiskScoringWeights#defaults()}.
- */
public static class RiskScoringProperties {
private int kycTier1Penalty = RiskScoringWeights.DEFAULT_KYC_TIER1_PENALTY;
private int highValuePenalty = RiskScoringWeights.DEFAULT_HIGH_VALUE_PENALTY;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/security/SecurityConfig.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/SecurityConfig.java
similarity index 96%
rename from payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/security/SecurityConfig.java
rename to compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/SecurityConfig.java
index 36ff9a1a..c8663932 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/security/SecurityConfig.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.orchestrator.application.security;
+package com.stablecoin.payments.compliance.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/filter/IdempotencyKeyFilter.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/filter/IdempotencyKeyFilter.java
index 198b1d24..4ca0dfd8 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/filter/IdempotencyKeyFilter.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/filter/IdempotencyKeyFilter.java
@@ -14,10 +14,6 @@
import java.io.IOException;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) — excluding actuator endpoints.
- */
@Slf4j
@Component
@Order(2)
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/service/ComplianceCheckApplicationService.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/service/ComplianceCheckApplicationService.java
index 9940806c..6d9d39e1 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/service/ComplianceCheckApplicationService.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/service/ComplianceCheckApplicationService.java
@@ -12,10 +12,6 @@
import java.util.UUID;
-/**
- * Thin application service that maps API DTOs to domain objects and delegates
- * all business logic to the {@link ComplianceCheckCommandHandler}.
- */
@Service
@RequiredArgsConstructor
public class ComplianceCheckApplicationService {
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/ComplianceCheck.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/ComplianceCheck.java
index 7b4c5436..f78f8687 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/ComplianceCheck.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/ComplianceCheck.java
@@ -34,15 +34,6 @@
import static com.stablecoin.payments.compliance.domain.model.ComplianceCheckTrigger.TRAVEL_RULE_COMPLETE;
import static com.stablecoin.payments.compliance.domain.model.ComplianceCheckTrigger.TRAVEL_RULE_FAILED;
-/**
- * Aggregate root for a compliance check on a payment.
- *
- * Enforces the compliance check pipeline via an internal state machine:
- * {@code PENDING -> KYC_IN_PROGRESS -> SANCTIONS_SCREENING -> AML_SCREENING ->
- * RISK_SCORING -> TRAVEL_RULE_PACKAGING -> PASSED}.
- *
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record ComplianceCheck(
UUID checkId,
@@ -92,11 +83,7 @@ public record ComplianceCheck(
new StateTransition<>(SANCTIONS_HIT, ESCALATE_MANUAL_REVIEW, MANUAL_REVIEW)
));
- // ── Factory Method ──────────────────────────────────────────────
- /**
- * Creates a new compliance check in PENDING status.
- */
public static ComplianceCheck initiate(UUID paymentId, UUID senderId, UUID recipientId,
Money sourceAmount, String sourceCountry,
String targetCountry, String targetCurrency) {
@@ -140,11 +127,7 @@ public static ComplianceCheck initiate(UUID paymentId, UUID senderId, UUID recip
.build();
}
- // ── State Transition Methods ────────────────────────────────────
- /**
- * Starts KYC verification. Transitions PENDING -> KYC_IN_PROGRESS.
- */
public ComplianceCheck startKyc() {
assertNotTerminal();
var nextStatus = STATE_MACHINE.transition(status, START_KYC);
@@ -153,9 +136,6 @@ public ComplianceCheck startKyc() {
.build();
}
- /**
- * Records a passing KYC result. Transitions KYC_IN_PROGRESS -> SANCTIONS_SCREENING.
- */
public ComplianceCheck passKyc(KycResult result) {
assertNotTerminal();
if (result == null) {
@@ -168,9 +148,6 @@ public ComplianceCheck passKyc(KycResult result) {
.build();
}
- /**
- * Records a failing KYC result. Transitions KYC_IN_PROGRESS -> FAILED.
- */
public ComplianceCheck failKyc(KycResult result) {
assertNotTerminal();
if (result == null) {
@@ -187,9 +164,6 @@ public ComplianceCheck failKyc(KycResult result) {
.build();
}
- /**
- * Records a clear sanctions screening result. Transitions SANCTIONS_SCREENING -> AML_SCREENING.
- */
public ComplianceCheck sanctionsClear(SanctionsResult result) {
assertNotTerminal();
if (result == null) {
@@ -202,9 +176,6 @@ public ComplianceCheck sanctionsClear(SanctionsResult result) {
.build();
}
- /**
- * Records a sanctions hit. Transitions SANCTIONS_SCREENING -> SANCTIONS_HIT.
- */
public ComplianceCheck sanctionsHitDetected(SanctionsResult result) {
assertNotTerminal();
if (result == null) {
@@ -221,9 +192,6 @@ public ComplianceCheck sanctionsHitDetected(SanctionsResult result) {
.build();
}
- /**
- * Records a clear AML screening result. Transitions AML_SCREENING -> RISK_SCORING.
- */
public ComplianceCheck amlClear(AmlResult result) {
assertNotTerminal();
if (result == null) {
@@ -236,9 +204,6 @@ public ComplianceCheck amlClear(AmlResult result) {
.build();
}
- /**
- * Records an AML flag. Transitions AML_SCREENING -> MANUAL_REVIEW.
- */
public ComplianceCheck amlFlagged(AmlResult result) {
assertNotTerminal();
if (result == null) {
@@ -255,9 +220,6 @@ public ComplianceCheck amlFlagged(AmlResult result) {
.build();
}
- /**
- * Records the risk score. Transitions RISK_SCORING -> TRAVEL_RULE_PACKAGING.
- */
public ComplianceCheck riskScored(RiskScore score) {
assertNotTerminal();
if (score == null) {
@@ -270,10 +232,6 @@ public ComplianceCheck riskScored(RiskScore score) {
.build();
}
- /**
- * Records a CRITICAL risk score. Transitions RISK_SCORING -> MANUAL_REVIEW.
- * CRITICAL scores block payment and require manual review to override.
- */
public ComplianceCheck riskCritical(RiskScore score) {
assertNotTerminal();
if (score == null) {
@@ -290,10 +248,6 @@ public ComplianceCheck riskCritical(RiskScore score) {
.build();
}
- /**
- * Completes the travel rule packaging (or skips if null).
- * Transitions TRAVEL_RULE_PACKAGING -> PASSED.
- */
public ComplianceCheck completeTravelRule(TravelRulePackage travelRule) {
assertNotTerminal();
var nextStatus = STATE_MACHINE.transition(status, TRAVEL_RULE_COMPLETE);
@@ -305,9 +259,6 @@ public ComplianceCheck completeTravelRule(TravelRulePackage travelRule) {
.build();
}
- /**
- * Fails travel rule transmission. Transitions TRAVEL_RULE_PACKAGING -> FAILED.
- */
public ComplianceCheck failTravelRule(String reason) {
assertNotTerminal();
var nextStatus = STATE_MACHINE.transition(status, TRAVEL_RULE_FAILED);
@@ -320,9 +271,6 @@ public ComplianceCheck failTravelRule(String reason) {
.build();
}
- /**
- * Escalates a sanctions hit to manual review. Transitions SANCTIONS_HIT -> MANUAL_REVIEW.
- */
public ComplianceCheck escalateToManualReview() {
var nextStatus = STATE_MACHINE.transition(status, ESCALATE_MANUAL_REVIEW);
return toBuilder()
@@ -331,26 +279,15 @@ public ComplianceCheck escalateToManualReview() {
.build();
}
- // ── Query Methods ───────────────────────────────────────────────
- /**
- * Returns true if this check is in a terminal state (PASSED, FAILED, SANCTIONS_HIT, MANUAL_REVIEW).
- */
public boolean isTerminal() {
return TERMINAL_STATES.contains(status);
}
- /**
- * Returns true if a given trigger can be applied from the current state.
- */
public boolean canApply(ComplianceCheckTrigger trigger) {
return STATE_MACHINE.canTransition(status, trigger);
}
- /**
- * Returns true if all sub-checks have passed (KYC, sanctions, AML, risk score).
- * Does not include travel rule — that may be skipped for low-value payments.
- */
public boolean allSubChecksPassed() {
return kycResult != null
&& kycResult.senderStatus() == KycStatus.VERIFIED
@@ -362,7 +299,6 @@ public boolean allSubChecksPassed() {
&& riskScore != null;
}
- // ── Invariant Guards ────────────────────────────────────────────
private void assertNotTerminal() {
if (isTerminal()) {
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringContext.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringContext.java
index ea053b89..e1ad3164 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringContext.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringContext.java
@@ -2,10 +2,6 @@
import lombok.Builder;
-/**
- * Groups all input data needed for risk scoring.
- * Separates the scoring inputs from the ComplianceCheck aggregate.
- */
@Builder(toBuilder = true)
public record RiskScoringContext(
ComplianceCheck check,
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringWeights.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringWeights.java
index 9a4bca67..c2bbcc6a 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringWeights.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/RiskScoringWeights.java
@@ -4,11 +4,6 @@
import java.util.Map;
-/**
- * Configurable weights for each risk scoring factor.
- * Weights represent the maximum penalty points each factor can contribute.
- * Total score is capped at 100.
- */
@Builder(toBuilder = true)
public record RiskScoringWeights(
int kycTier1Penalty,
@@ -50,10 +45,6 @@ public static RiskScoringWeights defaults() {
.build();
}
- /**
- * Returns the corridor-specific risk score override, or 0 if not configured.
- * Key format: "US-NG", "US-IR", etc.
- */
public int corridorRisk(String sourceCountry, String targetCountry) {
var key = sourceCountry + "-" + targetCountry;
return corridorRiskScores.getOrDefault(key, 0);
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/package-info.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/package-info.java
index b813ad21..857d4249 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/model/package-info.java
@@ -1,6 +1 @@
-/**
- * Domain models for the Compliance and Travel Rule bounded context.
- *
- * Aggregate root: {@code ComplianceCheck} (STA-81).
- */
package com.stablecoin.payments.compliance.domain.model;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/port/package-info.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/port/package-info.java
index 6b721453..0c9eb232 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/port/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/port/package-info.java
@@ -1,14 +1 @@
-/**
- * Outbound port interfaces for the Compliance domain.
- *
- * Persistence ports: {@link com.stablecoin.payments.compliance.domain.port.ComplianceCheckRepository},
- * {@link com.stablecoin.payments.compliance.domain.port.CustomerRiskProfileRepository}.
- *
- * Provider ports (Anti-Corruption Layer): {@link com.stablecoin.payments.compliance.domain.port.KycProvider},
- * {@link com.stablecoin.payments.compliance.domain.port.SanctionsProvider},
- * {@link com.stablecoin.payments.compliance.domain.port.AmlProvider},
- * {@link com.stablecoin.payments.compliance.domain.port.TravelRuleProvider}.
- *
- * Event port: {@link com.stablecoin.payments.compliance.domain.port.EventPublisher}.
- */
package com.stablecoin.payments.compliance.domain.port;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckCommandHandler.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckCommandHandler.java
index fbefc3fa..6f810c6b 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckCommandHandler.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckCommandHandler.java
@@ -29,13 +29,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Domain command handler that orchestrates the compliance check pipeline.
- *
- * Coordinates KYC, sanctions, AML, risk scoring, and travel rule sub-checks
- * through provider ports, persists the aggregate via the repository port,
- * and publishes domain events via the event publisher port.
- */
@Slf4j
@Service
@Transactional
@@ -52,11 +45,6 @@ public class ComplianceCheckCommandHandler {
private final RiskScoringService riskScoringService;
private final EventPublisher eventPublisher;
- /**
- * Initiates a new compliance check for a payment.
- *
- * @throws DuplicatePaymentException if a check already exists for the payment
- */
public ComplianceCheck initiateCheck(UUID paymentId, UUID senderId, UUID recipientId,
Money sourceAmount, String sourceCountry,
String targetCountry, String targetCurrency) {
@@ -82,22 +70,12 @@ public ComplianceCheck initiateCheck(UUID paymentId, UUID senderId, UUID recipie
return saved;
}
- /**
- * Retrieves a compliance check by its ID.
- *
- * @throws CheckNotFoundException if no check exists with the given ID
- */
@Transactional(readOnly = true)
public ComplianceCheck getCheck(UUID checkId) {
return checkRepository.findById(checkId)
.orElseThrow(() -> new CheckNotFoundException(checkId));
}
- /**
- * Retrieves a customer risk profile by customer ID.
- *
- * @throws CustomerNotFoundException if no profile exists for the customer
- */
@Transactional(readOnly = true)
public CustomerRiskProfile getCustomerRiskProfile(UUID customerId) {
return profileRepository.findByCustomerId(customerId)
@@ -105,28 +83,24 @@ public CustomerRiskProfile getCustomerRiskProfile(UUID customerId) {
}
private ComplianceCheck runPipeline(ComplianceCheck check) {
- // Step 1: KYC verification
var kycResult = kycProvider.verify(check.senderId(), check.recipientId());
check = complianceCheckService.recordKycResult(check, kycResult);
if (check.isTerminal()) {
return check;
}
- // Step 2: Sanctions screening
var sanctionsResult = sanctionsProvider.screen(check.senderId(), check.recipientId());
check = complianceCheckService.recordSanctionsResult(check, sanctionsResult);
if (check.isTerminal()) {
return check;
}
- // Step 3: AML analysis
var amlResult = amlProvider.analyze(check.senderId(), check.recipientId());
check = complianceCheckService.recordAmlResult(check, amlResult);
if (check.isTerminal()) {
return check;
}
- // Step 4: Risk scoring
var profile = profileRepository.findByCustomerId(check.senderId()).orElse(null);
var context = RiskScoringContext.builder()
.check(check)
@@ -139,7 +113,6 @@ private ComplianceCheck runPipeline(ComplianceCheck check) {
return check;
}
- // Step 5: Travel rule packaging
if (complianceCheckService.requiresTravelRule(check)) {
check = handleTravelRule(check);
} else {
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckService.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckService.java
index d011b068..d08ba704 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckService.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/ComplianceCheckService.java
@@ -15,9 +15,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Domain service that orchestrates the compliance check pipeline.
- */
@Slf4j
@Service
public class ComplianceCheckService {
@@ -71,11 +68,6 @@ public ComplianceCheck recordAmlResult(ComplianceCheck check, AmlResult amlResul
return check.amlClear(amlResult);
}
- /**
- * Records the risk score on the compliance check.
- * CRITICAL band blocks the payment and routes to MANUAL_REVIEW.
- * All other bands proceed to TRAVEL_RULE_PACKAGING.
- */
public ComplianceCheck recordRiskScore(ComplianceCheck check, RiskScore riskScore) {
log.info("Recording risk score for check={}, score={}, band={}",
check.checkId(), riskScore.score(), riskScore.band());
@@ -99,10 +91,6 @@ public ComplianceCheck skipTravelRule(ComplianceCheck check) {
return check.completeTravelRule(null);
}
- /**
- * Determines whether a compliance check requires Travel Rule data packaging
- * based on the FATF threshold (>= $1,000 / EUR 1,000).
- */
public boolean requiresTravelRule(ComplianceCheck check) {
var amount = check.sourceAmount();
var currency = check.sourceCurrency();
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/RiskScoringService.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/RiskScoringService.java
index a1493863..7d7c0492 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/RiskScoringService.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/RiskScoringService.java
@@ -12,11 +12,6 @@
import java.util.ArrayList;
import java.util.List;
-/**
- * Domain service that calculates a 0-100 risk score from multiple weighted factors.
- * Factors: KYC tier, high-value amount, AML flags, cross-border, corridor risk,
- * new customer, amount-to-limit ratio, transaction velocity.
- */
@Slf4j
@Service
public class RiskScoringService {
@@ -32,21 +27,6 @@ public RiskScoringService(RiskScoringWeights weights) {
this.weights = weights;
}
- /**
- * Calculates a risk score from multiple factors using the scoring context.
- *
- * Factors considered (each contributes configurable penalty points):
- *
- * KYC tier (TIER_1 adds penalty — lowest verification level)
- * Transaction amount (high-value threshold: >= $10,000)
- * AML flags from screening
- * Cross-border transaction
- * Corridor risk (configurable per country pair)
- * New customer (no existing risk profile)
- * Amount relative to tier limits (>= 80% of per-txn limit)
- * Transaction velocity (>= 10 recent transactions)
- *
- */
public RiskScore calculateScore(RiskScoringContext context) {
var check = context.check();
log.info("Calculating risk score for check={}", check.checkId());
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/package-info.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/package-info.java
index adbc73f6..df2b89c7 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/service/package-info.java
@@ -1,14 +1 @@
-/**
- * Domain services for compliance orchestration.
- *
- * {@link com.stablecoin.payments.compliance.domain.service.ComplianceCheckCommandHandler} orchestrates
- * the full compliance pipeline (KYC, sanctions, AML, risk scoring, travel rule), coordinates
- * persistence and event publishing through domain ports.
- *
- * {@link com.stablecoin.payments.compliance.domain.service.ComplianceCheckService} provides
- * state-transition helper methods for individual sub-checks.
- *
- * {@link com.stablecoin.payments.compliance.domain.service.RiskScoringService} calculates risk scores
- * from multiple factors.
- */
package com.stablecoin.payments.compliance.domain.service;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/statemachine/package-info.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/statemachine/package-info.java
index 1d6dcc9e..66c784d7 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/statemachine/package-info.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/domain/statemachine/package-info.java
@@ -1,10 +1 @@
-/**
- * Generic state machine for {@code ComplianceCheck} status transitions.
- *
- * {@link com.stablecoin.payments.compliance.domain.statemachine.StateMachine} validates
- * transitions between {@link com.stablecoin.payments.compliance.domain.model.ComplianceCheckStatus}
- * states using {@link com.stablecoin.payments.compliance.domain.model.ComplianceCheckTrigger} triggers.
- *
- * @see com.stablecoin.payments.compliance.domain.model.ComplianceCheck
- */
package com.stablecoin.payments.compliance.domain.statemachine;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/config/FallbackAdaptersConfig.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/config/FallbackAdaptersConfig.java
index 06dcaf1f..92704c8b 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/config/FallbackAdaptersConfig.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/config/FallbackAdaptersConfig.java
@@ -10,6 +10,7 @@
import com.stablecoin.payments.compliance.domain.port.SanctionsProvider;
import com.stablecoin.payments.compliance.domain.port.TravelRuleProvider;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -24,6 +25,7 @@
public class FallbackAdaptersConfig {
@Bean
+ @ConditionalOnMissingBean
public KycProvider fallbackKycProvider() {
log.warn("Using fallback KYC provider — all verifications will return VERIFIED");
return (senderId, recipientId) -> KycResult.builder()
@@ -38,6 +40,7 @@ public KycProvider fallbackKycProvider() {
}
@Bean
+ @ConditionalOnMissingBean
public SanctionsProvider fallbackSanctionsProvider() {
log.warn("Using fallback sanctions provider — no hits will be returned");
return (senderId, recipientId) -> SanctionsResult.builder()
@@ -54,6 +57,7 @@ public SanctionsProvider fallbackSanctionsProvider() {
}
@Bean
+ @ConditionalOnMissingBean
public AmlProvider fallbackAmlProvider() {
log.warn("Using fallback AML provider — no flags will be returned");
return (senderId, recipientId) -> AmlResult.builder()
@@ -66,6 +70,7 @@ public AmlProvider fallbackAmlProvider() {
}
@Bean
+ @ConditionalOnMissingBean
public TravelRuleProvider fallbackTravelRuleProvider() {
log.warn("Using fallback travel rule provider — transmissions will be simulated");
return travelRulePackage -> "fallback-tr-ref-" + UUID.randomUUID();
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/metrics/ComplianceMetrics.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/metrics/ComplianceMetrics.java
index cada8ff2..e37bad29 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/metrics/ComplianceMetrics.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/metrics/ComplianceMetrics.java
@@ -5,54 +5,34 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Compliance and Travel Rule service.
- *
- *
Tracks compliance check outcomes, sanctions hits, KYC results, and pipeline duration.
- */
@Component
@RequiredArgsConstructor
public class ComplianceMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a compliance check completion with its result.
- */
public void recordCheckCompleted(String result) {
meterRegistry.counter("compliance.check.completed",
"result", result
).increment();
}
- /**
- * Records a sanctions hit event.
- */
public void recordSanctionsHit(String provider) {
meterRegistry.counter("compliance.sanctions.hit",
"provider", provider
).increment();
}
- /**
- * Records a KYC verification result.
- */
public void recordKycResult(String status) {
meterRegistry.counter("compliance.kyc.result",
"status", status
).increment();
}
- /**
- * Starts a timer sample for measuring compliance check pipeline duration.
- */
public Timer.Sample startCheckTimer() {
return Timer.start(meterRegistry);
}
- /**
- * Stops the timer sample and records the compliance check duration.
- */
public void recordCheckDuration(Timer.Sample sample) {
sample.stop(meterRegistry.timer("compliance.check.duration"));
}
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/JaroWinklerSimilarity.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/JaroWinklerSimilarity.java
index 302e28a3..77bb71b4 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/JaroWinklerSimilarity.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/JaroWinklerSimilarity.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.compliance.infrastructure.provider.ofacsdn;
-/**
- * Self-contained Jaro-Winkler distance implementation for fuzzy name matching.
- * Returns a similarity score between 0.0 (no match) and 1.0 (exact match).
- */
final class JaroWinklerSimilarity {
private static final double WINKLER_PREFIX_WEIGHT = 0.1;
@@ -12,10 +8,6 @@ final class JaroWinklerSimilarity {
private JaroWinklerSimilarity() {
}
- /**
- * Computes the Jaro-Winkler similarity between two strings after normalization.
- * Normalization: lowercase, trim, collapse multiple whitespace to single space.
- */
static double similarity(String first, String second) {
if (first == null || second == null) {
return 0.0;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/OfacSdnSanctionsAdapter.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/OfacSdnSanctionsAdapter.java
index 27cc84c1..bd888d5f 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/OfacSdnSanctionsAdapter.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/OfacSdnSanctionsAdapter.java
@@ -36,9 +36,6 @@ public OfacSdnSanctionsAdapter(OfacSdnProperties properties, SdnListDownloader d
this.downloader = downloader;
}
- // TODO: SanctionsProvider port currently passes UUIDs, not party names.
- // OFAC SDN matching requires actual names. Once the port is extended with
- // senderName/recipientName (domain-level change), replace UUID.toString() below.
@Override
public SanctionsResult screen(UUID senderId, UUID recipientId) {
log.info("[OFAC-SDN] Screening sender={} recipient={}", senderId, recipientId);
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnListDownloader.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnListDownloader.java
index c4e50762..ff274ac1 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnListDownloader.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnListDownloader.java
@@ -17,11 +17,6 @@
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
-/**
- * Separate Spring bean for downloading the OFAC SDN list so that
- * {@link Retry @Retry} and {@link CircuitBreaker @CircuitBreaker} annotations
- * are intercepted by Spring AOP (self-invocation within the same bean bypasses proxies).
- */
@Slf4j
@Component
class SdnListDownloader {
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnXmlParser.java b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnXmlParser.java
index d29ea9e9..839e2d05 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnXmlParser.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnXmlParser.java
@@ -8,10 +8,6 @@
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
-/**
- * Parses OFAC SDN XML into {@link SdnEntry} records using StAX streaming parser
- * for memory-efficient processing of large XML files.
- */
final class SdnXmlParser {
private SdnXmlParser() {
diff --git a/compliance-travel-rule/compliance-travel-rule/src/testFixtures/java/com/stablecoin/payments/compliance/fixtures/ComplianceCheckFixtures.java b/compliance-travel-rule/compliance-travel-rule/src/testFixtures/java/com/stablecoin/payments/compliance/fixtures/ComplianceCheckFixtures.java
index 0eecd542..a4107a32 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/testFixtures/java/com/stablecoin/payments/compliance/fixtures/ComplianceCheckFixtures.java
+++ b/compliance-travel-rule/compliance-travel-rule/src/testFixtures/java/com/stablecoin/payments/compliance/fixtures/ComplianceCheckFixtures.java
@@ -137,9 +137,6 @@ public static KycResult aKycTier1Result(UUID checkId) {
.build();
}
- /**
- * Walks a check to RISK_SCORING status with configurable KYC tier, amount, countries, and AML result.
- */
public static ComplianceCheck aCheckInRiskScoringStatus(KycTier kycTier, BigDecimal amount,
String sourceCurrency,
String sourceCountry, String targetCountry,
@@ -185,9 +182,6 @@ public static ComplianceCheck aCheckInRiskScoringStatus(KycTier kycTier, BigDeci
.amlClear(amlResult);
}
- /**
- * Creates a RiskScoringContext with the given check, optional profile, and transaction count.
- */
public static RiskScoringContext aRiskScoringContext(ComplianceCheck check,
CustomerRiskProfile profile,
int recentTransactionCount) {
diff --git a/docker-compose.sandbox.yml b/docker-compose.sandbox.yml
index 4ca9c366..1b8a128a 100644
--- a/docker-compose.sandbox.yml
+++ b/docker-compose.sandbox.yml
@@ -113,7 +113,7 @@ services:
limits:
memory: 512M
healthcheck:
- test: ["CMD-SHELL", "temporal operator cluster health 2>/dev/null | grep -q 'OK' || exit 1"]
+ test: ["CMD-SHELL", "tctl --address $(hostname -i):7233 cluster health 2>/dev/null | grep -q SERVING || exit 1"]
interval: 15s
timeout: 10s
retries: 15
@@ -154,7 +154,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8083/compliance/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8083' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -191,7 +191,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8084/fx/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8084' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -229,7 +229,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8085/on-ramp/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8085' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -269,7 +269,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8086/custody/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8086' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -310,7 +310,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8087/off-ramp/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8087' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -342,7 +342,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8088/ledger/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8088' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
@@ -388,7 +388,7 @@ services:
memory: 512M
cpus: "0.5"
healthcheck:
- test: ["CMD-SHELL", "wget -q --spider http://localhost:8082/orchestrator/actuator/health || exit 1"]
+ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/8082' || exit 1"]
interval: 10s
timeout: 5s
retries: 60
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/ClockConfig.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/ClockConfig.java
similarity index 62%
rename from fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/ClockConfig.java
rename to fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/ClockConfig.java
index d319792b..94daf68e 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/ClockConfig.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/ClockConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.offramp.config;
+package com.stablecoin.payments.offramp.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -6,10 +6,6 @@
import java.time.Clock;
-/**
- * Provides a {@link Clock} bean for time-dependent domain logic.
- * Always available — FallbackAdaptersConfig's Clock is secondary via {@code @ConditionalOnMissingBean}.
- */
@Configuration
public class ClockConfig {
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/PayoutMonitorConfig.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/PayoutMonitorConfig.java
similarity index 80%
rename from fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/PayoutMonitorConfig.java
rename to fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/PayoutMonitorConfig.java
index 4a36cbb0..f49cee8b 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/PayoutMonitorConfig.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/PayoutMonitorConfig.java
@@ -1,11 +1,8 @@
-package com.stablecoin.payments.offramp.config;
+package com.stablecoin.payments.offramp.application.config;
import com.stablecoin.payments.offramp.domain.port.PayoutMonitorProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
-/**
- * Binds payout monitor settings from {@code app.payout.monitor.*} to the domain port.
- */
@ConfigurationProperties(prefix = "app.payout.monitor")
public record PayoutMonitorConfig(
boolean enabled,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/SecurityConfig.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/SecurityConfig.java
similarity index 97%
rename from fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/SecurityConfig.java
rename to fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/SecurityConfig.java
index ba716a2b..80657f32 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/SecurityConfig.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.offramp.config;
+package com.stablecoin.payments.offramp.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PartnerWebhookController.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PartnerWebhookController.java
index 65097360..32e15263 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PartnerWebhookController.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PartnerWebhookController.java
@@ -22,12 +22,6 @@
import java.time.format.DateTimeParseException;
import java.util.Map;
-/**
- * Thin HTTP handler for off-ramp partner webhook callbacks.
- *
- * Validates the HMAC signature, parses the partner event JSON,
- * and delegates to {@link WebhookCommandHandler}.
- */
@Slf4j
@RestController
@RequestMapping("/internal/webhooks/partner")
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PayoutController.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PayoutController.java
index 1bb40139..c4252250 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PayoutController.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/PayoutController.java
@@ -27,11 +27,6 @@
import java.util.UUID;
-/**
- * REST controller for payout order lifecycle management.
- *
- * Thin HTTP handler that delegates all business logic to {@link PayoutCommandHandler}.
- */
@Slf4j
@RestController
@RequestMapping("/v1/payouts")
@@ -40,12 +35,6 @@ public class PayoutController {
private final PayoutCommandHandler payoutCommandHandler;
- /**
- * Initiates a new payout order.
- *
- * Idempotent: returns 200 OK with the existing order if the same paymentId
- * is submitted again. Returns 202 ACCEPTED on first creation.
- */
@PostMapping
public ResponseEntity initiatePayout(
@Valid @RequestBody PayoutRequest request) {
@@ -80,18 +69,12 @@ public ResponseEntity initiatePayout(
return ResponseEntity.status(status).body(response);
}
- /**
- * Retrieves a payout order by its ID.
- */
@GetMapping("/{payoutId}")
public PayoutResponse getPayout(@PathVariable UUID payoutId) {
log.info("GET /v1/payouts/{}", payoutId);
return toPayoutResponse(payoutCommandHandler.getPayout(payoutId));
}
- /**
- * Retrieves a payout order by its associated payment ID.
- */
@GetMapping
public PayoutResponse getPayoutByPaymentId(@RequestParam UUID paymentId) {
log.info("GET /v1/payouts?paymentId={}", paymentId);
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.java
index 7f6bc32e..9ad7307d 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.java
@@ -14,10 +14,6 @@
import java.io.IOException;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) — excluding actuator endpoints.
- */
@Slf4j
@Component
@Order(2)
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/scheduler/PayoutMonitorJob.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/scheduler/PayoutMonitorJob.java
index 3362dd00..e259b9a2 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/scheduler/PayoutMonitorJob.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/scheduler/PayoutMonitorJob.java
@@ -7,13 +7,6 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
-/**
- * Scheduled job that delegates to {@link PayoutMonitorCommandHandler}
- * for stuck payout detection and escalation.
- *
- * Polls every 5 minutes by default (configurable via {@code app.payout.monitor.interval-ms}).
- * Disabled in tests via {@code app.payout.monitor.enabled=false}.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotFoundException.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotFoundException.java
index 53882da3..787e1172 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotFoundException.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotFoundException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when a payout order cannot be found by the given identifier.
- */
public class PayoutNotFoundException extends RuntimeException {
public static final String ERROR_CODE = "FR-1003";
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotRefundableException.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotRefundableException.java
index c7cd77b8..59d0ea9e 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotRefundableException.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutNotRefundableException.java
@@ -2,10 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when a payout order cannot be refunded (e.g., stablecoin already redeemed,
- * fiat already disbursed).
- */
public class PayoutNotRefundableException extends RuntimeException {
public static final String ERROR_CODE = "FR-2001";
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutPartnerException.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutPartnerException.java
index 8996e591..f6accff6 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutPartnerException.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/PayoutPartnerException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when an off-ramp partner interaction fails (e.g., Modulr SEPA transfer failure).
- */
public class PayoutPartnerException extends RuntimeException {
public static final String ERROR_CODE = "FR-2003";
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/RedemptionFailedException.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/RedemptionFailedException.java
index 18e685ec..51f350f2 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/RedemptionFailedException.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/exception/RedemptionFailedException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when stablecoin redemption fails.
- */
public class RedemptionFailedException extends RuntimeException {
public static final String ERROR_CODE = "FR-2002";
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/OffRampTransaction.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/OffRampTransaction.java
index 0077b895..9ed54f64 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/OffRampTransaction.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/OffRampTransaction.java
@@ -7,12 +7,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Entity recording off-ramp partner transaction events (audit trail).
- *
- * Each interaction with an off-ramp partner (Modulr, CurrencyCloud, Safaricom, etc.)
- * creates a new OffRampTransaction record for auditability.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record OffRampTransaction(
UUID offRampTxnId,
@@ -26,9 +20,6 @@ public record OffRampTransaction(
Instant receivedAt
) {
- /**
- * Creates a new OffRampTransaction.
- */
public static OffRampTransaction create(UUID payoutId, String partnerName,
String eventType, BigDecimal amount,
String currency, String status,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/PayoutOrder.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/PayoutOrder.java
index e4c4a03c..d01c9c8c 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/PayoutOrder.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/PayoutOrder.java
@@ -32,18 +32,6 @@
import static com.stablecoin.payments.offramp.domain.model.PayoutTrigger.MARK_PAYOUT_PROCESSING;
import static com.stablecoin.payments.offramp.domain.model.PayoutTrigger.START_REDEMPTION;
-/**
- * Aggregate root for a fiat off-ramp payout order.
- *
- * Enforces the payout lifecycle via an internal state machine:
- * {@code PENDING -> REDEEMING -> REDEEMED -> PAYOUT_INITIATED -> PAYOUT_PROCESSING -> COMPLETED}.
- *
- * For HOLD_STABLECOIN type: {@code PENDING -> STABLECOIN_HELD -> COMPLETED}.
- *
- * Failure paths lead to REDEMPTION_FAILED or PAYOUT_FAILED, both of which can escalate to MANUAL_REVIEW.
- *
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record PayoutOrder(
UUID payoutId,
@@ -74,37 +62,28 @@ public record PayoutOrder(
private static final Set TERMINAL_STATES = Set.of(COMPLETED, MANUAL_REVIEW);
- /** Fiat payout tolerance: fiat_amount must match redeemed * fx_rate within ±0.01 */
private static final BigDecimal FIAT_AMOUNT_TOLERANCE = new BigDecimal("0.01");
private static final StateMachine STATE_MACHINE =
new StateMachine<>(List.of(
- // -- Fiat happy path ------------------------------------------
new StateTransition<>(PENDING, START_REDEMPTION, REDEEMING),
new StateTransition<>(REDEEMING, COMPLETE_REDEMPTION, REDEEMED),
new StateTransition<>(REDEEMED, INITIATE_PAYOUT, PAYOUT_INITIATED),
new StateTransition<>(PAYOUT_INITIATED, MARK_PAYOUT_PROCESSING, PAYOUT_PROCESSING),
new StateTransition<>(PAYOUT_PROCESSING, COMPLETE_PAYOUT, COMPLETED),
- // -- Redemption failure ----------------------------------------
new StateTransition<>(REDEEMING, FAIL_REDEMPTION, REDEMPTION_FAILED),
new StateTransition<>(REDEMPTION_FAILED, ESCALATE_MANUAL_REVIEW, MANUAL_REVIEW),
- // -- Payout failure --------------------------------------------
new StateTransition<>(PAYOUT_INITIATED, FAIL_PAYOUT, PAYOUT_FAILED),
new StateTransition<>(PAYOUT_PROCESSING, FAIL_PAYOUT, PAYOUT_FAILED),
new StateTransition<>(PAYOUT_FAILED, ESCALATE_MANUAL_REVIEW, MANUAL_REVIEW),
- // -- Hold stablecoin path --------------------------------------
new StateTransition<>(PENDING, HOLD_STABLECOIN, STABLECOIN_HELD),
new StateTransition<>(STABLECOIN_HELD, COMPLETE_HOLD, COMPLETED)
));
- // -- Factory Method ---------------------------------------------------
- /**
- * Creates a new payout order in PENDING state.
- */
public static PayoutOrder create(UUID paymentId, UUID correlationId, UUID transferId,
PayoutType payoutType, StablecoinTicker stablecoin,
BigDecimal redeemedAmount, String targetCurrency,
@@ -175,11 +154,7 @@ public static PayoutOrder create(UUID paymentId, UUID correlationId, UUID transf
.build();
}
- // -- State Transition Methods -----------------------------------------
- /**
- * Starts the stablecoin redemption process. Transitions PENDING -> REDEEMING.
- */
public PayoutOrder startRedemption() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, START_REDEMPTION);
@@ -189,12 +164,6 @@ public PayoutOrder startRedemption() {
.build();
}
- /**
- * Completes the redemption and records the fiat amount received.
- * Transitions REDEEMING -> REDEEMED.
- *
- * Invariant: fiatAmount must match redeemedAmount * appliedFxRate within ±0.01 tolerance.
- */
public PayoutOrder completeRedemption(BigDecimal receivedFiatAmount) {
assertNotTerminal();
if (receivedFiatAmount == null || receivedFiatAmount.compareTo(BigDecimal.ZERO) <= 0) {
@@ -210,9 +179,6 @@ public PayoutOrder completeRedemption(BigDecimal receivedFiatAmount) {
.build();
}
- /**
- * Fails the redemption. Transitions REDEEMING -> REDEMPTION_FAILED.
- */
public PayoutOrder failRedemption(String reason) {
var nextState = STATE_MACHINE.transition(status, FAIL_REDEMPTION);
return toBuilder()
@@ -223,11 +189,6 @@ public PayoutOrder failRedemption(String reason) {
.build();
}
- /**
- * Initiates the fiat payout with a partner. Transitions REDEEMED -> PAYOUT_INITIATED.
- *
- * Invariant: cannot initiate payout unless stablecoin has been redeemed (status == REDEEMED).
- */
public PayoutOrder initiatePayout(String partnerRef) {
assertNotTerminal();
if (partnerRef == null || partnerRef.isBlank()) {
@@ -241,9 +202,6 @@ public PayoutOrder initiatePayout(String partnerRef) {
.build();
}
- /**
- * Marks the payout as processing (partner acknowledged). Transitions PAYOUT_INITIATED -> PAYOUT_PROCESSING.
- */
public PayoutOrder markPayoutProcessing() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, MARK_PAYOUT_PROCESSING);
@@ -253,9 +211,6 @@ public PayoutOrder markPayoutProcessing() {
.build();
}
- /**
- * Completes the payout. Transitions PAYOUT_PROCESSING -> COMPLETED.
- */
public PayoutOrder completePayout(String partnerRef, Instant settledAt) {
assertNotTerminal();
if (settledAt == null) {
@@ -270,9 +225,6 @@ public PayoutOrder completePayout(String partnerRef, Instant settledAt) {
.build();
}
- /**
- * Fails the payout. Can be triggered from PAYOUT_INITIATED or PAYOUT_PROCESSING.
- */
public PayoutOrder failPayout(String reason) {
var nextState = STATE_MACHINE.transition(status, FAIL_PAYOUT);
return toBuilder()
@@ -283,9 +235,6 @@ public PayoutOrder failPayout(String reason) {
.build();
}
- /**
- * Escalates to manual review. Can be triggered from REDEMPTION_FAILED or PAYOUT_FAILED.
- */
public PayoutOrder escalateToManualReview() {
var nextState = STATE_MACHINE.transition(status, ESCALATE_MANUAL_REVIEW);
return toBuilder()
@@ -294,10 +243,6 @@ public PayoutOrder escalateToManualReview() {
.build();
}
- /**
- * Holds stablecoin without redemption. Transitions PENDING -> STABLECOIN_HELD.
- * Only valid for HOLD_STABLECOIN payout type.
- */
public PayoutOrder holdStablecoin() {
assertNotTerminal();
if (payoutType != PayoutType.HOLD_STABLECOIN) {
@@ -311,9 +256,6 @@ public PayoutOrder holdStablecoin() {
.build();
}
- /**
- * Completes the hold. Transitions STABLECOIN_HELD -> COMPLETED.
- */
public PayoutOrder completeHold() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, COMPLETE_HOLD);
@@ -323,23 +265,15 @@ public PayoutOrder completeHold() {
.build();
}
- // -- Query Methods ----------------------------------------------------
- /**
- * Returns true if this payout order is in a terminal state (COMPLETED or MANUAL_REVIEW).
- */
public boolean isTerminal() {
return TERMINAL_STATES.contains(status);
}
- /**
- * Returns true if a given trigger can be applied from the current state.
- */
public boolean canApply(PayoutTrigger trigger) {
return STATE_MACHINE.canTransition(status, trigger);
}
- // -- Invariant Guards -------------------------------------------------
private void assertNotTerminal() {
if (isTerminal()) {
@@ -349,9 +283,6 @@ private void assertNotTerminal() {
}
}
- /**
- * Validates that the fiat amount matches redeemedAmount * appliedFxRate within tolerance.
- */
private void validateFiatAmountTolerance(BigDecimal receivedFiatAmount) {
var expectedFiat = redeemedAmount.multiply(appliedFxRate);
var difference = receivedFiatAmount.subtract(expectedFiat).abs();
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinRedemption.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinRedemption.java
index acc484b0..19fa29c8 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinRedemption.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinRedemption.java
@@ -7,12 +7,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Entity representing a stablecoin redemption for a payout order.
- *
- * Created when Circle (or another redemption provider) confirms the
- * conversion from stablecoin to fiat currency.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record StablecoinRedemption(
UUID redemptionId,
@@ -26,9 +20,6 @@ public record StablecoinRedemption(
Instant redeemedAt
) {
- /**
- * Creates a new StablecoinRedemption.
- */
public static StablecoinRedemption create(UUID payoutId, StablecoinTicker stablecoin,
BigDecimal redeemedAmount, BigDecimal fiatReceived,
String fiatCurrency, String partner,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinTicker.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinTicker.java
index a9f51c6f..406fbdae 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinTicker.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/model/StablecoinTicker.java
@@ -31,9 +31,6 @@ private record StablecoinInfo(String issuer, int decimals) {}
}
}
- /**
- * Factory that auto-fills issuer and decimals from the ticker.
- */
public static StablecoinTicker of(String ticker) {
return new StablecoinTicker(ticker, null, 0);
}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/IsolatedTransactionExecutor.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/IsolatedTransactionExecutor.java
new file mode 100644
index 00000000..27bafcbb
--- /dev/null
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/IsolatedTransactionExecutor.java
@@ -0,0 +1,6 @@
+package com.stablecoin.payments.offramp.domain.port;
+
+public interface IsolatedTransactionExecutor {
+
+ void executeInNewTransaction(Runnable action);
+}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutMonitorProperties.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutMonitorProperties.java
index ec759fb5..7040458a 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutMonitorProperties.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutMonitorProperties.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.offramp.domain.port;
-/**
- * Domain port for payout monitor configuration.
- * Implemented by application-layer config (e.g. {@code PayoutMonitorConfig}).
- */
public interface PayoutMonitorProperties {
int stuckThresholdMinutes();
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutPartnerGateway.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutPartnerGateway.java
index a2116d14..7e415276 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutPartnerGateway.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutPartnerGateway.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.offramp.domain.port;
-/**
- * Port for fiat payout via off-ramp partners (e.g., Modulr SEPA, CurrencyCloud).
- * Initiates the actual fiat disbursement to the recipient's bank or mobile money account.
- */
public interface PayoutPartnerGateway {
PayoutResult initiatePayout(PayoutRequest request);
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutRequest.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutRequest.java
index 238d1980..1b4f095d 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutRequest.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/PayoutRequest.java
@@ -8,12 +8,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Port DTO for initiating a fiat payout with an off-ramp partner.
- *
- * This is different from the API PayoutRequest — this is the domain's
- * outbound request to the partner gateway.
- */
public record PayoutRequest(
UUID payoutId,
BigDecimal fiatAmount,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/RedemptionGateway.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/RedemptionGateway.java
index c031681d..4039950c 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/RedemptionGateway.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/RedemptionGateway.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.offramp.domain.port;
-/**
- * Port for stablecoin redemption (e.g., Circle USDC redemption).
- * Converts stablecoins to fiat currency.
- */
public interface RedemptionGateway {
RedemptionResult redeem(RedemptionRequest request);
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/WebhookSignatureValidator.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/WebhookSignatureValidator.java
index 6c333ab4..748d9e34 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/WebhookSignatureValidator.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/port/WebhookSignatureValidator.java
@@ -1,19 +1,6 @@
package com.stablecoin.payments.offramp.domain.port;
-/**
- * Port interface for webhook signature validation.
- *
- * Implemented by partner-specific adapters (e.g., Modulr) to verify
- * the authenticity of incoming webhook requests using HMAC signatures.
- */
public interface WebhookSignatureValidator {
- /**
- * Validates the webhook signature against the raw payload.
- *
- * @param payload the raw request body
- * @param signature the signature header value
- * @return {@code true} if the signature is valid
- */
boolean isValid(String payload, String signature);
}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PartnerWebhookCommand.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PartnerWebhookCommand.java
index 69bbdec8..f04418a9 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PartnerWebhookCommand.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PartnerWebhookCommand.java
@@ -3,20 +3,6 @@
import java.math.BigDecimal;
import java.time.Instant;
-/**
- * Command representing a parsed webhook notification from an off-ramp partner.
- *
- * @param eventId unique event ID from the partner (for audit)
- * @param eventType event type: "payment.settled" or "payment.failed"
- * @param partnerName the partner name (e.g., "modulr")
- * @param partnerReference the partner's payment reference
- * @param amount the settlement amount
- * @param currency the settlement currency
- * @param status the payment status string from the partner
- * @param settledAt the settlement timestamp (null for failures)
- * @param failureReason failure reason (null for success)
- * @param rawPayload the raw JSON payload for audit trail
- */
public record PartnerWebhookCommand(
String eventId,
String eventType,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutCommandHandler.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutCommandHandler.java
index cb8d1428..dc480b8a 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutCommandHandler.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutCommandHandler.java
@@ -28,12 +28,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Domain command handler for payout order operations.
- *
- * Orchestrates: idempotency check → create order → redeem stablecoin →
- * initiate partner payout → record audit trail → publish events.
- */
@Slf4j
@Service
@Transactional
@@ -47,18 +41,6 @@ public class PayoutCommandHandler {
private final PayoutPartnerGateway payoutPartnerGateway;
private final PayoutEventPublisher eventPublisher;
- /**
- * Initiates a new payout order for a payment.
- *
- * Idempotent: if a payout order already exists for the given paymentId,
- * returns the existing order with {@code created = false}.
- *
- * For HOLD_STABLECOIN type: skips redemption and payout, transitions directly
- * to STABLECOIN_HELD → COMPLETED.
- *
- * For FIAT type: redeems stablecoin via RedemptionGateway, then initiates
- * fiat payout via PayoutPartnerGateway.
- */
public PayoutResult initiatePayout(UUID paymentId, UUID correlationId, UUID transferId,
PayoutType payoutType, StablecoinTicker stablecoin,
BigDecimal redeemedAmount, String targetCurrency,
@@ -164,25 +146,11 @@ public PayoutResult initiatePayout(UUID paymentId, UUID correlationId, UUID tran
return new PayoutResult(order, true);
}
- /**
- * Retrieves a payout order by its ID.
- *
- * @param payoutId the payout order identifier
- * @return the payout order
- * @throws PayoutNotFoundException if the order is not found
- */
public PayoutOrder getPayout(UUID payoutId) {
return payoutOrderRepository.findById(payoutId)
.orElseThrow(() -> new PayoutNotFoundException(payoutId));
}
- /**
- * Retrieves a payout order by its associated payment ID.
- *
- * @param paymentId the payment identifier
- * @return the payout order
- * @throws PayoutNotFoundException if the order is not found
- */
public PayoutOrder getPayoutByPaymentId(UUID paymentId) {
return payoutOrderRepository.findByPaymentId(paymentId)
.orElseThrow(() -> new PayoutNotFoundException(paymentId.toString()));
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandler.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandler.java
index c3e3d46f..6ba52d72 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandler.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandler.java
@@ -2,14 +2,13 @@
import com.stablecoin.payments.offramp.domain.event.FiatPayoutFailedEvent;
import com.stablecoin.payments.offramp.domain.model.PayoutStatus;
+import com.stablecoin.payments.offramp.domain.port.IsolatedTransactionExecutor;
import com.stablecoin.payments.offramp.domain.port.PayoutEventPublisher;
import com.stablecoin.payments.offramp.domain.port.PayoutMonitorProperties;
import com.stablecoin.payments.offramp.domain.port.PayoutOrderRepository;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.support.DefaultTransactionDefinition;
-import org.springframework.transaction.support.TransactionTemplate;
import java.time.Clock;
import java.time.temporal.ChronoUnit;
@@ -18,46 +17,18 @@
import static com.stablecoin.payments.offramp.domain.model.PayoutStatus.PAYOUT_INITIATED;
import static com.stablecoin.payments.offramp.domain.model.PayoutStatus.PAYOUT_PROCESSING;
-import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW;
-
-/**
- * Detects stuck payout orders (PAYOUT_INITIATED or PAYOUT_PROCESSING beyond the
- * configured threshold) and escalates them to PAYOUT_FAILED → MANUAL_REVIEW.
- *
- * Each stuck payout is processed in its own transaction to prevent one failure
- * from rolling back the entire batch. The order is re-fetched inside the
- * transaction to guard against TOCTOU races (e.g. webhook settling concurrently).
- */
+
@Slf4j
@Service
+@RequiredArgsConstructor
public class PayoutMonitorCommandHandler {
private final PayoutOrderRepository orderRepository;
private final PayoutEventPublisher eventPublisher;
private final PayoutMonitorProperties monitorProperties;
- private final TransactionTemplate requiresNewTx;
+ private final IsolatedTransactionExecutor isolatedTx;
private final Clock clock;
- public PayoutMonitorCommandHandler(
- PayoutOrderRepository orderRepository,
- PayoutEventPublisher eventPublisher,
- PayoutMonitorProperties monitorProperties,
- PlatformTransactionManager transactionManager,
- Clock clock) {
- this.orderRepository = orderRepository;
- this.eventPublisher = eventPublisher;
- this.monitorProperties = monitorProperties;
- this.clock = clock;
-
- var txDef = new DefaultTransactionDefinition();
- txDef.setPropagationBehavior(PROPAGATION_REQUIRES_NEW);
- this.requiresNewTx = new TransactionTemplate(transactionManager, txDef);
- }
-
- /**
- * Scans for stuck payouts and escalates each to MANUAL_REVIEW.
- * Called by {@code PayoutMonitorJob} on a fixed schedule.
- */
public void detectAndEscalateStuckPayouts() {
var threshold = clock.instant().minus(monitorProperties.stuckThresholdMinutes(), ChronoUnit.MINUTES);
@@ -80,8 +51,8 @@ public void detectAndEscalateStuckPayouts() {
for (var order : allStuck) {
try {
- requiresNewTx.executeWithoutResult(
- status -> escalateStuckPayout(order.payoutId(), order.status()));
+ isolatedTx.executeInNewTransaction(
+ () -> escalateStuckPayout(order.payoutId(), order.status()));
} catch (Exception ex) {
log.error("Failed to escalate payout {} — will retry next cycle",
order.payoutId(), ex);
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutResult.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutResult.java
index b76df6df..9cf7c7bb 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutResult.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/PayoutResult.java
@@ -2,8 +2,4 @@
import com.stablecoin.payments.offramp.domain.model.PayoutOrder;
-/**
- * Wrapper returned by {@link PayoutCommandHandler#initiatePayout}
- * so the controller can distinguish 202 ACCEPTED (new) vs 200 OK (idempotent replay).
- */
public record PayoutResult(PayoutOrder order, boolean created) {}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/WebhookCommandHandler.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/WebhookCommandHandler.java
index bbac0c36..ef43edad 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/WebhookCommandHandler.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/service/WebhookCommandHandler.java
@@ -19,12 +19,6 @@
import static com.stablecoin.payments.offramp.domain.service.PartnerWebhookCommand.EVENT_PAYMENT_FAILED;
import static com.stablecoin.payments.offramp.domain.service.PartnerWebhookCommand.EVENT_PAYMENT_SETTLED;
-/**
- * Domain command handler that processes partner webhook notifications.
- *
- * Orchestrates: lookup order by partnerReference -> check idempotency ->
- * transition state -> record OffRampTransaction -> publish event via outbox.
- */
@Slf4j
@Service
@Transactional
@@ -35,15 +29,6 @@ public class WebhookCommandHandler {
private final OffRampTransactionRepository transactionRepository;
private final PayoutEventPublisher eventPublisher;
- /**
- * Processes a webhook command from a partner settlement notification.
- *
- * Idempotent: if the payout order is already in COMPLETED or MANUAL_REVIEW
- * state, the webhook is skipped.
- *
- * @param command the parsed webhook command
- * @return the payout order (updated or unchanged if idempotent)
- */
public PayoutOrder handleWebhook(PartnerWebhookCommand command) {
log.info("Processing partner webhook eventId={} type={} partnerRef={}",
command.eventId(), command.eventType(), command.partnerReference());
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/FallbackAdaptersConfig.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/config/FallbackAdaptersConfig.java
similarity index 88%
rename from fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/FallbackAdaptersConfig.java
rename to fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/config/FallbackAdaptersConfig.java
index dfc5d97b..b052d053 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/FallbackAdaptersConfig.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/config/FallbackAdaptersConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.offramp.config;
+package com.stablecoin.payments.offramp.infrastructure.config;
import com.stablecoin.payments.offramp.domain.port.PayoutPartnerGateway;
import com.stablecoin.payments.offramp.domain.port.PayoutResult;
@@ -15,12 +15,6 @@
import java.time.Clock;
import java.util.UUID;
-/**
- * Provides fallback (dev/test) implementations of outbound ports.
- * Activated only when {@code app.fallback-adapters.enabled=true}.
- * Real adapters are registered by provider-specific configurations
- * under {@code infrastructure/provider//}.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "true")
@@ -29,6 +23,7 @@ public class FallbackAdaptersConfig {
private static final BigDecimal DEV_FEE_MULTIPLIER = BigDecimal.ONE;
@Bean
+ @ConditionalOnMissingBean
public RedemptionGateway fallbackRedemptionGateway(Clock clock) {
return request -> {
var fiatReceived = request.amount().multiply(request.appliedFxRate());
@@ -44,6 +39,7 @@ public RedemptionGateway fallbackRedemptionGateway(Clock clock) {
}
@Bean
+ @ConditionalOnMissingBean
public PayoutPartnerGateway fallbackPayoutPartnerGateway() {
return request -> {
log.warn("[FALLBACK-PAYOUT] Using dev payout gateway payoutId={} amount={} {}",
@@ -57,6 +53,7 @@ public PayoutPartnerGateway fallbackPayoutPartnerGateway() {
}
@Bean
+ @ConditionalOnMissingBean
public WebhookSignatureValidator fallbackWebhookSignatureValidator() {
return (payload, signature) -> {
log.warn("[FALLBACK-WEBHOOK] Using dev webhook validator — accepting all signatures");
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/metrics/OffRampMetrics.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/metrics/OffRampMetrics.java
index 6161fb82..7da8a781 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/metrics/OffRampMetrics.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/metrics/OffRampMetrics.java
@@ -4,20 +4,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Fiat Off-Ramp service.
- *
- * Tracks payout initiation, completion, failure, and redemption events.
- */
@Component
@RequiredArgsConstructor
public class OffRampMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a payout initiation event.
- */
public void recordPayoutInitiated(String partner, String currency) {
meterRegistry.counter("offramp.payout.initiated",
"partner", partner,
@@ -25,9 +17,6 @@ public void recordPayoutInitiated(String partner, String currency) {
).increment();
}
- /**
- * Records a payout completion event.
- */
public void recordPayoutCompleted(String partner, String currency) {
meterRegistry.counter("offramp.payout.completed",
"partner", partner,
@@ -35,9 +24,6 @@ public void recordPayoutCompleted(String partner, String currency) {
).increment();
}
- /**
- * Records a payout failure event.
- */
public void recordPayoutFailed(String partner, String reason) {
meterRegistry.counter("offramp.payout.failed",
"partner", partner,
@@ -45,9 +31,6 @@ public void recordPayoutFailed(String partner, String reason) {
).increment();
}
- /**
- * Records a stablecoin redemption completion event.
- */
public void recordRedemptionCompleted(String provider) {
meterRegistry.counter("offramp.redemption.completed",
"provider", provider
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutRequest.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutRequest.java
index 7572964c..97aeb9f9 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutRequest.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutRequest.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.offramp.infrastructure.provider.circle;
-/**
- * ACL DTO for Circle Business Account Payout API request.
- * Package-private — never leaks to domain.
- */
record CirclePayoutRequest(
String idempotencyKey,
CircleDestination destination,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutResponse.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutResponse.java
index 25466b93..e320242e 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutResponse.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CirclePayoutResponse.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.offramp.infrastructure.provider.circle;
-/**
- * ACL DTO for Circle Business Account Payout API response.
- * Package-private — never leaks to domain.
- */
record CirclePayoutResponse(CirclePayoutData data) {
record CirclePayoutData(
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentRequest.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentRequest.java
index fae2a5cb..32345cfe 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentRequest.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentRequest.java
@@ -2,20 +2,6 @@
import java.math.BigDecimal;
-/**
- * ACL DTO for Modulr Create Payment API request.
- * Package-private — never leaks to domain.
- *
- * Maps to: {@code POST /api-sandbox-token/payments}
- *
- * @param sourceAccountId Modulr source account ID
- * @param amount payment amount
- * @param currency ISO 4217 currency code (e.g., EUR, GBP)
- * @param reference payment reference visible to beneficiary
- * @param externalReference idempotency key / external correlation ID
- * @param destination beneficiary destination details
- * @param permittedScheme payment scheme (e.g., SEPA_CREDIT, FPS)
- */
record ModulrPaymentRequest(
String sourceAccountId,
BigDecimal amount,
@@ -26,16 +12,6 @@ record ModulrPaymentRequest(
String permittedScheme
) {
- /**
- * Beneficiary destination — supports IBAN (SEPA) and SCAN (FPS) types.
- * Null fields are excluded from JSON serialization.
- *
- * @param type destination type: "IBAN" or "SCAN"
- * @param iban beneficiary IBAN (SEPA only)
- * @param name beneficiary name
- * @param sortCode sort code (FPS/SCAN only)
- * @param accountNumber account number (FPS/SCAN only)
- */
record ModulrDestination(
String type,
String iban,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentResponse.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentResponse.java
index 782c70ee..6a22ea87 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentResponse.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPaymentResponse.java
@@ -2,18 +2,6 @@
import java.math.BigDecimal;
-/**
- * ACL DTO for Modulr Create Payment API response.
- * Package-private — never leaks to domain.
- *
- * @param id Modulr payment ID (e.g., "P120003AQM")
- * @param status payment status (e.g., "VALIDATED")
- * @param createdDate ISO 8601 creation timestamp
- * @param externalReference the external reference echoed back
- * @param approvalStatus approval status (e.g., "NOTNEEDED")
- * @param message optional status message
- * @param details payment details
- */
record ModulrPaymentResponse(
String id,
String status,
@@ -24,15 +12,6 @@ record ModulrPaymentResponse(
ModulrPaymentDetails details
) {
- /**
- * Payment details nested in the Modulr response.
- *
- * @param sourceAccountId source account used
- * @param destinationType destination type (e.g., "IBAN")
- * @param destination beneficiary destination
- * @param amount payment amount
- * @param reference payment reference
- */
record ModulrPaymentDetails(
String sourceAccountId,
String destinationType,
@@ -41,13 +20,6 @@ record ModulrPaymentDetails(
String reference
) {}
- /**
- * Destination details in the response.
- *
- * @param type destination type
- * @param iban beneficiary IBAN
- * @param name beneficiary name
- */
record ModulrDestinationDetails(
String type,
String iban,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapter.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapter.java
index 987c339d..1b7a398c 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapter.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapter.java
@@ -25,13 +25,6 @@
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
-/**
- * Modulr implementation of {@link PayoutPartnerGateway} for SEPA Credit transfers.
- *
- * Calls: {@code POST /api-sandbox-token/payments}
- * Auth: Bearer API key (sandbox token mode)
- * Scheme: SEPA_CREDIT with IBAN destination
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.payout.provider", havingValue = "modulr")
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrProperties.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrProperties.java
index f2277f4c..19bbe103 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrProperties.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrProperties.java
@@ -2,15 +2,6 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
-/**
- * Configuration properties for Modulr payout integration.
- *
- * @param baseUrl Modulr API base URL (sandbox: https://api-sandbox.modulrfinance.com)
- * @param apiKey Modulr API key (Bearer token for sandbox)
- * @param apiSecret Modulr API secret (used for HMAC auth in production)
- * @param sourceAccountId Modulr source account ID for outgoing payments
- * @param timeoutSeconds HTTP client timeout in seconds (default 10)
- */
@ConfigurationProperties(prefix = "app.payout.modulr")
public record ModulrProperties(
String baseUrl,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookProperties.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookProperties.java
index b4bbd200..b9cbc300 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookProperties.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookProperties.java
@@ -2,12 +2,6 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
-/**
- * Configuration properties for Modulr webhook HMAC validation.
- *
- * @param webhookSecret the HMAC-SHA256 secret shared with Modulr
- * @param toleranceSeconds the timestamp tolerance window (default 300 = 5 min)
- */
@ConfigurationProperties(prefix = "app.payout.modulr.webhook")
public record ModulrWebhookProperties(
String webhookSecret,
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookSignatureValidator.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookSignatureValidator.java
index 1a874596..b0a1af71 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookSignatureValidator.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrWebhookSignatureValidator.java
@@ -16,14 +16,6 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-/**
- * Modulr-specific webhook signature validator using HMAC-SHA256.
- *
- * Signature format: {@code t=,v1=}
- * where HMAC is computed as {@code HMAC-SHA256(webhook_secret, ".")}.
- *
- * Validates both the HMAC and the timestamp tolerance (default 5 minutes).
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.payout.provider", havingValue = "modulr")
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java
new file mode 100644
index 00000000..ab8290d1
--- /dev/null
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/transaction/IsolatedTransactionExecutorAdapter.java
@@ -0,0 +1,24 @@
+package com.stablecoin.payments.offramp.infrastructure.transaction;
+
+import com.stablecoin.payments.offramp.domain.port.IsolatedTransactionExecutor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW;
+
+@Component
+public class IsolatedTransactionExecutorAdapter implements IsolatedTransactionExecutor {
+
+ private final TransactionTemplate requiresNewTx;
+
+ public IsolatedTransactionExecutorAdapter(PlatformTransactionManager transactionManager) {
+ this.requiresNewTx = new TransactionTemplate(transactionManager);
+ this.requiresNewTx.setPropagationBehavior(PROPAGATION_REQUIRES_NEW);
+ }
+
+ @Override
+ public void executeInNewTransaction(Runnable action) {
+ requiresNewTx.executeWithoutResult(status -> action.run());
+ }
+}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V4__widen_recipient_account_hash.sql b/fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V4__widen_recipient_account_hash.sql
new file mode 100644
index 00000000..e3f1630e
--- /dev/null
+++ b/fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V4__widen_recipient_account_hash.sql
@@ -0,0 +1,6 @@
+-- ============================================================
+-- V4: Widen recipient_account_hash for real SHA-256 digest
+-- sha256: prefix (7 chars) + 64 hex chars = 71 chars
+-- ============================================================
+
+ALTER TABLE payout_orders ALTER COLUMN recipient_account_hash TYPE VARCHAR(128);
diff --git a/fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandlerTest.java b/fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandlerTest.java
index 55d0ed72..1b98ac1c 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandlerTest.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/domain/service/PayoutMonitorCommandHandlerTest.java
@@ -1,6 +1,7 @@
package com.stablecoin.payments.offramp.domain.service;
import com.stablecoin.payments.offramp.domain.event.FiatPayoutFailedEvent;
+import com.stablecoin.payments.offramp.domain.port.IsolatedTransactionExecutor;
import com.stablecoin.payments.offramp.domain.port.PayoutEventPublisher;
import com.stablecoin.payments.offramp.domain.port.PayoutMonitorProperties;
import com.stablecoin.payments.offramp.domain.port.PayoutOrderRepository;
@@ -11,10 +12,6 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.SimpleTransactionStatus;
import java.time.Clock;
import java.time.Duration;
@@ -56,7 +53,7 @@ void setUp() {
orderRepository,
eventPublisher,
new StubMonitorProperties(STUCK_THRESHOLD_MINUTES),
- passthroughTransactionManager(),
+ passthroughTransactionExecutor(),
clock
);
}
@@ -228,23 +225,8 @@ void shouldDoNothingWhenNoPayoutsFound() {
// -- Helpers ----------------------------------------------------------
- private static PlatformTransactionManager passthroughTransactionManager() {
- return new PlatformTransactionManager() {
- @Override
- public TransactionStatus getTransaction(TransactionDefinition definition) {
- return new SimpleTransactionStatus(true);
- }
-
- @Override
- public void commit(TransactionStatus status) {
- // no-op in unit tests
- }
-
- @Override
- public void rollback(TransactionStatus status) {
- // no-op in unit tests
- }
- };
+ private static IsolatedTransactionExecutor passthroughTransactionExecutor() {
+ return Runnable::run;
}
private record StubMonitorProperties(int stuckThresholdMinutes) implements PayoutMonitorProperties {
diff --git a/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/domain/model/PayoutOrderTestHelper.java b/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/domain/model/PayoutOrderTestHelper.java
index 45907461..9fdced54 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/domain/model/PayoutOrderTestHelper.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/domain/model/PayoutOrderTestHelper.java
@@ -2,18 +2,10 @@
import java.time.Instant;
-/**
- * Test-only helper that accesses the package-private {@code toBuilder()} on {@link PayoutOrder}.
- * Lives in the same package to reach the builder.
- */
public final class PayoutOrderTestHelper {
private PayoutOrderTestHelper() {}
- /**
- * Returns a copy of the given order with a different {@code updatedAt} timestamp.
- * Useful for simulating stuck payouts in monitor tests.
- */
public static PayoutOrder withUpdatedAt(PayoutOrder order, Instant updatedAt) {
return order.toBuilder().updatedAt(updatedAt).build();
}
diff --git a/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/PayoutOrderFixtures.java b/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/PayoutOrderFixtures.java
index 564dfacc..e2b7f98e 100644
--- a/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/PayoutOrderFixtures.java
+++ b/fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/PayoutOrderFixtures.java
@@ -19,7 +19,6 @@ public final class PayoutOrderFixtures {
private PayoutOrderFixtures() {}
- // -- Constants --------------------------------------------------------
public static final UUID PAYMENT_ID = UUID.fromString("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
public static final UUID CORRELATION_ID = UUID.fromString("b2c3d4e5-f6a7-8901-bcde-f12345678901");
@@ -33,7 +32,6 @@ private PayoutOrderFixtures() {}
public static final String PARTNER_REFERENCE = "modulr_ref_12345";
public static final String FAILURE_REASON = "Partner timeout exceeded";
- // -- Value Object Factories -------------------------------------------
public static StablecoinTicker aStablecoinTicker() {
return StablecoinTicker.of("USDC");
@@ -55,7 +53,6 @@ public static Money aMoney() {
return new Money(new BigDecimal("1000.00"), "USD");
}
- // -- API Request Factories --------------------------------------------
public static PayoutRequest aPayoutRequest() {
return new PayoutRequest(
@@ -83,7 +80,6 @@ public static PayoutRequest aHoldPayoutRequest() {
);
}
- // -- PayoutOrder State Factories --------------------------------------
public static PayoutOrder aPendingOrder() {
return PayoutOrder.create(
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/SecurityConfig.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/config/SecurityConfig.java
similarity index 97%
rename from fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/SecurityConfig.java
rename to fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/config/SecurityConfig.java
index 768a1bdd..ebdecb5a 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/SecurityConfig.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.onramp.config;
+package com.stablecoin.payments.onramp.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/CollectionController.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/CollectionController.java
index 1bdd9d1d..49fae159 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/CollectionController.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/CollectionController.java
@@ -29,12 +29,6 @@
import java.util.UUID;
-/**
- * REST controller for collection order lifecycle management.
- *
- * Thin HTTP handler that delegates all business logic to
- * {@link CollectionCommandHandler} and {@link RefundCommandHandler}.
- */
@Slf4j
@RestController
@RequestMapping("/v1/collections")
@@ -44,12 +38,6 @@ public class CollectionController {
private final CollectionCommandHandler collectionCommandHandler;
private final RefundCommandHandler refundCommandHandler;
- /**
- * Initiates a new collection order.
- *
- * Idempotent: returns 200 OK with the existing order if the same paymentId
- * is submitted again. Returns 201 CREATED on first creation.
- */
@PostMapping
public ResponseEntity initiateCollection(
@Valid @RequestBody CollectionRequest request) {
@@ -81,27 +69,18 @@ public ResponseEntity initiateCollection(
return ResponseEntity.status(status).body(response);
}
- /**
- * Retrieves a collection order by its ID.
- */
@GetMapping("/{collectionId}")
public CollectionResponse getCollection(@PathVariable UUID collectionId) {
log.info("GET /v1/collections/{}", collectionId);
return toCollectionResponse(collectionCommandHandler.getCollection(collectionId));
}
- /**
- * Retrieves a collection order by its associated payment ID.
- */
@GetMapping
public CollectionResponse getCollectionByPaymentId(@RequestParam UUID paymentId) {
log.info("GET /v1/collections?paymentId={}", paymentId);
return toCollectionResponse(collectionCommandHandler.getCollectionByPaymentId(paymentId));
}
- /**
- * Initiates a refund for a collected order.
- */
@PostMapping("/{collectionId}/refunds")
public ResponseEntity initiateRefund(
@PathVariable UUID collectionId,
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookController.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookController.java
index 8bbbe1c3..ce999f78 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookController.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookController.java
@@ -22,12 +22,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Thin HTTP handler for Stripe webhook callbacks.
- *
- * Validates the HMAC signature, parses the Stripe event JSON,
- * and delegates to {@link WebhookCommandHandler}.
- */
@Slf4j
@RestController
@RequestMapping("/internal/webhooks/psp/stripe")
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/CollectionExpiryJob.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/CollectionExpiryJob.java
index 6d9c568d..91857158 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/CollectionExpiryJob.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/CollectionExpiryJob.java
@@ -13,13 +13,6 @@
import static com.stablecoin.payments.onramp.domain.model.CollectionStatus.AWAITING_CONFIRMATION;
-/**
- * Scheduled job that expires collection orders that have been in
- * AWAITING_CONFIRMATION state past their {@code expiresAt} timestamp.
- *
- * Thin delegator — all business logic (state transition, save, event publish)
- * is handled by {@link CollectionCommandHandler#expireCollection}.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/ReconciliationJob.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/ReconciliationJob.java
index 29576f28..ccb250a2 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/ReconciliationJob.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/application/scheduler/ReconciliationJob.java
@@ -11,12 +11,6 @@
import static com.stablecoin.payments.onramp.domain.model.CollectionStatus.COLLECTED;
-/**
- * Scheduled job that runs daily reconciliation for collected orders.
- *
- * Finds all COLLECTED orders that have not yet been reconciled,
- * then delegates to {@link ReconciliationCommandHandler} for each.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/CollectionOrderNotFoundException.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/CollectionOrderNotFoundException.java
index 91753f5e..ac8eacd5 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/CollectionOrderNotFoundException.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/CollectionOrderNotFoundException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when a collection order cannot be found by the given identifier.
- */
public class CollectionOrderNotFoundException extends RuntimeException {
public static final String ERROR_CODE = "OR-1001";
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundAmountExceededException.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundAmountExceededException.java
index aa809666..27bb4ee3 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundAmountExceededException.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundAmountExceededException.java
@@ -4,9 +4,6 @@
import java.util.UUID;
-/**
- * Thrown when the requested refund amount exceeds the collected amount.
- */
public class RefundAmountExceededException extends RuntimeException {
public static final String ERROR_CODE = "OR-2003";
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotAllowedException.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotAllowedException.java
index eaed38cf..7ac939fe 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotAllowedException.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotAllowedException.java
@@ -4,10 +4,6 @@
import java.util.UUID;
-/**
- * Thrown when a refund is requested for a collection order
- * that is not in the COLLECTED state.
- */
public class RefundNotAllowedException extends RuntimeException {
public static final String ERROR_CODE = "OR-2002";
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotFoundException.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotFoundException.java
index e1216056..d60af16f 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotFoundException.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/exception/RefundNotFoundException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when a refund cannot be found by the given identifier.
- */
public class RefundNotFoundException extends RuntimeException {
public static final String ERROR_CODE = "OR-2001";
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/CollectionOrder.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/CollectionOrder.java
index d81bcee1..7578017c 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/CollectionOrder.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/CollectionOrder.java
@@ -31,17 +31,6 @@
import static com.stablecoin.payments.onramp.domain.model.CollectionTrigger.REFUND_PROCESSING_STARTED;
import static com.stablecoin.payments.onramp.domain.model.CollectionTrigger.START_REFUND;
-/**
- * Aggregate root for a fiat collection order.
- *
- * Enforces the collection lifecycle via an internal state machine:
- * {@code PENDING -> PAYMENT_INITIATED -> AWAITING_CONFIRMATION -> COLLECTED}.
- *
- * Handles edge cases: amount mismatches, manual review escalation,
- * and refund flows for compensation.
- *
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record CollectionOrder(
UUID collectionId,
@@ -67,29 +56,22 @@ public record CollectionOrder(
private static final StateMachine STATE_MACHINE =
new StateMachine<>(List.of(
- // -- Happy path -----------------------------------------------
new StateTransition<>(PENDING, INITIATE_PAYMENT, PAYMENT_INITIATED),
new StateTransition<>(PAYMENT_INITIATED, PSP_SESSION_CREATED, AWAITING_CONFIRMATION),
new StateTransition<>(AWAITING_CONFIRMATION, PAYMENT_CONFIRMED, COLLECTED),
- // -- Failure paths --------------------------------------------
new StateTransition<>(AWAITING_CONFIRMATION, PAYMENT_TIMEOUT, COLLECTION_FAILED),
new StateTransition<>(AWAITING_CONFIRMATION, AMOUNT_MISMATCH_DETECTED, AMOUNT_MISMATCH),
new StateTransition<>(AMOUNT_MISMATCH, ESCALATE_MANUAL_REVIEW, MANUAL_REVIEW),
new StateTransition<>(PENDING, FAIL, COLLECTION_FAILED),
new StateTransition<>(PAYMENT_INITIATED, FAIL, COLLECTION_FAILED),
- // -- Refund paths (compensation) ------------------------------
new StateTransition<>(COLLECTED, START_REFUND, REFUND_INITIATED),
new StateTransition<>(REFUND_INITIATED, REFUND_PROCESSING_STARTED, REFUND_PROCESSING),
new StateTransition<>(REFUND_PROCESSING, REFUND_COMPLETED, REFUNDED)
));
- // -- Factory Method ---------------------------------------------------
- /**
- * Creates a new collection order in PENDING state.
- */
public static CollectionOrder initiate(UUID paymentId, UUID correlationId,
Money amount, PaymentRail paymentRail,
PspIdentifier psp, BankAccount senderAccount) {
@@ -128,11 +110,7 @@ public static CollectionOrder initiate(UUID paymentId, UUID correlationId,
.build();
}
- // -- State Transition Methods -----------------------------------------
- /**
- * Initiates payment with PSP. Transitions PENDING -> PAYMENT_INITIATED.
- */
public CollectionOrder initiatePayment() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, INITIATE_PAYMENT);
@@ -142,9 +120,6 @@ public CollectionOrder initiatePayment() {
.build();
}
- /**
- * Records PSP session creation. Transitions PAYMENT_INITIATED -> AWAITING_CONFIRMATION.
- */
public CollectionOrder awaitConfirmation(String pspReference) {
assertNotTerminal();
if (pspReference == null || pspReference.isBlank()) {
@@ -158,9 +133,6 @@ public CollectionOrder awaitConfirmation(String pspReference) {
.build();
}
- /**
- * Confirms collection. Transitions AWAITING_CONFIRMATION -> COLLECTED.
- */
public CollectionOrder confirmCollection(Money collectedAmount) {
assertNotTerminal();
if (collectedAmount == null) {
@@ -175,9 +147,6 @@ public CollectionOrder confirmCollection(Money collectedAmount) {
.build();
}
- /**
- * Fails the collection. Can be triggered from PENDING or PAYMENT_INITIATED.
- */
public CollectionOrder failCollection(String reason, String errorCode) {
var nextState = STATE_MACHINE.transition(status, FAIL);
return toBuilder()
@@ -188,10 +157,6 @@ public CollectionOrder failCollection(String reason, String errorCode) {
.build();
}
- /**
- * Marks collection as timed out / PSP-rejected from AWAITING_CONFIRMATION.
- * Transitions AWAITING_CONFIRMATION -> COLLECTION_FAILED.
- */
public CollectionOrder timeoutCollection(String reason, String errorCode) {
var nextState = STATE_MACHINE.transition(status, PAYMENT_TIMEOUT);
return toBuilder()
@@ -202,9 +167,6 @@ public CollectionOrder timeoutCollection(String reason, String errorCode) {
.build();
}
- /**
- * Detects amount mismatch. Transitions AWAITING_CONFIRMATION -> AMOUNT_MISMATCH.
- */
public CollectionOrder detectAmountMismatch() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, AMOUNT_MISMATCH_DETECTED);
@@ -214,9 +176,6 @@ public CollectionOrder detectAmountMismatch() {
.build();
}
- /**
- * Escalates to manual review. Transitions AMOUNT_MISMATCH -> MANUAL_REVIEW.
- */
public CollectionOrder escalateToManualReview() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, ESCALATE_MANUAL_REVIEW);
@@ -226,9 +185,6 @@ public CollectionOrder escalateToManualReview() {
.build();
}
- /**
- * Initiates refund (compensation). Transitions COLLECTED -> REFUND_INITIATED.
- */
public CollectionOrder initiateRefund() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, START_REFUND);
@@ -238,9 +194,6 @@ public CollectionOrder initiateRefund() {
.build();
}
- /**
- * Starts refund processing. Transitions REFUND_INITIATED -> REFUND_PROCESSING.
- */
public CollectionOrder startRefundProcessing() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, REFUND_PROCESSING_STARTED);
@@ -250,9 +203,6 @@ public CollectionOrder startRefundProcessing() {
.build();
}
- /**
- * Completes refund. Transitions REFUND_PROCESSING -> REFUNDED.
- */
public CollectionOrder completeRefund() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(status, REFUND_COMPLETED);
@@ -262,23 +212,15 @@ public CollectionOrder completeRefund() {
.build();
}
- // -- Query Methods ----------------------------------------------------
- /**
- * Returns true if this collection order is in a terminal state.
- */
public boolean isTerminal() {
return TERMINAL_STATES.contains(status);
}
- /**
- * Returns true if a given trigger can be applied from the current state.
- */
public boolean canApply(CollectionTrigger trigger) {
return STATE_MACHINE.canTransition(status, trigger);
}
- // -- Invariant Guards -------------------------------------------------
private void assertNotTerminal() {
if (isTerminal()) {
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/PspTransaction.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/PspTransaction.java
index 5b5b1883..4c287b5d 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/PspTransaction.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/PspTransaction.java
@@ -6,12 +6,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Immutable event log recording PSP interactions.
- *
- * Each PSP webhook or API call response is captured as a {@code PspTransaction}.
- * No state transitions — this is an append-only audit record.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record PspTransaction(
UUID pspTxnId,
@@ -26,11 +20,7 @@ public record PspTransaction(
Instant receivedAt
) {
- // -- Factory Method ---------------------------------------------------
- /**
- * Creates a new PSP transaction record.
- */
public static PspTransaction create(UUID collectionId, String pspName,
String pspReference, PspTransactionDirection direction,
String eventType, Money amount,
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/ReconciliationRecord.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/ReconciliationRecord.java
index cb68a219..fc07b717 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/ReconciliationRecord.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/ReconciliationRecord.java
@@ -10,11 +10,6 @@
import static com.stablecoin.payments.onramp.domain.model.ReconciliationStatus.DISCREPANCY;
import static com.stablecoin.payments.onramp.domain.model.ReconciliationStatus.MATCHED;
-/**
- * Immutable record representing a reconciliation result for a collection order.
- *
- * Compares expected vs actual amounts and records match/discrepancy status.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record ReconciliationRecord(
UUID reconciliationId,
@@ -33,12 +28,6 @@ public record ReconciliationRecord(
private static final BigDecimal TOLERANCE = new BigDecimal("0.01");
- /**
- * Creates a reconciliation record by comparing expected and actual amounts.
- *
- * If the absolute difference is within tolerance (0.01), the record is MATCHED.
- * Otherwise, it is marked as DISCREPANCY with the difference captured.
- */
public static ReconciliationRecord reconcile(UUID collectionId, String psp,
String pspReference, BigDecimal expectedAmount,
BigDecimal actualAmount, String currency) {
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/Refund.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/Refund.java
index 6222336b..18cb4467 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/Refund.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/model/Refund.java
@@ -11,12 +11,6 @@
import static com.stablecoin.payments.onramp.domain.model.RefundStatus.PENDING;
import static com.stablecoin.payments.onramp.domain.model.RefundStatus.PROCESSING;
-/**
- * Child entity representing a refund for a collected payment.
- *
- * Tracks the refund lifecycle: {@code PENDING -> PROCESSING -> COMPLETED/FAILED}.
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record Refund(
UUID refundId,
@@ -31,11 +25,7 @@ public record Refund(
String failureReason
) {
- // -- Factory Method ---------------------------------------------------
- /**
- * Creates a new refund in PENDING state.
- */
public static Refund initiate(UUID collectionId, UUID paymentId,
Money refundAmount, String reason) {
if (collectionId == null) {
@@ -62,11 +52,7 @@ public static Refund initiate(UUID collectionId, UUID paymentId,
.build();
}
- // -- State Transition Methods -----------------------------------------
- /**
- * Starts processing the refund. Transitions PENDING -> PROCESSING.
- */
public Refund startProcessing() {
if (status != PENDING) {
throw new IllegalStateException(
@@ -77,9 +63,6 @@ public Refund startProcessing() {
.build();
}
- /**
- * Completes the refund. Transitions PROCESSING -> COMPLETED.
- */
public Refund complete(String pspRefundRef) {
if (status != PROCESSING) {
throw new IllegalStateException(
@@ -95,9 +78,6 @@ public Refund complete(String pspRefundRef) {
.build();
}
- /**
- * Fails the refund. Transitions PROCESSING -> FAILED.
- */
public Refund fail(String failureReason) {
if (status != PROCESSING) {
throw new IllegalStateException(
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/WebhookSignatureValidator.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/WebhookSignatureValidator.java
index b0ef40d4..e010772f 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/WebhookSignatureValidator.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/WebhookSignatureValidator.java
@@ -1,19 +1,6 @@
package com.stablecoin.payments.onramp.domain.port;
-/**
- * Port interface for webhook signature validation.
- *
- * Implemented by PSP-specific adapters (e.g., Stripe) to verify
- * the authenticity of incoming webhook requests using HMAC signatures.
- */
public interface WebhookSignatureValidator {
- /**
- * Validates the webhook signature against the raw payload.
- *
- * @param payload the raw request body
- * @param signature the signature header value
- * @return {@code true} if the signature is valid
- */
boolean isValid(String payload, String signature);
}
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java
index dd8c2717..705491b7 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java
@@ -23,12 +23,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Domain command handler for collection order operations.
- *
- * Orchestrates: idempotency check -> create order -> call PSP ->
- * transition state -> record PspTransaction -> publish event -> save.
- */
@Slf4j
@Service
@Transactional
@@ -40,20 +34,6 @@ public class CollectionCommandHandler {
private final PspGateway pspGateway;
private final CollectionEventPublisher eventPublisher;
- /**
- * Initiates a new collection order for a payment.
- *
- * Idempotent: if a collection order already exists for the given paymentId,
- * returns the existing order with {@code created = false}.
- *
- * @param paymentId the payment identifier
- * @param correlationId the correlation identifier for tracing
- * @param amount the amount to collect
- * @param paymentRail the payment rail details
- * @param psp the PSP identifier
- * @param senderAccount the sender bank account details
- * @return a {@link CollectionResult} indicating the order and whether it was newly created
- */
public CollectionResult initiateCollection(UUID paymentId, UUID correlationId,
Money amount, PaymentRail paymentRail,
PspIdentifier psp, BankAccount senderAccount) {
@@ -109,39 +89,16 @@ public CollectionResult initiateCollection(UUID paymentId, UUID correlationId,
return new CollectionResult(order, true);
}
- /**
- * Retrieves a collection order by its ID.
- *
- * @param collectionId the collection order identifier
- * @return the collection order
- * @throws CollectionOrderNotFoundException if the order is not found
- */
public CollectionOrder getCollection(UUID collectionId) {
return collectionOrderRepository.findById(collectionId)
.orElseThrow(() -> new CollectionOrderNotFoundException(collectionId));
}
- /**
- * Retrieves a collection order by its associated payment ID.
- *
- * @param paymentId the payment identifier
- * @return the collection order
- * @throws CollectionOrderNotFoundException if the order is not found
- */
public CollectionOrder getCollectionByPaymentId(UUID paymentId) {
return collectionOrderRepository.findByPaymentId(paymentId)
.orElseThrow(() -> new CollectionOrderNotFoundException(paymentId));
}
- /**
- * Expires a collection order that has been in AWAITING_CONFIRMATION past its timeout.
- *
- * Transitions the order to COLLECTION_FAILED, persists, and publishes a
- * {@link CollectionFailedEvent} via the outbox.
- *
- * @param order the expired order
- * @param now the current timestamp
- */
public void expireCollection(CollectionOrder order, Instant now) {
var expired = order.timeoutCollection("Collection expired", "OR-3001");
collectionOrderRepository.save(expired);
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionResult.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionResult.java
index 336b90ee..65b9af5b 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionResult.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionResult.java
@@ -2,8 +2,4 @@
import com.stablecoin.payments.onramp.domain.model.CollectionOrder;
-/**
- * Wrapper returned by {@link CollectionCommandHandler#initiateCollection}
- * so the controller can distinguish 201 CREATED vs 200 OK (idempotent replay).
- */
public record CollectionResult(CollectionOrder order, boolean created) {}
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/ReconciliationCommandHandler.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/ReconciliationCommandHandler.java
index 7ffb3d6e..cbed20df 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/ReconciliationCommandHandler.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/ReconciliationCommandHandler.java
@@ -14,12 +14,6 @@
import static com.stablecoin.payments.onramp.domain.model.ReconciliationStatus.DISCREPANCY;
-/**
- * Domain service responsible for reconciling collection orders.
- *
- * Compares expected vs collected amounts and persists the reconciliation result.
- * Publishes an alert event when a discrepancy is detected.
- */
@Slf4j
@Service
@Transactional
@@ -29,12 +23,6 @@ public class ReconciliationCommandHandler {
private final ReconciliationRecordRepository reconciliationRecordRepository;
private final CollectionEventPublisher eventPublisher;
- /**
- * Reconciles a single collection order by comparing expected and collected amounts.
- *
- * @param order the collected order to reconcile
- * @return the persisted reconciliation record
- */
public ReconciliationRecord reconcile(CollectionOrder order) {
if (reconciliationRecordRepository.existsByCollectionId(order.collectionId())) {
log.info("Reconciliation record already exists for collectionId={} — skipping",
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/RefundCommandHandler.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/RefundCommandHandler.java
index ec85c8f7..2b93dca6 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/RefundCommandHandler.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/RefundCommandHandler.java
@@ -22,12 +22,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Domain command handler for refund operations.
- *
- * Orchestrates: validate collection state -> check idempotency ->
- * create refund -> call PSP -> transition collection order -> publish event.
- */
@Slf4j
@Service
@Transactional
@@ -39,17 +33,6 @@ public class RefundCommandHandler {
private final PspGateway pspGateway;
private final CollectionEventPublisher eventPublisher;
- /**
- * Initiates a refund for a collected order.
- *
- * Idempotent: if a refund already exists for this collection in a
- * non-failed state (PENDING, PROCESSING, COMPLETED), returns the existing one.
- *
- * @param collectionId the collection order to refund
- * @param refundAmount the amount to refund
- * @param reason the reason for the refund
- * @return the created or existing refund
- */
public Refund initiateRefund(UUID collectionId, Money refundAmount, String reason) {
// 1. Find collection order
var order = collectionOrderRepository.findById(collectionId)
@@ -118,13 +101,6 @@ public Refund initiateRefund(UUID collectionId, Money refundAmount, String reaso
return refund;
}
- /**
- * Retrieves a refund by its ID.
- *
- * @param refundId the refund identifier
- * @return the refund
- * @throws RefundNotFoundException if the refund is not found
- */
public Refund getRefund(UUID refundId) {
return refundRepository.findById(refundId)
.orElseThrow(() -> new RefundNotFoundException(refundId));
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommand.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommand.java
index 2d4e3714..d619d255 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommand.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommand.java
@@ -4,17 +4,6 @@
import java.util.UUID;
-/**
- * Command representing a parsed webhook notification from a PSP.
- *
- * @param eventId the unique event ID from the PSP (for idempotency)
- * @param eventType the event type (e.g., "payment_intent.succeeded")
- * @param pspReference the PSP payment reference (e.g., Stripe PaymentIntent ID)
- * @param collectionId the collection order ID from metadata (optional)
- * @param amount the amount from the PSP event
- * @param status the payment status string from the PSP
- * @param rawPayload the raw JSON payload for audit trail
- */
public record WebhookCommand(
String eventId,
String eventType,
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommandHandler.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommandHandler.java
index fabeb68b..d873c039 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommandHandler.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/WebhookCommandHandler.java
@@ -17,12 +17,6 @@
import java.time.Instant;
-/**
- * Domain command handler that processes PSP webhook notifications.
- *
- * Orchestrates: lookup order by pspReference -> check idempotency ->
- * transition state -> record PspTransaction -> publish event via outbox.
- */
@Slf4j
@Service
@Transactional
@@ -37,15 +31,6 @@ public class WebhookCommandHandler {
private final PspTransactionRepository pspTransactionRepository;
private final CollectionEventPublisher eventPublisher;
- /**
- * Processes a webhook command from a PSP notification.
- *
- * Idempotent: if the collection order is already in a terminal or
- * completed state for the given event type, the webhook is skipped.
- *
- * @param command the parsed webhook command
- * @return the updated collection order
- */
public CollectionOrder handleWebhook(WebhookCommand command) {
log.info("Processing webhook eventId={} type={} pspRef={}",
command.eventId(), command.eventType(), command.pspReference());
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/FallbackAdaptersConfig.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/config/FallbackAdaptersConfig.java
similarity index 86%
rename from fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/FallbackAdaptersConfig.java
rename to fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/config/FallbackAdaptersConfig.java
index 1dae3c18..495dc43f 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/config/FallbackAdaptersConfig.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/config/FallbackAdaptersConfig.java
@@ -1,10 +1,11 @@
-package com.stablecoin.payments.onramp.config;
+package com.stablecoin.payments.onramp.infrastructure.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 com.stablecoin.payments.onramp.domain.port.WebhookSignatureValidator;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -12,18 +13,13 @@
import java.time.Clock;
import java.util.UUID;
-/**
- * Provides fallback (dev/test) implementations of outbound ports.
- * Activated only when {@code app.fallback-adapters.enabled=true}.
- * Real adapters are registered by provider-specific configurations
- * under {@code infrastructure/provider//}.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "true")
public class FallbackAdaptersConfig {
@Bean
+ @ConditionalOnMissingBean
public PspGateway fallbackPspGateway() {
return new PspGateway() {
@Override
@@ -45,6 +41,7 @@ public PspRefundResult initiateRefund(
}
@Bean
+ @ConditionalOnMissingBean
public WebhookSignatureValidator fallbackWebhookSignatureValidator() {
return (payload, signature) -> {
log.warn("[FALLBACK-WEBHOOK] Using dev webhook signature validator — always valid");
@@ -53,6 +50,7 @@ public WebhookSignatureValidator fallbackWebhookSignatureValidator() {
}
@Bean
+ @ConditionalOnMissingBean
public Clock clock() {
return Clock.systemUTC();
}
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/metrics/OnRampMetrics.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/metrics/OnRampMetrics.java
index 4bb1114d..51c4ba12 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/metrics/OnRampMetrics.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/metrics/OnRampMetrics.java
@@ -4,20 +4,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Fiat On-Ramp service.
- *
- * Tracks collection initiation, completion, failure, and refund events.
- */
@Component
@RequiredArgsConstructor
public class OnRampMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a collection initiation event.
- */
public void recordCollectionInitiated(String psp, String currency) {
meterRegistry.counter("onramp.collection.initiated",
"psp", psp,
@@ -25,9 +17,6 @@ public void recordCollectionInitiated(String psp, String currency) {
).increment();
}
- /**
- * Records a collection completion event.
- */
public void recordCollectionCompleted(String psp, String currency) {
meterRegistry.counter("onramp.collection.completed",
"psp", psp,
@@ -35,9 +24,6 @@ public void recordCollectionCompleted(String psp, String currency) {
).increment();
}
- /**
- * Records a collection failure event.
- */
public void recordCollectionFailed(String psp, String reason) {
meterRegistry.counter("onramp.collection.failed",
"psp", psp,
@@ -45,9 +31,6 @@ public void recordCollectionFailed(String psp, String reason) {
).increment();
}
- /**
- * Records a refund processed event.
- */
public void recordRefundProcessed(String psp) {
meterRegistry.counter("onramp.refund.processed",
"psp", psp
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePaymentIntentResponse.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePaymentIntentResponse.java
index 400c273a..aef9029f 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePaymentIntentResponse.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePaymentIntentResponse.java
@@ -2,14 +2,6 @@
import com.fasterxml.jackson.annotation.JsonProperty;
-/**
- * ACL DTO mapping the Stripe PaymentIntent response.
- *
- * Package-private — never leaks outside the Stripe adapter package.
- * Uses wrapper types (not primitives) to handle nullable JSON fields safely.
- *
- * @see Stripe PaymentIntent object
- */
record StripePaymentIntentResponse(
String id,
String object,
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeRefundResponse.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeRefundResponse.java
index 78cf7aea..ed836f3b 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeRefundResponse.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeRefundResponse.java
@@ -2,14 +2,6 @@
import com.fasterxml.jackson.annotation.JsonProperty;
-/**
- * ACL DTO mapping the Stripe Refund response.
- *
- * Package-private — never leaks outside the Stripe adapter package.
- * Uses wrapper types (not primitives) to handle nullable JSON fields safely.
- *
- * @see Stripe Refund object
- */
record StripeRefundResponse(
String id,
String object,
diff --git a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeSignatureValidator.java b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeSignatureValidator.java
index 11016315..1c7ff3d6 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeSignatureValidator.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripeSignatureValidator.java
@@ -16,14 +16,6 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-/**
- * Stripe-specific webhook signature validator using HMAC-SHA256.
- *
- * Stripe signature format: {@code t=,v1=}
- * where HMAC is computed as {@code HMAC-SHA256(webhook_secret, ".")}.
- *
- * Validates both the HMAC and the timestamp tolerance (default 5 minutes).
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.psp.provider", havingValue = "stripe")
diff --git a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java
index 7c5bbf95..28ef47d8 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java
@@ -16,7 +16,6 @@ public final class CollectionOrderFixtures {
private CollectionOrderFixtures() {}
- // -- Constants --------------------------------------------------------
public static final UUID PAYMENT_ID = UUID.fromString("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
public static final UUID CORRELATION_ID = UUID.fromString("b2c3d4e5-f6a7-8901-bcde-f12345678901");
@@ -25,7 +24,6 @@ private CollectionOrderFixtures() {}
public static final String FAILURE_REASON = "Payment timeout exceeded";
public static final String ERROR_CODE = "TIMEOUT_001";
- // -- Value Object Factories -------------------------------------------
public static Money aMoney() {
return new Money(new BigDecimal("1000.00"), "USD");
@@ -56,7 +54,6 @@ public static PspIdentifier aPspIdentifier() {
return new PspIdentifier("stripe_001", "Stripe");
}
- // -- CollectionOrder State Factories ----------------------------------
public static CollectionOrder aPendingOrder() {
return CollectionOrder.initiate(
@@ -105,7 +102,6 @@ public static CollectionOrder aRefundedOrder() {
return aRefundProcessingOrder().completeRefund();
}
- // -- API Request Factories --------------------------------------------
public static CollectionRequest aCollectionRequest() {
return new CollectionRequest(
diff --git a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/ReconciliationFixtures.java b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/ReconciliationFixtures.java
index c77aba6e..3567ec7c 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/ReconciliationFixtures.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/ReconciliationFixtures.java
@@ -13,14 +13,12 @@ public final class ReconciliationFixtures {
private ReconciliationFixtures() {}
- // -- Constants --------------------------------------------------------
public static final UUID RECONCILIATION_ID = UUID.fromString("c3d4e5f6-a7b8-9012-cdef-123456789012");
public static final UUID COLLECTION_ID = UUID.fromString("d4e5f6a7-b8c9-0123-defa-234567890123");
public static final String PSP_NAME = "Stripe";
public static final String PSP_REF = "psp_ref_stripe_12345";
- // -- ReconciliationRecord Factories -----------------------------------
public static ReconciliationRecord aMatchedReconciliation() {
return ReconciliationRecord.reconcile(
@@ -55,7 +53,6 @@ public static ReconciliationRecord anUnmatchedReconciliation() {
);
}
- // -- CollectionOrder for Reconciliation Tests -------------------------
public static CollectionOrder aCollectedOrderForReconciliation() {
return aCollectedOrder();
@@ -89,7 +86,6 @@ public static CollectionOrder aCollectedOrderWithinTolerance() {
return awaiting.confirmCollection(new Money(new BigDecimal("999.995"), "USD"));
}
- // -- CollectionOrder for Expiry Tests ---------------------------------
public static CollectionOrder anExpiredAwaitingConfirmationOrder() {
var pending = CollectionOrder.initiate(
diff --git a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java
index f43e4dc8..33ad7768 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java
@@ -14,13 +14,11 @@ public final class RefundFixtures {
private RefundFixtures() {}
- // -- Constants --------------------------------------------------------
public static final UUID COLLECTION_ID = UUID.fromString("c3d4e5f6-a7b8-9012-cdef-123456789012");
public static final String REFUND_REASON = "Customer requested refund";
public static final String FAILURE_REASON = "PSP rejected refund request";
- // -- Refund State Factories -------------------------------------------
public static Money aRefundAmount() {
return new Money(new BigDecimal("1000.00"), "USD");
@@ -47,7 +45,6 @@ public static Refund aFailedRefund() {
return aProcessingRefund().fail(FAILURE_REASON);
}
- // -- API Request Factories --------------------------------------------
public static RefundRequest aRefundRequest() {
return new RefundRequest(new BigDecimal("1000.00"), "USD", REFUND_REASON);
diff --git a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/WebhookFixtures.java b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/WebhookFixtures.java
index 04f6caab..845dbdf7 100644
--- a/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/WebhookFixtures.java
+++ b/fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/WebhookFixtures.java
@@ -17,7 +17,6 @@ public final class WebhookFixtures {
private WebhookFixtures() {}
- // -- Constants --------------------------------------------------------
public static final String WEBHOOK_SECRET = "whsec_test_secret_12345";
public static final String EVENT_ID = "evt_test_001";
@@ -26,7 +25,6 @@ private WebhookFixtures() {}
public static final String EVENT_TYPE_FAILED = "payment_intent.payment_failed";
public static final UUID COLLECTION_ID = UUID.fromString("c3d4e5f6-a7b8-9012-cdef-123456789012");
- // -- Stripe Event JSON -----------------------------------------------
public static String aSucceededEventJson() {
return aSucceededEventJson(PSP_REFERENCE, 100000L, "usd", COLLECTION_ID);
@@ -84,7 +82,6 @@ public static String aMismatchAmountEventJson() {
return aSucceededEventJson(PSP_REFERENCE, 50000L, "usd", COLLECTION_ID);
}
- // -- WebhookCommand factories ----------------------------------------
public static WebhookCommand aSucceededCommand() {
return new WebhookCommand(
@@ -155,7 +152,6 @@ public static WebhookCommand aMismatchCommand() {
aMismatchAmountEventJson());
}
- // -- Signature helpers -----------------------------------------------
public static String computeSignature(String payload, String secret) {
return computeSignature(payload, secret, Instant.now());
diff --git a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/application/service/FxRateLockApplicationService.java b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/application/service/FxRateLockApplicationService.java
index e4af9965..f53c8b92 100644
--- a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/application/service/FxRateLockApplicationService.java
+++ b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/application/service/FxRateLockApplicationService.java
@@ -39,10 +39,6 @@ public class FxRateLockApplicationService {
private final EventPublisher eventPublisher;
private final FxResponseMapper responseMapper;
- /**
- * Result of a lock-rate operation, indicating whether the lock was newly created
- * or returned from an existing idempotent match.
- */
public record LockRateResult(FxRateLockResponse response, boolean created) {}
@Transactional
diff --git a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/config/FallbackAdaptersConfig.java b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/config/FallbackAdaptersConfig.java
index f986f823..b795510f 100644
--- a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/config/FallbackAdaptersConfig.java
+++ b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/config/FallbackAdaptersConfig.java
@@ -4,6 +4,7 @@
import com.stablecoin.payments.fx.domain.port.RateCache;
import com.stablecoin.payments.fx.domain.port.RateProvider;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -19,6 +20,7 @@
public class FallbackAdaptersConfig {
@Bean
+ @ConditionalOnMissingBean
public RateProvider fallbackRateProvider() {
log.warn("Using fallback rate provider — returning fixed rates");
return new RateProvider() {
@@ -51,6 +53,7 @@ public String providerName() {
}
@Bean
+ @ConditionalOnMissingBean
public RateCache fallbackRateCache() {
log.warn("Using in-memory fallback rate cache — not suitable for production");
return new RateCache() {
diff --git a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/metrics/FxMetrics.java b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/metrics/FxMetrics.java
index d33db925..ba3606a6 100644
--- a/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/metrics/FxMetrics.java
+++ b/fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/metrics/FxMetrics.java
@@ -9,11 +9,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
-/**
- * Custom business metrics for the FX and Liquidity Engine service.
- *
- * Tracks active FX lock count (gauge), quote creation, lock acquisition, and lock expiry.
- */
@Component
@RequiredArgsConstructor
public class FxMetrics {
@@ -21,41 +16,26 @@ public class FxMetrics {
private final MeterRegistry meterRegistry;
private final Map activeLockCounters = new ConcurrentHashMap<>();
- /**
- * Increments the active lock gauge for a corridor.
- */
public void incrementActiveLocks(String corridor) {
getActiveLockCounter(corridor).incrementAndGet();
}
- /**
- * Decrements the active lock gauge for a corridor.
- */
public void decrementActiveLocks(String corridor) {
getActiveLockCounter(corridor).decrementAndGet();
}
- /**
- * Records a quote creation event.
- */
public void recordQuoteCreated(String corridor) {
meterRegistry.counter("fx.quote.created",
"corridor", corridor
).increment();
}
- /**
- * Records a lock acquisition event.
- */
public void recordLockAcquired(String corridor) {
meterRegistry.counter("fx.lock.acquired",
"corridor", corridor
).increment();
}
- /**
- * Records a lock expiry event.
- */
public void recordLockExpired(String corridor) {
meterRegistry.counter("fx.lock.expired",
"corridor", corridor
diff --git a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/SecurityConfig.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/config/SecurityConfig.java
similarity index 96%
rename from blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/SecurityConfig.java
rename to ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/config/SecurityConfig.java
index f6305f37..a23b8a31 100644
--- a/blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/config/SecurityConfig.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.custody.config;
+package com.stablecoin.payments.ledger.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/JournalPostedEvent.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/JournalPostedEvent.java
index 924ff95d..5c4b364f 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/JournalPostedEvent.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/JournalPostedEvent.java
@@ -3,9 +3,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Internal domain event raised when a ledger transaction with journal entries is posted.
- */
public record JournalPostedEvent(
UUID transactionId,
UUID paymentId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationCompletedDomainEvent.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationCompletedDomainEvent.java
index 447cbfe0..b73535e4 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationCompletedDomainEvent.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationCompletedDomainEvent.java
@@ -5,9 +5,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Internal domain event raised when reconciliation reaches a terminal state (RECONCILED).
- */
public record ReconciliationCompletedDomainEvent(
UUID recId,
UUID paymentId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationDiscrepancyDomainEvent.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationDiscrepancyDomainEvent.java
index c7c11ab1..095658e0 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationDiscrepancyDomainEvent.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/event/ReconciliationDiscrepancyDomainEvent.java
@@ -4,9 +4,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Internal domain event raised when reconciliation detects a discrepancy.
- */
public record ReconciliationDiscrepancyDomainEvent(
UUID recId,
UUID paymentId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AccountBalance.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AccountBalance.java
index 2ccb6adc..e97349ff 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AccountBalance.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AccountBalance.java
@@ -5,10 +5,6 @@
import java.util.Objects;
import java.util.UUID;
-/**
- * Real-time running balance per account+currency.
- * Updated on every journal entry with optimistic locking via version.
- */
public record AccountBalance(
String accountCode,
String currency,
@@ -24,9 +20,6 @@ public record AccountBalance(
Objects.requireNonNull(balance, "balance must not be null");
}
- /**
- * Creates an initial zero balance for an account+currency pair.
- */
public static AccountBalance zero(String accountCode, String currency) {
return new AccountBalance(
accountCode,
@@ -38,12 +31,6 @@ public static AccountBalance zero(String accountCode, String currency) {
);
}
- /**
- * Applies a journal entry to this balance and returns a new AccountBalance with updated values.
- * DEBIT increases ASSET/EXPENSE/CLEARING, decreases LIABILITY/REVENUE.
- * CREDIT decreases ASSET/EXPENSE/CLEARING, increases LIABILITY/REVENUE.
- * The caller is responsible for determining the sign — this method simply adds or subtracts.
- */
public AccountBalance applyEntry(JournalEntry entry, EntryType normalBalance) {
Objects.requireNonNull(entry, "entry must not be null");
Objects.requireNonNull(normalBalance, "normalBalance must not be null");
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AuditEvent.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AuditEvent.java
index 30609ceb..1f23e559 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AuditEvent.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/AuditEvent.java
@@ -4,10 +4,6 @@
import java.util.Objects;
import java.util.UUID;
-/**
- * Append-only audit event for the ledger service.
- * Immutable once created — no updates or deletes at domain or DB level.
- */
public record AuditEvent(
UUID auditId,
UUID correlationId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/LedgerTransaction.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/LedgerTransaction.java
index c2a4f3dc..464960ce 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/LedgerTransaction.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/LedgerTransaction.java
@@ -8,11 +8,6 @@
import java.util.UUID;
import java.util.stream.Collectors;
-/**
- * Aggregate root for the three-object model.
- * Groups balanced journal entry pairs — entries only created through transactions.
- * Immutable once created.
- */
public record LedgerTransaction(
UUID transactionId,
UUID paymentId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/ReconciliationRecord.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/ReconciliationRecord.java
index 6b95fd41..5cf7fe38 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/ReconciliationRecord.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/model/ReconciliationRecord.java
@@ -15,11 +15,6 @@
import static com.stablecoin.payments.ledger.domain.model.ReconciliationLegType.STABLECOIN_MINTED;
import static com.stablecoin.payments.ledger.domain.model.ReconciliationLegType.STABLECOIN_REDEEMED;
-/**
- * Per-payment reconciliation record tracking the 5-leg matching flow.
- * Required legs for RECONCILED status: FIAT_IN, STABLECOIN_MINTED, CHAIN_TRANSFERRED,
- * STABLECOIN_REDEEMED, FIAT_OUT. FX_RATE is metadata only.
- */
public record ReconciliationRecord(
UUID recId,
UUID paymentId,
@@ -45,9 +40,6 @@ public record ReconciliationRecord(
legs = List.copyOf(legs);
}
- /**
- * Creates a new PENDING reconciliation record for a payment.
- */
public static ReconciliationRecord create(UUID paymentId, BigDecimal tolerance) {
return new ReconciliationRecord(
UUID.randomUUID(),
@@ -62,9 +54,6 @@ public static ReconciliationRecord create(UUID paymentId, BigDecimal tolerance)
);
}
- /**
- * Adds a reconciliation leg and updates status based on leg completeness.
- */
public ReconciliationRecord addLeg(ReconciliationLeg leg) {
Objects.requireNonNull(leg, "leg must not be null");
@@ -97,10 +86,6 @@ public ReconciliationRecord addLeg(ReconciliationLeg leg) {
);
}
- /**
- * Finalizes reconciliation — checks all legs present and amounts within tolerance.
- * Returns RECONCILED or DISCREPANCY.
- */
public ReconciliationRecord finalize(BigDecimal discrepancy) {
Objects.requireNonNull(discrepancy, "discrepancy must not be null");
@@ -135,9 +120,6 @@ public ReconciliationRecord finalize(BigDecimal discrepancy) {
);
}
- /**
- * Marks the reconciliation as DISCREPANCY (e.g., due to payment failure).
- */
public ReconciliationRecord markDiscrepancy() {
return new ReconciliationRecord(
this.recId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/AccountBalanceRepository.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/AccountBalanceRepository.java
index 83476fc5..450e41f0 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/AccountBalanceRepository.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/AccountBalanceRepository.java
@@ -11,9 +11,6 @@ public interface AccountBalanceRepository {
Optional findByAccountCodeAndCurrency(String accountCode, String currency);
- /**
- * Finds the balance with a pessimistic lock (FOR UPDATE) for safe concurrent updates.
- */
Optional findForUpdate(String accountCode, String currency);
List findAll();
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/ReconciliationProperties.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/ReconciliationProperties.java
index b935a2f0..00764245 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/ReconciliationProperties.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/port/ReconciliationProperties.java
@@ -2,10 +2,6 @@
import java.math.BigDecimal;
-/**
- * Domain port for reconciliation configuration.
- * Implemented by application-layer config (e.g. {@code ReconciliationConfig}).
- */
public interface ReconciliationProperties {
BigDecimal tolerance();
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/AccountingRules.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/AccountingRules.java
index 15d6fe1c..e8e0e456 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/AccountingRules.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/AccountingRules.java
@@ -1,6 +1,5 @@
package com.stablecoin.payments.ledger.domain.service;
-
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@@ -8,13 +7,6 @@
import static com.stablecoin.payments.ledger.domain.model.EntryType.CREDIT;
import static com.stablecoin.payments.ledger.domain.model.EntryType.DEBIT;
-/**
- * Maps payment lifecycle events to balanced journal entry templates.
- * Each method returns a {@link TransactionRequest} with the correct debit/credit pairs
- * according to the chart of accounts.
- *
- * This is a pure domain service — no dependencies on Spring or infrastructure.
- */
public final class AccountingRules {
// Chart of accounts codes
@@ -32,9 +24,6 @@ public final class AccountingRules {
private AccountingRules() {
}
- /**
- * payment.initiated → DEBIT 1000 Fiat Receivable / CREDIT 2010 Client Funds Held
- */
public static TransactionRequest paymentInitiated(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String currency
@@ -49,9 +38,6 @@ public static TransactionRequest paymentInitiated(
);
}
- /**
- * fiat.collected → DEBIT 1001 Fiat Cash / CREDIT 1000 Fiat Receivable
- */
public static TransactionRequest fiatCollected(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String currency
@@ -66,9 +52,6 @@ public static TransactionRequest fiatCollected(
);
}
- /**
- * chain.transfer.submitted → DEBIT 1010 Stablecoin Inventory / CREDIT 9000 In-Transit Clearing
- */
public static TransactionRequest chainTransferSubmitted(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String stablecoin
@@ -83,9 +66,6 @@ public static TransactionRequest chainTransferSubmitted(
);
}
- /**
- * chain.transfer.confirmed → DEBIT 1020 Off-Ramp Receivable / CREDIT 1010 Stablecoin Inventory
- */
public static TransactionRequest chainTransferConfirmed(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String stablecoin
@@ -100,9 +80,6 @@ public static TransactionRequest chainTransferConfirmed(
);
}
- /**
- * stablecoin.redeemed → DEBIT 1030 Stablecoin Redeemed / CREDIT 1020 Off-Ramp Receivable
- */
public static TransactionRequest stablecoinRedeemed(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String stablecoin
@@ -117,9 +94,6 @@ public static TransactionRequest stablecoinRedeemed(
);
}
- /**
- * fiat.payout.completed → DEBIT 2010 Client Funds Held / CREDIT 2000 Fiat Payable
- */
public static TransactionRequest fiatPayoutCompleted(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal amount, String currency
@@ -134,9 +108,6 @@ public static TransactionRequest fiatPayoutCompleted(
);
}
- /**
- * payment.completed (clearing) → DEBIT 9000 In-Transit Clearing / CREDIT 1030 Stablecoin Redeemed
- */
public static TransactionRequest paymentCompletedClearing(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal stablecoinAmount, String stablecoin
@@ -151,9 +122,6 @@ public static TransactionRequest paymentCompletedClearing(
);
}
- /**
- * payment.completed (revenue) → DEBIT 2010 Client Funds Held / CREDIT 4000 FX Spread Revenue
- */
public static TransactionRequest paymentCompletedRevenue(
UUID paymentId, UUID correlationId, UUID sourceEventId,
BigDecimal feeAmount, String currency
@@ -168,10 +136,6 @@ public static TransactionRequest paymentCompletedRevenue(
);
}
- /**
- * payment.failed (reversal) — creates reversal entries for all existing entries.
- * Swaps DEBIT ↔ CREDIT with same amount/currency/account.
- */
public static List reversalEntries(List originalEntries) {
return originalEntries.stream()
.map(e -> new JournalEntryRequest(
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceCalculator.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceCalculator.java
index 634c9061..8c1632df 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceCalculator.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceCalculator.java
@@ -15,17 +15,6 @@
import java.util.List;
import java.util.Map;
-/**
- * Domain service that computes balance updates for journal entries.
- * Acquires pessimistic locks on AccountBalance rows in ascending account_code order
- * to prevent deadlocks during concurrent transaction posting.
- *
- * Balance rules:
- *
- * ASSET/CLEARING/EXPENSE: DEBIT increases, CREDIT decreases
- * LIABILITY/REVENUE: CREDIT increases, DEBIT decreases
- *
- */
@Service
@RequiredArgsConstructor
public class BalanceCalculator {
@@ -33,13 +22,6 @@ public class BalanceCalculator {
private final AccountRepository accountRepository;
private final AccountBalanceRepository balanceRepository;
- /**
- * Computes balance updates for all entries in a transaction.
- * Locks AccountBalance rows in ascending account_code + currency order to prevent deadlocks.
- *
- * @param entries the entry requests to compute balances for
- * @return map of "accountCode:currency" → BalanceUpdate
- */
public Map computeBalances(List entries) {
List sorted = entries.stream()
.sorted(Comparator.comparing(JournalEntryRequest::accountCode)
@@ -90,9 +72,6 @@ static BigDecimal computeNewBalance(
return currentBalance.subtract(amount);
}
- /**
- * Builds the composite key for balance lookup: "accountCode:currency".
- */
public static String balanceKey(String accountCode, String currency) {
return accountCode + ":" + currency;
}
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceUpdate.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceUpdate.java
index 38c4a51b..07da1318 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceUpdate.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/BalanceUpdate.java
@@ -3,10 +3,6 @@
import java.math.BigDecimal;
import java.util.Objects;
-/**
- * Value object representing the result of a balance computation.
- * Contains the new balance_after and account_version for a journal entry.
- */
public record BalanceUpdate(
BigDecimal balanceAfter,
long accountVersion
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalCommandHandler.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalCommandHandler.java
index 1606a8f3..e2da9bfe 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalCommandHandler.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalCommandHandler.java
@@ -22,18 +22,6 @@
import java.util.Set;
import java.util.UUID;
-/**
- * Domain command handler that orchestrates posting balanced ledger transactions.
- * Wires AccountingRules → BalanceCalculator → persistence for the full posting flow.
- *
- * Responsibilities:
- *
- * Idempotent event processing via source_event_id uniqueness
- * Sequence numbering across a payment's entries
- * Balance computation and AccountBalance persistence
- * Audit trail creation
- *
- */
@Service
@Transactional
@RequiredArgsConstructor
@@ -50,13 +38,6 @@ public class JournalCommandHandler {
private final BalanceCalculator balanceCalculator;
private final Clock clock;
- /**
- * Posts a balanced ledger transaction from a {@link TransactionRequest}.
- * Idempotent: if the source_event_id was already processed, returns the existing transaction.
- *
- * @param request the transaction request (from AccountingRules mapping)
- * @return the posted LedgerTransaction
- */
public LedgerTransaction postTransaction(TransactionRequest request) {
if (transactionRepository.existsBySourceEventId(request.sourceEventId())) {
return findExistingTransaction(request);
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalEntryRequest.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalEntryRequest.java
index 35519a41..6dc65b30 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalEntryRequest.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/JournalEntryRequest.java
@@ -5,10 +5,6 @@
import java.math.BigDecimal;
import java.util.Objects;
-/**
- * Value object representing a single debit or credit entry to be posted.
- * Used by {@link AccountingRules} to define the journal entry template for each event.
- */
public record JournalEntryRequest(
EntryType entryType,
String accountCode,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/LedgerQueryHandler.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/LedgerQueryHandler.java
index ed0a0162..cd143088 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/LedgerQueryHandler.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/LedgerQueryHandler.java
@@ -21,10 +21,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Read-only query handler for ledger data.
- * Not @Transactional — all methods are pure reads with no state changes.
- */
@Service
@RequiredArgsConstructor
public class LedgerQueryHandler {
@@ -35,10 +31,6 @@ public class LedgerQueryHandler {
private final AccountRepository accountRepository;
private final ReconciliationRepository reconciliationRepository;
- /**
- * Returns all ledger transactions with their entries for a payment.
- * Throws JournalNotFoundException if no transactions exist.
- */
public List getPaymentJournal(UUID paymentId) {
var transactions = transactionRepository.findByPaymentId(paymentId);
if (transactions.isEmpty()) {
@@ -47,17 +39,11 @@ public List getPaymentJournal(UUID paymentId) {
return transactions;
}
- /**
- * Returns the reconciliation record with all legs for a payment.
- */
public ReconciliationRecord getReconciliation(UUID paymentId) {
return reconciliationRepository.findByPaymentId(paymentId)
.orElseThrow(() -> new ReconciliationNotFoundException(paymentId));
}
- /**
- * Returns account info + multi-currency balances for a given account code.
- */
public AccountWithBalances getAccountBalance(String accountCode) {
var account = accountRepository.findByAccountCode(accountCode)
.orElseThrow(() -> new AccountNotFoundException(accountCode));
@@ -65,9 +51,6 @@ public AccountWithBalances getAccountBalance(String accountCode) {
return new AccountWithBalances(account, balances);
}
- /**
- * Returns paginated journal entries for a specific account + currency.
- */
public AccountHistory getAccountHistory(String accountCode, String currency, int page, int size) {
accountRepository.findByAccountCode(accountCode)
.orElseThrow(() -> new AccountNotFoundException(accountCode));
@@ -77,9 +60,6 @@ public AccountHistory getAccountHistory(String accountCode, String currency, int
return new AccountHistory(accountCode, currency, entries, page, size, totalElements);
}
- /**
- * Returns trial balance — all active accounts with debit/credit balance split.
- */
public TrialBalance getTrialBalance() {
var accounts = accountRepository.findByIsActive(true);
var allBalances = balanceRepository.findAll();
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/ReconciliationCommandHandler.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/ReconciliationCommandHandler.java
index ecb63cf2..9324ab6c 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/ReconciliationCommandHandler.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/ReconciliationCommandHandler.java
@@ -20,11 +20,6 @@
import java.util.Optional;
import java.util.UUID;
-/**
- * Reconciliation command handler: records legs as payment lifecycle events arrive,
- * finalizes reconciliation when all 5 required legs are present, and publishes
- * outcome events via outbox.
- */
@Slf4j
@Service
@Transactional
@@ -37,10 +32,6 @@ public class ReconciliationCommandHandler {
private final LedgerEventPublisher eventPublisher;
private final Clock clock;
- /**
- * Creates a PENDING reconciliation record for a payment.
- * Idempotent: returns existing record if already created.
- */
public ReconciliationRecord createRecord(UUID paymentId) {
return reconciliationRepository.findByPaymentId(paymentId)
.orElseGet(() -> {
@@ -49,10 +40,6 @@ var record = ReconciliationRecord.create(paymentId, properties.tolerance());
});
}
- /**
- * Records a reconciliation leg for a payment. Creates the reconciliation
- * record if it doesn't exist. Idempotent: skips if leg type already recorded.
- */
public void recordLeg(UUID paymentId, ReconciliationLegType legType,
BigDecimal amount, String currency, UUID sourceEventId) {
var record = createRecord(paymentId);
@@ -69,9 +56,6 @@ var record = createRecord(paymentId);
reconciliationRepository.save(record.addLeg(leg));
}
- /**
- * Finds a specific reconciliation leg for a payment.
- */
public Optional findLeg(UUID paymentId, ReconciliationLegType legType) {
return reconciliationRepository.findByPaymentId(paymentId)
.flatMap(record -> record.legs().stream()
@@ -79,9 +63,6 @@ public Optional findLeg(UUID paymentId, ReconciliationLegType
.findFirst());
}
- /**
- * Marks reconciliation as DISCREPANCY (e.g., on payment failure).
- */
public void markDiscrepancy(UUID paymentId) {
reconciliationRepository.findByPaymentId(paymentId)
.ifPresent(record -> {
@@ -96,12 +77,6 @@ public void markDiscrepancy(UUID paymentId) {
});
}
- /**
- * Finalizes reconciliation if all 5 required legs are present.
- * Calculates discrepancy (|stablecoin_minted - stablecoin_redeemed|) and
- * compares against tolerance. Publishes RECONCILED or DISCREPANCY event.
- * Idempotent: skips already finalized records or records without all legs.
- */
public Optional finalizeReconciliation(UUID paymentId) {
return reconciliationRepository.findByPaymentId(paymentId)
.filter(r -> r.status() != ReconciliationStatus.RECONCILED
@@ -134,10 +109,6 @@ private void publishOutcome(ReconciliationRecord saved, BigDecimal discrepancy)
}
}
- /**
- * Discrepancy = |stablecoin_minted - stablecoin_redeemed|.
- * These should be identical in a normal stablecoin sandwich flow.
- */
BigDecimal calculateDiscrepancy(ReconciliationRecord record) {
var mintedAmount = record.legs().stream()
.filter(l -> l.legType() == ReconciliationLegType.STABLECOIN_MINTED)
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/TransactionRequest.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/TransactionRequest.java
index b5c56972..5dc8117a 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/TransactionRequest.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/domain/service/TransactionRequest.java
@@ -4,9 +4,6 @@
import java.util.Objects;
import java.util.UUID;
-/**
- * Command object to create a balanced ledger transaction with journal entries.
- */
public record TransactionRequest(
UUID paymentId,
UUID correlationId,
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/config/FallbackAdaptersConfig.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/config/FallbackAdaptersConfig.java
index ee9a3c64..384ca2a5 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/config/FallbackAdaptersConfig.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/config/FallbackAdaptersConfig.java
@@ -4,13 +4,6 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
-/**
- * Provides fallback (dev/test) implementations of external provider ports.
- * Activated only when {@code app.fallback-adapters.enabled=true}.
- *
- * Note: Outbox-based event publishers are infrastructure adapters (DB-dependent)
- * and are NOT included here — they are always active when a database is present.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "true")
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/messaging/LedgerEventConsumer.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/messaging/LedgerEventConsumer.java
index 4dfc7ed6..4c9ea7cd 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/messaging/LedgerEventConsumer.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/messaging/LedgerEventConsumer.java
@@ -22,12 +22,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Kafka consumers for all 9 payment lifecycle events.
- * Each listener parses the event, delegates to {@link JournalCommandHandler}
- * for journal entry creation and {@link ReconciliationCommandHandler}
- * for reconciliation leg tracking.
- */
@Slf4j
@Component
@RequiredArgsConstructor
@@ -41,7 +35,6 @@ public class LedgerEventConsumer {
private final ReconciliationCommandHandler reconciliationCommandHandler;
private final LedgerTransactionRepository transactionRepository;
- // --- payment.initiated ---
@KafkaListener(topics = "payment.initiated", groupId = "ledger-payment")
@Transactional
@@ -58,7 +51,6 @@ public void onPaymentInitiated(String message) {
reconciliationCommandHandler.createRecord(event.paymentId());
}
- // --- fx.rate.locked ---
@KafkaListener(topics = "fx.rate.locked", groupId = "ledger-fx")
@Transactional
@@ -74,7 +66,6 @@ public void onFxRateLocked(String message) {
feeAmount, event.fromCurrency(), event.lockId());
}
- // --- fiat.collected ---
@KafkaListener(topics = "fiat.collected", groupId = "ledger-onramp")
@Transactional
@@ -92,7 +83,6 @@ public void onFiatCollected(String message) {
event.settledAmount(), event.currency(), event.eventId());
}
- // --- chain.transfer.submitted ---
@KafkaListener(topics = "chain.transfer.submitted", groupId = "ledger-chain-submit")
@Transactional
@@ -111,7 +101,6 @@ public void onChainTransferSubmitted(String message) {
amount, event.stablecoin(), event.eventId());
}
- // --- chain.transfer.confirmed ---
@KafkaListener(topics = "chain.transfer.confirmed", groupId = "ledger-chain-confirm")
@Transactional
@@ -140,7 +129,6 @@ public void onChainTransferConfirmed(String message) {
submittedEntry.amount(), submittedEntry.currency(), event.eventId());
}
- // --- stablecoin.redeemed ---
@KafkaListener(topics = "stablecoin.redeemed", groupId = "ledger-redeem")
@Transactional
@@ -158,7 +146,6 @@ public void onStablecoinRedeemed(String message) {
event.redeemedAmount(), event.stablecoin(), event.eventId());
}
- // --- fiat.payout.completed ---
@KafkaListener(topics = "fiat.payout.completed", groupId = "ledger-offramp")
@Transactional
@@ -176,7 +163,6 @@ public void onFiatPayoutCompleted(String message) {
event.fiatAmount(), event.targetCurrency(), event.eventId());
}
- // --- payment.completed ---
@KafkaListener(topics = "payment.completed", groupId = "ledger-complete")
@Transactional
@@ -218,7 +204,6 @@ public void onPaymentCompleted(String message) {
reconciliationCommandHandler.finalizeReconciliation(event.paymentId());
}
- // --- payment.failed ---
@KafkaListener(topics = "payment.failed", groupId = "ledger-failed")
@Transactional
@@ -247,7 +232,6 @@ public void onPaymentFailed(String message) {
reconciliationCommandHandler.markDiscrepancy(event.paymentId());
}
- // --- ACL Event DTOs (package-private) ---
record PaymentInitiatedEvent(UUID paymentId, UUID correlationId,
Money sourceAmount, Instant initiatedAt) {}
@@ -282,7 +266,6 @@ record Money(BigDecimal amount, String currency) {}
record FxRateInfo(UUID quoteId, String from, String to, BigDecimal rate,
Instant lockedAt, Instant expiresAt, String provider) {}
- // --- Helpers ---
private T parseEvent(String message, Class type) {
try {
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/metrics/LedgerMetrics.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/metrics/LedgerMetrics.java
index 386a7ff4..15de18fd 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/metrics/LedgerMetrics.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/metrics/LedgerMetrics.java
@@ -4,20 +4,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Ledger and Accounting service.
- *
- * Tracks transaction posting, reconciliation completion, and discrepancy detection.
- */
@Component
@RequiredArgsConstructor
public class LedgerMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a ledger transaction posted event.
- */
public void recordTransactionPosted(String type, String currency) {
meterRegistry.counter("ledger.transaction.posted",
"type", type,
@@ -25,18 +17,12 @@ public void recordTransactionPosted(String type, String currency) {
).increment();
}
- /**
- * Records a reconciliation completion event.
- */
public void recordReconciliationCompleted(String status) {
meterRegistry.counter("ledger.reconciliation.completed",
"status", status
).increment();
}
- /**
- * Records a reconciliation discrepancy event.
- */
public void recordReconciliationDiscrepancy() {
meterRegistry.counter("ledger.reconciliation.discrepancy").increment();
}
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/AuditEventEntity.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/AuditEventEntity.java
index deca559a..8b42f441 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/AuditEventEntity.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/AuditEventEntity.java
@@ -16,10 +16,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Immutable, append-only entity — no @Version, no update methods.
- * Partitioned by occurred_at (composite PK: audit_id + occurred_at).
- */
@Entity
@Table(name = "audit_events")
@IdClass(AuditEventId.class)
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/JournalEntryEntity.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/JournalEntryEntity.java
index 32ce2dce..a15ae914 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/JournalEntryEntity.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/JournalEntryEntity.java
@@ -15,10 +15,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Immutable, append-only entity — no @Version, no update methods.
- * Partitioned by created_at (composite PK: entry_id + created_at).
- */
@Entity
@Table(name = "journal_entries")
@IdClass(JournalEntryId.class)
diff --git a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/LedgerTransactionEntity.java b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/LedgerTransactionEntity.java
index d2e635bf..70b2dc86 100644
--- a/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/LedgerTransactionEntity.java
+++ b/ledger-accounting/ledger-accounting/src/main/java/com/stablecoin/payments/ledger/infrastructure/persistence/entity/LedgerTransactionEntity.java
@@ -13,9 +13,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Immutable, append-only entity — no @Version, no update methods.
- */
@Entity
@Table(name = "ledger_transactions")
@Getter
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/request/UpdateRoleRequest.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/request/UpdateRoleRequest.java
index ec87915e..8af2725d 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/request/UpdateRoleRequest.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/request/UpdateRoleRequest.java
@@ -4,10 +4,6 @@
import java.util.List;
-/**
- * Sent to {@code PATCH /v1/merchants/{merchantId}/roles/{roleId}}.
- * Only permissions can be updated on a custom role.
- */
public record UpdateRoleRequest(
@NotEmpty List<@NotEmpty String> permissions
) {}
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ChangeRoleResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ChangeRoleResponse.java
index 58321abc..d940c9d6 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ChangeRoleResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ChangeRoleResponse.java
@@ -3,9 +3,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Returned by {@code PATCH /v1/merchants/{merchantId}/users/{userId}/role}.
- */
public record ChangeRoleResponse(
UUID userId,
String oldRole,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/DataResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/DataResponse.java
index 63176844..05730c71 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/DataResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/DataResponse.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.merchant.iam.api.response;
-/**
- * Single-item envelope matching the spec's {@code {"data": {...}}} shape.
- */
public record DataResponse(T data) {
public static DataResponse of(T data) {
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/InvitationResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/InvitationResponse.java
index 63e3bba7..1c5b8166 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/InvitationResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/InvitationResponse.java
@@ -3,9 +3,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Returned by {@code POST /v1/merchants/{merchantId}/users/invite}.
- */
public record InvitationResponse(
UUID invitationId,
String email,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/LoginResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/LoginResponse.java
index 9c6d288c..d9a63ff9 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/LoginResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/LoginResponse.java
@@ -3,9 +3,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Returned on successful login (no MFA, or after MFA verify).
- */
public record LoginResponse(
String accessToken,
String refreshToken,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/MfaChallengeResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/MfaChallengeResponse.java
index fc0f52ab..6acee0cc 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/MfaChallengeResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/MfaChallengeResponse.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.merchant.iam.api.response;
-/**
- * Returned on login when MFA is required — client must call {@code POST /v1/auth/mfa/verify}.
- */
public record MfaChallengeResponse(
boolean mfaRequired,
String mfaChallengeId,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/PermissionCheckResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/PermissionCheckResponse.java
index 347188e2..53622a53 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/PermissionCheckResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/PermissionCheckResponse.java
@@ -1,8 +1,5 @@
package com.stablecoin.payments.merchant.iam.api.response;
-/**
- * Response for {@code GET /v1/auth/permissions/check} — used by S10 API Gateway.
- */
public record PermissionCheckResponse(
boolean allowed,
String role,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ReactivateUserResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ReactivateUserResponse.java
index 865a7ce1..d66ecc65 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ReactivateUserResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/ReactivateUserResponse.java
@@ -3,7 +3,4 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Returned by {@code POST /v1/merchants/{merchantId}/users/{userId}/reactivate}.
- */
public record ReactivateUserResponse(UUID userId, String status, Instant activatedAt) {}
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/RoleResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/RoleResponse.java
index 196b398b..e8e17a18 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/RoleResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/RoleResponse.java
@@ -4,9 +4,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Full role representation — used in list and create/update responses.
- */
public record RoleResponse(
UUID roleId,
String roleName,
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/SuspendUserResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/SuspendUserResponse.java
index 99ea06c3..4f38c6ec 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/SuspendUserResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/SuspendUserResponse.java
@@ -3,7 +3,4 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Returned by {@code POST /v1/merchants/{merchantId}/users/{userId}/suspend}.
- */
public record SuspendUserResponse(UUID userId, String status, Instant suspendedAt) {}
diff --git a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/UserResponse.java b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/UserResponse.java
index 8c9c6f62..444ccd81 100644
--- a/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/UserResponse.java
+++ b/merchant-iam/merchant-iam-api/src/main/java/com/stablecoin/payments/merchant/iam/api/response/UserResponse.java
@@ -3,9 +3,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * User representation returned by list and accept-invitation endpoints.
- */
public record UserResponse(
UUID userId,
UUID merchantId,
diff --git a/merchant-iam/merchant-iam-client/src/main/java/com/stablecoin/payments/merchant/iam/client/MerchantIamClient.java b/merchant-iam/merchant-iam-client/src/main/java/com/stablecoin/payments/merchant/iam/client/MerchantIamClient.java
index c34f03ff..963ae464 100644
--- a/merchant-iam/merchant-iam-client/src/main/java/com/stablecoin/payments/merchant/iam/client/MerchantIamClient.java
+++ b/merchant-iam/merchant-iam-client/src/main/java/com/stablecoin/payments/merchant/iam/client/MerchantIamClient.java
@@ -13,27 +13,9 @@
import java.util.UUID;
-/**
- * Feign client for S13 Merchant IAM & Role Management.
- *
- * Primary consumer: S10 API Gateway — calls {@code checkPermission} on every inbound request
- * to resolve whether the authenticated user has the required permission.
- *
- *
Configure the URL via {@code clients.merchant-iam.url} in the caller's {@code application.yml}.
- */
@FeignClient(name = "merchant-iam", url = "${clients.merchant-iam.url}")
public interface MerchantIamClient {
- /**
- * Checks whether a user has a specific permission.
- * Backed by Redis cache (60s TTL) with DB fallback.
- * Used by S10 for real-time per-request authorisation.
- *
- * @param userId the user's UUID
- * @param merchantId the merchant the user belongs to
- * @param permission permission string, e.g. {@code payments:write} or {@code team:manage}
- * @param bearerToken the caller's service bearer token ({@code Authorization: Bearer })
- */
@GetMapping("/v1/auth/permissions/check")
DataResponse checkPermission(
@RequestParam("user_id") UUID userId,
@@ -41,10 +23,6 @@ DataResponse checkPermission(
@RequestParam("permission") String permission,
@RequestHeader("Authorization") String bearerToken);
- /**
- * Lists all active users for a merchant (paginated).
- * Used by S10 to resolve user identity from a token.
- */
@GetMapping("/v1/merchants/{merchantId}/users")
PageResponse listUsers(
@PathVariable UUID merchantId,
@@ -52,21 +30,12 @@ PageResponse listUsers(
@RequestParam(defaultValue = "50") int size,
@RequestHeader("Authorization") String bearerToken);
- /**
- * Lists roles for a merchant.
- * Used to validate role assignments during token enrichment.
- */
@GetMapping("/v1/merchants/{merchantId}/roles")
PageResponse listRoles(
@PathVariable UUID merchantId,
@RequestParam(defaultValue = "false") boolean includeInactive,
@RequestHeader("Authorization") String bearerToken);
- /**
- * Retrieves the JWKS (public key set) for JWT signature verification.
- * S10 fetches this once on startup and caches it.
- * No auth header required — public endpoint.
- */
@GetMapping("/v1/.well-known/jwks.json")
String jwks();
}
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/CorrelationIdFilter.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/CorrelationIdFilter.java
index bc8922bf..b45315e0 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/CorrelationIdFilter.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/CorrelationIdFilter.java
@@ -13,10 +13,6 @@
import java.io.IOException;
import java.util.UUID;
-/**
- * Extracts {@code X-Correlation-Id} from inbound requests (or generates one),
- * stores it in MDC for log correlation, and echoes it back in the response.
- */
@Slf4j
@Component
@Order(1)
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/IdempotencyKeyFilter.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/IdempotencyKeyFilter.java
index 8cbdc230..e5aa6952 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/IdempotencyKeyFilter.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/IdempotencyKeyFilter.java
@@ -31,15 +31,6 @@
import java.util.HexFormat;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) -- excluding auth and invitation endpoints which use
- * tokens as implicit idempotency controls.
- * Uses INSERT-first reservation pattern to prevent TOCTOU races:
- * 1. Try INSERT with status_code=0 (reservation)
- * 2. If INSERT succeeds, proceed with request and UPDATE with real response
- * 3. If INSERT fails (duplicate), re-read stored record for replay or conflict
- */
@Slf4j
@Component
@Order(2)
@@ -57,7 +48,6 @@ public class IdempotencyKeyFilter extends OncePerRequestFilter {
"/actuator/"
);
- /** Path segments that mark a sub-resource as auth-related (login, refresh, logout, mfa). */
private static final Set AUTH_PATH_SEGMENTS = Set.of(
"/auth/login", "/auth/refresh", "/auth/logout", "/auth/mfa"
);
@@ -253,10 +243,6 @@ private String computeSha256(byte[] body) {
record IdempotencyRecord(String idempotencyKey, String requestHash, String responseBody, int statusCode) {}
- /**
- * Request wrapper that replays a cached body so downstream filters and
- * controllers can read the request body after it has already been consumed.
- */
private static class CachedBodyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/MetricsConfig.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/MetricsConfig.java
index 3206f932..ada1f187 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/MetricsConfig.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/config/MetricsConfig.java
@@ -5,10 +5,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-/**
- * Registers the 11 Micrometer counters specified in the S13 spec Section 7.
- * Inject the {@link Counter} beans where needed, or use {@link MeterRegistry} directly.
- */
@Configuration
public class MetricsConfig {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/AuthController.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/AuthController.java
index 0a3cde6d..46ddc445 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/AuthController.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/AuthController.java
@@ -45,12 +45,7 @@ public class AuthController {
private final MerchantTeamService merchantTeamService;
private final IamResponseMapper mapper;
- // ── Login ─────────────────────────────────────────────────────────────────
- /**
- * POST /v1/merchants/{merchantId}/auth/login
- * Returns LoginResponse on success, or MfaChallengeResponse if MFA is enabled.
- */
@PostMapping("/merchants/{merchantId}/auth/login")
public DataResponse> login(
@PathVariable UUID merchantId,
@@ -67,11 +62,6 @@ public DataResponse> login(
}
}
- /**
- * POST /v1/merchants/{merchantId}/auth/mfa/verify
- * Verifies TOTP code against the stored challenge; returns tokens on success.
- * The {@code mfaChallengeId} from the login MFA challenge response is passed as {@code totpCode}.
- */
@PostMapping("/merchants/{merchantId}/auth/mfa/verify")
public DataResponse verifyMfa(
@PathVariable UUID merchantId,
@@ -81,13 +71,7 @@ public DataResponse verifyMfa(
return DataResponse.of(buildLoginResponse(result));
}
- // ── Refresh token ────────────────────────────────────────────────────────
- /**
- * POST /v1/auth/refresh
- * Exchanges a valid refresh token for a new access token.
- * No authentication required — the refresh token itself serves as the credential.
- */
@PostMapping("/auth/refresh")
public DataResponse refresh(
@Valid @RequestBody RefreshTokenRequest request) {
@@ -96,13 +80,7 @@ public DataResponse refresh(
return DataResponse.of(new RefreshTokenResponse(result.accessToken(), result.expiresIn()));
}
- // ── MFA setup ──────────────────────────────────────────────────────────
- /**
- * POST /v1/merchants/{merchantId}/users/{userId}/mfa/setup
- * Generates a TOTP secret and provisioning URI for MFA enrollment.
- * Requires authentication — caller must be the target user or have team:manage permission.
- */
@PostMapping("/merchants/{merchantId}/users/{userId}/mfa/setup")
public DataResponse setupMfa(
@PathVariable UUID merchantId,
@@ -116,10 +94,6 @@ public DataResponse setupMfa(
return DataResponse.of(new MfaSetupResponse(result.secret(), result.provisioningUri()));
}
- /**
- * POST /v1/merchants/{merchantId}/users/{userId}/mfa/activate
- * Verifies the TOTP code against the provided secret and enables MFA for the user.
- */
@PostMapping("/merchants/{merchantId}/users/{userId}/mfa/activate")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void activateMfa(
@@ -134,12 +108,7 @@ public void activateMfa(
log.info("MFA activated userId={}", userId);
}
- // ── Invitation acceptance ─────────────────────────────────────────────────
- /**
- * POST /v1/invitations/{token}/accept
- * No auth required — invitation token in URL serves as credential.
- */
@PostMapping("/invitations/{token}/accept")
public DataResponse acceptInvitation(
@PathVariable String token,
@@ -151,12 +120,7 @@ public DataResponse acceptInvitation(
return DataResponse.of(mapper.toUserResponse(activated, role));
}
- // ── Logout ────────────────────────────────────────────────────────────────
- /**
- * POST /v1/auth/logout
- * Revokes all sessions for the authenticated user.
- */
@PostMapping("/auth/logout")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void logout() {
@@ -169,18 +133,12 @@ public void logout() {
}
}
- // ── JWKS ──────────────────────────────────────────────────────────────────
- /**
- * GET /v1/.well-known/jwks.json
- * Publishes S13's ES256 public key. S10 (API Gateway) fetches this to validate tokens.
- */
@GetMapping(value = "/.well-known/jwks.json", produces = MediaType.APPLICATION_JSON_VALUE)
public String jwks() {
return authService.jwksJson();
}
- // ── Helpers ───────────────────────────────────────────────────────────────
private void validateMfaAccess(UserAuthentication userAuth, UUID merchantId, UUID userId) {
if (!userAuth.merchantId().equals(merchantId)) {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/PermissionsController.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/PermissionsController.java
index 84f309f0..4d5aac25 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/PermissionsController.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/PermissionsController.java
@@ -22,10 +22,6 @@ public class PermissionsController {
private final PermissionQueryService permissionQueryService;
- /**
- * GET /v1/auth/permissions/check?user_id=&merchant_id=&permission=payments:write
- * Called by S10 API Gateway on every inbound request.
- */
@GetMapping("/check")
public DataResponse checkPermission(
@RequestParam("user_id") UUID userId,
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/mapper/IamResponseMapper.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/mapper/IamResponseMapper.java
index 76d07694..13952d89 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/mapper/IamResponseMapper.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/controller/mapper/IamResponseMapper.java
@@ -21,7 +21,6 @@
@Mapper
public interface IamResponseMapper {
- // ── Role ─────────────────────────────────────────────────────────────────
RoleSummary toRoleSummary(Role role);
@@ -29,7 +28,6 @@ public interface IamResponseMapper {
@Mapping(target = "userCount", constant = "0L")
RoleResponse toRoleResponse(Role role);
- // ── User ─────────────────────────────────────────────────────────────────
default UserResponse toUserResponse(MerchantUser user, Role role) {
return new UserResponse(
@@ -46,7 +44,6 @@ default UserResponse toUserResponse(MerchantUser user, Role role) {
user.createdAt());
}
- // ── Invitation ────────────────────────────────────────────────────────────
default InvitationResponse toInvitationResponse(Invitation invitation, String roleName) {
return new InvitationResponse(
@@ -59,7 +56,6 @@ default InvitationResponse toInvitationResponse(Invitation invitation, String ro
invitation.createdAt());
}
- // ── State-change responses ────────────────────────────────────────────────
default ChangeRoleResponse toChangeRoleResponse(RoleChangeResult result) {
return new ChangeRoleResponse(
@@ -78,7 +74,6 @@ default ReactivateUserResponse toReactivateUserResponse(MerchantUser user) {
return new ReactivateUserResponse(user.userId(), user.status().name(), user.activatedAt());
}
- // ── Helpers ───────────────────────────────────────────────────────────────
default List toPermissionStrings(List permissions) {
if (permissions == null) {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/InvitationExpiryJob.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/InvitationExpiryJob.java
index 5565c600..fbf66bbe 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/InvitationExpiryJob.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/InvitationExpiryJob.java
@@ -9,10 +9,6 @@
import java.time.Instant;
-/**
- * Marks PENDING invitations whose {@code expires_at} has passed as EXPIRED.
- * Runs every 15 minutes.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/SessionCleanupJob.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/SessionCleanupJob.java
index f7a3a3a5..fe8b6a20 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/SessionCleanupJob.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/job/SessionCleanupJob.java
@@ -9,10 +9,6 @@
import java.time.Instant;
-/**
- * Purges sessions that have expired (past {@code expires_at}) but were not explicitly revoked.
- * Runs every hour.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/security/UserAuthentication.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/security/UserAuthentication.java
index 04fee7c3..208e4d0e 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/security/UserAuthentication.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/application/security/UserAuthentication.java
@@ -6,10 +6,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Spring Security principal for S13 authenticated users.
- * Extracted from JWT claims after signature verification.
- */
public class UserAuthentication extends AbstractAuthenticationToken {
private final UUID userId;
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/AuthService.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/AuthService.java
index 9dbc5965..177f8e3c 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/AuthService.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/AuthService.java
@@ -16,10 +16,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Handles authentication: login, MFA verify, token issue, and logout.
- * Returns domain objects only — controllers map to API response DTOs.
- */
@Slf4j
@Service
@RequiredArgsConstructor
@@ -43,12 +39,7 @@ public record MfaChallengeResult(String challengeId, int expiresInSeconds) {}
public record MfaSetupResult(String secret, String provisioningUri) {}
- // ── Login ─────────────────────────────────────────────────────────────────
- /**
- * Validates credentials, enforces brute-force lockout.
- * Returns tokens on success; throws {@link MfaRequiredException} if MFA is enabled.
- */
@Transactional
public LoginResult login(UUID merchantId, String email, String password) {
var emailHash = emailHasher.hash(email);
@@ -85,10 +76,6 @@ public LoginResult login(UUID merchantId, String email, String password) {
return issueTokens(user, false);
}
- /**
- * Verifies a TOTP code using the stored MFA challenge.
- * Consumes the challenge (one-time use).
- */
@Transactional
public LoginResult verifyMfa(String challengeId, String totpCode) {
var challenge = mfaChallengeStore.consume(challengeId)
@@ -110,14 +97,9 @@ public LoginResult verifyMfa(String challengeId, String totpCode) {
return issueTokens(user, true);
}
- // ── Refresh token ────────────────────────────────────────────────────────
public record RefreshResult(String accessToken, int expiresIn) {}
- /**
- * Exchanges a valid refresh token for a new access token.
- * Validates the refresh JWT signature, expiry, session state, and that the user is still active.
- */
@Transactional(readOnly = true)
public RefreshResult refreshToken(String refreshTokenValue) {
var parsed = jwtTokenIssuer.parseRefreshToken(refreshTokenValue);
@@ -145,7 +127,6 @@ public RefreshResult refreshToken(String refreshTokenValue) {
return new RefreshResult(accessToken, 3600);
}
- // ── MFA setup ─────────────────────────────────────────────────────────────
@Transactional
public MfaSetupResult setupMfa(UUID userId, String email) {
@@ -168,7 +149,6 @@ public MerchantUser activateMfa(UUID userId, String secret, String totpCode) {
return saved;
}
- // ── Utilities ─────────────────────────────────────────────────────────────
@Transactional(readOnly = true)
public Role findRoleForUser(UUID roleId) {
@@ -186,7 +166,6 @@ public void logout(UUID userId) {
log.info("Logged out userId={}", userId);
}
- // ── Helpers ───────────────────────────────────────────────────────────────
private LoginResult issueTokens(MerchantUser user, boolean mfaVerified) {
var role = roleRepository.findById(user.roleId())
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/EmailHasher.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/EmailHasher.java
index faea2c1b..042c785f 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/EmailHasher.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/EmailHasher.java
@@ -8,10 +8,6 @@
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
-/**
- * Computes a deterministic SHA-256 hash of a normalised email address.
- * Used for: database lookup index, domain event {@code emailHash} field (no PII in Kafka).
- */
@Slf4j
@Component
public class EmailHasher {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/InvitationTokenGenerator.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/InvitationTokenGenerator.java
index 20f2cc4b..44320473 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/InvitationTokenGenerator.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/InvitationTokenGenerator.java
@@ -10,10 +10,6 @@
import java.util.Base64;
import java.util.HexFormat;
-/**
- * Generates cryptographically secure invitation tokens and computes their SHA-256 hashes.
- * The plaintext token is sent to the invitee; only the hash is stored in the database.
- */
@Slf4j
@Component
public class InvitationTokenGenerator {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/JwtTokenIssuer.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/JwtTokenIssuer.java
index c776f10e..a098ce9a 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/JwtTokenIssuer.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/JwtTokenIssuer.java
@@ -6,45 +6,18 @@
import java.util.List;
import java.util.UUID;
-/**
- * Domain port for issuing ES256 JWTs and publishing the JWKS endpoint key set.
- * Infrastructure provides a Nimbus JOSE+JWT implementation.
- */
public interface JwtTokenIssuer {
- /**
- * Issues an access token for the given user and role.
- * Claims include: sub, merchant_id, user_id, role, role_id, permissions, mfa_verified, jti.
- */
String issueAccessToken(MerchantUser user, Role role, boolean mfaVerified);
- /**
- * Issues an opaque refresh token bound to the user session.
- */
String issueRefreshToken(UUID userId, UUID sessionId);
- /**
- * Parses and verifies a JWT access token. Returns the extracted claims.
- *
- * @throws IllegalArgumentException if the token is invalid, expired, or tampered
- */
ParsedAccessToken parseAndVerify(String token);
- /**
- * Parses and verifies a JWT refresh token. Returns the extracted claims.
- *
- * @throws IllegalArgumentException if the token is invalid, expired, or not a refresh token
- */
ParsedRefreshToken parseRefreshToken(String token);
- /**
- * Returns the refresh token TTL in seconds (used to set session expiry).
- */
int refreshTokenTtlSeconds();
- /**
- * Returns the public key set in JWK Set JSON format for the JWKS endpoint.
- */
String jwksJson();
record ParsedRefreshToken(
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/LoginAttemptTracker.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/LoginAttemptTracker.java
index d9a4e995..fffa626a 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/LoginAttemptTracker.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/LoginAttemptTracker.java
@@ -1,17 +1,10 @@
package com.stablecoin.payments.merchant.iam.domain.team;
-/**
- * Domain port for brute-force protection.
- * Tracks failed login attempts per email hash and enforces lockouts.
- */
public interface LoginAttemptTracker {
- /** Records a failed attempt. Returns the new total failure count. */
int recordFailure(String emailHash);
- /** Returns true if the account is currently locked out. */
boolean isLockedOut(String emailHash);
- /** Clears the failure counter on successful login. */
void clearFailures(String emailHash);
}
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeam.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeam.java
index f9f177be..577e6696 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeam.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeam.java
@@ -28,11 +28,6 @@
import java.util.List;
import java.util.UUID;
-/**
- * Aggregate root for the Merchant Team bounded context.
- * Manages users, roles, invitations and enforces all team invariants.
- * Zero Spring/JPA dependencies — pure domain logic.
- */
public class MerchantTeam {
private static final Duration INVITATION_TTL = Duration.ofDays(7);
@@ -57,7 +52,6 @@ public static MerchantTeam create(UUID merchantId) {
return new MerchantTeam(merchantId, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
- // ── Queries ─────────────────────────────────────────────
public UUID getMerchantId() {
return merchantId;
@@ -75,11 +69,7 @@ public List getInvitations() {
return List.copyOf(invitations);
}
- // ── Commands ────────────────────────────────────────────
- /**
- * Seeds the 4 built-in roles for a newly activated merchant.
- */
public List seedBuiltInRoles() {
var seeded = new ArrayList();
for (BuiltInRole builtIn : BuiltInRole.values()) {
@@ -100,9 +90,6 @@ public List seedBuiltInRoles() {
return seeded;
}
- /**
- * Creates a custom (non-builtin) role for this merchant.
- */
public Role createCustomRole(String roleName, String description,
List permissions, UUID createdBy) {
var nameExists = roles.stream()
@@ -130,10 +117,6 @@ public Role createCustomRole(String roleName, String description,
return role;
}
- /**
- * Deletes (deactivates) a custom role. Built-in roles cannot be deleted.
- * Roles with active users assigned cannot be deleted.
- */
public Role deleteCustomRole(UUID roleId) {
var roleIdx = findRoleIndex(roleId);
var role = roles.get(roleIdx);
@@ -155,9 +138,6 @@ public Role deleteCustomRole(UUID roleId) {
return deactivated;
}
- /**
- * Updates permissions on a custom role. Built-in roles cannot be modified.
- */
public Role updateCustomRolePermissions(UUID roleId, List newPermissions) {
var roleIdx = findRoleIndex(roleId);
var role = roles.get(roleIdx);
@@ -171,10 +151,6 @@ public Role updateCustomRolePermissions(UUID roleId, List newPermiss
return updated;
}
- /**
- * Creates the first ADMIN user when a merchant is activated.
- * The user starts in INVITED status — they set their password via the invitation link.
- */
public MerchantUser createFirstAdmin(String email, String emailHash, String fullName,
String passwordHash) {
var adminRole = findRoleByName(BuiltInRole.ADMIN.name());
@@ -199,10 +175,6 @@ public MerchantUser createFirstAdmin(String email, String emailHash, String full
return user;
}
- /**
- * Invites a new user to the merchant team.
- * Creates a user in INVITED status and a pending invitation.
- */
public InviteResult inviteUser(String email, String emailHash, String fullName,
UUID roleId, UUID invitedBy, String tokenHash) {
// Invariant: email unique within merchant (among non-deactivated users)
@@ -266,9 +238,6 @@ public InviteResult inviteUser(String email, String emailHash, String fullName,
return new InviteResult(user, invitation);
}
- /**
- * Accepts a pending invitation. Transitions user INVITED → ACTIVE.
- */
public MerchantUser acceptInvitation(UUID invitationId, String fullName, String passwordHash) {
var invIdx = findInvitationIndex(invitationId);
var invitation = invitations.get(invIdx);
@@ -308,9 +277,6 @@ public MerchantUser acceptInvitation(UUID invitationId, String fullName, String
return activated;
}
- /**
- * Changes a user's role. Enforces last-admin invariant.
- */
public MerchantUser changeUserRole(UUID userId, UUID newRoleId, UUID changedBy) {
var userIdx = findUserIndex(userId);
var user = users.get(userIdx);
@@ -347,9 +313,6 @@ public MerchantUser changeUserRole(UUID userId, UUID newRoleId, UUID changedBy)
return changed;
}
- /**
- * Suspends a user. Transitions ACTIVE → SUSPENDED.
- */
public MerchantUser suspendUser(UUID userId, String reason, UUID suspendedBy) {
var userIdx = findUserIndex(userId);
var user = users.get(userIdx);
@@ -380,9 +343,6 @@ public MerchantUser suspendUser(UUID userId, String reason, UUID suspendedBy) {
return suspended;
}
- /**
- * Reactivates a suspended user. Transitions SUSPENDED → ACTIVE.
- */
public MerchantUser reactivateUser(UUID userId) {
var userIdx = findUserIndex(userId);
var user = users.get(userIdx);
@@ -406,9 +366,6 @@ public MerchantUser reactivateUser(UUID userId) {
return reactivated;
}
- /**
- * Deactivates a user (terminal state). Enforces last-admin invariant.
- */
public MerchantUser deactivateUser(UUID userId, String reason, UUID deactivatedBy) {
var userIdx = findUserIndex(userId);
var user = users.get(userIdx);
@@ -439,9 +396,6 @@ public MerchantUser deactivateUser(UUID userId, String reason, UUID deactivatedB
return deactivated;
}
- /**
- * Revokes all sessions for this merchant (e.g., merchant suspended).
- */
public AllSessionsRevokedEvent revokeAllSessions(String reason) {
var event = AllSessionsRevokedEvent.builder()
.schemaVersion(AllSessionsRevokedEvent.SCHEMA_VERSION)
@@ -456,7 +410,6 @@ public AllSessionsRevokedEvent revokeAllSessions(String reason) {
return event;
}
- // ── Domain Events ───────────────────────────────────────
public List domainEvents() {
return List.copyOf(domainEvents);
@@ -466,7 +419,6 @@ public void clearDomainEvents() {
domainEvents.clear();
}
- // ── Internal helpers ────────────────────────────────────
private int findUserIndex(UUID userId) {
for (int i = 0; i < users.size(); i++) {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeamService.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeamService.java
index 15656822..2f0e6765 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeamService.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MerchantTeamService.java
@@ -38,7 +38,6 @@ public class MerchantTeamService {
private final EmailHasher emailHasher;
private final PasswordHasher passwordHasher;
- // ── User management ─────────────────────────────────────────────────────
@Transactional
public InviteResult inviteUser(UUID merchantId, String email, String fullName,
@@ -151,10 +150,6 @@ public List listUsers(UUID merchantId, UserStatus statusFilter) {
.toList();
}
- /**
- * Lists users with their resolved roles in a single transaction.
- * Avoids lazy-load issues caused by calling findRole() outside the service transaction.
- */
@Transactional(readOnly = true)
public List listUsersWithRoles(UUID merchantId, UserStatus statusFilter) {
return listUsers(merchantId, statusFilter).stream()
@@ -166,10 +161,6 @@ public List listUsersWithRoles(UUID merchantId, UserStatus statusF
.toList();
}
- /**
- * Invites a user and returns both the result and the role name in one transaction.
- * Avoids the controller needing a second findRole() call.
- */
@Transactional
public InviteResultWithRole inviteUserWithRole(UUID merchantId, String email, String fullName,
UUID roleId, UUID invitedBy, String merchantName) {
@@ -183,7 +174,6 @@ public record UserWithRole(MerchantUser user, Role role) {}
public record InviteResultWithRole(InviteResult inviteResult, String roleName) {}
- // ── Role management ─────────────────────────────────────────────────────
@Transactional
public Role createRole(UUID merchantId, String roleName, String description,
@@ -241,13 +231,7 @@ public Role findRole(UUID roleId) {
.orElseThrow(() -> RoleNotFoundException.withId(roleId));
}
- // ── Merchant lifecycle (driven by Kafka events from S11) ─────────────────
- /**
- * Seeds the 4 built-in roles and creates the first ADMIN user.
- * Called when {@code merchant.activated} is received from S11.
- * Password hash is empty — the first admin sets their password via invitation link.
- */
@Transactional
public MerchantUser seedRolesAndFirstAdmin(UUID merchantId, String email,
String fullName, String merchantName) {
@@ -284,10 +268,6 @@ public MerchantUser seedRolesAndFirstAdmin(UUID merchantId, String email,
return admin;
}
- /**
- * Deactivates all non-deactivated users for a merchant.
- * Called when {@code merchant.closed} is received from S11.
- */
@Transactional
public void deactivateAllUsers(UUID merchantId) {
var users = userRepository.findByMerchantId(merchantId).stream()
@@ -301,7 +281,6 @@ public void deactivateAllUsers(UUID merchantId) {
log.info("Deactivated {} users for merchantId={}", users.size(), merchantId);
}
- // ── Internal helpers ────────────────────────────────────────────────────
private MerchantTeam loadTeam(UUID merchantId) {
var roles = roleRepository.findByMerchantId(merchantId);
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaChallengeStore.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaChallengeStore.java
index 56527dce..d30bb108 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaChallengeStore.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaChallengeStore.java
@@ -3,17 +3,11 @@
import java.util.Optional;
import java.util.UUID;
-/**
- * Domain port for storing short-lived MFA challenges.
- * A challenge maps a challengeId → (userId, merchantId) with a 5-minute TTL.
- */
public interface MfaChallengeStore {
record Challenge(UUID userId, UUID merchantId, String emailHash) {}
- /** Stores a challenge and returns the generated challengeId. */
String store(UUID userId, UUID merchantId, String emailHash);
- /** Looks up and removes a challenge by its ID. Returns empty if expired or not found. */
Optional consume(String challengeId);
}
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaProvider.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaProvider.java
index 9bbcc1bd..d6e48352 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaProvider.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/MfaProvider.java
@@ -1,26 +1,10 @@
package com.stablecoin.payments.merchant.iam.domain.team;
-/**
- * Domain port for TOTP-based MFA (RFC 6238, Google Authenticator compatible).
- * Infrastructure provides a dev.samstevens.totp implementation.
- */
public interface MfaProvider {
- /**
- * Generates a new TOTP secret for a user.
- * @return Base32-encoded secret
- */
String generateSecret();
- /**
- * Returns a provisioning URI for QR code generation.
- * @param email user email (label in authenticator app)
- * @param secret Base32-encoded secret
- */
String generateProvisioningUri(String email, String secret);
- /**
- * Verifies a 6-digit TOTP code against the secret.
- */
boolean verify(String secret, String totpCode);
}
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PasswordHasher.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PasswordHasher.java
index 60fa3828..e8faa812 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PasswordHasher.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PasswordHasher.java
@@ -1,9 +1,5 @@
package com.stablecoin.payments.merchant.iam.domain.team;
-/**
- * Domain port for password hashing and verification.
- * Infrastructure provides a bcrypt implementation (cost 12).
- */
public interface PasswordHasher {
String hash(String rawPassword);
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PermissionQueryService.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PermissionQueryService.java
index 97a6c0f1..48eb25fd 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PermissionQueryService.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/PermissionQueryService.java
@@ -12,11 +12,6 @@
import java.util.UUID;
-/**
- * Domain service for permission resolution.
- * Checks Redis cache first; falls back to DB on cache miss.
- * Called by S10 API Gateway via {@code GET /v1/auth/permissions/check}.
- */
@Slf4j
@Service
@RequiredArgsConstructor
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/model/core/Permission.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/model/core/Permission.java
index 8917da5b..f0e7d19d 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/model/core/Permission.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/domain/team/model/core/Permission.java
@@ -7,10 +7,6 @@ public record Permission(String namespace, String action) {
public static final String WILDCARD = "*";
- /**
- * Allowed permission namespaces per spec Section 2.
- * Wildcard is permitted for ADMIN roles only.
- */
public static final Set ALLOWED_NAMESPACES = Set.of(
"*", "payments", "transactions", "webhooks", "api_keys",
"team", "roles", "settings", "exports", "compliance"
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/auth/JwtProperties.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/auth/JwtProperties.java
index 36e62939..b99c6248 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/auth/JwtProperties.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/auth/JwtProperties.java
@@ -8,14 +8,9 @@
@Validated
@ConfigurationProperties(prefix = "merchant-iam.jwt")
public record JwtProperties(
- /** Base64-encoded PKCS#8 ES256 private key (PEM without headers). Dev: generated on startup. */
String privateKeyBase64,
- /** Issuer claim (iss) */
@NotBlank String issuer,
- /** Audience claim (aud) */
@NotBlank String audience,
- /** Access token TTL in seconds */
@Positive int accessTokenTtlSeconds,
- /** Refresh token TTL in seconds */
@Positive int refreshTokenTtlSeconds
) {}
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisLoginAttemptTracker.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisLoginAttemptTracker.java
index 60a4ba9c..e06fcf6d 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisLoginAttemptTracker.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisLoginAttemptTracker.java
@@ -9,13 +9,6 @@
import java.time.Duration;
import java.util.Objects;
-/**
- * Redis-backed brute-force protection.
- * Key: {@code login:fail:{emailHash}}
- * Value: failure count as string
- * TTL: 15 minutes, reset on each failure (sliding window)
- * Threshold: 5 failures triggers lockout
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisMfaChallengeStore.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisMfaChallengeStore.java
index 6705b9f8..ab31858d 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisMfaChallengeStore.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/cache/RedisMfaChallengeStore.java
@@ -10,13 +10,6 @@
import java.util.Optional;
import java.util.UUID;
-/**
- * Redis-backed MFA challenge store.
- * Key: {@code mfa:challenge:{challengeId}}
- * Value: {@code userId:merchantId:emailHash}
- * TTL: 5 minutes (challenges expire)
- * The key is consumed (deleted) on first successful read — one-time use.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantActivatedEvent.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantActivatedEvent.java
index dede51ae..19a2c255 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantActivatedEvent.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantActivatedEvent.java
@@ -3,11 +3,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Inbound event produced by S11 Merchant Onboarding on topic {@code merchant.activated}.
- * S13 uses this to seed built-in roles and create the first ADMIN user.
- * Accepts both camelCase (S11 native) and snake_case field names.
- */
public record MerchantActivatedEvent(
String eventId,
String eventType,
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantClosedEvent.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantClosedEvent.java
index 6d20b19f..e8eb5395 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantClosedEvent.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantClosedEvent.java
@@ -5,10 +5,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Inbound event produced by S11 on topic {@code merchant.closed}.
- * S13 deactivates all users and revokes all sessions.
- */
public record MerchantClosedEvent(
@JsonProperty("schema_version") String schemaVersion,
@JsonProperty("event_id") String eventId,
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantEventListener.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantEventListener.java
index a5cbdc46..303eaa6e 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantEventListener.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantEventListener.java
@@ -18,10 +18,6 @@ public class MerchantEventListener {
private final UserSessionRepository sessionRepository;
private final ObjectMapper objectMapper;
- /**
- * Consumes {@code merchant.activated} from S11.
- * Seeds the 4 built-in roles and creates the first ADMIN user for the merchant.
- */
@KafkaListener(topics = "merchant.activated", groupId = "merchant-iam-onboard")
public void onMerchantActivated(@Payload String payload) {
try {
@@ -41,10 +37,6 @@ public void onMerchantActivated(@Payload String payload) {
}
}
- /**
- * Consumes {@code merchant.suspended} from S11.
- * Revokes all active user sessions for the merchant.
- */
@KafkaListener(topics = "merchant.suspended", groupId = "merchant-iam-suspend")
public void onMerchantSuspended(@Payload String payload) {
try {
@@ -60,10 +52,6 @@ public void onMerchantSuspended(@Payload String payload) {
}
}
- /**
- * Consumes {@code merchant.closed} from S11.
- * Deactivates all users and revokes all sessions for the merchant.
- */
@KafkaListener(topics = "merchant.closed", groupId = "merchant-iam-close")
public void onMerchantClosed(@Payload String payload) {
try {
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantSuspendedEvent.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantSuspendedEvent.java
index 94827e8c..ae086639 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantSuspendedEvent.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/messaging/MerchantSuspendedEvent.java
@@ -5,10 +5,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Inbound event produced by S11 on topic {@code merchant.suspended}.
- * S13 revokes all user sessions for the merchant.
- */
public record MerchantSuspendedEvent(
@JsonProperty("schema_version") String schemaVersion,
@JsonProperty("event_id") String eventId,
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/MerchantUserEntityMapper.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/MerchantUserEntityMapper.java
index f20e64af..119b6974 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/MerchantUserEntityMapper.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/MerchantUserEntityMapper.java
@@ -12,10 +12,6 @@
@Mapper
public interface MerchantUserEntityMapper {
- /**
- * Domain → Entity.
- * email is encrypted/stored as bytea; role FK requires a pre-loaded RoleEntity.
- */
@Mapping(target = "email", expression = "java(toBytes(user.email()))")
@Mapping(target = "role", expression = "java(roleRef(user.roleId()))")
@Mapping(target = "status", expression = "java(user.status().name())")
@@ -23,10 +19,6 @@ public interface MerchantUserEntityMapper {
@Mapping(target = "version", ignore = true)
MerchantUserEntity toEntity(MerchantUser user);
- /**
- * Entity → Domain.
- * roleId is extracted from the role FK; email decoded from bytea.
- */
@Mapping(target = "email", expression = "java(fromBytes(entity.getEmail()))")
@Mapping(target = "roleId", expression = "java(entity.getRole().getRoleId())")
@Mapping(target = "status", expression = "java(com.stablecoin.payments.merchant.iam.domain.team.model.core.UserStatus.valueOf(entity.getStatus()))")
diff --git a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/RoleEntityMapper.java b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/RoleEntityMapper.java
index a9921b16..83304c46 100644
--- a/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/RoleEntityMapper.java
+++ b/merchant-iam/merchant-iam/src/main/java/com/stablecoin/payments/merchant/iam/infrastructure/persistence/mapper/RoleEntityMapper.java
@@ -15,11 +15,6 @@
@Mapper
public interface RoleEntityMapper {
- /**
- * Maps a Role domain object to a RoleEntity.
- * The permissions collection is deliberately ignored here;
- * the repository adapter populates permissions separately after saving.
- */
@Mapping(target = "permissions", ignore = true)
RoleEntity toEntityWithoutPermissions(Role role);
diff --git a/merchant-iam/merchant-iam/src/test/java/com/stablecoin/payments/merchant/iam/application/security/JwtAuthenticationFilterTest.java b/merchant-iam/merchant-iam/src/test/java/com/stablecoin/payments/merchant/iam/application/security/JwtAuthenticationFilterTest.java
index 1c347acb..ce4dc0d4 100644
--- a/merchant-iam/merchant-iam/src/test/java/com/stablecoin/payments/merchant/iam/application/security/JwtAuthenticationFilterTest.java
+++ b/merchant-iam/merchant-iam/src/test/java/com/stablecoin/payments/merchant/iam/application/security/JwtAuthenticationFilterTest.java
@@ -20,10 +20,9 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class JwtAuthenticationFilterTest {
@@ -58,7 +57,7 @@ class WhenNoBearerToken {
void shouldPassThroughWithoutAuthHeader() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
}
@@ -68,8 +67,8 @@ void shouldPassThroughWithNonBearerAuthHeader() throws ServletException, IOExcep
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(jwtTokenIssuer, never()).parseAndVerify(anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(jwtTokenIssuer).shouldHaveNoInteractions();
}
}
@@ -86,13 +85,13 @@ void shouldAuthenticateWithValidJwt() throws ServletException, IOException {
var token = "valid.jwt.token";
request.addHeader("Authorization", "Bearer " + token);
- when(jwtTokenIssuer.parseAndVerify(token)).thenReturn(
+ given(jwtTokenIssuer.parseAndVerify(token)).willReturn(
new ParsedAccessToken(jti, userId, merchantId, roleId,
"ADMIN", List.of("payments:read", "team:manage"), true, 9999999999L));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
+ then(filterChain).should().doFilter(request, response);
var auth = SecurityContextHolder.getContext().getAuthentication();
assertThat(auth).isInstanceOf(UserAuthentication.class);
var userAuth = (UserAuthentication) auth;
@@ -111,12 +110,12 @@ class WhenInvalidToken {
@Test
void shouldRejectExpiredToken() throws ServletException, IOException {
request.addHeader("Authorization", "Bearer expired.jwt.token");
- when(jwtTokenIssuer.parseAndVerify("expired.jwt.token"))
- .thenThrow(new IllegalArgumentException("JWT has expired"));
+ given(jwtTokenIssuer.parseAndVerify("expired.jwt.token"))
+ .willThrow(new IllegalArgumentException("JWT has expired"));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getContentAsString()).contains("IAM-4010");
}
@@ -124,12 +123,12 @@ void shouldRejectExpiredToken() throws ServletException, IOException {
@Test
void shouldRejectMalformedToken() throws ServletException, IOException {
request.addHeader("Authorization", "Bearer not-a-jwt");
- when(jwtTokenIssuer.parseAndVerify("not-a-jwt"))
- .thenThrow(new IllegalArgumentException("Invalid JWT"));
+ given(jwtTokenIssuer.parseAndVerify("not-a-jwt"))
+ .willThrow(new IllegalArgumentException("Invalid JWT"));
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain, never()).doFilter(request, response);
+ then(filterChain).should(never()).doFilter(request, response);
assertThat(response.getStatus()).isEqualTo(401);
}
}
@@ -143,7 +142,7 @@ void shouldSkipWhenAlreadyAuthenticated() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
- verify(filterChain).doFilter(request, response);
- verify(jwtTokenIssuer, never()).parseAndVerify(anyString());
+ then(filterChain).should().doFilter(request, response);
+ then(jwtTokenIssuer).shouldHaveNoInteractions();
}
}
diff --git a/merchant-iam/merchant-iam/src/testFixtures/java/com/stablecoin/payments/merchant/iam/fixtures/IamEntityFixtures.java b/merchant-iam/merchant-iam/src/testFixtures/java/com/stablecoin/payments/merchant/iam/fixtures/IamEntityFixtures.java
index 4ab1d712..1e2059a2 100644
--- a/merchant-iam/merchant-iam/src/testFixtures/java/com/stablecoin/payments/merchant/iam/fixtures/IamEntityFixtures.java
+++ b/merchant-iam/merchant-iam/src/testFixtures/java/com/stablecoin/payments/merchant/iam/fixtures/IamEntityFixtures.java
@@ -23,7 +23,6 @@ public static UUID defaultMerchantId() {
return MERCHANT_ID;
}
- // ─── Roles ───
public static RoleEntity anAdminRole() {
return RoleEntity.builder()
@@ -64,7 +63,6 @@ public static RoleEntity anInactiveRole() {
.build();
}
- // ─── Role Permissions ───
public static RolePermissionEntity aRolePermission(RoleEntity role, String permission) {
return RolePermissionEntity.builder()
@@ -75,7 +73,6 @@ public static RolePermissionEntity aRolePermission(RoleEntity role, String permi
.build();
}
- // ─── Merchant Users ───
public static MerchantUserEntity anActiveUser(RoleEntity role) {
return MerchantUserEntity.builder()
@@ -112,7 +109,6 @@ public static MerchantUserEntity anInvitedUser(RoleEntity role) {
.build();
}
- // ─── Invitations ───
public static InvitationEntity aPendingInvitation(RoleEntity role) {
return InvitationEntity.builder()
@@ -144,7 +140,6 @@ public static InvitationEntity anExpiredInvitation(RoleEntity role) {
.build();
}
- // ─── User Sessions ───
public static UserSessionEntity anActiveSession(MerchantUserEntity user) {
return UserSessionEntity.builder()
@@ -160,7 +155,6 @@ public static UserSessionEntity anActiveSession(MerchantUserEntity user) {
.build();
}
- // ─── Permission Audit Log ───
public static PermissionAuditLogEntity anAuditLogEntry() {
return PermissionAuditLogEntity.builder()
diff --git a/merchant-onboarding/merchant-onboarding/src/integration-test/java/com/stablecoin/payments/merchant/onboarding/config/TestTemporalConfig.java b/merchant-onboarding/merchant-onboarding/src/integration-test/java/com/stablecoin/payments/merchant/onboarding/config/TestTemporalConfig.java
index 2f497ae9..0674fcb3 100644
--- a/merchant-onboarding/merchant-onboarding/src/integration-test/java/com/stablecoin/payments/merchant/onboarding/config/TestTemporalConfig.java
+++ b/merchant-onboarding/merchant-onboarding/src/integration-test/java/com/stablecoin/payments/merchant/onboarding/config/TestTemporalConfig.java
@@ -7,8 +7,8 @@
import org.springframework.context.annotation.Bean;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
/**
* Provides a mock WorkflowClient for integration tests. Temporal auto-configuration is excluded, so we need to supply
@@ -21,8 +21,8 @@ public class TestTemporalConfig {
public WorkflowClient workflowClient() {
var client = mock(WorkflowClient.class);
var workflowStub = mock(MerchantOnboardingWorkflow.class);
- when(client.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(workflowStub);
- when(client.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(workflowStub);
+ given(client.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).willReturn(workflowStub);
+ given(client.newWorkflowStub(any(Class.class), any(String.class))).willReturn(workflowStub);
return client;
}
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/FallbackAdaptersConfig.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/FallbackAdaptersConfig.java
index 1293c3b6..9574fc42 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/FallbackAdaptersConfig.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/FallbackAdaptersConfig.java
@@ -13,18 +13,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-/**
- * Registers fallback (mock/in-memory) adapters for outbound ports when no real implementation is available.
- *
- * Activated when {@code app.fallback-adapters.enabled=true}. In production, real adapters are activated
- * via their own {@code @ConditionalOnProperty} flags and this config is not loaded.
- *
- * When does each adapter win?
- *
- * Local dev / integration tests — {@code app.fallback-adapters.enabled=true} → fallbacks registered
- * Production — property absent or {@code false} → real adapters used instead
- *
- */
@Configuration
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "true")
public class FallbackAdaptersConfig {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/IdempotencyKeyFilter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/IdempotencyKeyFilter.java
index 4420832e..b701c0f0 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/IdempotencyKeyFilter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/config/IdempotencyKeyFilter.java
@@ -31,14 +31,6 @@
import java.util.HexFormat;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) -- excluding webhook and actuator endpoints.
- * Uses INSERT-first reservation pattern to prevent TOCTOU races:
- * 1. Try INSERT with status_code=0 (reservation)
- * 2. If INSERT succeeds, proceed with request and UPDATE with real response
- * 3. If INSERT fails (duplicate), re-read stored record for replay or conflict
- */
@Slf4j
@Component
@Order(2)
@@ -241,10 +233,6 @@ private String computeSha256(byte[] body) {
record IdempotencyRecord(String idempotencyKey, String requestHash, String responseBody, int statusCode) {}
- /**
- * Request wrapper that replays a cached body so downstream filters and
- * controllers can read the request body after it has already been consumed.
- */
private static class CachedBodyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/controller/KybWebhookController.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/controller/KybWebhookController.java
index 998cd34b..1b5f9b51 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/controller/KybWebhookController.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/application/controller/KybWebhookController.java
@@ -19,12 +19,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Receives KYB provider webhook callbacks (Onfido check.completed events). Validates HMAC signature, delegates to
- * KybProvider.handleWebhook(), then signals the Temporal onboarding workflow with the result.
- *
- * No @PreAuthorize — webhook endpoints are authenticated via HMAC signature.
- */
@Slf4j
@RestController
@RequestMapping("/api/internal/webhooks")
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CompanyRegistryProvider.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CompanyRegistryProvider.java
index 0b4213df..fe8511d1 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CompanyRegistryProvider.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CompanyRegistryProvider.java
@@ -2,16 +2,8 @@
import java.util.Optional;
-/**
- * Outbound port for company registry lookups. Used to validate merchant registration number against official registries
- * (Companies House for GB, SEC EDGAR for US).
- */
public interface CompanyRegistryProvider {
- /**
- * Looks up a company by registration number and country. Returns company profile data if found, empty if not found or
- * country not supported.
- */
Optional lookup(String registrationNumber, String country);
record CompanyProfile(String companyName, String registrationNumber, String country, String companyStatus,
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CorridorEntitlementService.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CorridorEntitlementService.java
index 191e4186..d3afa8a0 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CorridorEntitlementService.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/CorridorEntitlementService.java
@@ -3,9 +3,6 @@
import com.stablecoin.payments.merchant.onboarding.domain.merchant.model.core.MerchantStatus;
import org.springframework.stereotype.Service;
-/**
- * Domain service: validates corridor approval against merchant status and regulatory rules.
- */
@Service
public class CorridorEntitlementService {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/DocumentStore.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/DocumentStore.java
index 47456b43..e4a4703c 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/DocumentStore.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/DocumentStore.java
@@ -2,10 +2,6 @@
import java.util.UUID;
-/**
- * Outbound port for document storage.
- * Infrastructure adapters generate pre-signed upload URLs; bytes never pass through the service.
- */
public interface DocumentStore {
String generateUploadUrl(UUID merchantId, String documentType, String fileName);
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/Merchant.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/Merchant.java
index 219002f3..f79b88b9 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/Merchant.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/Merchant.java
@@ -28,10 +28,6 @@
import static com.stablecoin.payments.merchant.onboarding.domain.merchant.model.core.MerchantStatus.PENDING_APPROVAL;
import static com.stablecoin.payments.merchant.onboarding.domain.merchant.model.core.MerchantStatus.SUSPENDED;
-/**
- * Aggregate root for the Merchant bounded context.
- * All state changes go through domain methods that enforce the state machine.
- */
@Getter
@Builder(toBuilder = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/MerchantActivationPolicy.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/MerchantActivationPolicy.java
index fe97fd6c..3d5429b1 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/MerchantActivationPolicy.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/MerchantActivationPolicy.java
@@ -6,9 +6,6 @@
import com.stablecoin.payments.merchant.onboarding.domain.merchant.model.core.RiskTier;
import org.springframework.stereotype.Service;
-/**
- * Domain service: enforces all invariants before a merchant can be activated.
- */
@Service
public class MerchantActivationPolicy {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/OnboardingWorkflowPort.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/OnboardingWorkflowPort.java
index 984ccc06..eec8a44e 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/OnboardingWorkflowPort.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/OnboardingWorkflowPort.java
@@ -2,10 +2,6 @@
import java.util.UUID;
-/**
- * Port for starting the onboarding workflow. The infrastructure layer provides the Temporal adapter; tests and local
- * profiles use a no-op fallback.
- */
public interface OnboardingWorkflowPort {
void startOnboarding(UUID merchantId);
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/RiskTierCalculator.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/RiskTierCalculator.java
index 65f2a953..972fc589 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/RiskTierCalculator.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/domain/merchant/RiskTierCalculator.java
@@ -5,11 +5,6 @@
import java.util.Map;
-/**
- * Domain service: maps KYB provider risk signals to internal RiskTier.
- * Score > 50 = HIGH risk (disqualifies activation).
- * Score 25–50 = MEDIUM, below 25 = LOW.
- */
@Service
public class RiskTierCalculator {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapter.java
index e9d0a593..d1301191 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapter.java
@@ -23,13 +23,6 @@
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
-/**
- * Companies House API adapter — free, real UK company data.
- *
- * API docs: Companies House Developer Hub
- *
- * Rate limit: 600 requests per 5-minute window. Authentication: HTTP Basic with API key as username, empty password.
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.company-registry.provider", havingValue = "companies-house")
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAddress.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAddress.java
index a5502053..a9229a21 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAddress.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAddress.java
@@ -3,9 +3,6 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
-/**
- * ACL DTO for the nested {@code registered_office_address} object in a Companies House company profile response.
- */
@JsonIgnoreProperties(ignoreUnknown = true)
record CompaniesHouseAddress(
@JsonProperty("address_line_1") String addressLine1,
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseCompanyResponse.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseCompanyResponse.java
index 5da25ef1..7e982548 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseCompanyResponse.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseCompanyResponse.java
@@ -5,10 +5,6 @@
import java.util.List;
-/**
- * ACL DTO for the Companies House {@code GET /company/{companyNumber}} response. Only maps fields needed by the domain
- * — remaining fields are ignored.
- */
@JsonIgnoreProperties(ignoreUnknown = true)
record CompaniesHouseCompanyResponse(
@JsonProperty("company_name") String companyName,
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/MockCompanyRegistryAdapter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/MockCompanyRegistryAdapter.java
index f442dce5..ea0788c7 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/MockCompanyRegistryAdapter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/MockCompanyRegistryAdapter.java
@@ -5,12 +5,6 @@
import java.util.Optional;
-/**
- * In-memory company registry for local development and fallback. Registered as a bean only when no other
- * {@link CompanyRegistryProvider} is available.
- *
- * @see FallbackAdaptersConfig
- */
@Slf4j
public class MockCompanyRegistryAdapter implements CompanyRegistryProvider {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java
index b89e0bcb..59936226 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java
@@ -25,17 +25,6 @@
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
-/**
- * Onfido sandbox/production KYB adapter.
- *
- * Uses Onfido REST API v3.6 to create applicants and submit checks. In sandbox mode, results are deterministic:
- *
- * {@code last_name = "Consider"} → consider (maps to MANUAL_REVIEW)
- * Any other last_name → clear (maps to PASSED)
- *
- *
- * Webhook callback completes the async flow via {@code handleWebhook()}.
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.kyb.provider", havingValue = "onfido")
@@ -66,7 +55,6 @@ public OnfidoKybAdapter(OnfidoProperties properties,
public KybVerification submit(UUID merchantId, String legalName, String registrationNumber, String country) {
log.info("[ONFIDO] Creating applicant for merchant={} legalName={}", merchantId, legalName);
- // Step 1: Create an applicant
var nameParts = splitName(legalName);
var applicantBody = Map.of("first_name", nameParts[0], "last_name", nameParts[1]);
@@ -76,7 +64,6 @@ public KybVerification submit(UUID merchantId, String legalName, String registra
var applicantId = (String) applicantResponse.get("id");
log.info("[ONFIDO] Applicant created applicantId={}", applicantId);
- // Step 2: Create a check
var checkBody = Map.of("applicant_id", applicantId, "report_names", List.of("document", "watchlist_standard"));
@SuppressWarnings("unchecked")
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoWebhookValidator.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoWebhookValidator.java
index 1dd7dd87..6a7b717b 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoWebhookValidator.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoWebhookValidator.java
@@ -15,14 +15,6 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-/**
- * Validates Onfido webhook HMAC-SHA256 signatures.
- *
- * Onfido sends the signature in the {@code X-SHA2-Signature} header. The signature is computed as
- * HMAC-SHA256(webhook_secret, raw_body).
- *
- * In sandbox mode with a placeholder secret, validation is skipped.
- */
@Slf4j
@Component
public class OnfidoWebhookValidator {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/KafkaEventActivities.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/KafkaEventActivities.java
index 48782dde..9015fc70 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/KafkaEventActivities.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/KafkaEventActivities.java
@@ -2,10 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity for publishing domain events to Kafka. Replaces the outbox pattern for workflow-managed events — Temporal
- * guarantees at-least-once execution.
- */
@ActivityInterface
public interface KafkaEventActivities {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivities.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivities.java
index d38a25f0..c38a502a 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivities.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivities.java
@@ -7,21 +7,9 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Activities for the merchant onboarding workflow. Each method is a single unit of work with its own retry policy.
- *
- * Note: {@code @ActivityMethod} is intentionally omitted — {@code @ActivityInterface} is sufficient and avoids
- * compatibility issues with Mockito proxies in tests.
- */
@ActivityInterface
public interface MerchantOnboardingActivities {
- /**
- * Validates the merchant's registration against an official company registry (Companies House for GB, SEC EDGAR for
- * US).
- *
- * @return the company status (e.g. "active", "dissolved") or "NOT_FOUND" / "UNSUPPORTED_COUNTRY"
- */
String verifyCompanyRegistry(UUID merchantId);
String startKyb(UUID merchantId);
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivitiesImpl.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivitiesImpl.java
index 32a83e6b..10c07030 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivitiesImpl.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/activity/MerchantOnboardingActivitiesImpl.java
@@ -112,18 +112,15 @@ public void rejectMerchant(UUID merchantId, String reason) {
@Override
public void notifyOpsTeam(UUID merchantId) {
log.info("[ACTIVITY] Notifying ops team for manual review merchantId={}", merchantId);
- // TODO: integrate with S9 Notifications service
}
@Override
public void sendDocumentReminder(UUID merchantId, List missingDocumentTypes) {
log.info("[ACTIVITY] Sending document reminder merchantId={} missing={}", merchantId, missingDocumentTypes);
- // TODO: integrate with S9 Notifications service
}
@Override
public void escalateReview(UUID merchantId) {
log.info("[ACTIVITY] Escalating manual review merchantId={}", merchantId);
- // TODO: integrate with S9 Notifications service
}
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/MockOnboardingWorkflowAdapter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/MockOnboardingWorkflowAdapter.java
index 4546ab8b..04091bab 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/MockOnboardingWorkflowAdapter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/MockOnboardingWorkflowAdapter.java
@@ -5,10 +5,6 @@
import java.util.UUID;
-/**
- * No-op workflow adapter used when Temporal is unavailable (local, test, integration-test profiles). Registered via
- * {@code FallbackAdaptersConfig} when {@code app.fallback-adapters.enabled=true}.
- */
@Slf4j
public class MockOnboardingWorkflowAdapter implements OnboardingWorkflowPort {
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/TemporalOnboardingWorkflowAdapter.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/TemporalOnboardingWorkflowAdapter.java
index 7bba204a..9be048d5 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/TemporalOnboardingWorkflowAdapter.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/adapter/TemporalOnboardingWorkflowAdapter.java
@@ -13,11 +13,6 @@
import java.time.Duration;
import java.util.UUID;
-/**
- * Temporal-backed adapter that starts the merchant onboarding workflow asynchronously.
- *
- * Workflow ID convention: {@code onboarding-}, task queue: {@code onboarding-workflow}.
- */
@Slf4j
@Component
@ConditionalOnProperty(name = "app.fallback-adapters.enabled", havingValue = "false", matchIfMissing = true)
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/config/TemporalWorkerConfig.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/config/TemporalWorkerConfig.java
index 40337277..a74a743b 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/config/TemporalWorkerConfig.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/config/TemporalWorkerConfig.java
@@ -10,10 +10,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-/**
- * Configures the Temporal worker that processes onboarding workflows and activities. Only activated when
- * {@code spring.temporal.namespace} is set — skipped in tests where Temporal auto-configuration is excluded.
- */
@Configuration
@RequiredArgsConstructor
@ConditionalOnProperty(name = "spring.temporal.workers.enabled", havingValue = "true", matchIfMissing = true)
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/DocumentUploadedSignal.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/DocumentUploadedSignal.java
index b358c0dc..defded15 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/DocumentUploadedSignal.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/DocumentUploadedSignal.java
@@ -2,9 +2,5 @@
import java.io.Serializable;
-/**
- * Signal payload sent when a merchant uploads a required document. Received by
- * {@code MerchantOnboardingWorkflow.documentUploaded()}.
- */
public record DocumentUploadedSignal(String documentType, String fileName, String s3Key) implements Serializable {
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/KybResultSignal.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/KybResultSignal.java
index eaad2c52..55cff7a6 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/KybResultSignal.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/KybResultSignal.java
@@ -5,10 +5,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Signal payload sent when a KYB provider returns a result (via webhook or polling). Received by
- * {@code MerchantOnboardingWorkflow.kybResultReceived()}.
- */
public record KybResultSignal(UUID kybId, String provider, String providerRef, String status,
Map riskSignals, String reviewNotes, Instant completedAt) implements Serializable {
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/ReviewDecisionSignal.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/ReviewDecisionSignal.java
index ccfdaba5..4028ee94 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/ReviewDecisionSignal.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/signal/ReviewDecisionSignal.java
@@ -3,9 +3,5 @@
import java.io.Serializable;
import java.util.UUID;
-/**
- * Signal payload sent when an ops reviewer makes a manual KYB decision. Received by
- * {@code MerchantOnboardingWorkflow.reviewDecision()}.
- */
public record ReviewDecisionSignal(String decision, String reason, UUID reviewedBy) implements Serializable {
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflow.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflow.java
index 5e0eb61f..13f57c30 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflow.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflow.java
@@ -10,53 +10,21 @@
import java.util.UUID;
-/**
- * Temporal workflow that orchestrates the merchant onboarding lifecycle:
- *
- * Submit KYB check to provider (Onfido)
- * Wait for KYB result (via webhook → signal)
- * If manual review needed — wait for ops decision (signal)
- * Calculate risk tier
- * Transition merchant to PENDING_APPROVAL
- * Publish domain events to Kafka
- *
- *
- * Workflow ID convention: {@code onboarding-}
- */
@WorkflowInterface
public interface MerchantOnboardingWorkflow {
- /**
- * Main workflow method — runs the full onboarding flow.
- *
- * @param merchantId
- * the merchant to onboard (must already be in APPLIED state)
- * @return the onboarding result (ACTIVE, REJECTED, or TIMED_OUT)
- */
@WorkflowMethod
OnboardingResult runOnboarding(UUID merchantId);
- /**
- * Signal: KYB provider returned a result (via Onfido webhook relay).
- */
@SignalMethod
void kybResultReceived(KybResultSignal signal);
- /**
- * Signal: merchant uploaded a required document.
- */
@SignalMethod
void documentUploaded(DocumentUploadedSignal signal);
- /**
- * Signal: ops reviewer made a manual review decision (APPROVED or REJECTED).
- */
@SignalMethod
void reviewDecision(ReviewDecisionSignal signal);
- /**
- * Query: current onboarding status for observability.
- */
@QueryMethod
String getOnboardingStatus();
}
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflowImpl.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflowImpl.java
index 1c3e1804..9490a804 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflowImpl.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/MerchantOnboardingWorkflowImpl.java
@@ -15,21 +15,6 @@
import java.util.Map;
import java.util.UUID;
-/**
- * Temporal workflow orchestrating the merchant KYB onboarding flow.
- *
- * Flow:
- *
- * Verify company against official registry (Companies House / SEC EDGAR)
- * Submit KYB check to provider (activity)
- * Wait for KYB result signal (7-day timeout)
- * If MANUAL_REVIEW — notify ops, wait for review decision (5-day timeout + escalation)
- * If PASSED — calculate risk tier, mark merchant as KYB passed
- * Publish domain events to Kafka
- *
- *
- * Workflow ID convention: {@code onboarding-}
- */
public class MerchantOnboardingWorkflowImpl implements MerchantOnboardingWorkflow {
private static final Duration KYB_TIMEOUT = Duration.ofDays(7);
@@ -58,7 +43,6 @@ public class MerchantOnboardingWorkflowImpl implements MerchantOnboardingWorkflo
@Override
public OnboardingResult runOnboarding(UUID merchantId) {
- // Step 1: Verify company against official registry
currentStatus = "VERIFYING_COMPANY";
var companyStatus = onboardingActivities.verifyCompanyRegistry(merchantId);
@@ -76,12 +60,10 @@ public OnboardingResult runOnboarding(UUID merchantId) {
return OnboardingResult.rejected(merchantId, "Company registry status is not active: " + companyStatus);
}
- // Step 2: Submit KYB check
currentStatus = "KYB_SUBMITTING";
var providerRef = onboardingActivities.startKyb(merchantId);
currentStatus = "AWAITING_KYB_RESULT";
- // Step 3: Wait for KYB result signal (from webhook relay)
boolean kybReceived = Workflow.await(KYB_TIMEOUT, () -> kybResult != null);
if (!kybReceived) {
@@ -91,7 +73,6 @@ public OnboardingResult runOnboarding(UUID merchantId) {
return OnboardingResult.timedOut(merchantId, "KYB verification timed out after 7 days");
}
- // Step 4: Process KYB result
var kybStatus = kybResult.status();
if ("FAILED".equals(kybStatus)) {
@@ -132,14 +113,12 @@ public OnboardingResult runOnboarding(UUID merchantId) {
// APPROVED — fall through to risk tier calculation
}
- // Step 5: Calculate risk tier and mark KYB passed
var riskSignals = kybResult.riskSignals() != null ? kybResult.riskSignals() : Map.of();
var riskTier = onboardingActivities.calculateRiskTier(riskSignals);
currentStatus = "KYB_PASSED";
onboardingActivities.markKybPassed(merchantId, riskTier);
- // Step 6: Publish events
publishKybPassedEvent(merchantId, kybResult, riskTier);
currentStatus = "PENDING_APPROVAL";
diff --git a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/OnboardingResult.java b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/OnboardingResult.java
index baeff900..06233c19 100644
--- a/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/OnboardingResult.java
+++ b/merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/temporal/workflow/OnboardingResult.java
@@ -3,9 +3,6 @@
import java.io.Serializable;
import java.util.UUID;
-/**
- * Result returned by {@link MerchantOnboardingWorkflow#runOnboarding(UUID)}.
- */
public record OnboardingResult(String status, UUID merchantId, String riskTier,
String failureReason) implements Serializable {
diff --git a/payment-orchestrator/payment-orchestrator-client/src/main/java/com/stablecoin/payments/orchestrator/client/PaymentOrchestratorClient.java b/payment-orchestrator/payment-orchestrator-client/src/main/java/com/stablecoin/payments/orchestrator/client/PaymentOrchestratorClient.java
index e0c66f21..d8493269 100644
--- a/payment-orchestrator/payment-orchestrator-client/src/main/java/com/stablecoin/payments/orchestrator/client/PaymentOrchestratorClient.java
+++ b/payment-orchestrator/payment-orchestrator-client/src/main/java/com/stablecoin/payments/orchestrator/client/PaymentOrchestratorClient.java
@@ -1,6 +1 @@
-/**
- * Feign client for inter-service calls to the Payment Orchestrator service.
- *
- * The Feign client interface will be defined here as API endpoints are implemented.
- */
package com.stablecoin.payments.orchestrator.client;
diff --git a/payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java b/payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java
index 5a11f0d8..07f4ad3d 100644
--- a/payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java
+++ b/payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java
@@ -57,8 +57,8 @@
import java.util.List;
import java.util.UUID;
-import static com.stablecoin.payments.orchestrator.application.config.TemporalConfig.TASK_QUEUE;
import static com.stablecoin.payments.orchestrator.application.filter.IdempotencyKeyFilter.IDEMPOTENCY_KEY_HEADER;
+import static com.stablecoin.payments.orchestrator.domain.service.PaymentCommandHandler.TASK_QUEUE;
import static com.stablecoin.payments.orchestrator.domain.workflow.dto.PaymentResult.PaymentResultStatus.COMPLETED;
import static com.stablecoin.payments.orchestrator.domain.workflow.dto.PaymentResult.PaymentResultStatus.FAILED;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/security/SecurityConfig.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/SecurityConfig.java
similarity index 96%
rename from compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/security/SecurityConfig.java
rename to payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/SecurityConfig.java
index 3f24ea96..18106ceb 100644
--- a/compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/application/security/SecurityConfig.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/SecurityConfig.java
@@ -1,4 +1,4 @@
-package com.stablecoin.payments.compliance.application.security;
+package com.stablecoin.payments.orchestrator.application.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java
index cde73545..77a28c00 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java
@@ -16,8 +16,6 @@
@ConditionalOnProperty(name = "app.temporal.client.enabled", havingValue = "true", matchIfMissing = true)
public class TemporalConfig {
- public static final String TASK_QUEUE = "payment-orchestrator-queue";
-
@Bean
public WorkflowServiceStubs workflowServiceStubs(
@Value("${temporal.server.address:localhost:7233}") String address) {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalWorkerConfig.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalWorkerConfig.java
index 430bd0b4..06fb2266 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalWorkerConfig.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalWorkerConfig.java
@@ -15,13 +15,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import static com.stablecoin.payments.orchestrator.application.config.TemporalConfig.TASK_QUEUE;
+import static com.stablecoin.payments.orchestrator.domain.service.PaymentCommandHandler.TASK_QUEUE;
-/**
- * Registers Temporal workers with workflow types and activity implementations.
- *
- * Disabled during integration/unit tests via {@code app.temporal.worker.enabled=false}.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.temporal.worker.enabled", havingValue = "true", matchIfMissing = true)
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/CancelPaymentRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/CancelPaymentRequest.java
index 2ded362b..25004a52 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/CancelPaymentRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/CancelPaymentRequest.java
@@ -2,9 +2,6 @@
import jakarta.validation.constraints.NotBlank;
-/**
- * Request DTO for cancelling a payment.
- */
public record CancelPaymentRequest(
@NotBlank(message = "reason is required")
String reason
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/InitiatePaymentRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/InitiatePaymentRequest.java
index 4d66f923..65e1d7ed 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/InitiatePaymentRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/InitiatePaymentRequest.java
@@ -7,9 +7,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for initiating a cross-border payment.
- */
public record InitiatePaymentRequest(
@NotNull(message = "senderId is required")
UUID senderId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentController.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentController.java
index 3dcbcb53..2de46c71 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentController.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentController.java
@@ -17,11 +17,6 @@
import java.util.UUID;
-/**
- * REST controller for payment lifecycle management.
- *
- * Thin HTTP handler that delegates all business logic to {@link PaymentCommandHandler}.
- */
@Slf4j
@RestController
@RequestMapping("/v1/payments")
@@ -30,12 +25,6 @@ public class PaymentController {
private final PaymentCommandHandler commandHandler;
- /**
- * Initiates a new cross-border payment.
- *
- * Idempotent: returns 200 with existing payment if the same Idempotency-Key is reused.
- * Returns 201 on first creation.
- */
@PostMapping
public ResponseEntity initiatePayment(
@NotBlank(message = "Idempotency-Key header is required")
@@ -61,19 +50,12 @@ public ResponseEntity initiatePayment(
return ResponseEntity.status(status).body(response);
}
- /**
- * Retrieves a payment by its ID.
- */
@GetMapping("/{paymentId}")
public PaymentResponse getPayment(@PathVariable UUID paymentId) {
log.info("GET /v1/payments/{}", paymentId);
return PaymentResponse.from(commandHandler.getPayment(paymentId));
}
- /**
- * Cancels a payment by sending a cancel signal to the workflow.
- * Returns 200 if accepted, 409 if the payment is in a terminal state.
- */
@PostMapping("/{paymentId}/cancel")
public PaymentResponse cancelPayment(
@PathVariable UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentResponse.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentResponse.java
index 0e7cea8d..37712731 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentResponse.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/controller/PaymentResponse.java
@@ -6,9 +6,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Response DTO representing a payment in the API layer.
- */
public record PaymentResponse(
UUID paymentId,
String idempotencyKey,
@@ -28,9 +25,6 @@ public record PaymentResponse(
Instant expiresAt
) {
- /**
- * Maps a domain {@link Payment} aggregate to the API response DTO.
- */
public static PaymentResponse from(Payment payment) {
return new PaymentResponse(
payment.paymentId(),
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/filter/IdempotencyKeyFilter.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/filter/IdempotencyKeyFilter.java
index 47ccc208..0a4df07b 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/filter/IdempotencyKeyFilter.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/filter/IdempotencyKeyFilter.java
@@ -14,10 +14,6 @@
import java.io.IOException;
import java.util.Set;
-/**
- * Enforces presence of {@code Idempotency-Key} header on state-mutating endpoints
- * (POST, PATCH, DELETE) -- excluding actuator endpoints.
- */
@Slf4j
@Component
@Order(2)
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/event/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/event/package-info.java
index 14dd42ef..30fa12f1 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/event/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/event/package-info.java
@@ -1,6 +1 @@
-/**
- * Domain event records published via the outbox.
- *
- * Populated in STA-105 (domain model implementation).
- */
package com.stablecoin.payments.orchestrator.domain.event;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/ChainId.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/ChainId.java
index 87e8e80f..0cf8c53f 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/ChainId.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/ChainId.java
@@ -2,11 +2,6 @@
import java.util.Set;
-/**
- * Value object representing a blockchain network identifier.
- *
- * Supported chains: ethereum, solana, stellar, tron, base, polygon.
- */
public record ChainId(String value) {
private static final Set SUPPORTED_CHAINS =
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Corridor.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Corridor.java
index df3d34ef..c4fe19f2 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Corridor.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Corridor.java
@@ -1,10 +1,5 @@
package com.stablecoin.payments.orchestrator.domain.model;
-/**
- * Value object representing a payment corridor (source country to target country).
- *
- * Invariant: sourceCountry must not equal targetCountry.
- */
public record Corridor(String sourceCountry, String targetCountry) {
public Corridor {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/FxRate.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/FxRate.java
index 1b372f2e..1d143809 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/FxRate.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/FxRate.java
@@ -4,11 +4,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Value object representing a locked FX rate quote.
- *
- * Invariant: rate must be positive, expiresAt must be after lockedAt.
- */
public record FxRate(
UUID quoteId,
String from,
@@ -46,9 +41,6 @@ public record FxRate(
}
}
- /**
- * Returns true if this rate has expired relative to the given instant.
- */
public boolean isExpired(Instant at) {
return at.isAfter(expiresAt);
}
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Money.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Money.java
index 976a31f8..ba1913c3 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Money.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Money.java
@@ -2,11 +2,6 @@
import java.math.BigDecimal;
-/**
- * Value object representing a monetary amount with currency.
- *
- * Invariant: amount must be positive, currency must not be blank.
- */
public record Money(BigDecimal amount, String currency) {
public Money {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Payment.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Payment.java
index 1397041e..69988946 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Payment.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/Payment.java
@@ -30,19 +30,6 @@
import static com.stablecoin.payments.orchestrator.domain.model.PaymentTrigger.SUBMIT_ON_CHAIN;
import static com.stablecoin.payments.orchestrator.domain.model.PaymentTrigger.WORKFLOW_COMPLETED;
-/**
- * Aggregate root for a cross-border payment.
- *
- * Enforces the payment saga pipeline via an internal state machine:
- * {@code INITIATED -> COMPLIANCE_CHECK -> FX_LOCKED -> FIAT_COLLECTION_PENDING ->
- * FIAT_COLLECTED -> ON_CHAIN_SUBMITTED -> ON_CHAIN_CONFIRMED -> OFF_RAMP_INITIATED ->
- * SETTLED -> COMPLETED}.
- *
- * Compensation states handle failure recovery: {@code COMPENSATING_FIAT_REFUND},
- * {@code COMPENSATING_STABLECOIN_RETURN}.
- *
- * Immutable record — all state transitions return new instances via {@code toBuilder()}.
- */
@Builder(toBuilder = true, access = AccessLevel.PACKAGE)
public record Payment(
UUID paymentId,
@@ -70,7 +57,6 @@ public record Payment(
private static final StateMachine STATE_MACHINE =
new StateMachine<>(List.of(
- // ── Happy path ──────────────────────────────────────────────
new StateTransition<>(INITIATED, START_COMPLIANCE, COMPLIANCE_CHECK),
new StateTransition<>(COMPLIANCE_CHECK, COMPLIANCE_PASSED, PaymentState.FX_LOCKED),
new StateTransition<>(PaymentState.FX_LOCKED, LOCK_FX, PaymentState.FIAT_COLLECTION_PENDING),
@@ -82,34 +68,26 @@ public record Payment(
new StateTransition<>(PaymentState.OFF_RAMP_INITIATED, SETTLE, PaymentState.SETTLED),
new StateTransition<>(PaymentState.SETTLED, COMPLETE, COMPLETED),
- // ── Workflow terminal sync (Temporal → DB) ──────────────────
// The Temporal workflow is the saga authority. When it completes,
// the WORKFLOW_COMPLETED trigger allows direct INITIATED → COMPLETED
// since all intermediate steps were validated by the workflow.
new StateTransition<>(INITIATED, WORKFLOW_COMPLETED, COMPLETED),
- // ── Failure from any active state ───────────────────────────
new StateTransition<>(INITIATED, FAIL, FAILED),
new StateTransition<>(COMPLIANCE_CHECK, FAIL, FAILED),
new StateTransition<>(PaymentState.FX_LOCKED, FAIL, FAILED),
new StateTransition<>(PaymentState.FIAT_COLLECTION_PENDING, FAIL, FAILED),
- // ── Compensation paths ──────────────────────────────────────
new StateTransition<>(PaymentState.FIAT_COLLECTED, START_COMPENSATION, COMPENSATING_FIAT_REFUND),
new StateTransition<>(PaymentState.ON_CHAIN_SUBMITTED, START_COMPENSATION, COMPENSATING_STABLECOIN_RETURN),
new StateTransition<>(PaymentState.ON_CHAIN_CONFIRMED, START_COMPENSATION, COMPENSATING_STABLECOIN_RETURN),
new StateTransition<>(PaymentState.OFF_RAMP_INITIATED, START_COMPENSATION, COMPENSATING_STABLECOIN_RETURN),
- // ── Compensation terminal ───────────────────────────────────
new StateTransition<>(COMPENSATING_FIAT_REFUND, FAIL, FAILED),
new StateTransition<>(COMPENSATING_STABLECOIN_RETURN, FAIL, FAILED)
));
- // ── Factory Method ──────────────────────────────────────────────
- /**
- * Creates a new payment in INITIATED state.
- */
public static Payment initiate(String idempotencyKey, UUID correlationId,
UUID senderId, UUID recipientId,
Money sourceAmount,
@@ -159,11 +137,7 @@ public static Payment initiate(String idempotencyKey, UUID correlationId,
.build();
}
- // ── State Transition Methods ────────────────────────────────────
- /**
- * Starts compliance check. Transitions INITIATED -> COMPLIANCE_CHECK.
- */
public Payment startComplianceCheck() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, START_COMPLIANCE);
@@ -173,9 +147,6 @@ public Payment startComplianceCheck() {
.build();
}
- /**
- * Locks FX rate after compliance passes. Transitions COMPLIANCE_CHECK -> FX_LOCKED.
- */
public Payment lockFxRate(FxRate fxRate) {
assertNotTerminal();
if (fxRate == null) {
@@ -191,9 +162,6 @@ public Payment lockFxRate(FxRate fxRate) {
.build();
}
- /**
- * Starts fiat collection. Transitions FX_LOCKED -> FIAT_COLLECTION_PENDING.
- */
public Payment startFiatCollection() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, LOCK_FX);
@@ -203,9 +171,6 @@ public Payment startFiatCollection() {
.build();
}
- /**
- * Confirms fiat collected. Transitions FIAT_COLLECTION_PENDING -> FIAT_COLLECTED.
- */
public Payment confirmFiatCollected() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, PaymentTrigger.FIAT_COLLECTED);
@@ -215,9 +180,6 @@ public Payment confirmFiatCollected() {
.build();
}
- /**
- * Submits on-chain transaction. Transitions FIAT_COLLECTED -> ON_CHAIN_SUBMITTED.
- */
public Payment submitOnChain(ChainId chainId) {
assertNotTerminal();
if (chainId == null) {
@@ -231,9 +193,6 @@ public Payment submitOnChain(ChainId chainId) {
.build();
}
- /**
- * Confirms on-chain transaction. Transitions ON_CHAIN_SUBMITTED -> ON_CHAIN_CONFIRMED.
- */
public Payment confirmOnChain(String transactionHash) {
assertNotTerminal();
if (transactionHash == null || transactionHash.isBlank()) {
@@ -247,9 +206,6 @@ public Payment confirmOnChain(String transactionHash) {
.build();
}
- /**
- * Initiates off-ramp. Transitions ON_CHAIN_CONFIRMED -> OFF_RAMP_INITIATED.
- */
public Payment initiateOffRamp() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, INITIATE_OFF_RAMP);
@@ -259,9 +215,6 @@ public Payment initiateOffRamp() {
.build();
}
- /**
- * Settles the payment. Transitions OFF_RAMP_INITIATED -> SETTLED.
- */
public Payment settle() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, SETTLE);
@@ -271,9 +224,6 @@ public Payment settle() {
.build();
}
- /**
- * Completes the payment. Transitions SETTLED -> COMPLETED.
- */
public Payment complete() {
assertNotTerminal();
var nextState = STATE_MACHINE.transition(state, COMPLETE);
@@ -283,13 +233,6 @@ public Payment complete() {
.build();
}
- /**
- * Completes the payment from a Temporal workflow result.
- *
- * Transitions directly INITIATED → COMPLETED using the WORKFLOW_COMPLETED trigger.
- * The Temporal workflow has already validated all intermediate saga steps —
- * the DB aggregate only needs the terminal state and result metadata.
- */
public Payment completeFromWorkflow(FxRate fxRate, Money targetAmount,
ChainId chain, String transactionHash) {
var nextState = STATE_MACHINE.transition(state, WORKFLOW_COMPLETED);
@@ -303,9 +246,6 @@ public Payment completeFromWorkflow(FxRate fxRate, Money targetAmount,
.build();
}
- /**
- * Fails the payment. Can be triggered from any non-terminal active state.
- */
public Payment fail(String reason) {
var nextState = STATE_MACHINE.transition(state, FAIL);
return toBuilder()
@@ -315,9 +255,6 @@ public Payment fail(String reason) {
.build();
}
- /**
- * Starts compensation flow. Applicable from post-fiat-collected states.
- */
public Payment startCompensation(String reason) {
assertNotTerminal();
if (reason == null || reason.isBlank()) {
@@ -331,30 +268,19 @@ public Payment startCompensation(String reason) {
.build();
}
- // ── Query Methods ───────────────────────────────────────────────
- /**
- * Returns true if this payment is in a terminal state (COMPLETED or FAILED).
- */
public boolean isTerminal() {
return TERMINAL_STATES.contains(state);
}
- /**
- * Returns true if a given trigger can be applied from the current state.
- */
public boolean canApply(PaymentTrigger trigger) {
return STATE_MACHINE.canTransition(state, trigger);
}
- /**
- * Returns true if this payment is in a compensation state.
- */
public boolean isCompensating() {
return state == COMPENSATING_FIAT_REFUND || state == COMPENSATING_STABLECOIN_RETURN;
}
- // ── Invariant Guards ────────────────────────────────────────────
private void assertNotTerminal() {
if (isTerminal()) {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotCancellableException.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotCancellableException.java
index 5209e2a4..a0595449 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotCancellableException.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotCancellableException.java
@@ -2,10 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when attempting to cancel a payment that is in a terminal state
- * and cannot be cancelled.
- */
public class PaymentNotCancellableException extends RuntimeException {
public PaymentNotCancellableException(UUID paymentId, PaymentState state) {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotFoundException.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotFoundException.java
index f53cc144..dbb9bd53 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotFoundException.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/PaymentNotFoundException.java
@@ -2,9 +2,6 @@
import java.util.UUID;
-/**
- * Thrown when a payment cannot be found by its ID.
- */
public class PaymentNotFoundException extends RuntimeException {
public PaymentNotFoundException(UUID paymentId) {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/package-info.java
index d91fa3c5..3d5b5183 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/model/package-info.java
@@ -1,6 +1 @@
-/**
- * Domain model classes: aggregates, value objects, and enums.
- *
- * Populated in STA-105 (domain model implementation).
- */
package com.stablecoin.payments.orchestrator.domain.model;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/port/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/port/package-info.java
index 992885f4..37e208e0 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/port/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/port/package-info.java
@@ -1,6 +1 @@
-/**
- * Domain port interfaces (hexagonal architecture).
- *
- * Populated in STA-105 (domain model implementation).
- */
package com.stablecoin.payments.orchestrator.domain.port;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/PaymentCommandHandler.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/PaymentCommandHandler.java
index aff8e3d0..843c8699 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/PaymentCommandHandler.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/PaymentCommandHandler.java
@@ -15,7 +15,6 @@
import io.temporal.client.WorkflowOptions;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -25,38 +24,18 @@
import java.util.Optional;
import java.util.UUID;
-import static com.stablecoin.payments.orchestrator.application.config.TemporalConfig.TASK_QUEUE;
-
-/**
- * Domain command handler for payment lifecycle operations.
- *
- * Orchestrates payment creation, retrieval, and cancellation.
- * Creates the Payment aggregate, persists it, and starts the Temporal workflow.
- */
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class PaymentCommandHandler {
+ public static final String TASK_QUEUE = "payment-orchestrator-queue";
+
private final PaymentRepository paymentRepository;
private final PaymentEventPublisher eventPublisher;
private final WorkflowClient workflowClient;
- /**
- * Initiates a new payment or returns existing one for idempotent replay.
- *
- * @param idempotencyKey unique key for idempotent replay
- * @param correlationId trace correlation ID
- * @param senderId sender merchant ID
- * @param recipientId recipient merchant ID
- * @param sourceAmount payment amount
- * @param sourceCurrency source currency code
- * @param targetCurrency target currency code
- * @param sourceCountry source country code
- * @param targetCountry target country code
- * @return the created or existing Payment, and whether it was a replay
- */
public InitiateResult initiatePayment(String idempotencyKey, UUID correlationId,
UUID senderId, UUID recipientId,
BigDecimal sourceAmount,
@@ -85,7 +64,7 @@ public InitiateResult initiatePayment(String idempotencyKey, UUID correlationId,
Payment saved;
try {
saved = paymentRepository.save(payment);
- } catch (DataIntegrityViolationException ex) {
+ } catch (RuntimeException ex) {
log.info("Concurrent duplicate for idempotencyKey={}, returning existing", idempotencyKey);
var concurrent = paymentRepository.findByIdempotencyKey(idempotencyKey)
.orElseThrow(() -> ex);
@@ -110,23 +89,12 @@ public InitiateResult initiatePayment(String idempotencyKey, UUID correlationId,
return new InitiateResult(saved, false);
}
- /**
- * Retrieves a payment by its ID.
- *
- * @throws PaymentNotFoundException if no payment exists with the given ID
- */
@Transactional(readOnly = true)
public Payment getPayment(UUID paymentId) {
return paymentRepository.findById(paymentId)
.orElseThrow(() -> new PaymentNotFoundException(paymentId));
}
- /**
- * Cancels a payment by sending a cancel signal to the Temporal workflow.
- *
- * @throws PaymentNotFoundException if no payment exists with the given ID
- * @throws PaymentNotCancellableException if the payment is in a terminal state
- */
public Payment cancelPayment(UUID paymentId, String reason) {
log.info("Cancelling payment paymentId={}, reason={}", paymentId, reason);
@@ -173,8 +141,5 @@ private void startWorkflow(Payment payment, String sourceCountry, String targetC
log.info("Temporal workflow started workflowId=payment-{}", payment.paymentId());
}
- /**
- * Result of initiatePayment, indicating whether this was an idempotent replay.
- */
public record InitiateResult(Payment payment, boolean replay) {}
}
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/package-info.java
index ef5f01a4..214dccb3 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/service/package-info.java
@@ -1,6 +1 @@
-/**
- * Domain services: saga coordination, compensation, expiry enforcement.
- *
- * Populated in STA-105 (domain model implementation).
- */
package com.stablecoin.payments.orchestrator.domain.service;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/statemachine/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/statemachine/package-info.java
index cc556bd9..d2617628 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/statemachine/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/statemachine/package-info.java
@@ -1,10 +1 @@
-/**
- * Generic state machine for {@code Payment} state transitions.
- *
- * {@link com.stablecoin.payments.orchestrator.domain.statemachine.StateMachine} validates
- * transitions between {@link com.stablecoin.payments.orchestrator.domain.model.PaymentState}
- * states using {@link com.stablecoin.payments.orchestrator.domain.model.PaymentTrigger} triggers.
- *
- * @see com.stablecoin.payments.orchestrator.domain.model.Payment
- */
package com.stablecoin.payments.orchestrator.domain.statemachine;
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflow.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflow.java
index f5c9fb68..17a2b1a0 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflow.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflow.java
@@ -10,22 +10,6 @@
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
-/**
- * Temporal workflow that orchestrates the cross-border payment saga.
- *
- * Full sandwich flow (5 steps):
- *
- * Compliance check (S2) — read-only, no compensation
- * FX rate lock (S6) — compensation: release lock
- * Fiat collection (S3) — async (webhook signal), compensation: refund
- * Chain transfer (S4) — async (monitor signal), compensation: return transfer
- * Off-ramp payout (S5) — fire-and-forget, no compensation
- *
- *
- * Workflow ID convention: {@code payment_id} (natural deduplication).
- * Task queue: {@code payment-orchestrator-queue}.
- * Workflow deadline: 30 minutes.
- */
@WorkflowInterface
public interface PaymentWorkflow {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java
index 4bb9e0e9..f7e74ba9 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java
@@ -38,28 +38,6 @@
import java.util.Deque;
import java.util.UUID;
-/**
- * Deterministic Temporal workflow implementation for the cross-border payment saga.
- *
- * Full sandwich flow (5 steps):
- *
- * Compliance check (S2) — read-only, no compensation
- * FX rate lock (S6) — compensation: release lock
- * Fiat collection (S3) — async (webhook signal), compensation: refund
- * Chain transfer (S4) — async (monitor signal), compensation: return transfer
- * Off-ramp payout (S5) — fire-and-forget, no compensation
- *
- *
- * Determinism rules enforced:
- *
- * Uses {@link Workflow#currentTimeMillis()} — never {@code System.currentTimeMillis()}
- * Uses {@link Workflow#getLogger(Class)} — never SLF4J directly
- * No {@code Random}, no I/O, no {@code Thread.sleep}
- *
- *
- * Saga compensation: compensation actions are pushed onto a LIFO stack
- * after each forward step succeeds. On cancel or failure, the stack is unwound in reverse order.
- */
public class PaymentWorkflowImpl implements PaymentWorkflow {
private static final Duration FIAT_COLLECTION_TIMEOUT = Duration.ofMinutes(30);
@@ -172,7 +150,6 @@ public PaymentResult executePayment(PaymentRequest request) {
log = Workflow.getLogger(PaymentWorkflowImpl.class);
log.info("Starting payment workflow for paymentId={}", request.paymentId());
- // ── Step 1: Compliance Check ────────────────────────────────────
currentState = "COMPLIANCE_CHECK";
log.info("Step 1: Running compliance check for paymentId={}", request.paymentId());
@@ -216,7 +193,6 @@ public PaymentResult executePayment(PaymentRequest request) {
return handleCancellation(request);
}
- // ── Step 2: FX Rate Lock ────────────────────────────────────────
currentState = "FX_LOCKING";
log.info("Step 2: Locking FX rate for paymentId={}", request.paymentId());
@@ -262,7 +238,6 @@ public PaymentResult executePayment(PaymentRequest request) {
return handleCancellation(request);
}
- // ── Step 3: Fiat Collection (S3 — async) ────────────────────────
currentState = "FIAT_COLLECTION_PENDING";
log.info("Step 3: Initiating fiat collection for paymentId={}", request.paymentId());
@@ -320,7 +295,6 @@ public PaymentResult executePayment(PaymentRequest request) {
return handleCancellation(request);
}
- // ── Step 4: Chain Transfer (S4 — async) ─────────────────────────
currentState = "ON_CHAIN_SUBMITTED";
log.info("Step 4: Submitting chain transfer for paymentId={}", request.paymentId());
@@ -379,7 +353,6 @@ public PaymentResult executePayment(PaymentRequest request) {
return handleCancellation(request);
}
- // ── Step 5: Off-Ramp Payout (S5) ────────────────────────────────
currentState = "OFF_RAMP_INITIATED";
log.info("Step 5: Initiating off-ramp payout for paymentId={}", request.paymentId());
@@ -414,7 +387,6 @@ public PaymentResult executePayment(PaymentRequest request) {
return handleFailureWithCompensation(request, reason);
}
- // ── Settlement Complete ─────────────────────────────────────────
currentState = "SETTLED";
log.info("Off-ramp payout initiated for paymentId={}, payoutId={}",
request.paymentId(), offRampResult.payoutId());
@@ -462,10 +434,6 @@ public String getPaymentState() {
private static final String REFUND_FIAT_PREFIX = "REFUND_FIAT:";
private static final String RETURN_CHAIN_PREFIX = "RETURN_CHAIN:";
- /**
- * Runs compensation stack in LIFO order and returns a FAILED result.
- * Used when cancellation is requested between saga steps.
- */
private PaymentResult handleCancellation(PaymentRequest request) {
currentState = "COMPENSATING";
var reason = cancelReason != null ? cancelReason.reason() : "Cancellation requested";
@@ -483,10 +451,6 @@ private PaymentResult handleCancellation(PaymentRequest request) {
return PaymentResult.failed(request.paymentId(), failureReason);
}
- /**
- * Runs compensation stack in LIFO order and returns a FAILED result.
- * Used when a forward step fails and compensation is needed.
- */
private PaymentResult handleFailureWithCompensation(PaymentRequest request, String reason) {
log.info("Running compensation for paymentId={}, reason={}, compensationSteps={}",
request.paymentId(), reason, compensationStack.size());
@@ -539,9 +503,6 @@ private void executeCompensationStep(String step, UUID paymentId, String reason)
}
}
- /**
- * Fire-and-forget event publishing. Failures are logged but do not block the saga.
- */
private void publishEvent(PaymentEventRequest request) {
try {
eventPublishingActivity.publishPaymentEvent(request);
@@ -551,10 +512,6 @@ private void publishEvent(PaymentEventRequest request) {
}
}
- /**
- * Syncs the workflow terminal state to the Payment aggregate in PostgreSQL.
- * Best-effort — failures are logged but do not block the workflow result.
- */
private void syncStateToDb(PaymentStateUpdate update) {
try {
updateStateActivity.updateState(update);
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainReturnRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainReturnRequest.java
index 80ec5020..a206a9f2 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainReturnRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainReturnRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the chain transfer return compensation activity.
- *
- * Used during saga compensation to submit a return transfer of stablecoin
- * back to the originating wallet via S4 Blockchain & Custody.
- */
public record ChainReturnRequest(
UUID originalTransferId,
UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferActivity.java
index 61476277..67cdae02 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferActivity.java
@@ -2,13 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for submitting a stablecoin transfer on-chain.
- *
- * Implementation calls S4 Blockchain & Custody via Feign to submit a USDC
- * transfer. The transfer is async (confirmed via block monitor → Kafka signal).
- * Includes a compensation method to return transferred stablecoin.
- */
@ActivityInterface
public interface ChainTransferActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferRequest.java
index 750b000d..5e17fbbf 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the chain transfer activity.
- *
- * Contains the payment and stablecoin information needed by
- * S4 Blockchain & Custody to submit a USDC transfer on-chain.
- */
public record ChainTransferRequest(
UUID paymentId,
UUID correlationId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferResult.java
index 40b38278..7b368936 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ChainTransferResult.java
@@ -2,13 +2,6 @@
import java.util.UUID;
-/**
- * Result DTO from the chain transfer activity.
- *
- * Contains the transfer ID and submission status. The transfer confirmation
- * is async — arrives via the {@code onChainConfirmed} workflow signal after
- * S4's block monitor detects sufficient confirmations.
- */
public record ChainTransferResult(
UUID transferId,
String chainId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceCheckActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceCheckActivity.java
index 0fdd301c..881737ca 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceCheckActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceCheckActivity.java
@@ -2,15 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for compliance/AML/sanctions screening.
- *
- * Implementation (STA-110) will call S2 Compliance service via Feign.
- * Activity stubs are configured with 30s start-to-close timeout.
- *
- * Note: {@code @ActivityMethod} is intentionally omitted — {@code @ActivityInterface}
- * is sufficient and avoids issues with Mockito proxies in Temporal test environments.
- */
@ActivityInterface
public interface ComplianceCheckActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceRequest.java
index a8306d03..50868060 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the compliance check activity.
- *
- * Contains sender/recipient information and payment details
- * needed by S2 Compliance service for screening.
- */
public record ComplianceRequest(
UUID paymentId,
UUID senderId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceResult.java
index 5ed5b6cb..cd089ce3 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/ComplianceResult.java
@@ -2,12 +2,6 @@
import java.util.UUID;
-/**
- * Result DTO from the compliance check activity.
- *
- * Contains the compliance check outcome and any screening reference
- * for audit trail purposes.
- */
public record ComplianceResult(
UUID checkId,
ComplianceStatus status,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/EventPublishingActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/EventPublishingActivity.java
index ff264eb1..54667c02 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/EventPublishingActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/EventPublishingActivity.java
@@ -2,12 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Temporal activity for publishing payment lifecycle events to Kafka via Namastack outbox.
- *
- * Events are published fire-and-forget from the workflow — failures do not block
- * the saga. The outbox guarantees at-least-once delivery to Kafka.
- */
@ActivityInterface
public interface EventPublishingActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionActivity.java
index ed4d16e5..1cec36f7 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionActivity.java
@@ -2,13 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for initiating fiat collection from the sender's bank.
- *
- * Implementation calls S3 Fiat On-Ramp via Feign to create a collection order.
- * The collection is async (confirmed via Stripe webhook → Kafka signal).
- * Includes a compensation method to refund a completed collection.
- */
@ActivityInterface
public interface FiatCollectionActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionRequest.java
index e826f989..dbe47285 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the fiat collection activity.
- *
- * Contains the payment and banking information needed by
- * S3 Fiat On-Ramp to initiate an ACH collection via Stripe.
- */
public record FiatCollectionRequest(
UUID paymentId,
UUID correlationId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionResult.java
index 56c7941c..e7c67998 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatCollectionResult.java
@@ -2,13 +2,6 @@
import java.util.UUID;
-/**
- * Result DTO from the fiat collection activity.
- *
- * Contains the collection order ID and initiation status. The collection
- * itself is async — actual confirmation arrives via the {@code onFiatCollected}
- * workflow signal after a Stripe webhook is received by S3.
- */
public record FiatCollectionResult(
UUID collectionId,
FiatCollectionStatus status,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatRefundRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatRefundRequest.java
index 15fc4fd8..e5d261c3 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatRefundRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FiatRefundRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the fiat collection refund compensation activity.
- *
- * Used during saga compensation to refund a previously collected fiat payment
- * back to the sender via S3 Fiat On-Ramp.
- */
public record FiatRefundRequest(
UUID collectionId,
UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockActivity.java
index 84576f59..7ce1f95a 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockActivity.java
@@ -2,16 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for locking an FX rate quote.
- *
- * Implementation calls S6 FX & Liquidity Engine via Feign to get a quote
- * and lock the rate. Includes a compensation method to release the lock
- * during saga rollback.
- *
- * Note: {@code @ActivityMethod} is intentionally omitted — {@code @ActivityInterface}
- * is sufficient and avoids issues with Mockito proxies in Temporal test environments.
- */
@ActivityInterface
public interface FxLockActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockRequest.java
index d71d3a9d..9c84fce6 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the FX rate lock activity.
- *
- * Contains the payment and currency pair information needed by
- * S6 FX & Liquidity Engine to lock an exchange rate.
- */
public record FxLockRequest(
String idempotencyKey,
UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockResult.java
index ef1f7aad..585b81fa 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxLockResult.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Result DTO from the FX rate lock activity.
- *
- * Contains the locked quote details including rate, converted amount,
- * and quote expiration for the payment saga.
- */
public record FxLockResult(
UUID lockId,
UUID quoteId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxReleaseRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxReleaseRequest.java
index f8b682e0..f292fcf2 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxReleaseRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/FxReleaseRequest.java
@@ -2,12 +2,6 @@
import java.util.UUID;
-/**
- * Request DTO for the FX lock release compensation activity.
- *
- * Used during saga compensation to release a previously locked FX rate,
- * freeing the reserved liquidity pool balance.
- */
public record FxReleaseRequest(
UUID lockId,
UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampActivity.java
index 7f4f0e55..a815b941 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampActivity.java
@@ -2,14 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for initiating a fiat payout to the recipient.
- *
- * Implementation calls S5 Fiat Off-Ramp via Feign to initiate a SEPA payout.
- * The payout is async (confirmed via partner webhook → Kafka event).
- * No compensation method — once stablecoin is redeemed by S5, the off-ramp
- * partner handles settlement.
- */
@ActivityInterface
public interface OffRampActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampRequest.java
index 16cc6a75..1da8299f 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Request DTO for the off-ramp payout activity.
- *
- * Contains the payment, transfer, and recipient information needed by
- * S5 Fiat Off-Ramp to initiate a SEPA payout via Modulr.
- */
public record OffRampRequest(
UUID paymentId,
UUID correlationId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampResult.java
index 80eb7e17..025fb2c3 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/OffRampResult.java
@@ -2,13 +2,6 @@
import java.util.UUID;
-/**
- * Result DTO from the off-ramp payout activity.
- *
- * Contains the payout ID and initiation status. The payout settlement
- * is async — actual confirmation arrives via a partner webhook to S5,
- * which publishes a {@code fiat.payout.completed} Kafka event.
- */
public record OffRampResult(
UUID payoutId,
OffRampStatus status,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentEventRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentEventRequest.java
index 5e9be338..d0fb268b 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentEventRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentEventRequest.java
@@ -5,19 +5,6 @@
import java.util.UUID;
-/**
- * Serializable DTO for publishing payment events from the Temporal workflow.
- *
- * Uses Strings for enum fields to ensure clean Temporal serialization.
- * The activity implementation maps this back to the appropriate domain event.
- *
- * @param eventType event type identifier matching the domain event TOPIC constant
- * @param paymentId payment aggregate ID (used as Kafka partition key)
- * @param correlationId trace correlation ID for distributed tracing
- * @param failedState state at which failure occurred (only for payment.failed)
- * @param reason failure or cancellation reason
- * @param errorCode error code (only for payment.failed)
- */
public record PaymentEventRequest(
String eventType,
UUID paymentId,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentStateUpdate.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentStateUpdate.java
index 38fdfc5e..3ff3fd5f 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentStateUpdate.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/PaymentStateUpdate.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * DTO for updating the Payment aggregate state from the Temporal workflow.
- *
- * Carries the terminal state (COMPLETED or FAILED) and any associated
- * metadata from the workflow result back to the database.
- */
public record PaymentStateUpdate(
UUID paymentId,
String terminalState,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/UpdatePaymentStateActivity.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/UpdatePaymentStateActivity.java
index 096c8e19..d5dce65a 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/UpdatePaymentStateActivity.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/activity/UpdatePaymentStateActivity.java
@@ -2,14 +2,6 @@
import io.temporal.activity.ActivityInterface;
-/**
- * Activity interface for syncing the Temporal workflow's terminal state
- * back to the Payment aggregate in PostgreSQL.
- *
- * Called at the end of every workflow execution path (COMPLETED or FAILED)
- * to ensure the database reflects the workflow result. Temporal retries
- * on transient DB failures, guaranteeing eventual consistency.
- */
@ActivityInterface
public interface UpdatePaymentStateActivity {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/CancelRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/CancelRequest.java
index 58e23072..5741cf9e 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/CancelRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/CancelRequest.java
@@ -2,11 +2,6 @@
import java.util.UUID;
-/**
- * Signal sent to the PaymentWorkflow to cancel a payment in progress.
- *
- * Triggers the compensation stack in LIFO order for any completed forward steps.
- */
public record CancelRequest(
UUID paymentId,
String reason,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/ChainConfirmedSignal.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/ChainConfirmedSignal.java
index 2714a5a0..558444da 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/ChainConfirmedSignal.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/ChainConfirmedSignal.java
@@ -2,11 +2,6 @@
import java.util.UUID;
-/**
- * Signal sent to the PaymentWorkflow when the on-chain transfer is confirmed.
- *
- * Phase 3 preparation — will be used when S4 Blockchain service is implemented.
- */
public record ChainConfirmedSignal(
UUID paymentId,
String txHash,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/FiatCollectedSignal.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/FiatCollectedSignal.java
index bbfc1f62..516123a2 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/FiatCollectedSignal.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/FiatCollectedSignal.java
@@ -3,11 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Signal sent to the PaymentWorkflow when fiat has been collected from the sender.
- *
- * Phase 3 preparation — will be used when S3 On-Ramp is implemented.
- */
public record FiatCollectedSignal(
UUID paymentId,
String providerReference,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentRequest.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentRequest.java
index 966575ca..12d806ce 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentRequest.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentRequest.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Input DTO for the PaymentWorkflow.
- *
- * Contains all the information needed to execute a cross-border payment saga.
- * Workflow ID should be set to {@code paymentId} for natural deduplication.
- */
public record PaymentRequest(
UUID paymentId,
String idempotencyKey,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentResult.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentResult.java
index 4cbe9095..25f73bc7 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentResult.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/dto/PaymentResult.java
@@ -3,12 +3,6 @@
import java.math.BigDecimal;
import java.util.UUID;
-/**
- * Output DTO for the PaymentWorkflow.
- *
- * Contains the final result of the payment saga execution including
- * the locked FX rate, converted amount, and terminal status.
- */
public record PaymentResult(
UUID paymentId,
PaymentResultStatus status,
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ChainTransferActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ChainTransferActivityImpl.java
index 649e90ca..ebae4ccc 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ChainTransferActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ChainTransferActivityImpl.java
@@ -15,14 +15,6 @@
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.ChainTransferResult.ChainTransferStatus.FAILED;
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.ChainTransferResult.ChainTransferStatus.SUBMITTED;
-/**
- * Temporal activity implementation that calls S4 Blockchain & Custody via Feign.
- *
- * Transfer submission is async — actual confirmation arrives via S4's block
- * monitor job, which publishes a Kafka event, consumed by S1 to signal the workflow.
- *
- * Compensation: submit a RETURN transfer back to the originating wallet.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ComplianceCheckActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ComplianceCheckActivityImpl.java
index 4677d5ed..3bd8ca6f 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ComplianceCheckActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/ComplianceCheckActivityImpl.java
@@ -18,14 +18,6 @@
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.ComplianceResult.ComplianceStatus.PASSED;
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.ComplianceResult.ComplianceStatus.SANCTIONS_HIT;
-/**
- * Temporal activity implementation that calls S2 Compliance Service via REST.
- *
- * Flow: POST to initiate check, then poll GET until terminal state.
- * Heartbeats during polling to signal liveness to the Temporal server.
- *
- * No compensation needed — compliance check does not move value.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/EventPublishingActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/EventPublishingActivityImpl.java
index 2200b80c..29685789 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/EventPublishingActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/EventPublishingActivityImpl.java
@@ -13,13 +13,6 @@
import java.time.Instant;
-/**
- * Temporal activity implementation that publishes payment events via Namastack outbox.
- *
- * Each invocation runs in its own transaction so the outbox write is atomic.
- * The {@link PaymentEventPublisher} uses {@code @Transactional(propagation = MANDATORY)},
- * which joins this activity's transaction.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FiatCollectionActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FiatCollectionActivityImpl.java
index c26a8452..deb35cbd 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FiatCollectionActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FiatCollectionActivityImpl.java
@@ -15,14 +15,6 @@
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.FiatCollectionResult.FiatCollectionStatus.FAILED;
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.FiatCollectionResult.FiatCollectionStatus.INITIATED;
-/**
- * Temporal activity implementation that calls S3 Fiat On-Ramp via Feign.
- *
- * Collection initiation is async — actual confirmation arrives via Stripe webhook
- * to S3, which publishes a Kafka event, consumed by S1 to send a workflow signal.
- *
- * Compensation: refund the collected fiat via S3 refund endpoint.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FxLockActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FxLockActivityImpl.java
index c7148a29..55eb63e0 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FxLockActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/FxLockActivityImpl.java
@@ -16,13 +16,6 @@
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.FxLockResult.FxLockStatus.FAILED;
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.FxLockResult.FxLockStatus.LOCKED;
-/**
- * Temporal activity implementation that calls S6 FX & Liquidity Engine via REST.
- *
- * Flow: GET quote → POST lock rate. Compensation releases the lock.
- *
- * Idempotency key: {@code {paymentId}:fx-lock}
- */
@Slf4j
@Component
@RequiredArgsConstructor
@@ -36,7 +29,6 @@ public FxLockResult lockFxRate(FxLockRequest request) {
request.paymentId(), request.sourceAmount(),
request.sourceCurrency(), request.targetCurrency());
- // Step 1: Get quote (4xx = non-retryable corridor/validation error)
var quote = fxEngineClient.getQuote(
request.sourceCurrency(),
request.targetCurrency(),
@@ -45,7 +37,6 @@ public FxLockResult lockFxRate(FxLockRequest request) {
log.info("Quote received for paymentId={}, quoteId={}, rate={}",
request.paymentId(), quote.quoteId(), quote.rate());
- // Step 2: Lock the quoted rate (paymentId used as correlationId for idempotency)
var lockRequest = new FxRateLockRequest(
request.paymentId(),
request.paymentId(),
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/OffRampActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/OffRampActivityImpl.java
index 1b89e764..7566a1f8 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/OffRampActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/OffRampActivityImpl.java
@@ -18,15 +18,6 @@
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.OffRampResult.OffRampStatus.FAILED;
import static com.stablecoin.payments.orchestrator.domain.workflow.activity.OffRampResult.OffRampStatus.INITIATED;
-/**
- * Temporal activity implementation that calls S5 Fiat Off-Ramp via Feign.
- *
- * Payout initiation is async — actual settlement arrives via partner webhook
- * to S5, which publishes a Kafka event consumed by S7 for ledger reconciliation.
- *
- * No compensation method — once stablecoin is redeemed, the off-ramp partner
- * handles the fiat payout settlement.
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/UpdatePaymentStateActivityImpl.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/UpdatePaymentStateActivityImpl.java
index d8cb3d73..3b15d9ee 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/UpdatePaymentStateActivityImpl.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/activity/UpdatePaymentStateActivityImpl.java
@@ -13,16 +13,6 @@
import java.time.Instant;
-/**
- * Temporal activity that syncs the workflow terminal state back to the
- * Payment aggregate in PostgreSQL.
- *
- * Called at the end of every workflow execution path. Uses the domain
- * aggregate's state machine methods to ensure valid transitions.
- *
- * Idempotent: if the payment is already in a terminal state, the update
- * is silently skipped (workflow replay or retry scenario).
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FallbackAdaptersConfig.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FallbackAdaptersConfig.java
index 7afe1e02..7573e7ab 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FallbackAdaptersConfig.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FallbackAdaptersConfig.java
@@ -3,15 +3,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
-/**
- * Fallback adapter beans for local development and testing.
- *
- * When production adapter beans are not available (e.g., Temporal workers,
- * external service clients), this configuration provides stub implementations
- * using {@code @ConditionalOnMissingBean}.
- *
- * Populated as downstream adapters are implemented in later tickets.
- */
@Slf4j
@Configuration
public class FallbackAdaptersConfig {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java
index 77c030b9..be218969 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java
@@ -6,10 +6,6 @@
import java.util.UUID;
-/**
- * Feign interceptor that adds an Idempotency-Key header to all outgoing requests.
- * Required by S2 Compliance Service which rejects POST requests without this header.
- */
@Component
public class FeignIdempotencyInterceptor implements RequestInterceptor {
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/messaging/PaymentSignalConsumer.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/messaging/PaymentSignalConsumer.java
index 8a931b0b..019074f6 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/messaging/PaymentSignalConsumer.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/messaging/PaymentSignalConsumer.java
@@ -16,19 +16,6 @@
import java.util.UUID;
import java.util.function.Consumer;
-/**
- * Kafka consumer that receives async events from S3 and S4,
- * and forwards them as Temporal signals to the payment workflow.
- *
- * Events consumed:
- *
- * {@code fiat.collected} → {@link PaymentWorkflow#onFiatCollected}
- * {@code chain.transfer.confirmed} → {@link PaymentWorkflow#onChainConfirmed}
- *
- *
- * Idempotent: sending a signal to an already-completed workflow is a no-op
- * (caught as {@link WorkflowNotFoundException}).
- */
@Slf4j
@Component
@RequiredArgsConstructor
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/metrics/PaymentMetrics.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/metrics/PaymentMetrics.java
index bf2c6962..f329d89a 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/metrics/PaymentMetrics.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/metrics/PaymentMetrics.java
@@ -5,20 +5,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-/**
- * Custom business metrics for the Payment Orchestrator service.
- *
- *
Tracks payment initiation, completion, failure counts and lifecycle duration.
- */
@Component
@RequiredArgsConstructor
public class PaymentMetrics {
private final MeterRegistry meterRegistry;
- /**
- * Records a payment initiation event.
- */
public void recordPaymentInitiated(String corridor, String currency) {
meterRegistry.counter("payment.initiated",
"corridor", corridor,
@@ -26,9 +18,6 @@ public void recordPaymentInitiated(String corridor, String currency) {
).increment();
}
- /**
- * Records a payment completion event.
- */
public void recordPaymentCompleted(String corridor, String currency) {
meterRegistry.counter("payment.completed",
"corridor", corridor,
@@ -36,9 +25,6 @@ public void recordPaymentCompleted(String corridor, String currency) {
).increment();
}
- /**
- * Records a payment failure event.
- */
public void recordPaymentFailed(String corridor, String reason) {
meterRegistry.counter("payment.failed",
"corridor", corridor,
@@ -46,16 +32,10 @@ public void recordPaymentFailed(String corridor, String reason) {
).increment();
}
- /**
- * Starts a timer sample for measuring payment lifecycle duration.
- */
public Timer.Sample startPaymentTimer() {
return Timer.start(meterRegistry);
}
- /**
- * Stops the timer sample and records the payment lifecycle duration.
- */
public void recordPaymentDuration(Timer.Sample sample, String corridor) {
sample.stop(meterRegistry.timer("payment.duration", "corridor", corridor));
}
diff --git a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/persistence/package-info.java b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/persistence/package-info.java
index 7b2790a2..8f966554 100644
--- a/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/persistence/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/persistence/package-info.java
@@ -1,6 +1 @@
-/**
- * JPA entities, repositories, and persistence adapters.
- *
- * Populated in STA-107 (persistence layer implementation).
- */
package com.stablecoin.payments.orchestrator.infrastructure.persistence;
diff --git a/payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/application/controller/PaymentControllerMvcTest.java b/payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/application/controller/PaymentControllerMvcTest.java
index ae1c62df..c61a1ce3 100644
--- a/payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/application/controller/PaymentControllerMvcTest.java
+++ b/payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/application/controller/PaymentControllerMvcTest.java
@@ -1,6 +1,6 @@
package com.stablecoin.payments.orchestrator.application.controller;
-import com.stablecoin.payments.orchestrator.application.security.SecurityConfig;
+import com.stablecoin.payments.orchestrator.application.config.SecurityConfig;
import com.stablecoin.payments.orchestrator.domain.model.PaymentNotCancellableException;
import com.stablecoin.payments.orchestrator.domain.model.PaymentNotFoundException;
import com.stablecoin.payments.orchestrator.domain.model.PaymentState;
diff --git a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/ComplianceActivityFixtures.java b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/ComplianceActivityFixtures.java
index 9ab92ac3..6e75de78 100644
--- a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/ComplianceActivityFixtures.java
+++ b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/ComplianceActivityFixtures.java
@@ -12,9 +12,6 @@
import static com.stablecoin.payments.orchestrator.fixtures.WorkflowFixtures.CHECK_ID;
import static com.stablecoin.payments.orchestrator.fixtures.WorkflowFixtures.PAYMENT_ID;
-/**
- * Test fixture factory methods for compliance activity DTOs.
- */
public final class ComplianceActivityFixtures {
private ComplianceActivityFixtures() {}
diff --git a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/FxActivityFixtures.java b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/FxActivityFixtures.java
index 3c3599c0..884d3e30 100644
--- a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/FxActivityFixtures.java
+++ b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/FxActivityFixtures.java
@@ -12,9 +12,6 @@
import static com.stablecoin.payments.orchestrator.fixtures.WorkflowFixtures.PAYMENT_ID;
import static com.stablecoin.payments.orchestrator.fixtures.WorkflowFixtures.QUOTE_ID;
-/**
- * Test fixture factory methods for FX activity DTOs.
- */
public final class FxActivityFixtures {
private FxActivityFixtures() {}
diff --git a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/PaymentFixtures.java b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/PaymentFixtures.java
index b5524b82..ed45e3ae 100644
--- a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/PaymentFixtures.java
+++ b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/PaymentFixtures.java
@@ -11,12 +11,6 @@
import java.time.Instant;
import java.util.UUID;
-/**
- * Test fixture factory methods for {@link Payment} and related value objects.
- *
- * Each method creates a Payment at the named state by walking through the state machine
- * from INITIATED. This guarantees that every fixture is reachable via valid transitions.
- */
public final class PaymentFixtures {
public static final Money SOURCE_AMOUNT = new Money(new BigDecimal("1000.00"), "USD");
@@ -35,16 +29,10 @@ public final class PaymentFixtures {
private PaymentFixtures() {}
- /**
- * Creates an {@link PaymentCommandHandler.InitiateResult} representing a new payment.
- */
public static PaymentCommandHandler.InitiateResult anInitiateResult() {
return new PaymentCommandHandler.InitiateResult(anInitiatedPayment(), false);
}
- /**
- * Creates an {@link PaymentCommandHandler.InitiateResult} representing an idempotent replay.
- */
public static PaymentCommandHandler.InitiateResult anIdempotentReplayResult() {
return new PaymentCommandHandler.InitiateResult(anInitiatedPayment(), true);
}
diff --git a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/WorkflowFixtures.java b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/WorkflowFixtures.java
index 38446e2c..cf5eb55e 100644
--- a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/WorkflowFixtures.java
+++ b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/WorkflowFixtures.java
@@ -19,9 +19,6 @@
import static com.stablecoin.payments.orchestrator.fixtures.PaymentFixtures.RECIPIENT_ID;
import static com.stablecoin.payments.orchestrator.fixtures.PaymentFixtures.SENDER_ID;
-/**
- * Test fixture factory methods for Temporal workflow and activity DTOs.
- */
public final class WorkflowFixtures {
public static final UUID PAYMENT_ID = UUID.fromString("00000000-0000-0000-0000-000000000001");
diff --git a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/package-info.java b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/package-info.java
index 4197c47b..2622c357 100644
--- a/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/package-info.java
+++ b/payment-orchestrator/payment-orchestrator/src/testFixtures/java/com/stablecoin/payments/orchestrator/fixtures/package-info.java
@@ -1,6 +1 @@
-/**
- * Test fixtures for the payment-orchestrator service.
- *
- * Domain fixture factories will be added in STA-106.
- */
package com.stablecoin.payments.orchestrator.fixtures;
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java
index 98b4c205..4c9edaad 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java
@@ -4,13 +4,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-/**
- * Registers {@link ExternalApiLoggingInterceptor} when
- * {@code app.external-api.logging.enabled=true}.
- *
- * Disabled by default — enable in sandbox/dev profiles only.
- * Body truncation controlled by {@code app.external-api.logging.max-body-length} (default 2000).
- */
@Configuration
@ConditionalOnProperty(name = "app.external-api.logging.enabled", havingValue = "true")
public class ExternalApiLoggingConfig {
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java
index e5171232..abc1dabb 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java
@@ -17,13 +17,6 @@
import java.nio.charset.StandardCharsets;
import java.util.Set;
-/**
- * Logs external API request/response details for debugging and sandbox testing.
- *
- * Activated via {@code app.external-api.logging.enabled=true} (disabled by default).
- * Redacts sensitive headers (Authorization, X-API-KEY) to prevent credential leaks.
- * Response body is buffered so downstream consumers can still read it.
- */
public class ExternalApiLoggingInterceptor implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(ExternalApiLoggingInterceptor.class);
@@ -39,10 +32,6 @@ public ExternalApiLoggingInterceptor(int maxBodyLength) {
this.maxBodyLength = maxBodyLength;
}
- /**
- * Applies this interceptor to a {@link RestClient.Builder} if the interceptor is non-null.
- * Call this in adapter constructors: {@code ExternalApiLoggingInterceptor.applyTo(builder, interceptor)}
- */
public static RestClient.Builder applyTo(RestClient.Builder builder,
@Nullable ExternalApiLoggingInterceptor interceptor) {
if (interceptor != null) {
@@ -110,10 +99,6 @@ static String redact(String value) {
return value.substring(0, REDACT_VISIBLE_CHARS) + "***";
}
- /**
- * Wraps a response to buffer its body so it can be read multiple times
- * (once for logging, once for the actual consumer).
- */
private static final class BufferedResponse implements ClientHttpResponse {
private final ClientHttpResponse delegate;
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/metrics/MetricsConfig.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/metrics/MetricsConfig.java
index 782b5e8d..d8cf4da9 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/metrics/MetricsConfig.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/metrics/MetricsConfig.java
@@ -11,14 +11,6 @@
import java.util.List;
-/**
- * Shared metrics configuration for all services.
- *
- *
Registers common tags (service name, environment) on every meter,
- * ensuring consistent labelling across the platform for Prometheus / Grafana dashboards.
- *
- *
Activated by default; disable with {@code app.metrics.enabled=false}.
- */
@Configuration
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnProperty(name = "app.metrics.enabled", havingValue = "true", matchIfMissing = true)
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/KafkaTracingConfig.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/KafkaTracingConfig.java
index b4b40fe8..faffc978 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/KafkaTracingConfig.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/KafkaTracingConfig.java
@@ -9,19 +9,6 @@
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.KafkaTemplate;
-/**
- * Enables Micrometer Observation on Kafka producers and consumers so that trace context
- * (W3C {@code traceparent}) is automatically propagated through Kafka message headers.
- *
- *
This ensures end-to-end trace visibility across the event-driven pipeline:
- * S1 (Orchestrator) → Kafka → S2 (Compliance) → Kafka → S6 (FX) etc.
- *
- *
Uses a {@link BeanPostProcessor} to enable observation on all {@link KafkaTemplate}
- * and {@link ConcurrentKafkaListenerContainerFactory} instances, including those created
- * manually by individual services (not just auto-configured beans).
- *
- *
Activated only when both tracing and Kafka are on the classpath.
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.tracing.enabled", havingValue = "true", matchIfMissing = true)
@@ -33,10 +20,6 @@ static KafkaObservationBeanPostProcessor kafkaObservationBeanPostProcessor() {
return new KafkaObservationBeanPostProcessor();
}
- /**
- * Post-processor that enables observation on all Kafka producer templates and
- * consumer container factories discovered in the application context.
- */
static class KafkaObservationBeanPostProcessor implements BeanPostProcessor {
@Override
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/TracingConfig.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/TracingConfig.java
index 23652e89..d2696204 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/TracingConfig.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/tracing/TracingConfig.java
@@ -3,21 +3,6 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
-/**
- * Enables distributed tracing across all services via Spring Boot auto-configuration.
- *
- *
When {@code app.tracing.enabled=true} (default), Spring Boot auto-configures:
- *
- * Micrometer Tracing bridge to OpenTelemetry ({@code micrometer-tracing-bridge-otel})
- * OTLP HTTP span exporter to the collector configured via
- * {@code management.otlp.tracing.endpoint}
- * W3C {@code traceparent} header propagation on Feign/RestClient calls
- * Trace context propagation through Kafka message headers when observation is enabled
- * MDC population with {@code traceId} and {@code spanId} for structured logging
- *
- *
- * Set {@code app.tracing.enabled=false} in tests or environments without a collector.
- */
@Configuration
@ConditionalOnProperty(name = "app.tracing.enabled", havingValue = "true", matchIfMissing = true)
public class TracingConfig {
diff --git a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/vault/VaultConfig.java b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/vault/VaultConfig.java
index 4a72103a..8be7c3c7 100644
--- a/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/vault/VaultConfig.java
+++ b/platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/vault/VaultConfig.java
@@ -4,31 +4,6 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
-/**
- * Activates Spring Cloud Vault integration for centralised secrets management.
- *
- *
Spring Cloud Vault auto-configures a {@code VaultTemplate} and the
- * {@code spring.config.import=optional:vault://} ConfigData source whenever its
- * starter is on the classpath and Vault connectivity is configured.
- *
- *
This configuration class acts as the opt-in toggle:
- *
- * Production / staging: set {@code app.vault.enabled=true} and supply
- * {@code VAULT_TOKEN} (or use Kubernetes/AppRole auth).
- * Local dev: Vault runs in Docker Compose dev mode with a static
- * root token ({@code dev-root-token}).
- * Tests: defaults to disabled ({@code matchIfMissing = false}) so that
- * unit and integration tests never require a running Vault instance.
- *
- *
- * Secrets are resolved through the KV v2 engine at
- * {@code secret/{application-name}} and the shared context
- * {@code secret/application}. Property names in Vault map directly to
- * Spring property keys (e.g. {@code spring.datasource.password}).
- *
- * @see
- * Spring Cloud Vault Reference
- */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "app.vault.enabled", havingValue = "true", matchIfMissing = false)