Skip to main content
Version: v2

Local to Enterprise Wallet Merge

Legend: Class / Handler Method Field / Config Key Event API Endpoint DB Table / Column
Quick Reference
  • Wallet Merge: Merges local wallets to existing enterprise wallets during findOrCreateCustomer (POST /customers/find)
  • Triggered by: TransferPaymentMethodCommandHandler when a golden record is found for a customer that also has a local wallet
  • Payment Handling: De-duplicates using vendorPaymentMethodFingerprint; transfers unique; updates or discards duplicates based on modifiedTs
  • Bank Accounts: Only ACTIVE bank accounts are transferred; INVALIDATED ones are deleted immediately
  • Migration Tracking: CustomerMigration entity tracks state (IN_PROGRESSCOMPLETED or FAILED)
  • Benefits: Preserves payment history, creates seamless cross-merchant experience

Overview

The Local to Enterprise Wallet Merge functionality provides a critical upgrade path within the Convenient Checkout Gateway (CCG) platform. This process is triggered automatically during the findOrCreateCustomer flow (POST /customers/find) when the system identifies that a customer has both a local wallet and a matching enterprise identity.

The merge is orchestrated by TransferPaymentMethodCommandHandler (invoked from CreateCustomerCommandHandler.findCustomerByGoldenRecord()) and PaymentMethodDeDupeCommandHandler for handling duplicate payment methods.

Key advantages of this functionality include:

  • Identity Unification: Combining fragmented customer identities
  • Payment Method Consolidation: Preserving and consolidating payment methods
  • Cross-Merchant Experience: Enabling wallet use across multiple merchants
  • Data Continuity: Maintaining transaction history and preferences
Architecture Diagram
Merge Process

A wallet merge happens when a local wallet needs upgrading to an enterprise wallet, facilitating unified identity. The process is implemented primarily in TransferPaymentMethodCommandHandler.

Process Flow

  1. Detect Merge Trigger (identifyLocalToEnterpriseMerge)

    • During findOrCreateCustomer, after a golden record is found, the system checks if an EnterpriseCustomer exists for the resolved customer.
    • If an enterprise customer exists, it searches for a local customer in the same merchant using customerQueryHandler.findLocalCustomer(merchantId, metadata).
    • Decision: Is there a matching active enterprise wallet and a separate active local wallet?
    • If the local customer ID equals the enterprise customer ID, the merge is skipped (already the same customer).
    Merge Trigger Scenarios — Same Merchant, Same Group, Different Groups

    Merge/upgrade can be triggered across different merchant and group configurations. For a full breakdown of when and how this trigger fires — including same-merchant precedence groups, same merchant group, different merchant groups, and why enterprise-only merchants never trigger a merge — see Merge / Upgrade Summary in the Enterprise Wallet Upgradation guide.

  2. Check for Existing or Failed Migration (resumeFailedMigration)

    • Queries CustomerMigrationRepository.findByMerchantCustomerIdAndEnterpriseCustomerId().
    • If a migration exists with status COMPLETED or IN_PROGRESS, throws CustomerMigrationAlreadyExistException (silently ignored via onErrorComplete).
    • If a migration exists with status FAILED, resumes it by setting status back to IN_PROGRESS.
  3. Create Migration Record (createMigrationRecord)

    • If no migration exists, creates a new CustomerMigration entity with:
      • merchantCustomerId: the local customer's ID
      • enterpriseCustomerId: the enterprise customer's ID
      • merchantId: from the X-Merchant-Id header
      • vendorMerchantCustomerId and vendorEnterpriseCustomerId: fetched from VendorCustomerRepository
      • status: IN_PROGRESS
  4. Deactivate Local Customer (markMerchantCustomerAsInactive)

    • Marks the local customer as inactive using CustomerUtil.updatedCustomerToInactive() and saves it.
    • Retains the record for audit/history.
  5. Identify and Transfer Payment Methods (getLocalAndEnterprisePaymentMethods)

    • Fetches all payment methods for both the local customer and enterprise customer in parallel via Mono.zipDelayError().
    • If the local customer has no payment methods, throws NoPaymentMethodsAvailableException and marks the migration as COMPLETED.
    • Filters out INVALIDATED bank accounts — these are deleted immediately via paymentMethodDeDupeCommandHandler.deleteLocalWallet().
    • Special handling for payment method types:
      • Cards: All valid cards are eligible for transfer.
      • ACH/Bank Accounts: Only ACTIVE accounts are transferred; INVALIDATED ones are deleted.
  6. De-duplicate Payment Methods (identifyNonUniquePaymentMethods / identifyUniquePaymentMethodsInMerchantCustomer)

    • Payment methods are categorized using their vendorPaymentMethodFingerprint:
      • Unique (fingerprint exists only in local wallet): Eligible for transfer to enterprise wallet.
      • Non-unique (fingerprint exists in both wallets): Handled by PaymentMethodDeDupeCommandHandler.
    • For non-unique (duplicate) payment methods:
      • Compares modifiedTs timestamps of the local and enterprise versions.
      • If the local wallet's payment method is more recent: Updates the enterprise payment method with local details (card: nameOnCard, expiryMonth, expiryYear, zipCode; ACH: accountType, nameOnAccount, status) and then deletes the local version.
      • If the enterprise wallet's payment method is more recent or equal: Retains the enterprise version and deletes the local version without updating.
    • For unique payment methods:
      • Builds a TransferPaymentMethodsEvent containing source (local) and destination (enterprise) customer details plus payment method transfer details.
  7. Publish Events

    • For each unique payment method being transferred: publishes TRANSFER_PAYMENT_METHODS_EVENT containing migration ID, source customer, destination customer, and payment method details.
    • For each non-unique payment method where local is newer and enterprise is updated: publishes PAYMENT_METHOD_UPDATED event with migration metadata (source: CCG_WALLET_MERGE, id: migrationId).
    • For each deleted local payment method (both unique and non-unique): publishes PAYMENT_METHOD_REPLACED event with the enterprise payment method data but the local customer's ID.
  8. Finalize Migration

    • If no more unique payment methods remain after de-duplication, marks migration as COMPLETED.
    • Otherwise, migration completes asynchronously after the TRANSFER_PAYMENT_METHODS_EVENT is processed.
  9. Error Handling & Recovery

  • If any step fails, migration status remains IN_PROGRESS or is set to FAILED by error handlers.
  • CustomerMigrationAlreadyExistException is silently completed via onErrorComplete — no error propagated to the caller.
  • Failed migrations can be retried on the next findOrCreateCustomer call for the same customer.
