Skip to content

Commit adf5d97

Browse files
feat(sandbox): sandbox E2E fixes — first real payment completed (STA-243)
16 bugs found and fixed during real sandbox E2E testing: - Stripe: remove confirm=true (ACH needs customer confirmation) - Modulr: raw auth key, SCAN destination, SEPA_CREDIT_TRANSFER scheme, reference truncation to 18 chars, FPS null scheme - Circle: round amount to 2 decimal places - DevCustodyAdapter: BigDecimal rounding for USDC minor units - OffRampActivityImpl: dynamic payment rail (GBP=FPS, EUR=SEPA) - RateRefreshJob: add USD<->GBP corridors - V6 migration: seed USD-GBP liquidity pool - GlobalExceptionHandler: log full stack trace for CustodySigningException - FallbackAdaptersConfig: add @ConditionalOnMissingBean (merchant-onboarding) - Sandbox YMLs: fix DB credentials (dev/dev), add external-api logging - docker-compose.dev.yml: fix Jaeger 1.76.0, Temporal postgres12 driver - docker-compose.sandbox.yml: full 12-container stack for E2E testing - Makefile: sandbox-up/down/build/test/status/tunnel/logs targets First successful payment: $5.00 USD -> 3.73 GBP through all 7 services with real Persona, Frankfurter, Stripe, Alchemy (Base Sepolia), Circle, and Modulr sandbox APIs. Closes #243 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b193e88 commit adf5d97

21 files changed

Lines changed: 557 additions & 34 deletions

File tree

Makefile

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
run-% db-reset db-psql topics \
55
deps outdated \
66
assemble sonar fresh ci \
7-
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e
7+
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e \
8+
sandbox-up sandbox-down sandbox-status sandbox-run-% sandbox-test \
9+
sandbox-tunnel sandbox-env-check
810

911
# ─────────────────────────────────────────────
1012
# Variables
@@ -170,3 +172,83 @@ e2e-test: ## Run Phase 3 E2E tests (stack must be running)
170172
PHASE3_TESTS_ENABLED=true $(GRADLE) :phase3-integration-tests:test --rerun
171173

