V2: Payment with Customer Creation
Overviewβ
V2 supports payment flows where the customer does not yet exist at the time the payment is initiated. When a new-customer payment is detected, the transaction is paused in a PENDING_FOR_CUSTOMER_CREATION state until the customer is provisioned by the wallet-customer-service. Once customer creation is confirmed, payment processing resumes automatically and child payment attempts are created as normal.
This behaviour applies to both single-payment and split-tender transactions. Guest payments are excluded β they bypass customer resolution entirely.
- Customer Creation Process β how customers are provisioned end-to-end
- Pay with wallet β the standard V2 payment flow without new-customer creation
Payment Flow Typesβ
The paymentFlowType field on the transaction determines how a missing customer is handled:
| Flow type | Customer not found | Behaviour |
|---|---|---|
USER_INITIATED_PAYMENT | Park and wait | Transaction transitions to PENDING_FOR_CUSTOMER_CREATION; orchestration is skipped until the customer is created |
MERCHANT_INITIATED_PAYMENT | Create inline | Customer is created synchronously (find-or-create) before the payment continues |
GUEST_PAYMENT | N/A | Customer lookup is skipped entirely |
State Machineβ
PENDING (initial)
β
βΌ [CustomerCheckCommand β customer not found, USER_INITIATED flow]
PENDING_FOR_CUSTOMER_CREATION
β
βββΆ [WALLET_CUSTOMER_UPDATED event received, status = FAILED]
β βββΆ FAILED (terminal β no child payments created)
β
βββΆ [WALLET_CUSTOMER_UPDATED event received, status = ACTIVE/CREATED]
βββΆ transaction.resumeOnCustomerFound(...)
βββΆ TransactionAttemptOrchestrator.createTransactionAttempt(...)
βββΆ success β PROCESSING / COMPLETED (normal flow)
βββΆ failure β FAILED (terminal β failure event published)
Flow Diagramβ
Key Componentsβ
CustomerCheckCommand (pre-processing)β
Runs as part of the transaction pre-processing command chain. Responsible for resolving the customer before any child payment attempts are created.
- Guest payments (
GUEST_PAYMENTflow type): skips customer lookup and continues. - Missing customer object: rejects the request immediately with a validation error.
USER_INITIATED_PAYMENT+ customer not found: transitions the transaction toPENDING_FOR_CUSTOMER_CREATION, saves it to the database, publishes awallet.payment.updatedevent, and setsCUSTOMER_PENDING_CREATION_KEY = truein the shared context.MERCHANT_INITIATED_PAYMENT+ customer not found: callscustomerClient.createCustomer(...)synchronously (find-or-create) and continues the payment flow without parking.
TransactionService.triggerOrchestrationβ
After post-processing commands complete, orchestration is gated by two checks:
if (transaction.isPendingForCustomerCreation()
|| Boolean.TRUE.equals(context.get(CUSTOMER_PENDING_CREATION_KEY))) {
// skip orchestration β no child payments created yet
return Mono.just(transaction);
}
Both conditions serve different scenarios:
isPendingForCustomerCreation()β catches idempotent retries where the existing DB record is already in the pending state.CUSTOMER_PENDING_CREATION_KEYflag β catches the initial request where the in-flight transaction was just transitioned toPENDING_FOR_CUSTOMER_CREATIONin the same request lifecycle.
CustomerConsumer.handleV2Transactionsβ
Listens for WALLET_CUSTOMER_UPDATED events from wallet-customer-service. On receipt, queries the database for all V2 transactions in PENDING_FOR_CUSTOMER_CREATION for the given customerId.
Customer creation succeeded:
- Calls
transaction.resumeOnCustomerFound(customerEvent)β updatescustomerId, enriches transaction details. - Saves the updated transaction.
- Calls
TransactionAttemptOrchestrator.createTransactionAttempt(...)to resume the payment pipeline. - On orchestration failure: calls
transaction.failOnCustomerCreationFailure(), saves FAILED state, publishes failure event.
Customer creation failed:
- Calls
transaction.failOnCustomerCreationFailure()β setsstatus = FAILEDwith a standard error response. - Saves the FAILED transaction.
- Publishes a
wallet.payment.updatedfailure event.
Idempotencyβ
If the same payment request is retried while the transaction is already in PENDING_FOR_CUSTOMER_CREATION:
handleDuplicateTransactionScenariodetects the existing record and reuses itstransactionId.- Post-processing commands are re-run against the existing transaction.
isPendingForCustomerCreation()returnstrue, so orchestration is skipped again.- No duplicate customer or transaction records are created.
Error Handlingβ
| Scenario | Behaviour |
|---|---|
Customer not found, USER_INITIATED_PAYMENT | Transaction parks in PENDING_FOR_CUSTOMER_CREATION; caller receives a successful PENDING response |
Customer not found, MERCHANT_INITIATED_PAYMENT | Customer is created synchronously; payment continues normally |
| Missing or null customer object (non-guest flow) | Pre-processing command returns TransactionCommandException; request rejected with 400 validation error; transaction never enters PENDING_FOR_CUSTOMER_CREATION |
WALLET_CUSTOMER_UPDATED received, customer creation failed | Transaction transitions to FAILED (terminal); failure event published; no child payments created; no automatic retries |
| Attempt creation fails after customer created | Transaction transitions to FAILED; failure event published |
| Gateway timeout during customer lookup | GATEWAY_TIMEOUT TransactionCommandException; request rejected |
Eventsβ
| Event | Direction | When |
|---|---|---|
wallet.payment.updated (status: PENDING_FOR_CUSTOMER_CREATION) | wallet-payment-service β downstream | Transaction parked waiting for customer |
WALLET_CUSTOMER_UPDATED | wallet-customer-service β wallet-payment-service | Customer provisioning completed (success or failure) |
wallet.payment.updated (status: PROCESSING/COMPLETED) | wallet-payment-service β downstream | Payment resumed and child attempt created successfully |
wallet.payment.updated (status: FAILED) | wallet-payment-service β downstream | Customer creation failed or attempt creation failed after resume |
All V2 events carry dataVersion: "2.0.0".
Constraints and Out-of-Scope Itemsβ
- Guest payments do not create customers and are not in scope.
- Customer updates or profile management beyond what is needed to resolve the payment are not handled here.
- Manual retries are not supported β resumption is fully automatic on
WALLET_CUSTOMER_UPDATED. - V1 payments (
cc_paymentrows) are handled by a separate path inCustomerConsumer.handleV1Paymentsand are not affected by this flow. - No wallet, payment method, or
PaymentMethodCreatedevents are emitted while the transaction is inPENDING_FOR_CUSTOMER_CREATION.