Commons Library — wallet-event-commons
Purpose
The wallet-event-commons library is the shared contract between all Convenient Checkout microservices. It contains DTOs, enums, event models, validation rules, and helper utilities that are consumed by every service in the ecosystem. Changes to this library propagate across the entire platform.
Every service depends on wallet-event-commons. A breaking change here breaks all services. Treat every PR to this library as a platform-wide change. See Change Management Rules below.
Repository
| Attribute | Value |
|---|---|
| Repo | wallet-event-commons |
| Language | Java |
| Build tool | Maven (pom.xml) |
| Package root | com.optum.wallet.common |
| Consumed by | All wallet-* services |
Package Structure
src/main/java/com/optum/wallet/common/
├── component/ ← Abstract base components (e.g., AbstractPaymentMethodDetails)
├── constants/ ← Static constants (MerchantEventTypes, PaymentMethodTypes, ErrorMessages)
├── dto/ ← Data Transfer Objects
│ ├── manufacturerCard/ ← Manufacturer/healthcare card DTOs
│ ├── merchant/ ← MerchantRequest, MerchantResponse
│ ├── telephonic/ ← Telephonic entry DTOs
│ └── ValidateResult.java
├── enums/ ← Shared enums (ALL cross-service enums live here)
│ ├── card/ ← CardBrand, CardChannel, CardFunding, CardNetwork, CardStatus
│ ├── dispute/ ← DisputeReason, DisputeStatus
│ ├── payment/ ← PaymentStatus, PaymentMethodType, PaymentCancellationReason, PaymentType, PaymentVendor
│ ├── refund/ ← RefundStatus, RefundReason, MerchantRefundStatus, MerchantRefundGroupStatus
│ ├── report/ ← ReportType, ReportStatus
│ ├── vendor/ ← Vendor, VendorChargeStatus, VendorPaymentMethodType
│ └── *.java ← Channel, ErrorGroup, ConsentCollectionType, etc.
├── event/ ← v1 event models (legacy — being superseded by events/v2/)
│ ├── builder/ ← EventBuilder
│ ├── dispute/dto/ ← Dispute event DTOs
│ ├── merchant/ ← MerchantEvent, MerchantPayload, MerchantSettings
│ ├── migration/dto/ ← Migration event DTOs
│ ├── notification/ ← NotificationEvent
│ ├── payment/dto/ ← v1 payment DTOs (Payment, ChargeDetails, Consent, etc.)
│ ├── paymentmethod/dto/ ← v1 payment method DTOs
│ ├── refund/dto/ ← v1 refund DTOs
│ ├── report/dto/ ← Report DTOs
│ ├── shared/ ← Shared event DTOs (Agent, EventContext, PhoneNumber, Vendor, error/)
│ └── *.java ← Event, EventStatus, ApiResponseStatus
├── events/ ← Top-level events (v1 + v2)
│ ├── v2/dto/ ← ★ V2 shared models
│ │ ├── error/ ← Problem, Remediation, RemediationType
│ │ ├── payment/ ← SplitPayment, PaymentAllocation, PaymentMethod, Card, USBankAccountDetails
│ │ └── refund/ ← MerchantRefundEventV2, RefundResponse, RefundAllocation, error/
│ └── *.java ← Event, EventBuilder, EventContext, EventStatus (v1 wrappers)
├── helper/ ← Helper utilities
│ └── manufacturerCard/ ← AgentHelper, CardHelper, MerchantConfigHelper, etc.
├── pojo/ ← Plain objects shared across domains
├── rules/ ← Business rule implementations
│ ├── agent/ ← AgentRule
│ ├── card/ ← DefaultCardRule, MFCardTypeRule
│ ├── medication/ ← Medication validation rules
│ └── merchantConfig/ ← MerchantConfigRule
├── util/ ← Utility classes (PaymentMethodUtils)
└── validation/ ← Validators (MedicationValidator, TestValidator)
Key Shared Enums
These enums define the canonical vocabulary across all services. Never define local alternatives.
ErrorGroup — Error Classification
public enum ErrorGroup {
CUSTOMER_ERROR,
PAYMENT_METHOD_ERROR,
PAYMENT_ERROR,
INVALID_REQUEST,
INTERNAL_ERROR,
WARNING,
NOT_FOUND,
UNAUTHORIZED,
INVALID_RESOURCE,
RETRIES_EXHAUSTED,
NOTIFICATION_ERROR,
SESSION_TIMEOUT,
SESSION_CANCELED,
FORBIDDEN,
UNPROCESSABLE_ENTITY,
PAYMENT_METHOD_MIGRATION_FAILED,
REFUND_ERROR
}
PaymentStatus — Payment Lifecycle
public enum PaymentStatus {
// In Progress
INITIATED, PROCESSING, PENDING,
PENDING_FOR_CUSTOMER_CREATION, PENDING_FOR_PAYMENT_METHOD_CREATION,
CANCEL_FAILED, PROCESSING_DEDUP_CHECK,
// Retriable
PAYMENT_METHOD_FAILED,
// Initialized
CAPTURE_INITIALIZED, CANCEL_INITIALIZED, CONFIRMATION_INITIALIZED,
// 3DS
AUTH_REQUIRED, CONFIRMATION_REQUIRED,
// Terminal — Success
ACCEPTED, AUTHORIZED, COMPLETED, PARTIAL_SUCCESS,
// Terminal — Failure
CANCELED, FAILED, NOT_INITIATED, ROLLED_BACK;
}
Other Critical Enums
| Enum | Package | Used By |
|---|---|---|
PaymentMethodType | enums.payment | Payments, Customers, Wallet |
PaymentCancellationReason | enums.payment | Payments |
PaymentType | enums.payment | Payments (SALE, PRE_AUTH) |
PaymentVendor | enums.payment | Payments, Adapter |
RefundStatus | enums.refund | Refunds |
MerchantRefundGroupStatus | enums.refund | Refund webhooks |
CardBrand | enums.card | Payments, Wallet, Reporting |
DisputeStatus | enums.dispute | Disputes |
ReportType / ReportStatus | enums.report | Reporting |
V2 Shared DTOs
The events.v2.dto package contains the v2 data models used across services:
Problem — Standard Error Response
Based on RFC 7807, used by all v2 API error responses:
Problem.builder()
.title(ErrorGroup.PAYMENT_ERROR)
.status(422)
.detail("Payment processing failed for all records.")
.build();
Factory methods: Problem.build(detail), Problem.buildBadRequest(detail), Problem.buildNotFound(detail), Problem.from(ErrorResponse).
SplitPayment — v2 Payment Model
The canonical v2 payment DTO consumed by the payment service and webhook dispatcher:
| Field | Type | Description |
|---|---|---|
id | UUID | Platform payment ID |
amount | Long | Total amount in cents |
authorizedAmount | Long | Total authorized |
capturedAmount | Long | Total captured |
merchantTransactionId | String | Idempotency key |
status | PaymentStatus | Current state |
paymentAllocations | List<PaymentAllocation> | 1-2 allocations |
customer | Customer | Customer details |
metadata | Map<String, String> | Custom key-value pairs |
consent | Consent | ACH consent details |
error | ErrorResponse | Error details (if failed) |
Remediation — Rollback Context
Used in split-tender failures to describe what corrective action was taken:
Remediation.builder()
.type(RemediationType.CANCEL)
.message("Automatic cancellation due to sibling allocation failure")
.build();
Versioning & Deployment Strategy
Maven Coordinates
The library is published under:
<dependency>
<groupId>com.optum.ccg</groupId>
<artifactId>wallet-commons</artifactId>
<version>2.0.13</version>
</dependency>
Each consuming service pins the version through a property in its pom.xml:
<properties>
<wallet-commons.version>2.0.13</wallet-commons.version>
</properties>
<dependencies>
<dependency>
<groupId>com.optum.ccg</groupId>
<artifactId>wallet-commons</artifactId>
<version>${wallet-commons.version}</version>
</dependency>
</dependencies>
To check which version a service is currently running, look at the property value in its pom.xml or run:
./mvnw dependency:tree | grep wallet-commons
Why Staying Current Matters
wallet-commons is the contract layer between every service in the platform. When a new enum value is added — for example, a new PaymentStatus state — every service that deserialises payment events must be on a version that knows about that value. A service running an old version will throw a JSON deserialisation error when it receives an event containing the unknown value, causing silent failures or exceptions in production.
In practice:
- A new
PaymentStatusvalue is added inwallet-commons2.1.0 wallet-stripe-adapter-serviceupgrades and starts publishing events with that new statuswallet-payment-serviceis still on2.0.13— it crashes deserialising those events
This is why upgrading wallet-commons promptly after a new release is not optional — it is a reliability requirement.
Semantic Versioning Rules
wallet-commons follows semantic versioning (MAJOR.MINOR.PATCH):
| Change Type | Version Bump | Who Must Act | Examples |
|---|---|---|---|
| PATCH | 2.0.13 → 2.0.14 | Optional upgrade; recommended | Javadoc fix, comment update, test addition — no API surface change |
| MINOR | 2.0.x → 2.1.0 | All services should upgrade; safe at own pace | New enum value, new optional DTO field, new class, new package |
| MAJOR | 2.x.x → 3.0.0 | All services must upgrade and coordinate | Removing a class or enum value, renaming a field, changing a field type |
What a Breaking Change Looks Like — And Why It Is Dangerous
Understanding breaking changes is critical for engineers who modify wallet-commons. The following are real examples of changes that look harmless but break every consuming service.
Example 1 — Renaming an enum value
// BEFORE — in wallet-commons 2.0.x
public enum PaymentStatus {
COMPLETED, FAILED, CANCELLED
}
// AFTER — engineer renames for consistency (BREAKING CHANGE)
public enum PaymentStatus {
COMPLETED, FAILED, CANCELED // ← renamed from CANCELLED to CANCELED
}
Impact: Every service that has CANCELLED in its database, in serialised JSON events, or in switch statements will fail at runtime. Jackson cannot deserialise "CANCELLED" into the new enum. This is a MAJOR version bump and requires a coordinated migration across all services.
Safe alternative: Add the new value and deprecate the old one — never remove or rename:
public enum PaymentStatus {
COMPLETED,
FAILED,
CANCELLED, // keep existing value
/** @deprecated Use {@link #CANCELLED} — spelling variant retained for consistency */
@Deprecated
CANCELED // add alias if truly needed
}
Example 2 — Removing an optional field from a DTO
// BEFORE — wallet-commons 2.0.x
public class SplitPayment {
private String description; // optional field
}
// AFTER — engineer removes it thinking it's unused (BREAKING CHANGE)
public class SplitPayment {
// description removed
}
Impact: Any service that serialises a SplitPayment with a non-null description, or any consumer that reads the field, will fail to compile or produce unexpected nulls at runtime.
Safe alternative: Mark the field @Deprecated and stop populating it. Remove it only in the next MAJOR bump after confirming no service reads or writes it.
Example 3 — Changing a field type
// BEFORE — wallet-commons 2.0.x
public class PaymentAllocation {
private Long amount;
}
// AFTER — changing type (BREAKING CHANGE)
public class PaymentAllocation {
private BigDecimal amount; // ← type changed
}
Impact: Compile failure in every service. JSON deserialisation may also fail if the wire format changes. This is always a MAJOR bump and requires code changes in every consuming service before deployment.
Safe Change Patterns — What You Can Do Without Risk
| Change | Safe? | Notes |
|---|---|---|
| Add a new enum value | ✅ MINOR | Consumers that don't use it are unaffected; consumers that deserialise events must upgrade |
| Add a new optional field to a DTO | ✅ MINOR | Jackson ignores unknown fields by default — existing consumers continue working |
| Add a new class or package | ✅ MINOR | No impact on existing code |
Add a new @Deprecated annotation | ✅ PATCH | Compiler warning only — no runtime impact |
| Fix a Javadoc comment | ✅ PATCH | Zero risk |
| Rename an enum value | ❌ MAJOR | Runtime deserialisation failures |
| Remove a field | ❌ MAJOR | Compile failures and potential NPEs |
| Remove an enum value | ❌ MAJOR | Runtime deserialisation failures and switch exhaustion |
| Change a field type | ❌ MAJOR | Compile failures |
| Change a field name | ❌ MAJOR | JSON deserialisation failures unless @JsonAlias added |
V1/V2 Coexistence in the Library
The library currently ships both V1 and V2 packages in the same artifact. This is an intentional transitional state while services migrate from V1 to V2.
com.optum.wallet.common/
├── event/ ← V1 DTOs (active but in deprecation — do not use in new V2 code)
│ ├── payment/dto/
│ ├── refund/dto/
│ └── shared/
└── events/v2/ ← V2 DTOs (current — use in all new code)
└── dto/
├── error/ Problem, RefundApiErrorResponse
├── payment/ V2 payment DTOs
└── refund/ V2 refund DTOs, error envelopes
| Rule | Detail |
|---|---|
| New V2 code | Import from com.optum.wallet.common.events.v2.* only |
| Existing V1 code | May continue using com.optum.wallet.common.event.* until explicitly migrated |
| No mixing | Do not import from both event/ and events/v2/ in the same class |
| New additions | All new shared types go into events/v2/ — never add to the V1 event/ package |
| Enums | PaymentStatus, RefundReason, ErrorGroup etc. are shared and version-agnostic — always import from com.optum.wallet.common.enums.* |
Release Process
1. Branch from main in wallet-event-commons
↓
2. Make additive change (new enum value, new class, etc.)
↓
3. Bump pom.xml version (MAJOR / MINOR / PATCH as appropriate)
↓
4. PR review — minimum 2 backend engineers; involve engineering manager for MAJOR bumps
↓
5. Merge and publish to internal Maven repository
↓
6. Open upgrade PRs in every consuming service
↓
7. Each service upgrades, compiles, and runs full test suite
↓
8. Deploy services — consumers of new values must deploy before publishers
For MAJOR bumps: services must be upgraded and deployed before any service starts emitting events or responses that use the new/changed API surface.
Worked Example: Adding a New Enum Value Safely
Suppose a new payment status DISPUTE_OPEN needs to be added.
Step 1 — Add the value to wallet-commons:
// In wallet-commons — PaymentStatus.java
public enum PaymentStatus {
// ... existing values ...
COMPLETED,
FAILED,
CANCELLED,
DISPUTE_OPEN // ← new value added
}
Step 2 — Bump the version (MINOR):
<!-- wallet-commons pom.xml -->
<version>2.1.0</version>
Step 3 — Publish to the internal Maven repository.
Step 4 — Upgrade all consuming services before any service starts emitting the new value:
<!-- In each service pom.xml -->
<wallet-commons.version>2.1.0</wallet-commons.version>
Step 5 — Handle the new value in switch statements. Any switch or if/else chain on PaymentStatus must be updated to handle DISPUTE_OPEN or compile warnings will surface:
// In wallet-payment-service — TransactionStateMachine.java
return switch (status) {
case COMPLETED -> handleCompleted();
case FAILED -> handleFailed();
case CANCELLED -> handleCancelled();
case DISPUTE_OPEN -> handleDisputeOpen(); // ← add handler
default -> throw new IllegalArgumentException("Unhandled status: " + status);
};
Step 6 — Run the full test suite in each service before merging the upgrade PRs.
The service that starts publishing events with DISPUTE_OPEN must deploy after all services that consume those events have upgraded to 2.1.0. Deploying the publisher first will cause deserialisation failures in consumers still on 2.0.x.
Upgrading a Service: Step-by-Step
For engineers picking up a wallet-commons upgrade PR:
# 1. Bump the version in pom.xml
# Change: <wallet-commons.version>2.0.13</wallet-commons.version>
# To: <wallet-commons.version>2.1.0</wallet-commons.version>
# 2. Compile and check for errors
./mvnw compile
# 3. Check for compiler warnings about deprecated types
./mvnw compile 2>&1 | grep -i "deprecated"
# 4. If new enum values were added, search for exhaustive switch statements
grep -r "PaymentStatus\|RefundStatus" src/ --include="*.java" \
| grep -i "switch\|case"
# 5. Run the full test suite
./mvnw test
# 6. If integration tests exist, run them too
./mvnw verify
If the compile step fails, cross-reference the wallet-commons changelog (in its CHANGELOG.md or release notes) to understand what changed and what code updates are required.
Deprecation Lifecycle
When a type needs to be removed, it goes through a mandatory deprecation window so consuming services have time to migrate:
Phase 1 — Mark deprecated (MINOR bump, e.g. 2.1.0)
Add @Deprecated(since = "2.1.0", forRemoval = true)
Add Javadoc pointing to the replacement
Services see compiler warnings — create migration tickets
Phase 2 — Migrate consuming services (within 2 sprints)
Each service replaces deprecated usage with the new type
Verified by checking for zero deprecation warnings on compile
Phase 3 — Remove (MAJOR bump, e.g. 3.0.0)
Deprecated type removed from the library
All services must already be migrated before this is published
Example of a properly deprecated class:
/**
* @deprecated Use {@link com.optum.wallet.common.events.v2.dto.error.Problem} instead.
* Will be removed in wallet-commons 3.0.0.
*/
@Deprecated(since = "2.1.0", forRemoval = true)
public class ErrorResponse implements Serializable {
private String code;
private String message;
}
Working With the Library Locally
To test an uncommitted wallet-commons change in a consuming service before publishing:
# In wallet-event-commons — install to local ~/.m2
./mvnw install -DskipTests
# In the consuming service — temporarily point to the local SNAPSHOT
# Change in pom.xml:
<wallet-commons.version>2.0.14-SNAPSHOT</wallet-commons.version>
# Run the service locally and verify the change works as expected
./mvnw spring-boot:run
# IMPORTANT — revert pom.xml before committing
# Never commit a SNAPSHOT version to any service
A SNAPSHOT dependency in a committed pom.xml means the build is non-reproducible — CI will pull whatever snapshot exists at build time, which may differ between runs. This breaks deterministic deployments and makes rollbacks unreliable.
Change Management Rules
| Rule | Detail |
|---|---|
| No breaking changes | Never remove or rename an enum value, field, or class that any service uses |
| Additive only | Add new enum values, new optional fields, new classes |
| Version bump | Every change requires a Maven version bump in pom.xml |
| Downstream update | After publishing, update the dependency version in all consuming services |
| Deprecation | Mark deprecated items with @Deprecated and add Javadoc explaining the replacement. Minimum 2 sprint deprecation window before removal. |
| Testing | All new enums and DTOs must have unit tests in the commons library itself |
| PR review | Commons PRs require review from at least 2 backend engineers; given the platform-wide impact, involve the engineering manager or tech lead when making structural changes |
When to Add to Commons vs. Keep in Service
| Put in Commons | Keep in Service |
|---|---|
| Enum used by ≥ 2 services | Enum only meaningful in one service |
| DTO that crosses service boundaries (events, webhooks) | Internal service DTOs (DB entities, internal mappers) |
| Validation rules that apply to multiple domains | Domain-specific business logic |
Shared error structures (Problem, ErrorGroup) | Service-specific error messages |
Current State vs. Target State
| Aspect | Current State | Target State | Migration Priority |
|---|---|---|---|
| Package organization | Mix of event/, events/, pojo/ with overlapping classes | Unified v2/ package with clear domain separation | P1 |
| v1/v2 coexistence | v1 DTOs in event/, v2 in events/v2/ — both active | Deprecate v1 DTOs; all services consume v2 only | P2 |
| Enum duplication | Some enums exist in both enums/ and event/shared/ | Single canonical location in enums/ | P1 |
| Error model | ErrorResponse (v1) and Problem (v2) coexist | All services use Problem for v2 responses | P2 |
| Test coverage | Tests exist but coverage varies | 100% coverage on all enums, DTOs, and rules | P2 |
| Javadoc | Partial documentation | All public classes and enums fully documented | P3 |