Checking access…

Skip to main content
Version: v2

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.

RELATED DOCUMENTATION
KEY RULES
  • Customer creation is supported only when a valid customer object 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_TRANSACTION level β€” no child CC_PAYMENT rows are created until the customer is provisioned.
  • Customer creation failure is terminal: the transaction transitions to FAILED and no retries are attempted automatically.

When This Flow Applies​

ConditionBehavior
customer object present, customer existsNormal payment flow β€” no pause
customer object present, customer does not existTransaction 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​

TERMINAL STATE

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​

info

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:

  • hsid
  • enterpriseIdentifier
  • metadata (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"
}
}
}
note

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 FlowCustomer FoundBehavior
USER_INITIATED_PAYMENTβœ… FoundEnriches transaction with customer data; proceeds normally
USER_INITIATED_PAYMENT❌ Not FoundTransaction saved as PENDING_FOR_CUSTOMER_CREATION; orchestration skipped
MERCHANT_INITIATED_PAYMENTβœ… FoundEnriches transaction; proceeds normally
MERCHANT_INITIATED_PAYMENT❌ Not FoundSynchronous findOrCreate β€” customer is created inline before proceeding
GUEST_PAYMENTN/ACustomer 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():

  1. Queries CC_TRANSACTION for rows in PENDING_FOR_CUSTOMER_CREATION matching the customer.
  2. Calls transaction.resumeOnCustomerFound(customerEvent) to enrich customer data and transition to PROCESSING.
  3. Saves the updated transaction.
  4. 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_PAYMENT child rows
  • No wallet or payment method records
  • No authorization or capture calls to the vendor (Stripe)
  • No PaymentMethodCreated events

The TransactionAttemptOrchestrator is only invoked after the CustomerUpdated event is received and the transaction transitions to PROCESSING.


Validation Rules​

RuleHTTP StatusError
customer object is required for non-guest flows400INVALID_REQUEST
customer must include at least one of hsid, enterpriseIdentifier, or metadata400INVALID_REQUEST β€” customer object must include at least one of...
customer.email must be a valid email address format400INVALID_REQUEST
customer.dateOfBirth must be in YYYY-MM-DD format400INVALID_REQUEST
customer.ssnLastFour must be exactly 4 digits400INVALID_REQUEST
customer.zip5 must be exactly 5 digits400INVALID_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​

EventWhen EmittedNotes
PAYMENT_UPDATED (status: PENDING_FOR_CUSTOMER_CREATION)Transaction enters pending stateEmitted by wallet-payment-service after saving; dataVersion: "2.0.0"
PAYMENT_UPDATED (status: PROCESSING)Customer created; processing resumesEmitted when customer event is consumed and transaction re-enters processing
PAYMENT_SUCCEEDEDAll allocations completeStandard completion event
PAYMENT_FAILEDCustomer creation failed or payment processing failedTerminal state

Edge Cases​

ScenarioBehaviorNotes
Retry with same merchantTransactionId while pendingReturns existing transaction in PENDING_FOR_CUSTOMER_CREATIONIdempotent β€” no duplicate customer or payment created
Customer creation fails in vendor systemTransaction transitions to FAILEDTerminal; no automatic retry; merchant must submit new request
Customer already exists at time of eventCustomerCheckCommand finds customer on first pass; normal flow proceeds without pausingCommon when customer is created by a concurrent request
Split-tender with new customerBoth allocations created only after customer is provisionedSame pausing behavior as single payment; all allocations start after resume
Customer event arrives before transaction is persistedTransaction not found in PENDING_FOR_CUSTOMER_CREATION query; event is effectively no-opRare race condition; merchant may need to retry the original request

V1 vs V2 Comparison​

CapabilityV1V2
New customer detectioncustomerClient.searchCustomer() in command handlerCustomerCheckCommand (post-processing command)
Pending state entityCC_PAYMENT row set to PENDING_FOR_CUSTOMER_CREATIONCC_TRANSACTION row set to PENDING_FOR_CUSTOMER_CREATION
Child payments created while pendingNo (guarded in handleExistingPayment())No (guarded in TransactionService.triggerOrchestration())
Resumption triggerCustomerConsumer.handlePayments()CustomerConsumer.handleV2Transactions()
Customer lookup on resumePaymentRepository.findByCustomerIdAndPaymentStatus()TransactionRepository.findByCustomerIdAndStatus()
Failure handlinghandlePaymentError() β†’ FAILEDtransaction.failOnCustomerCreationFailure() β†’ FAILED
Merchant-initiated synchronous createfindOrCreateCustomer() inlineCustomerCheckCommand.findOrCreateCustomer() inline
Split-tender supportNot applicable (V1 is single-payment)βœ… Supported