MPoC SDK
Getting Started
Quickstart Guide

MPoC SDK QuickStart

🧑🏽‍💻

You can find the example project here via github (opens in a new tab)

Add the MineSec's registry

To get the MineSec's package, setup the credential and registry in your project:

~/.gradle/gradle.properties
# minesec client registry
MINESEC_REGISTRY_LOGIN=minesec-product-support
MINESEC_REGISTRY_TOKEN={token-value}

Please request to our customer support for the token.

Then in your project's settings gradle, setup the MineSec's registry as the following:

settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
 
        // MineSec's maven registry
        maven {
            val MINESEC_REGISTRY_LOGIN: String? by settings
            val MINESEC_REGISTRY_TOKEN: String? by settings
 
            requireNotNull(MINESEC_REGISTRY_LOGIN) {
                """
                    Please set your MineSec Github credential in `gradle.properties`.
                    On local machine,
                    ** DO NOT **
                    ** DO NOT **
                    ** DO NOT **
                    Do not put it in the project's file. (and accidentally commit and push)
                    ** DO **
                    Do set it in your machine's global (~/.gradle/gradle.properties)
                """.trimIndent()
            }
            requireNotNull(MINESEC_REGISTRY_TOKEN)
            println("MS GPR: $MINESEC_REGISTRY_LOGIN")
 
            name = "MineSecMavenClientRegistry"
            url = uri("https://maven.pkg.github.com/theminesec/ms-registry-client")
            credentials {
                username = MINESEC_REGISTRY_LOGIN
                password = MINESEC_REGISTRY_TOKEN
            }
        }
    }
}

Add dependency in app gradle

In your app's build.gradle.kts, add the following dependency of the headless sdk:

app/build.gradle.kts
dependencies {
    // ... other deps
 
    // ++
    releaseImplementation("com.theminesec.sdk.mpoc:core-mpoc:2.01.001.010.025")
    // ++ for debug SDK, you can declare as debugImplementation
    debugImplementation("com.theminesec.sdk.mpoc:core-mpoc-stage:2.01.001.010.025")
}

Note that there are 2 versions:

  • core-mpoc: it is connected to prod environment, with all security feature enabled.
  • core-mpoc-stage: it is for developing and debugging, note some checking might be loosen with this variant.

Exclude some resource file

In the app's build.gradle.kts, also make sure the following packaging option exist (this is by default included in a new android project):

app/build.gradle.kts
android {
  // ...
  packaging {
    jniLibs.keepDebugSymbols.add("**/*.so")
 
    resources {
      excludes += "/META-INF/{AL2.0,LGPL2.1}"
 
      excludes += "/META-INF/DEPENDENCIES"
      excludes += "/META-INF/LICENSE"
      excludes += "/META-INF/LICENSE.txt"
      excludes += "/META-INF/license.txt"
      excludes += "/META-INF/NOTICE"
      excludes += "/META-INF/NOTICE.txt"
      excludes += "/META-INF/notice.txt"
      excludes += "/META-INF/ASL2.0"
      excludes += "/META-INF/*.kotlin_module"
    }
  }
  //...
  configurations {
    all {
      exclude("bcprov-jdk15on")
      exclude("bcpkix-jdk15on")
      exclude("jetified-bcprov-jdk15on-1.56")
      exclude("jetified-bcpkix-jdk15on-1.56")
      exclude("bcprov-jdk18on")
      exclude("bcpkix-jdk18on")
      exclude("jetified-bcprov-jdk18on-1.77")
      exclude("jetified-bcpkix-jdk18on-1.77")
      exclude("META-INF/versions/9")
    }
  }
}

Put the .license file to your project

Place the your-license-file.license in the app module's ./src/main/assets respectively

