Security & Compliance
Overview
The Convenient Checkout platform processes sensitive customer and financial data. Every engineer must follow strict data protection, clean code, and security best practices. These standards protect our customers, ensure regulatory readiness, and maintain the trust of merchants who integrate with our platform.
Data Protection Rules
What We Must Never Do
| Rule | Detail |
|---|---|
| Never log full card numbers | Card numbers (PAN) must never appear in logs, error messages, or database fields |
| Never log CVV/CVC | Card verification values must never be stored or logged anywhere |
| Never store raw card data | All card data is tokenized by Stripe; we only store last-four, brand, expiry |
| Never expose sensitive data in responses | API responses must only include masked/truncated card details |
| Never log full SSN | Only ssnLastFour is permissible; full SSN must never be stored or logged |
| Never log full bank account numbers | Only last-four digits may appear in logs or responses |
What We Must Always Do
| Rule | Detail |
|---|---|
| Mask in logs | If any card-adjacent or customer-sensitive data appears in logs, mask all but last 4 digits |
| Encrypt at rest | Database fields containing PII use column-level encryption |
| Encrypt in transit | All inter-service communication uses TLS 1.2+ |
| Rotate secrets | Stripe API keys and database credentials rotated per policy |
| Audit trail | Every state transition must be recorded with timestamp, actor, and reason |
| Minimize data collection | Only collect and store data that is required for the business operation |
Logging or storing full card numbers (PAN), CVV, full SSN, or full bank account numbers is a serious data protection violation. When in doubt, consult the Security team before adding any logging around payment method or customer data.
OAuth2 Scope Enforcement
Scope Matrix
| Scope | What It Allows | Who Gets It |
|---|---|---|
merchant | Create/read payments, refunds, sessions; cancel; capture | Merchant backend systems |
user | Create payments (widget-initiated), read payments | Checkout widget |
merchant-pci | Create token payments with raw card data | Certified merchant systems |
Validation Rules
- The API gateway validates the OAuth2 token before the request reaches the service.
- The
X-Merchant-Idheader must match a merchant linked to the token'sclientId. Mismatch →403 Forbidden. - The service must not re-implement token validation but may read claims for audit logging.
Input Validation & Sanitization
Defense-in-Depth Layers
| Layer | What It Validates |
|---|---|
| OpenAPI schema | Types, formats, enums, min/max, patterns, required fields |
| Controller annotations | @Valid, @NotNull, @Size, @Pattern on DTOs |
| Service layer | Cross-field rules (allocation sum, authorization flag combinations) |
| Repository layer | Database constraints (unique indexes, foreign keys, check constraints) |
Specific Rules
| Input | Validation | Rejection |
|---|---|---|
amount | Integer, min=1, max=100,000,000 (cents) | 400 if out of range |
merchantTransactionId | String, 1-50 chars | 400 if empty or too long |
email | Regex: ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$ | 400 if malformed |
ssnLastFour | Exactly 4 digits: ^\d{4}$ | 400 if malformed |
zip5 | Exactly 5 digits: ^[0-9]{5}$ | 400 if malformed |
statementDescriptorSuffix | 1-10 chars, alphanumeric + space/dot/hyphen, must contain letter | 400 if invalid |
metadata keys | 1-40 chars per key, 1-100 chars per value, max 20 entries | 400 if exceeded |
paymentAllocations | 1-2 items, each must have amount ≥ 1 | 400 if violated |
paymentCancellationReason | One of: DUPLICATE, FRAUDULENT, REQUESTED_BY_CUSTOMER, ABANDONED | 400 if invalid |
Sensitive Data in Logs
Allowed to Log
| Field | Example |
|---|---|
paymentId | 550e8400-e29b-41d4-a716-446655440000 |
merchantTransactionId | order-12345 |
merchantId | b955db5e-aef2-47de-bbb9-c80b9cc16e8f |
status | COMPLETED |
amount | 15000 |
Card lastFour | 4242 |
Card brand | visa |
Never Log
| Field | Why |
|---|---|
| Full card number (PAN) | Sensitive financial data |
| CVV / CVC | Sensitive financial data |
| Full SSN | PII — personally identifiable information |
| Full bank account number | Sensitive financial data / PII |
| Bank routing number | PII |
| OAuth token value | Security — could be replayed |
| Stripe secret API key | Security — grants full access |
Log Sanitization
User-controlled strings must be sanitized before logging using LogSanitizationUtil.sanitizeForLog(). This strips \n/\r characters that could be used to forge or inject fake log entries.
// Correct — sanitize all user-supplied strings before logging
log.info("Processing refund for merchantTransactionId: {}",
LogSanitizationUtil.sanitizeForLog(request.getMerchantTransactionId()));
log.error("Payment failed - PaymentId: {}, Message: {}",
LogSanitizationUtil.sanitizeForLog(
ex.getPaymentResponse() != null ? ex.getPaymentResponse().getId() : null),
ex.getMessage(), ex);
// Incorrect — raw user input in log statement
log.info("MerchantTxId: {}", request.getMerchantTransactionId()); // ❌
LogSanitizationUtil currently lives in wallet-payment-service and should be migrated to wallet-event-commons so all services can use it consistently. See improvement roadmap.
Merchant Scoping in Code
Every resource fetch must verify merchantId ownership before returning data. Return 404 (not 403) to prevent resource existence leakage:
// Correct — ownership check returns 404 on mismatch
private Mono<Transaction> validateMerchantOwnership(UUID merchantId, Transaction transaction) {
if (!transaction.getMerchantId().equals(merchantId)) {
log.error("Requested merchantID/Payment MerchantId do not match. "
+ "requestedMerchantId={}, resourceMerchantId={}",
merchantId, transaction.getMerchantId());
return Mono.error(new PaymentNotFoundException("Payment information not found"));
}
return Mono.just(transaction);
}
Dependency Management & Vulnerabilities
| Practice | Requirement |
|---|---|
| Dependency scanning | All services must run automated CVE scanning in CI pipelines |
| Upgrade cadence | Critical/High CVEs patched within 7 days; Medium within 30 days |
| No end-of-life dependencies | Dependencies past EOL must be upgraded or replaced |
| Transitive dependencies | Monitor transitive dependencies for vulnerabilities, not just direct |
| Lock files | Always commit lock files (pom.xml dependency versions pinned) |
| Secret detection | Use pre-commit hooks to prevent committing secrets, API keys, or credentials |
Clean Code & Best Practices
| Practice | Requirement |
|---|---|
| Code coverage | Minimum 80% unit test coverage on new code; strive for higher on critical paths |
| Code review | All PRs require at least 1 reviewer; commons library PRs require 2 |
| Static analysis | Linting and static analysis must pass in CI before merge |
| No dead code | Remove unused imports, methods, classes — don't comment them out |
| Single responsibility | Each class/method has one clear purpose |
| Meaningful names | Variables, methods, and classes named for intent, not abbreviation |
| Error handling | No swallowed exceptions; every catch block must log or propagate meaningfully |
Audit Trail Requirements
Every state transition must produce an audit record containing:
| Field | Description |
|---|---|
paymentId | The resource being modified |
previousStatus | Status before transition |
newStatus | Status after transition |
actor | Who initiated (merchantId, system, stripe-webhook) |
reason | Why (e.g., REQUESTED_BY_CUSTOMER, stripe_charge_succeeded) |
timestamp | UTC ISO 8601 |
requestId | Correlation ID for tracing |