172174
e2e: e2e-build e2e-up e2e-test ## Build images, start stack, run E2E tests
175+
176+
# ─────────────────────────────────────────────
177+
# Sandbox Testing (real external APIs)
178+
# ─────────────────────────────────────────────
179+
# Requires: .env.sandbox with real API keys (copy from .env.sandbox.template)
180+
# Infra: Docker Compose for Postgres, Kafka, Redis, Temporal
181+
# Tunnel: cloudflared for Stripe webhooks
182+
183+
SANDBOX_SERVICES := compliance-travel-rule fx-liquidity-engine fiat-on-ramp \
184+
blockchain-custody fiat-off-ramp ledger-accounting payment-orchestrator
185+
186+
sandbox-env-check: ## Verify .env.sandbox exists and key vars are set
187+
@test -f .env.sandbox || (echo "ERROR: .env.sandbox not found. Run: cp .env.sandbox.template .env.sandbox" && exit 1)
188+
@. ./.env.sandbox && test -n "$$STRIPE_TEST_SECRET_KEY" || (echo "ERROR: STRIPE_TEST_SECRET_KEY not set in .env.sandbox" && exit 1)
189+
@. ./.env.sandbox && test -n "$$ALCHEMY_API_KEY" || (echo "ERROR: ALCHEMY_API_KEY not set in .env.sandbox" && exit 1)
190+
@echo "✓ .env.sandbox loaded — keys present"
191+
192+
SANDBOX_COMPOSE := docker compose --env-file .env.sandbox -f docker-compose.sandbox.yml
193+
194+
sandbox-up: sandbox-env-check sandbox-build ## Build images, start infra + all 7 services with real sandbox APIs
195+
$(SANDBOX_COMPOSE) up -d
196+
@echo ""
197+
@echo "✓ Sandbox stack launching (7 services + infra)"
198+
@echo " Run: make sandbox-status to check health"
199+
@echo " Run: make sandbox-logs to tail all logs"
200+
@echo " Run: make sandbox-test to run adapter tests"
201+
202+
sandbox-down: ## Stop all sandbox containers
203+
$(SANDBOX_COMPOSE) down
204+
@echo "✓ Sandbox stopped"
205+
206+
sandbox-destroy: ## Stop sandbox and remove volumes (full reset)
207+
$(SANDBOX_COMPOSE) down -v
208+
@echo "✓ Sandbox destroyed"
209+
210+
sandbox-build: ## Build all service Docker images for sandbox
211+
$(GRADLE) $(foreach s,$(SERVICES),:$(s):$(s):jibDockerBuild) --parallel
212+
@echo "✓ All Docker images built"
213+
214+
sandbox-logs: ## Tail all sandbox service logs
215+
$(SANDBOX_COMPOSE) logs -f --tail=50
216+
217+
sandbox-tunnel: ## Start cloudflared tunnel for Stripe webhooks (runs in foreground)
218+
@echo "Starting Cloudflare tunnel → localhost:8085 (S3 Fiat On-Ramp)"
219+
@echo "Copy the https://xxx.trycloudflare.com URL to Stripe webhook dashboard"
220+
@echo "Endpoint path: /on-ramp/internal/webhooks/psp/stripe"
221+
@echo ""
222+
cloudflared tunnel --url http://localhost:8085
223+
224+
sandbox-run-%: sandbox-env-check ## Run a service in sandbox mode (e.g., make sandbox-run-fiat-on-ramp)
225+
set -a && . ./.env.sandbox && set +a && \
226+
$(GRADLE) :$*:$*:bootRun --args='--spring.profiles.active=sandbox'
227+
228+
sandbox-test: sandbox-env-check ## Run all sandbox adapter tests against real APIs
229+
set -a && . ./.env.sandbox && set +a && \
230+
$(GRADLE) \
231+
:fiat-on-ramp:fiat-on-ramp:test --tests '*StripeAdapterSandboxTest*' \
232+
:blockchain-custody:blockchain-custody:test --tests '*EvmRpcAdapterSandboxTest*' --tests '*SolanaRpcAdapterSandboxTest*' --tests '*FireblocksCustodyAdapterSandboxTest*' \
233+
:fiat-off-ramp:fiat-off-ramp:test --tests '*CircleRedemptionAdapterSandboxTest*' --tests '*ModulrPayoutAdapterSandboxTest*' \
234+
:fx-liquidity-engine:fx-liquidity-engine:test --tests '*FrankfurterRateAdapterSandboxTest*' \
235+
:merchant-onboarding:merchant-onboarding:test --tests '*CompaniesHouseAdapterSandboxTest*'
236+
237+
sandbox-test-%: sandbox-env-check ## Run sandbox tests for a service (e.g., make sandbox-test-fiat-on-ramp)
238+
set -a && . ./.env.sandbox && set +a && \
239+
$(GRADLE) :$*:$*:test --tests '*SandboxTest*'
240+
241+
sandbox-status: ## Show infra status + sandbox env summary
242+
@$(COMPOSE) ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || $(COMPOSE) ps
243+
@echo ""
244+
@echo "--- Sandbox API Keys ---"
245+
@test -f .env.sandbox && . ./.env.sandbox && \
246+
echo "Stripe: $${STRIPE_TEST_SECRET_KEY:+set}" && \
247+
echo "Alchemy: $${ALCHEMY_API_KEY:+set}" && \
248+
echo "Persona: $${PERSONA_SANDBOX_API_KEY:+set}" && \
249+
echo "Companies House: $${COMPANIES_HOUSE_API_KEY:+set}" && \
250+
echo "Circle: $${CIRCLE_SANDBOX_API_KEY:+set}" && \
251+
echo "Modulr: $${MODULR_SANDBOX_API_KEY:+set}" && \
252+
echo "Fireblocks: $${FIREBLOCKS_SANDBOX_API_KEY:+set}" && \
253+
echo "JWT Key: $${JWT_PRIVATE_KEY_BASE64:+set}" \
254+
|| echo "No .env.sandbox found"