Please request to our customer support for the license if you haven't got one.

      • your-license-file.license
  • Initialize SDK

    In your application, you should initialize the MPoC SDK before doing any card reading operations.:

    ClientProcess.kt
    fun initSdk() = viewModelScope.launch {
      // start initialization process
      MPoCAPI.initSdk(licenseName, app) { result ->
        when (result) {
          is MPoCResult.Success -> {
            writeMessage("init result success")
            val sdkInitResult = result.data
          }
     
          is MPoCResult.Failure -> {
            writeMessage("init fails " + result.code)
            writeMessage("init fails message " + result.message)
            writeMessage("init fail contextual " + result.contextual)
          }
     
        }
     
      }
    }
     
    /**
    below is the definition of sdkInitResult
    public final data class SdkInitResult(
    isRegistered: Boolean,
    sdkId: String,
    sdkBuildModel: String,
    sdkVersion: String,
    licenseId: String,
    customerId: String,
    serverUrl: String,
    kekCert: String,
    signCert: String
    )
     */

    Request a transaction

    To make a new sale request is fairly straight forward:

    From your other activity or fragment wish to launch the request, create a activity launcher for the result:

    MainActivity.kt
     launcher = registerForActivityResult(
      MineSecPaymentActivity.contract(MineSecPaymentActivity::class.java)
    ) {
      when (it) {
        is MPoCResult.Success -> {
          Log.d("${it.javaClass.simpleName} \n" + gson.toJson(it))
        }
        is MPoCResult.Failure -> {
          Log.d("transaction fails ${it.code} ${it.message} contextu=${it.contextual}")
        }
      }
    }

    The above launcher would print out whatever the result got back from the MineSecPaymentActivity.

    Then let's create another function to launch the Sale request:

    MainActivity.kt
      fun launchSale() {
      val validAmount = 1000  // Your input amount, it should be a "long" type
      if (validAmount == null || validAmount <= 0) {
        Log.d("Invalid amount. Transaction cannot proceed.")
        return
      }
      val transactionDto = MhdEmvTransactionDto(
        txnAmount = validAmount,
        txnCurrencyText = "USD",
        timeout = 80
      )
      // with customized PIN entry UI
      // NOTE: this is optional, you can use MineSec default PIN Pad style
      val pinPadConfig = PinPadConfig(
        pinPadStyle = PinPadStyle(
          backgroundColor = Color.LightGray.toArgb(),
          amountStyle = getOswaldBold30(this@MainActivity),
          digitStyle = getOswaldBold30(this@MainActivity),
          descriptionStyle = getOswaldBold30(this@MainActivity),
          buttonsColor = ButtonsColor(
            abortBtn = Color.DarkGray.toArgb(),
            confirmBtn = Color.Blue.toArgb(),
            clearBtn = Color.Red.toArgb(),
          )
        ),
        // Choose the PIN PAD entry mode
        pinEntryMode = PinEntryMode.SHUFFLE,
        pinEntryDetails = PinEntryDetails(
          // This message will be display on PIN Activity if PIN is required
          pinDescription = "Your PIN Please!",
     
          // this will be displayed on PIN PAD
          amount = "100.75 USD",
     
          // this is an additional message displayed under amount in PIN PAD
          description = "Please pay MineSec monthly invoice",
     
          // enable/disable PINBypass
          supportPINBypass = false
        )
      )
      val transactionDtoWithCustomizedPinPadUI = MhdEmvTransactionDto(
        txnAmount = sdkViewModel.amount.toLong(),
        txnCurrencyText = "USD",
        pinpadConfig = gson.toJson(pinPadConfig)
      )
    //        launcher.launch(transactionDtoWithCustomizedPinPadUI)
      launcher.launch(transactionDto)
    }

    Note PIN PAD style is an optional configuration allows you to customize the PIN PAD display settings.

    Wire it up

    Now to wire it all up, let's consider this:

    • When the app starts, in the application it'll do the init SDK, attestation will be performed in the background.
    • Then, launch a sale from the main activity, this request will be passed to the MineSecPaymentActivity
    MainActivity.kt
    import kotlinx.coroutines.flow.first
     
    class MainActivity : AppCompatActivity() {
        private val  launcher = registerForActivityResult(
          MineSecPaymentActivity.contract(MineSecPaymentActivity::class.java)
        ) {
          when (it) {
            is MPoCResult.Success -> {
              Log.d("${it.javaClass.simpleName} \n" + gson.toJson(it))
            }
            is MPoCResult.Failure -> {
              Log.d("transaction fails ${it.code} ${it.message} contextu=${it.contextual}")
            }
          }
        }
     
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launcher = registerForActivityResult(
          MineSecPaymentActivity.contract(MineSecPaymentActivity::class.java)
        ) {
          when (it) {
            is MPoCResult.Success -> {
              sdkViewModel.writeMessage("${it.javaClass.simpleName} \n" + gson.toJson(it))
            }
     
            is MPoCResult.Failure -> {
              sdkViewModel.writeMessage("transaction fails ${it.code} ${it.message} contextu=${it.contextual}")
            }
          }
        }
     
        setContent {
          //.....
        }
     
        lifecycleScope.launch {
          val result = registerSdk()
          if (result.isRegistered) {
            sdkViewModel.amount = "10000"
            payment()
          }
        }
     
      }
     
      private val licenseName = "your_licens_file.license"
     
      @SuppressLint("LogNotTimber")
      suspend fun registerSdk() = suspendCoroutine { continuation ->
        MPoCAPI.initSdk(licenseName,this.application) {
          when (it) {
            is MPoCResult.Failure -> {
              Log.d(
                "TAG",
                "sdk registration fails ${it.code} ${it.message} context=${it.contextual}"
              )
              continuation.resumeWith(Result.failure<SdkInitResult>(Exception(it.contextual)))
     
            }
     
            is MPoCResult.Success -> {
              Log.d("TAG", "register success $it")
              continuation.resumeWith(Result.success<SdkInitResult>(it.data))
            }
     
          }
        }
      }
     
      private fun payment() {
        val validAmount = sdkViewModel.amount.toLongOrNull()
        if (validAmount == null || validAmount <= 0) {
          sdkViewModel.writeMessage("Invalid amount. Transaction cannot proceed.")
          return
        }
        val transactionDto = MhdEmvTransactionDto(
          txnAmount = sdkViewModel.amount.toLong(),
          txnCurrencyText = "USD",
          timeout = 80
        )
        // with customized PIN entry UI
        val pinPadConfig = PinPadConfig(
          pinPadStyle = PinPadStyle(
            backgroundColor = Color.LightGray.toArgb(),
            amountStyle = getOswaldBold30(this@MainActivity),
            digitStyle = getOswaldBold30(this@MainActivity),
            descriptionStyle = getOswaldBold30(this@MainActivity),
            buttonsColor = ButtonsColor(
              abortBtn = Color.DarkGray.toArgb(),
              confirmBtn = Color.Blue.toArgb(),
              clearBtn = Color.Red.toArgb(),
            )
          ),
          pinEntryMode = PinEntryMode.SHUFFLE,
          pinEntryDetails = PinEntryDetails(
            pinDescription = "Your PIN Please!",
            amount = "100.75 USD",
            description = "Please pay MineSec monthly invoice",
            supportPINBypass = false
          )
        )
        val transactionDtoWithCustomizedPinPadUI = MhdEmvTransactionDto(
          txnAmount = sdkViewModel.amount.toLong(),
          txnCurrencyText = "USD",
          pinpadConfig = gson.toJson(pinPadConfig)
        )
    //        launcher.launch(transactionDtoWithCustomizedPinPadUI)
        launcher.launch(transactionDto)
      }
    }

    Note: You MUST call initSdk() before any operation, otherwise it will fail

    Run the app and you should be able to see:

    Hello