Android: Secure & Efficient Communication Between SDK & Application - Best Practices

50 Views Asked by At

Question:

I am developing a payment SDK in Android, which will be integrated into various consumer apps, including third-party applications for example Amazon. The SDK's primary function is to redirect users from the consumer app to my payment application securely, process payments, and then return the transaction status to the consumer app. I am using Kotlin and Jetpack Compose for this project.

Here's a high-level overview of the process:

  1. The consumer app initiates a payment using my SDK, which includes data such as the transaction amount and merchant ID.
  2. My SDK launches my payment application with this data, which is hashed with an HMAC using a secret key for security.
  3. The payment application calls a backend API to perform the transaction.
  4. Once the transaction is complete, the payment application needs to notify the SDK about the success or failure of the transaction.

I have a couple of questions:

Launching the Payment Application: I plan to use Android Intents to launch my payment application with the required data. Is this the most secure and efficient way to achieve this, considering security and performance concerns?

Callback Mechanism: What is the recommended approach for the payment application to communicate the transaction status (success or failure) back to the SDK in the consumer app? Are there any best practices for implementing a secure and reliable callback mechanism in this context? Should I use a Broadcast Receiver, inter-process communication (IPC), or some other method? What are your opinions or recommendations regarding this choice?

I want to ensure that the integration between the consumer app and my SDK is both secure and efficient. Any guidance, best practices, or code examples related to above mentioned scenario would be greatly appreciated.

The SDK is pretty straightforward. Here is some of the code chuck.

class AbcPayCheckout private constructor(
    private val secretKey: String,
    private val merchantId: String,
    private val externalRefId: String,
    private val amount: Double,
    private val activityResultLauncher: ActivityResultLauncher<Intent>?,
    private val abcPayCheckoutResultListener: AbcPayCheckoutResultListener?
) {
    init {
        if (secretKey.isBlank()) {
            throw IllegalArgumentException(SECRET_KEY_EXCEPTION)
        }

        if (merchantId.isBlank()) {
            throw IllegalArgumentException(MERCHANT_ID_EXCEPTION)
        }

        if (externalRefId.isBlank()) {
            throw IllegalArgumentException(EXTERNAL_REFERENCE_ID_EXCEPTION)
        }

        if (amount == 0.0) {
            throw IllegalArgumentException(AMOUNT_EXCEPTION)
        }

        if (activityResultLauncher == null) {
            throw IllegalArgumentException(ACTIVITY_RESULT_LAUNCHER_EXCEPTION)
        }
    }

    class Builder {
        private var secretKey: String = ""
        private var merchantId: String = ""
        private var externalRefId: String = ""
        private var amount: Double = 0.0
        private var activityResultLauncher: ActivityResultLauncher<Intent>? = null
        private var abcPayCheckoutResultListener: AbcPayCheckoutResultListener? = null

        fun setSecretKey(secretKey: String) = apply {
            this.secretKey = secretKey
        }

        fun setMerchantId(merchantId: String) = apply {
            this.merchantId = merchantId
        }

        fun setExternalRefId(externalRefId: String) = apply {
            this.externalRefId = externalRefId
        }

        fun setAmount(amount: Double) = apply {
            this.amount = amount
        }

        fun setActivityResultLauncher(activityResultLauncher: ActivityResultLauncher<Intent>) = apply {
            this.activityResultLauncher = activityResultLauncher
        }

        fun setAbcPayCheckoutResultListener(abcPayCheckoutResultListener: AbcPayCheckoutResultListener) = apply {
            this.abcPayCheckoutResultListener = abcPayCheckoutResultListener
        }

        fun build(): AbcPayCheckout {
            return AbcPayCheckout(
                secretKey,
                merchantId,
                externalRefId,
                amount,
                activityResultLauncher,
                abcPayCheckoutResultListener
            )
        }
    }

    fun openAbcPayApp(context: Context) {
        val hashedData = getHashedData(
            secretKey,"${merchantId}-${amount.getFormattedPrice()}-${externalRefId}"
        )

        val packageManager = context.packageManager

        val isAbcPayInstalled = try {
            packageManager.getPackageInfo(ABC_PAY_APP_PACKAGE_NAME, PackageManager.GET_ACTIVITIES)
            true
        } catch (e: PackageManager.NameNotFoundException) {
            false
        }

        if (isAbcPayInstalled) {
            val intent = packageManager.getLaunchIntentForPackage(ABC_PAY_APP_PACKAGE_NAME)

            if (intent != null) {
                intent.putExtra(HASHED_DATA, hashedData)
                activityResultLauncher?.launch(intent)
            }
        } else {
            try {
                val intent = Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse("market://details?id=$ABC_PAY_APP_PACKAGE_NAME")
                )
                context.startActivity(intent)
            } catch (e: android.content.ActivityNotFoundException) {
                val intent = Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse("https://play.google.com/store/apps/details?id=$ABC_PAY_APP_PACKAGE_NAME")
                )
                context.startActivity(intent)
            }
        }
    }
}

Note: I'm the developer of both the SDK and the Payment App. So you can suggest accordingly.

Thank you for your assistance.

0

There are 0 best solutions below