Customer Creation in Payment
π InternalInternal information β not visible in the public (merchant) site.
Overviewβ
V2 supports payment requests that include a new customer β one that does not yet exist in the system at the time the payment is initiated. When this happens, the transaction is automatically paused in PENDING_FOR_CUSTOMER_CREATION, and processing resumes once the customer is successfully provisioned.
This capability provides full parity with V1 behavior while extending it to both single-payment and split-tender flows through the V2 transaction pipeline.
- Customer Creation Process β full customer provisioning flow in wallet-customer-service
- Payment Lifecycle States β where
PENDING_FOR_CUSTOMER_CREATIONfits in the state machine - Single Payment Flow
- Split-Tender Payment Flow
- Customer creation is supported only when a valid
customerobject is provided in the payment request. - Guest payments do not create customers and are not affected by this flow.
- The transaction is paused at the
CC_TRANSACTIONlevel β no childCC_PAYMENTrows are created until the customer is provisioned. - Customer creation failure is terminal: the transaction transitions to
FAILEDand no retries are attempted automatically.
When This Flow Appliesβ
| Condition | Behavior |
|---|---|
customer object present, customer exists | Normal payment flow β no pause |
customer object present, customer does not exist | Transaction enters PENDING_FOR_CUSTOMER_CREATION; resumes on provisioning |
customer object absent (non-guest flow) | Request rejected with 400 INVALID_REQUEST |
Guest payment (paymentFlowType: GUEST_PAYMENT) | No customer lookup; customer object not required |
Sequence Diagramβ
State Transitionsβ
Once a transaction enters FAILED due to customer creation failure, no automatic retry is attempted. The merchant must submit a new payment request.
API Requestβ
For complete field definitions and constraints, see the Create Payment API Spec.
A payment request triggers customer creation when the customer object is provided and the customer cannot be found in the system. The customer object must include at least one of:
hsidenterpriseIdentifiermetadata(key-value pairs used as merchant-specific identifiers)
Sample Request β New Customer (Single Payment)
curl -X POST "https://api-stg.uhg.com/api/financial/commerce/nonprodcheckout/v2/payments" \
-H "Authorization: Bearer <token>" \
-H "X-Merchant-Id: b955db5e-aef2-47de-bbb9-c80b9cc16e8f" \
-H "X-Upstream-Env: dev" \
-H "Content-Type: application/json" \
-d '{
"merchantTransactionId": "order-20260414-001",
"amount": 15000,
"currencyCode": "USD",
"authorizeCard": false,
"partialAuthorization": false,
"description": "Payment for new member enrollment",
"customer": {
"hsid": "b313c1d1-b5b6-4ec7-8b5a-3a9cf7755060",
"firstName": "Jane",
"lastName": "Smith",
"email": "jane.smith@example.com",
"dateOfBirth": "1985-06-15"
},
"paymentAllocations": [
{
"amount": 15000,
"paymentMethod": {
"type": "CARD",
"sourceProvider": {
"name": "STRIPE"
},
"paymentMethodDetails": {
"number": "4242424242424242",
"expMonth": 12,
"expYear": 2028,
"cvc": "123"
}
}
}
],
"metadata": {
"orderId": "enrollment-67890",
"channel": "web"
}
}'
Sample Request β New Customer (Split-Tender)
curl -X POST "https://api-stg.uhg.com/api/financial/commerce/nonprodcheckout/v2/payments" \
-H "Authorization: Bearer <token>" \
-H "X-Merchant-Id: b955db5e-aef2-47de-bbb9-c80b9cc16e8f" \
-H "X-Upstream-Env: dev" \
-H "Content-Type: application/json" \
-d '{
"merchantTransactionId": "order-20260414-002",
"amount": 25000,
"currencyCode": "USD",
"authorizeCard": false,
"partialAuthorization": false,
"customer": {
"enterpriseIdentifier": "987654321",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
},
"paymentAllocations": [
{
"amount": 15000,
"paymentMethod": {
"type": "CARD",
"sourceProvider": { "name": "STRIPE" },
"paymentMethodDetails": {
"number": "4242424242424242",
"expMonth": 12,
"expYear": 2028,
"cvc": "123"
}
}
},
{
"amount": 10000,
"paymentMethod": {
"type": "CARD",
"sourceProvider": { "name": "STRIPE" },
"paymentMethodDetails": {
"number": "5555555555554444",
"expMonth": 10,
"expYear": 2027,
"cvc": "456"
}
}
}
]
}'
API Responseβ
202 Accepted β Transaction Paused for Customer Creationβ
The service returns 202 immediately. The transaction status is PENDING_FOR_CUSTOMER_CREATION.
{
"url": "https://api-stg.uhg.com/api/financial/commerce/nonprodcheckout/v2/payments/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"merchantTransactionId": "order-20260414-001",
"amount": 15000,
"currencyCode": "USD",
"status": "PENDING_FOR_CUSTOMER_CREATION",
"customer": {
"hsid": "b313c1d1-b5b6-4ec7-8b5a-3a9cf7755060",
"firstName": "Jane",
"lastName": "Smith",
"email": "jane.smith@example.com"
},
"paymentAllocations": null,
"paymentDateUtc": "2026-04-14T10:30:00Z",
"metadata": {
"orderId": "enrollment-67890",
"channel": "web"
}
}
}
paymentAllocations is null in this response. Child payment records are not created until after the customer is provisioned and processing resumes.
202 Accepted β After Customer Creation (GET)β
Once the customer is provisioned and processing resumes, a GET /v2/payments/{paymentId} reflects the final result:
{
"url": "https://api-stg.uhg.com/api/financial/commerce/nonprodcheckout/v2/payments/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"merchantTransactionId": "order-20260414-001",
"amount": 15000,
"authorizedAmount": 15000,
"capturedAmount": 15000,
"currencyCode": "USD",
"status": "COMPLETED",
"customer": {
"hsid": "b313c1d1-b5b6-4ec7-8b5a-3a9cf7755060",
"firstName": "Jane",
"lastName": "Smith",
"email": "jane.smith@example.com"
},
"paymentAllocations": [
{
"id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
"amount": 15000,
"authorizedAmount": 15000,
"capturedAmount": 15000,
"status": "COMPLETED",
"paymentMethod": {
"type": "CARD",
"card": {
"lastFour": "4242",
"brand": "visa",
"expMonth": 12,
"expYear": 2028
}
},
"vendor": {
"name": "STRIPE",
"transactionId": "pi_3NabcXYZ123456789"
}
}
],
"paymentDateUtc": "2026-04-14T10:30:00Z"
}
}
Internal Implementationβ
V2 Customer Detection β CustomerCheckCommand
The CustomerCheckCommand (post-processing command in the transaction pipeline) determines whether the customer exists:
| Payment Flow | Customer Found | Behavior |
|---|---|---|
USER_INITIATED_PAYMENT | β Found | Enriches transaction with customer data; proceeds normally |
USER_INITIATED_PAYMENT | β Not Found | Transaction saved as PENDING_FOR_CUSTOMER_CREATION; orchestration skipped |
MERCHANT_INITIATED_PAYMENT | β Found | Enriches transaction; proceeds normally |
MERCHANT_INITIATED_PAYMENT | β Not Found | Synchronous findOrCreate β customer is created inline before proceeding |
GUEST_PAYMENT | N/A | Customer lookup skipped entirely |
For user-initiated flows, customer creation is asynchronous β the system parks the transaction and waits for a CustomerUpdated event before resuming.
Resumption β CustomerConsumer
When wallet-customer-service finishes provisioning a customer, it publishes a CustomerUpdated event to the Azure Event Grid. The CustomerConsumer in wallet-payment-service handles this event for both V1 and V2:
CustomerUpdated (Azure Event Grid)
ββ> CustomerConsumer.consume()
ββ> handleV1Payments() β resumes cc_payment rows (V1 legacy path)
ββ> handleV2Transactions() β resumes cc_transaction rows (V2 path)
ββ> handleRefunds() β resumes waiting refunds
For V2 transactions, handleV2Transactions():
- Queries
CC_TRANSACTIONfor rows inPENDING_FOR_CUSTOMER_CREATIONmatching the customer. - Calls
transaction.resumeOnCustomerFound(customerEvent)to enrich customer data and transition toPROCESSING. - Saves the updated transaction.
- Re-triggers
TransactionAttemptOrchestrator.createTransactionAttempt()to create child payments and begin processing.
Idempotency While Pending
If the same merchantTransactionId is retried while the transaction is in PENDING_FOR_CUSTOMER_CREATION, TransactionService.isTransactionInProgress() detects the in-progress state and passes through without creating a duplicate record:
private boolean isTransactionInProgress(Transaction transaction) {
return transaction.isProcessing() ||
PaymentStatus.PENDING_FOR_CUSTOMER_CREATION.equals(transaction.getStatus()) ||
PaymentStatus.PENDING_FOR_PAYMENT_METHOD_CREATION.equals(transaction.getStatus());
}
No duplicate customer record is created and the original transaction continues processing once customer creation completes.
No Side Effects While Pending
While the transaction is in PENDING_FOR_CUSTOMER_CREATION, the following are explicitly not created or triggered:
- No
CC_PAYMENTchild rows - No wallet or payment method records
- No authorization or capture calls to the vendor (Stripe)
- No
PaymentMethodCreatedevents
The TransactionAttemptOrchestrator is only invoked after the CustomerUpdated event is received and the transaction transitions to PROCESSING.
Validation Rulesβ
| Rule | HTTP Status | Error |
|---|---|---|
customer object is required for non-guest flows | 400 | INVALID_REQUEST |
customer must include at least one of hsid, enterpriseIdentifier, or metadata | 400 | INVALID_REQUEST β customer object must include at least one of... |
customer.email must be a valid email address format | 400 | INVALID_REQUEST |
customer.dateOfBirth must be in YYYY-MM-DD format | 400 | INVALID_REQUEST |
customer.ssnLastFour must be exactly 4 digits | 400 | INVALID_REQUEST |
customer.zip5 must be exactly 5 digits | 400 | INVALID_REQUEST |
Guest payments (GUEST_PAYMENT) do not require a customer object | β | Validation skipped |
Error Scenariosβ
400 β Missing or Invalid Customer Objectβ
{
"title": "INVALID_REQUEST",
"status": 400,
"detail": "customer object must include at least one of customer.hsid, customer.enterpriseIdentifier, or customer.metadata"
}
422 β Customer Creation Failed (Webhook)β
When customer creation fails in wallet-customer-service, the transaction is marked FAILED. Merchants can observe this via GET /v2/payments/{paymentId} or the PAYMENT_FAILED webhook.
{
"title": "PAYMENT_ERROR",
"status": 422,
"detail": "Payment processing failed. Customer creation failed β payment cannot proceed.",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"merchantTransactionId": "order-20260414-001",
"amount": 15000,
"status": "FAILED",
"paymentAllocations": null,
"error": {
"code": "CUSTOMER_CREATION_FAILED",
"message": "Customer creation failed β payment cannot proceed."
}
}
}
Webhook Eventsβ
| Event | When Emitted | Notes |
|---|---|---|
PAYMENT_UPDATED (status: PENDING_FOR_CUSTOMER_CREATION) | Transaction enters pending state | Emitted by wallet-payment-service after saving; dataVersion: "2.0.0" |
PAYMENT_UPDATED (status: PROCESSING) | Customer created; processing resumes | Emitted when customer event is consumed and transaction re-enters processing |
PAYMENT_SUCCEEDED | All allocations complete | Standard completion event |
PAYMENT_FAILED | Customer creation failed or payment processing failed | Terminal state |
Edge Casesβ
| Scenario | Behavior | Notes |
|---|---|---|
Retry with same merchantTransactionId while pending | Returns existing transaction in PENDING_FOR_CUSTOMER_CREATION | Idempotent β no duplicate customer or payment created |
| Customer creation fails in vendor system | Transaction transitions to FAILED | Terminal; no automatic retry; merchant must submit new request |
| Customer already exists at time of event | CustomerCheckCommand finds customer on first pass; normal flow proceeds without pausing | Common when customer is created by a concurrent request |
| Split-tender with new customer | Both allocations created only after customer is provisioned | Same pausing behavior as single payment; all allocations start after resume |
| Customer event arrives before transaction is persisted | Transaction not found in PENDING_FOR_CUSTOMER_CREATION query; event is effectively no-op | Rare race condition; merchant may need to retry the original request |
V1 vs V2 Comparisonβ
| Capability | V1 | V2 |
|---|---|---|
| New customer detection | customerClient.searchCustomer() in command handler | CustomerCheckCommand (post-processing command) |
| Pending state entity | CC_PAYMENT row set to PENDING_FOR_CUSTOMER_CREATION | CC_TRANSACTION row set to PENDING_FOR_CUSTOMER_CREATION |
| Child payments created while pending | No (guarded in handleExistingPayment()) | No (guarded in TransactionService.triggerOrchestration()) |
| Resumption trigger | CustomerConsumer.handlePayments() | CustomerConsumer.handleV2Transactions() |
| Customer lookup on resume | PaymentRepository.findByCustomerIdAndPaymentStatus() | TransactionRepository.findByCustomerIdAndStatus() |
| Failure handling | handlePaymentError() β FAILED | transaction.failOnCustomerCreationFailure() β FAILED |
| Merchant-initiated synchronous create | findOrCreateCustomer() inline | CustomerCheckCommand.findOrCreateCustomer() inline |
| Split-tender support | Not applicable (V1 is single-payment) | β Supported |