SDK Tap To Phone

Ecart Pay Android SDK - Tap to Phone

Accept in-person card payments in your Android app using Tap to Phone technology. Enable your users to accept contactless payments by simply tapping the card on the device — no additional hardware required.


Prerequisites

RequirementMinimum Version
Android SDKminSdk 24 (Android 7.0)
Android StudioHedgehog or later
DeviceNFC-enabled with Tap to Phone support
⚠️

Important: The SDK requires a physical device with NFC support. Testing on emulators is not possible.


Get Your Credentials

To integrate the SDK, you need a pair of API keys specifically for Tap to Phone.

Steps to obtain credentials

  1. Log in to Ecart Pay
  2. In the sidebar menu, navigate to Integrations
  3. Select Dev Tools
  4. Click on Credentials
  5. Create new API Keys selecting the type SDK Tap To Phone
  6. Securely save your Public ID and Private ID
💡

Tip: You can create separate credentials for Sandbox (testing) and Production environments.

⚠️

Warning: Your Private ID is confidential. Never share it or include it in publicly visible code.


Installation

Add the Ecart Pay SDK to your project using Maven Central:

// build.gradle (app module)
dependencies {
    implementation 'com.ecartpay:tap-to-phone-sdk:1.0.1'
}

Make sure Maven Central is included in your repositories:

// settings.gradle
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Initial Setup

Initialize the SDK once, preferably in your Application class:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        EcartPaySDK.initialize(
            context = this,
            config = EcartPayConfig(
                publicId = "pk_live_your_public_id",
                privateId = "sk_live_your_private_id",
                environment = Environment.PRODUCTION
            )
        )
    }
}

Available Environments

EnvironmentUse CaseBase URL
Environment.SANDBOXTesting and developmentsandbox.ecartpay.com
Environment.PRODUCTIONLive transactionsecartpay.com

Quick Start

Set Up the Device (Recommended)

The setupDevice() method executes the entire setup process in a single call:

  1. Registers the device with Ecart Pay servers
  2. Enrolls the device with the payment provider (CyberSource)
EcartPaySDK.instance().setupDevice(activity = this) { result ->
    when (result) {
        is SetupResult.Ready -> {
            // ✅ Device ready to process payments
            Log.d("EcartPay", "Setup complete: ${result.deviceId}")
            enableChargeButton()
        }
        
        is SetupResult.TapToPayReadyRequired -> {
            // ⚠️ "Tap to Pay Ready" app installation required
            showInstallationDialog()
        }
        
        is SetupResult.Cancelled -> {
            // ℹ️ User cancelled the process
            Log.i("EcartPay", "Setup cancelled")
        }
        
        is SetupResult.RegistrationFailed -> {
            // ❌ Registration error
            Log.e("EcartPay", "Error: ${result.code} - ${result.message}")
            showError("Could not register device")
        }
        
        is SetupResult.EnrollmentFailed -> {
            // ❌ Registration succeeded but enrollment failed
            Log.e("EcartPay", "Enrollment error: ${result.message}")
            showError("Could not complete setup")
        }
    }
}

Install Tap to Pay Ready

When the SDK reports TapToPayReadyRequired, prompt the user to install the Visa companion app:

private fun showInstallationDialog() {
    AlertDialog.Builder(this)
        .setTitle("Installation Required")
        .setMessage("To accept card payments, you need to install Visa's \"Tap to Pay Ready\" application.")
        .setPositiveButton("Install") { _, _ ->
            EcartPaySDK.instance().openTapToPayReadyPlayStore()
        }
        .setNegativeButton("Cancel", null)
        .show()
}

Process a Payment

Once the device is set up, you can process payments in different ways:

Option A: Direct Charge (Simple Amount)

Ideal for quick charges where you don't need itemized products:

val request = PaymentRequest(
    amount = 150.00,
    currency = "MXN",
    email = "[email protected]",
    reference = "SALE-001"
)

EcartPaySDK.instance().startPayment(activity = this, request = request) { result ->
    when (result) {
        is PaymentResult.Approved -> {
            // ✅ Payment successful
            showReceipt(
                orderId = result.orderId,
                transactionId = result.transactionId,
                amount = result.amount,
                card = result.maskedCardNumber
            )
        }
        
        is PaymentResult.Declined -> {
            // ❌ Payment declined
            showError("Payment declined: ${result.reason}")
        }
        
        is PaymentResult.Cancelled -> {
            // ℹ️ User cancelled
            Log.i("EcartPay", "Payment cancelled")
        }
        
        is PaymentResult.Error -> {
            // ❌ Technical error
            Log.e("EcartPay", "Error: ${result.code} - ${result.message}")
            showError("An error occurred while processing the payment")
        }
    }
}

Option B: Itemized Charge

For sales with detailed products, useful for receipts and inventory:

val request = PaymentRequest(
    currency = "MXN",
    email = "[email protected]",
    firstName = "John",
    lastName = "Doe",
    phone = "5512345678",
    items = listOf(
        OrderItem(name = "Product A", price = 99.00, quantity = 2),
        OrderItem(name = "Product B", price = 150.00, quantity = 1)
    )
)

EcartPaySDK.instance().startPayment(activity = this, request = request) { result ->
    // Handle result...
}

Option C: Complete Charge (with Shipping)

For e-commerce with physical products:

