Checking access…

Skip to main content
Version: v2

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.

RELATED DOCUMENTATION

Payment Flow Types​

The paymentFlowType field on the transaction determines how a missing customer is handled:

Flow typeCustomer not foundBehaviour
USER_INITIATED_PAYMENTPark and waitTransaction transitions to PENDING_FOR_CUSTOMER_CREATION; orchestration is skipped until the customer is created
MERCHANT_INITIATED_PAYMENTCreate inlineCustomer is created synchronously (find-or-create) before the payment continues
GUEST_PAYMENTN/ACustomer 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_PAYMENT flow 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 to PENDING_FOR_CUSTOMER_CREATION, saves it to the database, publishes a wallet.payment.updated event, and sets CUSTOMER_PENDING_CREATION_KEY = true in the shared context.
  • MERCHANT_INITIATED_PAYMENT + customer not found: calls customerClient.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_KEY flag β€” catches the initial request where the in-flight transaction was just transitioned to PENDING_FOR_CUSTOMER_CREATION in 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:

  1. Calls transaction.resumeOnCustomerFound(customerEvent) β€” updates customerId, enriches transaction details.
  2. Saves the updated transaction.
  3. Calls TransactionAttemptOrchestrator.createTransactionAttempt(...) to resume the payment pipeline.
  4. On orchestration failure: calls transaction.failOnCustomerCreationFailure(), saves FAILED state, publishes failure event.

Customer creation failed:

  1. Calls transaction.failOnCustomerCreationFailure() β€” sets status = FAILED with a standard error response.
  2. Saves the FAILED transaction.
  3. Publishes a wallet.payment.updated failure event.

Idempotency​

If the same payment request is retried while the transaction is already in PENDING_FOR_CUSTOMER_CREATION:

  1. handleDuplicateTransactionScenario detects the existing record and reuses its transactionId.
  2. Post-processing commands are re-run against the existing transaction.
  3. isPendingForCustomerCreation() returns true, so orchestration is skipped again.
  4. No duplicate customer or transaction records are created.

Error Handling​

ScenarioBehaviour
Customer not found, USER_INITIATED_PAYMENTTransaction parks in PENDING_FOR_CUSTOMER_CREATION; caller receives a successful PENDING response
Customer not found, MERCHANT_INITIATED_PAYMENTCustomer 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 failedTransaction transitions to FAILED (terminal); failure event published; no child payments created; no automatic retries
Attempt creation fails after customer createdTransaction transitions to FAILED; failure event published
Gateway timeout during customer lookupGATEWAY_TIMEOUT TransactionCommandException; request rejected

Events​

EventDirectionWhen
wallet.payment.updated (status: PENDING_FOR_CUSTOMER_CREATION)wallet-payment-service β†’ downstreamTransaction parked waiting for customer
WALLET_CUSTOMER_UPDATEDwallet-customer-service β†’ wallet-payment-serviceCustomer provisioning completed (success or failure)
wallet.payment.updated (status: PROCESSING/COMPLETED)wallet-payment-service β†’ downstreamPayment resumed and child attempt created successfully
wallet.payment.updated (status: FAILED)wallet-payment-service β†’ downstreamCustomer 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_payment rows) are handled by a separate path in CustomerConsumer.handleV1Payments and are not affected by this flow.
  • No wallet, payment method, or PaymentMethodCreated events are emitted while the transaction is in PENDING_FOR_CUSTOMER_CREATION.