Skip to main content
EMV Level 2 contactless payment acceptance SDK for Android.

Overview

This SDK enables Android devices with NFC capability to accept contactless payments. It implements the EMV contactless specifications for card reading, authentication, and cryptogram generation.
Note: Production use requires EMV L2 certification from Visa, Mastercard, and other card networks.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    AtlasSoftPos (SDK Facade)                │
├─────────────────────────────────────────────────────────────┤
│                      Entry Point                            │
│         (PPSE Selection, AID Matching, Kernel Routing)      │
├──────────────┬──────────────┬──────────────┬───────────────┤
│ Visa Kernel  │   MC Kernel  │ Amex Kernel  │ Discover      │
│  (payWave)   │  (PayPass)   │ (ExpressPay) │  (D-PAS)      │
├──────────────┴──────────────┴──────────────┴───────────────┤
│                    Core Components                          │
│        TLV Parser  │  APDU Framework  │  Crypto Utils      │
├─────────────────────────────────────────────────────────────┤
│                  NFC Card Reader                            │
│              (Android NFC / IsoDep)                         │
└─────────────────────────────────────────────────────────────┘

Requirements

  • Android 8.0+ (API 26+)
  • NFC capability (IsoDep support)
  • For production: EMVCo L1 certified NFC antenna
  • Samsung Galaxy Tab Active Pro (EMVCo L1 certified)
  • Samsung Galaxy Tab Active 3/4
  • Purpose-built payment devices

Installation

dependencies {
    implementation 'com.atlas:softpos-sdk:1.0.0'
}

Basic Usage

class PaymentActivity : AppCompatActivity() {

    private lateinit var softPos: AtlasSoftPos

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialize SDK
        softPos = AtlasSoftPos.Builder(this)
            .setMerchantId("MERCHANT123456789")
            .setTerminalId("TERM0001")
            .setCvmRequiredLimit(2500)  // $25.00 CVM limit
            .build()
    }

    fun startPayment(amountCents: Long) {
        softPos.startTransaction(
            amount = amountCents,
            type = TransactionType.PURCHASE,
            callback = object : TransactionCallback {
                override fun onWaitingForCard() {
                    showUI("Tap card to pay")
                }

                override fun onCardDetected() {
                    showUI("Processing...")
                }

                override fun onResult(result: TransactionResult) {
                    when (result) {
                        is TransactionResult.OnlineRequired -> {
                            // Send to acquirer
                            sendToAcquirer(result.authorizationRequest)
                        }
                        is TransactionResult.Approved -> {
                            showUI("Approved")
                        }
                        is TransactionResult.Declined -> {
                            showUI("Declined: ${result.reason}")
                        }
                        is TransactionResult.Error -> {
                            showUI("Error: ${result.message}")
                        }
                    }
                }

                override fun onError(error: TransactionError) {
                    showUI("Error: $error")
                }

                override fun onCancelled() {
                    showUI("Cancelled")
                }
            }
        )
    }

    override fun onResume() {
        super.onResume()
        softPos.onResume()
    }

    override fun onPause() {
        super.onPause()
        softPos.onPause()
    }
}

Authorization Request

The AuthorizationRequest object contains all EMV data needed for online authorization:
data class AuthorizationRequest(
    val pan: String,                    // Primary Account Number
    val track2Equivalent: String,       // Track 2 data
    val expiryDate: String,            // YYMM format
    val applicationCryptogram: String,  // AC (ARQC/TC)
    val cryptogramInfoData: String,     // Cryptogram type indicator
    val atc: String,                    // Application Transaction Counter
    val issuerApplicationData: String,  // IAD
    val terminalVerificationResults: String,  // TVR
    val cvmResults: String,            // CVM Results
    val amountAuthorized: String,      // Amount
    val transactionDate: String,       // YYMMDD
    val unpredictableNumber: String,   // UN
    val aip: String,                   // Application Interchange Profile
    val aid: String,                   // Application Identifier
)
Send this to your payment processor/acquirer for authorization.

Supported Card Networks

NetworkStatusKernel
Visa✅ ImplementedqVSDC
Mastercard🚧 In ProgressPayPass M/Chip
American Express🚧 PlannedExpressPay
Discover🚧 PlannedD-PAS
JCB🚧 PlannedJ/Speedy

EMV Certification Path

To deploy in production:
  1. Visa Ready Tap to Phone - Apply at partner.visa.com
  2. Mastercard TQM - Terminal Quality Management certification
  3. PCI MPoC - Mobile Payments on COTS security evaluation
  4. EMVCo L1 - Device-level NFC certification

Testing

Unit Tests

./gradlew :softpos-sdk:test

Test Cards

Use EMV test cards from:
  • Visa VTS (Visa Test System)
  • Mastercard MTIP
  • Physical test cards from card network certification programs

Security Considerations

  • Card data is processed in memory only
  • PAN is never stored on device
  • Cryptograms are single-use
  • All sensitive data is cleared after transaction

Integration with Atlas

Send the authorization request to Atlas for processing:
override fun onResult(result: TransactionResult) {
    when (result) {
        is TransactionResult.OnlineRequired -> {
            // Send to Atlas API
            val response = atlasApi.processAuthorization(
                sessionId = currentSession.id,
                authRequest = result.authorizationRequest
            )

            // Complete transaction based on response
            softPos.completeTransaction(response.approved)
        }
    }
}