val request = PaymentRequest(
    currency = "MXN",
    email = "[email protected]",
    firstName = "Jane",
    lastName = "Smith",
    phone = "5598765432",
    reference = "ORDER-2024-001",
    referenceId = "ORD-12345",
    notifyUrl = "https://your-server.com/webhooks/ecartpay",
    items = listOf(
        OrderItem(name = "Smartphone XYZ", price = 8999.00, quantity = 1)
    ),
    shippingAddress = ShippingAddress(
        firstName = "Jane",
        lastName = "Smith",
        address1 = "123 Main Street",
        address2 = "Apt 4B",
        city = "Mexico City",
        postalCode = "06600",
        countryCode = "MX",
        stateCode = "CDMX",
        phone = "5598765432"
    ),
    shippingItems = listOf(
        ShippingItem(
            name = "Express Shipping",
            amount = 150.00,
            carrier = "FEDEX"
        )
    )
)

Field Reference

PaymentRequest

FieldTypeRequiredDescription
amountDouble?When no itemsTotal amount to charge
currencyStringYesCurrency: "MXN" or "USD"
emailString?NoCustomer email
phoneString?NoCustomer phone
firstNameString?NoCustomer first name
lastNameString?NoCustomer last name
referenceString?NoInternal reference (e.g., invoice number)
referenceIdString?NoExternal ID from your system
notifyUrlString?NoWebhook notification URL
itemsList<OrderItem>?When no amountList of products
shippingItemsList<ShippingItem>?NoShipping options
shippingAddressShippingAddress?NoDelivery address

OrderItem

FieldTypeRequiredDescription
nameStringYesProduct name
priceDoubleYesUnit price (> 0)
quantityIntYesQuantity (≥ 1)

ShippingItem

FieldTypeRequiredDescription
nameStringYesShipping method name
amountDoubleYesCost (can be 0)
carrierStringYesCarrier (e.g., "FEDEX", "DHL")
trackingNumberString?NoTracking number

ShippingAddress

FieldTypeRequiredDescription
firstNameString?Yes*Recipient first name
lastNameString?NoRecipient last name
address1String?Yes*Primary address
address2String?NoSecondary address
address3String?NoAdditional address
cityString?NoCity
postalCodeString?NoPostal code
countryCodeString?Yes*ISO country code (e.g., "MX")
countryNameString?NoCountry name
stateCodeString?NoState code
stateNameString?NoState name
phoneString?NoContact phone
referenceString?NoDelivery instructions

*Required by backend when shippingAddress is included


Advanced Integration

Step-by-Step Setup

If you need more control over the flow (e.g., to show different UI states for each step), you can use the individual methods:

Step 1: Register the Device

EcartPaySDK.instance().registerDevice { result ->
    when (result) {
        is RegistrationResult.Success -> {
            Log.d("EcartPay", "Device registered: ${result.deviceId}")
            // Continue with enrollment
        }
        is RegistrationResult.Failure -> {
            Log.e("EcartPay", "Error: ${result.code} - ${result.message}")
        }
    }
}

Step 2: Enroll the Device

EcartPaySDK.instance().enrollDevice(activity = this) { result ->
    when (result) {
        is EnrollmentResult.Enrolled -> {
            Log.d("EcartPay", "Enrollment complete")
        }
        EnrollmentResult.TapToPayReadyRequired -> {
            // Show installation dialog
        }
        EnrollmentResult.Cancelled -> {
            Log.i("EcartPay", "Enrollment cancelled")
        }
        is EnrollmentResult.Failure -> {
            Log.e("EcartPay", "Error: ${result.code} - ${result.message}")
        }
    }
}

Check Device Status

val sdk = EcartPaySDK.instance()

// Is registered?
val isRegistered = sdk.isDeviceRegistered
val deviceId = sdk.deviceId

// Has provider credentials?
val hasCredentials = sdk.hasProviderCredentials

// Is enrolled?
val isEnrolled = sdk.isEnrollmentPersisted

// Is Tap to Pay Ready installed?
val isAppInstalled = sdk.isTapToPayReadyInstalled()

Reset the Device

To clear all local configuration (useful when signing out):

EcartPaySDK.instance().reset()

Network Monitoring (Debug)

For debugging, you can observe all HTTP requests:

EcartPaySDK.instance().setNetworkListener(object : NetworkListener {
    override fun onRequest(method: String, url: String, body: String?) {
        Log.d("HTTP", "→ $method $url")
        body?.let { Log.d("HTTP", it) }
    }

    override fun onResponse(method: String, url: String, statusCode: Int, body: String) {
        Log.d("HTTP", "← $method $url [$statusCode]")
    }

    override fun onError(method: String, url: String, error: Throwable) {
        Log.e("HTTP", "✕ $method $url - ${error.message}")
    }
})

// Disable when done
EcartPaySDK.instance().setNetworkListener(null)

Error Handling

CodeDescriptionSolution
NOT_REGISTEREDDevice not registeredCall setupDevice() or registerDevice()
NOT_ENROLLEDDevice not enrolledCall setupDevice() or enrollDevice()
TAP_TO_PAY_READY_REQUIREDVisa app not installedUse openTapToPayReadyPlayStore()
MISSING_PROVIDER_CREDENTIALSIncomplete credentialsContact Ecart Pay support
API_401Invalid credentialsVerify your publicId and privateId
API_400Invalid request dataCheck PaymentRequest fields

ProGuard / R8

If you use code shrinking, the SDK includes its own ProGuard rules. No additional configuration is required.


Support

Have questions or issues with integration?


Did this page help you?