caution

When payment methods are deleted as part of the merge process, PAYMENT_METHOD_DELETED events are not sent to the merchant. Instead, PAYMENT_METHOD_REPLACED is published (pointing to the enterprise version).

Migration States

The CustomerMigration entity (stored in the customer_migration table) tracks the following states:

StatusDescriptionBehavior on Next findOrCreateCustomer Call
IN_PROGRESSMigration started but not completedSkipped via CustomerMigrationAlreadyExistException
COMPLETEDAll payment methods successfully transferredSkipped via CustomerMigrationAlreadyExistException
FAILEDError occurred during migrationResumed — status reset to IN_PROGRESS and process retried

CustomerMigration Entity Fields

FieldTypeDescription
idUUIDAuto-generated migration record ID
merchantCustomerIdUUIDThe local customer's wallet ID
enterpriseCustomerIdUUIDThe enterprise customer's wallet ID
merchantIdUUIDThe merchant ID from the request
vendorMerchantCustomerIdStringStripe customer ID for the local customer
vendorEnterpriseCustomerIdStringStripe customer ID for the enterprise customer
statusCustomerMigrationStatusCurrent migration state
errorErrorResponse (JSON)Error details if migration failed
Testing
  • Basic Merge:

    • Local wallet with unique payment methods is merged into an enterprise wallet.
    • Verify all payment methods and history are preserved.
  • Duplicate Payment Methods:

    • Both local and enterprise wallets have the same card or bank account.
    • Ensure deduplication logic retains only the most recent/valid method.
  • Mixed Payment Types:

    • Local wallet has cards, enterprise wallet has ACH/bank accounts (and vice versa).
    • Confirm all valid payment methods are transferred and properly categorized.
  • Failed Migration and Retry:

    • Simulate a migration failure (e.g., network error, data conflict).
    • Ensure migration can be retried and completes successfully without data loss.
  • Inactive Local Customer:

    • After merge, local customer should be marked inactive and not usable for new transactions.
  • Event Publishing:

    • Confirm that all relevant events (e.g., PAYMENT_METHOD_REPLACED) are published and received by downstream systems.
  • Edge Cases:

    • Local wallet with no payment methods.
    • Enterprise wallet already contains all local payment methods.
    • Large number of payment methods (stress test).
  • Audit and History:

    • Verify that transaction and payment history is preserved and accessible post-merge.
  • Orphan/Residual Data:

    • Ensure no orphaned or duplicate payment methods or customer records remain after merge.