package com.upipayment import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.net.Uri import android.view.View import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.facebook.react.bridge.* import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter import com.google.android.material.bottomsheet.BottomSheetDialog class UpiPaymentModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), PaymentModeAdapter.OnItemClick, ActivityEventListener { init { reactContext.addActivityEventListener(this); } private val MODULE_NAME = "UpiPayment" private val UPI_PAYMENT_REQUEST_CODE = 1001 private val paymentModeAdapter by lazy { PaymentModeAdapter(this) } private var promise: Promise? = null private var paymentUri: Uri? = null private var bottomSheet: BottomSheetDialog? = null private var options: ReadableMap? = null override fun getName(): String { return MODULE_NAME } @ReactMethod fun initWithParams(params: ReadableMap, options: ReadableMap?, promise: Promise) { this.promise = promise this.options = options paymentUri = Uri.Builder().scheme("upi").authority("pay") .appendQueryParameter("pa", getValueFromReadableMap(params, "receiverUpi")) .appendQueryParameter("mc", getValueFromReadableMap(params, "merchantCode")) .appendQueryParameter("pn", getValueFromReadableMap(params, "pn")) .appendQueryParameter("tn", getValueFromReadableMap(params, "note")) .appendQueryParameter("tr", getValueFromReadableMap(params, "txnRef")) .appendQueryParameter("am", params.getDouble("amount").toString()) .appendQueryParameter("cu", getValueFromReadableMap(params, "currency")).build() currentActivity?.runOnUiThread { showPaymentApps() } } @ReactMethod fun payWithUri(uri: String?, options: ReadableMap?, promise: Promise) { if (!isValidUpiUri(uri)) return promise.reject("401", "Invalid payment uri $uri") this.promise = promise this.options = options paymentUri = Uri.parse(uri) currentActivity?.runOnUiThread { showPaymentApps() } } @ReactMethod fun isAppInstalled(packageName: String?, promise: Promise) { if (packageName.isNullOrEmpty()) { return promise.reject("404", "No App installed") } try { reactApplicationContext.packageManager.getPackageInfo(packageName, 0) } catch (e: PackageManager.NameNotFoundException) { promise.reject("Not installed", "app not found") return } promise.resolve(true) } @ReactMethod fun fetchUpiApps(options: ReadableMap?, promise: Promise) { this.options = options val reactContext: ReactContext = reactApplicationContext val activity: Activity? = reactContext.currentActivity if (activity != null) { val apps = getUpiApps(activity) val writableMap = Arguments.createArray() apps.forEach { val readableMap = Arguments.createMap() readableMap.putString("packageName", it.packageName) readableMap.putString("appName", it.appName) writableMap.pushMap(readableMap) } promise.resolve(writableMap) } else { promise.reject("Activity Instance error", "Activity is null") } } private fun showPaymentApps() { val reactContext: ReactContext = reactApplicationContext val activity: Activity? = reactContext.currentActivity if (activity != null) { val apps = getUpiApps(activity) bottomSheet = BottomSheetDialog(activity) bottomSheet?.setContentView(R.layout.payment_modes) bottomSheet?.setOnCancelListener { promise?.reject("canceled", "Payment Canceled By User") } if (options?.hasKey("cancelable") == true) { bottomSheet?.setCancelable(options?.getBoolean("cancelable") == true) } if (options?.hasKey("cancelOnTouchOutside") == true) { bottomSheet?.setCanceledOnTouchOutside(options?.getBoolean("cancelOnTouchOutside") == true) } val rvPaymentModesList = bottomSheet?.findViewById(R.id.rvPaymentModesList) val tvNoAppAvailable = bottomSheet?.findViewById(R.id.tvNoAppAvailable) rvPaymentModesList?.adapter = paymentModeAdapter if (apps.isEmpty()) { tvNoAppAvailable?.visibility = View.VISIBLE rvPaymentModesList?.visibility = View.GONE } else { tvNoAppAvailable?.visibility = View.GONE rvPaymentModesList?.visibility = View.VISIBLE paymentModeAdapter.setList(apps) } bottomSheet?.show() } } private fun getUpiApps(activity: Activity): ArrayList { val list = ArrayList() // UPI intent for querying val uri = Uri.Builder().scheme("upi").authority("pay").build() val upiPayIntent = Intent(Intent.ACTION_VIEW).apply { data = uri } // Get blacklisted apps val blockedApps = getBlackListedApps() // Querying UPI-supported apps val resolveInfoList: List = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { activity.packageManager.queryIntentActivities(upiPayIntent, PackageManager.ResolveInfoFlags.of(0)) } else { @Suppress("DEPRECATION") activity.packageManager.queryIntentActivities(upiPayIntent, PackageManager.MATCH_DEFAULT_ONLY) } for (resolveInfo in resolveInfoList) { try { val info = resolveInfo.activityInfo val appIcon = activity.packageManager.getApplicationIcon(info.packageName) val appName = activity.packageManager.getApplicationLabel( activity.packageManager.getApplicationInfo(info.packageName, PackageManager.GET_META_DATA) ).toString() val mode = PaymentModeModal().apply { this.icon = appIcon this.appName = appName this.packageName = info.packageName } // Add to list if not blacklisted if (!blockedApps.contains(mode.packageName)) { list.add(mode) } } catch (e: Exception) { e.printStackTrace() } } return list } override fun onItemClick(packageName: String) { val eventData = Arguments.createMap() eventData.putString("packageName", packageName) eventData.putString("paymentUri", paymentUri.toString()) sendEvent("UpiAppSelected", eventData) if (bottomSheet != null && bottomSheet?.isShowing == true) { bottomSheet?.dismiss() } processPayment(packageName) } override fun onActivityResult( activity: Activity?, requestCode: Int, resultCode: Int, intent: Intent? ) { if (resultCode == Activity.RESULT_CANCELED) { promise?.reject("Canceled", "User canceled ") return } when (requestCode) { UPI_PAYMENT_REQUEST_CODE -> { val response = intent?.getStringExtra("response") promise?.resolve(response) } } } override fun onNewIntent(intent: Intent?) { } private fun isValidUpiUri(uri: String?): Boolean { if (uri.isNullOrEmpty()) return false return uri.contains("://pay") } private fun getValueFromReadableMap(readableMap: ReadableMap, key: String): String { if (readableMap.hasKey(key)) { return readableMap.getString(key).toString() } return "" } private fun sendEvent( eventName: String, params: WritableMap ) { reactApplicationContext.getJSModule(RCTDeviceEventEmitter::class.java).emit(eventName, params) } private fun processPayment(packageName: String?) { if (paymentUri.toString().trim().isEmpty()) { promise?.reject("401", "Invalid Payment uri") return; } val upiPayIntent = Intent(Intent.ACTION_VIEW) upiPayIntent.data = paymentUri upiPayIntent.setPackage(packageName) try { reactApplicationContext.startActivityForResult(upiPayIntent, UPI_PAYMENT_REQUEST_CODE, null) } catch (e: Exception) { promise?.reject("404", "App not installed") } } private fun getBlackListedApps(): ArrayList { val blackListedApps = ArrayList() if (options?.hasKey("blockedApps") == true) { val apps = options?.getArray("blockedApps") ?: return blackListedApps for (i in 0 until apps.size()) { val appPackage = apps.getString(i) blackListedApps.add(appPackage) } } return blackListedApps } }