api-gateway-iam/api-gateway-iam/src/main/resources/application-sandbox.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ api-gateway-iam:
2727
spring:
2828
datasource:
2929
url: jdbc:postgresql://localhost:5432/s10_api_gateway_iam
30-
username: sp_user
31-
password: sp_pass
30+
username: dev
31+
password: dev
3232

3333
cloud:
3434
vault:

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/application/controller/GlobalExceptionHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public ApiError handleWalletNotFound(WalletNotFoundException ex) {
6262
@ResponseStatus(INTERNAL_SERVER_ERROR)
6363
@ExceptionHandler(CustodySigningException.class)
6464
public ApiError handleCustodySigning(CustodySigningException ex) {
65-
log.error("Custody signing error: {}", ex.getClass().getSimpleName());
65+
log.error("Custody signing error: {}", ex.getMessage(), ex);
6666
return ApiError.of(CustodySigningException.ERROR_CODE,
6767
INTERNAL_SERVER_ERROR.getReasonPhrase(), "Custody signing failed");
6868
}

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import tools.jackson.databind.json.JsonMapper;
2121

2222
import java.math.BigInteger;
23+
import java.math.RoundingMode;
2324
import java.net.http.HttpClient;
2425
import java.net.http.HttpClient.Version;
2526
import java.time.Duration;
@@ -103,7 +104,8 @@ SignResult signAndSubmitEvm(SignRequest request) {
103104
var usdcContract = chainConfig.usdcContract();
104105
var amountMinorUnits = request.amount()
105106
.movePointRight(USDC_DECIMALS)
106-
.toBigIntegerExact();
107+
.setScale(0, RoundingMode.DOWN)
108+
.toBigInteger();
107109
var data = encodeErc20Transfer(request.toAddress(), amountMinorUnits);
108110

109111
var nonce = request.nonce() != null ? BigInteger.valueOf(request.nonce()) : BigInteger.ZERO;

blockchain-custody/blockchain-custody/src/main/resources/application-sandbox.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
spring:
77
datasource:
88
url: jdbc:postgresql://localhost:5432/s4_blockchain_custody
9-
username: sp_user
10-
password: sp_pass
9+
username: dev
10+
password: dev
1111

1212
cloud:
1313
vault:

compliance-travel-rule/compliance-travel-rule/src/main/resources/application-sandbox.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ app:
3535
spring:
3636
datasource:
3737
url: jdbc:postgresql://localhost:5432/s2_compliance
38-
username: sp_user
39-
password: sp_pass
38+
username: dev
39+
password: dev
4040

4141
cloud:
4242
vault:

docker-compose.dev.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,12 @@ services:
148148
ports:
149149
- "7233:7233"
150150
environment:
151-
DB: postgresql
151+
DB: postgres12
152152
DB_PORT: 5432
153153
POSTGRES_USER: dev
154154
POSTGRES_PWD: dev
155155
POSTGRES_SEEDS: postgres
156-
DYNAMIC_CONFIG_FILE_PATH: /etc/temporal/config/dynamicconfig/development-sql.yaml
156+
SKIP_DYNAMIC_CONFIG_SETUP: true
157157
depends_on:
158158
postgres:
159159
condition: service_healthy
@@ -241,7 +241,7 @@ services:
241241
# Jaeger — distributed trace visualization (STA-221)
242242
# ─────────────────────────────────────────────
243243
jaeger:
244-
image: jaegertracing/all-in-one:1.67
244+
image: jaegertracing/all-in-one:1.76.0
245245
container_name: sp-jaeger
246246
ports:
247247
- "16686:16686" # Jaeger UI

0 commit comments

Comments
 (0)